From d07996a7195931c68fa584a06b8603647c281d63 Mon Sep 17 00:00:00 2001 From: Kanbaru Date: Tue, 5 Mar 2024 10:45:21 +0800 Subject: [PATCH] add support for running ImGui in the Editor. --- .../ImGuiGodot/ImGuiEditorPlugin.cs | 111 ++++++++++++++++++ addons/imgui-godot/ImGuiGodot/ImGuiLayer.cs | 83 ++++++++++--- .../imgui-godot/ImGuiGodot/Internal/State.cs | 2 + .../ImGuiGodot/Internal/Viewports.cs | 51 +++++++- addons/imgui-godot/plugin.cfg | 5 +- 5 files changed, 230 insertions(+), 22 deletions(-) create mode 100644 addons/imgui-godot/ImGuiGodot/ImGuiEditorPlugin.cs diff --git a/addons/imgui-godot/ImGuiGodot/ImGuiEditorPlugin.cs b/addons/imgui-godot/ImGuiGodot/ImGuiEditorPlugin.cs new file mode 100644 index 0000000..5140cee --- /dev/null +++ b/addons/imgui-godot/ImGuiGodot/ImGuiEditorPlugin.cs @@ -0,0 +1,111 @@ +#if TOOLS +using Godot; +using ImGuiNET; + + +namespace ImGuiGodot; + +[Tool] +public partial class ImGuiEditorPlugin : EditorPlugin { + + #region VARIABLES + + private ImGuiLayer _imguiLayerControl; + private Callable _onImGuiLayoutCallback; + + #endregion + + + #region EditorPlugin override + + public override void _Ready() { + // Initialization of the plugin goes here. + Init(); + } + + public override void _EnterTree() { + if ( !Engine.IsEditorHint() ) { + AddAutoloadSingleton( "ImGuiLayer", "res://addons/imgui-godot/ImGuiLayer.tscn" ); + } + } + + public override void _ExitTree() { + + if ( !Engine.IsEditorHint() ) { + RemoveAutoloadSingleton( "ImGuiLayer" ); + } + else { + DeInit(); + + ImGuiLayer.Instance = null; + } + } + + public override void _Process( double delta ) { + base._Process( delta ); + UpdateOverlays(); + } + + public override bool _HasMainScreen() => false; + + public override void _MakeVisible( bool visible ) { + // if ( _imguiLayerControl != null ) { + // _imguiLayerControl.Visible = visible; + // } + } + + public override string _GetPluginName() => "ImGui"; + + public override Texture2D _GetPluginIcon() { + return EditorInterface.Singleton.GetBaseControl().GetThemeIcon( "ResourcePreloader", "EditorIcons" ); + } + + + #endregion + + + private void Init() { + _imguiLayerControl = new (); + _imguiLayerControl.Layer = 128; + _imguiLayerControl.ProcessMode = ProcessModeEnum.Always; + _imguiLayerControl.Visible = true; + _onImGuiLayoutCallback = new Callable( this, MethodName.OnImGuiLayout ); + + SetupWindow(); + + ImGuiLayer.Connect( _onImGuiLayoutCallback ); + } + + private void DeInit() { + // RemoveToolMenuItem( "Toggle ImGui" ); + + if ( _imguiLayerControl is not null ) { + ImGuiLayer.Disconnect( _onImGuiLayoutCallback ); + EditorInterface.Singleton.GetEditorMainScreen().RemoveChild( _imguiLayerControl ); + _imguiLayerControl.Free(); + _imguiLayerControl = null; + } + } + + private void SetupWindow() { + if ( _imguiLayerControl is not null ) { + EditorInterface.Singleton.GetEditorMainScreen().AddChild( _imguiLayerControl ); + } + + // _MakeVisible( false ); + } + + private void OnImGuiLayout() { + // your code here + ImGui.ShowDemoWindow(); + } + + + private void ToggleImGuiLayer() { + if ( _imguiLayerControl is not null ) { + _imguiLayerControl.Visible = !_imguiLayerControl.Visible; + } + } + +} +#endif diff --git a/addons/imgui-godot/ImGuiGodot/ImGuiLayer.cs b/addons/imgui-godot/ImGuiGodot/ImGuiLayer.cs index d047aaa..2a1fd46 100644 --- a/addons/imgui-godot/ImGuiGodot/ImGuiLayer.cs +++ b/addons/imgui-godot/ImGuiGodot/ImGuiLayer.cs @@ -3,11 +3,12 @@ using ImGuiNET; using System; + namespace ImGuiGodot; -public partial class ImGuiLayer : CanvasLayer +public partial class ImGuiLayer : CanvasLayer, ISerializationListener { - public static ImGuiLayer Instance { get; private set; } = null!; + public static ImGuiLayer Instance { get; internal set; } = null!; [Export(PropertyHint.ResourceType, "ImGuiConfig")] public GodotObject Config = null!; @@ -17,8 +18,10 @@ public partial class ImGuiLayer : CanvasLayer private Vector2I _subViewportSize = Vector2I.Zero; private Rid _ci; private Transform2D _finalTransform = Transform2D.Identity; - private UpdateFirst _updateFirst = null!; + private WeakReference< UpdateFirst > _updateFirst = null!; private bool _headless = false; + private Callable _onChangeVisibilityCallback; + public Node Signaler { get; private set; } = null!; private sealed partial class UpdateFirst : Node @@ -26,15 +29,23 @@ private sealed partial class UpdateFirst : Node private uint _counter = 0; private ImGuiLayer _parent = null!; private Window _window = null!; + private Callable _onChangeVisibilityCallback; public override void _Ready() { _parent = (ImGuiLayer)GetParent(); _window = GetWindow(); - _parent.VisibilityChanged += OnChangeVisibility; + _onChangeVisibilityCallback = new Callable( this, UpdateFirst.MethodName.OnChangeVisibility ); + _parent.Connect( ImGuiLayer.SignalName.VisibilityChanged, _onChangeVisibilityCallback ); OnChangeVisibility(); } + public override void _ExitTree() { + if ( _parent is not null ) { + _parent.Disconnect( ImGuiLayer.SignalName.VisibilityChanged, _onChangeVisibilityCallback ); + } + } + public override void _PhysicsProcess(double delta) { // call NewFrame occasionally if GUI isn't visible, to prevent leaks @@ -65,7 +76,10 @@ public override void _EnterTree() CheckContentScale(); ProcessPriority = int.MaxValue; - VisibilityChanged += OnChangeVisibility; + if ( !Engine.IsEditorHint() ) { + _onChangeVisibilityCallback = new Callable( this, ImGuiLayer.MethodName.OnChangeVisibility ); + Connect( ImGuiLayer.SignalName.VisibilityChanged, _onChangeVisibilityCallback ); + } _subViewportRid = Internal.Util.AddLayerSubViewport(this); _ci = RenderingServer.CanvasItemCreate(); @@ -78,6 +92,9 @@ public override void _EnterTree() ImGuiGD.Init(_window, _subViewportRid, (float)cfg.Get("Scale"), _headless ? RendererType.Dummy : Enum.Parse((string)cfg.Get("Renderer"))); + var io = ImGui.GetIO(); + io.ConfigFlags |= ImGuiConfigFlags.DockingEnable; + io.ConfigFlags |= ImGuiConfigFlags.ViewportsEnable; ImGui.GetIO().SetIniFilename((string)cfg.Get("IniFilename")); var fonts = (Godot.Collections.Array)cfg.Get("Fonts"); @@ -99,13 +116,11 @@ public override void _EnterTree() } ImGuiGD.RebuildFontAtlas(); - _updateFirst = new UpdateFirst - { - Name = "ImGuiLayer_UpdateFirst", - ProcessPriority = int.MinValue, - ProcessMode = ProcessModeEnum.Always, + var updateFirst = new UpdateFirst { + Name = "ImGuiLayer_UpdateFirst", ProcessPriority = int.MinValue, ProcessMode = ProcessModeEnum.Always }; - AddChild(_updateFirst); + _updateFirst = new WeakReference< UpdateFirst >( updateFirst ); + AddChild(updateFirst); Signaler = (Node)((GDScript)GD.Load("res://addons/imgui-godot/scripts/ImGuiSignaler.gd")).New(); Signaler.Name = "Signaler"; @@ -119,9 +134,17 @@ public override void _Ready() public override void _ExitTree() { + if ( !Engine.IsEditorHint() ) { + Disconnect( ImGuiLayer.SignalName.VisibilityChanged, _onChangeVisibilityCallback ); + } + ImGuiGD.Shutdown(); RenderingServer.FreeRid(_ci); RenderingServer.FreeRid(_subViewportRid); + + if ( Instance == this ) { + Instance = null; + } } private void OnChangeVisibility() @@ -184,9 +207,8 @@ public static void Connect(Callable callable) Instance?.Signaler.Connect("imgui_layout", callable); } - public static void Connect(Action action) - { - Connect(Callable.From(action)); + public static void Disconnect( Callable callable ) { + Instance?.Signaler?.Disconnect( "imgui_layout", callable ); } private void CheckContentScale() @@ -208,6 +230,39 @@ private void PrintErrContentScale() $" or {Window.ContentScaleModeEnum.CanvasItems}"); GD.PrintErr($" current mode is {_window.ContentScaleMode}/{_window.ContentScaleAspect}"); } + + public void OnBeforeSerialize() { + // Logger.Debug( Environment.StackTrace ); + // VisibilityChanged -= OnChangeVisibility; + + // if ( _updateFirst != null && _updateFirst.TryGetTarget( out var updateFirst ) ) { + // RemoveChild( updateFirst ); + // updateFirst.Free(); + // _updateFirst = null; + // } + // + // if ( Signaler != null ) { + // RemoveChild( Signaler ); + // Signaler.Free(); + // Signaler = null; + // } + } + + public void OnAfterDeserialize() { + + Instance = this; + + var updateFirst = new UpdateFirst { + Name = "ImGuiLayer_UpdateFirst", ProcessPriority = int.MinValue, ProcessMode = ProcessModeEnum.Always + }; + _updateFirst = new WeakReference< UpdateFirst >( updateFirst ); + AddChild( updateFirst ); + + Signaler = ( Node )( ( GDScript )GD.Load( "res://addons/imgui-godot/scripts/ImGuiSignaler.gd" ) ).New(); + Signaler.Name = "Signaler"; + AddChild( Signaler ); + } + } #else namespace ImGuiNET diff --git a/addons/imgui-godot/ImGuiGodot/Internal/State.cs b/addons/imgui-godot/ImGuiGodot/Internal/State.cs index 7cfbea5..33ad4ba 100644 --- a/addons/imgui-godot/ImGuiGodot/Internal/State.cs +++ b/addons/imgui-godot/ImGuiGodot/Internal/State.cs @@ -66,6 +66,8 @@ public State(Window mainWindow, Rid mainSubViewport, IRenderer renderer) public void Dispose() { + Viewports.Dispose(); + Viewports = null; if (ImGui.GetCurrentContext() != IntPtr.Zero) { ImGui.DestroyContext(); diff --git a/addons/imgui-godot/ImGuiGodot/Internal/Viewports.cs b/addons/imgui-godot/ImGuiGodot/Internal/Viewports.cs index 570b349..fcf4e17 100644 --- a/addons/imgui-godot/ImGuiGodot/Internal/Viewports.cs +++ b/addons/imgui-godot/ImGuiGodot/Internal/Viewports.cs @@ -11,6 +11,9 @@ internal sealed class GodotImGuiWindow : IDisposable { private readonly GCHandle _gcHandle; private readonly ImGuiViewportPtr _vp; + private readonly Callable _onCloseRequestedCall; + private readonly Callable _onSizeChangedCall; + private readonly Callable _onWindowInputCall; public Window GodotWindow { get; init; } @@ -38,10 +41,14 @@ public GodotImGuiWindow(ImGuiViewportPtr vp) Transparent = true, TransparentBg = true }; - - GodotWindow.CloseRequested += () => _vp.PlatformRequestClose = true; - GodotWindow.SizeChanged += () => _vp.PlatformRequestResize = true; - GodotWindow.WindowInput += OnWindowInput; + + _onCloseRequestedCall = Callable.From( OnCloseRequested ); + _onSizeChangedCall = Callable.From( OnSizeChanged ); + _onWindowInputCall = Callable.From< InputEvent >( OnWindowInput ); + + GodotWindow.Connect( Window.SignalName.CloseRequested, _onCloseRequestedCall ); + GodotWindow.Connect( Window.SignalName.SizeChanged, _onSizeChangedCall ); + GodotWindow.Connect( Window.SignalName.WindowInput, _onWindowInputCall ); ImGuiLayer.Instance.AddChild(GodotWindow); @@ -70,11 +77,22 @@ public void Dispose() { if (GodotWindow.GetParent() != null) { + GodotWindow.Disconnect( Window.SignalName.CloseRequested, _onCloseRequestedCall ); + GodotWindow.Disconnect( Window.SignalName.SizeChanged, _onSizeChangedCall ); + GodotWindow.Disconnect( Window.SignalName.WindowInput, _onWindowInputCall ); GodotWindow.QueueFree(); } _gcHandle.Free(); } + private void OnCloseRequested() { + _vp.PlatformRequestClose = true; + } + + private void OnSizeChanged() { + _vp.PlatformRequestResize = true; + } + private void OnWindowInput(InputEvent evt) { State.Instance.Input.ProcessInput(evt, GodotWindow); @@ -139,7 +157,7 @@ internal static Vector2I ToVector2I(this Vector2 v) } } -internal sealed partial class Viewports +internal sealed partial class Viewports : IDisposable { #if NET7_0_OR_GREATER #if GODOT_WINDOWS && !GODOT4_1_OR_GREATER @@ -276,6 +294,25 @@ private static unsafe void InitPlatformInterface() ImGuiPlatformIO_Set_Platform_GetWindowSize(pio, Marshal.GetFunctionPointerForDelegate(_getWindowSize)); } + private static unsafe void DeInitPlatformInterface() { + var pio = ImGui.GetPlatformIO().NativePtr; + + pio->Platform_CreateWindow = IntPtr.Zero; + pio->Platform_DestroyWindow = IntPtr.Zero; + pio->Platform_ShowWindow = IntPtr.Zero; + pio->Platform_SetWindowPos = IntPtr.Zero; + //pio->Platform_GetWindowPos = IntPtr.Zero; + pio->Platform_SetWindowSize = IntPtr.Zero; + //pio->Platform_GetWindowSize = IntPtr.Zero; + pio->Platform_SetWindowFocus = IntPtr.Zero; + pio->Platform_GetWindowFocus = IntPtr.Zero; + pio->Platform_GetWindowMinimized = IntPtr.Zero; + pio->Platform_SetWindowTitle = IntPtr.Zero; + + ImGuiPlatformIO_Set_Platform_GetWindowPos(pio, IntPtr.Zero); + ImGuiPlatformIO_Set_Platform_GetWindowSize(pio, IntPtr.Zero); + } + public Viewports(Window mainWindow, Rid mainSubViewport) { _mainWindow = new(ImGui.GetMainViewport(), mainWindow, mainSubViewport); @@ -284,6 +321,10 @@ public Viewports(Window mainWindow, Rid mainSubViewport) InitPlatformInterface(); UpdateMonitors(); } + + public void Dispose() { + DeInitPlatformInterface(); + } private static void Godot_CreateWindow(ImGuiViewportPtr vp) { diff --git a/addons/imgui-godot/plugin.cfg b/addons/imgui-godot/plugin.cfg index 940f9d4..32bea0e 100644 --- a/addons/imgui-godot/plugin.cfg +++ b/addons/imgui-godot/plugin.cfg @@ -3,6 +3,5 @@ name="imgui-godot" description="Dear ImGui for Godot" author="Patrick Dawson" -version="4.1.0" -script="scripts/ImGuiPlugin.gd" -version="4.1.1" +version="4.2.0" +script="ImGuiGodot/ImGuiEditorPlugin.cs"