using System.Numerics; using ImGuiNET; using Raylib_cs; using DaggerFramework.Rendering; namespace DaggerFramework { public class ImGuiRenderLayer : Layer { protected override void OnDraw(in Renderer renderer) { Layout(); _controller.Draw(in renderer); } protected virtual void Layout() { } protected override void OnStart() { _controller = new ImGuiController(); _controller.Load(Scene.Renderer.WindowSize); } protected override void OnUpdate(double dt) { _controller.Update(dt, Input); } private ImGuiController _controller; } public class ImGuiController : IDisposable, IDrawable { public ImGuiController() { _context = ImGui.CreateContext(); ImGui.SetCurrentContext(_context); } public void Dispose() { ImGui.DestroyContext(); } public void Load(Vector2 size) { ImGuiIOPtr io = ImGui.GetIO(); io.Fonts.AddFontDefault(); Resize(size); LoadFontTexture(); SetupInput(); ImGui.NewFrame(); } unsafe void LoadFontTexture() { ImGuiIOPtr io = ImGui.GetIO(); io.Fonts.GetTexDataAsRGBA32(out byte* pixels, out int width, out int height); // TODO: use engine API instead of Raylib. Image image = new Image { data = pixels, width = width, height = height, mipmaps = 1, format = PixelFormat.PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, }; _fontTexture = Raylib.LoadTextureFromImage(image); io.Fonts.SetTexID(new IntPtr(_fontTexture.id)); io.Fonts.ClearTexData(); } private void SetupInput() { ImGuiIOPtr io = ImGui.GetIO(); // Setup config flags io.ConfigFlags |= ImGuiConfigFlags.NavEnableKeyboard; // Setup back-end capabilities flags io.BackendFlags |= ImGuiBackendFlags.HasMouseCursors; io.BackendFlags |= ImGuiBackendFlags.HasSetMousePos; // Keyboard mapping. ImGui will use those indices to peek into the io.KeysDown[] array. io.KeyMap[(int)ImGuiKey.Tab] = (int)KeyboardKey.Tab; io.KeyMap[(int)ImGuiKey.LeftArrow] = (int)KeyboardKey.Left; io.KeyMap[(int)ImGuiKey.RightArrow] = (int)KeyboardKey.Right; io.KeyMap[(int)ImGuiKey.UpArrow] = (int)KeyboardKey.Up; io.KeyMap[(int)ImGuiKey.DownArrow] = (int)KeyboardKey.Down; io.KeyMap[(int)ImGuiKey.PageUp] = (int)KeyboardKey.PageUp; io.KeyMap[(int)ImGuiKey.PageDown] = (int)KeyboardKey.PageDown; io.KeyMap[(int)ImGuiKey.Home] = (int)KeyboardKey.Home; io.KeyMap[(int)ImGuiKey.End] = (int)KeyboardKey.End; io.KeyMap[(int)ImGuiKey.Insert] = (int)KeyboardKey.Insert; io.KeyMap[(int)ImGuiKey.Delete] = (int)KeyboardKey.Delete; io.KeyMap[(int)ImGuiKey.Backspace] = (int)KeyboardKey.Backspace; io.KeyMap[(int)ImGuiKey.Space] = (int)KeyboardKey.Spacebar; io.KeyMap[(int)ImGuiKey.Enter] = (int)KeyboardKey.Enter; io.KeyMap[(int)ImGuiKey.Escape] = (int)KeyboardKey.Escape; io.KeyMap[(int)ImGuiKey.A] = (int)KeyboardKey.A; io.KeyMap[(int)ImGuiKey.C] = (int)KeyboardKey.C; io.KeyMap[(int)ImGuiKey.V] = (int)KeyboardKey.V; io.KeyMap[(int)ImGuiKey.X] = (int)KeyboardKey.X; io.KeyMap[(int)ImGuiKey.Y] = (int)KeyboardKey.Y; io.KeyMap[(int)ImGuiKey.Z] = (int)KeyboardKey.Z; } public void Resize(Vector2 size) { ImGuiIOPtr io = ImGui.GetIO(); io.DisplaySize = size / _scaleFactor; } public void Update(double dt, InputHandler input) { ImGuiIOPtr io = ImGui.GetIO(); io.DisplayFramebufferScale = Vector2.One; io.DeltaTime = (float)dt; UpdateKeyboard(input); UpdateMouse(input); ImGui.NewFrame(); } private void UpdateKeyboard(InputHandler input) { ImGuiIOPtr io = ImGui.GetIO(); // Modifiers are not reliable across systems io.KeyCtrl = io.KeysDown[(int)KeyboardKey.LeftControl] || io.KeysDown[(int)KeyboardKey.RightControl]; io.KeyShift = io.KeysDown[(int)KeyboardKey.LeftShift] || io.KeysDown[(int)KeyboardKey.RightShift]; io.KeyAlt = io.KeysDown[(int)KeyboardKey.LeftAlt] || io.KeysDown[(int)KeyboardKey.RightAlt]; io.KeySuper = io.KeysDown[(int)KeyboardKey.LeftSuper] || io.KeysDown[(int)KeyboardKey.RightSuper]; // Key states for (int i = (int)KeyboardKey.Spacebar; i < (int)KeyboardKey.KBMenu + 1; i++) { io.KeysDown[i] = input.IsKeyboardKeyDown((KeyboardKey)i); } // Key input int keyPressed = input.GetCharPressed(); if (keyPressed != 0) { io.AddInputCharacter((uint)keyPressed); } } private void UpdateMouse(InputHandler input) { ImGuiIOPtr io = ImGui.GetIO(); // Store button states for (int i = 0; i < io.MouseDown.Count; i++) { io.MouseDown[i] = input.IsMouseButtonDown((MouseButton)i); } // Mouse scroll io.MouseWheel += input.GetMouseWheelMovement(); // Mouse position Vector2 mousePosition = io.MousePos; // TODO: // bool focused = Raylib.IsWindowFocused(); if (io.WantSetMousePos) { input.SetMousePosition(mousePosition); } else { io.MousePos = input.GetMousePosition(); } // Mouse cursor state if ((io.ConfigFlags & ImGuiConfigFlags.NoMouseCursorChange) == 0 || input.IsCursorHidden()) { ImGuiMouseCursor cursor = ImGui.GetMouseCursor(); if (cursor == ImGuiMouseCursor.None || io.MouseDrawCursor) { input.HideCursor(); } else { input.ShowCursor(); } } } private void Render() { ImGui.Render(); } private void RenderCommandLists(ImDrawDataPtr data) { // Scale coordinates for retina displays (screen coordinates != framebuffer coordinates) int fbWidth = (int)(data.DisplaySize.X * data.FramebufferScale.X); int fbHeight = (int)(data.DisplaySize.Y * data.FramebufferScale.Y); // Avoid rendering if display is minimized or if the command list is empty if (fbWidth <= 0 || fbHeight <= 0 || data.CmdListsCount == 0) { return; } Rlgl.rlDrawRenderBatchActive(); Rlgl.rlDisableBackfaceCulling(); Rlgl.rlEnableScissorTest(); data.ScaleClipRects(ImGui.GetIO().DisplayFramebufferScale); for (int n = 0; n < data.CmdListsCount; n++) { int idxOffset = 0; ImDrawListPtr cmdList = data.CmdListsRange[n]; // Vertex buffer and index buffer generated by DearImGui ImPtrVector vtxBuffer = cmdList.VtxBuffer; ImVector idxBuffer = cmdList.IdxBuffer; for (int cmdi = 0; cmdi < cmdList.CmdBuffer.Size; cmdi++) { ImDrawCmdPtr pcmd = cmdList.CmdBuffer[cmdi]; // Scissor rect Vector2 pos = data.DisplayPos; int rectX = (int)((pcmd.ClipRect.X - pos.X) * data.FramebufferScale.X); int rectY = (int)((pcmd.ClipRect.Y - pos.Y) * data.FramebufferScale.Y); int rectW = (int)((pcmd.ClipRect.Z - rectX) * data.FramebufferScale.Y); int rectH = (int)((pcmd.ClipRect.W - rectY) * data.FramebufferScale.Y); Rlgl.rlScissor(rectX, Raylib.GetScreenHeight() - (rectY + rectH), rectW, rectH); if (pcmd.UserCallback != IntPtr.Zero) { // pcmd.UserCallback(cmdList, pcmd); idxOffset += (int)pcmd.ElemCount; } else { DrawTriangles(pcmd.ElemCount, idxOffset, (int)pcmd.VtxOffset, idxBuffer, vtxBuffer, pcmd.TextureId); idxOffset += (int)pcmd.ElemCount; Rlgl.rlDrawRenderBatchActive(); } } } Rlgl.rlSetTexture(0); Rlgl.rlDisableScissorTest(); Rlgl.rlEnableBackfaceCulling(); } private Color GetColor(uint hexValue) { Color color; color.r = (byte)(hexValue & 0xFF); color.g = (byte)((hexValue >> 8) & 0xFF); color.b = (byte)((hexValue >> 16) & 0xFF); color.a = (byte)((hexValue >> 24) & 0xFF); return color; } void DrawTriangleVertex(ImDrawVertPtr idxVert) { Color c = GetColor(idxVert.col); Rlgl.rlColor4ub(c.r, c.g, c.b, c.a); Rlgl.rlTexCoord2f(idxVert.uv.X, idxVert.uv.Y); Rlgl.rlVertex2f(idxVert.pos.X, idxVert.pos.Y); } // Draw the imgui triangle data private void DrawTriangles(uint count, int idxOffset, int vtxOffset, ImVector idxBuffer, ImPtrVector idxVert, IntPtr textureId) { ushort index = 0; ImDrawVertPtr vertex; if (Rlgl.rlCheckRenderBatchLimit((int)count * 3)) { Rlgl.rlDrawRenderBatchActive(); } Rlgl.rlBegin(DrawMode.TRIANGLES); Rlgl.rlSetTexture((uint)textureId); for (int i = 0; i <= (count - 3); i += 3) { index = idxBuffer[idxOffset + i]; vertex = idxVert[vtxOffset + index]; DrawTriangleVertex(vertex); index = idxBuffer[idxOffset + i + 1]; vertex = idxVert[vtxOffset + index]; DrawTriangleVertex(vertex); index = idxBuffer[idxOffset + i + 2]; vertex = idxVert[vtxOffset + index]; DrawTriangleVertex(vertex); } Rlgl.rlEnd(); } public void Draw(in Renderer renderer) { ImGui.Render(); RenderCommandLists(ImGui.GetDrawData()); } private IntPtr _context; private Texture2D _fontTexture; private Vector2 _scaleFactor = Vector2.One; } }