# 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 such as `google-sora-fonts`, `liberation-fonts` or `dejavu-fonts` - 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 cargo run --example showcase ``` Other useful examples: - `cargo run --example widgets` — broad widget survey - `cargo run --example inputs` — text entry - `cargo run --example scroll` — scroll viewport patterns - `cargo run --example mini_shell` — overlays, animation and theme switching All examples require a running Wayland compositor. ## 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. `Canvas` walks a chain of common system font paths (`fonts-sora`, `fonts-liberation`, `fonts-dejavu`, `fonts-freefont`, …) and uses 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 canvas construction 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`, `image` - `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 layer-shell example, use `examples/mini_shell.rs` as the reference entry point. ## 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`, `image` - `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`. ## 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 - [`docs/architecture.md`](./architecture.md) — multi-surface patterns, theming, animation and performance - [`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