Skip to content

Instantly share code, notes, and snippets.

@parzivail
Created July 27, 2025 16:05
Show Gist options
  • Save parzivail/3dfd178f4339b8873af244a8b96bdead to your computer and use it in GitHub Desktop.
Save parzivail/3dfd178f4339b8873af244a8b96bdead to your computer and use it in GitHub Desktop.
Hexa-based imgui 1.92.2 backend for Raylib with RendererHasTextures and RendererHasVtxOffset support
/*******************************************************************************************
*
* Based on: https://github.com/raylib-extras/rlImGui-cs (ZLib license)
*
********************************************************************************************/
using System.Numerics;
using System.Runtime.InteropServices;
using Hexa.NET.ImGui;
using Hexa.NET.ImNodes;
using Hexa.NET.ImPlot;
using Raylib_cs;
namespace rlImGui_cs;
public static class rlImGui
{
private static class GL
{
[DllImport("opengl32.dll", EntryPoint = "glPixelStorei", CallingConvention = CallingConvention.Cdecl)]
public static extern void glPixelStorei(PixelStoreParam pname, int param);
public enum PixelStoreParam : uint
{
GL_UNPACK_ROW_LENGTH = 0x0CF2,
GL_UNPACK_ALIGNMENT = 0x0CF5
}
}
private static readonly Dictionary<ImGuiMouseCursor, MouseCursor> MouseCursorMap = new()
{
[ImGuiMouseCursor.Arrow] = MouseCursor.Arrow,
[ImGuiMouseCursor.TextInput] = MouseCursor.IBeam,
[ImGuiMouseCursor.Hand] = MouseCursor.PointingHand,
[ImGuiMouseCursor.ResizeAll] = MouseCursor.ResizeAll,
[ImGuiMouseCursor.ResizeEw] = MouseCursor.ResizeEw,
[ImGuiMouseCursor.ResizeNesw] = MouseCursor.ResizeNesw,
[ImGuiMouseCursor.ResizeNs] = MouseCursor.ResizeNs,
[ImGuiMouseCursor.ResizeNwse] = MouseCursor.ResizeNwse,
[ImGuiMouseCursor.NotAllowed] = MouseCursor.NotAllowed,
};
private static readonly Dictionary<KeyboardKey, ImGuiKey> RaylibKeyMap = new()
{
[KeyboardKey.Apostrophe] = ImGuiKey.Apostrophe,
[KeyboardKey.Comma] = ImGuiKey.Comma,
[KeyboardKey.Minus] = ImGuiKey.Minus,
[KeyboardKey.Period] = ImGuiKey.Period,
[KeyboardKey.Slash] = ImGuiKey.Slash,
[KeyboardKey.Zero] = ImGuiKey.Key0,
[KeyboardKey.One] = ImGuiKey.Key1,
[KeyboardKey.Two] = ImGuiKey.Key2,
[KeyboardKey.Three] = ImGuiKey.Key3,
[KeyboardKey.Four] = ImGuiKey.Key4,
[KeyboardKey.Five] = ImGuiKey.Key5,
[KeyboardKey.Six] = ImGuiKey.Key6,
[KeyboardKey.Seven] = ImGuiKey.Key7,
[KeyboardKey.Eight] = ImGuiKey.Key8,
[KeyboardKey.Nine] = ImGuiKey.Key9,
[KeyboardKey.Semicolon] = ImGuiKey.Semicolon,
[KeyboardKey.Equal] = ImGuiKey.Equal,
[KeyboardKey.A] = ImGuiKey.A,
[KeyboardKey.B] = ImGuiKey.B,
[KeyboardKey.C] = ImGuiKey.C,
[KeyboardKey.D] = ImGuiKey.D,
[KeyboardKey.E] = ImGuiKey.E,
[KeyboardKey.F] = ImGuiKey.F,
[KeyboardKey.G] = ImGuiKey.G,
[KeyboardKey.H] = ImGuiKey.H,
[KeyboardKey.I] = ImGuiKey.I,
[KeyboardKey.J] = ImGuiKey.J,
[KeyboardKey.K] = ImGuiKey.K,
[KeyboardKey.L] = ImGuiKey.L,
[KeyboardKey.M] = ImGuiKey.M,
[KeyboardKey.N] = ImGuiKey.N,
[KeyboardKey.O] = ImGuiKey.O,
[KeyboardKey.P] = ImGuiKey.P,
[KeyboardKey.Q] = ImGuiKey.Q,
[KeyboardKey.R] = ImGuiKey.R,
[KeyboardKey.S] = ImGuiKey.S,
[KeyboardKey.T] = ImGuiKey.T,
[KeyboardKey.U] = ImGuiKey.U,
[KeyboardKey.V] = ImGuiKey.V,
[KeyboardKey.W] = ImGuiKey.W,
[KeyboardKey.X] = ImGuiKey.X,
[KeyboardKey.Y] = ImGuiKey.Y,
[KeyboardKey.Z] = ImGuiKey.Z,
[KeyboardKey.Space] = ImGuiKey.Space,
[KeyboardKey.Escape] = ImGuiKey.Escape,
[KeyboardKey.Enter] = ImGuiKey.Enter,
[KeyboardKey.Tab] = ImGuiKey.Tab,
[KeyboardKey.Backspace] = ImGuiKey.Backspace,
[KeyboardKey.Insert] = ImGuiKey.Insert,
[KeyboardKey.Delete] = ImGuiKey.Delete,
[KeyboardKey.Right] = ImGuiKey.RightArrow,
[KeyboardKey.Left] = ImGuiKey.LeftArrow,
[KeyboardKey.Down] = ImGuiKey.DownArrow,
[KeyboardKey.Up] = ImGuiKey.UpArrow,
[KeyboardKey.PageUp] = ImGuiKey.PageUp,
[KeyboardKey.PageDown] = ImGuiKey.PageDown,
[KeyboardKey.Home] = ImGuiKey.Home,
[KeyboardKey.End] = ImGuiKey.End,
[KeyboardKey.CapsLock] = ImGuiKey.CapsLock,
[KeyboardKey.ScrollLock] = ImGuiKey.ScrollLock,
[KeyboardKey.NumLock] = ImGuiKey.NumLock,
[KeyboardKey.PrintScreen] = ImGuiKey.PrintScreen,
[KeyboardKey.Pause] = ImGuiKey.Pause,
[KeyboardKey.F1] = ImGuiKey.F1,
[KeyboardKey.F2] = ImGuiKey.F2,
[KeyboardKey.F3] = ImGuiKey.F3,
[KeyboardKey.F4] = ImGuiKey.F4,
[KeyboardKey.F5] = ImGuiKey.F5,
[KeyboardKey.F6] = ImGuiKey.F6,
[KeyboardKey.F7] = ImGuiKey.F7,
[KeyboardKey.F8] = ImGuiKey.F8,
[KeyboardKey.F9] = ImGuiKey.F9,
[KeyboardKey.F10] = ImGuiKey.F10,
[KeyboardKey.F11] = ImGuiKey.F11,
[KeyboardKey.F12] = ImGuiKey.F12,
[KeyboardKey.LeftShift] = ImGuiKey.LeftShift,
[KeyboardKey.LeftControl] = ImGuiKey.LeftCtrl,
[KeyboardKey.LeftAlt] = ImGuiKey.LeftAlt,
[KeyboardKey.LeftSuper] = ImGuiKey.LeftSuper,
[KeyboardKey.RightShift] = ImGuiKey.RightShift,
[KeyboardKey.RightControl] = ImGuiKey.RightCtrl,
[KeyboardKey.RightAlt] = ImGuiKey.RightAlt,
[KeyboardKey.RightSuper] = ImGuiKey.RightSuper,
[KeyboardKey.KeyboardMenu] = ImGuiKey.Menu,
[KeyboardKey.LeftBracket] = ImGuiKey.LeftBracket,
[KeyboardKey.Backslash] = ImGuiKey.Backslash,
[KeyboardKey.RightBracket] = ImGuiKey.RightBracket,
[KeyboardKey.Grave] = ImGuiKey.GraveAccent,
[KeyboardKey.Kp0] = ImGuiKey.Keypad0,
[KeyboardKey.Kp1] = ImGuiKey.Keypad1,
[KeyboardKey.Kp2] = ImGuiKey.Keypad2,
[KeyboardKey.Kp3] = ImGuiKey.Keypad3,
[KeyboardKey.Kp4] = ImGuiKey.Keypad4,
[KeyboardKey.Kp5] = ImGuiKey.Keypad5,
[KeyboardKey.Kp6] = ImGuiKey.Keypad6,
[KeyboardKey.Kp7] = ImGuiKey.Keypad7,
[KeyboardKey.Kp8] = ImGuiKey.Keypad8,
[KeyboardKey.Kp9] = ImGuiKey.Keypad9,
[KeyboardKey.KpDecimal] = ImGuiKey.KeypadDecimal,
[KeyboardKey.KpDivide] = ImGuiKey.KeypadDivide,
[KeyboardKey.KpMultiply] = ImGuiKey.KeypadMultiply,
[KeyboardKey.KpSubtract] = ImGuiKey.KeypadSubtract,
[KeyboardKey.KpAdd] = ImGuiKey.KeypadAdd,
[KeyboardKey.KpEnter] = ImGuiKey.KeypadEnter,
[KeyboardKey.KpEqual] = ImGuiKey.KeypadEqual
};
/// <summary>
/// Callback for cases where the user wants to install additional fonts.
/// </summary>
public static event Action<ImGuiIOPtr>? SetupUserFonts;
private static ImGuiContextPtr _imGuiContext;
private static ImNodesContextPtr _imNodesContext;
private static ImPlotContextPtr _imPlotContext;
private static ImGuiMouseCursor _currentMouseCursor = ImGuiMouseCursor.Count;
private static bool _lastFrameFocused;
private static bool _lastControlPressed;
private static bool _lastShiftPressed;
private static bool _lastAltPressed;
private static bool _lastSuperPressed;
private delegate void UserRenderCallback(ImDrawListPtr list, ImDrawCmd cmd);
private unsafe delegate sbyte* GetClipTextCallback(IntPtr userData);
private static readonly unsafe GetClipTextCallback GetClip = rImGuiGetClipText;
internal static unsafe sbyte* rImGuiGetClipText(IntPtr userData)
{
return Raylib.GetClipboardText();
}
private unsafe delegate void SetClipTextCallback(IntPtr userData, sbyte* text);
private static readonly unsafe SetClipTextCallback SetClip = rlImGuiSetClipText;
internal static unsafe void rlImGuiSetClipText(IntPtr userData, sbyte* text)
{
Raylib.SetClipboardText(text);
}
private static bool rlImGuiIsControlDown()
{
return Raylib.IsKeyDown(KeyboardKey.RightControl) || Raylib.IsKeyDown(KeyboardKey.LeftControl);
}
private static bool rlImGuiIsShiftDown()
{
return Raylib.IsKeyDown(KeyboardKey.RightShift) || Raylib.IsKeyDown(KeyboardKey.LeftShift);
}
private static bool rlImGuiIsAltDown()
{
return Raylib.IsKeyDown(KeyboardKey.RightAlt) || Raylib.IsKeyDown(KeyboardKey.LeftAlt);
}
private static bool rlImGuiIsSuperDown()
{
return Raylib.IsKeyDown(KeyboardKey.RightSuper) || Raylib.IsKeyDown(KeyboardKey.LeftSuper);
}
/// <summary>
/// Sets up ImGui, loads fonts and themes
/// </summary>
/// <param name="darkTheme">when true(default) the dark theme is used, when false the light theme is used</param>
/// <param name="enableDocking">when true(not default) docking support will be enabled/param>
public static void Setup(bool darkTheme = true, bool enableDocking = false)
{
BeginInitImGui();
if (darkTheme)
ImGui.StyleColorsDark();
else
ImGui.StyleColorsLight();
if (enableDocking)
ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.DockingEnable;
EndInitImGui();
}
/// <summary>
/// Custom initialization. Not needed if you call Setup. Only needed if you want to add custom setup code.
/// must be followed by EndInitImGui
/// </summary>
public static void BeginInitImGui()
{
_lastFrameFocused = Raylib.IsWindowFocused();
_lastControlPressed = false;
_lastShiftPressed = false;
_lastAltPressed = false;
_lastSuperPressed = false;
_imGuiContext = ImGui.CreateContext();
_imNodesContext = ImNodes.CreateContext();
_imPlotContext = ImPlot.CreateContext();
}
/// <summary>
/// End Custom initialization. Not needed if you call Setup. Only needed if you want to add custom setup code.
/// must be proceeded by BeginInitImGui
/// </summary>
public static unsafe void EndInitImGui()
{
ImGui.SetCurrentContext(_imGuiContext);
ImGui.GetIO().Fonts.AddFontDefault();
var io = ImGui.GetIO();
SetupUserFonts?.Invoke(io);
io.BackendFlags |= ImGuiBackendFlags.HasMouseCursors;
io.BackendFlags |= ImGuiBackendFlags.HasSetMousePos;
io.BackendFlags |= ImGuiBackendFlags.HasGamepad;
io.BackendFlags |= ImGuiBackendFlags.RendererHasTextures;
io.BackendFlags |= ImGuiBackendFlags.RendererHasVtxOffset;
io.MousePos.X = 0;
io.MousePos.Y = 0;
// copy/paste callbacks
var platformIO = ImGui.GetPlatformIO();
platformIO.PlatformSetClipboardTextFn = (void*)Marshal.GetFunctionPointerForDelegate(SetClip);
platformIO.PlatformGetClipboardTextFn = (void*)Marshal.GetFunctionPointerForDelegate(GetClip);
platformIO.PlatformClipboardUserData = null;
}
private static void SetMouseEvent(ImGuiIOPtr io, MouseButton rayMouse, ImGuiMouseButton imGuiMouse)
{
if (Raylib.IsMouseButtonPressed(rayMouse))
io.AddMouseButtonEvent((int)imGuiMouse, true);
else if (Raylib.IsMouseButtonReleased(rayMouse))
io.AddMouseButtonEvent((int)imGuiMouse, false);
}
private static void NewFrame(float dt = -1)
{
var io = ImGui.GetIO();
if (Raylib.IsWindowFullscreen())
{
var monitor = Raylib.GetCurrentMonitor();
io.DisplaySize = new Vector2(Raylib.GetMonitorWidth(monitor), Raylib.GetMonitorHeight(monitor));
}
else
{
io.DisplaySize = new Vector2(Raylib.GetScreenWidth(), Raylib.GetScreenHeight());
}
io.DisplayFramebufferScale = new Vector2(1, 1);
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || Raylib.IsWindowState(ConfigFlags.HighDpiWindow))
io.DisplayFramebufferScale = Raylib.GetWindowScaleDPI();
io.DeltaTime = dt >= 0 ? dt : Raylib.GetFrameTime();
if (io.WantSetMousePos)
{
Raylib.SetMousePosition((int)io.MousePos.X, (int)io.MousePos.Y);
}
else
{
io.AddMousePosEvent(Raylib.GetMouseX(), Raylib.GetMouseY());
}
SetMouseEvent(io, MouseButton.Left, ImGuiMouseButton.Left);
SetMouseEvent(io, MouseButton.Right, ImGuiMouseButton.Right);
SetMouseEvent(io, MouseButton.Middle, ImGuiMouseButton.Middle);
SetMouseEvent(io, MouseButton.Forward, ImGuiMouseButton.Middle + 1);
SetMouseEvent(io, MouseButton.Back, ImGuiMouseButton.Middle + 2);
var wheelMove = Raylib.GetMouseWheelMoveV();
io.AddMouseWheelEvent(wheelMove.X, wheelMove.Y);
if ((io.ConfigFlags & ImGuiConfigFlags.NoMouseCursorChange) == 0)
{
var imguiCursor = ImGui.GetMouseCursor();
if (imguiCursor != _currentMouseCursor || io.MouseDrawCursor)
{
_currentMouseCursor = imguiCursor;
if (io.MouseDrawCursor || imguiCursor == ImGuiMouseCursor.None)
{
Raylib.HideCursor();
}
else
{
Raylib.ShowCursor();
if ((io.ConfigFlags & ImGuiConfigFlags.NoMouseCursorChange) == 0)
Raylib.SetMouseCursor(MouseCursorMap.GetValueOrDefault(imguiCursor, MouseCursor.Default));
}
}
}
}
private static void FrameEvents()
{
var io = ImGui.GetIO();
bool focused = Raylib.IsWindowFocused();
if (focused != _lastFrameFocused)
io.AddFocusEvent(focused);
_lastFrameFocused = focused;
// handle the modifier key events so that shortcuts work
var ctrlDown = rlImGuiIsControlDown();
if (ctrlDown != _lastControlPressed)
io.AddKeyEvent(ImGuiKey.ModCtrl, ctrlDown);
_lastControlPressed = ctrlDown;
var shiftDown = rlImGuiIsShiftDown();
if (shiftDown != _lastShiftPressed)
io.AddKeyEvent(ImGuiKey.ModShift, shiftDown);
_lastShiftPressed = shiftDown;
var altDown = rlImGuiIsAltDown();
if (altDown != _lastAltPressed)
io.AddKeyEvent(ImGuiKey.ModAlt, altDown);
_lastAltPressed = altDown;
var superDown = rlImGuiIsSuperDown();
if (superDown != _lastSuperPressed)
io.AddKeyEvent(ImGuiKey.ModSuper, superDown);
_lastSuperPressed = superDown;
// get the pressed keys, they are in event order
var keyId = Raylib.GetKeyPressed();
while (keyId != 0)
{
var key = (KeyboardKey)keyId;
if (RaylibKeyMap.TryGetValue(key, out var value))
io.AddKeyEvent(value, true);
keyId = Raylib.GetKeyPressed();
}
// look for any keys that were down last frame and see if they were down and are released
foreach (var (rlKey, imKey) in RaylibKeyMap)
{
if (Raylib.IsKeyReleased(rlKey))
io.AddKeyEvent(imKey, false);
}
// add the text input in order
var pressed = Raylib.GetCharPressed();
while (pressed != 0)
{
io.AddInputCharacter((uint)pressed);
pressed = Raylib.GetCharPressed();
}
// gamepads
if ((io.ConfigFlags & ImGuiConfigFlags.NavEnableGamepad) != 0 && Raylib.IsGamepadAvailable(0))
{
HandleGamepadButtonEvent(io, GamepadButton.LeftFaceUp, ImGuiKey.GamepadDpadUp);
HandleGamepadButtonEvent(io, GamepadButton.LeftFaceRight, ImGuiKey.GamepadDpadRight);
HandleGamepadButtonEvent(io, GamepadButton.LeftFaceDown, ImGuiKey.GamepadDpadDown);
HandleGamepadButtonEvent(io, GamepadButton.LeftFaceLeft, ImGuiKey.GamepadDpadLeft);
HandleGamepadButtonEvent(io, GamepadButton.RightFaceUp, ImGuiKey.GamepadFaceUp);
HandleGamepadButtonEvent(io, GamepadButton.RightFaceRight, ImGuiKey.GamepadFaceLeft);
HandleGamepadButtonEvent(io, GamepadButton.RightFaceDown, ImGuiKey.GamepadFaceDown);
HandleGamepadButtonEvent(io, GamepadButton.RightFaceLeft, ImGuiKey.GamepadFaceRight);
HandleGamepadButtonEvent(io, GamepadButton.LeftTrigger1, ImGuiKey.GamepadL1);
HandleGamepadButtonEvent(io, GamepadButton.LeftTrigger2, ImGuiKey.GamepadL2);
HandleGamepadButtonEvent(io, GamepadButton.RightTrigger1, ImGuiKey.GamepadR1);
HandleGamepadButtonEvent(io, GamepadButton.RightTrigger2, ImGuiKey.GamepadR2);
HandleGamepadButtonEvent(io, GamepadButton.LeftThumb, ImGuiKey.GamepadL3);
HandleGamepadButtonEvent(io, GamepadButton.RightThumb, ImGuiKey.GamepadR3);
HandleGamepadButtonEvent(io, GamepadButton.MiddleLeft, ImGuiKey.GamepadStart);
HandleGamepadButtonEvent(io, GamepadButton.MiddleRight, ImGuiKey.GamepadBack);
// left stick
HandleGamepadStickEvent(io, GamepadAxis.LeftX, ImGuiKey.GamepadLStickLeft, ImGuiKey.GamepadLStickRight);
HandleGamepadStickEvent(io, GamepadAxis.LeftY, ImGuiKey.GamepadLStickUp, ImGuiKey.GamepadLStickDown);
// right stick
HandleGamepadStickEvent(io, GamepadAxis.RightX, ImGuiKey.GamepadRStickLeft, ImGuiKey.GamepadRStickRight);
HandleGamepadStickEvent(io, GamepadAxis.RightY, ImGuiKey.GamepadRStickUp, ImGuiKey.GamepadRStickDown);
}
}
private static void HandleGamepadButtonEvent(ImGuiIOPtr io, GamepadButton button, ImGuiKey key)
{
if (Raylib.IsGamepadButtonPressed(0, button))
io.AddKeyEvent(key, true);
else if (Raylib.IsGamepadButtonReleased(0, button))
io.AddKeyEvent(key, false);
}
private static void HandleGamepadStickEvent(ImGuiIOPtr io, GamepadAxis axis, ImGuiKey negKey, ImGuiKey posKey)
{
const float deadZone = 0.20f;
var axisValue = Raylib.GetGamepadAxisMovement(0, axis);
io.AddKeyAnalogEvent(negKey, axisValue < -deadZone, axisValue < -deadZone ? -axisValue : 0);
io.AddKeyAnalogEvent(posKey, axisValue > deadZone, axisValue > deadZone ? axisValue : 0);
}
/// <summary>
/// Starts a new ImGui Frame
/// </summary>
/// <param name="dt">optional delta time, any value &lt; 0 will use raylib GetFrameTime</param>
public static void Begin(float dt = -1)
{
ImGui.SetCurrentContext(_imGuiContext);
ImNodes.SetImGuiContext(_imGuiContext);
ImPlot.SetImGuiContext(_imGuiContext);
ImNodes.SetCurrentContext(_imNodesContext);
ImPlot.SetCurrentContext(_imPlotContext);
NewFrame(dt);
FrameEvents();
ImGui.NewFrame();
}
private static void EnableScissor(float x, float y, float width, float height)
{
Rlgl.EnableScissorTest();
var io = ImGui.GetIO();
var scale = new Vector2(1.0f, 1.0f);
if (Raylib.IsWindowState(ConfigFlags.HighDpiWindow) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
scale = io.DisplayFramebufferScale;
Rlgl.Scissor((int)(x * scale.X),
(int)((io.DisplaySize.Y - (int)(y + height)) * scale.Y),
(int)(width * scale.X),
(int)(height * scale.Y));
}
private static void TriangleVert(ImDrawVertPtr idxVert)
{
var color = ImGui.ColorConvertU32ToFloat4(idxVert.Col);
Rlgl.Color4f(color.X, color.Y, color.Z, color.W);
Rlgl.TexCoord2f(idxVert.Uv.X, idxVert.Uv.Y);
Rlgl.Vertex2f(idxVert.Pos.X, idxVert.Pos.Y);
}
private static unsafe void RenderTriangles(uint count, uint indexStart, uint vertexStart, ImVector<ushort> indexBuffer, ImVector<ImDrawVert> vertBuffer, ImTextureID imTextureId)
{
if (count < 3)
return;
uint textureId = 0;
if (imTextureId.Handle != 0)
{
textureId = (uint)imTextureId.Handle;
}
Rlgl.Begin(DrawMode.Triangles);
Rlgl.SetTexture(textureId);
for (var i = 0; i < count; i += 3)
{
if (Rlgl.CheckRenderBatchLimit(3))
{
Rlgl.Begin(DrawMode.Triangles);
Rlgl.SetTexture(textureId);
}
var indexA = indexBuffer.Data[(int)indexStart + i] + vertexStart;
var indexB = indexBuffer.Data[(int)indexStart + i + 1] + vertexStart;
var indexC = indexBuffer.Data[(int)indexStart + i + 2] + vertexStart;
var vertexA = vertBuffer.Data[indexA];
var vertexB = vertBuffer.Data[indexB];
var vertexC = vertBuffer.Data[indexC];
TriangleVert(&vertexA);
TriangleVert(&vertexB);
TriangleVert(&vertexC);
}
Rlgl.End();
}
private static unsafe void RenderData()
{
Rlgl.DrawRenderBatchActive();
Rlgl.DisableBackfaceCulling();
var data = ImGui.GetDrawData();
for (var i = 0; i < data.Textures.Size; i++)
if (data.Textures[i].Status != ImTextureStatus.Ok)
UpdateTexture(ref data, i);
for (var l = 0; l < data.CmdListsCount; l++)
{
var commandList = data.CmdLists.Data[l];
for (var cmdIndex = 0; cmdIndex < commandList.CmdBuffer.Size; cmdIndex++)
{
var cmd = commandList.CmdBuffer.Data[cmdIndex];
EnableScissor(cmd.ClipRect.X - data.DisplayPos.X, cmd.ClipRect.Y - data.DisplayPos.Y, cmd.ClipRect.Z - (cmd.ClipRect.X - data.DisplayPos.X), cmd.ClipRect.W - (cmd.ClipRect.Y - data.DisplayPos.Y));
if (cmd.UserCallback != (void*)IntPtr.Zero)
{
var cb = Marshal.GetDelegateForFunctionPointer<UserRenderCallback>((IntPtr)cmd.UserCallback);
cb(commandList, cmd);
continue;
}
RenderTriangles(cmd.ElemCount, cmd.IdxOffset, cmd.VtxOffset, commandList.IdxBuffer, commandList.VtxBuffer, cmd.GetTexID());
Rlgl.DrawRenderBatchActive();
}
}
Rlgl.SetTexture(0);
Rlgl.DisableScissorTest();
Rlgl.EnableBackfaceCulling();
}
private static unsafe void UpdateTexture(ref ImDrawDataPtr data, int textureIndex)
{
var tex = data.Textures[textureIndex];
if (tex.Status == ImTextureStatus.WantCreate)
{
var image = new Image
{
Data = tex.Pixels,
Width = tex.Width,
Height = tex.Height,
Mipmaps = 1,
Format = tex.Format switch
{
ImTextureFormat.Rgba32 => PixelFormat.UncompressedR8G8B8A8,
ImTextureFormat.Alpha8 => PixelFormat.UncompressedGrayscale,
_ => throw new ArgumentOutOfRangeException()
},
};
var texture = Raylib.LoadTextureFromImage(image);
tex.SetTexID(texture.Id);
tex.SetStatus(ImTextureStatus.Ok);
}
else if (tex.Status == ImTextureStatus.WantUpdates)
{
GL.glPixelStorei(GL.PixelStoreParam.GL_UNPACK_ROW_LENGTH, tex.Width);
GL.glPixelStorei(GL.PixelStoreParam.GL_UNPACK_ALIGNMENT, 1);
for (var i = 0; i < tex.Updates.Size; i++)
{
var update = tex.Updates[i];
var pixels = tex.GetPixelsAt(update.X, update.Y);
Rlgl.UpdateTexture(
(uint)tex.GetTexID(),
update.X, update.Y, update.W, update.H,
tex.Format switch
{
ImTextureFormat.Rgba32 => PixelFormat.UncompressedR8G8B8A8,
ImTextureFormat.Alpha8 => PixelFormat.UncompressedGrayscale,
_ => throw new ArgumentOutOfRangeException()
},
pixels
);
}
GL.glPixelStorei(GL.PixelStoreParam.GL_UNPACK_ROW_LENGTH, 0);
tex.SetStatus(ImTextureStatus.Ok);
}
else if (tex is { Status: ImTextureStatus.WantDestroy, UnusedFrames: > 0 })
{
Rlgl.UnloadTexture((uint)tex.GetTexID());
tex.SetTexID(ImTextureID.Null);
tex.SetStatus(ImTextureStatus.Destroyed);
}
}
/// <summary>
/// Ends an ImGui frame and submits all ImGui drawing to raylib for processing.
/// </summary>
public static void End()
{
var io = ImGui.GetIO();
if ((io.ConfigFlags & ImGuiConfigFlags.ViewportsEnable) != 0)
{
ImGui.UpdatePlatformWindows();
ImGui.RenderPlatformWindowsDefault();
}
ImGui.SetCurrentContext(_imGuiContext);
ImGui.Render();
RenderData();
}
/// <summary>
/// Cleanup ImGui and unload font atlas
/// </summary>
public static void Shutdown()
{
ImGui.DestroyContext();
}
/// <summary>
/// Draw a texture as an image in an ImGui Context
/// Uses the current ImGui Cursor position and the full texture size.
/// </summary>
/// <param name="image">The raylib texture to draw</param>
public static void Image(Texture2D image)
{
ImGui.Image(
new ImTextureRef()
{
TexID = image.Id
},
new Vector2(image.Width, image.Height)
);
}
/// <summary>
/// Draw a texture as an image in an ImGui Context at a specific size
/// Uses the current ImGui Cursor position and the specified width and height
/// The image will be scaled up or down to fit as needed
/// </summary>
/// <param name="image">The raylib texture to draw</param>
/// <param name="width">The width of the drawn image</param>
/// <param name="height">The height of the drawn image</param>
public static void ImageSize(Texture2D image, int width, int height)
{
ImGui.Image(
new ImTextureRef()
{
TexID = image.Id
},
new Vector2(width, height)
);
}
/// <summary>
/// Draw a texture as an image in an ImGui Context at a specific size
/// Uses the current ImGui Cursor position and the specified size
/// The image will be scaled up or down to fit as needed
/// </summary>
/// <param name="image">The raylib texture to draw</param>
/// <param name="size">The size of drawn image</param>
public static void ImageSize(Texture2D image, Vector2 size)
{
ImGui.Image(
new ImTextureRef()
{
TexID = image.Id
},
size
);
}
/// <summary>
/// Draw a portion texture as an image in an ImGui Context at a defined size
/// Uses the current ImGui Cursor position and the specified size
/// The image will be scaled up or down to fit as needed
/// </summary>
/// <param name="image">The raylib texture to draw</param>
/// <param name="destWidth">The width of the drawn image</param>
/// <param name="destHeight">The height of the drawn image</param>
/// <param name="sourceRect">The portion of the texture to draw as an image. Negative values for the width and height will flip the image</param>
public static void ImageRect(Texture2D image, int destWidth, int destHeight, Rectangle sourceRect)
{
var uv0 = new Vector2();
var uv1 = new Vector2();
if (sourceRect.Width < 0)
{
uv0.X = -(sourceRect.X / image.Width);
uv1.X = uv0.X - Math.Abs(sourceRect.Width) / image.Width;
}
else
{
uv0.X = sourceRect.X / image.Width;
uv1.X = uv0.X + sourceRect.Width / image.Width;
}
if (sourceRect.Height < 0)
{
uv0.Y = -(sourceRect.Y / image.Height);
uv1.Y = uv0.Y - Math.Abs(sourceRect.Height) / image.Height;
}
else
{
uv0.Y = sourceRect.Y / image.Height;
uv1.Y = uv0.Y + sourceRect.Height / image.Height;
}
ImGui.Image(
new ImTextureRef()
{
TexID = image.Id
},
new Vector2(destWidth, destHeight),
uv0, uv1
);
}
/// <summary>
/// Draws a render texture as an image an ImGui Context, automatically flipping the Y axis so it will show correctly on screen
/// </summary>
/// <param name="image">The render texture to draw</param>
public static void ImageRenderTexture(RenderTexture2D image)
{
ImageRect(image.Texture, image.Texture.Width, image.Texture.Height, new Rectangle(0, 0, image.Texture.Width, -image.Texture.Height));
}
/// <summary>
/// Draws a render texture as an image to the current ImGui Context, flipping the Y axis so it will show correctly on the screen
/// The texture will be scaled to fit the content are available, centered if desired
/// </summary>
/// <param name="image">The render texture to draw</param>
/// <param name="center">When true the texture will be centered in the content area. When false the image will be left and top justified</param>
public static void ImageRenderTextureFit(RenderTexture2D image, bool center = true)
{
var area = ImGui.GetContentRegionAvail();
var scale = area.X / image.Texture.Width;
var y = image.Texture.Height * scale;
if (y > area.Y)
{
scale = area.Y / image.Texture.Height;
}
var sizeX = (int)(image.Texture.Width * scale);
var sizeY = (int)(image.Texture.Height * scale);
if (center)
{
ImGui.SetCursorPosX(0);
ImGui.SetCursorPosX(area.X / 2 - sizeX / 2f);
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + (area.Y / 2 - sizeY / 2f));
}
ImageRect(image.Texture, sizeX, sizeY, new Rectangle(0, 0, image.Texture.Width, -image.Texture.Height));
}
/// <summary>
/// Draws a texture as an image button in an ImGui context. Uses the current ImGui cursor position and the full size of the texture
/// </summary>
/// <param name="name">The display name and ImGui ID for the button</param>
/// <param name="image">The texture to draw</param>
/// <returns>True if the button was clicked</returns>
public static bool ImageButton(string name, Texture2D image)
{
return ImageButtonSize(name, image, new Vector2(image.Width, image.Height));
}
/// <summary>
/// Draws a texture as an image button in an ImGui context. Uses the current ImGui cursor position and the specified size.
/// </summary>
/// <param name="name">The display name and ImGui ID for the button</param>
/// <param name="image">The texture to draw</param>
/// <param name="size">The size of the button/param>
/// <returns>True if the button was clicked</returns>
public static bool ImageButtonSize(string name, Texture2D image, Vector2 size)
{
return ImGui.ImageButton(
name,
new ImTextureRef()
{
TexID = image.Id
},
size
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment