Created
July 27, 2025 16:05
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/******************************************************************************************* | |
* | |
* 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 < 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