Skip to content

Commit

Permalink
Add IME support
Browse files Browse the repository at this point in the history
  • Loading branch information
pkdawson committed Sep 1, 2024
1 parent 7440ffd commit d0da181
Show file tree
Hide file tree
Showing 11 changed files with 158 additions and 78 deletions.
3 changes: 2 additions & 1 deletion addons/imgui-godot/ImGuiGodot/ImGuiController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public partial class ImGuiController : Node
public static ImGuiController Instance { get; private set; } = null!;
private ImGuiControllerHelper _helper = null!;
public Node Signaler { get; private set; } = null!;
private readonly StringName _signalName = "imgui_layout";

private sealed partial class ImGuiControllerHelper : Node
{
Expand Down Expand Up @@ -78,7 +79,7 @@ public override void _ExitTree()

public override void _Process(double delta)
{
Signaler.EmitSignal("imgui_layout");
Signaler.EmitSignal(_signalName);
Internal.State.Instance.Render();
Internal.State.Instance.InProcessFrame = false;
}
Expand Down
9 changes: 5 additions & 4 deletions addons/imgui-godot/ImGuiGodot/ImGuiGD.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
#if GODOT_PC
#nullable enable
using Godot;
using ImGuiGodot.Internal;
using System;

namespace ImGuiGodot;

public static class ImGuiGD
{
private static readonly IBackend _backend;
private static readonly Internal.IBackend _backend;

/// <summary>
/// Deadzone for all axes
Expand Down Expand Up @@ -44,7 +43,9 @@ public static bool Visible

static ImGuiGD()
{
_backend = ClassDB.ClassExists("ImGuiGD") ? new BackendNative() : new BackendNet();
_backend = ClassDB.ClassExists("ImGuiGD")
? new Internal.BackendNative()
: new Internal.BackendNet();
}

public static IntPtr BindTexture(Texture2D tex)
Expand Down Expand Up @@ -115,7 +116,7 @@ public static void SetMainViewport(Viewport vp)
/// </summary>
public static bool ToolInit()
{
if (_backend is BackendNative nbe)
if (_backend is Internal.BackendNative nbe)
{
nbe.ToolInit();
return true;
Expand Down
28 changes: 23 additions & 5 deletions addons/imgui-godot/ImGuiGodot/Internal/Input.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ internal class Input
private Vector2 _mouseWheel = Vector2.Zero;
private ImGuiMouseCursor _currentCursor = ImGuiMouseCursor.None;
private readonly bool _hasMouse = DisplayServer.HasFeature(DisplayServer.Feature.Mouse);
private bool _takingTextInput = false;

protected virtual void UpdateMousePos(ImGuiIOPtr io)
{
Expand Down Expand Up @@ -140,6 +141,15 @@ protected bool HandleEvent(InputEvent evt)
var io = ImGui.GetIO();
bool consumed = false;

if (io.WantTextInput && !_takingTextInput)
{
// avoid IME issues if a text input control was focused
State.Instance.Layer.GetViewport().GuiReleaseFocus();

// TODO: show virtual keyboard?
}
_takingTextInput = io.WantTextInput;

if (evt is InputEventMouseMotion mm)
{
consumed = io.WantCaptureMouse;
Expand Down Expand Up @@ -184,15 +194,19 @@ protected bool HandleEvent(InputEvent evt)
{
UpdateKeyMods(io);
ImGuiKey igk = ConvertKey(k.Keycode);
bool pressed = k.Pressed;
long unicode = k.Unicode;

if (igk != ImGuiKey.None)
{
io.AddKeyEvent(igk, k.Pressed);
io.AddKeyEvent(igk, pressed);
}

if (k.Pressed && k.Unicode != 0 && io.WantTextInput)
{
io.AddInputCharacter((uint)k.Unicode);
}
if (pressed && unicode != 0 && io.WantTextInput)
{
io.AddInputCharacterUTF16((ushort)unicode);
}

consumed = io.WantCaptureKeyboard || io.WantTextInput;
k.Dispose();
}
Expand Down Expand Up @@ -268,6 +282,10 @@ public static void ProcessNotification(long what)
case MainLoop.NotificationApplicationFocusOut:
ImGui.GetIO().AddFocusEvent(false);
break;
case MainLoop.NotificationOsImeUpdate:
// workaround for Godot suppressing key up events during IME
ImGui.GetIO().ClearInputKeys();
break;
}
}

Expand Down
23 changes: 23 additions & 0 deletions addons/imgui-godot/ImGuiGodot/Internal/State.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ private enum RendererType

internal static State Instance { get; set; } = null!;

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void PlatformSetImeDataFn(
nint ctx,
ImGuiViewportPtr vp,
ImGuiPlatformImeDataPtr data);
private static readonly PlatformSetImeDataFn _setImeData = SetImeData;

public State(IRenderer renderer)
{
Renderer = renderer;
Expand Down Expand Up @@ -63,6 +70,7 @@ public State(IRenderer renderer)
{
io.NativePtr->BackendPlatformName = (byte*)_backendName;
io.NativePtr->BackendRendererName = (byte*)_rendererName;
io.NativePtr->PlatformSetImeDataFn = Marshal.GetFunctionPointerForDelegate(_setImeData);
}

Viewports = new Viewports();
Expand Down Expand Up @@ -184,5 +192,20 @@ public void Render()
ImGui.UpdatePlatformWindows();
Renderer.Render();
}

private static void SetImeData(nint ctx, ImGuiViewportPtr vp, ImGuiPlatformImeDataPtr data)
{
int windowID = (int)vp.PlatformHandle;

DisplayServer.WindowSetImeActive(data.WantVisible, windowID);
if (data.WantVisible)
{
Vector2I pos = new(
(int)(data.InputPos.X - vp.Pos.X),
(int)(data.InputPos.Y - vp.Pos.Y + data.InputLineHeight)
);
DisplayServer.WindowSetImePosition(pos, windowID);
}
}
}
#endif
116 changes: 59 additions & 57 deletions doc/examples/GdsDemo/demo.gd
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ var ms_selection := []
var table_items := []

func _ready():
Engine.max_fps = 120

var io := ImGui.GetIO()
io.ConfigFlags |= ImGui.ConfigFlags_ViewportsEnable

Expand All @@ -30,70 +32,70 @@ func _process(_delta: float) -> void:

var gdver: String = Engine.get_version_info()["string"]

ImGui.Begin("Demo")
ImGui.Text("ImGui in")
ImGui.SameLine()
ImGui.TextLinkOpenURLEx("Godot %s" % gdver, "https://www.godotengine.org")
ImGui.Text("mem %.1f KiB / peak %.1f KiB" % [
OS.get_static_memory_usage() / 1024.0,
OS.get_static_memory_peak_usage() / 1024.0])
ImGui.Separator()

ImGui.DragFloat("myfloat", myfloat)
ImGui.Text(str(myfloat[0]))
ImGui.InputText("mystr", mystr, 32)
ImGui.Text(mystr[0])

ImGui.PlotHistogram("histogram", values, values.size())
ImGui.PlotLines("lines", values, values.size())
ImGui.ListBox("choices", current_item, items, items.size())
ImGui.Combo("combo", current_item, items)
ImGui.Text("choice = %s" % items[current_item[0]])

ImGui.SeparatorText("Multi-Select")
if ImGui.BeginChild("MSItems", Vector2(0,0), ImGui.ChildFlags_FrameStyle):
var flags := ImGui.MultiSelectFlags_ClearOnEscape | ImGui.MultiSelectFlags_BoxSelect1d
var ms_io := ImGui.BeginMultiSelectEx(flags, ms_selection.size(), ms_items.size())
apply_selection_requests(ms_io)
for i in range(items.size()):
var is_selected := ms_selection.has(i)
ImGui.SetNextItemSelectionUserData(i)
ImGui.SelectableEx(ms_items[i], is_selected)
ms_io = ImGui.EndMultiSelect()
apply_selection_requests(ms_io)
if ImGui.Begin("Demo"):
ImGui.Text("ImGui in")
ImGui.SameLine()
ImGui.TextLinkOpenURLEx("Godot %s" % gdver, "https://www.godotengine.org")
ImGui.Text("mem %.1f KiB / peak %.1f KiB" % [
OS.get_static_memory_usage() / 1024.0,
OS.get_static_memory_peak_usage() / 1024.0])
ImGui.Separator()

ImGui.DragFloat("myfloat", myfloat)
ImGui.Text(str(myfloat[0]))
ImGui.InputText("mystr", mystr, 32)
ImGui.Text(mystr[0])

ImGui.PlotHistogram("histogram", values, values.size())
ImGui.PlotLines("lines", values, values.size())
ImGui.ListBox("choices", current_item, items, items.size())
ImGui.Combo("combo", current_item, items)
ImGui.Text("choice = %s" % items[current_item[0]])

ImGui.SeparatorText("Multi-Select")
if ImGui.BeginChild("MSItems", Vector2(0,0), ImGui.ChildFlags_FrameStyle):
var flags := ImGui.MultiSelectFlags_ClearOnEscape | ImGui.MultiSelectFlags_BoxSelect1d
var ms_io := ImGui.BeginMultiSelectEx(flags, ms_selection.size(), ms_items.size())
apply_selection_requests(ms_io)
for i in range(items.size()):
var is_selected := ms_selection.has(i)
ImGui.SetNextItemSelectionUserData(i)
ImGui.SelectableEx(ms_items[i], is_selected)
ms_io = ImGui.EndMultiSelect()
apply_selection_requests(ms_io)
ImGui.EndChild()
ImGui.End()

ImGui.Begin("Sortable Table")
if ImGui.BeginTable("sortable_table", 2, ImGui.TableFlags_Sortable):
ImGui.TableSetupColumn("ID", ImGui.TableColumnFlags_DefaultSort)
ImGui.TableSetupColumn("Name")
ImGui.TableSetupScrollFreeze(0, 1)
ImGui.TableHeadersRow()

var sort_specs := ImGui.TableGetSortSpecs()
if sort_specs.SpecsDirty:
for spec: ImGuiTableColumnSortSpecsPtr in sort_specs.Specs:
var col := spec.ColumnIndex
if spec.SortDirection == ImGui.SortDirection_Ascending:
table_items.sort_custom(func(lhs, rhs): return lhs[col] < rhs[col])
else:
table_items.sort_custom(func(lhs, rhs): return lhs[col] > rhs[col])
sort_specs.SpecsDirty = false

for i in range(table_items.size()):
ImGui.TableNextRow()
ImGui.TableNextColumn()
ImGui.Text("%d" % table_items[i][0])
ImGui.TableNextColumn()
ImGui.Text(table_items[i][1])
ImGui.EndTable()
if ImGui.Begin("Sortable Table"):
if ImGui.BeginTable("sortable_table", 2, ImGui.TableFlags_Sortable):
ImGui.TableSetupColumn("ID", ImGui.TableColumnFlags_DefaultSort)
ImGui.TableSetupColumn("Name")
ImGui.TableSetupScrollFreeze(0, 1)
ImGui.TableHeadersRow()

var sort_specs := ImGui.TableGetSortSpecs()
if sort_specs.SpecsDirty:
for spec: ImGuiTableColumnSortSpecsPtr in sort_specs.Specs:
var col := spec.ColumnIndex
if spec.SortDirection == ImGui.SortDirection_Ascending:
table_items.sort_custom(func(lhs, rhs): return lhs[col] < rhs[col])
else:
table_items.sort_custom(func(lhs, rhs): return lhs[col] > rhs[col])
sort_specs.SpecsDirty = false

for i in range(table_items.size()):
ImGui.TableNextRow()
ImGui.TableNextColumn()
ImGui.Text("%d" % table_items[i][0])
ImGui.TableNextColumn()
ImGui.Text(table_items[i][1])
ImGui.EndTable()
ImGui.End()

ImGui.SetNextWindowClass(wc_topmost)
ImGui.SetNextWindowSize(Vector2(200, 200), ImGui.Cond_Once)
ImGui.Begin("topmost viewport window")
ImGui.TextWrapped("when this is a viewport window outside the main window, it will stay on top")
if ImGui.Begin("topmost viewport window"):
ImGui.TextWrapped("when this is a viewport window outside the main window, it will stay on top")
ImGui.End()

func _physics_process(_delta: float) -> void:
Expand Down
3 changes: 2 additions & 1 deletion doc/examples/GdsDemo/project.godot
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ config_version=5

config/name="GdsDemo"
run/main_scene="res://main.tscn"
config/features=PackedStringArray("4.2", "Forward Plus")
config/features=PackedStringArray("4.3", "Forward Plus")
config/icon="res://icon.svg"

[autoload]
Expand All @@ -22,6 +22,7 @@ ImGuiRoot="*res://addons/imgui-godot/data/ImGuiRoot.tscn"
[display]

window/subwindows/embed_subwindows=false
window/vsync/vsync_mode=0

[editor_plugins]

Expand Down
2 changes: 1 addition & 1 deletion gdext/godot-cpp
16 changes: 16 additions & 0 deletions gdext/src/Context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,21 @@ namespace {
std::unique_ptr<Context> ctx;

const char* PlatformName = "godot4";

void SetImeData(ImGuiContext* ctx, ImGuiViewport* vp, ImGuiPlatformImeData* data)
{
DisplayServer* DS = DisplayServer::get_singleton();
const int32_t windowID = (int32_t)(intptr_t)vp->PlatformHandle;

DS->window_set_ime_active(data->WantVisible, windowID);
if (data->WantVisible)
{
Vector2i pos;
pos.x = data->InputPos.x - vp->Pos.x;
pos.y = data->InputPos.y - vp->Pos.y + data->InputLineHeight;
DS->window_set_ime_position(pos, windowID);
}
}
} // namespace

Context* GetContext()
Expand All @@ -31,6 +46,7 @@ Context::Context(std::unique_ptr<Renderer> r)

io.BackendPlatformName = PlatformName;
io.BackendRendererName = renderer->Name();
io.PlatformSetImeDataFn = SetImeData;

viewports = std::make_unique<Viewports>();
}
Expand Down
2 changes: 1 addition & 1 deletion gdext/src/ImGuiAPI.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ struct GdsPtr<String>
{
if (bufhash != std::hash<std::string_view>{}({buf.begin(), buf.end()}))
{
arr[0] = String(buf.data());
arr[0] = String::utf8(buf.data());
}
}

Expand Down
4 changes: 3 additions & 1 deletion gdext/src/ImGuiController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,10 @@ void ImGuiController::_process(double delta)
}
#endif

static const StringName signalName("imgui_layout");
emit_signal(signalName);

Context* ctx = GetContext();
emit_signal("imgui_layout");
ctx->Render();
ctx->inProcessFrame = false;
}
Expand Down
Loading

0 comments on commit d0da181

Please sign in to comment.