# ltk onboarding This guide is for the first hour with `ltk`: what environment you need, how to run the examples, how to build a minimal app, when to use layer-shell vs a regular window, and what theme/font assumptions the toolkit currently makes. If you already know the basics and want the deeper rationale, read [`docs/architecture.md`](./architecture.md) next. ## What `ltk` is `ltk` is a Rust UI toolkit for Wayland. It is aimed first at the Eydos shell stack, but it can also be used to build normal client applications and runtime-free UI surfaces. At a high level: - Implement the `App` trait. - Return an `Element` tree from `view()`. - React to user input by handling messages in `update()`. - Start the event loop with `ltk::run(app)`. The model is declarative and Elm-shaped: the widget tree is rebuilt from your state, then `ltk` handles layout, drawing and input dispatch. If you are browsing the crate through `cargo doc`, the public API is also grouped conceptually into three entry points: - `ltk::window` — basic application windows - `ltk::shell` — layer-shell and overlays - `ltk::runtime` — advanced runtime hooks and runtime-free embedding Most users should start with `ltk::window` and ignore the other two until they have a normal app window running. ## Before you start `ltk` is not a browser toolkit and not a cross-platform desktop toolkit. Today it assumes: - a running **Wayland** session - Wayland client libraries available through Rust dependencies - a usable system font — on Debian (the crate's own packaging target): `fonts-sora`, `fonts-liberation` or `fonts-dejavu` - an installed `default` theme, or a development theme directory exposed through `LTK_THEMES_DIR` The rendering backend is selected automatically: - **GLES** when EGL/GLES is available - **software** fallback otherwise, or when `LTK_FORCE_SOFTWARE=1` ## Fastest way to see it working From the repo root: ```bash LTK_THEMES_DIR=themes cargo run --example showcase ``` The `LTK_THEMES_DIR=themes` prefix points theme lookup at the in-repo `themes/` directory (see *Theme and font setup* below); without it, on a machine without the `default` theme installed, the example runs on the embedded B/W fallback with a red banner. The other examples, same prefix: - `cargo run --example widgets` — broad widget survey - `cargo run --example inputs` — plain and secure text fields with a show/hide-password toggle - `cargo run --example scroll` — the two main scroll use cases: a long list and an app-drawer-style grid - `cargo run --example sliders` — the Glass effect on horizontal and vertical sliders - `cargo run --example combo` — select/dropdown with editable query and multi-select chips - `cargo run --example pickers` — notebook tabs, date, time and color pickers - `cargo run --example dialog` — modal confirm, non-modal pick and the other dialog shapes - `cargo run --example carousel` — focused-tile carousel - `cargo run --example responsive` — fluid vs physical scaling side by side - `cargo run --example clip_path` — per-path canvas clipping - `cargo run --example mini_shell` — overlays, animation and theme switching All examples require a running Wayland compositor. ## Build and test The `Makefile` wraps the cargo invocations the repo expects: - `make all` — release build - `make test` — `cargo test --features test-support`; a bare `cargo test` fails because the integration tests import the feature-gated `ltk::test_support` - `make doctest-md` — typechecks the code snippets in `docs/*.md`, so API drift surfaces in CI like a normal doctest failure - `make examples` — runs every example in sequence with `LTK_THEMES_DIR=themes` - `make doc` — `cargo doc --no-deps` ## Theme and font setup `ltk` currently expects a theme named `default`. Lookup order is: 1. `LTK_THEMES_DIR//` 2. `$XDG_DATA_HOME/ltk/themes//` 3. `/usr/share/ltk/themes//` For development inside this repository, the simplest setup is: ```bash export LTK_THEMES_DIR="$PWD/themes" ``` That makes `ThemeDocument::find("default")` resolve to `$PWD/themes/default/theme.json`. Font loading is separate from theme lookup. `src/system_fonts.rs` walks a chain of common system font *file paths* — the files installed by the Debian packages `fonts-sora`, `fonts-liberation`, `fonts-freefont` and `fonts-dejavu`, plus the equivalent locations other distros use — and loads the first one it finds. If nothing matches, it falls back to an embedded Sora Regular (~50 KB, SIL OFL 1.1) shipped inside the crate, so font resolution never panics on a system without the expected fonts. Installing one of the listed packages is still recommended for richer glyph coverage. ## Your first app The smallest useful `ltk` app implements `App`, returns a tree from `view()`, updates its state in `update()`, and calls `ltk::run(...)`. ```rust,no_run use ltk::{ App, Element, Keysym, button, column, spacer, text }; #[derive(Clone)] enum Msg { Increment, } struct CounterApp { value: u32, } impl App for CounterApp { type Message = Msg; fn view( &self ) -> Element { column::() .padding( 32.0 ) .spacing( 16.0 ) .center_y( true ) .push( text( "Hello from ltk" ).size( 28.0 ) ) .push( text( format!( "Count: {}", self.value ) ).size( 18.0 ) ) .push( spacer() ) .push( button( "Increment" ).on_press( Msg::Increment ) ) .into() } fn update( &mut self, msg: Msg ) { match msg { Msg::Increment => self.value += 1, } } fn on_key( &mut self, keysym: Keysym ) -> Option { if keysym == Keysym::Escape { std::process::exit( 0 ); } None } } fn main() { ltk::run( CounterApp { value: 0 } ); } ``` ### Minimal `Cargo.toml` ```toml [package] name = "my-ltk-app" version = "0.1.0" edition = "2021" [dependencies] ltk = { path = "../ltk" } ``` If you vend `ltk` from crates.io later, replace the `path` dependency with a versioned one. ## Public API Layers `ltk` exposes most items at the crate root, but for documentation and discovery it is useful to think of the library in three layers. ### 1. `ltk::window` This is the default entry point for third-party applications. Use it for: - normal application windows - tools and prototypes - most widget/layout work The APIs you will usually touch first live here conceptually: - `App` - `Element` - `button`, `text`, `text_edit`, `img_widget` - `column`, `row`, `stack`, `grid`, `spacer` - `container`, `scroll`, `slider`, `toggle`, `checkbox`, `radio` - `Color` - `run` ### 2. `ltk::shell` This layer groups the APIs that matter when your surface is part of the shell rather than a normal app window. Use it for: - bars and docks - homescreens - notifications - greeters and lock screens - transient overlays The most important APIs in this layer are: - `ShellMode` - `Layer` - `Anchor` - `OverlaySpec` - `OverlayId` - `overlays()` ### 3. `ltk::runtime` This layer is for advanced integration points. Use it when you need: - external wakeups via `set_channel_sender()` - timer-driven or async state via `poll_external()` / `poll_interval()` - redraw narrowing via `invalidate_after()` - runtime theme state access - runtime-free embedding through `core::UiSurface` Most applications do not need to start here. ## Regular app window vs shell surface Most consumers should start with a **regular window**. Default behaviour: - `shell_mode()` defaults to `ShellMode::Window` - `ltk::run(app)` creates an xdg-shell toplevel Use this for: - normal applications - internal tools - prototypes while learning the toolkit Switch to **layer-shell** only when you are building a shell component: - top bar - dock - homescreen - notification surface - lock screen / greeter The knobs you will usually override are: - `shell_mode()` - `layer_anchor()` - `layer_size()` - `exclusive_zone()` - `keyboard_exclusive()` - `background_color()` For a non-trivial multi-surface example, use `examples/mini_shell.rs` as the reference entry point — note it runs as a regular window (it never overrides `shell_mode()`) and demonstrates screen routing, coordinated overlays, an animated OSD and live theme switching. The layer-shell knobs themselves are exercised by the downstream shell components, not by the in-repo examples. ## The APIs you will touch first In practice, most first apps only need a small subset of the surface area. Start here: - `App` - `Element` - `button`, `text`, `text_edit`, `img_widget` - `column`, `row`, `stack`, `grid`, `spacer` - `container`, `scroll`, `slider`, `toggle`, `checkbox`, `radio` - `Color` - `run` Do not start with these unless you need them: - `ltk::shell` - `ltk::runtime` - `overlays()` - gesture hooks such as `on_swipe_*` - `set_channel_sender()` / `poll_external()` - `core::UiSurface` - custom theming APIs ## Message flow and state The expected shape is: 1. user interaction emits a `Message` 2. `update()` mutates app state 3. `view()` rebuilds the UI from that state Example: ```rust #[derive(Clone)] enum Msg { NameChanged( String ), Submit, } ``` For small apps, one top-level `enum Msg` is enough. Once the app grows, split state by screen/panel and wrap sub-messages in the top-level enum: ```rust,no_run # #[ derive( Clone ) ] pub enum HomeMsg {} # #[ derive( Clone ) ] pub enum SettingsMsg {} enum AppMsg { Home( HomeMsg ), Settings( SettingsMsg ), Quit, } ``` This is the pattern used by `examples/mini_shell.rs`. ## Responsive sizing: fluid vs physical `ltk` gives you two ways to make an interface adapt to the display, and you can mix them per value. **Fluid** sizes are a fraction of the surface (best for full-screen system surfaces); **physical** sizes stay a constant real-world size (best for conventional windowed apps). In practice you write a size as a `Length` and bound it with `.clamp`: ```rust use ltk::{ text, Length }; // Fluid: 6 % of the surface's short side, never below 20 px nor above 44 px. text("Welcome").size(Length::vmin(6.0).clamp(20.0, 44.0)); ``` Stock widgets follow a process-wide mode (`set_widget_scaling`, fluid by default); an explicit `Length` on a widget overrides it. For the units (`vmin` / `orient` / `dp` / …), the clamp discipline and how it all resolves, see the *Responsive sizing* section of [`architecture.md`](architecture.md#responsive-sizing) and the `Length` rustdoc. ## Recommended learning order If you are new to the library, this order minimizes confusion: 1. Run `examples/showcase.rs`. 2. Read the crate-level docs in `src/lib.rs`, especially `ltk::window`. 3. Build a plain xdg-shell window with `button`, `text`, `column`. 4. Add input handling with `text_edit` or `slider`. 5. Only then look at `ltk::shell` for overlays and layer-shell. 6. Move to `ltk::runtime` only when you need advanced hooks or embedding. ## Performance rules of thumb `ltk` is designed to sleep when idle and redraw only on real changes, but the application can still make bad choices. Keep these rules in mind: - keep `view()` pure and cheap - do not do filesystem I/O, parsing or image decoding inside `view()` - cache expensive derived data on your app struct - leave `poll_interval()` as `None` unless you genuinely need periodic wakeups - only return `true` from `is_animating()` while something is actually moving On mobile targets, the last two matter directly for battery life. ## When to use `core::UiSurface` Most apps should ignore `core` at first. Use `core::UiSurface` when you want `ltk`'s layout/drawing/hit-testing without `ltk::run()`. Typical cases: - compositor-side decorations - embedding `ltk` widgets in another render loop - offscreen rendering or previews There is coverage for that path in `tests/core_surface.rs`. ## Current assumptions and rough edges This repo is usable, but a few current behaviours are worth knowing up front: - examples and docs assume Wayland, not X11 - theming is process-global - theme discovery currently expects a `default` theme on disk (a B/W fallback document kicks in when missing, with a red banner on every frame so the gap is impossible to miss) - the architecture docs mention downstream consumer repos that are not part of this repository None of that blocks learning the toolkit, but it matters when you evaluate `ltk` as a third-party dependency. ## What to read next In the README's recommended order — onboarding, then the widget catalogue, then the cookbook, then architecture: - [`docs/widgets.md`](./widgets.md) — per-widget catalogue: what each one is, when to use it, minimal example - [`docs/cookbook.md`](./cookbook.md) — concrete recipes: slide-in panels, password fields, runtime theme toggle, channel-driven state - [`docs/architecture.md`](./architecture.md) — multi-surface patterns, theming, animation and performance - [`docs/theming.md`](./theming.md) — JSON theme schema, slot conventions, runtime APIs - [`examples/showcase.rs`](../examples/showcase.rs) — smallest visual tour - [`examples/widgets.rs`](../examples/widgets.rs) — broader widget coverage - [`examples/mini_shell.rs`](../examples/mini_shell.rs) — overlays and shell patterns - [`tests/core_surface.rs`](../tests/core_surface.rs) — runtime-free rendering