#![allow(missing_docs)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] pub mod window; mod engine; mod layer; mod primitive; mod settings; mod text; #[cfg(feature = "image")] mod raster; #[cfg(feature = "svg")] mod vector; #[cfg(feature = "geometry")] pub mod geometry; pub use iced_graphics as graphics; pub use iced_graphics::core; pub use layer::Layer; pub use primitive::Primitive; pub use settings::Settings; #[cfg(feature = "geometry")] pub use geometry::Geometry; use crate::core::renderer; use crate::core::{ Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation, }; use crate::engine::Engine; use crate::graphics::Viewport; use crate::graphics::compositor; use crate::graphics::text::{Editor, Paragraph}; /// A [`tiny-skia`] graphics renderer for [`iced`]. /// /// [`tiny-skia`]: https://github.com/RazrFalcon/tiny-skia /// [`iced`]: https://github.com/iced-rs/iced #[derive(Debug)] pub struct Renderer { default_font: Font, default_text_size: Pixels, layers: layer::Stack, engine: Engine, // TODO: Shared engine } impl Renderer { pub fn new(default_font: Font, default_text_size: Pixels) -> Self { Self { default_font, default_text_size, layers: layer::Stack::new(), engine: Engine::new(), } } pub fn layers(&mut self) -> &[Layer] { self.layers.flush(); self.layers.as_slice() } pub fn draw>( &mut self, pixels: &mut tiny_skia::PixmapMut<'_>, clip_mask: &mut tiny_skia::Mask, viewport: &Viewport, damage: &[Rectangle], background_color: Color, overlay: &[T], ) { let physical_size = viewport.physical_size(); let scale_factor = viewport.scale_factor() as f32; if !overlay.is_empty() { let path = tiny_skia::PathBuilder::from_rect( tiny_skia::Rect::from_xywh( 0.0, 0.0, physical_size.width as f32, physical_size.height as f32, ) .expect("Create damage rectangle"), ); pixels.fill_path( &path, &tiny_skia::Paint { shader: tiny_skia::Shader::SolidColor(engine::into_color( Color { a: 0.1, ..background_color }, )), anti_alias: false, ..Default::default() }, tiny_skia::FillRule::default(), tiny_skia::Transform::identity(), None, ); } self.layers.flush(); for ®ion in damage { let region = region * scale_factor; let path = tiny_skia::PathBuilder::from_rect( tiny_skia::Rect::from_xywh( region.x, region.y, region.width, region.height, ) .expect("Create damage rectangle"), ); pixels.fill_path( &path, &tiny_skia::Paint { shader: tiny_skia::Shader::SolidColor(engine::into_color( background_color, )), anti_alias: false, blend_mode: tiny_skia::BlendMode::Source, ..Default::default() }, tiny_skia::FillRule::default(), tiny_skia::Transform::identity(), None, ); for layer in self.layers.iter() { let Some(clip_bounds) = region.intersection(&(layer.bounds * scale_factor)) else { continue; }; engine::adjust_clip_mask(clip_mask, clip_bounds); for (quad, background) in &layer.quads { self.engine.draw_quad( quad, background, Transformation::scale(scale_factor), pixels, clip_mask, clip_bounds, ); } for group in &layer.primitives { let Some(new_clip_bounds) = (group.clip_bounds() * scale_factor) .intersection(&clip_bounds) else { continue; }; engine::adjust_clip_mask(clip_mask, new_clip_bounds); for primitive in group.as_slice() { self.engine.draw_primitive( primitive, group.transformation() * Transformation::scale(scale_factor), pixels, clip_mask, clip_bounds, ); } engine::adjust_clip_mask(clip_mask, clip_bounds); } for image in &layer.images { self.engine.draw_image( image, Transformation::scale(scale_factor), pixels, clip_mask, clip_bounds, ); } for group in &layer.text { for text in group.as_slice() { self.engine.draw_text( text, group.transformation() * Transformation::scale(scale_factor), pixels, clip_mask, clip_bounds, ); } } } if !overlay.is_empty() { pixels.stroke_path( &path, &tiny_skia::Paint { shader: tiny_skia::Shader::SolidColor( engine::into_color(Color::from_rgb(1.0, 0.0, 0.0)), ), anti_alias: false, ..tiny_skia::Paint::default() }, &tiny_skia::Stroke { width: 1.0, ..tiny_skia::Stroke::default() }, tiny_skia::Transform::identity(), None, ); } } self.engine.trim(); } } impl core::Renderer for Renderer { fn start_layer(&mut self, bounds: Rectangle) { self.layers.push_clip(bounds); } fn end_layer(&mut self) { self.layers.pop_clip(); } fn start_transformation(&mut self, transformation: Transformation) { self.layers.push_transformation(transformation); } fn end_transformation(&mut self) { self.layers.pop_transformation(); } fn fill_quad( &mut self, quad: renderer::Quad, background: impl Into, ) { let (layer, transformation) = self.layers.current_mut(); layer.draw_quad(quad, background.into(), transformation); } fn clear(&mut self) { self.layers.clear(); } } impl core::text::Renderer for Renderer { type Font = Font; type Paragraph = Paragraph; type Editor = Editor; const ICON_FONT: Font = Font::with_name("Iced-Icons"); const CHECKMARK_ICON: char = '\u{f00c}'; const ARROW_DOWN_ICON: char = '\u{e800}'; fn default_font(&self) -> Self::Font { self.default_font } fn default_size(&self) -> Pixels { self.default_text_size } fn fill_paragraph( &mut self, text: &Self::Paragraph, position: Point, color: Color, clip_bounds: Rectangle, ) { let (layer, transformation) = self.layers.current_mut(); layer.draw_paragraph( text, position, color, clip_bounds, transformation, ); } fn fill_editor( &mut self, editor: &Self::Editor, position: Point, color: Color, clip_bounds: Rectangle, ) { let (layer, transformation) = self.layers.current_mut(); layer.draw_editor(editor, position, color, clip_bounds, transformation); } fn fill_text( &mut self, text: core::Text, position: Point, color: Color, clip_bounds: Rectangle, ) { let (layer, transformation) = self.layers.current_mut(); layer.draw_text(text, position, color, clip_bounds, transformation); } } #[cfg(feature = "geometry")] impl graphics::geometry::Renderer for Renderer { type Geometry = Geometry; type Frame = geometry::Frame; fn new_frame(&self, size: core::Size) -> Self::Frame { geometry::Frame::new(size) } fn draw_geometry(&mut self, geometry: Self::Geometry) { let (layer, transformation) = self.layers.current_mut(); match geometry { Geometry::Live { primitives, images, text, clip_bounds, } => { layer.draw_primitive_group( primitives, clip_bounds, transformation, ); for image in images { layer.draw_image(image, transformation); } layer.draw_text_group(text, clip_bounds, transformation); } Geometry::Cache(cache) => { layer.draw_primitive_cache( cache.primitives, cache.clip_bounds, transformation, ); for image in cache.images.iter() { layer.draw_image(image.clone(), transformation); } layer.draw_text_cache( cache.text, cache.clip_bounds, transformation, ); } } } } impl graphics::mesh::Renderer for Renderer { fn draw_mesh(&mut self, _mesh: graphics::Mesh) { log::warn!("iced_tiny_skia does not support drawing meshes"); } } #[cfg(feature = "image")] impl core::image::Renderer for Renderer { type Handle = core::image::Handle; fn measure_image(&self, handle: &Self::Handle) -> crate::core::Size { self.engine.raster_pipeline.dimensions(handle) } fn draw_image(&mut self, image: core::Image, bounds: Rectangle) { let (layer, transformation) = self.layers.current_mut(); layer.draw_raster(image, bounds, transformation); } } #[cfg(feature = "svg")] impl core::svg::Renderer for Renderer { fn measure_svg( &self, handle: &core::svg::Handle, ) -> crate::core::Size { self.engine.vector_pipeline.viewport_dimensions(handle) } fn draw_svg(&mut self, svg: core::Svg, bounds: Rectangle) { let (layer, transformation) = self.layers.current_mut(); layer.draw_svg(svg, bounds, transformation); } } impl compositor::Default for Renderer { type Compositor = window::Compositor; } impl renderer::Headless for Renderer { fn new(default_font: Font, default_text_size: Pixels) -> Self { Self::new(default_font, default_text_size) } fn screenshot( &mut self, size: Size, scale_factor: f32, background_color: Color, ) -> Vec { let viewport = Viewport::with_physical_size(size, f64::from(scale_factor)); window::compositor::screenshot::<&str>( self, &viewport, background_color, &[], ) } }