Commit Graph

7 Commits

Author SHA1 Message Date
806dee5167 render, types, ci: resolution-time dp with per-canvas density, bounded GLES image cache, clippy gate, backend capability matrix
Some checks failed
CI / build + test (push) Has been cancelled
CI / cargo audit (push) Has been cancelled
Length::dp no longer collapses to absolute pixels at construction: the design value travels in a new LengthBase::Dp variant and the density multiplication happens when the length is resolved. Previously dp( n ) baked in whatever density() returned at view-build time, so correctness across output changes depended on the view being rebuilt after set_density and in that order; now a density change is picked up by the very next paint with no reconstruction. Length::resolve keeps its signature (process density), and the new Length::resolve_with_density takes an explicit factor. dp becomes const in the bargain.
Density also becomes overridable per canvas, the first step towards surface-local responsive state. SoftwareCanvas and GlesCanvas carry a density: Option<f32> analogous to the layout_viewport introduced for sub-canvas fluid resolution: None means "use the process global", Canvas::set_density pins a local factor, and sub-canvases inherit it. All canvas-routed resolution honours it — geom_px / font_px for stock-widget design pixels, and the new Canvas::resolve_geom / resolve_font for explicit Length values, which every widget now uses in place of the raw l.resolve( canvas.viewport_layout(), EM ) pattern (row, column, wrap_grid, spacer, container, separator, button, text, rich_text, text_edit, list_item, vslider, image, and the container draw path). Overlay sizing keeps resolving against the main surface with the global density, which is what it describes. New tests cover explicit-density resolution, resolution-time application, the local-over-global override and sub-canvas inheritance.
The GLES image texture cache is now bounded. It was content-keyed but unbounded and never evicted, so a stream of distinct buffers — a photo carousel, video thumbnails — grew GPU memory for the lifetime of the canvas. The cache now tracks an estimated byte total (RGBA8, w × h × 4) against a 32 MiB budget and evicts least-recently-drawn textures on insert; the most recent entry is never evicted, so a single texture larger than the whole budget still draws and simply owns the cache until replaced. Drop-time cleanup is unchanged: drain deletes whatever the map holds.
CI gains a Clippy step (workspace, all targets, test-support, -D warnings) sharing the build cache of the test job, with make clippy mirroring the invocation locally and CONTRIBUTING listing it. Run make clippy locally before pushing the first time — the gate has not seen the tree yet and pre-existing lints will fail CI until addressed.
docs/backends.md formalises the software/GLES capability matrix that was previously scattered across per-method rustdoc: parity set (fills, strokes, text, images, paths, path clips), graceful degradations on software (flat-fill gradients, no shadows, no backdrop blur, hard bottom edge), GPU-only features (external textures), the shared Oklab-fallback limitation, and the cross-backend blit panic. Linked from README, onboarding and architecture's known-gaps list, which now states the parity gaps explicitly. The dp/density prose in architecture.md, lib.rs and the Length rustdoc is updated for resolution-time semantics and the per-canvas override.
2026-08-01 10:17:00 +02:00
1fd697aa6d docs overhaul, orientation API, fluid-sizing fixes, examples made honest
Some checks failed
CI / build + test (push) Has been cancelled
CI / cargo audit (push) Has been cancelled
Documentation pass: every claim in docs/ and the meta files was audited against the source and the drift fixed — around ninety corrections. CONTRIBUTING and the CI workflow now run cargo test with --features test-support (the gated test_support module made both the documented commands and the CI build fail to compile), make example becomes make examples, make doctest-md and the debhelper requirement of make clean are documented, and patch shape asks for a CHANGELOG entry. theming.md loses the nonexistent surface.backdrop, gains the real gradient defaults (linear-rgb, oklab), the six slot variants including typography, the ten-field palette, a truthful effects-consumer table, the ThemePreference/from_hour API and a responsive-sizing note; the stale docstrings in src/theme that fed the drift are fixed too. architecture.md's "Known gaps" section is rewritten against reality (multi-touch slots, xdg-activation, a11y live regions and SetValue/Increment/Decrement are implemented), gains a module map, subsurfaces and window-lifecycle coverage, and correct crustace/loginmanager paths. widgets.md fixes the ten factual errors (stateless spinner, toast/combo via overlays(), tooltip hover contract, row has no max_width, scroll axes, multiline text_edit, dialog panic wording) and now states the column() 16 px default padding — the recurring ambush — plus row's differing 0 default and dialog's max_width. onboarding, README and cookbook get the remaining sweep: build/test instructions, complete example lists, img_widget, clipping-parity honesty, ~30 Hz software cap, read_rgba_pixels signature, tab indentation in snippets, and rustdoc-style links that rendered literally are gone everywhere. CHANGELOG is restructured per Keep a Changelog with the missing entries (window_resizable, claims_raw_touch, Row::align_top/fill_height, caret fixes, dependency pins) and the pad_v Added/Changed contradiction resolved.
New adaptive-layout API: ltk::orientation() with the Orientation enum, backed by viewport_size()/set_viewport_size — the runtime records the main surface's physical dimensions on every configure, before App::on_resize, so view() can branch a layout on portrait vs landscape without hand-tracking resizes. The portrait rule matches Length::orient (square counts as portrait); embedders driving core::UiSurface call set_viewport_size themselves. Documented in the crate root's responsive-design section and architecture.md.
Fluid-vs-fixed sizing fixes in widgets, all the same disease — fluid content inside a fixed-pixel box. TextEdit::fixed_width takes impl Into<Length> (f32 call sites keep compiling as px) and the time picker's digit fields move to Length::fluid( 72.0 ), matching their fluid font so digits can no longer outgrow the box. Dialog::max_width takes impl Into<Length> with a Length::fluid( 480.0 ) default so the card scales with the stock buttons inside it, and the card's interior no longer stacks the column() default 16 px padding on top of CARD_PADDING — that double inset squeezed the action row until its buttons clipped on narrow windows. App::on_pointer_axis now triggers a view rebuild and repaint; previously state mutated in the hook did not paint until the next unrelated event.
Examples reworked to be honest demos: responsive's mode/density controls become stock buttons in a grid/column so they follow the modes they demonstrate instead of overflowing; dialog's openers stack vertically, and the example gains the app-level ESC handler so the ESC chain closes an open dialog first and quits second; widgets' tab strip now switches real per-tab pages; carousel gains pointer/touch drag through the horizontal-swipe hooks (crustace's pager pattern), one-tile-per-detent mouse wheel, and snap math driven by the real surface width from on_resize instead of a hardcoded 800; clip_path arranges its cells by ltk::orientation() and sizes them from the counter-axis of the flow.
2026-07-30 19:28:26 +02:00
ce893ac776 responsive fluid/physical scaling, widget-API stabilization, and perf guardrails
Some checks failed
CI / build + test (push) Has been cancelled
CI / cargo audit (push) Has been cancelled
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`.
2026-07-07 17:40:33 +02:00
fc045a9c22 ltk: ext-session-lock-v1 client surface mode, plus a read-only mode for text fields
Some checks failed
CI / build + test (push) Has been cancelled
CI / cargo audit (push) Has been cancelled
Add a third Wayland surface type to the runtime so an ltk `App` can be a screen locker, alongside the existing xdg-shell window and wlr-layer-shell surfaces. A new `ShellMode::SessionLock` makes `run()` bind `ext_session_lock_manager_v1` and request the lock at startup; the lock surface itself is created in the new `SessionLockHandler::locked` callback (one surface on the first advertised output) and replaces the `SurfaceKind::PendingLock` placeholder the main surface holds until the compositor grants the lock. The `configure` event routes through the same `on_configure` path as layer and xdg surfaces, so sizing and rendering are unchanged, and `finished` (the compositor denied or ended the lock) tears the loop down. The whole thing is additive and opt-in: the `Window` and `Layer` paths are untouched and nothing enters lock mode unless an `App` returns `ShellMode::SessionLock`, so existing apps are unaffected — the only non-additive edits are the two exhaustive `match`es on `SurfaceKind` (`wl_surface` / `try_wl_surface`), which gain arms for the two new variants.
Doing the locker as a first-class surface rather than compositing a static texture into an offscreen `UiSurface` is the whole point: the compositor gives the lock surface keyboard focus, so ltk's existing text-input, editing, focus and IME machinery works inside the lock exactly as on any other surface — cursor, click-to-focus, Tab, character input. A locker built on top of this is just a normal interactive ltk app that happens to be presented on the lock layer, with no special input plumbing on the compositor or the app side.
`App::requested_exit()` is the new way an app asks the runtime to tear the surface down and leave the loop; it is polled after every batch of `update`s. It exists because of the one hard invariant of `ext-session-lock-v1`: a locker that disconnects without sending `unlock` leaves the compositor's outputs blanked forever — that is the protocol's deliberate anti-bypass guarantee. So when `requested_exit()` returns true and the surface is a session lock, the loop calls `session_lock.unlock()` and round-trips the connection before setting `exit_requested`, lifting the lock cleanly; for a `Window` or `Layer` surface there is no lock and it simply exits. The consequence for lock apps is that they must stop calling `process::exit` from the lock path and instead flip a flag they return from `requested_exit()`.
`text_edit` gains a `read_only( bool )` builder. A read-only field still renders its box and value in the normal field style but takes no keyboard focus and accepts no input: `Element::is_focusable` and `Element::is_text_input` now return false for a read-only `TextEdit`, which keeps it out of the Tab cycle, off the keyboard-edit path, and stops the cursor from ever being drawn on it. The flag is carried through `map_msg` so it survives `Element::map`. This is for presenting a known, non-editable value in the same visual idiom as the editable fields beside it — for example the already-known user shown on a session lock, where letting that field take focus or blink a cursor would be wrong.
The `shell_mode()` doc comment and the README now list the `SessionLock` surface type and point at `requested_exit()` for the unlock. Two warnings are cleared along the way: the runtime no longer stores the `SessionLockState` after requesting the lock — it has no `Drop`, so the manager object outlives the dropped handle inside the connection and the lock lifecycle runs entirely off the returned `SessionLock`, which removes a never-read field — and a pre-existing rustdoc `private_intra_doc_links` warning in `list_item` (a public doc comment linking to the private `theme::ICON_SIZE`) is downgraded to plain code formatting.
2026-05-26 00:11:33 +02:00
f3621b72c9 Added examples in README.md 2026-05-10 15:23:22 +02:00
bbab5e238d First commit. Version 0.1.0 2026-05-10 09:58:23 +02:00
af105b7f7d Initial commit 2026-04-23 11:08:43 +02:00