// SPDX-License-Identifier: LGPL-2.1-only // Copyright (C) 2026 Liberux Labs, S. L. use crate::render::Canvas; use crate::widget::Element; /// A non-scrollable clipped viewport for revealing only part of a child tree. /// /// Unlike `Scroll`, this widget does not own /// any gesture state. It simply renders its child into an off-screen canvas /// and blits the result into the allocated rect, clipping anything outside. pub struct Viewport { /// The child element to render inside the viewport. pub child: Box>, /// Optional fixed width in logical pixels. When omitted the /// viewport reports `max_width` as its preferred width — i.e. it /// stretches to fill whatever horizontal slice the parent layout /// gives it. Set explicitly when the viewport has to coexist with /// `flex` siblings (the flex would otherwise lose every pixel to /// the viewport's `max_width` claim) or whenever the inner content /// has its own intrinsic width and the viewport is just a vertical /// clip. pub width: Option, /// Optional fixed height in logical pixels. When omitted the viewport /// reports the child's natural height. pub height: Option, /// Logical pixels at the bottom edge that fade to transparent during the /// blit. Zero leaves the bottom hard-edged. Useful for slide-in panels so /// the leading edge of the animation does not knife-cut against the layer /// below it. pub fade_bottom: f32, } impl Viewport { pub fn new( child: impl Into> ) -> Self { Self { child: Box::new( child.into() ), width: None, height: None, fade_bottom: 0.0 } } /// Set a fixed viewport width in logical pixels. Mirrors /// [`Self::height`]: with an explicit value the viewport /// reports `w` as its preferred width and the child is laid /// out against `w` rather than the parent-supplied `max_width`. pub fn width( mut self, w: f32 ) -> Self { self.width = Some( w.max( 0.0 ) ); self } /// Set a fixed viewport height in logical pixels. pub fn height( mut self, h: f32 ) -> Self { self.height = Some( h.max( 0.0 ) ); self } /// Feather the bottom `px` rows of the viewport so its lower edge dissolves /// to transparent. Drawn by the GLES blit shader as a linear alpha ramp /// over the bottom band; the software backend currently renders a hard /// edge regardless. pub fn fade_bottom( mut self, px: f32 ) -> Self { self.fade_bottom = px.max( 0.0 ); self } pub fn preferred_size( &self, max_width: f32, canvas: &Canvas ) -> ( f32, f32 ) { let inner_w = self.width.unwrap_or( max_width ); let child_h = self.child.preferred_size( inner_w, canvas ).1; ( self.width.unwrap_or( max_width ), self.height.unwrap_or( child_h ) ) } /// No-op — rendering is handled by `layout_and_draw` in `draw.rs`. pub fn draw( &self ) {} pub( crate ) fn map_msg( self, f: &super::MapFn ) -> Viewport where U: Clone + 'static, Msg: 'static, { Viewport { child: Box::new( self.child.map_arc( f ) ), width: self.width, height: self.height, fade_bottom: self.fade_bottom, } } } impl From> for Element { fn from( v: Viewport ) -> Self { Element::Viewport( v ) } } /// Create a clipped viewport wrapping `child`. pub fn viewport( child: impl Into> ) -> Viewport { Viewport::new( child ) } #[ cfg( test ) ] mod tests;