Content drawn inside a scroll rendered smaller than identical content outside it: the scroll viewport draws its child into a sub-canvas sized to the viewport rect, and Canvas::viewport_layout / viewport_logical — the viewports that geom_px and font_px resolve fluid Lengths against — returned the canvas's own size. Inside a 312 px sub-canvas on a 360 px surface every fluid geometry (icon sizes, row heights, paddings) and font size resolved against 312 instead of 360, shrinking the scroll content by the width ratio, ~13 % in a phone-sized window with 24 px margins. First observed in Eydos Settings' Wi-Fi list, where the connected network row sits outside the scroll and the rest inside: the same full-fan signal icon measured 21x18 px outside and 18x16 px inside. Add a layout_viewport field to SoftwareCanvas and GlesCanvas, None on root canvases and set by sub_canvas to the parent's effective layout viewport, so nested sub-canvases keep propagating the root surface size. viewport_layout and viewport_logical consult the inherited value before falling back to the canvas size. Root canvases are untouched, and the GLES clip layers, which reuse sub_canvas, get the same correction.
7.5 KiB
Changelog
All notable changes to ltk are documented here. The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]
Added
-
ListItem::trailing_icon( rgba, w, h )— right-aligned icon slot (disclosure arrow) drawn atTRAILING_ICON_SIZE(21 px) and vertically centered, alongside the existing leadingicon. Coexists withtrailingtext, which shifts to the icon's left. Symbolic icons should be pre-tinted by the caller (tint_symbolic), matching the leading-icon contract. -
ListItem::pad_h( impl Into<Length> )— per-item override of the horizontal inset between the row edge and its content; without it the theme default (16 px) applies. -
Responsive sizing system with two selectable modes via
WidgetScaling(Fluid/Physical;set_widget_scaling/widget_scaling, defaultFluid). NewLengthconstructors —orient( portrait, landscape )(a percentage of the width in portrait, of the height in landscape),fluid( px )(surface-proportional, calibrated againstset_fluid_referenceand bounded byFLUID_MIN/FLUID_MAX),dp( px )(constant physical size scaled byset_density/density), andwidget( px )(picks fluid or dp per the active mode).Canvas::geom_px(geometry, physical layout space) andCanvas::font_px(font, bridging the logical / physical split per mode) give widgets and apps one resolution path. -
Button::font_size/height/widthandTextEdit::heightbuilders, allimpl Into<Length>, so control boxes scale with the surface.Text::line_height( mult )opens the gap between wrapped lines.Separator::pad_v(withLength::px( 0.0 )for a flush divider). -
Performance guardrails: opt-in diagnostics via
LTK_PERF_WARN=1(stuck animation, sustained software-render animation, lowpoll_interval) and a ~30 Hz software-animation cap overridable withApp::cap_software_animation. -
test-supportCargo feature gates thetest_supportmodule so third-party builds never see it (ltk's ownmake testenables it).
Changed
OverlaySpec::sizeis now( Length, Length )(was( u32, u32 )), resolved against the main surface when the overlay is materialized; wrap existing sizes inLength::px( … )for the old fixed behaviour.TextEdit::font_sizeandSeparator::thickness/pad_vnow takeimpl Into<Length>(weref32), resolved like the button label (font space) / geometry space; thef32sentinels are gone (f32call sites still compile viaInto<Length>).- Renamed
set_design_reference/design_reference→set_fluid_reference/fluid_reference.Length::dpchanged meaning — it used to be a surface-proportional value, and that behaviour now lives onLength::fluid;dpis the constant-physical-size unit. - Widget struct fields are now
pub( crate )(configured through builders), except the value / state types apps read or construct (Time,Date,ComboState).
Fixed
- Content inside a
scrollno longer renders smaller than the rest of the surface. Scroll viewports draw their child into a sub-canvas sized to the viewport rect, and fluidLengthresolution (Canvas::geom_px/font_px— icon sizes, row heights, paddings, font sizes) resolved against that smaller canvas instead of the surface, shrinking everything inside any scroll by the width ratio (~13 % in a 360 px window with 24 px margins). Sub-canvases now inherit the root canvas's layout viewport (viewport_layout/viewport_logical), propagated through nested sub-canvases, so geometry resolves identically inside and outside offscreen content.
0.2.0 — 2026-06-25
This release adds the primitives an embedder needs to drive ltk as the render backend for a retained, externally-owned widget tree (for example projecting an Android view hierarchy onto an ltk surface). Each is kept general rather than tied to one consumer.
Added
Canvas::set_clip_path— anti-aliased clipping to an arbitrary vector path (&[PathCmd]) on both backends. The software backend installs a tiny-skia coverage mask; the GLES backend captures the clipped draws into an offscreen layer and composites them back through an anti-aliased coverage mask. Complements the existing rect clip (set_clip_rects) for shaped clips such as a circular avatar, a rounded card or aVectorDrawablemask. Seeexamples/clip_path.rs.Canvas::fill_path/Canvas::stroke_pathover a newPathCmdcommand list (MoveTo/LineTo/QuadTo/CubicTo/Close, in surface coordinates) — renders an arbitrary vector path, aPath, aVectorDrawableor a Lottie frame. The software backend rasterises directly with tiny-skia; the GLES backend rasterises into a tiny-skia pixmap and uploads it, so both backends share the same path rasteriser and stay at visual parity.Canvas::read_rgba_pixels— reads any canvas into tightly packed straight-alpha RGBA8 (top-left row first), on both the GLES and software backends (the software path un-premultiplies its pixmap).Canvas::is_softwarelets a caller branch on the backend (e.g. honour a real path clip on software but only a bounding rect on GLES).measure_text( text, size )(re-exported at the crate root) — measures one line with the default UI font and the system fallback chain, returning(width, line_height)in pixels without a liveCanvas, for an embedder's measure pass that must match the renderer's metrics. Backed by a process-wide cached primary-font handle.Stack::push_placed— appends a child at an exact rect, bypassing alignment and intrinsic sizing, so a view tree whose geometry is computed elsewhere can be projected onto a Stack in paint order.Stack::push_placed_clippedadditionally clips the child's subtree to a rect (Android'sclipChildren): overflowing content is not painted.ExternalSource::Cpuand theExternal::cpuconstructor — an immediate-mode CPU drawing closure invoked once per frame with the canvas and the widget's laid-out rect, working on both backends. Hosts a customView.onDrawstraight onto the ltk canvas without a GL texture round-trip, unlike the existingTexturesource which only renders on GLES.RichTextwidget — wrapped paragraph text carrying aMsgper clickable link range; the layout pass emits one hit rect per link line so taps land on the link rather than the whole paragraph. The ltk side of an AndroidSpannedcarryingURLSpan/ClickableSpan.
Fixed
- GLES clip-layer composite no longer renders path-clipped content vertically flipped: the offscreen layer was sampled at the inverted screen Y. The software backend was unaffected.
- Gesture: the horizontal pager is driven only once the swipe axis locks horizontal. Previously the pre-lock lateral drift of a vertical gesture could arm the consumer's pager without a matching release event, which could freeze the surface until an unrelated gesture reset it.
- Software/GLES parity: the software backend now snaps glyph pen positions and image destinations to integer pixels (rounding to nearest), matching the GLES backend. Previously it truncated, so text and 1:1 images could land up to half a pixel off between backends and sample ~1 px softer.
Changed
- Default theme launcher SVGs replaced with renderer-compatible versions.
0.1.0 — 2026-03-10
- Initial release.