Skip to content

Commit

Permalink
add support for running ImGui in the Editor.
Browse files Browse the repository at this point in the history
  • Loading branch information
lihaochen910 committed Mar 5, 2024
1 parent 1331798 commit d07996a
Show file tree
Hide file tree
Showing 5 changed files with 230 additions and 22 deletions.
111 changes: 111 additions & 0 deletions addons/imgui-godot/ImGuiGodot/ImGuiEditorPlugin.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#if TOOLS
using Godot;
using ImGuiNET;

Check failure on line 4 in addons/imgui-godot/ImGuiGodot/ImGuiEditorPlugin.cs

View workflow job for this annotation

GitHub Actions / .NET 6.0.x

Avoid multiple blank lines

Check failure on line 4 in addons/imgui-godot/ImGuiGodot/ImGuiEditorPlugin.cs

View workflow job for this annotation

GitHub Actions / .NET 8.0.x

Avoid multiple blank lines

namespace ImGuiGodot;

[Tool]
public partial class ImGuiEditorPlugin : EditorPlugin {

#region VARIABLES

private ImGuiLayer _imguiLayerControl;
private Callable _onImGuiLayoutCallback;

#endregion


Check failure on line 18 in addons/imgui-godot/ImGuiGodot/ImGuiEditorPlugin.cs

View workflow job for this annotation

GitHub Actions / .NET 6.0.x

Avoid multiple blank lines

Check failure on line 18 in addons/imgui-godot/ImGuiGodot/ImGuiEditorPlugin.cs

View workflow job for this annotation

GitHub Actions / .NET 8.0.x

Avoid multiple blank lines
#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 );

Check failure on line 72 in addons/imgui-godot/ImGuiGodot/ImGuiEditorPlugin.cs

View workflow job for this annotation

GitHub Actions / .NET 6.0.x

Avoid multiple blank lines

Check failure on line 72 in addons/imgui-godot/ImGuiGodot/ImGuiEditorPlugin.cs

View workflow job for this annotation

GitHub Actions / .NET 8.0.x

Avoid multiple blank lines

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
83 changes: 69 additions & 14 deletions addons/imgui-godot/ImGuiGodot/ImGuiLayer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
using ImGuiNET;
using System;

Check failure on line 5 in addons/imgui-godot/ImGuiGodot/ImGuiLayer.cs

View workflow job for this annotation

GitHub Actions / .NET 6.0.x

Avoid multiple blank lines

Check failure on line 5 in addons/imgui-godot/ImGuiGodot/ImGuiLayer.cs

View workflow job for this annotation

GitHub Actions / .NET 8.0.x

Avoid multiple blank lines

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!;
Expand All @@ -17,24 +18,34 @@ 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
{
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 );

Check failure on line 38 in addons/imgui-godot/ImGuiGodot/ImGuiLayer.cs

View workflow job for this annotation

GitHub Actions / .NET 6.0.x

Name can be simplified

Check failure on line 38 in addons/imgui-godot/ImGuiGodot/ImGuiLayer.cs

View workflow job for this annotation

GitHub Actions / .NET 8.0.x

Name can be simplified
_parent.Connect( ImGuiLayer.SignalName.VisibilityChanged, _onChangeVisibilityCallback );

Check failure on line 39 in addons/imgui-godot/ImGuiGodot/ImGuiLayer.cs

View workflow job for this annotation

GitHub Actions / .NET 6.0.x

Name can be simplified

Check failure on line 39 in addons/imgui-godot/ImGuiGodot/ImGuiLayer.cs

View workflow job for this annotation

GitHub Actions / .NET 8.0.x

Name can be simplified
OnChangeVisibility();
}

public override void _ExitTree() {
if ( _parent is not null ) {
_parent.Disconnect( ImGuiLayer.SignalName.VisibilityChanged, _onChangeVisibilityCallback );
}
}

Check failure on line 47 in addons/imgui-godot/ImGuiGodot/ImGuiLayer.cs

View workflow job for this annotation

GitHub Actions / .NET 6.0.x

Name can be simplified

public override void _PhysicsProcess(double delta)
{
// call NewFrame occasionally if GUI isn't visible, to prevent leaks
Expand Down Expand Up @@ -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();
Expand All @@ -78,6 +92,9 @@ public override void _EnterTree()
ImGuiGD.Init(_window, _subViewportRid, (float)cfg.Get("Scale"),
_headless ? RendererType.Dummy : Enum.Parse<RendererType>((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");
Expand All @@ -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";
Expand All @@ -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 ) {

Check failure on line 145 in addons/imgui-godot/ImGuiGodot/ImGuiLayer.cs

View workflow job for this annotation

GitHub Actions / .NET 8.0.x

Name can be simplified
Instance = null;
}
}

private void OnChangeVisibility()
Expand Down Expand Up @@ -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()
Expand All @@ -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
Expand Down
2 changes: 2 additions & 0 deletions addons/imgui-godot/ImGuiGodot/Internal/State.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
51 changes: 46 additions & 5 deletions addons/imgui-godot/ImGuiGodot/Internal/Viewports.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }

Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand All @@ -284,6 +321,10 @@ public Viewports(Window mainWindow, Rid mainSubViewport)
InitPlatformInterface();
UpdateMonitors();
}

public void Dispose() {
DeInitPlatformInterface();
}

private static void Godot_CreateWindow(ImGuiViewportPtr vp)
{
Expand Down
5 changes: 2 additions & 3 deletions addons/imgui-godot/plugin.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -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"

0 comments on commit d07996a

Please sign in to comment.