Go to file
2025-10-21 23:49:59 +02:00
.build Victory. Text editor working well. Docs being written. 2025-10-21 23:49:59 +02:00
bin Victory. Text editor working well. Docs being written. 2025-10-21 23:49:59 +02:00
modules Victory. Text editor working well. Docs being written. 2025-10-21 23:49:59 +02:00
src Victory. Text editor working well. Docs being written. 2025-10-21 23:49:59 +02:00
first.jai updated SDL, sizing text by words with kb_text_shape 2025-07-08 23:46:36 +02:00
README.md Victory. Text editor working well. Docs being written. 2025-10-21 23:49:59 +02:00

UI

Overview

This is a small Jai library to do some basic immediate mode UI. It has a variety of widgets and writing new ones should be relatively easy, if you follow the existing ones.

As opposed to a UI library you might find on the Internet, like Dear ImGui, this library is sadly not platform independent. It is relatively coupled with OpenGL, so you in order to integrate it to a project, you will need to also provide a GL context with GLFW, RGFW, SDL, or the like.

Architecture

The main concept behind this UI system is that as you call the ui_* functions like ui_begin/ui_end, etc. You are implicitly building the tree that represents the UI hierarchy. That is the magic of an immediate mode API. You take advantage of the fact that functions are called in a stack across time. Which is implicitly a Tree. At the end of the frame you give this tree to the rendering function, which recurses through it pre-order, drawing the panels, icons, text, etc., in the correct order.

Using this architecutre as the base places some undesirable constraints on the further design of the system though. For example, the most useful part of this library is the auto-layout algorithm. It's not super sophisticated, but it works. And in order to do this auto layout you need two basic pieces of information. Where are the ui elements and how big are they. Properties that the layout algorithm can itself change!

So, the easiest solution to this problem is to separate everything into distinct phases (or passes). In the first pass you are declaring the tree structure of the UI as you go in a bottom up fashion, recording the initial positions and sizes. Then after you're done you can traverse the tree from the top down adjusting the layout. This is only possible because at this point in time you have all the information including nodes' siblings, the size they are, they size they'd like to be, etc.

Public API

Some functions have a begin/end pair, becuase you necesarily have to wait until things finish happening inside of them to be able to calculate their initial size, etc. Other functions don't have this begin/end pair, because they don't allow children, they're a leaf node, and they don't need further information to finish their calculations.

ui_new_frame :: ()

This has to be called every frame, before calling any other UI function.

ui_begin :: (s: string)

This begins a panel, which other widgets can go inside of.

ui_end :: ()

Has to be called after a ui_begin to finish its UI Node.

ui_text_input :: (s: string, font_colour := Vector4.{1, 1, 1, 1})

This starts a text editor!

ui_begin_container :: (s: string, gradient := false, colour_left := Vector4.{}, colour_right := Vector4.{}, max_x: s32 = -1, max_y: s32 = -1, flags: ContainerFlags = 0)

Similar to a normal ui_begin, but you can have this one inside the main panel, recursively. Very useful to change the layout direction (Left To Right vs Top to Bottom)

ui_end_container :: ()

Must be called after a ui_begin_container, to calculate sizing and finish the node in the UI Tree.

ui_icon :: (s: string, texture: *Texture, size: Vector2)

Renders an OpenGL texture.

ui_label :: (s: string = "", colour := Vector4.{1.0, 1.0, 1.0, 1.0})

Renders text.

ui_spacing :: (s: string)

Forces spacing between elements. The auto layout algorithm tries to maximize the space between the node before the spacing and after the spacing, taking into account the max size of the parent container.

ui_button :: (s: string = "", icon_texture: *Texture = null, font_colour := Vector4.{1, 1, 1, 1}, draw_frame := true) -> bool

Renders a panel with optional text in it and an optional OpenGL texture. When clicked it returns true. Very useful to run conditional code like:

if ui_button(...) { do_something(); }

ui_font_icon :: (font: *Font, s: string, size: Vector2, colour: Vector4 = .{1, 1, 1, 1})

Renders an icon stored in a font. you have to figure out what the name of the glyph is and pray FreeType can find it. You can look up the names using a tool like FontDrop!.

ui_progress_bar :: (s: string = "", progress: float, size: Vector2, background := Vector4.{1, 1, 1, 1}, foreground := Vector4.{1, 1, 1, 1})

Renders a progress bar.

ui_end_frame :: ()

Must be called after finishing the UI Tree. You must not call any UI Tree building functions (any of the previous ones) after this call and before a new ui_begin_frame.

ui_render :: ()

Renders the UI with OpenGL.

Example

Please look at main.jai for an example.