Crate ltk

Source
Expand description

§ltk — Liberux ToolKit

A lightweight Wayland UI toolkit built on top of smithay-client-toolkit, tiny-skia and fontdue.

ltk is the UI toolkit for the Liberux desktop. The Liberux compositor (Forge) handles window management, decorations, and positioning — ltk focuses on rendering the content of each Wayland surface.

ltk is also a public library for third-party developers building native Wayland applications. If you are approaching the crate through cargo doc, the API is grouped conceptually into three navigation modules:

  • window — the basic application window path most apps should start with
  • shell — layer-shell and overlay APIs for shell-like surfaces
  • runtime — advanced runtime hooks, invalidation, channels, and runtime-free embedding via core::UiSurface

§Quick start

use ltk::{App, Element, column, text, button, spacer, Color, ButtonVariant};

#[derive(Clone)]
enum Msg { Quit }

struct MyApp;

impl App for MyApp
{
    type Message = Msg;

    fn view( &self ) -> Element<Msg>
    {
        column()
            .push( text( "Hello, ltk!" ).size( 32.0 ).color( Color::WHITE ) )
            .push( spacer() )
            .push( button( "Quit" ).on_press( Msg::Quit ) )
            .into()
    }

    fn update( &mut self, msg: Msg )
    {
        match msg { Msg::Quit => std::process::exit( 0 ) }
    }
}

fn main() { ltk::run( MyApp ); }

§Architecture

  • App trait — implement this to define your application.
  • Element enum — the widget tree returned by App::view.
  • Widgets, layouts and primitive types are listed below in their own sidebar sections; the widgets, layouts and types modules are concept-oriented landing pages that cargo doc exposes for the same set, grouped by category.

§Rendering backends

All drawing goes through a single Canvas, which is one of two interchangeable backends exposing the same API. The GLES backend (GlesCanvas) is the default when an EGL/OpenGL ES context is available and renders on the GPU; the software backend (SoftwareCanvas) rasterises on the CPU with tiny-skia and is the fallback when there is no GL context (and what offscreen/preview rendering uses). The two are kept at visual parity for the common primitives, with a few backend-specific traits documented per method (e.g. multi-rect Canvas::set_clip_rects is an exact mask on software but a bounding-box scissor on GLES, and Canvas::is_software lets a caller branch on a real path clip vs a bounding rect). run() selects the backend automatically; core::UiSurface lets an embedder force one.

§Widgets

The interactive and decorative leaves of the Element tree:

See widgets for the grouped landing page and docs/widgets.md for the per-widget catalogue.

§Layouts

Composable arrangers for Element trees:

  • column() — vertical flow.
  • row() — horizontal flow.
  • stack() — z-order overlay with per-child alignment.
  • grid() — fixed-column-count wrapping grid.
  • spacer() — invisible flexible filler.

See layouts for the grouped landing page.

§Types

Geometry and primitive values that flow through every builder:

See types for the full module with //! description.

§Responsive design: one UI from phone to desktop

A core goal of ltk is that a single view reads coherently across a portrait phone, a tablet and a landscape desktop window — no per-target forks, no media-query soup. The mechanism is fluid sizing: express sizes as a fraction of the surface so the whole design breathes with the screen, instead of freezing at one pixel size that only looks right on one device.

§The core rule — fluid, but clamped

Default to Length::vmin( pct ).clamp( lo, hi ) for every font size, padding, spacing and spacer:

  • the percentage tracks the surface’s smaller side, so portrait and landscape stay coherent — an element keeps the same fraction of the narrow axis whichever way the device is held;
  • the px clamp bounds the fluid range so the design never collapses on a watch-sized surface nor balloons on a 4K monitor.

The clamp is not optional polish: it is what turns pure proportional sizing (fragile at the extremes) into bounded proportional sizing (robust everywhere). Treat “always clamp a fluid value” as the rule, not the exception.

§Orientation-dependent proportion — Length::orient

Sometimes the right proportion differs by orientation, not merely the reference axis. A logo may want 40 % of the width in portrait — there is vertical room to spare — but only 5 % of the height in landscape, where vertical room is scarce. Length::orient( portrait, landscape ) expresses exactly that: portrait % of the width when the surface is portrait, landscape % of the height when it is landscape (the short side of each orientation, but with its own proportion).

For images, pair it with Image::short_side, which sizes the image along the screen’s short side and lets the other axis follow the source aspect ratio:

// 40 % of the width in portrait, 5 % of the height in landscape.
img_widget( rgba, w, h ).short_side( Length::orient( 40.0, 5.0 ) ).into()

§When constant physical size matters instead

Fluid units scale with the screen’s pixels, not with real-world millimetres — and legibility is a function of physical (angular) size, not of what fraction of the screen a glyph fills. When a size must stay a constant physical size across very different displays, use the other mode: Length::dp (a density-independent pixel — n × density, the mainstream HiDPI unit), or LengthBase::Em for text relative to the root font size. The pre-calibrated theme::typography scale (theme::typography::h0theme::typography::body_xs) is built on clamped vmin, so it stays fluid while respecting a readable px floor and ceiling — a good default for running text.

§The two modes, and choosing per app

ltk offers both strategies as first-class citizens and lets the app pick — per value, or process-wide for every stock widget:

  • FluidLength::fluid and the raw vmin / vmax / vw / vh / orient units. Surface-proportional; tracks the short side (width in portrait, height in landscape). Best for full-screen system surfaces on known hardware (lock screen, greeter, splash, kiosk, launcher).
  • PhysicalLength::dp plus set_density / density. Constant real-world size, HiDPI-aware. Best for conventional windowed apps across an open-ended device set.

Stock widgets carry one design pixel per dimension and resolve it through the process-wide WidgetScaling mode (set_widget_scaling): WidgetScaling::Fluid (the default) reads it as Length::fluid, WidgetScaling::Physical as Length::dp. An explicit Length on a widget always overrides the mode.

§Structural changes — branch in view()

When the layout structure must change (sidebar → bottom tabs, two columns → one) rather than merely resize, branch in view() on surface_width / surface_height. Keep this for genuine restructuring; Length covers all pure sizing.

§Runtime-free embedding

Use core::UiSurface when you need ltk’s layout, drawing and hit-testing without run() — typically for compositor-side decorations, embedding ltk widgets in another render loop, or offscreen rendering / previews.

§Licence and third-party assets

ltk itself is distributed under LGPL-2.1-only. The default theme bundles two third-party asset sets that travel under their own licences and must be credited when the toolkit (or a binary that embeds the default theme) is redistributed:

  • Symbolic icons under themes/default/icons/catalogue/ — Streamline’s Core Line Free set, CC BY 4.0, © Streamline. Some files modified for the symbolic-tinting pipeline; details in themes/default/icons/catalogue/LICENSE.md. Upstream: https://www.streamlinehq.com/icons/core-line-free.
  • Sora Regular (src/theme/fallback/Sora-Regular.otf) — the embedded font fallback, SIL OFL 1.1, © The Sora Project Authors, Jonathan Barnbrook, Julián Moncada.
  • Pointer cursors under themes/default/cursors/ — GNOME’s Adwaita cursor theme (the cursors GNOME Shell ships), bundled verbatim, © the GNOME Project. Offered upstream under CC BY-SA 3.0 or LGPL 3, and CC BY-SA 4.0 for the newer assets; any one option satisfies the licence. See themes/default/cursors/README.md (what the set is and how it is used) and themes/default/cursors/LICENSE.md (attribution). Upstream: https://gitlab.gnome.org/GNOME/adwaita-icon-theme.

The remaining artwork in the default theme (wallpapers, lockscreens, launcher logo, brand-mark variants, 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 lives in debian/copyright of the source tree.

Re-exports§

Modules§

  • Scaffolding shared by full-screen ambient surfaces (greeter, lock screen, kiosk): theme bring-up, branding/wallpaper loading and the wallpaper-backed view stack. Thin convenience over theme and WallpaperBundle — every app that paints a wallpaper behind centred content repeats this otherwise.
  • Runtime-free UI surface primitives.
  • EGL bootstrap for the GPU rendering path.
  • GPU-accelerated rendering backend using EGL + GLES2 / GLES3.
  • Layouts — composable arrangers for Element trees.
  • Advanced runtime and embedding API.
  • Shell and layer-surface API.
  • Single- or multi-line text: a font-sized, coloured, aligned string that either stays on one line (truncating with an ellipsis on overflow) or word-wraps to the layout width.
  • Text input field — single-line or multiline. The widget itself owns layout / draw; the runtime side of text editing (insert, delete, cursor movement, selection, clipboard) lives in the event_loop::text_editing private module.
  • Theming for ltk-based applications.
  • Geometry and primitive value types used across the public API.
  • Orientation-aware wallpaper helper.
  • Widget that hosts content rendered by an external GL producer.
  • Widgets — the interactive and decorative leaves of the Element tree.
  • Basic application-window API.

Structs§

  • Layer-shell anchor edges. Determines which screen edges the surface is attached to.
  • The sender end of a channel
  • A two-state opt-in control with a square box and a check glyph.
  • RGBA colour selector with sliders, hex input, preview swatch and a continuous hue strip.
  • A vertical layout container.
  • A combo / select / dropdown widget.
  • Application-owned state for a Combo.
  • A calendar date in the proleptic Gregorian calendar. No time component, no timezone. month is 1–12, day is 1–31 (further constrained by days_in_month).
  • Locale settings for the date picker. Month names and day-of-week labels are pulled from the i18n registry via [rust_i18n::t!] so changing the active locale (e.g. set_locale("es")) flips the calendar without recreating the widget. The remaining piece is the first day of the week, which varies independently of language (US starts on Sunday, ES / FR / DE on Monday) — that one stays as a builder field.
  • Calendar date selector.
  • A centered confirmation dialog with optional title, subtitle, body and action buttons.
  • A widget that defers its pixels to an external GL texture producer.
  • Wraps an Element so that a Row treats it like a Spacer for leftover-width distribution, but draws the child inside the allocated rect.
  • The keyboard symbol, often corresponding to a character.
  • A clickable range [start, end) (byte offsets into the content) and the message to emit when it is tapped.
  • A row inside a list with a primary label and optional subtitle / trailing text.
  • Paginated tab container.
  • One page of a Notebook — a label for the tab strip and the element to show when this page is active.
  • Stable identifier for an overlay surface. The runtime uses it to diff the overlay list between frames: if the same OverlayId is returned from App::overlays on consecutive frames the underlying Wayland surface and its internal state are kept; if it disappears the surface is destroyed.
  • Description of an additional layer-shell surface rendered on top of the main application surface.
  • Wraps any Element and emits a message on tap. Use when you want click-to-emit on something richer than a Button — for example a Container styled as a card holding a row of icon + labels.
  • A linear progress indicator for determinate operations.
  • One option inside a mutually-exclusive group.
  • A wrapped paragraph with clickable link ranges — the ltk counterpart of an Android Spanned carrying URLSpan / ClickableSpan. Each LinkSpan pairs a byte range with a Msg emitted on tap; the layout pass yields one hit rect per link line so taps land on the link rather than the paragraph.
  • A horizontal layout container.
  • A horizontal divider line.
  • A horizontal slider for selecting a value in a range.
  • A flexible, invisible spacer that expands to fill available space.
  • An indeterminate progress spinner.
  • A layout that draws all its children stacked on top of each other. Each child can be positioned within the Stack rect via HAlign/VAlign.
  • Stable identifier for a subsurface, used to diff the list returned by App::subsurfaces between frames the same way OverlayId diffs overlays.
  • An input-transparent child surface composited over the main surface and moved by the compositor.
  • Segmented horizontal tab selector. One row of pressable cells with the active cell painted as a filled pill and inactive cells as plain text. Build it from a slice of labels; produce an Element with Self::build (or by .into()-ing it where an Element is expected).
  • A wall-clock time, no date / no timezone. hour is 0–23 (24-hour representation regardless of TimePicker::twelve_hour display mode), minute and second are 0–59.
  • Time-of-day selector with up / down steppers per unit.
  • Builder for a transient bottom-anchored notification overlay.
  • A two-state on / off switch.
  • Builder for an xdg-popup-backed hint anchored to a widget.
  • A vertical slider — a rounded pill that fills from bottom to top to indicate its value.
  • A non-scrollable clipped viewport for revealing only part of a child tree.
  • Button styled for compositor / window decorations.
  • A grid layout that wraps children into rows of a fixed column count.

Enums§

  • Visual style of a text button.
  • Per-frame rendering surface. Wraps a backend (software or GPU) behind an enum so widgets can stay backend-agnostic.
  • Backends an External widget can pull pixels from.
  • Horizontal alignment of a child within a Stack rect.
  • Which surfaces a given App::update mutation can affect.
  • Layer-shell layer position.
  • Reasons crate::try_run (and therefore crate::run) can fail to bring up the event loop.
  • Which axes a Scroll viewport allows to move along. Determines whether the layout grows the child past the viewport width, the viewport height, or both, and which axis gesture / wheel deltas route to.
  • Wayland shell mode for the application surface.
  • Which axis a slider tracks. Used by input dispatch to pick the right value_from_*_in_rect formula for a Slider (horizontal) or crate::widget::vslider::VSlider (vertical).
  • Which surface a SubsurfaceSpec is composited as a child of. Main (the default position) parents to the app’s main surface; Overlay(id) parents to one of the App::overlays surfaces, so a slide can ride above app windows the way an overlay panel does. An Overlay parent that is absent or not yet configured is skipped for that frame.
  • One of the surfaces an App can target with an invalidation. Used inside InvalidationScope::Only to name the affected surfaces.
  • Horizontal alignment of text within its layout rect.
  • Wayland ext-foreign-toplevel-list-v1 event delivered to apps via App::on_toplevel_event. Opened fires after the compositor commits the first done for a new handle (so app_id is the value in effect at that point — the protocol allows the compositor to re-commit later, but most don’t). Closed fires when the compositor sends closed and the runtime is about to destroy the handle proxy. Both carry the same id, the Wayland protocol id of the handle, unique within the session and stable for the handle’s lifetime.
  • Vertical alignment of a child within a Stack rect.
  • Semantic role for a window-decoration button.

Traits§

  • Trait that application types must implement to integrate with ltk.

Functions§

  • Add delta months to (year, month) with wraparound. Negative deltas walk backwards; year crosses are handled.
  • Create a Checkbox in the given state.
  • Create a ColorPicker starting from the given colour.
  • Format a Color as #RRGGBB (or #RRGGBBAA when with_alpha is true and the colour is not fully opaque). Bytes are clamped to 0..=255.
  • Create an empty column layout.
  • Create a Combo over items driven by state.
  • Create a DatePicker with the given selected date.
  • Day of the week for (year, month, day). 0 = Sunday, 1 = Monday, …, 6 = Saturday. Uses Zeller’s congruence; undefined for years before AD 1.
  • Number of days in month of year (1-indexed month). Returns 0 for invalid month numbers.
  • Construct a Dialog. See the type’s documentation for the full builder API and the lowering details.
  • Create a grid layout with the given number of columns.
  • true when year is a leap year in the proleptic Gregorian calendar.
  • true when the active surfaces on this thread render through the software path. Used by view code that wants to avoid pipeline effects the software backend doesn’t implement.
  • Create a ListItem with the given primary label.
  • Measure a single line of text at size px with the default UI font (and the system fallback chain), returning (width, line_height) in pixels. For laying text out without a live crate::Canvas — e.g. an embedded Android measure pass that needs the same metrics the renderer will use.
  • Create an empty Notebook.
  • Parse a hex colour string ("#RGB" / "#RGBA" / "#RRGGBB" / "#RRGGBBAA", with or without the leading #, case-insensitive) into a Color. Returns None for malformed input.
  • Create a ProgressBar at the given fraction (clamped to [0.0, 1.0]).
  • Create a Radio option in the given state.
  • Free-function shorthand for RichText::new.
  • Create an empty row layout.
  • Run the application. Blocks until the window is closed.
  • Create a scrollable viewport wrapping child. Defaults to vertical scrolling; chain Scroll::horizontal or Scroll::both to switch axes.
  • Create a default Separator (theme divider colour, 1 px thickness).
  • Create a Slider with the given value (clamped to [0.0, 1.0]).
  • Create a flexible spacer with weight 1.
  • Create a Spinner with default size and theme colour.
  • Create an empty Stack.
  • Create a TabBar from any iterable of label-likes.
  • Create a TimePicker with the given current time.
  • Create a Toast with the given message.
  • Create a Toggle in the given state.
  • Create a Tooltip anchored to the widget tagged with anchor_id.
  • Run the application, returning a typed error on init failure.
  • Create a clipped viewport wrapping child.
  • Create a VSlider at the given value (clamped to [0.0, 1.0]).
  • Build an External widget that hosts content rendered by a caller-managed GL texture producer.
  • Create a window-decoration button.
  • Create the standard minimize / maximize-or-restore / close control group.