This commit is contained in:
Soreepeong 2024-03-07 00:37:46 +09:00
parent 8a21fc721f
commit c326537f9f
6 changed files with 383 additions and 444 deletions

View file

@ -96,12 +96,6 @@ internal class DalamudCommands : IServiceType
ShowInHelp = false,
});
commandManager.AddHandler("/xlime", new CommandInfo(this.OnDebugDrawIMEPanel)
{
HelpMessage = Loc.Localize("DalamudIMEPanelHelp", "Draw IME panel"),
ShowInHelp = false,
});
commandManager.AddHandler("/xllog", new CommandInfo(this.OnOpenLog)
{
HelpMessage = Loc.Localize("DalamudDevLogHelp", "Open dev log DEBUG"),
@ -308,11 +302,6 @@ internal class DalamudCommands : IServiceType
dalamudInterface.ToggleDataWindow(arguments);
}
private void OnDebugDrawIMEPanel(string command, string arguments)
{
Service<DalamudInterface>.Get().OpenImeWindow();
}
private void OnOpenLog(string command, string arguments)
{
Service<DalamudInterface>.Get().ToggleLogWindow();

View file

@ -3,6 +3,7 @@ using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Numerics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
@ -17,6 +18,8 @@ using Dalamud.Interface.Utility;
using ImGuiNET;
using Serilog;
using TerraFX.Interop.Windows;
using static TerraFX.Interop.Windows.Windows;
@ -26,12 +29,21 @@ namespace Dalamud.Interface.Internal;
/// <summary>
/// This class handles CJK IME.
/// </summary>
[ServiceManager.BlockingEarlyLoadedService]
[ServiceManager.EarlyLoadedService]
internal sealed unsafe class DalamudIme : IDisposable, IServiceType
{
private const int CImGuiStbTextCreateUndoOffset = 0xB57A0;
private const int CImGuiStbTextUndoOffset = 0xB59C0;
private const int ImePageSize = 9;
private static readonly Dictionary<int, string> WmNames =
typeof(WM).GetFields(BindingFlags.Public | BindingFlags.Static)
.Where(x => x.IsLiteral && !x.IsInitOnly && x.FieldType == typeof(int))
.Select(x => ((int)x.GetRawConstantValue()!, x.Name))
.DistinctBy(x => x.Item1)
.ToDictionary(x => x.Item1, x => x.Name);
private static readonly UnicodeRange[] HanRange =
{
UnicodeRanges.CjkRadicalsSupplement,
@ -57,8 +69,41 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType
private static readonly delegate* unmanaged<ImGuiInputTextState*, StbTextEditState*, void> StbTextUndo;
[ServiceManager.ServiceDependency]
private readonly WndProcHookManager wndProcHookManager = Service<WndProcHookManager>.Get();
private readonly InterfaceManager interfaceManager;
private readonly ImGuiSetPlatformImeDataDelegate setPlatformImeDataDelegate;
/// <summary>The candidates.</summary>
private readonly List<string> candidateStrings = new();
/// <summary>The selected imm component.</summary>
private string compositionString = string.Empty;
/// <summary>The cursor position in screen coordinates.</summary>
private Vector2 cursorScreenPos;
/// <summary>The associated viewport.</summary>
private ImGuiViewportPtr associatedViewport;
/// <summary>The index of the first imm candidate in relation to the full list.</summary>
private CANDIDATELIST immCandNative;
/// <summary>The partial conversion from-range.</summary>
private int partialConversionFrom;
/// <summary>The partial conversion to-range.</summary>
private int partialConversionTo;
/// <summary>The cursor offset in the composition string.</summary>
private int compositionCursorOffset;
/// <summary>The input mode icon from <see cref="SeIconChar"/>.</summary>
private char inputModeIcon;
/// <summary>Undo range for modifying the buffer while composition is in progress.</summary>
private (int Start, int End, int Cursor)? temporaryUndoSelection;
[SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1003:Symbols should be spaced correctly", Justification = ".")]
@ -87,7 +132,17 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType
}
[ServiceManager.ServiceConstructor]
private DalamudIme() => this.setPlatformImeDataDelegate = this.ImGuiSetPlatformImeData;
private DalamudIme(InterfaceManager.InterfaceManagerWithScene imws)
{
Debug.Assert(ImGuiHelpers.IsImGuiInitialized, "IMWS initialized but IsImGuiInitialized is false?");
this.interfaceManager = imws.Manager;
this.setPlatformImeDataDelegate = this.ImGuiSetPlatformImeData;
ImGui.GetIO().SetPlatformImeDataFn = Marshal.GetFunctionPointerForDelegate(this.setPlatformImeDataDelegate);
this.interfaceManager.Draw += this.Draw;
this.wndProcHookManager.PreWndProc += this.WndProcHookManagerOnPreWndProc;
}
/// <summary>
/// Finalizes an instance of the <see cref="DalamudIme"/> class.
@ -109,7 +164,7 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType
/// <summary>
/// Gets a value indicating whether to display the cursor in input text. This also deals with blinking.
/// </summary>
internal static bool ShowCursorInInputText
private static bool ShowCursorInInputText
{
get
{
@ -126,63 +181,21 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType
}
}
/// <summary>
/// Gets the cursor position, in screen coordinates.
/// </summary>
internal Vector2 CursorPos { get; private set; }
/// <summary>
/// Gets the associated viewport.
/// </summary>
internal ImGuiViewportPtr AssociatedViewport { get; private set; }
/// <summary>
/// Gets the index of the first imm candidate in relation to the full list.
/// </summary>
internal CANDIDATELIST ImmCandNative { get; private set; }
/// <summary>
/// Gets the imm candidates.
/// </summary>
internal List<string> ImmCand { get; private set; } = new();
/// <summary>
/// Gets the selected imm component.
/// </summary>
internal string ImmComp { get; private set; } = string.Empty;
/// <summary>
/// Gets the partial conversion from-range.
/// </summary>
internal int PartialConversionFrom { get; private set; }
/// <summary>
/// Gets the partial conversion to-range.
/// </summary>
internal int PartialConversionTo { get; private set; }
/// <summary>
/// Gets the cursor offset in the composition string.
/// </summary>
internal int CompositionCursorOffset { get; private set; }
/// <summary>
/// Gets a value indicating whether to display partial conversion status.
/// </summary>
internal bool ShowPartialConversion => this.PartialConversionFrom != 0 ||
this.PartialConversionTo != this.ImmComp.Length;
/// <summary>
/// Gets the input mode icon from <see cref="SeIconChar"/>.
/// </summary>
internal char InputModeIcon { get; private set; }
private static ImGuiInputTextState* TextState =>
(ImGuiInputTextState*)(ImGui.GetCurrentContext() + ImGuiContextOffsets.TextStateOffset);
/// <summary>Gets a value indicating whether to display partial conversion status.</summary>
private bool ShowPartialConversion => this.partialConversionFrom != 0 ||
this.partialConversionTo != this.compositionString.Length;
/// <summary>Gets a value indicating whether to draw.</summary>
private bool ShouldDraw =>
this.candidateStrings.Count != 0 || this.ShowPartialConversion || this.inputModeIcon != default;
/// <inheritdoc/>
public void Dispose()
{
this.interfaceManager.Draw -= this.Draw;
this.ReleaseUnmanagedResources();
GC.SuppressFinalize(this);
}
@ -195,13 +208,13 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType
{
foreach (var chr in str)
{
if (HanRange.Any(x => x.FirstCodePoint <= chr && chr < x.FirstCodePoint + x.Length))
if (!this.EncounteredHan)
{
if (Service<FontAtlasFactory>.Get()
?.GetFdtReader(GameFontFamilyAndSize.Axis12)
.FindGlyph(chr) is null)
if (HanRange.Any(x => x.FirstCodePoint <= chr && chr < x.FirstCodePoint + x.Length))
{
if (!this.EncounteredHan)
if (Service<FontAtlasFactory>.Get()
?.GetFdtReader(GameFontFamilyAndSize.Axis12)
.FindGlyph(chr) is null)
{
this.EncounteredHan = true;
Service<InterfaceManager>.Get().RebuildFonts();
@ -209,9 +222,9 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType
}
}
if (HangulRange.Any(x => x.FirstCodePoint <= chr && chr < x.FirstCodePoint + x.Length))
if (!this.EncounteredHangul)
{
if (!this.EncounteredHangul)
if (HangulRange.Any(x => x.FirstCodePoint <= chr && chr < x.FirstCodePoint + x.Length))
{
this.EncounteredHangul = true;
Service<InterfaceManager>.Get().RebuildFonts();
@ -220,11 +233,24 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType
}
}
/// <summary>
/// Processes window messages.
/// </summary>
/// <param name="args">The arguments.</param>
public void ProcessImeMessage(WndProcEventArgs args)
private static string ImmGetCompositionString(HIMC hImc, uint comp)
{
var numBytes = ImmGetCompositionStringW(hImc, comp, null, 0);
if (numBytes == 0)
return string.Empty;
var data = stackalloc char[numBytes / 2];
_ = ImmGetCompositionStringW(hImc, comp, data, (uint)numBytes);
return new(data, 0, numBytes / 2);
}
private void ReleaseUnmanagedResources()
{
if (ImGuiHelpers.IsImGuiInitialized)
ImGui.GetIO().SetPlatformImeDataFn = nint.Zero;
}
private void WndProcHookManagerOnPreWndProc(WndProcEventArgs args)
{
if (!ImGuiHelpers.IsImGuiInitialized)
return;
@ -246,7 +272,7 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType
case WM.WM_IME_NOTIFY
when (nint)args.WParam is IMN.IMN_OPENCANDIDATE or IMN.IMN_CLOSECANDIDATE
or IMN.IMN_CHANGECANDIDATE:
this.UpdateImeWindowStatus(hImc);
this.UpdateCandidates(hImc);
args.SuppressWithValue(0);
break;
@ -260,22 +286,22 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType
else
this.ReplaceCompositionString(hImc, (uint)args.LParam);
// Log.Verbose($"{nameof(WM.WM_IME_COMPOSITION)}({(nint)args.LParam:X}): {this.ImmComp}");
// Log.Verbose($"{nameof(WM.WM_IME_COMPOSITION)}({(nint)args.LParam:X}): {this.compositionString}");
args.SuppressWithValue(0);
break;
case WM.WM_IME_ENDCOMPOSITION:
// Log.Verbose($"{nameof(WM.WM_IME_ENDCOMPOSITION)}({(nint)args.WParam:X}, {(nint)args.LParam:X}): {this.ImmComp}");
// Log.Verbose($"{nameof(WM.WM_IME_ENDCOMPOSITION)}({(nint)args.WParam:X}, {(nint)args.LParam:X}): {this.compositionString}");
args.SuppressWithValue(0);
break;
case WM.WM_IME_CONTROL:
// Log.Verbose($"{nameof(WM.WM_IME_CONTROL)}({(nint)args.WParam:X}, {(nint)args.LParam:X}): {this.ImmComp}");
// Log.Verbose($"{nameof(WM.WM_IME_CONTROL)}({(nint)args.WParam:X}, {(nint)args.LParam:X}): {this.compositionString}");
args.SuppressWithValue(0);
break;
case WM.WM_IME_REQUEST:
// Log.Verbose($"{nameof(WM.WM_IME_REQUEST)}({(nint)args.WParam:X}, {(nint)args.LParam:X}): {this.ImmComp}");
// Log.Verbose($"{nameof(WM.WM_IME_REQUEST)}({(nint)args.WParam:X}, {(nint)args.LParam:X}): {this.compositionString}");
args.SuppressWithValue(0);
break;
@ -283,12 +309,12 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType
// Hide candidate and composition windows.
args.LParam = (LPARAM)((nint)args.LParam & ~(ISC_SHOWUICOMPOSITIONWINDOW | 0xF));
// Log.Verbose($"{nameof(WM.WM_IME_SETCONTEXT)}({(nint)args.WParam:X}, {(nint)args.LParam:X}): {this.ImmComp}");
// Log.Verbose($"{nameof(WM.WM_IME_SETCONTEXT)}({(nint)args.WParam:X}, {(nint)args.LParam:X}): {this.compositionString}");
args.SuppressWithDefault();
break;
case WM.WM_IME_NOTIFY:
// Log.Verbose($"{nameof(WM.WM_IME_NOTIFY)}({(nint)args.WParam:X}): {this.ImmComp}");
// Log.Verbose($"{nameof(WM.WM_IME_NOTIFY)}({(nint)args.WParam:X}): {this.compositionString}");
break;
case WM.WM_KEYDOWN when (int)args.WParam is
@ -302,12 +328,14 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType
or VK.VK_RIGHT
or VK.VK_DOWN
or VK.VK_RETURN:
if (this.ImmCand.Count != 0)
if (this.candidateStrings.Count != 0)
{
this.ClearState(hImc);
args.WParam = VK.VK_PROCESSKEY;
}
this.UpdateCandidates(hImc);
break;
case WM.WM_LBUTTONDOWN:
@ -316,9 +344,15 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType
case WM.WM_XBUTTONDOWN:
ImmNotifyIME(hImc, NI.NI_COMPOSITIONSTR, CPS_COMPLETE, 0);
break;
// default:
// Log.Verbose($"{(WmNames.TryGetValue((int)args.Message, out var v) ? v : args.Message.ToString())}({(nint)args.WParam:X}, {(nint)args.LParam:X})");
// break;
}
this.UpdateInputLanguage(hImc);
if (this.inputModeIcon == (char)SeIconChar.ImeKoreanHangul)
this.UpdateCandidates(hImc);
}
finally
{
@ -326,23 +360,6 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType
}
}
private static string ImmGetCompositionString(HIMC hImc, uint comp)
{
var numBytes = ImmGetCompositionStringW(hImc, comp, null, 0);
if (numBytes == 0)
return string.Empty;
var data = stackalloc char[numBytes / 2];
_ = ImmGetCompositionStringW(hImc, comp, data, (uint)numBytes);
return new(data, 0, numBytes / 2);
}
private void ReleaseUnmanagedResources()
{
if (ImGuiHelpers.IsImGuiInitialized)
ImGui.GetIO().SetPlatformImeDataFn = nint.Zero;
}
private void UpdateInputLanguage(HIMC hImc)
{
uint conv, sent;
@ -359,41 +376,39 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType
{
case LANG.LANG_KOREAN:
if (native)
this.InputModeIcon = (char)SeIconChar.ImeKoreanHangul;
this.inputModeIcon = (char)SeIconChar.ImeKoreanHangul;
else if (fullwidth)
this.InputModeIcon = (char)SeIconChar.ImeAlphanumeric;
this.inputModeIcon = (char)SeIconChar.ImeAlphanumeric;
else
this.InputModeIcon = (char)SeIconChar.ImeAlphanumericHalfWidth;
this.inputModeIcon = (char)SeIconChar.ImeAlphanumericHalfWidth;
break;
case LANG.LANG_JAPANESE:
// wtf
// see the function called from: 48 8b 0d ?? ?? ?? ?? e8 ?? ?? ?? ?? 8b d8 e9 ?? 00 00 0
if (open && native && katakana && fullwidth)
this.InputModeIcon = (char)SeIconChar.ImeKatakana;
this.inputModeIcon = (char)SeIconChar.ImeKatakana;
else if (open && native && katakana)
this.InputModeIcon = (char)SeIconChar.ImeKatakanaHalfWidth;
this.inputModeIcon = (char)SeIconChar.ImeKatakanaHalfWidth;
else if (open && native)
this.InputModeIcon = (char)SeIconChar.ImeHiragana;
this.inputModeIcon = (char)SeIconChar.ImeHiragana;
else if (open && fullwidth)
this.InputModeIcon = (char)SeIconChar.ImeAlphanumeric;
this.inputModeIcon = (char)SeIconChar.ImeAlphanumeric;
else
this.InputModeIcon = (char)SeIconChar.ImeAlphanumericHalfWidth;
this.inputModeIcon = (char)SeIconChar.ImeAlphanumericHalfWidth;
break;
case LANG.LANG_CHINESE:
if (native)
this.InputModeIcon = (char)SeIconChar.ImeChineseHan;
this.inputModeIcon = (char)SeIconChar.ImeChineseHan;
else
this.InputModeIcon = (char)SeIconChar.ImeChineseLatin;
this.inputModeIcon = (char)SeIconChar.ImeChineseLatin;
break;
default:
this.InputModeIcon = default;
this.inputModeIcon = default;
break;
}
this.UpdateImeWindowStatus(hImc);
}
private void ReplaceCompositionString(HIMC hImc, uint comp)
@ -425,14 +440,14 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType
return;
}
this.ImmComp = newString;
this.CompositionCursorOffset = ImmGetCompositionStringW(hImc, GCS.GCS_CURSORPOS, null, 0);
this.compositionString = newString;
this.compositionCursorOffset = ImmGetCompositionStringW(hImc, GCS.GCS_CURSORPOS, null, 0);
if ((comp & GCS.GCS_COMPATTR) != 0)
{
var attrLength = ImmGetCompositionStringW(hImc, GCS.GCS_COMPATTR, null, 0);
var attrPtr = stackalloc byte[attrLength];
var attr = new Span<byte>(attrPtr, Math.Min(this.ImmComp.Length, attrLength));
var attr = new Span<byte>(attrPtr, Math.Min(this.compositionString.Length, attrLength));
_ = ImmGetCompositionStringW(hImc, GCS.GCS_COMPATTR, attrPtr, (uint)attrLength);
var l = 0;
while (l < attr.Length && attr[l] is not ATTR_TARGET_CONVERTED and not ATTR_TARGET_NOTCONVERTED)
@ -442,37 +457,37 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType
while (r < attr.Length && attr[r] is ATTR_TARGET_CONVERTED or ATTR_TARGET_NOTCONVERTED)
r++;
if (r == 0 || l == this.ImmComp.Length)
(l, r) = (0, this.ImmComp.Length);
if (r == 0 || l == this.compositionString.Length)
(l, r) = (0, this.compositionString.Length);
(this.PartialConversionFrom, this.PartialConversionTo) = (l, r);
(this.partialConversionFrom, this.partialConversionTo) = (l, r);
}
else
{
this.PartialConversionFrom = 0;
this.PartialConversionTo = this.ImmComp.Length;
this.partialConversionFrom = 0;
this.partialConversionTo = this.compositionString.Length;
}
this.UpdateImeWindowStatus(hImc);
this.UpdateCandidates(hImc);
}
private void ClearState(HIMC hImc)
{
this.ImmComp = string.Empty;
this.PartialConversionFrom = this.PartialConversionTo = 0;
this.CompositionCursorOffset = 0;
this.compositionString = string.Empty;
this.partialConversionFrom = this.partialConversionTo = 0;
this.compositionCursorOffset = 0;
this.temporaryUndoSelection = null;
TextState->Stb.SelectStart = TextState->Stb.Cursor = TextState->Stb.SelectEnd;
ImmNotifyIME(hImc, NI.NI_COMPOSITIONSTR, CPS_CANCEL, 0);
this.UpdateImeWindowStatus(default);
this.UpdateCandidates(default);
// Log.Information($"{nameof(this.ClearState)}");
}
private void LoadCand(HIMC hImc)
private void UpdateCandidates(HIMC hImc)
{
this.ImmCand.Clear();
this.ImmCandNative = default;
this.candidateStrings.Clear();
this.immCandNative = default;
if (hImc == default)
return;
@ -486,7 +501,7 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType
return;
ref var candlist = ref *(CANDIDATELIST*)pStorage;
this.ImmCandNative = candlist;
this.immCandNative = candlist;
if (candlist.dwPageSize == 0 || candlist.dwCount == 0)
return;
@ -495,39 +510,250 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType
(int)candlist.dwPageStart,
(int)Math.Min(candlist.dwCount - candlist.dwPageStart, candlist.dwPageSize)))
{
this.ImmCand.Add(new((char*)(pStorage + candlist.dwOffset[i])));
this.ReflectCharacterEncounters(this.ImmCand[^1]);
this.candidateStrings.Add(new((char*)(pStorage + candlist.dwOffset[i])));
this.ReflectCharacterEncounters(this.candidateStrings[^1]);
}
}
private void UpdateImeWindowStatus(HIMC hImc)
{
if (Service<DalamudInterface>.GetNullable() is not { } di)
return;
this.LoadCand(hImc);
if (this.ImmCand.Count != 0 || this.ShowPartialConversion || this.InputModeIcon != default)
di.OpenImeWindow();
else
di.CloseImeWindow();
}
private void ImGuiSetPlatformImeData(ImGuiViewportPtr viewport, ImGuiPlatformImeDataPtr data)
{
this.CursorPos = data.InputPos;
this.AssociatedViewport = data.WantVisible ? viewport : default;
this.cursorScreenPos = data.InputPos;
this.associatedViewport = data.WantVisible ? viewport : default;
}
[ServiceManager.CallWhenServicesReady("Effectively waiting for cimgui context initialization.")]
private void ContinueConstruction(InterfaceManager.InterfaceManagerWithScene interfaceManagerWithScene)
private void Draw()
{
if (!ImGuiHelpers.IsImGuiInitialized)
if (!this.ShouldDraw)
return;
if (Service<DalamudIme>.GetNullable() is not { } ime)
return;
var viewport = ime.associatedViewport;
if (viewport.NativePtr is null)
return;
var drawCand = ime.candidateStrings.Count != 0;
var drawConv = drawCand || ime.ShowPartialConversion;
var drawIme = ime.inputModeIcon != 0;
var imeIconFont = InterfaceManager.DefaultFont;
var pad = ImGui.GetStyle().WindowPadding;
var candTextSize = ImGui.CalcTextSize(ime.compositionString == string.Empty ? " " : ime.compositionString);
var native = ime.immCandNative;
var totalIndex = native.dwSelection + 1;
var totalSize = native.dwCount;
var pageStart = native.dwPageStart;
var pageIndex = (pageStart / ImePageSize) + 1;
var pageCount = (totalSize / ImePageSize) + 1;
var pageInfo = $"{totalIndex}/{totalSize} ({pageIndex}/{pageCount})";
// Calc the window size.
var maxTextWidth = 0f;
for (var i = 0; i < ime.candidateStrings.Count; i++)
{
throw new InvalidOperationException(
$"Expected {nameof(InterfaceManager.InterfaceManagerWithScene)} to have initialized ImGui.");
var textSize = ImGui.CalcTextSize($"{i + 1}. {ime.candidateStrings[i]}");
maxTextWidth = maxTextWidth > textSize.X ? maxTextWidth : textSize.X;
}
ImGui.GetIO().SetPlatformImeDataFn = Marshal.GetFunctionPointerForDelegate(this.setPlatformImeDataDelegate);
maxTextWidth = maxTextWidth > ImGui.CalcTextSize(pageInfo).X ? maxTextWidth : ImGui.CalcTextSize(pageInfo).X;
maxTextWidth = maxTextWidth > ImGui.CalcTextSize(ime.compositionString).X
? maxTextWidth
: ImGui.CalcTextSize(ime.compositionString).X;
var numEntries = (drawCand ? ime.candidateStrings.Count + 1 : 0) + 1 + (drawIme ? 1 : 0);
var spaceY = ImGui.GetStyle().ItemSpacing.Y;
var imeWindowHeight = (spaceY * (numEntries - 1)) + (candTextSize.Y * numEntries);
var windowSize = new Vector2(maxTextWidth, imeWindowHeight) + (pad * 2);
// 1. Figure out the expanding direction.
var expandUpward = ime.cursorScreenPos.Y + windowSize.Y > viewport.WorkPos.Y + viewport.WorkSize.Y;
var windowPos = ime.cursorScreenPos - pad;
if (expandUpward)
{
windowPos.Y -= windowSize.Y - candTextSize.Y - (pad.Y * 2);
if (drawIme)
windowPos.Y += candTextSize.Y + spaceY;
}
else
{
if (drawIme)
windowPos.Y -= candTextSize.Y + spaceY;
}
// 2. Contain within the viewport. Do not use clamp, as the target window might be too small.
if (windowPos.X < viewport.WorkPos.X)
windowPos.X = viewport.WorkPos.X;
else if (windowPos.X + windowSize.X > viewport.WorkPos.X + viewport.WorkSize.X)
windowPos.X = (viewport.WorkPos.X + viewport.WorkSize.X) - windowSize.X;
if (windowPos.Y < viewport.WorkPos.Y)
windowPos.Y = viewport.WorkPos.Y;
else if (windowPos.Y + windowSize.Y > viewport.WorkPos.Y + viewport.WorkSize.Y)
windowPos.Y = (viewport.WorkPos.Y + viewport.WorkSize.Y) - windowSize.Y;
var cursor = windowPos + pad;
// Draw the ime window.
var drawList = ImGui.GetForegroundDrawList(viewport);
// Draw the background rect for candidates.
if (drawCand)
{
Vector2 candRectLt, candRectRb;
if (!expandUpward)
{
candRectLt = windowPos + candTextSize with { X = 0 } + pad with { X = 0 };
candRectRb = windowPos + windowSize;
if (drawIme)
candRectLt.Y += spaceY + candTextSize.Y;
}
else
{
candRectLt = windowPos;
candRectRb = windowPos + (windowSize - candTextSize with { X = 0 } - pad with { X = 0 });
if (drawIme)
candRectRb.Y -= spaceY + candTextSize.Y;
}
drawList.AddRectFilled(
candRectLt,
candRectRb,
ImGui.GetColorU32(ImGuiCol.WindowBg),
ImGui.GetStyle().WindowRounding);
}
if (!expandUpward && drawIme)
{
for (var dx = -2; dx <= 2; dx++)
{
for (var dy = -2; dy <= 2; dy++)
{
if (dx != 0 || dy != 0)
{
imeIconFont.RenderChar(
drawList,
imeIconFont.FontSize,
cursor + new Vector2(dx, dy),
ImGui.GetColorU32(ImGuiCol.WindowBg),
ime.inputModeIcon);
}
}
}
imeIconFont.RenderChar(
drawList,
imeIconFont.FontSize,
cursor,
ImGui.GetColorU32(ImGuiCol.Text),
ime.inputModeIcon);
cursor.Y += candTextSize.Y + spaceY;
}
if (!expandUpward && drawConv)
{
DrawTextBeingConverted();
cursor.Y += candTextSize.Y + spaceY;
// Add a separator.
drawList.AddLine(cursor, cursor + new Vector2(maxTextWidth, 0), ImGui.GetColorU32(ImGuiCol.Separator));
}
if (drawCand)
{
// Add the candidate words.
for (var i = 0; i < ime.candidateStrings.Count; i++)
{
var selected = i == (native.dwSelection % ImePageSize);
var color = ImGui.GetColorU32(ImGuiCol.Text);
if (selected)
color = ImGui.GetColorU32(ImGuiCol.NavHighlight);
drawList.AddText(cursor, color, $"{i + 1}. {ime.candidateStrings[i]}");
cursor.Y += candTextSize.Y + spaceY;
}
// Add a separator
drawList.AddLine(cursor, cursor + new Vector2(maxTextWidth, 0), ImGui.GetColorU32(ImGuiCol.Separator));
// Add the pages infomation.
drawList.AddText(cursor, ImGui.GetColorU32(ImGuiCol.Text), pageInfo);
cursor.Y += candTextSize.Y + spaceY;
}
if (expandUpward && drawConv)
{
// Add a separator.
drawList.AddLine(cursor, cursor + new Vector2(maxTextWidth, 0), ImGui.GetColorU32(ImGuiCol.Separator));
DrawTextBeingConverted();
cursor.Y += candTextSize.Y + spaceY;
}
if (expandUpward && drawIme)
{
for (var dx = -2; dx <= 2; dx++)
{
for (var dy = -2; dy <= 2; dy++)
{
if (dx != 0 || dy != 0)
{
imeIconFont.RenderChar(
drawList,
imeIconFont.FontSize,
cursor + new Vector2(dx, dy),
ImGui.GetColorU32(ImGuiCol.WindowBg),
ime.inputModeIcon);
}
}
}
imeIconFont.RenderChar(
drawList,
imeIconFont.FontSize,
cursor,
ImGui.GetColorU32(ImGuiCol.Text),
ime.inputModeIcon);
}
return;
void DrawTextBeingConverted()
{
// Draw the text background.
drawList.AddRectFilled(
cursor - (pad / 2),
cursor + candTextSize + (pad / 2),
ImGui.GetColorU32(ImGuiCol.WindowBg));
// If only a part of the full text is marked for conversion, then draw background for the part being edited.
if (ime.partialConversionFrom != 0 || ime.partialConversionTo != ime.compositionString.Length)
{
var part1 = ime.compositionString[..ime.partialConversionFrom];
var part2 = ime.compositionString[..ime.partialConversionTo];
var size1 = ImGui.CalcTextSize(part1);
var size2 = ImGui.CalcTextSize(part2);
drawList.AddRectFilled(
cursor + size1 with { Y = 0 },
cursor + size2,
ImGui.GetColorU32(ImGuiCol.TextSelectedBg));
}
// Add the text being converted.
drawList.AddText(cursor, ImGui.GetColorU32(ImGuiCol.Text), ime.compositionString);
// Draw the caret inside the composition string.
if (DalamudIme.ShowCursorInInputText)
{
var partBeforeCaret = ime.compositionString[..ime.compositionCursorOffset];
var sizeBeforeCaret = ImGui.CalcTextSize(partBeforeCaret);
drawList.AddLine(
cursor + sizeBeforeCaret with { Y = 0 },
cursor + sizeBeforeCaret,
ImGui.GetColorU32(ImGuiCol.Text));
}
}
}
/// <summary>

View file

@ -61,7 +61,6 @@ internal class DalamudInterface : IDisposable, IServiceType
private readonly ComponentDemoWindow componentDemoWindow;
private readonly DataWindow dataWindow;
private readonly GamepadModeNotifierWindow gamepadModeNotifierWindow;
private readonly DalamudImeWindow imeWindow;
private readonly ConsoleWindow consoleWindow;
private readonly PluginStatWindow pluginStatWindow;
private readonly PluginInstallerWindow pluginWindow;
@ -114,7 +113,6 @@ internal class DalamudInterface : IDisposable, IServiceType
this.componentDemoWindow = new ComponentDemoWindow() { IsOpen = false };
this.dataWindow = new DataWindow() { IsOpen = false };
this.gamepadModeNotifierWindow = new GamepadModeNotifierWindow() { IsOpen = false };
this.imeWindow = new DalamudImeWindow() { IsOpen = false };
this.consoleWindow = new ConsoleWindow(configuration) { IsOpen = configuration.LogOpenAtStartup };
this.pluginStatWindow = new PluginStatWindow() { IsOpen = false };
this.pluginWindow = new PluginInstallerWindow(pluginImageCache, configuration) { IsOpen = false };
@ -142,7 +140,6 @@ internal class DalamudInterface : IDisposable, IServiceType
this.WindowSystem.AddWindow(this.componentDemoWindow);
this.WindowSystem.AddWindow(this.dataWindow);
this.WindowSystem.AddWindow(this.gamepadModeNotifierWindow);
this.WindowSystem.AddWindow(this.imeWindow);
this.WindowSystem.AddWindow(this.consoleWindow);
this.WindowSystem.AddWindow(this.pluginStatWindow);
this.WindowSystem.AddWindow(this.pluginWindow);
@ -265,11 +262,6 @@ internal class DalamudInterface : IDisposable, IServiceType
/// </summary>
public void OpenGamepadModeNotifierWindow() => this.gamepadModeNotifierWindow.IsOpen = true;
/// <summary>
/// Opens the <see cref="DalamudImeWindow"/>.
/// </summary>
public void OpenImeWindow() => this.imeWindow.IsOpen = true;
/// <summary>
/// Opens the <see cref="ConsoleWindow"/>.
/// </summary>
@ -365,11 +357,6 @@ internal class DalamudInterface : IDisposable, IServiceType
#region Close
/// <summary>
/// Closes the <see cref="DalamudImeWindow"/>.
/// </summary>
public void CloseImeWindow() => this.imeWindow.IsOpen = false;
/// <summary>
/// Closes the <see cref="GamepadModeNotifierWindow"/>.
/// </summary>
@ -417,11 +404,6 @@ internal class DalamudInterface : IDisposable, IServiceType
/// </summary>
public void ToggleGamepadModeNotifierWindow() => this.gamepadModeNotifierWindow.Toggle();
/// <summary>
/// Toggles the <see cref="DalamudImeWindow"/>.
/// </summary>
public void ToggleImeWindow() => this.imeWindow.Toggle();
/// <summary>
/// Toggles the <see cref="ConsoleWindow"/>.
/// </summary>

View file

@ -67,9 +67,6 @@ internal class InterfaceManager : IDisposable, IServiceType
[ServiceManager.ServiceDependency]
private readonly WndProcHookManager wndProcHookManager = Service<WndProcHookManager>.Get();
[ServiceManager.ServiceDependency]
private readonly DalamudIme dalamudIme = Service<DalamudIme>.Get();
private readonly SwapChainVtableResolver address = new();
private readonly Hook<SetCursorDelegate> setCursorHook;
@ -627,8 +624,6 @@ internal class InterfaceManager : IDisposable, IServiceType
var r = this.scene?.ProcessWndProcW(args.Hwnd, (User32.WindowMessage)args.Message, args.WParam, args.LParam);
if (r is not null)
args.SuppressWithValue(r.Value);
this.dalamudIme.ProcessImeMessage(args);
}
/*

View file

@ -1,266 +0,0 @@
using System.Numerics;
using Dalamud.Interface.Windowing;
using ImGuiNET;
namespace Dalamud.Interface.Internal.Windows;
/// <summary>
/// A window for displaying IME details.
/// </summary>
internal unsafe class DalamudImeWindow : Window
{
private const int ImePageSize = 9;
/// <summary>
/// Initializes a new instance of the <see cref="DalamudImeWindow"/> class.
/// </summary>
public DalamudImeWindow()
: base(
"Dalamud IME",
ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoBackground)
{
this.Size = default(Vector2);
this.RespectCloseHotkey = false;
}
/// <inheritdoc/>
public override void Draw()
{
}
/// <inheritdoc/>
public override void PostDraw()
{
if (Service<DalamudIme>.GetNullable() is not { } ime)
return;
var viewport = ime.AssociatedViewport;
if (viewport.NativePtr is null)
return;
var drawCand = ime.ImmCand.Count != 0;
var drawConv = drawCand || ime.ShowPartialConversion;
var drawIme = ime.InputModeIcon != 0;
var imeIconFont = InterfaceManager.DefaultFont;
var pad = ImGui.GetStyle().WindowPadding;
var candTextSize = ImGui.CalcTextSize(ime.ImmComp == string.Empty ? " " : ime.ImmComp);
var native = ime.ImmCandNative;
var totalIndex = native.dwSelection + 1;
var totalSize = native.dwCount;
var pageStart = native.dwPageStart;
var pageIndex = (pageStart / ImePageSize) + 1;
var pageCount = (totalSize / ImePageSize) + 1;
var pageInfo = $"{totalIndex}/{totalSize} ({pageIndex}/{pageCount})";
// Calc the window size.
var maxTextWidth = 0f;
for (var i = 0; i < ime.ImmCand.Count; i++)
{
var textSize = ImGui.CalcTextSize($"{i + 1}. {ime.ImmCand[i]}");
maxTextWidth = maxTextWidth > textSize.X ? maxTextWidth : textSize.X;
}
maxTextWidth = maxTextWidth > ImGui.CalcTextSize(pageInfo).X ? maxTextWidth : ImGui.CalcTextSize(pageInfo).X;
maxTextWidth = maxTextWidth > ImGui.CalcTextSize(ime.ImmComp).X
? maxTextWidth
: ImGui.CalcTextSize(ime.ImmComp).X;
var numEntries = (drawCand ? ime.ImmCand.Count + 1 : 0) + 1 + (drawIme ? 1 : 0);
var spaceY = ImGui.GetStyle().ItemSpacing.Y;
var imeWindowHeight = (spaceY * (numEntries - 1)) + (candTextSize.Y * numEntries);
var windowSize = new Vector2(maxTextWidth, imeWindowHeight) + (pad * 2);
// 1. Figure out the expanding direction.
var expandUpward = ime.CursorPos.Y + windowSize.Y > viewport.WorkPos.Y + viewport.WorkSize.Y;
var windowPos = ime.CursorPos - pad;
if (expandUpward)
{
windowPos.Y -= windowSize.Y - candTextSize.Y - (pad.Y * 2);
if (drawIme)
windowPos.Y += candTextSize.Y + spaceY;
}
else
{
if (drawIme)
windowPos.Y -= candTextSize.Y + spaceY;
}
// 2. Contain within the viewport. Do not use clamp, as the target window might be too small.
if (windowPos.X < viewport.WorkPos.X)
windowPos.X = viewport.WorkPos.X;
else if (windowPos.X + windowSize.X > viewport.WorkPos.X + viewport.WorkSize.X)
windowPos.X = (viewport.WorkPos.X + viewport.WorkSize.X) - windowSize.X;
if (windowPos.Y < viewport.WorkPos.Y)
windowPos.Y = viewport.WorkPos.Y;
else if (windowPos.Y + windowSize.Y > viewport.WorkPos.Y + viewport.WorkSize.Y)
windowPos.Y = (viewport.WorkPos.Y + viewport.WorkSize.Y) - windowSize.Y;
var cursor = windowPos + pad;
// Draw the ime window.
var drawList = ImGui.GetForegroundDrawList(viewport);
// Draw the background rect for candidates.
if (drawCand)
{
Vector2 candRectLt, candRectRb;
if (!expandUpward)
{
candRectLt = windowPos + candTextSize with { X = 0 } + pad with { X = 0 };
candRectRb = windowPos + windowSize;
if (drawIme)
candRectLt.Y += spaceY + candTextSize.Y;
}
else
{
candRectLt = windowPos;
candRectRb = windowPos + (windowSize - candTextSize with { X = 0 } - pad with { X = 0 });
if (drawIme)
candRectRb.Y -= spaceY + candTextSize.Y;
}
drawList.AddRectFilled(
candRectLt,
candRectRb,
ImGui.GetColorU32(ImGuiCol.WindowBg),
ImGui.GetStyle().WindowRounding);
}
if (!expandUpward && drawIme)
{
for (var dx = -2; dx <= 2; dx++)
{
for (var dy = -2; dy <= 2; dy++)
{
if (dx != 0 || dy != 0)
{
imeIconFont.RenderChar(
drawList,
imeIconFont.FontSize,
cursor + new Vector2(dx, dy),
ImGui.GetColorU32(ImGuiCol.WindowBg),
ime.InputModeIcon);
}
}
}
imeIconFont.RenderChar(
drawList,
imeIconFont.FontSize,
cursor,
ImGui.GetColorU32(ImGuiCol.Text),
ime.InputModeIcon);
cursor.Y += candTextSize.Y + spaceY;
}
if (!expandUpward && drawConv)
{
DrawTextBeingConverted();
cursor.Y += candTextSize.Y + spaceY;
// Add a separator.
drawList.AddLine(cursor, cursor + new Vector2(maxTextWidth, 0), ImGui.GetColorU32(ImGuiCol.Separator));
}
if (drawCand)
{
// Add the candidate words.
for (var i = 0; i < ime.ImmCand.Count; i++)
{
var selected = i == (native.dwSelection % ImePageSize);
var color = ImGui.GetColorU32(ImGuiCol.Text);
if (selected)
color = ImGui.GetColorU32(ImGuiCol.NavHighlight);
drawList.AddText(cursor, color, $"{i + 1}. {ime.ImmCand[i]}");
cursor.Y += candTextSize.Y + spaceY;
}
// Add a separator
drawList.AddLine(cursor, cursor + new Vector2(maxTextWidth, 0), ImGui.GetColorU32(ImGuiCol.Separator));
// Add the pages infomation.
drawList.AddText(cursor, ImGui.GetColorU32(ImGuiCol.Text), pageInfo);
cursor.Y += candTextSize.Y + spaceY;
}
if (expandUpward && drawConv)
{
// Add a separator.
drawList.AddLine(cursor, cursor + new Vector2(maxTextWidth, 0), ImGui.GetColorU32(ImGuiCol.Separator));
DrawTextBeingConverted();
cursor.Y += candTextSize.Y + spaceY;
}
if (expandUpward && drawIme)
{
for (var dx = -2; dx <= 2; dx++)
{
for (var dy = -2; dy <= 2; dy++)
{
if (dx != 0 || dy != 0)
{
imeIconFont.RenderChar(
drawList,
imeIconFont.FontSize,
cursor + new Vector2(dx, dy),
ImGui.GetColorU32(ImGuiCol.WindowBg),
ime.InputModeIcon);
}
}
}
imeIconFont.RenderChar(
drawList,
imeIconFont.FontSize,
cursor,
ImGui.GetColorU32(ImGuiCol.Text),
ime.InputModeIcon);
}
return;
void DrawTextBeingConverted()
{
// Draw the text background.
drawList.AddRectFilled(
cursor - (pad / 2),
cursor + candTextSize + (pad / 2),
ImGui.GetColorU32(ImGuiCol.WindowBg));
// If only a part of the full text is marked for conversion, then draw background for the part being edited.
if (ime.PartialConversionFrom != 0 || ime.PartialConversionTo != ime.ImmComp.Length)
{
var part1 = ime.ImmComp[..ime.PartialConversionFrom];
var part2 = ime.ImmComp[..ime.PartialConversionTo];
var size1 = ImGui.CalcTextSize(part1);
var size2 = ImGui.CalcTextSize(part2);
drawList.AddRectFilled(
cursor + size1 with { Y = 0 },
cursor + size2,
ImGui.GetColorU32(ImGuiCol.TextSelectedBg));
}
// Add the text being converted.
drawList.AddText(cursor, ImGui.GetColorU32(ImGuiCol.Text), ime.ImmComp);
// Draw the caret inside the composition string.
if (DalamudIme.ShowCursorInInputText)
{
var partBeforeCaret = ime.ImmComp[..ime.CompositionCursorOffset];
var sizeBeforeCaret = ImGui.CalcTextSize(partBeforeCaret);
drawList.AddLine(
cursor + sizeBeforeCaret with { Y = 0 },
cursor + sizeBeforeCaret,
ImGui.GetColorU32(ImGuiCol.Text));
}
}
}
}

View file

@ -165,6 +165,7 @@ internal static class ServiceManager
var earlyLoadingServices = new HashSet<Type>();
var blockingEarlyLoadingServices = new HashSet<Type>();
var providedServices = new HashSet<Type>();
var dependencyServicesMap = new Dictionary<Type, List<Type>>();
var getAsyncTaskMap = new Dictionary<Type, Task>();
@ -197,7 +198,10 @@ internal static class ServiceManager
// We don't actually need to load provided services, something else does
if (serviceKind.HasFlag(ServiceKind.ProvidedService))
{
providedServices.Add(serviceType);
continue;
}
Debug.Assert(
serviceKind.HasFlag(ServiceKind.EarlyLoadedService) ||
@ -340,7 +344,16 @@ internal static class ServiceManager
}
if (!tasks.Any())
throw new InvalidOperationException("Unresolvable dependency cycle detected");
{
// No more services we can start loading for now.
// Either we're waiting for provided services, or there's a dependency cycle.
providedServices.RemoveWhere(x => getAsyncTaskMap[x].IsCompleted);
if (providedServices.Any())
await Task.WhenAny(providedServices.Select(x => getAsyncTaskMap[x]));
else
throw new InvalidOperationException("Unresolvable dependency cycle detected");
continue;
}
if (servicesToLoad.Any())
{