render: sub-canvases inherit the root layout viewport for fluid resolution
Some checks failed
CI / build + test (push) Has been cancelled
CI / cargo audit (push) Has been cancelled

Content drawn inside a scroll rendered smaller than identical content outside it: the scroll viewport draws its child into a sub-canvas sized to the viewport rect, and Canvas::viewport_layout / viewport_logical — the viewports that geom_px and font_px resolve fluid Lengths against — returned the canvas's own size. Inside a 312 px sub-canvas on a 360 px surface every fluid geometry (icon sizes, row heights, paddings) and font size resolved against 312 instead of 360, shrinking the scroll content by the width ratio, ~13 % in a phone-sized window with 24 px margins. First observed in Eydos Settings' Wi-Fi list, where the connected network row sits outside the scroll and the rest inside: the same full-fan signal icon measured 21x18 px outside and 18x16 px inside.
Add a layout_viewport field to SoftwareCanvas and GlesCanvas, None on root canvases and set by sub_canvas to the parent's effective layout viewport, so nested sub-canvases keep propagating the root surface size. viewport_layout and viewport_logical consult the inherited value before falling back to the canvas size. Root canvases are untouched, and the GLES clip layers, which reuse sub_canvas, get the same correction.
This commit is contained in:
2026-07-30 15:23:52 +02:00
parent 9b99a18e26
commit 14572ebfb6
5 changed files with 44 additions and 3 deletions

View File

@@ -21,6 +21,10 @@ All notable changes to `ltk` are documented here. The format is based on [Keep a
- **Renamed** `set_design_reference` / `design_reference``set_fluid_reference` / `fluid_reference`. **`Length::dp` changed meaning** — it used to be a surface-proportional value, and that behaviour now lives on `Length::fluid`; `dp` is the constant-physical-size unit. - **Renamed** `set_design_reference` / `design_reference``set_fluid_reference` / `fluid_reference`. **`Length::dp` changed meaning** — it used to be a surface-proportional value, and that behaviour now lives on `Length::fluid`; `dp` is the constant-physical-size unit.
- **Widget struct fields are now `pub( crate )`** (configured through builders), except the value / state types apps read or construct (`Time`, `Date`, `ComboState`). - **Widget struct fields are now `pub( crate )`** (configured through builders), except the value / state types apps read or construct (`Time`, `Date`, `ComboState`).
### Fixed
- **Content inside a `scroll` no longer renders smaller than the rest of the surface.** Scroll viewports draw their child into a sub-canvas sized to the viewport rect, and fluid `Length` resolution (`Canvas::geom_px` / `font_px` — icon sizes, row heights, paddings, font sizes) resolved against that smaller canvas instead of the surface, shrinking everything inside any scroll by the width ratio (~13 % in a 360 px window with 24 px margins). Sub-canvases now inherit the root canvas's layout viewport (`viewport_layout` / `viewport_logical`), propagated through nested sub-canvases, so geometry resolves identically inside and outside offscreen content.
## [0.2.0] — 2026-06-25 ## [0.2.0] — 2026-06-25
This release adds the primitives an embedder needs to drive ltk as the render backend for a retained, externally-owned widget tree (for example projecting an Android view hierarchy onto an ltk surface). Each is kept general rather than tied to one consumer. This release adds the primitives an embedder needs to drive ltk as the render backend for a retained, externally-owned widget tree (for example projecting an Android view hierarchy onto an ltk surface). Each is kept general rather than tied to one consumer.

View File

@@ -151,6 +151,12 @@ pub struct GlesCanvas
/// falls back to [`Self::font`]. /// falls back to [`Self::font`].
pub font_registry: Option<Arc<FontRegistry>>, pub font_registry: Option<Arc<FontRegistry>>,
pub dpi_scale: f32, pub dpi_scale: f32,
/// Layout-resolution viewport inherited from the parent canvas.
/// `None` on a root canvas (resolve against own size); sub-canvases
/// carry the root surface size so fluid geometry and fonts resolve
/// the same inside offscreen content (scroll viewports, clip layers)
/// as outside it.
pub( crate ) layout_viewport: Option<( f32, f32 )>,
pub global_alpha: f32, pub global_alpha: f32,
pub width: u32, pub width: u32,
pub height: u32, pub height: u32,

View File

@@ -336,6 +336,7 @@ impl GlesCanvas
font_face, font_face,
font_registry: None, font_registry: None,
dpi_scale: 1.0, dpi_scale: 1.0,
layout_viewport: None,
global_alpha: 1.0, global_alpha: 1.0,
width, width,
height, height,
@@ -547,6 +548,8 @@ impl GlesCanvas
font_face: self.font_face, font_face: self.font_face,
font_registry: self.font_registry.as_ref().map( Arc::clone ), font_registry: self.font_registry.as_ref().map( Arc::clone ),
dpi_scale: self.dpi_scale, dpi_scale: self.dpi_scale,
layout_viewport: Some( self.layout_viewport.unwrap_or(
( self.width as f32, self.height as f32 ) ) ),
global_alpha: self.global_alpha, global_alpha: self.global_alpha,
width, width,
height, height,

View File

@@ -137,6 +137,12 @@ pub struct SoftwareCanvas
pub font_registry: Option<Arc<FontRegistry>>, pub font_registry: Option<Arc<FontRegistry>>,
/// DPI scale factor applied to font sizes. /// DPI scale factor applied to font sizes.
pub dpi_scale: f32, pub dpi_scale: f32,
/// Layout-resolution viewport inherited from the parent canvas.
/// `None` on a root canvas (resolve against own size); sub-canvases
/// carry the root surface size so fluid geometry and fonts resolve
/// the same inside offscreen content (scroll viewports, clip layers)
/// as outside it.
pub( crate ) layout_viewport: Option<( f32, f32 )>,
/// Global alpha multiplier for all drawing operations (0.0 = /// Global alpha multiplier for all drawing operations (0.0 =
/// transparent, 1.0 = opaque). /// transparent, 1.0 = opaque).
pub global_alpha: f32, pub global_alpha: f32,
@@ -216,13 +222,21 @@ impl Canvas
/// ``` /// ```
pub fn viewport_logical( &self ) -> ( f32, f32 ) pub fn viewport_logical( &self ) -> ( f32, f32 )
{ {
let ( pw, ph ) = self.size(); let ( pw, ph ) = match self.layout_viewport()
{
Some( ( w, h ) ) => ( w, h ),
None =>
{
let ( w, h ) = self.size();
( w as f32, h as f32 )
}
};
let scale = self.dpi_scale(); let scale = self.dpi_scale();
if scale > 0.0 if scale > 0.0
{ {
( pw as f32 / scale, ph as f32 / scale ) ( pw / scale, ph / scale )
} else { } else {
( pw as f32, ph as f32 ) ( pw, ph )
} }
} }
@@ -235,10 +249,21 @@ impl Canvas
/// by `dpi_scale` at raster time, so they must NOT use this. /// by `dpi_scale` at raster time, so they must NOT use this.
pub fn viewport_layout( &self ) -> ( f32, f32 ) pub fn viewport_layout( &self ) -> ( f32, f32 )
{ {
if let Some( v ) = self.layout_viewport() { return v; }
let ( pw, ph ) = self.size(); let ( pw, ph ) = self.size();
( pw as f32, ph as f32 ) ( pw as f32, ph as f32 )
} }
/// Inherited layout viewport, when this is a sub-canvas.
fn layout_viewport( &self ) -> Option<( f32, f32 )>
{
match self
{
Canvas::Software( c ) => c.layout_viewport,
Canvas::Gles( c ) => c.layout_viewport,
}
}
/// Resolve a stock-widget **geometry** design pixel (height, padding, /// Resolve a stock-widget **geometry** design pixel (height, padding,
/// box size, gap…) through the process-wide [`crate::WidgetScaling`] /// box size, gap…) through the process-wide [`crate::WidgetScaling`]
/// mode, into a concrete physical-pixel value for the layout tree. /// mode, into a concrete physical-pixel value for the layout tree.

View File

@@ -36,6 +36,7 @@ impl SoftwareCanvas
font_face: handle.face, font_face: handle.face,
font_registry: None, font_registry: None,
dpi_scale: 1.0, dpi_scale: 1.0,
layout_viewport: None,
global_alpha: 1.0, global_alpha: 1.0,
glyph_cache: HashMap::new(), glyph_cache: HashMap::new(),
clip_mask: None, clip_mask: None,
@@ -54,6 +55,8 @@ impl SoftwareCanvas
font_face: self.font_face, font_face: self.font_face,
font_registry: self.font_registry.as_ref().map( Arc::clone ), font_registry: self.font_registry.as_ref().map( Arc::clone ),
dpi_scale: self.dpi_scale, dpi_scale: self.dpi_scale,
layout_viewport: Some( self.layout_viewport.unwrap_or(
( self.pixmap.width() as f32, self.pixmap.height() as f32 ) ) ),
global_alpha: self.global_alpha, global_alpha: self.global_alpha,
glyph_cache: HashMap::new(), glyph_cache: HashMap::new(),
clip_mask: None, clip_mask: None,