Files
ltk/src/widget/external/mod.rs
Pedro M. de Echanove Pasquin ce893ac776
Some checks are pending
CI / build + test (push) Waiting to run
CI / cargo audit (push) Waiting to run
responsive fluid/physical scaling, widget-API stabilization, and perf guardrails
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

162 lines
5.6 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// SPDX-License-Identifier: LGPL-2.1-only
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>
//! Widget that hosts content rendered by an external GL producer.
//!
//! Reserves layout space and, at draw time, samples a caller-provided GL
//! texture into the LTK canvas via [`Canvas::draw_external_texture`]. The
//! producer (a web engine, a video decoder, …) keeps ownership of the
//! texture and updates it on its own cadence; LTK only composites.
//!
//! # Source closure contract
//!
//! [`ExternalSource::Texture`] carries an
//! `Arc<dyn Fn(&glow::Context, Rect) -> Option<glow::Texture>>` that LTK
//! invokes once per frame, with its GLES context current. The producer:
//!
//! 1. Reads `Rect` (in physical pixels of the host surface) — useful for
//! sizing the inner viewport (e.g. resizing a `WPEToplevel` to match)
//! and for translating input coordinates by `rect.origin`.
//! 2. May allocate / update GL textures against the supplied
//! `glow::Context`.
//! 3. May bind extension-imported EGLImages onto a persistent texture.
//! 4. Returns the `glow::Texture` to sample, or `None` to paint
//! transparent (e.g. while the first frame is still being produced).
//!
//! Returning `None` for one frame and `Some` for the next is fine; LTK
//! re-invokes on every redraw.
//!
//! # Use cases
//!
//! * `ltk-webkit` hosting a `WPEView`-rendered page.
//! * Video / media playback widgets that decode into a GL texture.
//! * Any embedding that already has a GLES producer and wants its output
//! in-line with the rest of the LTK widget tree.
//!
//! # Example
//!
//! ```rust,no_run
//! # use std::sync::{ Arc, Mutex };
//! # use ltk::{ External, ExternalSource, Element };
//! # #[ derive( Clone ) ] enum Msg {}
//! # fn _ex() -> Element<Msg> {
//! let cached_texture: Arc<Mutex<Option<glow::Texture>>> = Arc::new( Mutex::new( None ) );
//! let cached = Arc::clone( &cached_texture );
//! External::new(
//! 800.0, 600.0,
//! ExternalSource::Texture( Arc::new( move | _gl, _rect | -> Option<glow::Texture>
//! {
//! *cached.lock().ok()?
//! } ) ),
//! ).into()
//! # }
//! ```
use std::sync::Arc;
use crate::render::Canvas;
use crate::types::Rect;
/// A widget that defers its pixels to an external GL texture producer.
///
/// The widget itself is non-interactive and produces no messages. Wrap it
/// in a [`crate::widget::pressable::Pressable`] (or similar) when input
/// capture is needed.
pub struct External
{
/// Reserved width in logical pixels.
pub( crate ) width: f32,
/// Reserved height in logical pixels.
pub( crate ) height: f32,
/// Source of the GL texture sampled at draw time.
pub( crate ) source: ExternalSource,
/// Opacity multiplier in `[0.0, 1.0]`.
pub( crate ) opacity: f32,
}
/// Backends an [`External`] widget can pull pixels from.
///
/// The only variant today is [`ExternalSource::Texture`], a closure
/// returning the current GL texture name. Future variants (DMA-BUF
/// imports, software pixmaps for the CPU backend, …) can be added
/// without changing call sites.
#[ derive( Clone ) ]
pub enum ExternalSource
{
/// Closure invoked once per frame with LTK's [`glow::Context`] current
/// and the widget's laid-out rect (in physical pixels of the host
/// surface). The producer can allocate textures, bind extension-
/// imported EGLImages, etc., and returns the texture name to sample.
/// Returning `None` paints transparent — useful while the producer
/// is still warming up its first frame. The rect is exposed so
/// embedders can size their inner viewport to match LTK's actual
/// allocation (e.g. resize a WPEToplevel on layout change) and
/// translate input coordinates by `rect.origin`.
Texture( Arc<dyn Fn( &glow::Context, Rect ) -> Option<glow::Texture> + Send + Sync> ),
/// Closure invoked once per frame with LTK's [`Canvas`] and the widget's
/// laid-out rect, for immediate-mode CPU drawing (no GL texture). Used to
/// host an Android `View`'s custom `onDraw` straight onto the LTK canvas.
/// Works on both the GLES and software backends.
Cpu( Arc<dyn Fn( &mut Canvas, Rect ) + Send + Sync> ),
}
impl External
{
/// Build a new external-content widget reserving `width × height`
/// logical pixels.
pub fn new( width: f32, height: f32, source: ExternalSource ) -> Self
{
Self { width, height, source, opacity: 1.0 }
}
/// Build an external widget that paints via an immediate-mode CPU closure
/// reserving `width × height` logical pixels.
pub fn cpu( width: f32, height: f32, draw: impl Fn( &mut Canvas, Rect ) + Send + Sync + 'static ) -> Self
{
Self::new( width, height, ExternalSource::Cpu( Arc::new( draw ) ) )
}
/// Override the opacity multiplier. Default: `1.0`.
pub fn opacity( mut self, opacity: f32 ) -> Self
{
self.opacity = opacity.clamp( 0.0, 1.0 );
self
}
pub fn preferred_size( &self, _max_width: f32 ) -> ( f32, f32 )
{
( self.width, self.height )
}
pub fn draw( &self, canvas: &mut Canvas, rect: Rect )
{
match &self.source
{
// Immediate CPU drawing works against any backend; the producer
// paints straight into the canvas at `rect`.
ExternalSource::Cpu( draw ) =>
{
draw( canvas, rect );
}
// External GL content only renders against the GLES backend — the
// software backend has no GL texture to sample. Skip silently.
ExternalSource::Texture( get ) =>
{
let gl = match canvas
{
Canvas::Software( _ ) => return,
Canvas::Gles( c ) => c.gl.clone(),
};
if let Some( tex ) = get( &gl, rect )
{
canvas.draw_external_texture( tex, rect, self.opacity );
}
}
}
}
}
#[ cfg( test ) ]
mod tests;