[ANN] CImGui.jl - A Wrapper for Bloat-free Immediate Mode Graphical User interface(Dear ImGui)

CImGui.jl is a wrapper for cimgui: a thin c-api wrapper programmatically generated for the excellent C++ immediate mode gui Dear ImGui. Dear ImGui is mainly for creating content creation tools and visualization / debug tools. Let me quote from ImGui’s README directly:

Dear ImGui allows you create elaborate tools as well as very short-lived ones. On the extreme side of short-liveness: using the Edit&Continue (hot code reload) feature of modern compilers you can add a few widgets to tweaks variables while your application is running, and remove the code a minute later! Dear ImGui is not just for tweaking values. You can use it to trace a running algorithm by just emitting text commands. You can use it along with your own reflection data to browse your dataset live. You can use it to expose the internals of a subsystem in your engine, to create a logger, an inspection tool, a profiler, a debugger, an entire game making editor/framework, etc.

CImGui.jl aims to provide the same user experience as the original ImGui C++ API and I’ve ported almost all of the built-in demos of ImGui to Julia:

37%20PM
37%20PM%202
37%20PM%203
37%20PM%204

This package is still somwhat in its BETA phase. Any feedback would be highly appreciated!

Cheers!

45 Likes

This is great. Your demo loaded in 1 second the first time.

Would it be possible to integrate this with Observables?

1 Like

How does this compare to Nuklear.jl. I.e. relative advantages, disadvantages, etc?

he should know, he made that library too :smile:

That’s probably due to JIT overhead, those examples are not precompiled.

I didn’t know Observables.jl before, it looks like we could build a more easy to use high-level GUI interfaces with it.

I meant that is amazingly fast! To load an interface from cold start of Julia just after the package install :slight_smile: GUIs normally take forever in Julia.

Observables do make it pretty easy to connect things together. Makie and Interact.jl use them, Gtk has GtkReactive which uses the older Signals based framework.

Another benefit of using Obserables is that apps could abstract the interface for all the common widgets, and say swap ImGui for a web interface when required without too much work.

6 Likes

Both nuklear and Dear ImGui are inmmediate mode GUIs. Both their design and APIs are quite the same. One distinctive feature of nuklear is its customizability(via these flags), but these implementation details are usually agnostic to end users and many wrappers like Nuklear.jl only use a fixed configuration, so this advantage is totally lost.

On the other hand, Dear ImGui is under active and sustainable maintenance. There are some cool features like docking and multi-viewport will be coming in this year. For now, CImGui.jl has a thin wrapper which is as close as the original C++ API, so CImGui.jl’s interfaces are simpler than Nuklear.jl’s C89 API.

that looks awesome. i’ve been wanting a simple gui tool to be able to put simple drawings in a window and give it basic controls like zoom, pan , etc…

this looks perfect for that!

i’m shocked that i’ve never heard of dear imgui before.

That’s probably because Dear ImGui is not for creating UIs for the average end-user. As explained in the Dear ImGui’s FAQs, it’s a renderer-agnostic GUI lib and does not provide any 2D/3D rendering utilities by default. People need to build their own 2D texture renderer for drawing images. Although this can be done in a couple of lines using quite a few knowledge in 3D graphics, the learning curve may look steep for those who don’t have any relevant background. Maybe I should add a default 2D image renderer to CImGui.jl. I’m still looking for a way to integrate ImGui with other 2D plotting tools in the Julia ecosystem, so stay tuned. :slight_smile:

My personal use case is to build simple GUI tools for 3D computer vision tasks like IntelRealSense-viewer or open3d. I feel like Dear ImGui is the perfect tool for this kinda use case. The learning curve of its API is really flat.

5 Likes

Hi @Gnimuc, thanks for this one more contribution to the GUI packages, it looks really impressive. But, like in the Nuklear.jl case, I still have the problem with the font size that it’s too small in HD screens. In the Nuklear case you added the possibility to increase the font size. That kind of worked but it requires that each user sets the fonts to adjust to his/her case. Isn’t there a more general solution to this problem?

Really nice! The demo loads very fast and has so many features! I concur with the suggestion of using Observables for the logic (it has worked quite well for Interact and Makie as far as I understand).

I’m playing with the idea of trying to unify “widget syntax” across GUI packages based on a lightweight Widgets.jl package (here are some docs). The idea is that a Widget has some components, optionally an output and a layout function that defines how it’s rendered. The idea is that the GUI package needs to define a backend and overload methods for the backend for the various widgets (slider, textbox, spinbox, dropdown, etc…) and then the same syntax (including fancier things like @manipulate) would work on various backends. Here is an attempt of doing that for AbstractPlotting/Makie.

4 Likes

This is actually another reason why I prefer Dear ImGui. There is a StyleEditor shipped along with the library. You could uncomment the following code to pre-load different fonts:

and let users to config their favorite style:

08%20PM

3 Likes

X-ref:

and the roadmap says:

Make the examples look better, improve styles, improve font support, make the examples hi-DPI aware.

Yes, much better. Now the widget sizes adapt for larger fonts, which was not the case for Nuklear (I think). And very good news from the roadmap.

hi @piever, I have no experience of Interact.jl-based GUI before, so please correct me if I’m wrong. After reading the docs, I feel like the underlying design between ImGui and Interact are quite different.

ImGui is immediate mode UI that doesn’t hold any hidden states, which means it manipulates the value and does “drawing” stuff (emits vertices, textures, drawing cmds, etc to drawing data buffer/window stack) simultaneously. For example, what this line of code CImGui.ColorEdit3("color", col1) does is “drawing” the slider widget and immediately set col1 to current color/value selected by users. Unlike retained mode UI, all of the widgets get updated every frame regardless of whether the states changed or not(the idea is to update widgets and the states every frame instead of storing hidden states and calculating which widget should be re-rendering). As a result, widgets in ImGui do not return outputs, so current unify widget syntax may not fit.

Considering the following example:

import Colors
using Plots

function mycolorpicker()
    r = slider(0:255, label = "red")
    g = slider(0:255, label = "green")
    b = slider(0:255, label = "blue")
    output = Interact.@map Colors.RGB(&r/255, &g/255, &b/255)
    plt = Interact.@map plot(sin, color = &output)
    wdg = Widget(["r" => r, "g" => g, "b" => b], output = output)
    @layout! wdg hbox(plt, vbox(:r, :g, :b)) ## custom layout: by default things are stacked vertically
end

In ImGui, one might write:

# this is a fake Julia code snippet, but you may already get the idea.
let
r, g, b = 0, 0, 0 # ImGui won't handle states for us, we need to find a way to store them ourselves.
function mycolorpicker()
    # Interact.@map magic should happen here, but is it still necessary?
    slider(&r, 0, 255, label = "red")
    slider(&g, 0, 255, label = "green")
    slider(&b, 0, 255, label = "blue")
    # emit textures immediately to drawing buffer
    plot(sin, color = Colors.RGB(r/255, g/255, b/255))
    SameLine()/NoSameLine() # layouts are not a part of widgets in ImGui
end
end

My intuition is that the Widget struct would be a wrapper for the CImGui widgets. For example, in case of a slider, one would create something where the output is an Observable corresponding to the value of the slider and the “layout” (i.e. the thing that is actually rendered would be the CImGui slider), so something like:

function Widgets.slider(::CImGuiBackend, range; label = label)
  imslider = slider(range)
  imoutput = # observable corresponding to slider output
  Widget{:slider}(output = imoutput, layout = _ -> hbox(imslider, label))
end

The tricky bit is to have an observable that carries the value of the slider (which was already mentioned above as "supporting the Observables framework). The easiest is probably to initialize the observable to the same value as the slider and at every frame if the slider is changed, update the observable.

1 Like

Just want to say that going from an ] add CImGui CSyntax, to being able to run julia demo.jl and have everything work extremely well (and look beautiful and minimalistic to boot) is just absolutely amazing. Thanks for putting this wrapper together @Gnimuc! I look forward to using this for all sorts of stuff. :slightly_smiling_face:

2 Likes

Unfortunately I’ve noticed an important downside of Dear ImGui (not specifically the Julia wrapper). When we launch the demo window for instances the GPU activity raises above 35%, even if we don’t touch any window. This is quite bad for the durability of battery charges on laptops.

2 Likes

not quite fits ImGui’s design, but it’s good for Widget API consistency, so makes sense to me. :slight_smile:

That’s probably due to the implementation of OpenGL backend is not very efficient. It queries a lot OpenGL global states in the rendering loop:

This implementation is copied from Dear ImGui which maybe a trade-off bwteen correctness and performance.