A Dear ImGui binding for hica. Immediate-mode GUI backed by Dear ImGui + SDL2 + OpenGL3, with the Inter font and the Ilseon dark theme bundled out of the box. Includes ImPlot for 2-D plotting.
brew install sdl2 # macOS
# sudo apt install libsdl2-dev # Linuxhica add imgui
hica fetchThis records the dependency in hica.hml and downloads the package.
hica fetch downloads the hica source files; the compiled C++ backend must be obtained separately (it contains native code that cannot be distributed as plain hica):
mkdir -p vendor/imgui/lib
# macOS Apple Silicon:
curl -L https://github.com/cladam/imgui/releases/latest/download/libimgui_hica-macos-arm64.a \
-o vendor/imgui/lib/libimgui_hica.a
# Linux x86-64:
# curl -L https://github.com/cladam/imgui/releases/latest/download/libimgui_hica-linux-x86_64.a \
# -o vendor/imgui/lib/libimgui_hica.aAlternatively, build from source (requires clang++ and SDL2 dev headers):
cd vendor/imgui && ./build.shAdd the linker flags to your project's hica.hml. Run sdl2-config --libs to find the SDL2 lib path on your machine.
macOS:
@koka {
flags: "--cclinkopts=-L./vendor/imgui/lib --cclinkopts=-L/opt/homebrew/opt/sdl2/lib --cclib=imgui_hica --cclib=SDL2 --cclinkopts=-lc++ --cclinkopts=-framework --cclinkopts=OpenGL"
}
Linux:
@koka {
flags: "--cclinkopts=-L./vendor/imgui/lib --cclib=imgui_hica --cclib=SDL2 --cclib=GL"
}
import "imgui"
import "imgui"
fun main() {
var count = 0
gui_window("My App", 520, 360, () => {
gui_text("Counter: " + show(count))
if gui_button("Increment") {
count = count + 1
}
})
}
gui_window opens the OS window, runs the event loop, and calls your lambda every frame. Close the window to exit.
| Function | Signature | Purpose |
|---|---|---|
gui_window |
(title: string, w: int, h: int, frame: () -> ()) -> () |
Open window and run the frame loop |
gui_window handles gui_init, gui_begin_frame / gui_end_frame, and gui_shutdown for you. Call the low-level lifecycle functions directly only if you need custom control.
| Function | Signature | Purpose |
|---|---|---|
gui_init |
(title: string, w: int, h: int) -> () |
Open window, init SDL2 + OpenGL + ImGui |
gui_begin_frame |
() -> bool |
Start frame; returns false when window is closed |
gui_end_frame |
() -> () |
Render and swap buffers |
gui_shutdown |
() -> () |
Tear down ImGui, SDL2, OpenGL |
All widgets must be called between gui_begin_frame() and gui_end_frame() — or inside the gui_window lambda.
| Function | Signature | Purpose |
|---|---|---|
gui_text |
(s: string) -> () |
Static text label |
gui_text_colored |
(s: string, r: float, g: float, b: float, a: float) -> () |
Text in an RGBA colour (channels 0.0–1.0) |
gui_text_wrapped |
(s: string) -> () |
Text that wraps at the window edge |
gui_bullet_text |
(s: string) -> () |
Bullet point + text |
gui_button |
(label: string) -> bool |
Button; returns true on the click frame |
gui_checkbox |
(label: string, checked: bool) -> bool |
Toggle; returns current state |
gui_slider_int |
(label: string, min: int, max: int, default: int) -> int |
Integer slider; returns current value |
gui_slider_float |
(label: string, min: float, max: float, default: float) -> float |
Float slider; returns current value |
gui_drag_int |
(label: string, min: int, max: int, default: int, speed: float) -> int |
Click-drag int control; speed ≈ change per pixel |
gui_drag_float |
(label: string, min: float, max: float, default: float, speed: float) -> float |
Click-drag float control |
gui_input_text |
(label: string, capacity: int) -> string |
Single-line text input; returns current content |
gui_input_int |
(label: string, default: int) -> int |
Integer input field with +/- buttons |
gui_input_float |
(label: string, default: float) -> float |
Float input field |
gui_radio_button |
(label: string, active: bool) -> bool |
Radio button; returns true when clicked (caller manages which is selected) |
gui_selectable |
(label: string, selected: bool) -> bool |
Clickable row; returns current selected state (widget owns state) |
gui_combo |
(label: string, items: string, default: int) -> int |
Dropdown; items is newline-separated ("A\nB\nC"); returns selected index |
gui_progress_bar |
(fraction: float, overlay: string) -> () |
Horizontal bar filled 0.0–1.0; overlay="" shows default XX% label |
| Function | Signature | Purpose |
|---|---|---|
gui_separator |
() -> () |
Horizontal rule |
gui_same_line |
() -> () |
Place next widget on the same line |
gui_new_line |
() -> () |
Undo a same_line; move to the next line |
gui_spacing |
() -> () |
Extra vertical gap |
gui_dummy |
(w: float, h: float) -> () |
Invisible spacer of exact pixel size |
gui_indent |
() -> () |
Push indent (shift subsequent widgets right) |
gui_unindent |
() -> () |
Pop indent |
| Function | Signature | Purpose |
|---|---|---|
gui_begin_panel |
(label: string) -> bool |
Start a collapsible section; returns true when expanded |
gui_end_panel |
() -> () |
End the collapsible section |
Call gui_end_panel() only when gui_begin_panel() returns true:
if gui_begin_panel("Settings") {
gui_slider_int("Volume", 0, 100, 80)
gui_end_panel()
}| Helper | Signature | Purpose |
|---|---|---|
gui_child |
(id: string, w: float, h: float, content: () -> ()) -> () |
Scrollable child region |
gui_tree |
(label: string, content: () -> ()) -> () |
Collapsible tree node |
gui_with_item_width |
(w: float, content: () -> ()) -> () |
Constrain next widget width in pixels |
gui_tab_bar |
(id: string, content: () -> ()) -> () |
Tab bar container |
gui_tab |
(label: string, content: () -> ()) -> () |
One tab inside a gui_tab_bar |
| Function | Signature | Purpose |
|---|---|---|
gui_tooltip |
(text: string) -> () |
Show a text tooltip when the previous widget is hovered |
gui_set_tooltip |
(text: string) -> () |
Raw version (same behaviour) |
gui_begin_tooltip |
() -> bool |
Start a custom-content tooltip region |
gui_end_tooltip |
() -> () |
End the tooltip region |
| Helper | Signature | Purpose |
|---|---|---|
gui_open_popup |
(id: string) -> () |
Schedule a popup to open next frame |
gui_popup |
(id: string, content: () -> ()) -> () |
Non-blocking popup window |
gui_modal |
(id: string, content: () -> ()) -> () |
Blocking modal dialog |
gui_close_popup |
() -> () |
Dismiss the current popup from within its block |
if gui_button("Options") { gui_open_popup("opts") }
gui_popup("opts", () => {
if gui_menu_item("Reset") { ... }
if gui_button("Close") { gui_close_popup() }
})| Helper | Signature | Purpose |
|---|---|---|
gui_main_menu |
(content: () -> ()) -> () |
Top-level OS menu bar |
gui_menu |
(label: string, content: () -> ()) -> () |
Drop-down menu inside a bar or another menu |
gui_menu_item |
(label: string) -> bool |
Clickable item; returns true when clicked |
gui_main_menu(() => {
gui_menu("File", () => {
if gui_menu_item("Save") { save() }
if gui_menu_item("Quit") { }
})
})| Function | Signature | Purpose |
|---|---|---|
gui_begin_table |
(id: string, columns: int, flags: int) -> bool |
Start a table; returns true when visible |
gui_end_table |
() -> () |
End the table |
gui_table_setup_column |
(label: string) -> () |
Define a column header label |
gui_table_headers_row |
() -> () |
Emit the header row from setup columns |
gui_table_next_row |
() -> () |
Advance to the next row |
gui_table_next_column |
() -> () |
Advance to the next cell |
Common flag values (combine with +):
| Value | Meaning |
|---|---|
0 |
Plain table, no decorations |
1 |
Inner cell borders |
2 |
Outer border |
3 |
Both borders |
64 |
Alternating row background |
67 |
Borders + alternating rows (most common) |
if gui_begin_table("##scores", 3, 67) {
gui_table_setup_column("Player")
gui_table_setup_column("Score")
gui_table_setup_column("Level")
gui_table_headers_row()
gui_table_next_row()
gui_table_next_column() // cell [0,0]
gui_text("Alice")
gui_table_next_column() // cell [0,1]
gui_text("1024")
gui_table_next_column() // cell [0,2]
gui_text("12")
gui_end_table()
}Sliders, checkboxes, and text inputs manage their own persistent state keyed by label string. Read the return value each frame to get the current value, the hica var bindings work naturally:
var volume = 80
var muted = false
gui_window("Player", 400, 200, () => {
volume = gui_slider_int("Volume", 0, 100, volume)
muted = gui_checkbox("Mute", muted)
if muted {
gui_text(" (audio muted)")
}
})See examples/hello-gui in this repo for a full widget showcase, and examples/chart for ImPlot.
To run the examples from a clone of this repo:
./build.sh # build libimgui_hica.a (once)
hica run examples/hello-gui/hello-gui.hc
hica run examples/chart/chart.hcmacOS:
brew install sdl2Linux (Debian/Ubuntu):
sudo apt install libsdl2-dev libgl-devcd lib/imgui
./build.shThis compiles Dear ImGui, the SDL2 + OpenGL3 backends, the C glue layer, and archives everything into lib/libimgui_hica.a. The Inter font and Ilseon theme are baked in – no runtime assets needed.
The bundled defaults are:
- Font: Inter Regular 16px, HiDPI-aware (scales with display pixel density)
- Theme: Ilseon, an OLED-focused dark palette with organic muted accents, sourced from the hica standard library
backend/
imgui_glue.h # Plain-C API: hk_gui_* functions
imgui_glue.cpp # C++ implementation — Dear ImGui + SDL2 + OpenGL3
kk_imgui.c # Koka FFI trampolines (compiled by Koka)
inter_font.h # Inter-Regular.ttf embedded as a C array
src/
imgui.kk # Koka module: pub extern gui_* functions + gui_window
vendor/
imgui/ # Dear ImGui source (cloned by build.sh)
fonts/
Inter-Regular.ttf
examples/
tests/
build.sh # Builds libimgui_hica.a
lib/
libimgui_hica.a # Pre-built static library (after ./build.sh)
MIT