// SPDX-License-Identifier: LGPL-2.1-only // Copyright (C) 2026 Liberux Labs, S. L. //! 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 Option>` 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 { //! let cached_texture: Arc>> = 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 //! { //! *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 Option + 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 ), } 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;