GUI.md
July 14, 2026 ยท View on GitHub
Provides a wrapper around the wonderful Dear ImGUI library (exposed in Julia as *CImGui.jl), plus many related helpers.
Note that you can freely convert between B+ vectors (Vec{N, T}) and CImGui vectors (ImVec2, ImVec4, ImColor).
GUI service
The Dear ImGUI integration is provided as a B+ Context service, meaning it is a graphics-thread-singleton that you control with the following functions:
service_GUI_init()to start the serviceservice_GUI_exists()to check if the service is already running
service_GUI_shutdown()to manually kill the service (you don't have to do this if the program is closing)service_GUI_start_frame()to set up before any GUI calls are made for the current frameservice_GUI_end_frame()to finish up and render the GUI to the currently-bound target, presumably the screen.service_GUI_rebuild_fonts()to update the GUI library after you've added custom fonts.service_GUI()to get the current service instance
If you want to pass a texture (or texture view) to be drawn in the GUI, you must wrap it in a call to gui_tex_handle(). Do not just pass the OpenGL handle for the resource.
Note that the underlying library uses static variables, and does not manage multiple independent contexts, so you cannot run this service from more than one GL Context at a time.
Text Editor
Text editing through a C library is tricky, due to the use of C-style strings, the need to integrate clipboard, and also the need to dynamically resize the string as the user writes larger and larger text. BplusApp.GUI.GuiText handles all of this for you.
- Create an instance with the initial string value, and optionally configure some of its fields.
- For example
GuiText("ab\ncdef\ngh", is_multiline=true)
- For example
- Display the text widget with
any_changes::Bool = gui_text!(my_text). - Get the current value with
string(my_text). - Change the text value with
update!(my_text, new_string).
Scoped Helper Functions
Most of Dear ImGUI's state is static variables, which you configure as you draw things.
Often, it is useful to temporarily set some state, run GUI code, then undo your changes.
B+ offers many helper functions which manage this, by taking your GUI code as a lambda.
You should use Julia's do block syntax to pass the lambda, for example:
gui_with_indentation() do
# Your indented GUI code here
end
Nearly all of these scoped helpers have a convenient optional keyword argument, unchanged::Bool,
which if true will generally run your lambda without changing the Dear ImGUI state.
The following functions are availble (lambda parameter is omitted for brevity):
gui_window(args...; kw_args...)::Optionalnests your code inside a new window.- All arguments are passed through to the
CImGui.Begin()call. - Returns the output of your code block, or
nothingif the UI was culled
- All arguments are passed through to the
gui_with_item_width(width::Real; unchanged=false)changes the width of widgets.gui_with_indentation(indent::Optional{Real} = nothing; unchanged=false)indents some GUI code.gui_with_tooltip(; skip=false)adds a tooltip to the previous widget/group, with your lambda filling in the tooltip's GUI. This function has the parameterskipinstead ofunchangedbecause if true, your lambda will not run at all.gui_with_padding(padding...; unchanged=false)sets the padding used within a window.- You can pass x and y values, or a tuple of X/Y values.
gui_with_clip_rect(rect::Box2Df, intersect_with_current_rect::Bool, draw_list = nothing; unchanged=false)sets the clip rectangle.gui_with_font(font_or_idx::Union{Ptr, Int}; unchanged=false)switches to one of the fonts you've already loaded into Dear ImGUI.gui_with_unescaped_tabbing(; unchanged=false)disables the ability to switch between widgets with Tab.- Useful if you want Tab to be recognized in a text editor.
gui_with_nested_id(values::Union{AbstractString, Ptr, Integer}...; unchanged=false)pushes new data onto Dear ImGUI's ID stack. Dear ImGUI widgets must be uniquely identified by their label plus the state of the ID stack at the time they are called, so this helps you distinguish between different widgets that have the same label.gui_within_fold(label; unchanged=false)nests some GUI within a collapsible region.- Dear ImGUI calls this a "tree node".
gui_with_style(var::CImGui.LibCImGui.ImGuiStyleVar, color::Union{Real, Vec2, gVec2, Tuple{Any, Any}}; unchanged=false)configures a specific part of Dear ImGUI's drawing style.gui_with_style(color_idx::CImGui.LibCImGui.ImGuiCol_, value::Union{UInt32, Vec3, Vec4, gVec4}; unchanged=false)sets a specific color for a specific part of Dear ImGUI's widget library.gui_within_group(; unchanged=false)allows widgets to be referred to as an entire group. For example, you can make a vertical layout section within a horizontal layout section by callingCImGui.SameLine()after the entire vertical group.gui_tab_views(; unchanged=false)allows you to define multiple tabs, each with associated widgets inside it.gui_tab_item(; unchanged=false)defines one tab view within.
gui_within_child_window(size, flags=0; unchanged=false)::Optionalnests a GUI within a sub-window. Returns the output of your code block, ornothingif the window is culled.
Other GUI helpers
gui_get_draw_clip_area()::Box2Dfgets the current clipping rect for a given draw list (see Draw destination below for useful shorthand).gui_add_font_from_memory_ttf(bytes_view, pixel_sizes[, oversampling])will handle loading a new TTF font at multiple sizes. For each size you request, a corresponding font handle is returned in the output list.
Drawing wrappers
Dear ImGUI has many functions for drawing primitives, and we offer some helper types and functions to greatly simplify their use.
gui_draw_line(coords, color, thickness=1; ...)gui_draw_rect(coords, color; ...)gui_draw_quad(corners, color; ...)
Coordinate format depends on the specific function (NTuple{2, v2f} for lines, Box2Df for rects, etc.),
but in every case they use absolute coordinates.
You can make them relative by wrapping the data in GuiDrawCursorRelative(coords, emit_dummy).
That second argument emit_dummy controls whether the shape advances Dear ImGUI's cursor,
either with a Bool or a per-axis Vec{2, Bool}.
The color parameter comes in a variety of forms,
and not all of them are allowed depending on the shape being drawn.
The most common options are GuiDrawColorBorder (only draws a border),
GuiDrawColorFilled (draws a filled shape),
and GuiDrawColorTexture (draws a filled shape containing an image).
Lastly, the functions have an optional keyword parameter canvas to control where the shape is drawn.
By default it goes to the foreground of the current window,
but you can pass any raw draw list (Ptr{CimGui.ImDrawList})
or an element of the enum GuiDrawingCanvas.
Color/fill type
Drawing functions which take a GuiDrawSimpleColorType may be given a GuiDrawBorder, to draw the border only, or GuiDrawFilled, to draw the entire inside of the shape.
Drawing functions which take a GuiDrawColorType have the extra option of GuiDrawMultiColor, which assigns a different color to each coordinate.
Coordinates
Drawing functions normally take absolute coordintes, but our functions let you wrap them in a GuiDrawCursorRelative to make them relative to where Dear ImGUI is about to place its next widget.
If using these relative coordinates, then you can also have the drawing function generate a Dummy widget reaching to the max corner of the shape.
Draw destination
Drawing in Dear ImGUI is normally done to a special object called a "draw list",
but 90% of the time this is going to be the current window's draw list,
and 9% of the time this will be the screen foreground or background.
So all our functions which take a draw list allow you to provide shorthand for these three cases,
represented with the enum GuiDrawingCanvas.
By default all drawing uses GuiDrawingCanvas.current_window.
Other Helper Functions
gui_spherical_vector(label, direction::v3f; settings...)::v3fedits a vector using spherical coordinates (pitch and yaw).gui_next_window_space(uv_space::Box2Df, ...)sets the size of the next GUI window in percentages of this program's window size. You may optionally provide a padding border and/or pixel-size clamp.