diff --git a/addons/imgui-godot/ImGuiGodot/ImGuiController.cs b/addons/imgui-godot/ImGuiGodot/ImGuiController.cs
index 69f2f80..0c03fbd 100644
--- a/addons/imgui-godot/ImGuiGodot/ImGuiController.cs
+++ b/addons/imgui-godot/ImGuiGodot/ImGuiController.cs
@@ -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
{
@@ -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;
}
diff --git a/addons/imgui-godot/ImGuiGodot/ImGuiGD.cs b/addons/imgui-godot/ImGuiGodot/ImGuiGD.cs
index 3a2c6b2..dc4727f 100644
--- a/addons/imgui-godot/ImGuiGodot/ImGuiGD.cs
+++ b/addons/imgui-godot/ImGuiGodot/ImGuiGD.cs
@@ -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;
///
/// Deadzone for all axes
@@ -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)
@@ -115,7 +116,7 @@ public static void SetMainViewport(Viewport vp)
///
public static bool ToolInit()
{
- if (_backend is BackendNative nbe)
+ if (_backend is Internal.BackendNative nbe)
{
nbe.ToolInit();
return true;
diff --git a/addons/imgui-godot/ImGuiGodot/Internal/Input.cs b/addons/imgui-godot/ImGuiGodot/Internal/Input.cs
index 3ad67c7..668081a 100644
--- a/addons/imgui-godot/ImGuiGodot/Internal/Input.cs
+++ b/addons/imgui-godot/ImGuiGodot/Internal/Input.cs
@@ -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)
{
@@ -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;
@@ -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();
}
@@ -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;
}
}
diff --git a/addons/imgui-godot/ImGuiGodot/Internal/State.cs b/addons/imgui-godot/ImGuiGodot/Internal/State.cs
index fa47a07..af39d2a 100644
--- a/addons/imgui-godot/ImGuiGodot/Internal/State.cs
+++ b/addons/imgui-godot/ImGuiGodot/Internal/State.cs
@@ -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;
@@ -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();
@@ -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
diff --git a/doc/examples/GdsDemo/demo.gd b/doc/examples/GdsDemo/demo.gd
index 42058a6..8b39884 100644
--- a/doc/examples/GdsDemo/demo.gd
+++ b/doc/examples/GdsDemo/demo.gd
@@ -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
@@ -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:
diff --git a/doc/examples/GdsDemo/project.godot b/doc/examples/GdsDemo/project.godot
index 21fc64b..6d9fa44 100644
--- a/doc/examples/GdsDemo/project.godot
+++ b/doc/examples/GdsDemo/project.godot
@@ -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]
@@ -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]
diff --git a/gdext/godot-cpp b/gdext/godot-cpp
index d6e5286..1f9a0b7 160000
--- a/gdext/godot-cpp
+++ b/gdext/godot-cpp
@@ -1 +1 @@
-Subproject commit d6e5286cc19bbd5b2c626207d3b01a8f145c0f76
+Subproject commit 1f9a0b7171b4a3c89139236653b13318d312ab39
diff --git a/gdext/src/Context.cpp b/gdext/src/Context.cpp
index d8032ac..cff56a1 100644
--- a/gdext/src/Context.cpp
+++ b/gdext/src/Context.cpp
@@ -11,6 +11,21 @@ namespace {
std::unique_ptr 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()
@@ -31,6 +46,7 @@ Context::Context(std::unique_ptr r)
io.BackendPlatformName = PlatformName;
io.BackendRendererName = renderer->Name();
+ io.PlatformSetImeDataFn = SetImeData;
viewports = std::make_unique();
}
diff --git a/gdext/src/ImGuiAPI.h b/gdext/src/ImGuiAPI.h
index 2a889f7..f989650 100644
--- a/gdext/src/ImGuiAPI.h
+++ b/gdext/src/ImGuiAPI.h
@@ -50,7 +50,7 @@ struct GdsPtr
{
if (bufhash != std::hash{}({buf.begin(), buf.end()}))
{
- arr[0] = String(buf.data());
+ arr[0] = String::utf8(buf.data());
}
}
diff --git a/gdext/src/ImGuiController.cpp b/gdext/src/ImGuiController.cpp
index 886dd4f..378ad3b 100644
--- a/gdext/src/ImGuiController.cpp
+++ b/gdext/src/ImGuiController.cpp
@@ -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;
}
diff --git a/gdext/src/Input.cpp b/gdext/src/Input.cpp
index 0200a0d..8cd4c00 100644
--- a/gdext/src/Input.cpp
+++ b/gdext/src/Input.cpp
@@ -28,6 +28,7 @@ struct Input::Impl
Vector2 mouseWheel;
ImGuiMouseCursor currentCursor = ImGuiMouseCursor_None;
bool hasMouse = false;
+ bool takingTextInput = false;
};
namespace {
@@ -199,9 +200,17 @@ void Input::ProcessSubViewportWidget(const Ref& evt)
bool Input::HandleEvent(const Ref& evt)
{
ImGuiIO& io = ImGui::GetIO();
-
bool consumed = false;
+ if (io.WantTextInput && !impl->takingTextInput)
+ {
+ // avoid IME issues if a text input control was focused
+ GetContext()->layer->get_viewport()->gui_release_focus();
+
+ // TODO: show virtual keyboard?
+ }
+ impl->takingTextInput = io.WantTextInput;
+
if (Ref mm = evt; mm.is_valid())
{
consumed = io.WantCaptureMouse;
@@ -247,14 +256,17 @@ bool Input::HandleEvent(const Ref& evt)
{
UpdateKeyMods(io);
ImGuiKey igk = ToImGuiKey(k->get_keycode());
+ bool pressed = k->is_pressed();
+ uint32_t unicode = k->get_unicode();
+
if (igk != ImGuiKey_None)
{
- bool pressed = k->is_pressed();
- io.AddKeyEvent(igk, k->is_pressed());
- if (pressed && k->get_unicode() != 0 && io.WantTextInput)
- {
- io.AddInputCharacter(k->get_unicode());
- }
+ io.AddKeyEvent(igk, pressed);
+ }
+
+ if (pressed && unicode != 0 && io.WantTextInput)
+ {
+ io.AddInputCharacterUTF16(unicode);
}
consumed = io.WantCaptureKeyboard || io.WantTextInput;
}
@@ -333,6 +345,10 @@ void Input::ProcessNotification(int what)
case Node::NOTIFICATION_APPLICATION_FOCUS_OUT:
ImGui::GetIO().AddFocusEvent(false);
break;
+ case MainLoop::NOTIFICATION_OS_IME_UPDATE:
+ // workaround for Godot suppressing key up events during IME
+ ImGui::GetIO().ClearInputKeys();
+ break;
};
}