uing

Crystal binding for libui

UIng

test Lines of Code Ask DeepWiki

UIng is a Crystal binding for libui-ng.

libui-ng uses the native APIs of each platform: Win32 API, Direct2D, and DirectWrite on Windows; Cocoa (AppKit) on macOS; and GTK+ 3.10+ and Pango on Linux/Unix. You get windows, buttons, text boxes, menus, dialogs, drawing areas, and other standard widgets.

Windows Mac Linux

Quick Start

Clone the repository:

git clone https://github.com/kojix2/uing
cd uing

Create the libui directory and download the static library for your platform:

crystal run download.cr

To run the control_gallery example, use the following command:

crystal run examples/control_gallery.cr

Windows MSVC Setup

For Windows users using MSVC, use Developer Command Prompt or add Windows Kits path:

$env:Path += ";C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64"

Installation

Add the dependency to your shard.yml:

dependencies:
  uing:
    github: kojix2/uing

The required libui-ng binary is automatically downloaded from the kojix2/libui-ng GitHub Releases via postinstall.

Supported Platforms

  • macOS: x86_64 (64-bit), ARM64 (Apple Silicon)
  • Linux: x86_64 (64-bit), ARM64
  • Windows: x86_64 (64-bit, MSVC and MinGW), x86 (32-bit, MSVC only)

Usage

require "uing"

UIng.init

window = UIng::Window.new("Hello World", 300, 200)
window.on_closing do
  UIng.quit
  true
end

button = UIng::Button.new("Click me")
button.on_clicked do
  window.msg_box("Info", "Button clicked!")
end

window.set_child(button)
window.show

UIng.main
UIng.uninit

DSL style

require "uing"

UIng.init do
  UIng::Window.new("Hello World", 300, 200) { |win|
    on_closing { UIng.quit; true }
    set_child {
      UIng::Button.new("Click me") {
        on_clicked {
          win.msg_box("Info", "Button clicked!")
        }
      }
    }
    show
  }

  UIng.main
end

Note: The DSL style is implemented using Crystal's with ... yield syntax internally.

Examples Gallery

This gallery shows screenshots of example on three platforms (Ubuntu, Windows, macOS).
Images are automatically generated and stored in the screenshots branch.

Window

Control Ubuntu Windows macOS
Window basic_window-ubuntu basic_window-windows basic_window-macos

Control

Control Ubuntu Windows macOS
Button basic_button-ubuntu basic_button-windows basic_button-macos
Checkbox basic_checkbox-ubuntu basic_checkbox-windows basic_checkbox-macos
ColorButton basic_color_button-ubuntu basic_color_button-windows basic_color_button-macos
Combobox basic_combobox-ubuntu basic_combobox-windows basic_combobox-macos
DateTimePicker basic_date_time_picker-ubuntu basic_date_time_picker-windows basic_date_time_picker-macos
EditableCombobox basic_editable_combobox-ubuntu basic_editable_combobox-windows basic_editable_combobox-macos
Entry basic_entry-ubuntu basic_entry-windows basic_entry-macos
FontButton basic_font_button-ubuntu basic_font_button-windows basic_font_button-macos
Label basic_label-ubuntu basic_label-windows basic_label-macos
MultilineEntry basic_multiline_entry-ubuntu basic_multiline_entry-windows basic_multiline_entry-macos
Progressbar basic_progressbar-ubuntu basic_progressbar-windows basic_progressbar-macos
RadioButtons basic_radio_buttons-ubuntu basic_radio_buttons-windows basic_radio_buttons-macos
Separator basic_separator-ubuntu basic_separator-windows basic_separator-macos
Slider basic_slider-ubuntu basic_slider-windows basic_slider-macos
Spinbox basic_spinbox-ubuntu basic_spinbox-windows basic_spinbox-macos

Container Control

Container Ubuntu Windows macOS
Box (Horizontal) basic_box_horizontal-ubuntu basic_box_horizontal-windows basic_box_horizontal-macos
Box (Vertical) basic_box_vertical-ubuntu basic_box_vertical-windows basic_box_vertical-macos
Tab basic_tab-ubuntu basic_tab-windows basic_tab-macos
Form basic_form-ubuntu basic_form-windows basic_form-macos
Group basic_group-ubuntu basic_group-windows basic_group-macos
Grid basic_grid-ubuntu basic_grid-windows basic_grid-macos
Grid (Calculator) grid_calculator-ubuntu grid_calculator-windows grid_calculator-macos

Note: Grid Layout does not work as expected on macOS.

Table

Example Ubuntu Windows macOS
basic_table basic_table-ubuntu basic_table-windows basic_table-macos
csv_viewer csv_viewer-ubuntu csv_viewer-windows csv_viewer-macos
advanced_table advanced_table-ubuntu advanced_table-windows advanced_table-macos

Note: The Table API has quirks. For example, you must manually free memory as instructed when the program terminates.

Area

Example Ubuntu Windows macOS
basic_area basic_area-ubuntu basic_area-windows basic_area-macos
area_basic_shapes area_basic_shapes-ubuntu area_basic_shapes-windows area_basic_shapes-macos
area_colors_and_brushes area_colors_and_brushes-ubuntu area_colors_and_brushes-windows area_colors_and_brushes-macos
area_analog_clock area_analog_clock-ubuntu area_analog_clock-windows area_analog_clock-macos
spirograph spirograph-ubuntu spirograph-windows spirograph-macos
area_matrix area_matrix-ubuntu area_matrix-windows area_matrix-macos
basic_draw_text basic_draw_text-ubuntu basic_draw_text-windows basic_draw_text-macos
reversi reversi-ubuntu reversi-windows reversi-macos
area_breakout area_breakout-ubuntu area_breakout-windows area_breakout-macos
boid3d boid3d-ubuntu boid3d-windows boid3d-macos

Menu

Example Ubuntu Windows macOS
basic_menu basic_menu-ubuntu basic_menu-windows basic_menu-macos

Dialog

Example Ubuntu Windows macOS
basic_msg_box basic_msg_box-ubuntu basic_msg_box-windows basic_msg_box-macos
basic_msg_box_error basic_msg_box_error-ubuntu basic_msg_box_error-windows basic_msg_box_error-macos

Image (experimental)

Example Ubuntu Windows macOS
area_draw_image area_draw_image-ubuntu area_draw_image-windows area_draw_image-macos
basic_image_view basic_image_view-ubuntu basic_image_view-windows basic_image_view-macos

Note: Image display is a feature introduced experimentally in a fork of libui-ng. Please be aware that this feature is not present in the original libui-ng.

API Levels

Level Defined in Example Description
High-Level src/uing/*.cr button.on_clicked { }, etc. Object-oriented API
Low-Level src/uing/lib_ui/lib_ui.cr UIng::LibUI.new_button, etc. Direct bindings to libui
  • Almost all basic control functions such as Window, Label, and Button are covered.
  • APIs for advanced controls such as Table and Area are also provided. However, these are still under development and there may still be memory management issues.

Control Destruction and Memory Management

libui-ng is a C library, so normally the user is expected to manage memory manually. However, it also provides some automatic memory management. The basic rule is: when a parent control is destroyed, all of its child controls are automatically destroyed as well.

For example, when destroy is called on controls such as Window, Box, Grid, Group, or Tab, it first destroys all child controls and then destroys itself.

In the case of a Window, there are two common situations:

When the user clicks the close (×) button on the title bar In this case, on_closing is triggered, and Window#destroy is called automatically.

When on_should_quit is triggered This means the entire program is about to quit. But here, Window#destroy is not called automatically, so the user needs to call it explicitly.

Crystal has garbage collection (GC) and provides a finalize hook when objects are freed. But since the timing of GC is unpredictable, combining it with libui-ng’s own automatic destruction can easily lead to issues. That’s why this project does not use finalize hooks.

It may seem a bit tricky, but understanding how libui-ng handles memory will help you avoid problems when building applications.

Some controls require specific destruction order for proper memory cleanup

  • #6
    • Table
  • #19
    • MultilineEntry
    • Tab
    • Grid
    • Area

Limitations

libui-ng is cross-platform, but comes with some limitations:

  1. The original libui-ng does not provide image display functionality. A patch has been applied in this project to add experimental support, which is available in the main branch.

  2. The grid layout system is known to be broken on macOS. A patch has been applied to improve the behavior, though it still does not fully match the expected design.

  3. Precise widget positioning is not possible. Control placement is intentionally coarse and cannot be specified numerically. This is likely an intentional constraint to ensure consistent behavior across all three platforms.

  4. There is no function to delete columns from the table.

Windows Setup

Hide Console Window

MinGW:

crystal build app.cr --link-flags "-mwindows"

MSVC:

crystal build app.cr --link-flags=/SUBSYSTEM:WINDOWS

Packaging Your Application

To learn how to package your UIng-based application for distribution, refer to the md5_checker example.
This example demonstrates a simple way to bundle your Crystal app with the required native libraries, making it easy to share with others.

Development

  • UIng::LibUI is the module for direct C bindings
  • Initially, crystal_lib was used to generate low-level bindings   - However, it required many manual conversions, such as changing LibC::Int to Bool. Currently, it is better to use AI.
  • When adding new UI components, follow the established callback management patterns
  • libui libraries are generated using GitHub Actions at kojix2/libui-ng in the pre-build branch.
    • Enhancement patches such as image display functionality are provided on the dev branch.

Sustainability

From experience, maintaining a GUI library is harder than it looks, often requiring two to three times more effort than expected. Rather than chasing new features, we should prioritize sustainability.

Memory Safety

UIng applies several strategies to ensure safe interoperation between Crystal’s garbage-collected runtime and native C code:

  • Callback Protection: Most callbacks are stored as instance variables of their controls, preventing them from being collected by the GC. Closures are additionally protected using ::Box.box(), allowing Crystal blocks that capture external variables to be safely used as C callbacks.

  • Reference Chains: Controls are passed to parent containers (e.g., Window -> Box -> Button), ensuring that children remain referenced as long as the parent exists. Root components such as Window and Menu are stored as class variables to avoid premature collection.

  • Extended Handler Structures: For complex controls like Area and Table, extended C structs embed the base handler along with extra fields for boxed callbacks. Static C-compatible trampolines cast back to these extended structs and invoke the stored closures safely.

  • Resource Management: finalize is avoided due to the non-deterministic timing of GC. Instead, RAII-style APIs are provided: resources are freed automatically when leaving a block, so users rarely need to call free manually.

Closures in Low-Level Contexts

  • Many methods support Crystal closures because the underlying libui-ng functions accept a data parameter.

  • In some low-level APIs, such as function pointers assigned to struct members, no data can be passed. UIng works around this by using struct inheritance and boxed data to support closures in these cases.

  • This approach is used in controls like Table and Area.

Use of AI Coding

This project was developed with the assistance of generative AI.

In particular, AI was extensively used for:

  • Creating GitHub Actions workflows for screenshot automation
  • Generating complex example programs
  • Producing patches for libui-ng

While kojix2 enjoys code generation and “Vibe Coding,” UIng is not a library created purely with that approach. In reality, it was built through substantial manual work, iterative design, and human review of AI-generated code.

I also maintain several projects that are based purely on the Vibe Coding style, but I do not consider this library to be one of them.

Contributing

  • Fork this repository
  • Report bugs and submit pull requests
  • Improve documentation
  • Test memory safety improvements

License

MIT License

Repository

uing

Owner
Statistic
  • 13
  • 0
  • 3
  • 1
  • 1
  • 4 days ago
  • April 20, 2024
License

MIT License

Links
Synced at

Thu, 18 Sep 2025 08:27:06 GMT

Languages