Add `Canvas::set_clip_path(&[PathCmd])`, clipping subsequent draws to an arbitrary vector path with an anti-aliased edge, on both the software and GLES backends. It complements the existing rect clip (`set_clip_rects`) and is what an embedder needs to render a shaped clip — a circular avatar, a rounded card, a `VectorDrawable` mask — rather than a bounding box. Kept general rather than tied to any one consumer. Software backend: rasterise the path into an anti-aliased tiny-skia coverage `Mask` (Winding fill) and install it as the active clip mask. Every software primitive already threads `clip_mask` through tiny-skia (fills, strokes, lines, paths, images, text, blit), so the path clip applies uniformly with smooth edges. `clip_bounds` reports the path's bounding box while it is active. GLES backend: a 1-bit stencil would clip exactly but leave a hard, aliased edge, so instead the clipped draws are captured into an offscreen layer and composited back through an anti-aliased coverage mask. `set_clip_path` rasterises the path coverage (tiny-skia, anti-aliased), uploads it as a mask texture, allocates a full-canvas layer FBO on first use, and redirects subsequent draws to it via `activate_target`. Ending the clip (`clear_clip` / `set_clip_rects` / a new `set_clip_path`) composites the layer back onto the canvas FBO with a new two-sampler program (`CLIP_COMPOSITE_FRAG_SRC`) that multiplies the layer colour by the mask coverage and blends it premultiplied-over. The layer attaches to the canvas's own shadow FBO, so it needs no stencil bits in the EGL config; it is freed and reallocated on resize and freed on drop, and shared programs/uniforms are copied to sub-canvases like the rest. Usage: a path clip is bracketed — `set_clip_path` then, after the clipped draws, `clear_clip` or `set_clip_rects` to flush it (on GLES this is when the layer is composited). Snapshot the prior clip with `clip_bounds` beforehand and restore it with `set_clip_rects` to compose with an outer clip without leaking state. Add an `examples/clip_path.rs` demo (rounded rect, circle, triangle — same smooth result on both backends) and software-backend unit tests covering the bounding box, the empty-path clear, and a pixel-level check that a triangular clip masks a fill to the path silhouette rather than its bounding box. The GLES layer-composite path needs a live GL context and is exercised by the example. Also fix three rustdoc intra-doc-link warnings surfaced along the way: a private-item link in `app.rs` (`scroll`) and the new GLES doc (`SoftwareCanvas::set_clip_path`) demoted to code spans, and a redundant explicit link target in `chassis.rs`.
67 lines
2.4 KiB
Rust
67 lines
2.4 KiB
Rust
//! Scaffolding shared by full-screen ambient surfaces (greeter, lock screen,
|
|
//! kiosk): theme bring-up, branding/wallpaper loading and the wallpaper-backed
|
|
//! view stack. Thin convenience over [`theme`](crate::theme) and
|
|
//! [`WallpaperBundle`] — every app that paints a
|
|
//! wallpaper behind centred content repeats this otherwise.
|
|
|
|
use std::sync::Arc;
|
|
|
|
use crate::{ Color, Element, ImageData, ThemeMode, WallpaperBundle };
|
|
|
|
/// Find, install and activate the `default` theme document in `mode`. Returns
|
|
/// the failure message instead of exiting; the caller decides how to abort.
|
|
pub fn set_default_theme( mode: ThemeMode ) -> Result<(), String>
|
|
{
|
|
let doc = crate::ThemeDocument::find( "default" ).map_err( |e| format!( "{e}" ) )?;
|
|
crate::set_active_document( doc );
|
|
crate::set_active_mode( mode );
|
|
Ok( () )
|
|
}
|
|
|
|
/// Decode the active theme's horizontal logo to RGBA, `size` px on the longer
|
|
/// edge. `None` if the theme ships no logo or the SVG fails to rasterise.
|
|
pub fn theme_logo_rgba( size: u32 ) -> Option<ImageData>
|
|
{
|
|
let path = crate::theme_logo_horizontal()?;
|
|
let bytes = std::fs::read( &path ).ok()?;
|
|
crate::decode_svg_bytes( &bytes, size )
|
|
}
|
|
|
|
/// Load a symbolic theme icon to RGBA, tinted with `tint`. `None` if missing.
|
|
pub fn theme_icon_tinted( name: &str, size: u32, tint: Color ) -> Option<ImageData>
|
|
{
|
|
let ( rgba, w, h ) = crate::theme_icon_rgba( name, size )?;
|
|
Some( ( Arc::new( crate::tint_symbolic( &rgba, tint ) ), w, h ) )
|
|
}
|
|
|
|
/// The active theme's branding image `name` (e.g. `"wallpaper"`,
|
|
/// `"lockscreen"`) as a [`WallpaperBundle`], falling back to a solid fill of
|
|
/// the palette background when the theme ships none.
|
|
pub fn branding_bundle_or_solid( name: &str ) -> WallpaperBundle
|
|
{
|
|
let path = crate::theme_branding_image( name, 0, 0 );
|
|
let bg = crate::theme_palette().bg;
|
|
WallpaperBundle::from_path_or_solid(
|
|
path.as_deref(),
|
|
( bg.r * 255.0 ) as u8,
|
|
( bg.g * 255.0 ) as u8,
|
|
( bg.b * 255.0 ) as u8,
|
|
)
|
|
}
|
|
|
|
/// Convenience for [`branding_bundle_or_solid`] with `"wallpaper"`.
|
|
pub fn wallpaper_bundle_or_solid() -> WallpaperBundle
|
|
{
|
|
branding_bundle_or_solid( "wallpaper" )
|
|
}
|
|
|
|
/// Stack `content` over `wallpaper` resolved for the given surface size.
|
|
pub fn backdrop<M: Clone>( content: Element<M>, wallpaper: &WallpaperBundle, w: u32, h: u32 ) -> Element<M>
|
|
{
|
|
let ( rgba, iw, ih ) = wallpaper.for_size( w, h );
|
|
crate::stack::<M>()
|
|
.push( crate::img_widget( rgba, iw, ih ) )
|
|
.push( content )
|
|
.into()
|
|
}
|