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 withshell— layer-shell and overlay APIs for shell-like surfacesruntime— advanced runtime hooks, invalidation, channels, and runtime-free embedding viacore::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
Apptrait — implement this to define your application.Elementenum — the widget tree returned byApp::view.- Widgets, layouts and primitive types are listed below in their own
sidebar sections; the
widgets,layoutsandtypesmodules are concept-oriented landing pages thatcargo docexposes 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:
- Buttons / activations —
button(),icon_button,pressable,window_button,list_item(). - Stateful binary controls —
toggle(),checkbox(),radio(). - Continuous controls —
slider(),vslider(),progress_bar(). - Text —
text(),text_edit(). - Decoration —
container(),separator(),img_widget(). - Clipping wrappers —
scroll(),viewport(),flex(). - Modal overlays —
dialog()(centered confirmation card with optional title, subtitle, custom body and action row; built-in scrim, ESC-to-cancel, and tap-outside-to-dismiss for the non-modal variant).
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:
Color,Rect,Point,Size,Corners,WidgetId.Length— a size/distance that may be absolute pixels (LengthBase::Px), relative to the surface viewport (LengthBase::Vw/LengthBase::Vh/LengthBase::Vmin/LengthBase::Vmax/LengthBase::Orient) or to the root font size (LengthBase::Em). Every setter that takes a size, padding, spacing or font height now acceptsimpl Into<Length>, so legacy.size( 24.0 )keeps working while new code can write.size( Length::vmin( 4.0 ).clamp( 16.0, 32.0 ) )for a typeface that scales with the screen. See Responsive design below.
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
clampbounds 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::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::fluidand the rawvmin/vmax/vw/vh/orientunits. 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::dpplusset_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 inthemes/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. Seethemes/default/cursors/README.md(what the set is and how it is used) andthemes/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§
pub use theme::Palette;pub use theme::ThemeMode;pub use theme::ThemePreference;pub use theme::ThemeError;pub use theme::ThemeDocument;pub use theme::Mode;pub use theme::SlotStore;pub use theme::WallpaperSpec;pub use theme::WallpaperFit;pub use theme::LauncherSpec;pub use theme::WindowControlsSpec;pub use theme::active_document;pub use theme::active_mode;pub use theme::active_theme_id;pub use theme::is_fallback_active;pub use theme::set_active_document;pub use theme::set_active_mode;pub use theme::tint_symbolic;pub use theme::color as theme_color;pub use theme::color_or as theme_color_or;pub use theme::paint as theme_paint;pub use theme::paint as theme_paint;pub use theme::shadows as theme_shadows;pub use theme::surface as theme_surface;pub use theme::surface as theme_surface;pub use theme::text_style as theme_text_style;pub use theme::text_style as theme_text_style;pub use theme::resolve_surface as theme_resolve_surface;pub use theme::palette as theme_palette;pub use theme::palette as theme_palette;pub use theme::window_controls as theme_window_controls;pub use theme::wallpaper as theme_wallpaper;pub use theme::lockscreen as theme_lockscreen;pub use theme::app_icon as theme_app_icon;pub use theme::app_default_icon as theme_app_default_icon;pub use theme::launcher_icon as theme_launcher_icon;pub use theme::logo as theme_logo;pub use theme::logo_square as theme_logo_square;pub use theme::logo_horizontal as theme_logo_horizontal;pub use theme::branding_asset as theme_branding_asset;pub use theme::branding_raster as theme_branding_raster;pub use theme::branding_image as theme_branding_image;pub use theme::decode_svg_bytes;pub use theme::icon_path as theme_icon_path;pub use theme::icon_rgba as theme_icon_rgba;pub use gles_render::BorrowedGlesTexture;pub use gles_render::GlesVersion;pub use wallpaper::WallpaperBundle;pub use wallpaper::ImageData;pub use chassis::set_default_theme;pub use chassis::theme_logo_rgba;pub use chassis::theme_icon_tinted;pub use chassis::branding_bundle_or_solid;pub use chassis::wallpaper_bundle_or_solid;pub use chassis::backdrop;pub use types::Color;pub use types::Corners;pub use types::CursorShape;pub use types::Length;pub use types::LengthBase;pub use types::PathCmd;pub use types::Point;pub use types::Rect;pub use types::Size;pub use types::WidgetId;pub use types::WidgetScaling;pub use types::FLUID_MIN;pub use types::FLUID_MAX;pub use types::fluid_reference;pub use types::set_fluid_reference;pub use types::density;pub use types::set_density;pub use types::widget_scaling;pub use types::set_widget_scaling;
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
themeandWallpaperBundle— 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
Elementtrees. - 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_editingprivate 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
Elementtree. - 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.
monthis 1–12,dayis 1–31 (further constrained bydays_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.
- 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
OverlayIdis returned fromApp::overlayson 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.
- 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
SpannedcarryingURLSpan/ClickableSpan. EachLinkSpanpairs a byte range with aMsgemitted 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.
- Stable identifier for a subsurface, used to diff the list returned by
App::subsurfacesbetween frames the same wayOverlayIddiffs 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
ElementwithSelf::build(or by.into()-ing it where anElementis expected). - A wall-clock time, no date / no timezone.
houris 0–23 (24-hour representation regardless ofTimePicker::twelve_hourdisplay mode),minuteandsecondare 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
Externalwidget can pull pixels from. - Horizontal alignment of a child within a
Stackrect. - Which surfaces a given
App::updatemutation can affect. - Layer-shell layer position.
- Reasons
crate::try_run(and thereforecrate::run) can fail to bring up the event loop. - Which axes a
Scrollviewport 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_rectformula for aSlider(horizontal) orcrate::widget::vslider::VSlider(vertical). - Which surface a
SubsurfaceSpecis composited as a child of.Main(the default position) parents to the app’s main surface;Overlay(id)parents to one of theApp::overlayssurfaces, so a slide can ride above app windows the way an overlay panel does. AnOverlayparent that is absent or not yet configured is skipped for that frame. - One of the surfaces an
Appcan target with an invalidation. Used insideInvalidationScope::Onlyto name the affected surfaces. - Horizontal alignment of text within its layout rect.
- Wayland
ext-foreign-toplevel-list-v1event delivered to apps viaApp::on_toplevel_event.Openedfires after the compositor commits the firstdonefor a new handle (soapp_idis the value in effect at that point — the protocol allows the compositor to re-commit later, but most don’t).Closedfires when the compositor sendsclosedand the runtime is about to destroy the handle proxy. Both carry the sameid, 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
Stackrect. - Semantic role for a window-decoration button.
Traits§
- Trait that application types must implement to integrate with ltk.
Functions§
- Add
deltamonths to(year, month)with wraparound. Negative deltas walk backwards; year crosses are handled. - Create a
Checkboxin the given state. - Create a
ColorPickerstarting from the given colour. - Format a
Coloras#RRGGBB(or#RRGGBBAAwhenwith_alphais true and the colour is not fully opaque). Bytes are clamped to0..=255. - Create an empty column layout.
- Create a
DatePickerwith 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
monthofyear(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.
truewhenyearis a leap year in the proleptic Gregorian calendar.truewhen 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
ListItemwith the given primary label. - Measure a single line of
textatsizepx with the default UI font (and the system fallback chain), returning(width, line_height)in pixels. For laying text out without a livecrate::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 aColor. ReturnsNonefor malformed input. - Create a
ProgressBarat the given fraction (clamped to[0.0, 1.0]). - Create a
Radiooption 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; chainScroll::horizontalorScroll::bothto switch axes. - Create a default
Separator(theme divider colour, 1 px thickness). - Create a
Sliderwith the given value (clamped to[0.0, 1.0]). - Create a flexible spacer with weight 1.
- Create a
Spinnerwith default size and theme colour. - Create an empty
Stack. - Create a
TabBarfrom any iterable of label-likes. - Create a
TimePickerwith the given current time. - Create a
Toastwith the given message. - Create a
Togglein the given state. - Create a
Tooltipanchored to the widget tagged withanchor_id. - Run the application, returning a typed error on init failure.
- Create a clipped viewport wrapping
child. - Create a
VSliderat the given value (clamped to[0.0, 1.0]). - Build an
Externalwidget 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.