
# Hello World [C]

## Introduction

C is the "native" surface of Azul. Every other language binding (Rust, C++, Python, ...) 
ultimately calls into the same `azul.h` symbols described here, so anything that works in 
C works everywhere.

The good news is that you do not have to build Azul "from scratch" as a C user. The 
library ships as a prebuilt DLL and a single header file, both of which you can drop 
into an existing project and link like any other system library. 

## Installation

### Pre-built DLL (recommended)

On Debian/Ubuntu and Fedora/RHEL you can install the prebuilt package
straight from the GitHub release:

```sh
# linux - Debian / Ubuntu (installs /usr/include/azul.h + /usr/lib/libazul.so)
curl -L -O https://github.com/fschutt/azul/releases/download/0.2.0/azul_${VERSION}_amd64.deb
sudo apt install ./azul_${VERSION}_amd64.deb

# linux - Fedora / RHEL
curl -L -O https://github.com/fschutt/azul/releases/download/0.2.0/azul-0.2.0-1.x86_64.rpm
sudo dnf install ./azul-0.2.0-1.x86_64.rpm
```

ARM64 and other architectures are on the
[GitHub releases page](https://github.com/fschutt/azul/releases/tag/0.2.0).
The `.deb` / `.rpm` installs `libazul.so` plus the `azul.h` header into the
standard system locations, so a plain `cc hello-world.c -lazul` will pick
everything up.

Instead of downloading the `.deb` manually, Debian/Ubuntu users can also
subscribe to the self-hosted apt repository served from azul.rs (it is
unsigned, hence the explicit `[trusted=yes]`):

```sh
echo "deb [trusted=yes] https://azul.rs/ui/apt stable main" | sudo tee /etc/apt/sources.list.d/azul.list
sudo apt update
sudo apt install azul
```

On macOS, a self-hosted Homebrew tap (a real git repository served from
azul.rs - no external registry involved) installs `libazul.dylib` plus
`azul.h`:

```sh
brew tap fschutt/azul https://azul.rs/ui/homebrew-azul.git
brew install fschutt/azul/azul
```

Experimental Chocolatey / Arch / Alpine mirrors are published best-effort under
`azul.rs/ui/{nuget,arch,alpine}`. On
Windows (and for CI on any platform), download the
header and the library directly from the
[release page](https://azul.rs/ui/release/0.2.0):

```sh
# header (same file on every platform)
curl -L -O https://azul.rs/ui/release/0.2.0/azul.h

# windows (plus azul.lib if you link with MSVC)
curl.exe -L -O https://azul.rs/ui/release/0.2.0/azul.dll
# linux
curl -L -O https://azul.rs/ui/release/0.2.0/libazul.so
# macos (Apple Silicon; Intel: libazul.x86_64.dylib)
curl -L -O https://azul.rs/ui/release/0.2.0/libazul.dylib
```

You then either install both into a system path or pass `-I` and `-L` to the compiler.

## Simple "Counter" Example

The C version of the counter is about ~60 lines (without comments):

```c
#include "azul.h"
#include <stdio.h>
#include <string.h>

// Tiny helper: turn a compile-time string literal into an AzString.
// AzString_fromConstStr is a designated-initializer macro - in expression
// context it needs a compound-literal cast to `(AzString){...}`. The
// resulting AzString just points at the literal (which lives in .rodata)
// with a NoDestructor - zero allocation. For runtime-built strings, see
// AzString_copyFromBytes further down.
#define AZ_CONST_STR(s) ((AzString)AzString_fromConstStr(s))

// Data model: Plain old struct - the "single source of truth" for app state.
typedef struct { uint32_t counter; } MyDataModel;

// Called once when the framework drops the last RefAny pointing at
// our struct. No heap allocation here, so the body is empty.
void MyDataModel_destructor(void* m) { }

// AZ_REFLECT expands into a small set of helper functions:
// 
//   MyDataModel_upcast(struct)         -> AzRefAny
//   MyDataModel_downcastRef(&refany,&MyDataModelRef) -> bool (const access)
//   MyDataModel_downcastMut(&refany,&MyDataModelRefMut) -> bool (mutable access)
// 
// It stores a compiler-generated tag in the RefAny, so that the 
// framework can verify the type-safety casts at runtime.
//
// Alternatively, use AZ_REFLECT_JSON if you want to plug into the
// state-hot-reload machinery (requires you to supply toJson /
// fromJson callbacks).
AZ_REFLECT(MyDataModel, MyDataModel_destructor);
// AZ_REFLECT_JSON(MyDataModel, destructor_fn, toJson, fromJson)

// Forward-declare on_click so layout() can pass it to the button.
// All UI callbacks share this same signature.
AzUpdate on_click(AzRefAny data, AzCallbackInfo info);

// f(DataModel) -> Dom. Runs once on startup and again after every
// callback that returns Update::RefreshDom.
AzDom layout(AzRefAny data, AzLayoutCallbackInfo info) {

    // We downcast non-mutably from the RefAny to our MyDataModelRef
    // (generated by AZ_REFLECT macro). We only want to read the counter
    // so we don't use the "RefMut" here.
    MyDataModelRef d = MyDataModelRef_create(&data);
    if (!MyDataModel_downcastRef(&data, &d)) {
        // _delete is safe even though the downcast failed: it only
        // releases what _create allocated and skips the borrow-count
        // decrease when .ptr was never set.
        MyDataModelRef_delete(&d);
        return AzDom_createBody();
    }

    // Format the counter value into a string and immediately
    // release the const borrow - we don't need it further.
    char buffer[20];
    int written = snprintf(buffer, 20, "%d", d.ptr->counter);
    MyDataModelRef_delete(&d);

    // copy the bytes to an owned AzString, since char[20] will
    // go out of scope at the end of this function
    AzString label_text = AzString_copyFromBytes(
        (const uint8_t*)buffer,
        0 /* start */,
        written /* num_bytes to copy */
    );

    // AzDom_create_text would just creates the raw inline text node
    // ("p::text" in CSS) - but we have to wrap it in a <p> block here
    AzDom label_dom = AzDom_createPWithText(label_text);
    AzDom_setCss(&label_dom, AZ_CONST_STR(
        "font-size: 50px;"
    ));

    // Button widget - has its own helper API on top of Dom
    AzButton button = AzButton_create(AZ_CONST_STR("Increase counter"));
    AzButton_setButtonType(&button, AzButtonType_Primary);

    // RefAny_clone bumps the refcount - clone ownership 
    // is moved into the AzButton so the AzButton
    // can hand it back to on_click later.
    AzRefAny data_clone = AzRefAny_clone(&data);
    AzButton_setOnClick(&button, data_clone, on_click);

    // Convert the button widget to a plain Dom node so we
    // can append it like any other.
    AzDom button_dom = AzButton_dom(button);

    // Final wrapup and return
    AzDom body = AzDom_createBody();
    AzDom_addChild(&body, label_dom);
    AzDom_addChild(&body, button_dom);
    return body;
}

// Click callback, framework invokes this function pointer 
// when the button's hit-test matches a MouseUp event.
AzUpdate on_click(AzRefAny data, AzCallbackInfo info) {

    // Now we use the RefMut variant (generated by AZ_REFLECT),
    // to perform the runtime borrow check. Failure means the RefAny 
    // is already borrowed elsewhere or holds a different type.
    MyDataModelRefMut d = MyDataModelRefMut_create(&data);
    if (!MyDataModel_downcastMut(&data, &d)) {
        // You could debug here. _delete is safe on this failure
        // path too - see the note in layout() above.
        MyDataModelRefMut_delete(&d);
        return AzUpdate_DoNothing;
    }

    // Actually increase the counter behind the MyDataModelRefMut 
    // (thread safe, no other thread has access to this at this point)
    d.ptr->counter += 1;

    // ALWAYS pair _create with _delete before returning - on every
    // path, including failed downcasts. Forgetting it after a
    // successful downcast leaves the RefAny borrowed and the next
    // downcast will fail.
    MyDataModelRefMut_delete(&d);

    // RefreshDom now queues a new layout() invocation:
    // dom build -> cascade -> relayout -> display list -> render
    return AzUpdate_RefreshDom;
}

int main() {

    // Initialize the data model
    MyDataModel model = { .counter = 5 };

    // Move ownership of the model into a RefAny.
    AzRefAny data = MyDataModel_upcast(model);

    // Configure the window(s) to spawn on startup. layout() is the
    // "/" default route; SPA-style routing is done later by swapping
    // the layout callback on a window.
    AzWindowCreateOptions window = AzWindowCreateOptions_create(layout);

    // Play with the window options
    window.window_state.title = AZ_CONST_STR("Hello World!");
    window.window_state.size.dimensions.width = 400.0;
    window.window_state.size.dimensions.height = 300.0;

    // OS draws close/min/max buttons; framework auto-injects a
    // draggable titlebar above our content.
    window.window_state.flags.decorations =
        AzWindowDecorations_NoTitleAutoInject;
    window.window_state.flags.background_material =
        AzWindowBackgroundMaterial_Sidebar;

    // AppConfig discovers system-native styling, monitor layout, etc.
    AzApp app = AzApp_create(data, AzAppConfig_create());

    // Blocks until the last window closes (on Win32 this never
    // returns; on other systems it depends on window_config flags).
    AzApp_run(&app, window);
    AzApp_delete(&app);
    return 0;
}
```

Five things to notice.

- **`AZ_REFLECT(...)`** — generates the upcast / downcast helpers and a runtime type tag the framework uses to verify casts. The destructor parameter is a function pointer the framework calls when the last `RefAny` referencing your struct is dropped; if your struct owns heap data, free it here. The longer form `AZ_REFLECT_JSON(struct, destructor, toJson, fromJson)` additionally plugs into the state-hot-reload machinery — not needed for hello-world.
- **`FooRef_create` / `FooRef_delete`** — every `_create` must be paired with a `_delete` before the function returns, on the success *and* the failure path. `_delete` is failure-safe: if the downcast never succeeded (`.ptr` is still `0`), it only releases the bookkeeping that `_create` allocated and does not touch the borrow count. After a successful downcast it is the C version of Rust's `RefMut` going out of scope: it releases the runtime borrow on this RefAny instance.
- **`AzRefAny_clone`** — bumps the reference count, does not deep-copy your struct. The second `RefAny` is moved into the button so the click handler can downcast it later. Cloning is thread-safe (refcount is atomic).
- **`AzString_fromConstStr` vs `AzString_copyFromBytes`** — strings cross the FFI as length-prefixed UTF-8 buffers, not `const char*`. For compile-time string literals, `AzString_fromConstStr` (wrapped in our `AZ_CONST_STR` macro) is a zero-allocation designated initializer: the resulting `AzString` just points at `.rodata` and carries a `NoDestructor` so the framework will not try to free it. For runtime-built strings (e.g. the counter `snprintf`'d into a stack buffer above), use `AzString_copyFromBytes` instead — it copies the bytes into a refcounted heap buffer so the framework can outlive your stack frame. For NUL-terminated runtime strings, `azul.h` also ships the shorthand `AZ_STR(s)`, which expands to `AzString_copyFromBytes((const uint8_t*)(s), 0, strlen(s))`.
- **`AzDom_setCss("...")`** — accepts a CSS string, the C analogue of Rust's `set_css`. Multi-property strings are valid: `"font-size: 50px; color: white;"`. You can also embed `:hover { }`, `:focus { }`, `@media ... { }`, `@os(macos >= sonoma) { }` dynamic queries directly inline — in difference to regular CSS. The string is parsed once on the first cascade and cached, so this is not a per-frame cost. (For programmatic, type-safe access without a CSS string round-trip, use `AzDom_addCssProperty` with the `AzCssProperty_*` constructors instead.)

Things we did not use that you may want to explore next.

- `AzLayoutCallbackInfo` — read-only access to the system font cache, image cache, GL context, current window size, routing, and localization dictionaries.
- `AzCallbackInfo` — many functions for navigating the DOM, mutating CSS without rebuilding the tree, querying computed layout / styles, etc.
- `AzWindowCreateOptions` — title, size, decorations, transparency, monitor pinning. Covered in [windowing](../windowing.md).

## Build and run

If you installed `libazul` through the `.deb` / `.rpm` package, the
header and the shared library live in standard locations and the
compiler will find them on its own — one line is enough:

```sh
cc hello-world.c -lazul -o hello-world
./hello-world
```

(On Windows the equivalent is `cl hello-world.c azul.dll.lib` once the
downloaded MSVC import library `azul.dll.lib` is on the linker search path,
with `azul.dll` next to the resulting `.exe` at run time.)

If you downloaded the header and DLL manually (or built from source),
you have to point the compiler at them explicitly. `-I` / `-L` add
include and link search paths; `-Wl,-rpath` tells the dynamic loader
where to find `libazul.{so,dylib}` at runtime so you do not have to
set `LD_LIBRARY_PATH` (Linux) or `DYLD_LIBRARY_PATH` (macOS) every
time you run the binary.

```sh
# Linux
cc hello-world.c \
   -I/path/to/azul-headers \
   -L/path/to/azul-lib \
   -lazul -ldl -lpthread -lm \
   -Wl,-rpath,/path/to/azul-lib \
   -o hello-world

# macOS — @executable_path resolves relative to the binary, so you can
# ship the .dylib next to the .bin and the loader will pick it up
cc hello-world.c \
   -I/path/to/azul-headers \
   -L/path/to/azul-lib -lazul \
   -Wl,-rpath,@executable_path/. \
   -o hello-world

# Windows (MSVC) — drop azul.dll next to the .exe at run time
cl hello-world.c /I path\to\azul-headers ^
   /link /LIBPATH:path\to\azul-lib azul.dll.lib
```

You should see the window pictured on the [hello-world landing page](../hello-world.md). Click the button: the counter increments, the layout callback re-runs, and the new value renders.

1. `AzApp_run` opened a native window and ran `layout()` once with your `RefAny` on startup.
2. The returned `AzDom` was styled, laid out, and rendered (default: CPU-rendered; can be GPU-rendered if needed).
3. On click, the button's event filter matched a `MouseUp` inside its hit-test bounds. The framework borrowed the `RefAny` mutably, ran `on_click`, observed the `AzUpdate_RefreshDom` return, and re-invoked `layout()`.
4. The new `AzDom` was diffed against the previous one; only the changed text node was repainted.

## Common errors

- **Linker reports unresolved `Az*` symbols** — the dynamic library is not on the linker path. Check `-L` and `-l` (or `LIBPATH` on MSVC).
- **Runtime: "library not found"** — the loader cannot find `libazul.{so,dylib,dll}`. On Linux export `LD_LIBRARY_PATH`; on macOS use `-Wl,-rpath,@executable_path/.`; on Windows place `azul.dll` next to the `.exe`.
- **`downcastMut` / `downcastRef` returns `false`** — usually a missing `_delete` somewhere is leaving the `RefAny` borrowed. Less commonly, it holds a different type than you think. Return `AzUpdate_DoNothing` (or `AzDom_createBody()`) and audit your borrow scopes.
- **Counter does not update on click** — the click callback returned `AzUpdate_DoNothing`, or the downcast failed silently. Add a `printf` to the failure branch to verify.
- **The window opens blank** — the layout callback returned `AzDom_createBody()` with no children, or the `_addChild` calls were applied to the wrong node.

### Building from source

Only needed if you want to track `master` or patch the library locally:

```sh
# git clone https://github.com/fschutt/azul
# cd myfolder/azul
# generate the bindings from api.json (required)
cargo run -p azul-doc --release -- codegen all
# build the actual DLL with the now-generated .rs C-API bindings
cargo build -p azul-dll --release --features build-dll
```

Notice the required `--features build-dll`, as this is a flag to "build the DLL, don't link to it". The DLL lands at `target/release/libazul.{so,dylib}` (or `azul.dll`). The header is previously generated by `azul-doc codegen all` and ends up at `target/codegen/azul.h`. Copy both somewhere your C compiler can find.

## Coming Up Next

- [Application Architecture](../architecture.md) — Explains the concepts of architecting a larger Azul application
- [Document Object Model](../dom.md) — The Dom tree - node types, hierarchy, and CSS
- [Hello World [C++]](cpp.md)
