ltk/widget/viewport/
mod.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
// SPDX-License-Identifier: LGPL-2.1-only
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>

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<Msg: Clone>
{
	/// The child element to render inside the viewport.
	pub child: Box<Element<Msg>>,
	/// 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<f32>,
	/// Optional fixed height in logical pixels. When omitted the viewport
	/// reports the child's natural height.
	pub height: Option<f32>,
	/// 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<Msg: Clone> Viewport<Msg>
{
	pub fn new( child: impl Into<Element<Msg>> ) -> 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<U>( self, f: &super::MapFn<Msg, U> ) -> Viewport<U>
	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<Msg: Clone + 'static> From<Viewport<Msg>> for Element<Msg>
{
	fn from( v: Viewport<Msg> ) -> Self
	{
		Element::Viewport( v )
	}
}

/// Create a clipped viewport wrapping `child`.
pub fn viewport<Msg: Clone>( child: impl Into<Element<Msg>> ) -> Viewport<Msg>
{
	Viewport::new( child )
}

#[ cfg( test ) ]
mod tests;