Minimal UI library written in Odin
  • Odin 99%
  • Makefile 1%
Find a file
2026-08-17 15:28:44 +03:00
backend perf(sdl3): ttf.SetFontSize should be called only once 2026-08-16 10:49:33 +03:00
examples chore: use raven package name instead of ui in examples 2026-08-15 17:05:26 +03:00
tests chore: use raven package name instead of ui in examples 2026-08-15 17:05:26 +03:00
button.odin fix: draw_button should set desired cursor 2026-08-09 17:04:52 +03:00
draggable.odin chore: use raven package name instead of ui in examples 2026-08-15 17:05:26 +03:00
helpers.odin feat: add method for detecting mouse click only within bounds 2026-08-17 15:28:44 +03:00
Makefile chore: add makefile with common actions 2026-08-08 22:18:52 +03:00
odinfmt.json first commit 2026-08-02 00:11:43 +03:00
raven.odin feat: support multiple backends 2026-08-08 22:18:30 +03:00
README.md chore: use raven package name instead of ui in examples 2026-08-15 17:05:26 +03:00
scroll.odin feat: support multiple backends 2026-08-08 22:18:30 +03:00
text.odin perf(sdl3): ttf.SetFontSize should be called only once 2026-08-16 10:49:33 +03:00
text_editor.odin feat: add mouse support to text editor 2026-08-09 16:56:53 +03:00
theme.odin feat(text): configurable text overflow strategies 2026-08-14 20:33:25 +03:00

Raven UI Library

🚧 This project is still experimental

Raven is a minimalistic immediate-mode UI library for Raylib written in Odin.

It is a loose collection of tools, rather than a complete framework. You are encouraged to copy the source code into your project and modify and extend it as required.

Instead of a complex layout algorithm, it gives the user full control over how and where UI elements are drawn, by providing various helpers:

count: int

text := fmt.aprintf("count: %d", count, allocator = context.temp_allocator)
text_rec := raven.draw_text(text, {0, 0})

// Every draw_* procedure takes a Vector2 position and returns a Rectangle position+size.
// So every subsequent draw can simply offset its position from the previous Rectangle.
button_rec := raven.draw_button("increment", raven.pos_below(text_rec))
if raven.is_clicked(button_rec) do count += 1

Raven is backend independent. You may use SDL3, or Raylib as your rendering backend. The default backend is Raylib. To switch the used backend, the RAVEN_BACKEND compile flag can be used:

odin build my_project -define:RAVEN_BACKEND=sdl3

When using OLS, you may want to define a profile in ols.json to tell OLS which backend you are using:

{
    "$schema": "https://raw.githubusercontent.com/DanielGavin/ols/master/misc/ols.schema.json",
    "profile": "sdl3",
    "profiles": [
        { "name": "sdl3", "defines": { "RAVEN_BACKEND": "sdl3" } },
        { "name": "raylib", "defines": { "RAVEN_BACKEND": "raylib" } }
    ]
}

Features

  • Text drawing and measuring, with word wrapping and Unicode support
  • Buttons
  • Scroll containers
  • Text editor with selection, clipboard, and keyboard navigation support
  • Font loading with Unicode codepoint ranges
  • Theming via raven.default_theme

Usage

Copy the library directory into your project and import it:

import "raven"

Run an example:

odin run examples/counter

See the examples directory for more examples.

Run the tests:

odin test tests/

Stateful elements

Elements with state are split into an update and a draw procedure. Call both once per frame:

raven.update_text_editor(&editor)
raven.draw_text_editor(&editor, bounds)

Custom UI elements

Custom elements work just like the built-in ones: a procedure that draws at a position and returns a Rectangle. See examples/custom_ui_element for a complete example.

draw_my_number_selector :: proc(number: ^int, pos: raylib.Vector2) -> raylib.Rectangle {
	button_theme := raven.default_theme
	button_theme.button_background_color = {0, 0, 0, 255} // black
	button_theme.button_hover_background_color = raylib.Color{20, 20, 20, 255}

	text := fmt.aprint(number^, allocator = context.temp_allocator)

	// Set a max width for the text and pad on both sides.
	// This fixes the button jumping when text width changes.
	text_size := raven.measure_text(text)
	max_text_width :: 30
	text_offset_x := (max_text_width - text_size.x) / 2

	minus_rec := raven.draw_button("-", pos, theme = button_theme)
	text_rec := raven.draw_text(text, raven.pos_right(minus_rec, gap = 10 + text_offset_x))
	text_rec.width += text_offset_x
	plus_rec := raven.draw_button("+", raven.pos_right(text_rec, gap = 10), theme = button_theme)

	if raven.is_clicked(minus_rec) do number^ -= 1
	if raven.is_clicked(plus_rec) do number^ += 1

	return raven.combine_recs(minus_rec, text_rec, plus_rec)
}

Theming

The raven.Theme struct is a convenient way to apply styling to UI elements. Many procedures in Raven take an optional theme argument. When omitted, it defaults to the global raven.default_theme.

To customize the default styling of elements, mutate raven.default_theme:

raven.default_theme.font_size = 24
raven.default_theme.text_color = raylib.WHITE

To give specific elements a different look, create a new theme as required:

button_theme := raven.default_theme
button_theme.button_background_color = raylib.DARKGRAY

raven.draw_button("delete", {10, 10}, theme = button_theme)