Looking at all those tools, libraries, and many ways of developing
GUIs in C++, it can become easily overwhelming. This at
least happened to me; every time I set out to
"finally solve how I will create GUIs in C++"
I got blasted by all the possibilities. From very simplistic to huge
monster like frameworks with own language extensions. In the end I
never just created.
Over time though, I became more and more familiar with
Dear ImGui, an immediate mode
"GUI for C++ with minimal dependencies",
creating tools for game development. Never taking it serious for
"end-user" applications, I've got the final push to try it
a video from Yan Chernikov. Trying ImGui, in combination with
I just started learning, I was pleasantly surprised about the
Here it goes, a basic setup I started using for end-user
applications. Of course, as many frameworks there are opinions, and
this one is only mine.
The first thing I did was to rename src/some_library to
src/core. This then contains the logging and debugging
helper, also this is where I will create the Window and
SDL2 and Dear ImGui
SDL2 will be added as dependency through
FetchContent_Declare, same as the other dependencies in
The new file vendor/sdl/CMakeLists.txt will serve to
show a message on fetching and make SDL2 available.
Adding Dear ImGui as dependency works the same, by adding the
content fetching in vendor/CMakeLists.txt. Instead of
using a regular release I will use the "docking" branch,
enabling a flexible way of moving and docking UI widgets.
As ImGui does not support CMake, there needs to be a bit more done
inside the new file vendor/imgui/CMakeLists.txt. The
whole file will set up ImGui as a library by adding all necessary
source files, defining the including directory and linking
SDL2 to it.
Now, ImGui and SDL2 need to be added to the Core
Basic application setup
The basic application will run the main loop and handle properly
stopping the application on close. There will also be an exit status
in case of an issues.
The basic implementation of Application.hpp looks like
Step-by-step, the constructor will initialise SDL2 and
catch possible issues on creation. The destructor will only quit
SDL2 for now.
Inside run will be the main loop and some error
Last but not least, calling stop will end the main
To actual run the application it needs to be added to the projects
Returning the exit status from app.run() will serve as
exit code of the application. Last step to make this work is to add
the newly created files to CMake.
Creating the window
Next is setting up the window for rendering. The
Settings struct will define the base size of the window
of 1280 times 720, I decided to pick.
Here the full implementation of Window.
One at a time, the biggest part is the constructor.
First step is to actually create a window — it should be
resizable and work on high DPI displays, like Retina displays.
Besides that, the window will be created centered on the screen with
the provided settings for size and window title.
Besides the window, a renderer is needed to actually draw something
into the window.
The renderer will use VSync and hardware acceleration. Both,
renderer and window, will get cleaned up in the destructor.
For the setup of ImGui, Window will also expose the
native window and renderer.
Not forgetting to add those new files to CMake again.
Now, the Application will create and manage the window
holding a reference through a unique_ptr.
The window will be created inside the
The renderer set up inside the main loops run method.
Showing something on the screen
To actually get something on screen, ImGui and the
SDL2 ImGui backend and renderer need to be set up. The
SDL2 renderer will, depending on the operating system,
pick a different render backend — DirectX 10, 11 or 12 for Windows,
Metal for Mac, and so on. You can see for yourself inside the
if you want.
The imports are ImGui, the SDL2 ImGui implementation,
and the ImGui SDL2
Now, the run method needs to create the ImGui context,
initialise the SDL2 renderer, poll events, create a new
frame and actually render something. But, everything step-by-step.
First thing is to create a new ImGui context and set the flags
NavEnableKeyboard and ViewportsEnable for
enabling keyboard navigation and multiple viewports.
Next comes the SDL2 renderer.
Inside the main loop will be the event polling — the
SDL_QUIT event will stop the main loop by calling
stop() from Application.
Finally, creating a new frame and calling the ImGui render method.
The whole run method now looks like this:
One last important thing, adding some cleanup functions inside the
Litr, I can build and run the application with
litr build,start. Without Litr, the same can be
achieved by running CMake and the generated executable.
This is how it looks like so far.
Rendering a widget
Let's get something rendering inside that window. For this I add a
private variable to Application holding the information
if the new widget should be visible.
After creating the ImGui frame, before the ImGui render, the first
"some panel" will be defined.
There it is, in all it's tiny glory — looking
too small and not reacting to any click of the mouse. The reason for
this is that the SDL2 renderer-scale is not yet
adjusted to my Retina display on Mac. Let's fix it next.
Handling high DPI displays
For high DPI displays, like Apples Retina display, I need to change
the renderer scaling. For this I created a new public method
get_scale in Window.
To implement get_scale, it is necessary to divide the
render output size by the window size. The method will return one
value, in that case x computed from width, where height
is only needed for the computation.
The scale is then set up at the end of the
Building and running the application now shows a way better result
The basics are up and running, dock spaces will now enable
rearranging widgets and docking those to the window and each other,
giving a user the option to
freely define the application layout.
For this to work the ImGui flag DockingEnable needs to
be set in run.
Inside the main loop, after the frame creation, but
before the widget, will the setup for the dock space be placed.
This will create a full sized dock space, covering the whole window.
Have a look at the following video to see the dock space in action.
Customising the event handling from the previous implemented polling
is not much more work; setting up a new method in
Application called on_event.
The implementation will use a switch statement to
handle different events.
Furthermore, three specialised events that will be called from the
generic event handler. The on_minimize and
on_shown handler will set the
m_minimized state of the application. This will be used
to optimise the application when in idle mode. The
on_close event will call stop.
From the generic event handler on_event the three
specialised events are called.
To get on_event hooked up is by adding it to the
current event polling inside the main loop.
Using the m_minimized state to optimise the application
in idle mode by wrapping the viewport and dock space creation, as
well as the widget, to not run when minimised.
Inside the dock space, before the widget setup, is a good place to
add the code for the application menu. This will serve to exit the
application and show or hide the "some panel" widget.
This will render the menu at the top of the window, enabling some
basic application control.
Making things look a little less "Debugger UI" like, adding
a custom font can go a long way. I will use the amazing Open Source
Manrope. After downloading it all fonts will be placed relative to
src/app/App/Main.cpp file inside a
fonts folder. The full path of the folder is
The code for the font is placed inside
Application after the ConfigFlags, but
before the renderer setup.
A final look at the application now shows the amazing new font,
everything coming together and starting to look like an end-user
And that is that! A base to continue when creating GUIs in C++ for
me. And again, there is a more extended version of this on GitHub.