render, types, ci: resolution-time dp with per-canvas density, bounded GLES image cache, clippy gate, backend capability matrix
Some checks failed
CI / build + test (push) Has been cancelled
CI / cargo audit (push) Has been cancelled

Length::dp no longer collapses to absolute pixels at construction: the design value travels in a new LengthBase::Dp variant and the density multiplication happens when the length is resolved. Previously dp( n ) baked in whatever density() returned at view-build time, so correctness across output changes depended on the view being rebuilt after set_density and in that order; now a density change is picked up by the very next paint with no reconstruction. Length::resolve keeps its signature (process density), and the new Length::resolve_with_density takes an explicit factor. dp becomes const in the bargain.
Density also becomes overridable per canvas, the first step towards surface-local responsive state. SoftwareCanvas and GlesCanvas carry a density: Option<f32> analogous to the layout_viewport introduced for sub-canvas fluid resolution: None means "use the process global", Canvas::set_density pins a local factor, and sub-canvases inherit it. All canvas-routed resolution honours it — geom_px / font_px for stock-widget design pixels, and the new Canvas::resolve_geom / resolve_font for explicit Length values, which every widget now uses in place of the raw l.resolve( canvas.viewport_layout(), EM ) pattern (row, column, wrap_grid, spacer, container, separator, button, text, rich_text, text_edit, list_item, vslider, image, and the container draw path). Overlay sizing keeps resolving against the main surface with the global density, which is what it describes. New tests cover explicit-density resolution, resolution-time application, the local-over-global override and sub-canvas inheritance.
The GLES image texture cache is now bounded. It was content-keyed but unbounded and never evicted, so a stream of distinct buffers — a photo carousel, video thumbnails — grew GPU memory for the lifetime of the canvas. The cache now tracks an estimated byte total (RGBA8, w × h × 4) against a 32 MiB budget and evicts least-recently-drawn textures on insert; the most recent entry is never evicted, so a single texture larger than the whole budget still draws and simply owns the cache until replaced. Drop-time cleanup is unchanged: drain deletes whatever the map holds.
CI gains a Clippy step (workspace, all targets, test-support, -D warnings) sharing the build cache of the test job, with make clippy mirroring the invocation locally and CONTRIBUTING listing it. Run make clippy locally before pushing the first time — the gate has not seen the tree yet and pre-existing lints will fail CI until addressed.
docs/backends.md formalises the software/GLES capability matrix that was previously scattered across per-method rustdoc: parity set (fills, strokes, text, images, paths, path clips), graceful degradations on software (flat-fill gradients, no shadows, no backdrop blur, hard bottom edge), GPU-only features (external textures), the shared Oklab-fallback limitation, and the cross-backend blit panic. Linked from README, onboarding and architecture's known-gaps list, which now states the parity gaps explicitly. The dp/density prose in architecture.md, lib.rs and the Length rustdoc is updated for resolution-time semantics and the per-canvas override.
This commit is contained in:
2026-08-01 10:17:00 +02:00
parent a7f953ca42
commit 806dee5167
29 changed files with 323 additions and 80 deletions

View File

@@ -122,19 +122,19 @@ impl<Msg: Clone> Column<Msg>
#[ inline ]
fn resolved_spacing( &self, canvas: &Canvas ) -> f32
{
self.spacing.resolve( canvas.viewport_layout(), Length::EM_BASE_DEFAULT )
canvas.resolve_geom( self.spacing )
}
#[ inline ]
fn resolved_padding( &self, canvas: &Canvas ) -> f32
{
self.padding.resolve( canvas.viewport_layout(), Length::EM_BASE_DEFAULT )
canvas.resolve_geom( self.padding )
}
#[ inline ]
fn resolved_max_width( &self, canvas: &Canvas ) -> Option<f32>
{
self.max_width.map( |l| l.resolve( canvas.viewport_layout(), Length::EM_BASE_DEFAULT ) )
self.max_width.map( |l| canvas.resolve_geom( l ) )
}
/// Report the intrinsic content width as preferred width instead of filling

View File

@@ -92,13 +92,13 @@ impl<Msg: Clone> Row<Msg>
#[ inline ]
fn resolved_spacing( &self, canvas: &Canvas ) -> f32
{
self.spacing.resolve( canvas.viewport_layout(), Length::EM_BASE_DEFAULT )
canvas.resolve_geom( self.spacing )
}
#[ inline ]
fn resolved_padding( &self, canvas: &Canvas ) -> f32
{
self.padding.resolve( canvas.viewport_layout(), Length::EM_BASE_DEFAULT )
canvas.resolve_geom( self.padding )
}
/// Push the content block to the right edge of the available width.

View File

@@ -115,11 +115,9 @@ impl Spacer
/// weighted by `weight`.
pub fn preferred_size( &self, canvas: &Canvas ) -> ( f32, f32 )
{
let vp = canvas.viewport_layout();
let em = Length::EM_BASE_DEFAULT;
(
self.fixed_width .map( |l| l.resolve( vp, em ) ).unwrap_or( 0.0 ),
self.fixed_height.map( |l| l.resolve( vp, em ) ).unwrap_or( 0.0 ),
self.fixed_width .map( |l| canvas.resolve_geom( l ) ).unwrap_or( 0.0 ),
self.fixed_height.map( |l| canvas.resolve_geom( l ) ).unwrap_or( 0.0 ),
)
}
@@ -128,12 +126,12 @@ impl Spacer
/// layout only needs the main-axis size for one orientation.
pub fn resolved_height( &self, canvas: &Canvas ) -> Option<f32>
{
self.fixed_height.map( |l| l.resolve( canvas.viewport_layout(), Length::EM_BASE_DEFAULT ) )
self.fixed_height.map( |l| canvas.resolve_geom( l ) )
}
pub fn resolved_width( &self, canvas: &Canvas ) -> Option<f32>
{
self.fixed_width.map( |l| l.resolve( canvas.viewport_layout(), Length::EM_BASE_DEFAULT ) )
self.fixed_width.map( |l| canvas.resolve_geom( l ) )
}
/// No-op — spacers are invisible.

View File

@@ -116,7 +116,7 @@ impl<Msg: Clone> WrapGrid<Msg>
{
Some( m ) =>
{
let m = m.resolve( canvas.viewport_layout(), Length::EM_BASE_DEFAULT ).max( 1.0 );
let m = canvas.resolve_geom( m ).max( 1.0 );
let cols = ( ( ( inner_w + sx ) / ( m + sx ) ).floor() as usize ).max( 1 );
match self.max_columns
{
@@ -130,12 +130,10 @@ impl<Msg: Clone> WrapGrid<Msg>
fn resolved( &self, canvas: &Canvas ) -> ( f32, f32, f32 )
{
let vp = canvas.viewport_layout();
let em = Length::EM_BASE_DEFAULT;
(
self.spacing_x.resolve( vp, em ),
self.spacing_y.resolve( vp, em ),
self.padding.resolve( vp, em ),
canvas.resolve_geom( self.spacing_x ),
canvas.resolve_geom( self.spacing_y ),
canvas.resolve_geom( self.padding ),
)
}