Crate ltk

Source
Expand description

§ltk — Liberux ToolKit

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

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 app_id( &self ) -> &str { "net.example.Hello" }

    // Nothing worth restoring in a one-button app.
    fn save_state( &self ) -> Option<Vec<u8>> { None }
    fn restore_state( &mut self, _state: Vec<u8> ) {}

    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; grid_min_cell() derives the column count from the width instead.
  • 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).

When the structure of the layout should change with the orientation — a row of panels in landscape, the same panels stacked in portrait — branch the view on orientation() (backed by viewport_size(), recorded by the runtime on every configure and sharing orient’s square-counts-as-portrait rule). See examples/clip_path.rs.

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 × the pixel density, applied when the value is resolved — the mainstream HiDPI unit), or LengthBase::Em for text relative to the root font size. The pre-calibrated theme::typography scale (theme::typography::h0…theme::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:

  • Fluid — Length::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).
  • Physical — Length::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.
  • The user’s folders: XDG’s on Linux, the profile’s on Windows. An app keeps its files in a folder of its own under one of these, named after itself.
  • EGL bootstrap for the GPU rendering path.
  • A dialog that picks a file to open, or a folder and a name to save one as, over crate::files, alike natively and in a browser. The app holds it, puts its FileChooser::view in its own view, and hands its messages to FileChooser::update, which says when the user chose.
  • The user’s files, asynchronously and alike on every platform: natively through std::fs on a worker thread of ltk’s, in a browser through forge/web’s file requests (forge/web/DESIGN.md, section 11). Every operation hands its result to done, whose message reaches the app through its ChannelSender, so nothing blocks the app’s thread. Results come in no set order, so an app that may have moved on checks that a result is still the one it waits for.
  • Glyph source: outlines extracted on first use, never up front.
  • GPU-accelerated rendering backend using EGL + GLES2 / GLES3.
  • Layouts — composable arrangers for Element trees.
  • NavStack: the navigation state of a multi-screen app.
  • The on-screen keyboard: layouts from data files, and a Panel any host drives with presses and draws on a canvas.
  • A print dialog with a preview of the pages, and the system’s printers through CUPS. The app renders the document to PDF itself: the dialog asks it for a preview whenever the layout changes, and says where to print and how once the user accepts.
  • 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.
  • The monotonic clock, which std does not provide in a browser: Instant is std’s natively and web_time’s on wasm32, where std::time::Instant::now panics. An app that builds for both uses it in place of std’s.
  • Geometry and primitive value types used across the public API.
  • Reading themes, fonts, keyboard layouts and an app’s own files: from the file system, or in a browser, which has none, from the files the page loaded into memory with ltk::web::load_resources or ltk::web::load_tree. An app that also builds for the browser reads its files through it.
  • 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.
  • A wrapper that re-positions its child relative to an anchor widget found in the previous frame’s layout snapshot.
  • Delivers messages into the run loop from any thread and wakes it. Given to the app by crate::App::set_channel_sender.
  • 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 its parent treats it like a Spacer for leftover-space distribution, but draws the child inside the allocated rect: leftover width inside a Row, leftover height inside a Column.
  • 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.
  • A mouse button pressed or released, as App::on_mouse_button reports it.
  • 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 key press or release as App::on_raw_key_event reports it.
  • 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).
  • One tab: a key that stays the same while it is open, which its animation follows, and what it shows.
  • Animation state of a tab strip, kept by the app across frames.
  • The open documents, as a strip on a desktop and a bar on a phone.
  • The colours of a TabStrip and its TabOverview. The default follows the theme.
  • Resolve the font size into a concrete logical px. Some( len ) resolves against Canvas::viewport_logical — the font space, so × dpi_scale at raster yields the right physical size — exactly like the button label; None follows the widget-scaling mode at the theme default. Shared by the draw and hit-test paths, which all carry a canvas. A run of a multiline field’s value drawn in a colour of its own, as a syntax highlighter marks keywords, strings and comments. range is in bytes of the value and should fall on character boundaries; runs are expected in order and not overlapping, and what no run covers takes the field’s colour.
  • 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§

  • What a back request does once the app has nothing left to go back to. Returned by App::back_at_root.
  • What an app did with a back request. Returned by App::on_back.
  • 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.
  • A mouse button, as App::on_mouse_button reports it.
  • 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.
  • Which of the two selections an app reads, through App::take_paste_request.
  • 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.
  • The form a TabStrip takes.
  • Horizontal alignment of text within its layout rect.
  • What an app asks a text field to do, through App::take_text_command, as its menu or its search would: the field’s cursor and selection are the runtime’s, which the app does not see.
  • 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. identifier is the compositor’s opaque per-window token, the one to name a single window by when talking to the compositor.
  • Vertical alignment of a child within a Stack rect.
  • Semantic role for a window-decoration button.

Constants§

  • Extra width a wrapping Text needs beyond its widest line: the lines wrap to the rect less this much, and a left-aligned line is inset by it. A host that sizes rects from its own measurement must add it.

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.
  • Draw child right under the widget whose .id( … ) is anchor, as it was laid out on the previous frame: a menu under the button that opens it. Until the anchor has been drawn the child fills the rect its parent gives it.
  • Caret positions for a single line of text at size px with the default UI font: one x per char boundary in logical order (chars + 1 values, starting at 0). A cluster’s advance is split evenly across its chars.
  • caret_offsets with font leading the resolver, as crate::Canvas::draw_text_with_font draws it.
  • 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.
  • Create an adaptive grid: the column count is derived at layout time from the available width, fitting as many columns as possible while keeping every cell at least min_cell_width wide (never fewer than one). Cells then share the width equally, so they range between min_cell_width and just under twice it — cap the count with max_columns to let cells grow instead on very wide surfaces.
  • 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.
  • Vertical metrics of the default UI font at size px.
  • 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.
  • A monospace face for text that lines up in columns, such as line numbers or a terminal; None when none is installed.
  • 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.
  • The app’s resource bundle, when set_resource_dir gave one; on Android, the resources unpacked from the APK; on Windows, otherwise, ltk\ beside the executable when it exists. An app finds its own bundled files there.
  • Free-function shorthand for RichText::new.
  • Create an empty row layout.
  • Run the application. Blocks until the window is closed; in a browser, whose page must not block, it returns once the app is running.
  • 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, widget-scaling default thickness unless overridden).
  • Use dir as the app’s resource bundle. Call it before the theme is first used; later calls are ignored.
  • 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 TabItem.
  • Create a TabOverview of tabs.
  • Create a TabStrip of tabs.
  • 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.