Responsive scaling. ltk now offers two first-class ways to size a UI so it adapts across screens, chosen per process via `WidgetScaling { Fluid, Physical }` (`set_widget_scaling` / `widget_scaling`, default `Fluid`). Fluid sizing (`Length::fluid( px )`) makes a design pixel a proportion of the surface's smaller side, calibrated against a reference width (`set_fluid_reference` / `fluid_reference`, 412 px default) and bounded by `FLUID_MIN` / `FLUID_MAX`; physical sizing (`Length::dp( px )`) is a constant-physical-size pixel scaled by display density (`set_density` / `density`). `Length` gains `orient( portrait, landscape )` — resolve one value in portrait, another in landscape — plus `widget( px )`, which picks fluid or dp per the active mode. Canvas exposes `geom_px` (geometry, resolved in physical layout space) and `font_px` (font size, bridging logical / physical per mode) so widgets and apps share one resolution path. Note the rename: `set_design_reference` / `design_reference` became `set_fluid_reference` / `fluid_reference`, and `Length::dp` changed meaning — the old surface-proportional behaviour now lives on `Length::fluid`.
Widgets. Every stock widget resolves its default geometry and font through the widget-scaling mode instead of frozen pixels, so a whole UI scales coherently without per-call units. New size builders where they were missing: `button` gains `font_size` / `height`, `text_edit` gains `height` / `font_size_fluid`, `separator` gains `pad_v`, and assorted widgets accept a `Length` where they previously took only `f32`.
Overlays. `OverlaySpec::size` is now `( Length, Length )` instead of `( u32, u32 )`, resolved against the main surface when the overlay is materialized, so overlays can scale with the display; `Length::px( … )` reproduces the old fixed sizing.
API stabilization (toward 1.0). Widget struct fields are now `pub( crate )` — they are configured through builders, not field access — except the value / state types apps genuinely read or construct (`Time`, `Date`, `ComboState`), which stay public. The internal `test_support` helpers move behind a `test-support` Cargo feature (off by default, so third-party builds never see them; ltk's own `make test` enables it). `Separator` drops its `0.0`-means-mode sentinel for `Option<Length>`, so an explicit `pad_v( 0.0 )` is a real flush divider distinct from the mode-following default.
Performance guardrails. Opt-in diagnostics via `LTK_PERF_WARN=1` warn about stuck animations, sustained software-render animation, and low `poll_interval`; software-rendered animation is capped near 30 Hz to spare CPU on machines that fall back off EGL. Apps can override the cap with `App::cap_software_animation`.
Docs and build. The two scaling modes are documented in README, onboarding and architecture, with the earlier gradient / backdrop doc drift cleaned up. The Makefile now ships the `locales/` directory into the packaged crate (fixing i18n keys rendering raw for downstreams), builds the new `responsive` example, and runs tests with `--features test-support`.
411 lines
14 KiB
Markdown
411 lines
14 KiB
Markdown
# ltk
|
|
|
|
`ltk` is a public Rust UI toolkit for Wayland applications.
|
|
|
|
It is developed by Liberux as part of the Eydos stack, where it powers
|
|
shell and application surfaces, but it is published as a reusable library for
|
|
third-party developers building their own Wayland software.
|
|
|
|
Being written in Rust is also part of the project's value proposition:
|
|
|
|
- memory safety without a garbage collector
|
|
- predictable resource lifetimes through ownership and borrowing
|
|
- good control over allocations and data movement in rendering-heavy code
|
|
- a strong fit for low-level UI, graphics, and system-integration work
|
|
|
|
For a Wayland toolkit, that combination is useful in practice: it reduces an
|
|
entire class of memory-management bugs common in lower-level UI stacks while
|
|
still allowing tight control over performance-sensitive paths.
|
|
|
|
## What It Is
|
|
|
|
`ltk` is a lightweight, declarative toolkit with an Elm-shaped model:
|
|
|
|
- implement `App`
|
|
- return an `Element<Msg>` tree from `view()`
|
|
- update your state in `update()`
|
|
- run the event loop with `ltk::run(app)`
|
|
|
|
The runtime handles layout, drawing, input dispatch, focus, overlays, and
|
|
backend selection between GLES and software rendering.
|
|
|
|
## What It Is Not
|
|
|
|
`ltk` is not:
|
|
|
|
- a browser UI toolkit
|
|
- a cross-platform desktop toolkit
|
|
- a general-purpose web-style framework
|
|
|
|
Today it is specifically a Wayland toolkit. If you are building native Wayland
|
|
applications, panels, launchers, lock screens, or other shell-adjacent
|
|
surfaces, it is in scope. If you need Windows, macOS, or browser targets, it
|
|
is not.
|
|
|
|
## Project Status
|
|
|
|
`ltk` is a public library intended for third-party use, but it is still shaped
|
|
by real production needs inside the Liberux / Eydos ecosystem.
|
|
|
|
That means:
|
|
|
|
- the API is usable for external applications today
|
|
- the project is optimized first for native Wayland workloads
|
|
- some advanced APIs are still more shell-oriented than app-oriented
|
|
- public documentation and examples are present, but the project is not trying
|
|
to present itself as a cross-platform beginner toolkit
|
|
|
|
If you are evaluating `ltk` for a third-party application, the right mental
|
|
model is "public Wayland toolkit with production consumers" rather than
|
|
"experimental demo crate".
|
|
|
|
## Why Third Parties Might Use It
|
|
|
|
`ltk` is designed around a few practical goals:
|
|
|
|
- low idle wakeups and event-driven redraws
|
|
- partial redraws and damage tracking
|
|
- a simple declarative tree instead of retained widgets
|
|
- direct support for normal windows, layer-shell, and ext-session-lock surfaces
|
|
- a runtime-free core (`ltk::core::UiSurface`) for embedding layout and drawing
|
|
without `ltk::run()`
|
|
|
|
This makes it especially relevant for:
|
|
|
|
- Wayland applications
|
|
- mobile-first Linux shells
|
|
- launchers and dashboards
|
|
- greeters and lock screens
|
|
- compositor-side or embedded UI surfaces
|
|
|
|
## Quick Start
|
|
|
|
Add `ltk` to your `Cargo.toml`:
|
|
|
|
```toml
|
|
[dependencies]
|
|
ltk = { path = "../ltk" }
|
|
```
|
|
|
|
Minimal app:
|
|
|
|
```rust
|
|
use ltk::{ App, Element, button, column, spacer, text };
|
|
|
|
#[derive(Clone)]
|
|
enum Msg
|
|
{
|
|
Increment,
|
|
}
|
|
|
|
struct CounterApp
|
|
{
|
|
value: u32,
|
|
}
|
|
|
|
impl App for CounterApp
|
|
{
|
|
type Message = Msg;
|
|
|
|
fn view( &self ) -> Element<Msg>
|
|
{
|
|
column::<Msg>()
|
|
.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 main()
|
|
{
|
|
ltk::run( CounterApp { value: 0 } );
|
|
}
|
|
```
|
|
|
|
## Requirements
|
|
|
|
`ltk` currently assumes:
|
|
|
|
- **Rust 1.85** or newer (the toolchain shipped with Debian stable; declared
|
|
as `rust-version` in `Cargo.toml`).
|
|
- A running **Wayland** session — there is no X11 backend.
|
|
- System headers for `libwayland`, `libegl` and `libxkbcommon` at compile
|
|
time. On Debian / Ubuntu:
|
|
```bash
|
|
sudo apt-get install libwayland-dev libegl-dev libxkbcommon-dev pkg-config
|
|
```
|
|
- A usable system font (`fonts-sora`, `fonts-liberation`, `fonts-dejavu`,
|
|
…). If none is installed `ltk` falls back to an embedded Sora Regular
|
|
build with a stderr warning.
|
|
- A theme named `default`, installed system-wide (the
|
|
`ltk-theme-default` Debian package drops it under
|
|
`/usr/share/ltk/themes/default/`) or exposed through
|
|
`LTK_THEMES_DIR` for development.
|
|
|
|
Rendering backend selection is automatic:
|
|
|
|
- **GLES** when EGL is available (every modern Wayland compositor).
|
|
- **Software** fallback otherwise.
|
|
- Set `LTK_FORCE_SOFTWARE=1` to force the software path even when EGL is
|
|
available — useful for headless test runs and for diagnosing
|
|
driver-specific bugs.
|
|
|
|
For development inside this repository:
|
|
|
|
```bash
|
|
export LTK_THEMES_DIR="$PWD/themes"
|
|
cargo run --example showcase
|
|
```
|
|
|
|
## Examples
|
|
|
|
Useful entry points in this repository:
|
|
|
|
- `cargo run --example showcase`
|
|
- `cargo run --example responsive`
|
|
- `cargo run --example widgets`
|
|
- `cargo run --example inputs`
|
|
- `cargo run --example scroll`
|
|
- `cargo run --example combo`
|
|
- `cargo run --example dialog`
|
|
- `cargo run --example sliders`
|
|
- `cargo run --example pickers`
|
|
- `cargo run --example mini_shell`
|
|
|
|
In general:
|
|
|
|
- start with `showcase` for a regular app window
|
|
- use `responsive` to see the fluid vs physical modes on stock widgets
|
|
- use `widgets` to see the core controls
|
|
- use `mini_shell` if you need overlays, theme switching, or shell-style
|
|
composition
|
|
|
|
## Public API Overview
|
|
|
|
Most applications should start with this subset:
|
|
|
|
- `App`
|
|
- `Element<Msg>`
|
|
- widgets such as `button`, `text`, `text_edit`, `image`
|
|
- layouts such as `column`, `row`, `stack`, `grid`, `spacer`
|
|
- `Color`
|
|
- `run`
|
|
|
|
More advanced APIs are available when needed:
|
|
|
|
- `overlays()`
|
|
- `shell_mode()` and layer-shell controls
|
|
- `set_channel_sender()` and `poll_external()`
|
|
- gesture hooks such as `on_swipe_*`
|
|
- `core::UiSurface`
|
|
- runtime theme APIs
|
|
|
|
## Responsive Design
|
|
|
|
`ltk` offers **two** first-class ways to make an interface adapt to the
|
|
display, chosen per value or per process:
|
|
|
|
- **Fluid** — sizes are a fraction of the surface, tracking the short side
|
|
(width in portrait, height in landscape). Best for full-screen system
|
|
surfaces. Written with `Length::vmin` / `orient` / `fluid` and bounded with
|
|
`.clamp`.
|
|
- **Physical** — sizes stay a constant real-world size across displays (the
|
|
mainstream HiDPI `dp` model). Best for conventional windowed apps. Written
|
|
with `Length::dp` plus `set_density`.
|
|
|
|
Stock widgets follow the process-wide mode (`set_widget_scaling`, fluid by
|
|
default); an explicit `Length` on a widget always overrides it. The full
|
|
mechanics live in [`docs/architecture.md`](docs/architecture.md#responsive-sizing),
|
|
a walkthrough in [`docs/onboarding.md`](docs/onboarding.md), and the per-item
|
|
reference in the `Length` / `WidgetScaling` rustdoc.
|
|
|
|
## Windows and Shell Surfaces
|
|
|
|
By default, `ltk` creates a regular `xdg-shell` window.
|
|
|
|
That is the right starting point for:
|
|
|
|
- normal applications
|
|
- internal tools
|
|
- prototypes
|
|
|
|
Switch to layer-shell only when you are building shell surfaces such as:
|
|
|
|
- top bars
|
|
- docks
|
|
- homescreens
|
|
- notifications
|
|
- greeters
|
|
- lock screens
|
|
|
|
For a screen locker, use `ShellMode::SessionLock` instead of layer-shell: it
|
|
presents an `ext-session-lock-v1` surface that the compositor keeps on top of
|
|
everything until the app returns `true` from `requested_exit()`, which makes
|
|
the runtime call `unlock` and lift the lock.
|
|
|
|
## Performance Notes
|
|
|
|
`ltk` is designed to sleep when idle and redraw only on real work.
|
|
|
|
The main rules for downstream applications are:
|
|
|
|
- keep `view()` pure and cheap
|
|
- do not perform I/O inside `view()`
|
|
- use `poll_interval()` sparingly
|
|
- return `true` from `is_animating()` only while something is actually moving
|
|
- cache decoded images and expensive derived state in your app
|
|
|
|
The library already provides:
|
|
|
|
- event-driven redraw scheduling
|
|
- per-surface invalidation
|
|
- partial redraws for interaction-only changes
|
|
- GPU and software backends behind the same widget API
|
|
|
|
## Backend Differences
|
|
|
|
The public API is the same across backends, but visual parity is not perfect
|
|
yet. The widget tree, layout, hit-testing, text, images, fills, strokes and
|
|
clipping all paint identically on both paths. The gaps are in gradients and the
|
|
shadow / backdrop pipeline.
|
|
|
|
Effects that currently render only on the **GLES** backend, and degrade on the
|
|
**Software** backend:
|
|
|
|
- **Gradients** (linear and radial, via `Canvas::fill_paint_rect`) — rendered with
|
|
dedicated shaders on GLES; on software they collapse to a flat fill from the
|
|
first stop (tiny-skia can render gradients natively, but that is not wired up
|
|
yet).
|
|
- **Outer drop shadows** (`Canvas::fill_shadow_outer`) — themed surfaces that
|
|
declare a `Shadow` slot show the soft halo on GLES and a flat fill on
|
|
software.
|
|
- **Inner / inset shadows** (`Canvas::fill_shadow_inset`) — `InsetShadow`
|
|
slots paint nothing on software.
|
|
- **Inset shadow blend modes** — `PlusLighter`, `Multiply`, `Screen` and
|
|
`Overlay` are GLES-only; the GLES `Overlay` path snapshots the framebuffer
|
|
and computes the CSS Overlay formula in-shader, which has no software
|
|
equivalent today.
|
|
|
|
Calls to these APIs are safe on both backends — they simply produce a flatter
|
|
appearance under software. No widget panics, returns an error, or skips
|
|
unrelated drawing.
|
|
|
|
If your application leans heavily on shadows or inset effects, validate both
|
|
rendering paths before shipping. Force the software path with:
|
|
|
|
```bash
|
|
LTK_FORCE_SOFTWARE=1 cargo run --example showcase
|
|
```
|
|
|
|
Closing this gap (porting the shadow / inset-shadow pipeline to tiny-skia) is
|
|
on the post-v0.1 roadmap.
|
|
|
|
## Documentation
|
|
|
|
| File | When to read it |
|
|
| --- | --- |
|
|
| [`docs/onboarding.md`](docs/onboarding.md) | First hour with the library — environment, first app, what to ignore at first. |
|
|
| [`docs/architecture.md`](docs/architecture.md) | Runtime model, overlays, animation, theming, performance and where the cost of a frame lives. |
|
|
| [`docs/widgets.md`](docs/widgets.md) | Per-widget catalogue: what each one is, when to use it, minimal example, see-also. |
|
|
| [`docs/theming.md`](docs/theming.md) | JSON theme schema, slot conventions, runtime APIs. |
|
|
| [`docs/cookbook.md`](docs/cookbook.md) | Concrete recipes — slide-in panels, password fields, runtime theme toggle, channel-driven state, embedding without `ltk::run`. |
|
|
| `cargo doc --open` | Per-item rustdoc for the public API. |
|
|
| [`SECURITY.md`](SECURITY.md) | How to report a vulnerability and what is in / out of scope. |
|
|
| [`CONTRIBUTING.md`](CONTRIBUTING.md) | Build, test, code style, patch shape. |
|
|
|
|
Recommended reading order for a new contributor:
|
|
|
|
1. run `examples/showcase.rs`
|
|
2. read `docs/onboarding.md`
|
|
3. browse `docs/widgets.md` for the catalogue
|
|
4. dip into `docs/cookbook.md` when you hit a specific shape
|
|
5. open `docs/architecture.md` once you need overlays, animations, or
|
|
runtime theming.
|
|
|
|
## Relationship to Liberux and Eydos
|
|
|
|
Liberux is the promoter and primary maintainer of `ltk`.
|
|
|
|
The project exists because Eydos needs a native Wayland toolkit for its
|
|
own shell and application stack, but `ltk` is intentionally published as a
|
|
public library rather than kept as a private internal component. Third-party
|
|
developers are part of the intended audience.
|
|
|
|
That origin matters because it explains the current priorities:
|
|
|
|
- strong Wayland focus
|
|
- support for layer-shell and shell-style overlays
|
|
- attention to mobile power usage
|
|
- theming and runtime surfaces that fit an operating system environment
|
|
|
|
## License
|
|
|
|
This project is licensed under `LGPL-2.1-only`.
|
|
|
|
That means third parties can use `ltk` in their own applications, including
|
|
proprietary ones, subject to the obligations of the GNU Lesser General Public
|
|
License v2.1. If you are planning a commercial or closed-source product, read
|
|
the license text carefully and make sure your distribution model complies with
|
|
it.
|
|
|
|
See [LICENSE](./LICENSE).
|
|
|
|
## Third-party assets
|
|
|
|
`ltk`'s default theme bundles two third-party asset sets that travel under
|
|
their own licences. Anyone redistributing the toolkit (or a binary that
|
|
embeds the default theme) must propagate the attributions below.
|
|
|
|
- **Symbolic icons** under `themes/default/icons/catalogue/` come from
|
|
Streamline's *Core Line Free* set, distributed under
|
|
[Creative Commons Attribution 4.0 International](https://creativecommons.org/licenses/by/4.0/)
|
|
(CC BY 4.0). © Streamline. Some files have been modified for the
|
|
symbolic-tinting pipeline; details in
|
|
[`themes/default/icons/catalogue/LICENSE.md`](./themes/default/icons/catalogue/LICENSE.md).
|
|
Upstream: <https://www.streamlinehq.com/icons/core-line-free>.
|
|
- **Sora Regular** (`src/theme/fallback/Sora-Regular.otf`) is the
|
|
embedded font fallback, distributed under
|
|
[SIL Open Font Licence 1.1](https://scripts.sil.org/OFL).
|
|
© 2019-2020 The Sora Project Authors, Jonathan Barnbrook, Julián Moncada.
|
|
Upstream: <https://github.com/sora-xor/sora-font>.
|
|
|
|
The remaining artwork in the default theme — wallpapers, lockscreens,
|
|
launcher logo, brand-mark variants and per-application icons — is
|
|
original to Liberux Labs and travels under the toolkit's own
|
|
`LGPL-2.1-only` licence.
|
|
|
|
The full Debian-style declaration of every asset and its licence lives
|
|
in [`debian/copyright`](./debian/copyright); that is the file the `.deb`
|
|
ships under `/usr/share/doc/libltk*/copyright`.
|
|
|
|
## Contributing
|
|
|
|
Patches and bug reports are welcome. Read
|
|
[`CONTRIBUTING.md`](CONTRIBUTING.md) for the practical mechanics: build
|
|
prerequisites, how to run tests, the project's Modified Allman code
|
|
style, and what shape a pull request should take.
|
|
|
|
For security-sensitive issues see [`SECURITY.md`](SECURITY.md) — please
|
|
do not file those through the public issue tracker.
|
|
|
|
If you are evaluating `ltk` for a third-party product and are unsure
|
|
whether your use case is in scope, open a discussion before writing
|
|
code. That is especially useful when you are:
|
|
|
|
- missing an app-facing example,
|
|
- blocked by a shell-oriented assumption in the API,
|
|
- trying to understand whether a given platform target is realistic.
|