Embedder primitives: placed-child clipping, offscreen RGBA readback, standalone text measurement
Three additions an embedder needs to drive ltk as the render backend for a retained, externally-owned widget tree, each kept general rather than tied to one consumer. Stack placed-child clipping. `Stack::push_placed_clipped(e, rect, clip)` places a child at an exact rect like `push_placed` but clips its subtree's drawing to `clip` (in the Stack's coordinate space) — Android's clipChildren: content that overflows, such as a scrolled list row reaching above the list or an inner card past a rounded bubble, is not painted. The Stack child tuple grows an 8th `Option<Rect>` for the clip, and `layout_and_draw` brackets a clipped child with `set_clip_rects`/restore around the recursion. The clip is shifted by the Stack's own origin to match the placed rect (which `Stack::layout` already offsets), so a Stack laid out at a non-zero origin clips in the right place rather than off by that origin. Offscreen RGBA readback. `Canvas::read_rgba_pixels(out)` reads any canvas into tightly packed straight-alpha RGBA8, top-left row first. Unlike `read_gles_rgba_pixels` it also serves the software backend, un-premultiplying its pixmap, so an offscreen software canvas (an embedder's scratch bitmap) can be read back into a straight-alpha buffer. `Canvas::is_software()` lets a caller branch on the backend — e.g. to honour a real path clip on software but only a bounding rect on GLES. Standalone text measurement. `measure_text(text, size)`, re-exported at the crate root, measures one line with the default UI font and the system fallback chain, returning `(width, line_height)` in pixels without a live `Canvas` — for an embedder's measure pass that must produce the same metrics the renderer will later use. It is backed by `system_fonts::primary_handle()`, a process-wide cached handle for the primary UI font (the same default a canvas loads, with the bundled-font fallback), which widens `render::helpers::load_default_font_bytes` to `pub(crate)`.
This commit is contained in:
@@ -52,10 +52,11 @@ pub enum VAlign
|
||||
pub struct Stack<Msg: Clone>
|
||||
{
|
||||
/// Children with their alignment, margin, and extra `(x, y)` translation
|
||||
/// applied after alignment. Drawn in order — last child is on top. The final
|
||||
/// applied after alignment. Drawn in order — last child is on top. The 7th
|
||||
/// `Option<Rect>` overrides alignment/sizing with an exact rect (see
|
||||
/// [`push_placed`](Self::push_placed)).
|
||||
pub children: Vec<( Element<Msg>, HAlign, VAlign, f32, f32, f32, Option<Rect> )>,
|
||||
/// [`push_placed`](Self::push_placed)); the 8th clips the child's draw to a
|
||||
/// rect (Android clipChildren — see [`push_placed_clipped`](Self::push_placed_clipped)).
|
||||
pub children: Vec<( Element<Msg>, HAlign, VAlign, f32, f32, f32, Option<Rect>, Option<Rect> )>,
|
||||
/// When `true`, [`preferred_size`](Self::preferred_size) reports the max of
|
||||
/// children's intrinsic widths and heights instead of `(max_width, tallest)`.
|
||||
pub fit_content: bool,
|
||||
@@ -102,7 +103,7 @@ impl<Msg: Clone> Stack<Msg>
|
||||
margin: f32,
|
||||
) -> Self
|
||||
{
|
||||
self.children.push( ( e.into(), h_align, v_align, margin, 0.0, 0.0, None ) );
|
||||
self.children.push( ( e.into(), h_align, v_align, margin, 0.0, 0.0, None, None ) );
|
||||
self
|
||||
}
|
||||
|
||||
@@ -119,7 +120,7 @@ impl<Msg: Clone> Stack<Msg>
|
||||
offset_y: f32,
|
||||
) -> Self
|
||||
{
|
||||
self.children.push( ( e.into(), h_align, v_align, 0.0, offset_x, offset_y, None ) );
|
||||
self.children.push( ( e.into(), h_align, v_align, 0.0, offset_x, offset_y, None, None ) );
|
||||
self
|
||||
}
|
||||
|
||||
@@ -133,7 +134,22 @@ impl<Msg: Clone> Stack<Msg>
|
||||
rect: Rect,
|
||||
) -> Self
|
||||
{
|
||||
self.children.push( ( e.into(), HAlign::Start, VAlign::Top, 0.0, 0.0, 0.0, Some( rect ) ) );
|
||||
self.children.push( ( e.into(), HAlign::Start, VAlign::Top, 0.0, 0.0, 0.0, Some( rect ), None ) );
|
||||
self
|
||||
}
|
||||
|
||||
/// Like [`push_placed`](Self::push_placed) but clips the child's drawing to
|
||||
/// `clip` (in the Stack's coordinate space). Mirrors Android's clipChildren:
|
||||
/// content that overflows the clip — e.g. a scrolled list row reaching above
|
||||
/// the list, or an inner card past a rounded bubble — is not painted.
|
||||
pub fn push_placed_clipped(
|
||||
mut self,
|
||||
e: impl Into<Element<Msg>>,
|
||||
rect: Rect,
|
||||
clip: Rect,
|
||||
) -> Self
|
||||
{
|
||||
self.children.push( ( e.into(), HAlign::Start, VAlign::Top, 0.0, 0.0, 0.0, Some( rect ), Some( clip ) ) );
|
||||
self
|
||||
}
|
||||
|
||||
@@ -142,7 +158,7 @@ impl<Msg: Clone> Stack<Msg>
|
||||
if self.fit_content
|
||||
{
|
||||
let content_w = self.children.iter()
|
||||
.map( |( c, _, _, _, _, _, _ )| match c
|
||||
.map( |( c, .. )| match c
|
||||
{
|
||||
Element::Spacer( s ) => s.resolved_width( canvas ).unwrap_or( 0.0 ),
|
||||
Element::Separator( _ ) => 0.0,
|
||||
@@ -157,13 +173,13 @@ impl<Msg: Clone> Stack<Msg>
|
||||
} )
|
||||
.fold( 0.0_f32, f32::max );
|
||||
let max_h = self.children.iter()
|
||||
.map( |( c, _, _, _, _, _, _ )| c.preferred_size( max_width, canvas ).1 )
|
||||
.map( |( c, .. )| c.preferred_size( max_width, canvas ).1 )
|
||||
.fold( 0.0_f32, f32::max );
|
||||
return ( content_w.min( max_width ), max_h );
|
||||
}
|
||||
|
||||
let max_h = self.children.iter()
|
||||
.map( |( c, _, _, _, _, _, _ )| c.preferred_size( max_width, canvas ).1 )
|
||||
.map( |( c, .. )| c.preferred_size( max_width, canvas ).1 )
|
||||
.fold( 0.0_f32, f32::max );
|
||||
( max_width, max_h )
|
||||
}
|
||||
@@ -171,7 +187,7 @@ impl<Msg: Clone> Stack<Msg>
|
||||
/// Return `(rect, child_index)` pairs, computing each child's rect from its alignment.
|
||||
pub fn layout( &self, rect: Rect, canvas: &Canvas ) -> Vec<(Rect, usize)>
|
||||
{
|
||||
self.children.iter().enumerate().map( |( i, ( child, h_align, v_align, margin, ox, oy, placed ) )|
|
||||
self.children.iter().enumerate().map( |( i, ( child, h_align, v_align, margin, ox, oy, placed, .. ) )|
|
||||
{
|
||||
if let Some( p ) = placed
|
||||
{
|
||||
@@ -213,8 +229,8 @@ impl<Msg: Clone> Stack<Msg>
|
||||
Stack
|
||||
{
|
||||
children: self.children.into_iter()
|
||||
.map( |( child, ha, va, margin, ox, oy, placed )|
|
||||
( child.map_arc( f ), ha, va, margin, ox, oy, placed ) )
|
||||
.map( |( child, ha, va, margin, ox, oy, placed, clip )|
|
||||
( child.map_arc( f ), ha, va, margin, ox, oy, placed, clip ) )
|
||||
.collect(),
|
||||
fit_content: self.fit_content,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user