Dear ImGui + Raylib + C#

Dear ImGui is slowly becoming the fastest way to develop user interface applications, whether for real-time applications like video games or even as a standard UI for modern software.

Dear ImGui is slowly becoming the fastest way to develop user interface applications, whether for real-time applications like video games or even as a standard UI for modern software.

There are a huge number of articles on the subject, and huge numbers of software and games that use Dear ImGui. There are a whole bunch of bindings for various languages as well. The one we will be looking at today will be C#.

Why are we discussing this?

Dear ImGui is a powerful framework for user interface development. Raylib is also a phenomenal library, allowing you to leverage hardware-acceleration without needing to use directly OpenGL, DirectX or other low-level frameworks.

These two libraries make for a powerful combination, and on .NET Core 6 can allow you to make software or video games with ease and be usable on Windows, Linux and Mac.

C# is one of my favourite programming languages. Although it is garbage-collected, C# is still efficient for the low-level libraries we will be using and resources are surprisingly well managed for the low-level bindings.

How do you get started?

First off, we need to create a project in Visual Studio. In this tutorial, I will be using Visual Studio 2022 Preview. Preview is not required and the standard Community edition is fine.

When creating a new project, we need to make sure to select "Console App" under C#. The Console App is required as we don't want to see any other form of boilerplate code like we might see in WinForms or another API. The way to tell this is .NET Core is the indication it can be built on Windows, Linux and Mac.

Once you have confirmed selection, click "Next".

Fill in your new project details in the next screen. See here that I do not check the option "Place solution and project in the same directory". If you need to create additional projects, it is better practice to simply leave this unchecked.

Click "Next".

Finally, select the Framework. In this case, we are selecting .NET 6.0. Also, what I do is check the option "Do not use top-level statements". What this does is give you a traditional C# code layout, including a namespace wrapper for your main entry point and using statements. This is what I prefer, so I will use this method.

If you opt to leave this unchecked, tutorials may look a little different so you may have to make some changes while you follow along. If you are new to C#, I would suggest keeping to the options I supply so the tutorials match up.

Finally, click "Create".

Setting up the Project

When we launch our new project for the first time, we get the following screen.

This will be changed later, but we will need to do some setup before we can continue.

First, we need to install some NuGet packages. Let's right-click our project (NOT the solution) in the Solution Explorer and go to "Manage NuGet Packages...".

Under "Browse", type in "raylib" to begin with.

Make sure you select "Raylib-cs" and not any of the other options. This is the binding we will be working with and is supported by the owner of the Raylib library.

Click "Install" in the other pane for the latest release.

Next, type in "imgui" and do the same.

It should be the top option, which in this case is ImGui.NET. This binding is made by the same person who makes Veldrid, so there are native bindings for this. However, Veldrid is another graphics framework like Raylib, but we will not be using Veldrid in this example.

Install "ImGui.NET" and this completes this section of the tutorial.

Creating our Controller

Next, we need to create some form of controller that captures all the Raylib inputs, and renders ImGui related draw lists into the Raylib pipeline.

Instead of trying to explain this tedious process, you can simply copy and paste the below code into a file you may call "Controller.cs".

This code has been adapted from the Raylib-ImGui binding originally written in C++, which can be found here.

using System;
using System.Collections.Generic;
using System.Buffers;
using System.Numerics;
using System.Reflection;
using System.IO;
using System.Runtime.CompilerServices;
using ImGuiNET;
using Raylib_cs;
using System.Runtime.InteropServices;

namespace StoryCreator
{

internal unsafe class ImGuiController
{

    private Vector2 mousePosition;
    private Vector2 displaySize;
    private float delta;
    private bool isKeyCtrl;
    private bool isKeyShift;
    private bool isKeyAlt;
    private bool isKeySuper;

    static double g_Time = 0.0;
    static bool g_UnloadAtlas = false;
    static uint g_AtlasTexID = 0;

    static string GetClipboardText()
    {
        return Raylib.GetClipboardText_();
    }

    static void SetClipboardText(string text)
    {
        Raylib.SetClipboardText(text);
    }

    public ImGuiController()
    {
        Init();
    }

    private void Init()
    {
        var io = ImGui.GetIO();

        io.KeyMap[(int)ImGuiKey.Tab] = (int)KeyboardKey.KEY_TAB;
        io.KeyMap[(int)ImGuiKey.LeftArrow] = (int)KeyboardKey.KEY_LEFT;
        io.KeyMap[(int)ImGuiKey.RightArrow] = (int)KeyboardKey.KEY_RIGHT;
        io.KeyMap[(int)ImGuiKey.UpArrow] = (int)KeyboardKey.KEY_UP;
        io.KeyMap[(int)ImGuiKey.DownArrow] = (int)KeyboardKey.KEY_DOWN;
        io.KeyMap[(int)ImGuiKey.PageUp] = (int)KeyboardKey.KEY_PAGE_UP;
        io.KeyMap[(int)ImGuiKey.PageDown] = (int)KeyboardKey.KEY_PAGE_DOWN;
        io.KeyMap[(int)ImGuiKey.Home] = (int)KeyboardKey.KEY_HOME;
        io.KeyMap[(int)ImGuiKey.End] = (int)KeyboardKey.KEY_END;
        io.KeyMap[(int)ImGuiKey.Insert] = (int)KeyboardKey.KEY_INSERT;
        io.KeyMap[(int)ImGuiKey.Delete] = (int)KeyboardKey.KEY_DELETE;
        io.KeyMap[(int)ImGuiKey.Backspace] = (int)KeyboardKey.KEY_BACKSPACE;
        io.KeyMap[(int)ImGuiKey.Space] = (int)KeyboardKey.KEY_SPACE;
        io.KeyMap[(int)ImGuiKey.Enter] = (int)KeyboardKey.KEY_ENTER;
        io.KeyMap[(int)ImGuiKey.Escape] = (int)KeyboardKey.KEY_ESCAPE;
        io.KeyMap[(int)ImGuiKey.KeypadEnter] = (int)KeyboardKey.KEY_KP_ENTER;
        io.KeyMap[(int)ImGuiKey.A] = (int)KeyboardKey.KEY_A;
        io.KeyMap[(int)ImGuiKey.C] = (int)KeyboardKey.KEY_C;
        io.KeyMap[(int)ImGuiKey.V] = (int)KeyboardKey.KEY_V;
        io.KeyMap[(int)ImGuiKey.X] = (int)KeyboardKey.KEY_X;
        io.KeyMap[(int)ImGuiKey.Y] = (int)KeyboardKey.KEY_Y;
        io.KeyMap[(int)ImGuiKey.Z] = (int)KeyboardKey.KEY_Z;

        mousePosition = new Vector2(0, 0);
        io.MousePos = mousePosition;
		
        // Use this space to add more fonts
        LoadDefaultFontAtlas();
    }

    public void Shutdown()
    {
        if (g_UnloadAtlas)
        {
            ImGui.GetIO().Fonts.ClearFonts();
        }
        g_Time = 0.0;
    }

    private void UpdateMousePosAndButtons()
    {
        var io = ImGui.GetIO();

        if (io.WantSetMousePos)
            Raylib.SetMousePosition((int)io.MousePos.X, (int)io.MousePos.Y);

        io.MouseDown[0] = Raylib.IsMouseButtonDown(MouseButton.MOUSE_LEFT_BUTTON);
        io.MouseDown[1] = Raylib.IsMouseButtonDown(MouseButton.MOUSE_RIGHT_BUTTON);
        io.MouseDown[2] = Raylib.IsMouseButtonDown(MouseButton.MOUSE_MIDDLE_BUTTON);

        if (!Raylib.IsWindowMinimized())
            mousePosition = new Vector2(Raylib.GetMouseX(), Raylib.GetMouseY());

        io.MousePos = mousePosition;
    }

    private void UpdateMouseCursor()
    {
        var io = ImGui.GetIO();
        if (io.ConfigFlags.HasFlag(ImGuiConfigFlags.NoMouseCursorChange))
            return;

        var imgui_cursor = ImGui.GetMouseCursor();
        if (io.MouseDrawCursor || imgui_cursor == ImGuiMouseCursor.None)
        {
            Raylib.HideCursor();
        }
        else
        {
            Raylib.ShowCursor();
        }
    }

    public void NewFrame()
    {
        var io = ImGui.GetIO();

        displaySize = new Vector2(Raylib.GetScreenWidth(), Raylib.GetScreenHeight());
        io.DisplaySize = displaySize;

        double current_time = Raylib.GetTime();
        delta = g_Time > 0.0 ? (float)(current_time - g_Time) : 1.0f / 60.0f;
        io.DeltaTime = delta;

        isKeyCtrl = Raylib.IsKeyDown(KeyboardKey.KEY_RIGHT_CONTROL) || Raylib.IsKeyDown(KeyboardKey.KEY_LEFT_CONTROL);
        isKeyShift = Raylib.IsKeyDown(KeyboardKey.KEY_RIGHT_SHIFT) || Raylib.IsKeyDown(KeyboardKey.KEY_LEFT_SHIFT);
        isKeyAlt = Raylib.IsKeyDown(KeyboardKey.KEY_RIGHT_ALT) || Raylib.IsKeyDown(KeyboardKey.KEY_LEFT_ALT);
        isKeySuper = Raylib.IsKeyDown(KeyboardKey.KEY_RIGHT_SUPER) || Raylib.IsKeyDown(KeyboardKey.KEY_LEFT_SUPER);

        io.KeyCtrl = isKeyCtrl;
        io.KeyAlt = isKeyAlt;
        io.KeyShift = isKeyShift;
        io.KeySuper = isKeySuper;

        UpdateMousePosAndButtons();
        UpdateMouseCursor();

        if (Raylib.GetMouseWheelMove() > 0)
        {
            io.MouseWheel += 1;
        }
        else if (Raylib.GetMouseWheelMove() < 0)
        {
            io.MouseWheel -= 1;
        }
    }

    public bool ProcessEvent()
    {
        var io = ImGui.GetIO();

        io.KeysDown[(int)KeyboardKey.KEY_APOSTROPHE] = Raylib.IsKeyDown(KeyboardKey.KEY_APOSTROPHE);
        io.KeysDown[(int)KeyboardKey.KEY_COMMA] = Raylib.IsKeyDown(KeyboardKey.KEY_COMMA);
        io.KeysDown[(int)KeyboardKey.KEY_MINUS] = Raylib.IsKeyDown(KeyboardKey.KEY_MINUS);
        io.KeysDown[(int)KeyboardKey.KEY_PERIOD] = Raylib.IsKeyDown(KeyboardKey.KEY_PERIOD);
        io.KeysDown[(int)KeyboardKey.KEY_SLASH] = Raylib.IsKeyDown(KeyboardKey.KEY_SLASH);
        io.KeysDown[(int)KeyboardKey.KEY_ZERO] = Raylib.IsKeyDown(KeyboardKey.KEY_ZERO);
        io.KeysDown[(int)KeyboardKey.KEY_ONE] = Raylib.IsKeyDown(KeyboardKey.KEY_ONE);
        io.KeysDown[(int)KeyboardKey.KEY_TWO] = Raylib.IsKeyDown(KeyboardKey.KEY_TWO);
        io.KeysDown[(int)KeyboardKey.KEY_THREE] = Raylib.IsKeyDown(KeyboardKey.KEY_THREE);
        io.KeysDown[(int)KeyboardKey.KEY_FOUR] = Raylib.IsKeyDown(KeyboardKey.KEY_FOUR);
        io.KeysDown[(int)KeyboardKey.KEY_FIVE] = Raylib.IsKeyDown(KeyboardKey.KEY_FIVE);
        io.KeysDown[(int)KeyboardKey.KEY_SIX] = Raylib.IsKeyDown(KeyboardKey.KEY_SIX);
        io.KeysDown[(int)KeyboardKey.KEY_SEVEN] = Raylib.IsKeyDown(KeyboardKey.KEY_SEVEN);
        io.KeysDown[(int)KeyboardKey.KEY_EIGHT] = Raylib.IsKeyDown(KeyboardKey.KEY_EIGHT);
        io.KeysDown[(int)KeyboardKey.KEY_NINE] = Raylib.IsKeyDown(KeyboardKey.KEY_NINE);
        io.KeysDown[(int)KeyboardKey.KEY_SEMICOLON] = Raylib.IsKeyDown(KeyboardKey.KEY_SEMICOLON);
        io.KeysDown[(int)KeyboardKey.KEY_EQUAL] = Raylib.IsKeyDown(KeyboardKey.KEY_EQUAL);
        io.KeysDown[(int)KeyboardKey.KEY_A] = Raylib.IsKeyDown(KeyboardKey.KEY_A);
        io.KeysDown[(int)KeyboardKey.KEY_B] = Raylib.IsKeyDown(KeyboardKey.KEY_B);
        io.KeysDown[(int)KeyboardKey.KEY_C] = Raylib.IsKeyDown(KeyboardKey.KEY_C);
        io.KeysDown[(int)KeyboardKey.KEY_D] = Raylib.IsKeyDown(KeyboardKey.KEY_D);
        io.KeysDown[(int)KeyboardKey.KEY_E] = Raylib.IsKeyDown(KeyboardKey.KEY_E);
        io.KeysDown[(int)KeyboardKey.KEY_F] = Raylib.IsKeyDown(KeyboardKey.KEY_F);
        io.KeysDown[(int)KeyboardKey.KEY_G] = Raylib.IsKeyDown(KeyboardKey.KEY_G);
        io.KeysDown[(int)KeyboardKey.KEY_H] = Raylib.IsKeyDown(KeyboardKey.KEY_H);
        io.KeysDown[(int)KeyboardKey.KEY_I] = Raylib.IsKeyDown(KeyboardKey.KEY_I);
        io.KeysDown[(int)KeyboardKey.KEY_J] = Raylib.IsKeyDown(KeyboardKey.KEY_J);
        io.KeysDown[(int)KeyboardKey.KEY_K] = Raylib.IsKeyDown(KeyboardKey.KEY_K);
        io.KeysDown[(int)KeyboardKey.KEY_L] = Raylib.IsKeyDown(KeyboardKey.KEY_L);
        io.KeysDown[(int)KeyboardKey.KEY_M] = Raylib.IsKeyDown(KeyboardKey.KEY_M);
        io.KeysDown[(int)KeyboardKey.KEY_N] = Raylib.IsKeyDown(KeyboardKey.KEY_N);
        io.KeysDown[(int)KeyboardKey.KEY_O] = Raylib.IsKeyDown(KeyboardKey.KEY_O);
        io.KeysDown[(int)KeyboardKey.KEY_P] = Raylib.IsKeyDown(KeyboardKey.KEY_P);
        io.KeysDown[(int)KeyboardKey.KEY_Q] = Raylib.IsKeyDown(KeyboardKey.KEY_Q);
        io.KeysDown[(int)KeyboardKey.KEY_R] = Raylib.IsKeyDown(KeyboardKey.KEY_R);
        io.KeysDown[(int)KeyboardKey.KEY_S] = Raylib.IsKeyDown(KeyboardKey.KEY_S);
        io.KeysDown[(int)KeyboardKey.KEY_T] = Raylib.IsKeyDown(KeyboardKey.KEY_T);
        io.KeysDown[(int)KeyboardKey.KEY_U] = Raylib.IsKeyDown(KeyboardKey.KEY_U);
        io.KeysDown[(int)KeyboardKey.KEY_V] = Raylib.IsKeyDown(KeyboardKey.KEY_V);
        io.KeysDown[(int)KeyboardKey.KEY_W] = Raylib.IsKeyDown(KeyboardKey.KEY_W);
        io.KeysDown[(int)KeyboardKey.KEY_X] = Raylib.IsKeyDown(KeyboardKey.KEY_X);
        io.KeysDown[(int)KeyboardKey.KEY_Y] = Raylib.IsKeyDown(KeyboardKey.KEY_Y);
        io.KeysDown[(int)KeyboardKey.KEY_Z] = Raylib.IsKeyDown(KeyboardKey.KEY_Z);
        io.KeysDown[(int)KeyboardKey.KEY_SPACE] = Raylib.IsKeyDown(KeyboardKey.KEY_SPACE);
        io.KeysDown[(int)KeyboardKey.KEY_ESCAPE] = Raylib.IsKeyDown(KeyboardKey.KEY_ESCAPE);
        io.KeysDown[(int)KeyboardKey.KEY_ENTER] = Raylib.IsKeyDown(KeyboardKey.KEY_ENTER);
        io.KeysDown[(int)KeyboardKey.KEY_TAB] = Raylib.IsKeyDown(KeyboardKey.KEY_TAB);
        io.KeysDown[(int)KeyboardKey.KEY_BACKSPACE] = Raylib.IsKeyDown(KeyboardKey.KEY_BACKSPACE);
        io.KeysDown[(int)KeyboardKey.KEY_INSERT] = Raylib.IsKeyDown(KeyboardKey.KEY_INSERT);
        io.KeysDown[(int)KeyboardKey.KEY_DELETE] = Raylib.IsKeyDown(KeyboardKey.KEY_DELETE);
        io.KeysDown[(int)KeyboardKey.KEY_RIGHT] = Raylib.IsKeyDown(KeyboardKey.KEY_RIGHT);
        io.KeysDown[(int)KeyboardKey.KEY_LEFT] = Raylib.IsKeyDown(KeyboardKey.KEY_LEFT);
        io.KeysDown[(int)KeyboardKey.KEY_DOWN] = Raylib.IsKeyDown(KeyboardKey.KEY_DOWN);
        io.KeysDown[(int)KeyboardKey.KEY_UP] = Raylib.IsKeyDown(KeyboardKey.KEY_UP);
        io.KeysDown[(int)KeyboardKey.KEY_PAGE_UP] = Raylib.IsKeyDown(KeyboardKey.KEY_PAGE_UP);
        io.KeysDown[(int)KeyboardKey.KEY_PAGE_DOWN] = Raylib.IsKeyDown(KeyboardKey.KEY_PAGE_DOWN);
        io.KeysDown[(int)KeyboardKey.KEY_HOME] = Raylib.IsKeyDown(KeyboardKey.KEY_HOME);
        io.KeysDown[(int)KeyboardKey.KEY_END] = Raylib.IsKeyDown(KeyboardKey.KEY_END);
        io.KeysDown[(int)KeyboardKey.KEY_CAPS_LOCK] = Raylib.IsKeyDown(KeyboardKey.KEY_CAPS_LOCK);
        io.KeysDown[(int)KeyboardKey.KEY_SCROLL_LOCK] = Raylib.IsKeyDown(KeyboardKey.KEY_SCROLL_LOCK);
        io.KeysDown[(int)KeyboardKey.KEY_NUM_LOCK] = Raylib.IsKeyDown(KeyboardKey.KEY_NUM_LOCK);
        io.KeysDown[(int)KeyboardKey.KEY_PRINT_SCREEN] = Raylib.IsKeyDown(KeyboardKey.KEY_PRINT_SCREEN);
        io.KeysDown[(int)KeyboardKey.KEY_PAUSE] = Raylib.IsKeyDown(KeyboardKey.KEY_PAUSE);
        io.KeysDown[(int)KeyboardKey.KEY_F1] = Raylib.IsKeyDown(KeyboardKey.KEY_F1);
        io.KeysDown[(int)KeyboardKey.KEY_F2] = Raylib.IsKeyDown(KeyboardKey.KEY_F2);
        io.KeysDown[(int)KeyboardKey.KEY_F3] = Raylib.IsKeyDown(KeyboardKey.KEY_F3);
        io.KeysDown[(int)KeyboardKey.KEY_F4] = Raylib.IsKeyDown(KeyboardKey.KEY_F4);
        io.KeysDown[(int)KeyboardKey.KEY_F5] = Raylib.IsKeyDown(KeyboardKey.KEY_F5);
        io.KeysDown[(int)KeyboardKey.KEY_F6] = Raylib.IsKeyDown(KeyboardKey.KEY_F6);
        io.KeysDown[(int)KeyboardKey.KEY_F7] = Raylib.IsKeyDown(KeyboardKey.KEY_F7);
        io.KeysDown[(int)KeyboardKey.KEY_F8] = Raylib.IsKeyDown(KeyboardKey.KEY_F8);
        io.KeysDown[(int)KeyboardKey.KEY_F9] = Raylib.IsKeyDown(KeyboardKey.KEY_F9);
        io.KeysDown[(int)KeyboardKey.KEY_F10] = Raylib.IsKeyDown(KeyboardKey.KEY_F10);
        io.KeysDown[(int)KeyboardKey.KEY_F11] = Raylib.IsKeyDown(KeyboardKey.KEY_F11);
        io.KeysDown[(int)KeyboardKey.KEY_F12] = Raylib.IsKeyDown(KeyboardKey.KEY_F12);
        io.KeysDown[(int)KeyboardKey.KEY_LEFT_SHIFT] = Raylib.IsKeyDown(KeyboardKey.KEY_LEFT_SHIFT);
        io.KeysDown[(int)KeyboardKey.KEY_LEFT_CONTROL] = Raylib.IsKeyDown(KeyboardKey.KEY_LEFT_CONTROL);
        io.KeysDown[(int)KeyboardKey.KEY_LEFT_ALT] = Raylib.IsKeyDown(KeyboardKey.KEY_LEFT_ALT);
        io.KeysDown[(int)KeyboardKey.KEY_LEFT_SUPER] = Raylib.IsKeyDown(KeyboardKey.KEY_LEFT_SUPER);
        io.KeysDown[(int)KeyboardKey.KEY_RIGHT_SHIFT] = Raylib.IsKeyDown(KeyboardKey.KEY_RIGHT_SHIFT);
        io.KeysDown[(int)KeyboardKey.KEY_RIGHT_CONTROL] = Raylib.IsKeyDown(KeyboardKey.KEY_RIGHT_CONTROL);
        io.KeysDown[(int)KeyboardKey.KEY_RIGHT_ALT] = Raylib.IsKeyDown(KeyboardKey.KEY_RIGHT_ALT);
        io.KeysDown[(int)KeyboardKey.KEY_RIGHT_SUPER] = Raylib.IsKeyDown(KeyboardKey.KEY_RIGHT_SUPER);
        io.KeysDown[(int)KeyboardKey.KEY_KB_MENU] = Raylib.IsKeyDown(KeyboardKey.KEY_KB_MENU);
        io.KeysDown[(int)KeyboardKey.KEY_LEFT_BRACKET] = Raylib.IsKeyDown(KeyboardKey.KEY_LEFT_BRACKET);
        io.KeysDown[(int)KeyboardKey.KEY_BACKSLASH] = Raylib.IsKeyDown(KeyboardKey.KEY_BACKSLASH);
        io.KeysDown[(int)KeyboardKey.KEY_RIGHT_BRACKET] = Raylib.IsKeyDown(KeyboardKey.KEY_RIGHT_BRACKET);
        io.KeysDown[(int)KeyboardKey.KEY_GRAVE] = Raylib.IsKeyDown(KeyboardKey.KEY_GRAVE);
        io.KeysDown[(int)KeyboardKey.KEY_KP_0] = Raylib.IsKeyDown(KeyboardKey.KEY_KP_0);
        io.KeysDown[(int)KeyboardKey.KEY_KP_1] = Raylib.IsKeyDown(KeyboardKey.KEY_KP_1);
        io.KeysDown[(int)KeyboardKey.KEY_KP_2] = Raylib.IsKeyDown(KeyboardKey.KEY_KP_2);
        io.KeysDown[(int)KeyboardKey.KEY_KP_3] = Raylib.IsKeyDown(KeyboardKey.KEY_KP_3);
        io.KeysDown[(int)KeyboardKey.KEY_KP_4] = Raylib.IsKeyDown(KeyboardKey.KEY_KP_4);
        io.KeysDown[(int)KeyboardKey.KEY_KP_5] = Raylib.IsKeyDown(KeyboardKey.KEY_KP_5);
        io.KeysDown[(int)KeyboardKey.KEY_KP_6] = Raylib.IsKeyDown(KeyboardKey.KEY_KP_6);
        io.KeysDown[(int)KeyboardKey.KEY_KP_7] = Raylib.IsKeyDown(KeyboardKey.KEY_KP_7);
        io.KeysDown[(int)KeyboardKey.KEY_KP_8] = Raylib.IsKeyDown(KeyboardKey.KEY_KP_8);
        io.KeysDown[(int)KeyboardKey.KEY_KP_9] = Raylib.IsKeyDown(KeyboardKey.KEY_KP_9);
        io.KeysDown[(int)KeyboardKey.KEY_KP_DECIMAL] = Raylib.IsKeyDown(KeyboardKey.KEY_KP_DECIMAL);
        io.KeysDown[(int)KeyboardKey.KEY_KP_DIVIDE] = Raylib.IsKeyDown(KeyboardKey.KEY_KP_DIVIDE);
        io.KeysDown[(int)KeyboardKey.KEY_KP_MULTIPLY] = Raylib.IsKeyDown(KeyboardKey.KEY_KP_MULTIPLY);
        io.KeysDown[(int)KeyboardKey.KEY_KP_SUBTRACT] = Raylib.IsKeyDown(KeyboardKey.KEY_KP_SUBTRACT);
        io.KeysDown[(int)KeyboardKey.KEY_KP_ADD] = Raylib.IsKeyDown(KeyboardKey.KEY_KP_ADD);
        io.KeysDown[(int)KeyboardKey.KEY_KP_ENTER] = Raylib.IsKeyDown(KeyboardKey.KEY_KP_ENTER);
        io.KeysDown[(int)KeyboardKey.KEY_KP_EQUAL] = Raylib.IsKeyDown(KeyboardKey.KEY_KP_EQUAL);
        io.KeysDown[(int)KeyboardKey.KEY_BACK] = Raylib.IsKeyDown(KeyboardKey.KEY_BACK);
        io.KeysDown[(int)KeyboardKey.KEY_MENU] = Raylib.IsKeyDown(KeyboardKey.KEY_MENU);
        io.KeysDown[(int)KeyboardKey.KEY_VOLUME_UP] = Raylib.IsKeyDown(KeyboardKey.KEY_VOLUME_UP);
        io.KeysDown[(int)KeyboardKey.KEY_VOLUME_DOWN] = Raylib.IsKeyDown(KeyboardKey.KEY_VOLUME_DOWN);

        int length = 0;
        io.AddInputCharactersUTF8(Raylib.CodepointToUTF8(Raylib.GetCharPressed(), ref length));

        return true;
    }

    void LoadDefaultFontAtlas()
    {
        if (!g_UnloadAtlas)
        {
            var io = ImGui.GetIO();
            byte* pixels;
            int width, height, bpp;
            Image image;

            io.Fonts.GetTexDataAsRGBA32(out pixels, out width, out height, out bpp);
            var size = Raylib.GetPixelDataSize(width, height, PixelFormat.PIXELFORMAT_UNCOMPRESSED_R8G8B8A8);
            image.data = (void*)Marshal.AllocHGlobal(size);
            Buffer.MemoryCopy(pixels, image.data, size, size);
            image.width = width;
            image.height = height;
            image.mipmaps = 1;
            image.format = PixelFormat.PIXELFORMAT_UNCOMPRESSED_R8G8B8A8;
            var tex = Raylib.LoadTextureFromImage(image);
            g_AtlasTexID = tex.id;
            io.Fonts.TexID = (IntPtr)g_AtlasTexID;
            Marshal.FreeHGlobal((IntPtr)pixels);
            Marshal.FreeHGlobal((IntPtr)image.data);
            g_UnloadAtlas = true;
        }
    }

    public void Render(ImDrawDataPtr draw_data)
    {
        Rlgl.rlDisableBackfaceCulling();
        for (int n = 0; n < draw_data.CmdListsCount; n++)
        {
            ImDrawListPtr cmd_list = draw_data.CmdListsRange[n];
            uint idx_index = 0;
            for (int i = 0; i < cmd_list.CmdBuffer.Size; i++)
            {
                var pcmd = cmd_list.CmdBuffer[i];
                var pos = draw_data.DisplayPos;
                var rectX = (int)(pcmd.ClipRect.X - pos.X);
                var rectY = (int)(pcmd.ClipRect.Y - pos.Y);
                var rectW = (int)(pcmd.ClipRect.Z - rectX);
                var rectH = (int)(pcmd.ClipRect.W - rectY);
                Raylib.BeginScissorMode(rectX, rectY, rectW, rectH);
                {
                    var ti = pcmd.TextureId;
                    for (int j = 0; j <= (pcmd.ElemCount - 3); j += 3)
                    {
                        if (pcmd.ElemCount == 0)
                        {
                            break;
                        }

                        Rlgl.rlPushMatrix();
                        Rlgl.rlBegin(DrawMode.TRIANGLES);
                        Rlgl.rlSetTexture((uint)ti.ToInt32());

                        ImDrawVertPtr vertex;
                        ushort index;

                        index = cmd_list.IdxBuffer[(int)(j + idx_index)];
                        vertex = cmd_list.VtxBuffer[index];
                        DrawTriangleVertex(vertex);

                        index = cmd_list.IdxBuffer[(int)(j + 2 + idx_index)];
                        vertex = cmd_list.VtxBuffer[index];
                        DrawTriangleVertex(vertex);

                        index = cmd_list.IdxBuffer[(int)(j + 1 + idx_index)];
                        vertex = cmd_list.VtxBuffer[index];
                        DrawTriangleVertex(vertex);

                        Rlgl.rlDisableTexture();
                        Rlgl.rlEnd();
                        Rlgl.rlPopMatrix();
                    }
                }

                idx_index += pcmd.ElemCount;
            }
        }

        Raylib.EndScissorMode();
        Rlgl.rlEnableBackfaceCulling();
    }

    void DrawTriangleVertex(ImDrawVertPtr idx_vert)
    {
        Color c = new Color((byte)(idx_vert.col >> 0), (byte)(idx_vert.col >> 8), (byte)(idx_vert.col >> 16), (byte)(idx_vert.col >> 24));
        Rlgl.rlColor4ub(c.r, c.g, c.b, c.a);
        Rlgl.rlTexCoord2f(idx_vert.uv.X, idx_vert.uv.Y);
        Rlgl.rlVertex2f(idx_vert.pos.X, idx_vert.pos.Y);
    }

}

}

You will likely get some errors, likely caused by the fact that we are using pointers and are not compiling in unsafe context.

To fix this issue, we need to compile in unsafe context.

Go to the Solution Explorer, right-click on the project and go down to Properties.

Then, scroll down until you find the following and check the option showing as "Allow code that uses the 'unsafe' keyword to compile."

Creating the Main Loop

Finally, we need to add some code to our main entry point to get ourselves into position for debugging.

Back in Program.cs, we need to add some using statements for the namespaces we require.

using Raylib_cs;
using ImGuiNET;

Our namespace and class should be setup, so we can start adding code to our main entry point.

static void Main(string[] args)
{
    Raylib.InitWindow(1280, 720, "Story Creator");
    Raylib.SetTargetFPS(60);

    var context = ImGui.CreateContext();
    ImGui.SetCurrentContext(context);

    var controller = new Controller();
            
    while (!Raylib.WindowShouldClose())
    {
        controller.NewFrame();
        controller.ProcessEvent();
        ImGui.NewFrame();

        Raylib.BeginDrawing();
        Raylib.ClearBackground(Color.DARKGRAY);



        ImGui.Render();
        controller.Render(ImGui.GetDrawData());
        Raylib.EndDrawing();
    }

    controller.Shutdown();
}

We need to initialise the window and set a target FPS (frames per second) as Raylib requires these before we can start rendering anything.

Next, we create a context. ImGui will not initialise without this context and does not do so internally. We have to do this ourselves.

Next, we create our Controller class. It is from this where our rendering will happen.

We begin our render loop by using the Raylib function, Raylib.WindowShouldClose().

The next few instructions we supply are a mixture of processing events, beginning our drawing, rendering and ending our drawing.

Finally, if we exit our application, we shutdown all our resources.

Assuming everything has gone to plan, we should expect to see the following results:

BOOM! We have something on the screen and rendering!

Let's test an ImGui function to see if things are running smoothly on the ImGui front as well.

if (ImGui.Button("Cick me!"))
{
    Console.WriteLine("Button clicked.");
}

Our final code produces the following results:

Our Final Code

using System;
using Raylib_cs;
using ImGuiNET;

namespace StoryCreator
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Raylib.InitWindow(1280, 720, "Story Creator");
            Raylib.SetTargetFPS(60);

            var context = ImGui.CreateContext();
            ImGui.SetCurrentContext(context);

            var controller = new Controller();
            
            while (!Raylib.WindowShouldClose())
            {
                controller.NewFrame();
                controller.ProcessEvent();
                ImGui.NewFrame();

                Raylib.BeginDrawing();
                Raylib.ClearBackground(Color.DARKGRAY);

                if (ImGui.Button("Cick me!"))
                {
                    Console.WriteLine("Button clicked.");
                }

                ImGui.Render();
                controller.Render(ImGui.GetDrawData());
                Raylib.EndDrawing();
            }

            controller.Shutdown();
        }
    }
}

Final Thoughts

As you can see, Dear ImGui, Raylib and C# are actually quite easy to setup and get started with a new project. This is saying something considering we are talking about .NET Core which has had a tendency of breaking with first-party tools like MAUI. True, this framework is still in its infancy, but when you have Dear ImGui and Raylib, it's hard to use something like MAUI when these two tools exist and work straight out of the box.

Why even use Raylib & Dear ImGui together?

Raylib and Dear ImGui are both incredibly simple libraries, both of which built from scratch by independent developers who love their passion and driven to build professional software tools for other developers.

Raylib especially, as finding a graphics library for rendering using something simple was not so easy to come by before Raylib existed. We have seen the likes of XNA, Unity, MonoGame in the C# space. We have seen the likes of Unreal, SDL and SFML for C++. OpenFL, Luxe and Kha exist for Haxe.

There are huge numbers of libraries that exist for software development and real-time rendering, but most of them require the use of additional tools to take full advantage of them or take more time to setup and get started with them.

The learning curve of Raylib and Dear ImGui is so small that using any of these other libraries can literally be thrown out of the window. Why use the other choices when these two libraries exist, with how easy it is to use? You cannot go wrong with these combinations and I will be using these libraries for the foreseeable future for all my development projects, as they are so incredibly simple.

You've successfully subscribed to Luke Selman Blog
Great! Next, complete checkout to get full access to all premium content.
Error! Could not sign up. invalid link.
Welcome back! You've successfully signed in.
Error! Could not sign in. Please try again.
Success! Your account is fully activated, you now have access to all content.
Error! Stripe checkout failed.
Success! Your billing info is updated.
Error! Billing info update failed.