Merge pull request #2351 from iced-rs/custom-renderer-injection

Type-Driven Renderer Fallback
This commit is contained in:
Héctor Ramón 2024-03-25 21:36:44 +01:00 committed by GitHub
commit a2a8381a49
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
56 changed files with 1994 additions and 1555 deletions

View file

@ -18,9 +18,11 @@ all-features = true
maintenance = { status = "actively-developed" }
[features]
default = ["wgpu", "fira-sans", "auto-detect-theme"]
default = ["wgpu", "tiny-skia", "fira-sans", "auto-detect-theme"]
# Enable the `wgpu` GPU-accelerated renderer backend
wgpu = ["iced_renderer/wgpu", "iced_widget/wgpu"]
# Enable the `tiny-skia` software renderer backend
tiny-skia = ["iced_renderer/tiny-skia"]
# Enables the `Image` widget
image = ["iced_widget/image", "dep:image"]
# Enables the `Svg` widget

View file

@ -95,7 +95,7 @@ impl<'a, Message, Theme, Renderer> Element<'a, Message, Theme, Renderer> {
///
/// ```no_run
/// # mod iced {
/// # pub type Element<'a, Message> = iced_core::Element<'a, Message, iced_core::Theme, iced_core::renderer::Null>;
/// # pub type Element<'a, Message> = iced_core::Element<'a, Message, iced_core::Theme, ()>;
/// #
/// # pub mod widget {
/// # pub fn row<'a, Message>(iter: impl IntoIterator<Item = super::Element<'a, Message>>) -> super::Element<'a, Message> {
@ -109,7 +109,7 @@ impl<'a, Message, Theme, Renderer> Element<'a, Message, Theme, Renderer> {
/// # pub enum Message {}
/// # pub struct Counter;
/// #
/// # pub type Element<'a, Message> = iced_core::Element<'a, Message, iced_core::Theme, iced_core::renderer::Null>;
/// # pub type Element<'a, Message> = iced_core::Element<'a, Message, iced_core::Theme, ()>;
/// #
/// # impl Counter {
/// # pub fn view(&self) -> Element<Message> {

View file

@ -186,11 +186,11 @@ pub trait Renderer: crate::Renderer {
type Handle: Clone + Hash;
/// Returns the dimensions of an image for the given [`Handle`].
fn dimensions(&self, handle: &Self::Handle) -> Size<u32>;
fn measure_image(&self, handle: &Self::Handle) -> Size<u32>;
/// Draws an image with the given [`Handle`] and inside the provided
/// `bounds`.
fn draw(
fn draw_image(
&mut self,
handle: Self::Handle,
filter_method: FilterMethod,

View file

@ -2,26 +2,47 @@
#[cfg(debug_assertions)]
mod null;
#[cfg(debug_assertions)]
pub use null::Null;
use crate::{
Background, Border, Color, Rectangle, Shadow, Size, Transformation, Vector,
};
/// A component that can be used by widgets to draw themselves on a screen.
pub trait Renderer: Sized {
pub trait Renderer {
/// Starts recording a new layer.
fn start_layer(&mut self);
/// Ends recording a new layer.
///
/// The new layer will clip its contents to the provided `bounds`.
fn end_layer(&mut self, bounds: Rectangle);
/// Draws the primitives recorded in the given closure in a new layer.
///
/// The layer will clip its contents to the provided `bounds`.
fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self));
fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) {
self.start_layer();
f(self);
self.end_layer(bounds);
}
/// Starts recording with a new [`Transformation`].
fn start_transformation(&mut self);
/// Ends recording a new layer.
///
/// The new layer will clip its contents to the provided `bounds`.
fn end_transformation(&mut self, transformation: Transformation);
/// Applies a [`Transformation`] to the primitives recorded in the given closure.
fn with_transformation(
&mut self,
transformation: Transformation,
f: impl FnOnce(&mut Self),
);
) {
self.start_transformation();
f(self);
self.end_transformation(transformation);
}
/// Applies a translation to the primitives recorded in the given closure.
fn with_translation(

View file

@ -1,5 +1,7 @@
use crate::alignment;
use crate::image;
use crate::renderer::{self, Renderer};
use crate::svg;
use crate::text::{self, Text};
use crate::{
Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation,
@ -7,28 +9,14 @@ use crate::{
use std::borrow::Cow;
/// A renderer that does nothing.
///
/// It can be useful if you are writing tests!
#[derive(Debug, Clone, Copy, Default)]
pub struct Null;
impl Renderer for () {
fn start_layer(&mut self) {}
impl Null {
/// Creates a new [`Null`] renderer.
pub fn new() -> Null {
Null
}
}
fn end_layer(&mut self, _bounds: Rectangle) {}
impl Renderer for Null {
fn with_layer(&mut self, _bounds: Rectangle, _f: impl FnOnce(&mut Self)) {}
fn start_transformation(&mut self) {}
fn with_transformation(
&mut self,
_transformation: Transformation,
_f: impl FnOnce(&mut Self),
) {
}
fn end_transformation(&mut self, _transformation: Transformation) {}
fn clear(&mut self) {}
@ -40,7 +28,7 @@ impl Renderer for Null {
}
}
impl text::Renderer for Null {
impl text::Renderer for () {
type Font = Font;
type Paragraph = ();
type Editor = ();
@ -174,3 +162,33 @@ impl text::Editor for () {
) {
}
}
impl image::Renderer for () {
type Handle = ();
fn measure_image(&self, _handle: &Self::Handle) -> Size<u32> {
Size::default()
}
fn draw_image(
&mut self,
_handle: Self::Handle,
_filter_method: image::FilterMethod,
_bounds: Rectangle,
) {
}
}
impl svg::Renderer for () {
fn measure_svg(&self, _handle: &svg::Handle) -> Size<u32> {
Size::default()
}
fn draw_svg(
&mut self,
_handle: svg::Handle,
_color: Option<Color>,
_bounds: Rectangle,
) {
}
}

View file

@ -1,7 +1,7 @@
use crate::Vector;
/// An amount of space in 2 dimensions.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Size<T = f32> {
/// The width.
pub width: T,

View file

@ -91,8 +91,13 @@ impl std::fmt::Debug for Data {
/// [renderer]: crate::renderer
pub trait Renderer: crate::Renderer {
/// Returns the default dimensions of an SVG for the given [`Handle`].
fn dimensions(&self, handle: &Handle) -> Size<u32>;
fn measure_svg(&self, handle: &Handle) -> Size<u32>;
/// Draws an SVG with the given [`Handle`], an optional [`Color`] filter, and inside the provided `bounds`.
fn draw(&mut self, handle: Handle, color: Option<Color>, bounds: Rectangle);
fn draw_svg(
&mut self,
handle: Handle,
color: Option<Color>,
bounds: Rectangle,
);
}

View file

@ -143,23 +143,18 @@ mod bezier {
bounds: Rectangle,
cursor: mouse::Cursor,
) -> Vec<Geometry> {
let content = self.state.cache.draw(
renderer,
bounds.size(),
|frame: &mut Frame| {
let content =
self.state.cache.draw(renderer, bounds.size(), |frame| {
Curve::draw_all(self.curves, frame);
frame.stroke(
&Path::rectangle(Point::ORIGIN, frame.size()),
Stroke::default().with_width(2.0),
);
},
);
});
if let Some(pending) = state {
let pending_curve = pending.draw(renderer, bounds, cursor);
vec![content, pending_curve]
vec![content, pending.draw(renderer, bounds, cursor)]
} else {
vec![content]
}

View file

@ -602,9 +602,7 @@ mod grid {
frame.into_geometry()
};
if self.scaling < 0.2 || !self.show_lines {
vec![life, overlay]
} else {
if self.scaling >= 0.2 && self.show_lines {
let grid =
self.grid_cache.draw(renderer, bounds.size(), |frame| {
frame.translate(center);
@ -641,6 +639,8 @@ mod grid {
});
vec![life, grid, overlay]
} else {
vec![life, overlay]
}
}

View file

@ -44,7 +44,9 @@ mod rainbow {
cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
use iced::advanced::graphics::mesh::{self, Mesh, SolidVertex2D};
use iced::advanced::graphics::mesh::{
self, Mesh, Renderer as _, SolidVertex2D,
};
use iced::advanced::Renderer as _;
let bounds = layout.bounds();

View file

@ -358,7 +358,7 @@ where
|renderer| {
use iced::advanced::graphics::geometry::Renderer as _;
renderer.draw(vec![geometry]);
renderer.draw_geometry(geometry);
},
);
}

View file

@ -1,6 +1,6 @@
use iced::mouse;
use iced::widget::canvas::event::{self, Event};
use iced::widget::canvas::{self, Canvas};
use iced::widget::canvas::{self, Canvas, Geometry};
use iced::widget::{column, row, slider, text};
use iced::{Color, Length, Point, Rectangle, Renderer, Size, Theme};
@ -111,7 +111,7 @@ impl canvas::Program<Message> for SierpinskiGraph {
_theme: &Theme,
bounds: Rectangle,
_cursor: mouse::Cursor,
) -> Vec<canvas::Geometry> {
) -> Vec<Geometry> {
let geom = self.cache.draw(renderer, bounds.size(), |frame| {
frame.stroke(
&canvas::Path::rectangle(Point::ORIGIN, frame.size()),

View file

@ -10,7 +10,7 @@ use iced::mouse;
use iced::widget::canvas;
use iced::widget::canvas::gradient;
use iced::widget::canvas::stroke::{self, Stroke};
use iced::widget::canvas::Path;
use iced::widget::canvas::{Geometry, Path};
use iced::window;
use iced::{
Color, Element, Length, Point, Rectangle, Renderer, Size, Subscription,
@ -130,7 +130,7 @@ impl<Message> canvas::Program<Message> for State {
_theme: &Theme,
bounds: Rectangle,
_cursor: mouse::Cursor,
) -> Vec<canvas::Geometry> {
) -> Vec<Geometry> {
use std::f32::consts::PI;
let background =

View file

@ -2,15 +2,19 @@
use crate::core::image;
use crate::core::svg;
use crate::core::Size;
use crate::{Compositor, Mesh, Renderer};
use std::borrow::Cow;
/// The graphics backend of a [`Renderer`].
///
/// [`Renderer`]: crate::Renderer
pub trait Backend {
pub trait Backend: Sized {
/// The custom kind of primitives this [`Backend`] supports.
type Primitive;
type Primitive: TryFrom<Mesh, Error = &'static str>;
/// The default compositor of this [`Backend`].
type Compositor: Compositor<Renderer = Renderer<Self>>;
}
/// A graphics backend that supports text rendering.

42
graphics/src/cached.rs Normal file
View file

@ -0,0 +1,42 @@
use crate::Primitive;
use std::sync::Arc;
/// A piece of data that can be cached.
pub trait Cached: Sized {
/// The type of cache produced.
type Cache;
/// Loads the [`Cache`] into a proper instance.
///
/// [`Cache`]: Self::Cache
fn load(cache: &Self::Cache) -> Self;
/// Caches this value, producing its corresponding [`Cache`].
///
/// [`Cache`]: Self::Cache
fn cache(self) -> Self::Cache;
}
impl<T> Cached for Primitive<T> {
type Cache = Arc<Self>;
fn load(cache: &Arc<Self>) -> Self {
Self::Cache {
content: cache.clone(),
}
}
fn cache(self) -> Arc<Self> {
Arc::new(self)
}
}
#[cfg(debug_assertions)]
impl Cached for () {
type Cache = ();
fn load(_cache: &Self::Cache) -> Self {}
fn cache(self) -> Self::Cache {}
}

View file

@ -1,9 +1,8 @@
//! A compositor is responsible for initializing a renderer and managing window
//! surfaces.
use crate::{Error, Viewport};
use crate::core::Color;
use crate::futures::{MaybeSend, MaybeSync};
use crate::{Error, Settings, Viewport};
use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
use std::future::Future;
@ -11,19 +10,28 @@ use thiserror::Error;
/// A graphics compositor that can draw to windows.
pub trait Compositor: Sized {
/// The settings of the backend.
type Settings: Default;
/// The iced renderer of the backend.
type Renderer: iced_core::Renderer;
type Renderer;
/// The surface of the backend.
type Surface;
/// Creates a new [`Compositor`].
fn new<W: Window + Clone>(
settings: Self::Settings,
settings: Settings,
compatible_window: W,
) -> impl Future<Output = Result<Self, Error>> {
Self::with_backend(settings, compatible_window, None)
}
/// Creates a new [`Compositor`] with a backend preference.
///
/// If the backend does not match the preference, it will return
/// [`Error::GraphicsAdapterNotFound`].
fn with_backend<W: Window + Clone>(
_settings: Settings,
_compatible_window: W,
_backend: Option<&str>,
) -> impl Future<Output = Result<Self, Error>>;
/// Creates a [`Self::Renderer`] for the [`Compositor`].
@ -93,6 +101,12 @@ impl<T> Window for T where
{
}
/// Defines the default compositor of a renderer.
pub trait Default {
/// The compositor of the renderer.
type Compositor: Compositor<Renderer = Self>;
}
/// Result of an unsuccessful call to [`Compositor::present`].
#[derive(Clone, PartialEq, Eq, Debug, Error)]
pub enum SurfaceError {
@ -122,3 +136,69 @@ pub struct Information {
/// Contains the graphics backend.
pub backend: String,
}
#[cfg(debug_assertions)]
impl Compositor for () {
type Renderer = ();
type Surface = ();
async fn with_backend<W: Window + Clone>(
_settings: Settings,
_compatible_window: W,
_preffered_backend: Option<&str>,
) -> Result<Self, Error> {
Ok(())
}
fn create_renderer(&self) -> Self::Renderer {}
fn create_surface<W: Window + Clone>(
&mut self,
_window: W,
_width: u32,
_height: u32,
) -> Self::Surface {
}
fn configure_surface(
&mut self,
_surface: &mut Self::Surface,
_width: u32,
_height: u32,
) {
}
fn fetch_information(&self) -> Information {
Information {
adapter: String::from("Null Renderer"),
backend: String::from("Null"),
}
}
fn present<T: AsRef<str>>(
&mut self,
_renderer: &mut Self::Renderer,
_surface: &mut Self::Surface,
_viewport: &Viewport,
_background_color: Color,
_overlay: &[T],
) -> Result<(), SurfaceError> {
Ok(())
}
fn screenshot<T: AsRef<str>>(
&mut self,
_renderer: &mut Self::Renderer,
_surface: &mut Self::Surface,
_viewport: &Viewport,
_background_color: Color,
_overlay: &[T],
) -> Vec<u8> {
vec![]
}
}
#[cfg(debug_assertions)]
impl Default for () {
type Compositor = ();
}

View file

@ -1,5 +1,7 @@
//! See what can go wrong when creating graphical backends.
/// An error that occurred while creating an application's graphical context.
#[derive(Debug, thiserror::Error)]
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum Error {
/// The requested backend version is not supported.
#[error("the requested backend version is not supported")]
@ -11,9 +13,30 @@ pub enum Error {
/// A suitable graphics adapter or device could not be found.
#[error("a suitable graphics adapter or device could not be found")]
GraphicsAdapterNotFound,
GraphicsAdapterNotFound {
/// The name of the backend where the error happened
backend: &'static str,
/// The reason why this backend could not be used
reason: Reason,
},
/// An error occurred in the context's internal backend
#[error("an error occurred in the context's internal backend")]
BackendError(String),
/// Multiple errors occurred
#[error("multiple errors occurred: {0:?}")]
List(Vec<Self>),
}
/// The reason why a graphics adapter could not be found
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Reason {
/// The backend did not match the preference
DidNotMatch {
/// The preferred backend
preferred_backend: String,
},
/// The request to create the backend failed
RequestFailed(String),
}

View file

@ -1,12 +1,16 @@
//! Build and draw geometry.
pub mod fill;
pub mod frame;
pub mod path;
pub mod stroke;
mod cache;
mod style;
mod text;
pub use cache::Cache;
pub use fill::Fill;
pub use frame::Frame;
pub use path::Path;
pub use stroke::{LineCap, LineDash, LineJoin, Stroke};
pub use style::Style;
@ -14,11 +18,39 @@ pub use text::Text;
pub use crate::gradient::{self, Gradient};
/// A renderer capable of drawing some [`Self::Geometry`].
pub trait Renderer: crate::core::Renderer {
/// The kind of geometry this renderer can draw.
type Geometry;
use crate::core::{self, Size};
use crate::Cached;
/// Draws the given layers of [`Self::Geometry`].
fn draw(&mut self, layers: Vec<Self::Geometry>);
/// A renderer capable of drawing some [`Self::Geometry`].
pub trait Renderer: core::Renderer {
/// The kind of geometry this renderer can draw.
type Geometry: Cached;
/// The kind of [`Frame`] this renderer supports.
type Frame: frame::Backend<Geometry = Self::Geometry>;
/// Creates a new [`Self::Frame`].
fn new_frame(&self, size: Size) -> Self::Frame;
/// Draws the given [`Self::Geometry`].
fn draw_geometry(&mut self, geometry: Self::Geometry);
}
/// The graphics backend of a geometry renderer.
pub trait Backend {
/// The kind of [`Frame`] this backend supports.
type Frame: frame::Backend;
/// Creates a new [`Self::Frame`].
fn new_frame(&self, size: Size) -> Self::Frame;
}
#[cfg(debug_assertions)]
impl Renderer for () {
type Geometry = ();
type Frame = ();
fn new_frame(&self, _size: Size) -> Self::Frame {}
fn draw_geometry(&mut self, _geometry: Self::Geometry) {}
}

View file

@ -0,0 +1,108 @@
use crate::core::Size;
use crate::geometry::{self, Frame};
use crate::Cached;
use std::cell::RefCell;
/// A simple cache that stores generated geometry to avoid recomputation.
///
/// A [`Cache`] will not redraw its geometry unless the dimensions of its layer
/// change or it is explicitly cleared.
pub struct Cache<Renderer>
where
Renderer: geometry::Renderer,
{
state: RefCell<State<Renderer::Geometry>>,
}
impl<Renderer> Cache<Renderer>
where
Renderer: geometry::Renderer,
{
/// Creates a new empty [`Cache`].
pub fn new() -> Self {
Cache {
state: RefCell::new(State::Empty),
}
}
/// Clears the [`Cache`], forcing a redraw the next time it is used.
pub fn clear(&self) {
*self.state.borrow_mut() = State::Empty;
}
/// Draws geometry using the provided closure and stores it in the
/// [`Cache`].
///
/// The closure will only be called when
/// - the bounds have changed since the previous draw call.
/// - the [`Cache`] is empty or has been explicitly cleared.
///
/// Otherwise, the previously stored geometry will be returned. The
/// [`Cache`] is not cleared in this case. In other words, it will keep
/// returning the stored geometry if needed.
pub fn draw(
&self,
renderer: &Renderer,
bounds: Size,
draw_fn: impl FnOnce(&mut Frame<Renderer>),
) -> Renderer::Geometry {
use std::ops::Deref;
if let State::Filled {
bounds: cached_bounds,
geometry,
} = self.state.borrow().deref()
{
if *cached_bounds == bounds {
return Cached::load(geometry);
}
}
let mut frame = Frame::new(renderer, bounds);
draw_fn(&mut frame);
let geometry = frame.into_geometry().cache();
let result = Cached::load(&geometry);
*self.state.borrow_mut() = State::Filled { bounds, geometry };
result
}
}
impl<Renderer> std::fmt::Debug for Cache<Renderer>
where
Renderer: geometry::Renderer,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let state = self.state.borrow();
match *state {
State::Empty => write!(f, "Cache::Empty"),
State::Filled { bounds, .. } => {
write!(f, "Cache::Filled {{ bounds: {bounds:?} }}")
}
}
}
}
impl<Renderer> Default for Cache<Renderer>
where
Renderer: geometry::Renderer,
{
fn default() -> Self {
Self::new()
}
}
enum State<Geometry>
where
Geometry: Cached,
{
Empty,
Filled {
bounds: Size,
geometry: Geometry::Cache,
},
}

View file

@ -0,0 +1,251 @@
//! Draw and generate geometry.
use crate::core::{Point, Radians, Rectangle, Size, Vector};
use crate::geometry::{self, Fill, Path, Stroke, Text};
/// The region of a surface that can be used to draw geometry.
#[allow(missing_debug_implementations)]
pub struct Frame<Renderer>
where
Renderer: geometry::Renderer,
{
raw: Renderer::Frame,
}
impl<Renderer> Frame<Renderer>
where
Renderer: geometry::Renderer,
{
/// Creates a new [`Frame`] with the given dimensions.
pub fn new(renderer: &Renderer, size: Size) -> Self {
Self {
raw: renderer.new_frame(size),
}
}
/// Returns the width of the [`Frame`].
pub fn width(&self) -> f32 {
self.raw.width()
}
/// Returns the height of the [`Frame`].
pub fn height(&self) -> f32 {
self.raw.height()
}
/// Returns the dimensions of the [`Frame`].
pub fn size(&self) -> Size {
self.raw.size()
}
/// Returns the coordinate of the center of the [`Frame`].
pub fn center(&self) -> Point {
self.raw.center()
}
/// Draws the given [`Path`] on the [`Frame`] by filling it with the
/// provided style.
pub fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
self.raw.fill(path, fill);
}
/// Draws an axis-aligned rectangle given its top-left corner coordinate and
/// its `Size` on the [`Frame`] by filling it with the provided style.
pub fn fill_rectangle(
&mut self,
top_left: Point,
size: Size,
fill: impl Into<Fill>,
) {
self.raw.fill_rectangle(top_left, size, fill);
}
/// Draws the stroke of the given [`Path`] on the [`Frame`] with the
/// provided style.
pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) {
self.raw.stroke(path, stroke);
}
/// Draws the characters of the given [`Text`] on the [`Frame`], filling
/// them with the given color.
///
/// __Warning:__ All text will be rendered on top of all the layers of
/// a `Canvas`. Therefore, it is currently only meant to be used for
/// overlays, which is the most common use case.
pub fn fill_text(&mut self, text: impl Into<Text>) {
self.raw.fill_text(text);
}
/// Stores the current transform of the [`Frame`] and executes the given
/// drawing operations, restoring the transform afterwards.
///
/// This method is useful to compose transforms and perform drawing
/// operations in different coordinate systems.
#[inline]
pub fn with_save<R>(&mut self, f: impl FnOnce(&mut Self) -> R) -> R {
self.push_transform();
let result = f(self);
self.pop_transform();
result
}
/// Pushes the current transform in the transform stack.
pub fn push_transform(&mut self) {
self.raw.push_transform();
}
/// Pops a transform from the transform stack and sets it as the current transform.
pub fn pop_transform(&mut self) {
self.raw.pop_transform();
}
/// Executes the given drawing operations within a [`Rectangle`] region,
/// clipping any geometry that overflows its bounds. Any transformations
/// performed are local to the provided closure.
///
/// This method is useful to perform drawing operations that need to be
/// clipped.
#[inline]
pub fn with_clip<R>(
&mut self,
region: Rectangle,
f: impl FnOnce(&mut Self) -> R,
) -> R {
let mut frame = self.draft(region.size());
let result = f(&mut frame);
let origin = Point::new(region.x, region.y);
self.paste(frame, origin);
result
}
/// Creates a new [`Frame`] with the given [`Size`].
///
/// Draw its contents back to this [`Frame`] with [`paste`].
///
/// [`paste`]: Self::paste
pub fn draft(&mut self, size: Size) -> Self {
Self {
raw: self.raw.draft(size),
}
}
/// Draws the contents of the given [`Frame`] with origin at the given [`Point`].
pub fn paste(&mut self, frame: Self, at: Point) {
self.raw.paste(frame.raw, at);
}
/// Applies a translation to the current transform of the [`Frame`].
pub fn translate(&mut self, translation: Vector) {
self.raw.translate(translation);
}
/// Applies a rotation in radians to the current transform of the [`Frame`].
pub fn rotate(&mut self, angle: impl Into<Radians>) {
self.raw.rotate(angle);
}
/// Applies a uniform scaling to the current transform of the [`Frame`].
pub fn scale(&mut self, scale: impl Into<f32>) {
self.raw.scale(scale);
}
/// Applies a non-uniform scaling to the current transform of the [`Frame`].
pub fn scale_nonuniform(&mut self, scale: impl Into<Vector>) {
self.raw.scale_nonuniform(scale);
}
/// Turns the [`Frame`] into its underlying geometry.
pub fn into_geometry(self) -> Renderer::Geometry {
self.raw.into_geometry()
}
}
/// The internal implementation of a [`Frame`].
///
/// Analogous to [`Frame`]. See [`Frame`] for the documentation
/// of each method.
#[allow(missing_docs)]
pub trait Backend: Sized {
type Geometry;
fn width(&self) -> f32;
fn height(&self) -> f32;
fn size(&self) -> Size;
fn center(&self) -> Point;
fn push_transform(&mut self);
fn pop_transform(&mut self);
fn translate(&mut self, translation: Vector);
fn rotate(&mut self, angle: impl Into<Radians>);
fn scale(&mut self, scale: impl Into<f32>);
fn scale_nonuniform(&mut self, scale: impl Into<Vector>);
fn draft(&mut self, size: Size) -> Self;
fn paste(&mut self, frame: Self, at: Point);
fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>);
fn fill(&mut self, path: &Path, fill: impl Into<Fill>);
fn fill_text(&mut self, text: impl Into<Text>);
fn fill_rectangle(
&mut self,
top_left: Point,
size: Size,
fill: impl Into<Fill>,
);
fn into_geometry(self) -> Self::Geometry;
}
#[cfg(debug_assertions)]
impl Backend for () {
type Geometry = ();
fn width(&self) -> f32 {
0.0
}
fn height(&self) -> f32 {
0.0
}
fn size(&self) -> Size {
Size::ZERO
}
fn center(&self) -> Point {
Point::ORIGIN
}
fn push_transform(&mut self) {}
fn pop_transform(&mut self) {}
fn translate(&mut self, _translation: Vector) {}
fn rotate(&mut self, _angle: impl Into<Radians>) {}
fn scale(&mut self, _scale: impl Into<f32>) {}
fn scale_nonuniform(&mut self, _scale: impl Into<Vector>) {}
fn draft(&mut self, _size: Size) -> Self {}
fn paste(&mut self, _frame: Self, _at: Point) {}
fn stroke<'a>(&mut self, _path: &Path, _stroke: impl Into<Stroke<'a>>) {}
fn fill(&mut self, _path: &Path, _fill: impl Into<Fill>) {}
fn fill_text(&mut self, _text: impl Into<Text>) {}
fn fill_rectangle(
&mut self,
_top_left: Point,
_size: Size,
_fill: impl Into<Fill>,
) {
}
fn into_geometry(self) -> Self::Geometry {}
}

View file

@ -17,14 +17,16 @@
)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
mod antialiasing;
mod error;
mod cached;
mod primitive;
mod settings;
mod viewport;
pub mod backend;
pub mod color;
pub mod compositor;
pub mod damage;
pub mod error;
pub mod gradient;
pub mod mesh;
pub mod renderer;
@ -38,6 +40,7 @@ pub mod image;
pub use antialiasing::Antialiasing;
pub use backend::Backend;
pub use cached::Cached;
pub use compositor::Compositor;
pub use damage::Damage;
pub use error::Error;
@ -45,6 +48,7 @@ pub use gradient::Gradient;
pub use mesh::Mesh;
pub use primitive::Primitive;
pub use renderer::Renderer;
pub use settings::Settings;
pub use viewport::Viewport;
pub use iced_core as core;

View file

@ -74,3 +74,9 @@ pub struct GradientVertex2D {
/// The packed vertex data of the gradient.
pub gradient: gradient::Packed,
}
/// A renderer capable of drawing a [`Mesh`].
pub trait Renderer {
/// Draws the given [`Mesh`].
fn draw_mesh(&mut self, mesh: Mesh);
}

View file

@ -1,5 +1,6 @@
//! Create a renderer from a [`Backend`].
use crate::backend::{self, Backend};
use crate::compositor;
use crate::core;
use crate::core::image;
use crate::core::renderer;
@ -8,8 +9,9 @@ use crate::core::text::Text;
use crate::core::{
Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation,
};
use crate::mesh;
use crate::text;
use crate::Primitive;
use crate::{Mesh, Primitive};
use std::borrow::Cow;
@ -20,6 +22,7 @@ pub struct Renderer<B: Backend> {
default_font: Font,
default_text_size: Pixels,
primitives: Vec<Primitive<B::Primitive>>,
stack: Vec<Vec<Primitive<B::Primitive>>>,
}
impl<B: Backend> Renderer<B> {
@ -34,6 +37,7 @@ impl<B: Backend> Renderer<B> {
default_font,
default_text_size,
primitives: Vec::new(),
stack: Vec::new(),
}
}
@ -55,61 +59,35 @@ impl<B: Backend> Renderer<B> {
) -> O {
f(&mut self.backend, &self.primitives)
}
/// Starts recording a new layer.
pub fn start_layer(&mut self) -> Vec<Primitive<B::Primitive>> {
std::mem::take(&mut self.primitives)
}
/// Ends the recording of a layer.
pub fn end_layer(
&mut self,
primitives: Vec<Primitive<B::Primitive>>,
bounds: Rectangle,
) {
let layer = std::mem::replace(&mut self.primitives, primitives);
impl<B: Backend> iced_core::Renderer for Renderer<B> {
fn start_layer(&mut self) {
self.stack.push(std::mem::take(&mut self.primitives));
}
fn end_layer(&mut self, bounds: Rectangle) {
let layer = std::mem::replace(
&mut self.primitives,
self.stack.pop().expect("a layer should be recording"),
);
self.primitives.push(Primitive::group(layer).clip(bounds));
}
/// Starts recording a translation.
pub fn start_transformation(&mut self) -> Vec<Primitive<B::Primitive>> {
std::mem::take(&mut self.primitives)
fn start_transformation(&mut self) {
self.stack.push(std::mem::take(&mut self.primitives));
}
/// Ends the recording of a translation.
pub fn end_transformation(
&mut self,
primitives: Vec<Primitive<B::Primitive>>,
transformation: Transformation,
) {
let layer = std::mem::replace(&mut self.primitives, primitives);
fn end_transformation(&mut self, transformation: Transformation) {
let layer = std::mem::replace(
&mut self.primitives,
self.stack.pop().expect("a layer should be recording"),
);
self.primitives
.push(Primitive::group(layer).transform(transformation));
}
}
impl<B: Backend> iced_core::Renderer for Renderer<B> {
fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) {
let current = self.start_layer();
f(self);
self.end_layer(current, bounds);
}
fn with_transformation(
&mut self,
transformation: Transformation,
f: impl FnOnce(&mut Self),
) {
let current = self.start_transformation();
f(self);
self.end_transformation(current, transformation);
}
fn fill_quad(
&mut self,
@ -211,11 +189,11 @@ where
{
type Handle = image::Handle;
fn dimensions(&self, handle: &image::Handle) -> Size<u32> {
fn measure_image(&self, handle: &image::Handle) -> Size<u32> {
self.backend().dimensions(handle)
}
fn draw(
fn draw_image(
&mut self,
handle: image::Handle,
filter_method: image::FilterMethod,
@ -233,11 +211,11 @@ impl<B> svg::Renderer for Renderer<B>
where
B: Backend + backend::Svg,
{
fn dimensions(&self, handle: &svg::Handle) -> Size<u32> {
fn measure_svg(&self, handle: &svg::Handle) -> Size<u32> {
self.backend().viewport_dimensions(handle)
}
fn draw(
fn draw_svg(
&mut self,
handle: svg::Handle,
color: Option<Color>,
@ -250,3 +228,42 @@ where
});
}
}
impl<B: Backend> mesh::Renderer for Renderer<B> {
fn draw_mesh(&mut self, mesh: Mesh) {
match B::Primitive::try_from(mesh) {
Ok(primitive) => {
self.draw_primitive(Primitive::Custom(primitive));
}
Err(error) => {
log::warn!("mesh primitive could not be drawn: {error:?}");
}
}
}
}
#[cfg(feature = "geometry")]
impl<B> crate::geometry::Renderer for Renderer<B>
where
B: Backend + crate::geometry::Backend,
B::Frame:
crate::geometry::frame::Backend<Geometry = Primitive<B::Primitive>>,
{
type Frame = B::Frame;
type Geometry = Primitive<B::Primitive>;
fn new_frame(&self, size: Size) -> Self::Frame {
self.backend.new_frame(size)
}
fn draw_geometry(&mut self, geometry: Self::Geometry) {
self.draw_primitive(geometry);
}
}
impl<B> compositor::Default for Renderer<B>
where
B: Backend,
{
type Compositor = B::Compositor;
}

View file

@ -1,5 +1,5 @@
use crate::core::{Font, Pixels};
use crate::graphics::Antialiasing;
use crate::Antialiasing;
/// The settings of a Backend.
#[derive(Debug, Clone, Copy, PartialEq)]

View file

@ -12,9 +12,10 @@ keywords.workspace = true
[features]
wgpu = ["iced_wgpu"]
image = ["iced_tiny_skia/image", "iced_wgpu?/image"]
svg = ["iced_tiny_skia/svg", "iced_wgpu?/svg"]
geometry = ["iced_graphics/geometry", "iced_tiny_skia/geometry", "iced_wgpu?/geometry"]
tiny-skia = ["iced_tiny_skia"]
image = ["iced_tiny_skia?/image", "iced_wgpu?/image"]
svg = ["iced_tiny_skia?/svg", "iced_wgpu?/svg"]
geometry = ["iced_graphics/geometry", "iced_tiny_skia?/geometry", "iced_wgpu?/geometry"]
tracing = ["iced_wgpu?/tracing"]
web-colors = ["iced_wgpu?/web-colors"]
webgl = ["iced_wgpu?/webgl"]
@ -22,7 +23,9 @@ fira-sans = ["iced_graphics/fira-sans"]
[dependencies]
iced_graphics.workspace = true
iced_tiny_skia.workspace = true
iced_tiny_skia.optional = true
iced_wgpu.workspace = true
iced_wgpu.optional = true

View file

@ -1,270 +1 @@
use crate::core::Color;
use crate::graphics::compositor::{Information, SurfaceError, Window};
use crate::graphics::{Error, Viewport};
use crate::{Renderer, Settings};
use std::env;
use std::future::Future;
pub enum Compositor {
TinySkia(iced_tiny_skia::window::Compositor),
#[cfg(feature = "wgpu")]
Wgpu(iced_wgpu::window::Compositor),
}
pub enum Surface {
TinySkia(iced_tiny_skia::window::Surface),
#[cfg(feature = "wgpu")]
Wgpu(iced_wgpu::window::Surface<'static>),
}
impl crate::graphics::Compositor for Compositor {
type Settings = Settings;
type Renderer = Renderer;
type Surface = Surface;
fn new<W: Window + Clone>(
settings: Self::Settings,
compatible_window: W,
) -> impl Future<Output = Result<Self, Error>> {
let candidates =
Candidate::list_from_env().unwrap_or(Candidate::default_list());
async move {
let mut error = Error::GraphicsAdapterNotFound;
for candidate in candidates {
match candidate.build(settings, compatible_window.clone()).await
{
Ok(compositor) => return Ok(compositor),
Err(new_error) => {
error = new_error;
}
}
}
Err(error)
}
}
fn create_renderer(&self) -> Self::Renderer {
match self {
Compositor::TinySkia(compositor) => {
Renderer::TinySkia(compositor.create_renderer())
}
#[cfg(feature = "wgpu")]
Compositor::Wgpu(compositor) => {
Renderer::Wgpu(compositor.create_renderer())
}
}
}
fn create_surface<W: Window + Clone>(
&mut self,
window: W,
width: u32,
height: u32,
) -> Surface {
match self {
Self::TinySkia(compositor) => Surface::TinySkia(
compositor.create_surface(window, width, height),
),
#[cfg(feature = "wgpu")]
Self::Wgpu(compositor) => {
Surface::Wgpu(compositor.create_surface(window, width, height))
}
}
}
fn configure_surface(
&mut self,
surface: &mut Surface,
width: u32,
height: u32,
) {
match (self, surface) {
(Self::TinySkia(compositor), Surface::TinySkia(surface)) => {
compositor.configure_surface(surface, width, height);
}
#[cfg(feature = "wgpu")]
(Self::Wgpu(compositor), Surface::Wgpu(surface)) => {
compositor.configure_surface(surface, width, height);
}
#[allow(unreachable_patterns)]
_ => panic!(
"The provided surface is not compatible with the compositor."
),
}
}
fn fetch_information(&self) -> Information {
match self {
Self::TinySkia(compositor) => compositor.fetch_information(),
#[cfg(feature = "wgpu")]
Self::Wgpu(compositor) => compositor.fetch_information(),
}
}
fn present<T: AsRef<str>>(
&mut self,
renderer: &mut Self::Renderer,
surface: &mut Self::Surface,
viewport: &Viewport,
background_color: Color,
overlay: &[T],
) -> Result<(), SurfaceError> {
match (self, renderer, surface) {
(
Self::TinySkia(_compositor),
crate::Renderer::TinySkia(renderer),
Surface::TinySkia(surface),
) => renderer.with_primitives(|backend, primitives| {
iced_tiny_skia::window::compositor::present(
backend,
surface,
primitives,
viewport,
background_color,
overlay,
)
}),
#[cfg(feature = "wgpu")]
(
Self::Wgpu(compositor),
crate::Renderer::Wgpu(renderer),
Surface::Wgpu(surface),
) => renderer.with_primitives(|backend, primitives| {
iced_wgpu::window::compositor::present(
compositor,
backend,
surface,
primitives,
viewport,
background_color,
overlay,
)
}),
#[allow(unreachable_patterns)]
_ => panic!(
"The provided renderer or surface are not compatible \
with the compositor."
),
}
}
fn screenshot<T: AsRef<str>>(
&mut self,
renderer: &mut Self::Renderer,
surface: &mut Self::Surface,
viewport: &Viewport,
background_color: Color,
overlay: &[T],
) -> Vec<u8> {
match (self, renderer, surface) {
(
Self::TinySkia(_compositor),
Renderer::TinySkia(renderer),
Surface::TinySkia(surface),
) => renderer.with_primitives(|backend, primitives| {
iced_tiny_skia::window::compositor::screenshot(
surface,
backend,
primitives,
viewport,
background_color,
overlay,
)
}),
#[cfg(feature = "wgpu")]
(
Self::Wgpu(compositor),
Renderer::Wgpu(renderer),
Surface::Wgpu(_),
) => renderer.with_primitives(|backend, primitives| {
iced_wgpu::window::compositor::screenshot(
compositor,
backend,
primitives,
viewport,
background_color,
overlay,
)
}),
#[allow(unreachable_patterns)]
_ => panic!(
"The provided renderer or backend are not compatible \
with the compositor."
),
}
}
}
enum Candidate {
Wgpu,
TinySkia,
}
impl Candidate {
fn default_list() -> Vec<Self> {
vec![
#[cfg(feature = "wgpu")]
Self::Wgpu,
Self::TinySkia,
]
}
fn list_from_env() -> Option<Vec<Self>> {
let backends = env::var("ICED_BACKEND").ok()?;
Some(
backends
.split(',')
.map(str::trim)
.map(|backend| match backend {
"wgpu" => Self::Wgpu,
"tiny-skia" => Self::TinySkia,
_ => panic!("unknown backend value: \"{backend}\""),
})
.collect(),
)
}
async fn build<W: Window>(
self,
settings: Settings,
_compatible_window: W,
) -> Result<Compositor, Error> {
match self {
Self::TinySkia => {
let compositor = iced_tiny_skia::window::compositor::new(
iced_tiny_skia::Settings {
default_font: settings.default_font,
default_text_size: settings.default_text_size,
},
_compatible_window,
);
Ok(Compositor::TinySkia(compositor))
}
#[cfg(feature = "wgpu")]
Self::Wgpu => {
let compositor = iced_wgpu::window::compositor::new(
iced_wgpu::Settings {
default_font: settings.default_font,
default_text_size: settings.default_text_size,
antialiasing: settings.antialiasing,
..iced_wgpu::Settings::from_env()
},
_compatible_window,
)
.await?;
Ok(Compositor::Wgpu(compositor))
}
#[cfg(not(feature = "wgpu"))]
Self::Wgpu => {
panic!("`wgpu` feature was not enabled in `iced_renderer`")
}
}
}
}

570
renderer/src/fallback.rs Normal file
View file

@ -0,0 +1,570 @@
use crate::core::image;
use crate::core::renderer;
use crate::core::svg;
use crate::core::{
self, Background, Color, Point, Rectangle, Size, Transformation,
};
use crate::graphics;
use crate::graphics::compositor;
use crate::graphics::mesh;
pub enum Renderer<L, R> {
Left(L),
Right(R),
}
macro_rules! delegate {
($renderer:expr, $name:ident, $body:expr) => {
match $renderer {
Self::Left($name) => $body,
Self::Right($name) => $body,
}
};
}
impl<L, R> core::Renderer for Renderer<L, R>
where
L: core::Renderer,
R: core::Renderer,
{
fn fill_quad(
&mut self,
quad: renderer::Quad,
background: impl Into<Background>,
) {
delegate!(self, renderer, renderer.fill_quad(quad, background.into()));
}
fn clear(&mut self) {
delegate!(self, renderer, renderer.clear());
}
fn start_layer(&mut self) {
delegate!(self, renderer, renderer.start_layer());
}
fn end_layer(&mut self, bounds: Rectangle) {
delegate!(self, renderer, renderer.end_layer(bounds));
}
fn start_transformation(&mut self) {
delegate!(self, renderer, renderer.start_transformation());
}
fn end_transformation(&mut self, transformation: Transformation) {
delegate!(self, renderer, renderer.end_transformation(transformation));
}
}
impl<L, R> core::text::Renderer for Renderer<L, R>
where
L: core::text::Renderer,
R: core::text::Renderer<
Font = L::Font,
Paragraph = L::Paragraph,
Editor = L::Editor,
>,
{
type Font = L::Font;
type Paragraph = L::Paragraph;
type Editor = L::Editor;
const ICON_FONT: Self::Font = L::ICON_FONT;
const CHECKMARK_ICON: char = L::CHECKMARK_ICON;
const ARROW_DOWN_ICON: char = L::ARROW_DOWN_ICON;
fn default_font(&self) -> Self::Font {
delegate!(self, renderer, renderer.default_font())
}
fn default_size(&self) -> core::Pixels {
delegate!(self, renderer, renderer.default_size())
}
fn load_font(&mut self, font: std::borrow::Cow<'static, [u8]>) {
delegate!(self, renderer, renderer.load_font(font));
}
fn fill_paragraph(
&mut self,
text: &Self::Paragraph,
position: Point,
color: Color,
clip_bounds: Rectangle,
) {
delegate!(
self,
renderer,
renderer.fill_paragraph(text, position, color, clip_bounds)
);
}
fn fill_editor(
&mut self,
editor: &Self::Editor,
position: Point,
color: Color,
clip_bounds: Rectangle,
) {
delegate!(
self,
renderer,
renderer.fill_editor(editor, position, color, clip_bounds)
);
}
fn fill_text(
&mut self,
text: core::Text<'_, Self::Font>,
position: Point,
color: Color,
clip_bounds: Rectangle,
) {
delegate!(
self,
renderer,
renderer.fill_text(text, position, color, clip_bounds)
);
}
}
impl<L, R> image::Renderer for Renderer<L, R>
where
L: image::Renderer,
R: image::Renderer<Handle = L::Handle>,
{
type Handle = L::Handle;
fn measure_image(&self, handle: &Self::Handle) -> Size<u32> {
delegate!(self, renderer, renderer.measure_image(handle))
}
fn draw_image(
&mut self,
handle: Self::Handle,
filter_method: image::FilterMethod,
bounds: Rectangle,
) {
delegate!(
self,
renderer,
renderer.draw_image(handle, filter_method, bounds)
);
}
}
impl<L, R> svg::Renderer for Renderer<L, R>
where
L: svg::Renderer,
R: svg::Renderer,
{
fn measure_svg(&self, handle: &svg::Handle) -> Size<u32> {
delegate!(self, renderer, renderer.measure_svg(handle))
}
fn draw_svg(
&mut self,
handle: svg::Handle,
color: Option<Color>,
bounds: Rectangle,
) {
delegate!(self, renderer, renderer.draw_svg(handle, color, bounds));
}
}
impl<L, R> mesh::Renderer for Renderer<L, R>
where
L: mesh::Renderer,
R: mesh::Renderer,
{
fn draw_mesh(&mut self, mesh: graphics::Mesh) {
delegate!(self, renderer, renderer.draw_mesh(mesh));
}
}
pub enum Compositor<L, R>
where
L: graphics::Compositor,
R: graphics::Compositor,
{
Left(L),
Right(R),
}
pub enum Surface<L, R> {
Left(L),
Right(R),
}
impl<L, R> graphics::Compositor for Compositor<L, R>
where
L: graphics::Compositor,
R: graphics::Compositor,
{
type Renderer = Renderer<L::Renderer, R::Renderer>;
type Surface = Surface<L::Surface, R::Surface>;
async fn with_backend<W: compositor::Window + Clone>(
settings: graphics::Settings,
compatible_window: W,
backend: Option<&str>,
) -> Result<Self, graphics::Error> {
use std::env;
let backends = backend
.map(str::to_owned)
.or_else(|| env::var("ICED_BACKEND").ok());
let mut candidates: Vec<_> = backends
.map(|backends| {
backends
.split(',')
.filter(|candidate| !candidate.is_empty())
.map(str::to_owned)
.map(Some)
.collect()
})
.unwrap_or_default();
if candidates.is_empty() {
candidates.push(None);
}
let mut errors = vec![];
for backend in candidates.iter().map(Option::as_deref) {
match L::with_backend(settings, compatible_window.clone(), backend)
.await
{
Ok(compositor) => return Ok(Self::Left(compositor)),
Err(error) => {
errors.push(error);
}
}
match R::with_backend(settings, compatible_window.clone(), backend)
.await
{
Ok(compositor) => return Ok(Self::Right(compositor)),
Err(error) => {
errors.push(error);
}
}
}
Err(graphics::Error::List(errors))
}
fn create_renderer(&self) -> Self::Renderer {
match self {
Self::Left(compositor) => {
Renderer::Left(compositor.create_renderer())
}
Self::Right(compositor) => {
Renderer::Right(compositor.create_renderer())
}
}
}
fn create_surface<W: compositor::Window + Clone>(
&mut self,
window: W,
width: u32,
height: u32,
) -> Self::Surface {
match self {
Self::Left(compositor) => {
Surface::Left(compositor.create_surface(window, width, height))
}
Self::Right(compositor) => {
Surface::Right(compositor.create_surface(window, width, height))
}
}
}
fn configure_surface(
&mut self,
surface: &mut Self::Surface,
width: u32,
height: u32,
) {
match (self, surface) {
(Self::Left(compositor), Surface::Left(surface)) => {
compositor.configure_surface(surface, width, height);
}
(Self::Right(compositor), Surface::Right(surface)) => {
compositor.configure_surface(surface, width, height);
}
_ => unreachable!(),
}
}
fn fetch_information(&self) -> compositor::Information {
delegate!(self, compositor, compositor.fetch_information())
}
fn present<T: AsRef<str>>(
&mut self,
renderer: &mut Self::Renderer,
surface: &mut Self::Surface,
viewport: &graphics::Viewport,
background_color: Color,
overlay: &[T],
) -> Result<(), compositor::SurfaceError> {
match (self, renderer, surface) {
(
Self::Left(compositor),
Renderer::Left(renderer),
Surface::Left(surface),
) => compositor.present(
renderer,
surface,
viewport,
background_color,
overlay,
),
(
Self::Right(compositor),
Renderer::Right(renderer),
Surface::Right(surface),
) => compositor.present(
renderer,
surface,
viewport,
background_color,
overlay,
),
_ => unreachable!(),
}
}
fn screenshot<T: AsRef<str>>(
&mut self,
renderer: &mut Self::Renderer,
surface: &mut Self::Surface,
viewport: &graphics::Viewport,
background_color: Color,
overlay: &[T],
) -> Vec<u8> {
match (self, renderer, surface) {
(
Self::Left(compositor),
Renderer::Left(renderer),
Surface::Left(surface),
) => compositor.screenshot(
renderer,
surface,
viewport,
background_color,
overlay,
),
(
Self::Right(compositor),
Renderer::Right(renderer),
Surface::Right(surface),
) => compositor.screenshot(
renderer,
surface,
viewport,
background_color,
overlay,
),
_ => unreachable!(),
}
}
}
#[cfg(feature = "wgpu")]
impl<L, R> iced_wgpu::primitive::pipeline::Renderer for Renderer<L, R>
where
L: iced_wgpu::primitive::pipeline::Renderer,
R: core::Renderer,
{
fn draw_pipeline_primitive(
&mut self,
bounds: Rectangle,
primitive: impl iced_wgpu::primitive::pipeline::Primitive,
) {
match self {
Self::Left(renderer) => {
renderer.draw_pipeline_primitive(bounds, primitive);
}
Self::Right(_) => {
log::warn!(
"Custom shader primitive is not supported with this renderer."
);
}
}
}
}
#[cfg(feature = "geometry")]
mod geometry {
use super::Renderer;
use crate::core::{Point, Radians, Size, Vector};
use crate::graphics::geometry::{self, Fill, Path, Stroke, Text};
use crate::graphics::Cached;
impl<L, R> geometry::Renderer for Renderer<L, R>
where
L: geometry::Renderer,
R: geometry::Renderer,
{
type Geometry = Geometry<L::Geometry, R::Geometry>;
type Frame = Frame<L::Frame, R::Frame>;
fn new_frame(&self, size: iced_graphics::core::Size) -> Self::Frame {
match self {
Self::Left(renderer) => Frame::Left(renderer.new_frame(size)),
Self::Right(renderer) => Frame::Right(renderer.new_frame(size)),
}
}
fn draw_geometry(&mut self, geometry: Self::Geometry) {
match (self, geometry) {
(Self::Left(renderer), Geometry::Left(geometry)) => {
renderer.draw_geometry(geometry);
}
(Self::Right(renderer), Geometry::Right(geometry)) => {
renderer.draw_geometry(geometry);
}
_ => unreachable!(),
}
}
}
pub enum Geometry<L, R> {
Left(L),
Right(R),
}
impl<L, R> Cached for Geometry<L, R>
where
L: Cached,
R: Cached,
{
type Cache = Geometry<L::Cache, R::Cache>;
fn load(cache: &Self::Cache) -> Self {
match cache {
Geometry::Left(cache) => Self::Left(L::load(cache)),
Geometry::Right(cache) => Self::Right(R::load(cache)),
}
}
fn cache(self) -> Self::Cache {
match self {
Self::Left(geometry) => Geometry::Left(geometry.cache()),
Self::Right(geometry) => Geometry::Right(geometry.cache()),
}
}
}
pub enum Frame<L, R> {
Left(L),
Right(R),
}
impl<L, R> geometry::frame::Backend for Frame<L, R>
where
L: geometry::frame::Backend,
R: geometry::frame::Backend,
{
type Geometry = Geometry<L::Geometry, R::Geometry>;
fn width(&self) -> f32 {
delegate!(self, frame, frame.width())
}
fn height(&self) -> f32 {
delegate!(self, frame, frame.height())
}
fn size(&self) -> Size {
delegate!(self, frame, frame.size())
}
fn center(&self) -> Point {
delegate!(self, frame, frame.center())
}
fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
delegate!(self, frame, frame.fill(path, fill));
}
fn fill_rectangle(
&mut self,
top_left: Point,
size: Size,
fill: impl Into<Fill>,
) {
delegate!(self, frame, frame.fill_rectangle(top_left, size, fill));
}
fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) {
delegate!(self, frame, frame.stroke(path, stroke));
}
fn fill_text(&mut self, text: impl Into<Text>) {
delegate!(self, frame, frame.fill_text(text));
}
fn push_transform(&mut self) {
delegate!(self, frame, frame.push_transform());
}
fn pop_transform(&mut self) {
delegate!(self, frame, frame.pop_transform());
}
fn draft(&mut self, size: Size) -> Self {
match self {
Self::Left(frame) => Self::Left(frame.draft(size)),
Self::Right(frame) => Self::Right(frame.draft(size)),
}
}
fn paste(&mut self, frame: Self, at: Point) {
match (self, frame) {
(Self::Left(target), Self::Left(source)) => {
target.paste(source, at);
}
(Self::Right(target), Self::Right(source)) => {
target.paste(source, at);
}
_ => unreachable!(),
}
}
fn translate(&mut self, translation: Vector) {
delegate!(self, frame, frame.translate(translation));
}
fn rotate(&mut self, angle: impl Into<Radians>) {
delegate!(self, frame, frame.rotate(angle));
}
fn scale(&mut self, scale: impl Into<f32>) {
delegate!(self, frame, frame.scale(scale));
}
fn scale_nonuniform(&mut self, scale: impl Into<Vector>) {
delegate!(self, frame, frame.scale_nonuniform(scale));
}
fn into_geometry(self) -> Self::Geometry {
match self {
Frame::Left(frame) => Geometry::Left(frame.into_geometry()),
Frame::Right(frame) => Geometry::Right(frame.into_geometry()),
}
}
}
}
impl<L, R> compositor::Default for Renderer<L, R>
where
L: compositor::Default,
R: compositor::Default,
{
type Compositor = Compositor<L::Compositor, R::Compositor>;
}

View file

@ -1,210 +0,0 @@
mod cache;
pub use cache::Cache;
use crate::core::{Point, Radians, Rectangle, Size, Transformation, Vector};
use crate::graphics::geometry::{Fill, Path, Stroke, Text};
use crate::Renderer;
macro_rules! delegate {
($frame:expr, $name:ident, $body:expr) => {
match $frame {
Self::TinySkia($name) => $body,
#[cfg(feature = "wgpu")]
Self::Wgpu($name) => $body,
}
};
}
pub enum Geometry {
TinySkia(iced_tiny_skia::Primitive),
#[cfg(feature = "wgpu")]
Wgpu(iced_wgpu::Primitive),
}
impl Geometry {
pub fn transform(self, transformation: Transformation) -> Self {
match self {
Self::TinySkia(primitive) => {
Self::TinySkia(primitive.transform(transformation))
}
#[cfg(feature = "wgpu")]
Self::Wgpu(primitive) => {
Self::Wgpu(primitive.transform(transformation))
}
}
}
}
pub enum Frame {
TinySkia(iced_tiny_skia::geometry::Frame),
#[cfg(feature = "wgpu")]
Wgpu(iced_wgpu::geometry::Frame),
}
impl Frame {
pub fn new(renderer: &Renderer, size: Size) -> Self {
match renderer {
Renderer::TinySkia(_) => {
Frame::TinySkia(iced_tiny_skia::geometry::Frame::new(size))
}
#[cfg(feature = "wgpu")]
Renderer::Wgpu(_) => {
Frame::Wgpu(iced_wgpu::geometry::Frame::new(size))
}
}
}
/// Returns the width of the [`Frame`].
#[inline]
pub fn width(&self) -> f32 {
delegate!(self, frame, frame.width())
}
/// Returns the height of the [`Frame`].
#[inline]
pub fn height(&self) -> f32 {
delegate!(self, frame, frame.height())
}
/// Returns the dimensions of the [`Frame`].
#[inline]
pub fn size(&self) -> Size {
delegate!(self, frame, frame.size())
}
/// Returns the coordinate of the center of the [`Frame`].
#[inline]
pub fn center(&self) -> Point {
delegate!(self, frame, frame.center())
}
/// Draws the given [`Path`] on the [`Frame`] by filling it with the
/// provided style.
pub fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
delegate!(self, frame, frame.fill(path, fill));
}
/// Draws an axis-aligned rectangle given its top-left corner coordinate and
/// its `Size` on the [`Frame`] by filling it with the provided style.
pub fn fill_rectangle(
&mut self,
top_left: Point,
size: Size,
fill: impl Into<Fill>,
) {
delegate!(self, frame, frame.fill_rectangle(top_left, size, fill));
}
/// Draws the stroke of the given [`Path`] on the [`Frame`] with the
/// provided style.
pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) {
delegate!(self, frame, frame.stroke(path, stroke));
}
/// Draws the characters of the given [`Text`] on the [`Frame`], filling
/// them with the given color.
///
/// __Warning:__ Text currently does not work well with rotations and scale
/// transforms! The position will be correctly transformed, but the
/// resulting glyphs will not be rotated or scaled properly.
///
/// Additionally, all text will be rendered on top of all the layers of
/// a `Canvas`. Therefore, it is currently only meant to be used for
/// overlays, which is the most common use case.
///
/// Support for vectorial text is planned, and should address all these
/// limitations.
pub fn fill_text(&mut self, text: impl Into<Text>) {
delegate!(self, frame, frame.fill_text(text));
}
/// Stores the current transform of the [`Frame`] and executes the given
/// drawing operations, restoring the transform afterwards.
///
/// This method is useful to compose transforms and perform drawing
/// operations in different coordinate systems.
#[inline]
pub fn with_save<R>(&mut self, f: impl FnOnce(&mut Frame) -> R) -> R {
delegate!(self, frame, frame.push_transform());
let result = f(self);
delegate!(self, frame, frame.pop_transform());
result
}
/// Executes the given drawing operations within a [`Rectangle`] region,
/// clipping any geometry that overflows its bounds. Any transformations
/// performed are local to the provided closure.
///
/// This method is useful to perform drawing operations that need to be
/// clipped.
#[inline]
pub fn with_clip<R>(
&mut self,
region: Rectangle,
f: impl FnOnce(&mut Frame) -> R,
) -> R {
let mut frame = match self {
Self::TinySkia(_) => Self::TinySkia(
iced_tiny_skia::geometry::Frame::new(region.size()),
),
#[cfg(feature = "wgpu")]
Self::Wgpu(_) => {
Self::Wgpu(iced_wgpu::geometry::Frame::new(region.size()))
}
};
let result = f(&mut frame);
let origin = Point::new(region.x, region.y);
match (self, frame) {
(Self::TinySkia(target), Self::TinySkia(frame)) => {
target.clip(frame, origin);
}
#[cfg(feature = "wgpu")]
(Self::Wgpu(target), Self::Wgpu(frame)) => {
target.clip(frame, origin);
}
#[allow(unreachable_patterns)]
_ => unreachable!(),
};
result
}
/// Applies a translation to the current transform of the [`Frame`].
#[inline]
pub fn translate(&mut self, translation: Vector) {
delegate!(self, frame, frame.translate(translation));
}
/// Applies a rotation in radians to the current transform of the [`Frame`].
#[inline]
pub fn rotate(&mut self, angle: impl Into<Radians>) {
delegate!(self, frame, frame.rotate(angle));
}
/// Applies a uniform scaling to the current transform of the [`Frame`].
#[inline]
pub fn scale(&mut self, scale: impl Into<f32>) {
delegate!(self, frame, frame.scale(scale));
}
/// Applies a non-uniform scaling to the current transform of the [`Frame`].
#[inline]
pub fn scale_nonuniform(&mut self, scale: impl Into<Vector>) {
delegate!(self, frame, frame.scale_nonuniform(scale));
}
pub fn into_geometry(self) -> Geometry {
match self {
Self::TinySkia(frame) => Geometry::TinySkia(frame.into_primitive()),
#[cfg(feature = "wgpu")]
Self::Wgpu(frame) => Geometry::Wgpu(frame.into_primitive()),
}
}
}

View file

@ -1,125 +0,0 @@
use crate::core::Size;
use crate::geometry::{Frame, Geometry};
use crate::Renderer;
use std::cell::RefCell;
use std::sync::Arc;
/// A simple cache that stores generated [`Geometry`] to avoid recomputation.
///
/// A [`Cache`] will not redraw its geometry unless the dimensions of its layer
/// change or it is explicitly cleared.
#[derive(Debug, Default)]
pub struct Cache {
state: RefCell<State>,
}
#[derive(Debug, Default)]
enum State {
#[default]
Empty,
Filled {
bounds: Size,
primitive: Internal,
},
}
#[derive(Debug, Clone)]
enum Internal {
TinySkia(Arc<iced_tiny_skia::Primitive>),
#[cfg(feature = "wgpu")]
Wgpu(Arc<iced_wgpu::Primitive>),
}
impl Cache {
/// Creates a new empty [`Cache`].
pub fn new() -> Self {
Cache {
state: RefCell::default(),
}
}
/// Clears the [`Cache`], forcing a redraw the next time it is used.
pub fn clear(&self) {
*self.state.borrow_mut() = State::Empty;
}
/// Draws [`Geometry`] using the provided closure and stores it in the
/// [`Cache`].
///
/// The closure will only be called when
/// - the bounds have changed since the previous draw call.
/// - the [`Cache`] is empty or has been explicitly cleared.
///
/// Otherwise, the previously stored [`Geometry`] will be returned. The
/// [`Cache`] is not cleared in this case. In other words, it will keep
/// returning the stored [`Geometry`] if needed.
pub fn draw(
&self,
renderer: &Renderer,
bounds: Size,
draw_fn: impl FnOnce(&mut Frame),
) -> Geometry {
use std::ops::Deref;
if let State::Filled {
bounds: cached_bounds,
primitive,
} = self.state.borrow().deref()
{
if *cached_bounds == bounds {
match primitive {
Internal::TinySkia(primitive) => {
return Geometry::TinySkia(
iced_tiny_skia::Primitive::Cache {
content: primitive.clone(),
},
);
}
#[cfg(feature = "wgpu")]
Internal::Wgpu(primitive) => {
return Geometry::Wgpu(iced_wgpu::Primitive::Cache {
content: primitive.clone(),
});
}
}
}
}
let mut frame = Frame::new(renderer, bounds);
draw_fn(&mut frame);
let primitive = {
let geometry = frame.into_geometry();
match geometry {
Geometry::TinySkia(primitive) => {
Internal::TinySkia(Arc::new(primitive))
}
#[cfg(feature = "wgpu")]
Geometry::Wgpu(primitive) => {
Internal::Wgpu(Arc::new(primitive))
}
}
};
*self.state.borrow_mut() = State::Filled {
bounds,
primitive: primitive.clone(),
};
match primitive {
Internal::TinySkia(primitive) => {
Geometry::TinySkia(iced_tiny_skia::Primitive::Cache {
content: primitive,
})
}
#[cfg(feature = "wgpu")]
Internal::Wgpu(primitive) => {
Geometry::Wgpu(iced_wgpu::Primitive::Cache {
content: primitive,
})
}
}
}
}

View file

@ -4,299 +4,51 @@
#[cfg(feature = "wgpu")]
pub use iced_wgpu as wgpu;
pub mod compositor;
#[cfg(feature = "geometry")]
pub mod geometry;
mod settings;
pub mod fallback;
pub use iced_graphics as graphics;
pub use iced_graphics::core;
pub use compositor::Compositor;
pub use settings::Settings;
#[cfg(feature = "geometry")]
pub use geometry::Geometry;
use crate::core::renderer;
use crate::core::text::{self, Text};
use crate::core::{
Background, Color, Font, Pixels, Point, Rectangle, Transformation,
};
use crate::graphics::text::Editor;
use crate::graphics::text::Paragraph;
use crate::graphics::Mesh;
use std::borrow::Cow;
pub use iced_graphics::geometry;
/// The default graphics renderer for [`iced`].
///
/// [`iced`]: https://github.com/iced-rs/iced
pub enum Renderer {
TinySkia(iced_tiny_skia::Renderer),
#[cfg(feature = "wgpu")]
Wgpu(iced_wgpu::Renderer),
pub type Renderer = renderer::Renderer;
/// The default graphics compositor for [`iced`].
///
/// [`iced`]: https://github.com/iced-rs/iced
pub type Compositor = renderer::Compositor;
#[cfg(all(feature = "wgpu", feature = "tiny-skia"))]
mod renderer {
pub type Renderer = crate::fallback::Renderer<
iced_wgpu::Renderer,
iced_tiny_skia::Renderer,
>;
pub type Compositor = crate::fallback::Compositor<
iced_wgpu::window::Compositor,
iced_tiny_skia::window::Compositor,
>;
}
macro_rules! delegate {
($renderer:expr, $name:ident, $body:expr) => {
match $renderer {
Self::TinySkia($name) => $body,
#[cfg(feature = "wgpu")]
Self::Wgpu($name) => $body,
}
};
#[cfg(all(feature = "wgpu", not(feature = "tiny-skia")))]
mod renderer {
pub type Renderer = iced_wgpu::Renderer;
pub type Compositor = iced_wgpu::window::Compositor;
}
impl Renderer {
pub fn draw_mesh(&mut self, mesh: Mesh) {
match self {
Self::TinySkia(_) => {
log::warn!("Unsupported mesh primitive: {mesh:?}");
}
#[cfg(feature = "wgpu")]
Self::Wgpu(renderer) => {
renderer.draw_primitive(iced_wgpu::Primitive::Custom(
iced_wgpu::primitive::Custom::Mesh(mesh),
));
}
}
}
#[cfg(all(not(feature = "wgpu"), feature = "tiny-skia"))]
mod renderer {
pub type Renderer = iced_tiny_skia::Renderer;
pub type Compositor = iced_tiny_skia::window::Compositor;
}
impl core::Renderer for Renderer {
fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) {
match self {
Self::TinySkia(renderer) => {
let primitives = renderer.start_layer();
f(self);
match self {
Self::TinySkia(renderer) => {
renderer.end_layer(primitives, bounds);
}
#[cfg(feature = "wgpu")]
_ => unreachable!(),
}
}
#[cfg(feature = "wgpu")]
Self::Wgpu(renderer) => {
let primitives = renderer.start_layer();
f(self);
match self {
#[cfg(feature = "wgpu")]
Self::Wgpu(renderer) => {
renderer.end_layer(primitives, bounds);
}
_ => unreachable!(),
}
}
}
}
fn with_transformation(
&mut self,
transformation: Transformation,
f: impl FnOnce(&mut Self),
) {
match self {
Self::TinySkia(renderer) => {
let primitives = renderer.start_transformation();
f(self);
match self {
Self::TinySkia(renderer) => {
renderer.end_transformation(primitives, transformation);
}
#[cfg(feature = "wgpu")]
_ => unreachable!(),
}
}
#[cfg(feature = "wgpu")]
Self::Wgpu(renderer) => {
let primitives = renderer.start_transformation();
f(self);
match self {
#[cfg(feature = "wgpu")]
Self::Wgpu(renderer) => {
renderer.end_transformation(primitives, transformation);
}
_ => unreachable!(),
}
}
}
}
fn fill_quad(
&mut self,
quad: renderer::Quad,
background: impl Into<Background>,
) {
delegate!(self, renderer, renderer.fill_quad(quad, background));
}
fn clear(&mut self) {
delegate!(self, renderer, renderer.clear());
}
}
impl text::Renderer for Renderer {
type Font = Font;
type Paragraph = Paragraph;
type Editor = Editor;
const ICON_FONT: Font = iced_tiny_skia::Renderer::ICON_FONT;
const CHECKMARK_ICON: char = iced_tiny_skia::Renderer::CHECKMARK_ICON;
const ARROW_DOWN_ICON: char = iced_tiny_skia::Renderer::ARROW_DOWN_ICON;
fn default_font(&self) -> Self::Font {
delegate!(self, renderer, renderer.default_font())
}
fn default_size(&self) -> Pixels {
delegate!(self, renderer, renderer.default_size())
}
fn load_font(&mut self, bytes: Cow<'static, [u8]>) {
delegate!(self, renderer, renderer.load_font(bytes));
}
fn fill_paragraph(
&mut self,
paragraph: &Self::Paragraph,
position: Point,
color: Color,
clip_bounds: Rectangle,
) {
delegate!(
self,
renderer,
renderer.fill_paragraph(paragraph, position, color, clip_bounds)
);
}
fn fill_editor(
&mut self,
editor: &Self::Editor,
position: Point,
color: Color,
clip_bounds: Rectangle,
) {
delegate!(
self,
renderer,
renderer.fill_editor(editor, position, color, clip_bounds)
);
}
fn fill_text(
&mut self,
text: Text<'_, Self::Font>,
position: Point,
color: Color,
clip_bounds: Rectangle,
) {
delegate!(
self,
renderer,
renderer.fill_text(text, position, color, clip_bounds)
);
}
}
#[cfg(feature = "image")]
impl crate::core::image::Renderer for Renderer {
type Handle = crate::core::image::Handle;
fn dimensions(
&self,
handle: &crate::core::image::Handle,
) -> core::Size<u32> {
delegate!(self, renderer, renderer.dimensions(handle))
}
fn draw(
&mut self,
handle: crate::core::image::Handle,
filter_method: crate::core::image::FilterMethod,
bounds: Rectangle,
) {
delegate!(self, renderer, renderer.draw(handle, filter_method, bounds));
}
}
#[cfg(feature = "svg")]
impl crate::core::svg::Renderer for Renderer {
fn dimensions(&self, handle: &crate::core::svg::Handle) -> core::Size<u32> {
delegate!(self, renderer, renderer.dimensions(handle))
}
fn draw(
&mut self,
handle: crate::core::svg::Handle,
color: Option<crate::core::Color>,
bounds: Rectangle,
) {
delegate!(self, renderer, renderer.draw(handle, color, bounds));
}
}
#[cfg(feature = "geometry")]
impl crate::graphics::geometry::Renderer for Renderer {
type Geometry = crate::Geometry;
fn draw(&mut self, layers: Vec<Self::Geometry>) {
match self {
Self::TinySkia(renderer) => {
for layer in layers {
match layer {
crate::Geometry::TinySkia(primitive) => {
renderer.draw_primitive(primitive);
}
#[cfg(feature = "wgpu")]
crate::Geometry::Wgpu(_) => unreachable!(),
}
}
}
#[cfg(feature = "wgpu")]
Self::Wgpu(renderer) => {
for layer in layers {
match layer {
crate::Geometry::Wgpu(primitive) => {
renderer.draw_primitive(primitive);
}
crate::Geometry::TinySkia(_) => unreachable!(),
}
}
}
}
}
}
#[cfg(feature = "wgpu")]
impl iced_wgpu::primitive::pipeline::Renderer for Renderer {
fn draw_pipeline_primitive(
&mut self,
bounds: Rectangle,
primitive: impl wgpu::primitive::pipeline::Primitive,
) {
match self {
Self::TinySkia(_renderer) => {
log::warn!(
"Custom shader primitive is unavailable with tiny-skia."
);
}
Self::Wgpu(renderer) => {
renderer.draw_pipeline_primitive(bounds, primitive);
}
}
}
#[cfg(not(any(feature = "wgpu", feature = "tiny-skia")))]
mod renderer {
pub type Renderer = ();
pub type Compositor = ();
}

View file

@ -2,7 +2,7 @@
use crate::Command;
use iced_core::text;
use iced_core::{Element, Renderer};
use iced_core::Element;
mod state;
@ -11,7 +11,7 @@ pub use state::State;
/// The core of a user interface application following The Elm Architecture.
pub trait Program: Sized {
/// The graphics backend to use to draw the [`Program`].
type Renderer: Renderer + text::Renderer;
type Renderer: text::Renderer;
/// The theme used to draw the [`Program`].
type Theme;

View file

@ -45,7 +45,7 @@ where
///
/// ```no_run
/// # mod iced_wgpu {
/// # pub use iced_runtime::core::renderer::Null as Renderer;
/// # pub type Renderer = ();
/// # }
/// #
/// # pub struct Counter;
@ -62,7 +62,7 @@ where
/// // Initialization
/// let mut counter = Counter::new();
/// let mut cache = user_interface::Cache::new();
/// let mut renderer = Renderer::new();
/// let mut renderer = Renderer::default();
/// let mut window_size = Size::new(1024.0, 768.0);
///
/// // Application loop
@ -121,7 +121,7 @@ where
///
/// ```no_run
/// # mod iced_wgpu {
/// # pub use iced_runtime::core::renderer::Null as Renderer;
/// # pub type Renderer = ();
/// # }
/// #
/// # pub struct Counter;
@ -139,7 +139,7 @@ where
///
/// let mut counter = Counter::new();
/// let mut cache = user_interface::Cache::new();
/// let mut renderer = Renderer::new();
/// let mut renderer = Renderer::default();
/// let mut window_size = Size::new(1024.0, 768.0);
/// let mut cursor = mouse::Cursor::default();
/// let mut clipboard = clipboard::Null;
@ -374,7 +374,7 @@ where
///
/// ```no_run
/// # mod iced_wgpu {
/// # pub use iced_runtime::core::renderer::Null as Renderer;
/// # pub type Renderer = ();
/// # pub type Theme = ();
/// # }
/// #
@ -394,7 +394,7 @@ where
///
/// let mut counter = Counter::new();
/// let mut cache = user_interface::Cache::new();
/// let mut renderer = Renderer::new();
/// let mut renderer = Renderer::default();
/// let mut window_size = Size::new(1024.0, 768.0);
/// let mut cursor = mouse::Cursor::default();
/// let mut clipboard = clipboard::Null;

View file

@ -1,4 +1,6 @@
//! Build interactive cross-platform applications.
use crate::core::text;
use crate::graphics::compositor;
use crate::shell::application;
use crate::{Command, Element, Executor, Settings, Subscription};
@ -60,7 +62,7 @@ pub use application::{Appearance, DefaultStyle};
/// ```no_run
/// use iced::advanced::Application;
/// use iced::executor;
/// use iced::{Command, Element, Settings, Theme};
/// use iced::{Command, Element, Settings, Theme, Renderer};
///
/// pub fn main() -> iced::Result {
/// Hello::run(Settings::default())
@ -73,6 +75,7 @@ pub use application::{Appearance, DefaultStyle};
/// type Flags = ();
/// type Message = ();
/// type Theme = Theme;
/// type Renderer = Renderer;
///
/// fn new(_flags: ()) -> (Hello, Command<Self::Message>) {
/// (Hello, Command::none())
@ -109,6 +112,9 @@ where
/// The theme of your [`Application`].
type Theme: Default;
/// The renderer of your [`Application`].
type Renderer: text::Renderer + compositor::Default;
/// The data needed to initialize your [`Application`].
type Flags;
@ -142,7 +148,7 @@ where
/// Returns the widgets to display in the [`Application`].
///
/// These widgets can produce __messages__ based on user interaction.
fn view(&self) -> Element<'_, Self::Message, Self::Theme, crate::Renderer>;
fn view(&self) -> Element<'_, Self::Message, Self::Theme, Self::Renderer>;
/// Returns the current [`Theme`] of the [`Application`].
///
@ -195,7 +201,7 @@ where
Self: 'static,
{
#[allow(clippy::needless_update)]
let renderer_settings = crate::renderer::Settings {
let renderer_settings = crate::graphics::Settings {
default_font: settings.default_font,
default_text_size: settings.default_text_size,
antialiasing: if settings.antialiasing {
@ -203,13 +209,13 @@ where
} else {
None
},
..crate::renderer::Settings::default()
..crate::graphics::Settings::default()
};
let run = crate::shell::application::run::<
Instance<Self>,
Self::Executor,
crate::renderer::Compositor,
<Self::Renderer as compositor::Default>::Compositor,
>(settings.into(), renderer_settings);
#[cfg(target_arch = "wasm32")]
@ -241,7 +247,7 @@ where
{
type Message = A::Message;
type Theme = A::Theme;
type Renderer = crate::Renderer;
type Renderer = A::Renderer;
fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
self.0.update(message)

View file

@ -372,15 +372,16 @@ pub type Result = std::result::Result<(), Error>;
/// ]
/// }
/// ```
pub fn run<State, Message, Theme>(
pub fn run<State, Message, Theme, Renderer>(
title: impl program::Title<State> + 'static,
update: impl program::Update<State, Message> + 'static,
view: impl for<'a> program::View<'a, State, Message, Theme> + 'static,
view: impl for<'a> program::View<'a, State, Message, Theme, Renderer> + 'static,
) -> Result
where
State: Default + 'static,
Message: std::fmt::Debug + Send + 'static,
Theme: Default + program::DefaultStyle + 'static,
Renderer: program::Renderer + 'static,
{
program(title, update, view).run()
}

View file

@ -174,7 +174,7 @@ where
Self: 'static,
{
#[allow(clippy::needless_update)]
let renderer_settings = crate::renderer::Settings {
let renderer_settings = crate::graphics::Settings {
default_font: settings.default_font,
default_text_size: settings.default_text_size,
antialiasing: if settings.antialiasing {
@ -182,7 +182,7 @@ where
} else {
None
},
..crate::renderer::Settings::default()
..crate::graphics::Settings::default()
};
Ok(crate::shell::multi_window::run::<

View file

@ -31,7 +31,9 @@
//! }
//! ```
use crate::application::Application;
use crate::core::text;
use crate::executor::{self, Executor};
use crate::graphics::compositor;
use crate::window;
use crate::{Command, Element, Font, Result, Settings, Size, Subscription};
@ -67,37 +69,41 @@ use std::borrow::Cow;
/// ]
/// }
/// ```
pub fn program<State, Message, Theme>(
pub fn program<State, Message, Theme, Renderer>(
title: impl Title<State>,
update: impl Update<State, Message>,
view: impl for<'a> self::View<'a, State, Message, Theme>,
view: impl for<'a> self::View<'a, State, Message, Theme, Renderer>,
) -> Program<impl Definition<State = State, Message = Message, Theme = Theme>>
where
State: 'static,
Message: Send + std::fmt::Debug,
Theme: Default + DefaultStyle,
Renderer: self::Renderer,
{
use std::marker::PhantomData;
struct Application<State, Message, Theme, Update, View> {
struct Application<State, Message, Theme, Renderer, Update, View> {
update: Update,
view: View,
_state: PhantomData<State>,
_message: PhantomData<Message>,
_theme: PhantomData<Theme>,
_renderer: PhantomData<Renderer>,
}
impl<State, Message, Theme, Update, View> Definition
for Application<State, Message, Theme, Update, View>
impl<State, Message, Theme, Renderer, Update, View> Definition
for Application<State, Message, Theme, Renderer, Update, View>
where
Message: Send + std::fmt::Debug,
Theme: Default + DefaultStyle,
Renderer: self::Renderer,
Update: self::Update<State, Message>,
View: for<'a> self::View<'a, State, Message, Theme>,
View: for<'a> self::View<'a, State, Message, Theme, Renderer>,
{
type State = State;
type Message = Message;
type Theme = Theme;
type Renderer = Renderer;
type Executor = executor::Default;
fn load(&self) -> Command<Self::Message> {
@ -115,7 +121,7 @@ where
fn view<'a>(
&self,
state: &'a Self::State,
) -> Element<'a, Self::Message, Self::Theme> {
) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> {
self.view.view(state).into()
}
}
@ -127,6 +133,7 @@ where
_state: PhantomData,
_message: PhantomData,
_theme: PhantomData,
_renderer: PhantomData,
},
settings: Settings::default(),
}
@ -184,6 +191,7 @@ impl<P: Definition> Program<P> {
impl<P: Definition, I: Fn() -> P::State> Application for Instance<P, I> {
type Message = P::Message;
type Theme = P::Theme;
type Renderer = P::Renderer;
type Flags = (P, I);
type Executor = P::Executor;
@ -216,7 +224,7 @@ impl<P: Definition> Program<P> {
fn view(
&self,
) -> crate::Element<'_, Self::Message, Self::Theme, crate::Renderer>
) -> crate::Element<'_, Self::Message, Self::Theme, Self::Renderer>
{
self.program.view(&self.state)
}
@ -417,6 +425,9 @@ pub trait Definition: Sized {
/// The theme of the program.
type Theme: Default + DefaultStyle;
/// The renderer of the program.
type Renderer: Renderer;
/// The executor of the program.
type Executor: Executor;
@ -431,7 +442,7 @@ pub trait Definition: Sized {
fn view<'a>(
&self,
state: &'a Self::State,
) -> Element<'a, Self::Message, Self::Theme>;
) -> Element<'a, Self::Message, Self::Theme, Self::Renderer>;
fn title(&self, _state: &Self::State) -> String {
String::from("A cool iced application!")
@ -470,6 +481,7 @@ fn with_title<P: Definition>(
type State = P::State;
type Message = P::Message;
type Theme = P::Theme;
type Renderer = P::Renderer;
type Executor = P::Executor;
fn load(&self) -> Command<Self::Message> {
@ -491,7 +503,7 @@ fn with_title<P: Definition>(
fn view<'a>(
&self,
state: &'a Self::State,
) -> Element<'a, Self::Message, Self::Theme> {
) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> {
self.program.view(state)
}
@ -534,6 +546,7 @@ fn with_load<P: Definition>(
type State = P::State;
type Message = P::Message;
type Theme = P::Theme;
type Renderer = P::Renderer;
type Executor = executor::Default;
fn load(&self) -> Command<Self::Message> {
@ -551,7 +564,7 @@ fn with_load<P: Definition>(
fn view<'a>(
&self,
state: &'a Self::State,
) -> Element<'a, Self::Message, Self::Theme> {
) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> {
self.program.view(state)
}
@ -598,6 +611,7 @@ fn with_subscription<P: Definition>(
type State = P::State;
type Message = P::Message;
type Theme = P::Theme;
type Renderer = P::Renderer;
type Executor = executor::Default;
fn subscription(
@ -622,7 +636,7 @@ fn with_subscription<P: Definition>(
fn view<'a>(
&self,
state: &'a Self::State,
) -> Element<'a, Self::Message, Self::Theme> {
) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> {
self.program.view(state)
}
@ -665,6 +679,7 @@ fn with_theme<P: Definition>(
type State = P::State;
type Message = P::Message;
type Theme = P::Theme;
type Renderer = P::Renderer;
type Executor = P::Executor;
fn theme(&self, state: &Self::State) -> Self::Theme {
@ -690,7 +705,7 @@ fn with_theme<P: Definition>(
fn view<'a>(
&self,
state: &'a Self::State,
) -> Element<'a, Self::Message, Self::Theme> {
) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> {
self.program.view(state)
}
@ -729,6 +744,7 @@ fn with_style<P: Definition>(
type State = P::State;
type Message = P::Message;
type Theme = P::Theme;
type Renderer = P::Renderer;
type Executor = P::Executor;
fn style(
@ -758,7 +774,7 @@ fn with_style<P: Definition>(
fn view<'a>(
&self,
state: &'a Self::State,
) -> Element<'a, Self::Message, Self::Theme> {
) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> {
self.program.view(state)
}
@ -834,18 +850,30 @@ where
///
/// This trait allows the [`program`] builder to take any closure that
/// returns any `Into<Element<'_, Message>>`.
pub trait View<'a, State, Message, Theme> {
pub trait View<'a, State, Message, Theme, Renderer> {
/// Produces the widget of the [`Program`].
fn view(&self, state: &'a State) -> impl Into<Element<'a, Message, Theme>>;
fn view(
&self,
state: &'a State,
) -> impl Into<Element<'a, Message, Theme, Renderer>>;
}
impl<'a, T, State, Message, Theme, Widget> View<'a, State, Message, Theme> for T
impl<'a, T, State, Message, Theme, Renderer, Widget>
View<'a, State, Message, Theme, Renderer> for T
where
T: Fn(&'a State) -> Widget,
State: 'static,
Widget: Into<Element<'a, Message, Theme>>,
Widget: Into<Element<'a, Message, Theme, Renderer>>,
{
fn view(&self, state: &'a State) -> impl Into<Element<'a, Message, Theme>> {
fn view(
&self,
state: &'a State,
) -> impl Into<Element<'a, Message, Theme, Renderer>> {
self(state)
}
}
/// The renderer of some [`Program`].
pub trait Renderer: text::Renderer + compositor::Default {}
impl<T> Renderer for T where T: text::Renderer + compositor::Default {}

View file

@ -5,6 +5,7 @@ use crate::graphics::backend;
use crate::graphics::text;
use crate::graphics::{Damage, Viewport};
use crate::primitive::{self, Primitive};
use crate::window;
use std::borrow::Cow;
@ -989,8 +990,9 @@ fn rounded_box_sdf(
(x.powf(2.0) + y.powf(2.0)).sqrt() - radius
}
impl iced_graphics::Backend for Backend {
impl backend::Backend for Backend {
type Primitive = primitive::Custom;
type Compositor = window::Compositor;
}
impl backend::Text for Backend {
@ -1018,3 +1020,12 @@ impl backend::Svg for Backend {
self.vector_pipeline.viewport_dimensions(handle)
}
}
#[cfg(feature = "geometry")]
impl crate::graphics::geometry::Backend for Backend {
type Frame = crate::geometry::Frame;
fn new_frame(&self, size: Size) -> Self::Frame {
crate::geometry::Frame::new(size)
}
}

View file

@ -4,7 +4,7 @@ use crate::core::{
};
use crate::graphics::geometry::fill::{self, Fill};
use crate::graphics::geometry::stroke::{self, Stroke};
use crate::graphics::geometry::{Path, Style, Text};
use crate::graphics::geometry::{self, Path, Style, Text};
use crate::graphics::Gradient;
use crate::primitive::{self, Primitive};
@ -25,23 +25,36 @@ impl Frame {
}
}
pub fn width(&self) -> f32 {
pub fn into_primitive(self) -> Primitive {
Primitive::Clip {
bounds: Rectangle::new(Point::ORIGIN, self.size),
content: Box::new(Primitive::Group {
primitives: self.primitives,
}),
}
}
}
impl geometry::frame::Backend for Frame {
type Geometry = Primitive;
fn width(&self) -> f32 {
self.size.width
}
pub fn height(&self) -> f32 {
fn height(&self) -> f32 {
self.size.height
}
pub fn size(&self) -> Size {
fn size(&self) -> Size {
self.size
}
pub fn center(&self) -> Point {
fn center(&self) -> Point {
Point::new(self.size.width / 2.0, self.size.height / 2.0)
}
pub fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
let Some(path) =
convert_path(path).and_then(|path| path.transform(self.transform))
else {
@ -61,7 +74,7 @@ impl Frame {
}));
}
pub fn fill_rectangle(
fn fill_rectangle(
&mut self,
top_left: Point,
size: Size,
@ -89,7 +102,7 @@ impl Frame {
}));
}
pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) {
fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) {
let Some(path) =
convert_path(path).and_then(|path| path.transform(self.transform))
else {
@ -110,7 +123,7 @@ impl Frame {
}));
}
pub fn fill_text(&mut self, text: impl Into<Text>) {
fn fill_text(&mut self, text: impl Into<Text>) {
let text = text.into();
let (scale_x, scale_y) = self.transform.get_scale();
@ -174,51 +187,50 @@ impl Frame {
}
}
pub fn push_transform(&mut self) {
fn push_transform(&mut self) {
self.stack.push(self.transform);
}
pub fn pop_transform(&mut self) {
fn pop_transform(&mut self) {
self.transform = self.stack.pop().expect("Pop transform");
}
pub fn clip(&mut self, frame: Self, at: Point) {
fn draft(&mut self, size: Size) -> Self {
Self::new(size)
}
fn paste(&mut self, frame: Self, at: Point) {
self.primitives.push(Primitive::Transform {
transformation: Transformation::translate(at.x, at.y),
content: Box::new(frame.into_primitive()),
});
}
pub fn translate(&mut self, translation: Vector) {
fn translate(&mut self, translation: Vector) {
self.transform =
self.transform.pre_translate(translation.x, translation.y);
}
pub fn rotate(&mut self, angle: impl Into<Radians>) {
fn rotate(&mut self, angle: impl Into<Radians>) {
self.transform = self.transform.pre_concat(
tiny_skia::Transform::from_rotate(angle.into().0.to_degrees()),
);
}
pub fn scale(&mut self, scale: impl Into<f32>) {
fn scale(&mut self, scale: impl Into<f32>) {
let scale = scale.into();
self.scale_nonuniform(Vector { x: scale, y: scale });
}
pub fn scale_nonuniform(&mut self, scale: impl Into<Vector>) {
fn scale_nonuniform(&mut self, scale: impl Into<Vector>) {
let scale = scale.into();
self.transform = self.transform.pre_scale(scale.x, scale.y);
}
pub fn into_primitive(self) -> Primitive {
Primitive::Clip {
bounds: Rectangle::new(Point::ORIGIN, self.size),
content: Box::new(Primitive::Group {
primitives: self.primitives,
}),
}
fn into_geometry(self) -> Self::Geometry {
self.into_primitive()
}
}

View file

@ -1,5 +1,5 @@
use crate::core::Rectangle;
use crate::graphics::Damage;
use crate::graphics::{Damage, Mesh};
pub type Primitive = crate::graphics::Primitive<Custom>;
@ -42,3 +42,11 @@ impl Damage for Custom {
}
}
}
impl TryFrom<Mesh> for Custom {
type Error = &'static str;
fn try_from(_mesh: Mesh) -> Result<Self, Self::Error> {
Err("unsupported")
}
}

View file

@ -1,4 +1,5 @@
use crate::core::{Font, Pixels};
use crate::graphics;
/// The settings of a [`Backend`].
///
@ -22,3 +23,12 @@ impl Default for Settings {
}
}
}
impl From<graphics::Settings> for Settings {
fn from(settings: graphics::Settings) -> Self {
Self {
default_font: settings.default_font,
default_text_size: settings.default_text_size,
}
}
}

View file

@ -1,11 +1,11 @@
use crate::core::{Color, Rectangle, Size};
use crate::graphics::compositor::{self, Information};
use crate::graphics::damage;
use crate::graphics::{Error, Viewport};
use crate::graphics::error::{self, Error};
use crate::graphics::{self, Viewport};
use crate::{Backend, Primitive, Renderer, Settings};
use std::collections::VecDeque;
use std::future::{self, Future};
use std::num::NonZeroU32;
pub struct Compositor {
@ -25,15 +25,25 @@ pub struct Surface {
}
impl crate::graphics::Compositor for Compositor {
type Settings = Settings;
type Renderer = Renderer;
type Surface = Surface;
fn new<W: compositor::Window>(
settings: Self::Settings,
async fn with_backend<W: compositor::Window>(
settings: graphics::Settings,
compatible_window: W,
) -> impl Future<Output = Result<Self, Error>> {
future::ready(Ok(new(settings, compatible_window)))
backend: Option<&str>,
) -> Result<Self, Error> {
match backend {
None | Some("tiny-skia") | Some("tiny_skia") => {
Ok(new(settings.into(), compatible_window))
}
Some(backend) => Err(Error::GraphicsAdapterNotFound {
backend: "tiny-skia",
reason: error::Reason::DidNotMatch {
preferred_backend: backend.to_owned(),
},
}),
}
}
fn create_renderer(&self) -> Self::Renderer {

View file

@ -32,6 +32,7 @@ glyphon.workspace = true
guillotiere.workspace = true
log.workspace = true
once_cell.workspace = true
thiserror.workspace = true
wgpu.workspace = true
lyon.workspace = true

View file

@ -7,6 +7,7 @@ use crate::primitive::{self, Primitive};
use crate::quad;
use crate::text;
use crate::triangle;
use crate::window;
use crate::{Layer, Settings};
#[cfg(feature = "tracing")]
@ -371,8 +372,9 @@ impl Backend {
}
}
impl crate::graphics::Backend for Backend {
impl backend::Backend for Backend {
type Primitive = primitive::Custom;
type Compositor = window::Compositor;
}
impl backend::Text for Backend {
@ -397,3 +399,12 @@ impl backend::Svg for Backend {
self.image_pipeline.viewport_dimensions(handle)
}
}
#[cfg(feature = "geometry")]
impl crate::graphics::geometry::Backend for Backend {
type Frame = crate::geometry::Frame;
fn new_frame(&self, size: Size) -> Self::Frame {
crate::geometry::Frame::new(size)
}
}

View file

@ -6,7 +6,7 @@ use crate::core::{
use crate::graphics::color;
use crate::graphics::geometry::fill::{self, Fill};
use crate::graphics::geometry::{
LineCap, LineDash, LineJoin, Path, Stroke, Style, Text,
self, LineCap, LineDash, LineJoin, Path, Stroke, Style, Text,
};
use crate::graphics::gradient::{self, Gradient};
use crate::graphics::mesh::{self, Mesh};
@ -14,6 +14,7 @@ use crate::primitive::{self, Primitive};
use lyon::geom::euclid;
use lyon::tessellation;
use std::borrow::Cow;
/// A frame for drawing some geometry.
@ -27,6 +28,325 @@ pub struct Frame {
stroke_tessellator: tessellation::StrokeTessellator,
}
impl Frame {
/// Creates a new [`Frame`] with the given [`Size`].
pub fn new(size: Size) -> Frame {
Frame {
size,
buffers: BufferStack::new(),
primitives: Vec::new(),
transforms: Transforms {
previous: Vec::new(),
current: Transform(lyon::math::Transform::identity()),
},
fill_tessellator: tessellation::FillTessellator::new(),
stroke_tessellator: tessellation::StrokeTessellator::new(),
}
}
fn into_primitives(mut self) -> Vec<Primitive> {
for buffer in self.buffers.stack {
match buffer {
Buffer::Solid(buffer) => {
if !buffer.indices.is_empty() {
self.primitives.push(Primitive::Custom(
primitive::Custom::Mesh(Mesh::Solid {
buffers: mesh::Indexed {
vertices: buffer.vertices,
indices: buffer.indices,
},
size: self.size,
}),
));
}
}
Buffer::Gradient(buffer) => {
if !buffer.indices.is_empty() {
self.primitives.push(Primitive::Custom(
primitive::Custom::Mesh(Mesh::Gradient {
buffers: mesh::Indexed {
vertices: buffer.vertices,
indices: buffer.indices,
},
size: self.size,
}),
));
}
}
}
}
self.primitives
}
}
impl geometry::frame::Backend for Frame {
type Geometry = Primitive;
/// Creates a new empty [`Frame`] with the given dimensions.
///
/// The default coordinate system of a [`Frame`] has its origin at the
/// top-left corner of its bounds.
#[inline]
fn width(&self) -> f32 {
self.size.width
}
#[inline]
fn height(&self) -> f32 {
self.size.height
}
#[inline]
fn size(&self) -> Size {
self.size
}
#[inline]
fn center(&self) -> Point {
Point::new(self.size.width / 2.0, self.size.height / 2.0)
}
fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
let Fill { style, rule } = fill.into();
let mut buffer = self
.buffers
.get_fill(&self.transforms.current.transform_style(style));
let options = tessellation::FillOptions::default()
.with_fill_rule(into_fill_rule(rule));
if self.transforms.current.is_identity() {
self.fill_tessellator.tessellate_path(
path.raw(),
&options,
buffer.as_mut(),
)
} else {
let path = path.transform(&self.transforms.current.0);
self.fill_tessellator.tessellate_path(
path.raw(),
&options,
buffer.as_mut(),
)
}
.expect("Tessellate path.");
}
fn fill_rectangle(
&mut self,
top_left: Point,
size: Size,
fill: impl Into<Fill>,
) {
let Fill { style, rule } = fill.into();
let mut buffer = self
.buffers
.get_fill(&self.transforms.current.transform_style(style));
let top_left = self
.transforms
.current
.0
.transform_point(lyon::math::Point::new(top_left.x, top_left.y));
let size =
self.transforms.current.0.transform_vector(
lyon::math::Vector::new(size.width, size.height),
);
let options = tessellation::FillOptions::default()
.with_fill_rule(into_fill_rule(rule));
self.fill_tessellator
.tessellate_rectangle(
&lyon::math::Box2D::new(top_left, top_left + size),
&options,
buffer.as_mut(),
)
.expect("Fill rectangle");
}
fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) {
let stroke = stroke.into();
let mut buffer = self
.buffers
.get_stroke(&self.transforms.current.transform_style(stroke.style));
let mut options = tessellation::StrokeOptions::default();
options.line_width = stroke.width;
options.start_cap = into_line_cap(stroke.line_cap);
options.end_cap = into_line_cap(stroke.line_cap);
options.line_join = into_line_join(stroke.line_join);
let path = if stroke.line_dash.segments.is_empty() {
Cow::Borrowed(path)
} else {
Cow::Owned(dashed(path, stroke.line_dash))
};
if self.transforms.current.is_identity() {
self.stroke_tessellator.tessellate_path(
path.raw(),
&options,
buffer.as_mut(),
)
} else {
let path = path.transform(&self.transforms.current.0);
self.stroke_tessellator.tessellate_path(
path.raw(),
&options,
buffer.as_mut(),
)
}
.expect("Stroke path");
}
fn fill_text(&mut self, text: impl Into<Text>) {
let text = text.into();
let (scale_x, scale_y) = self.transforms.current.scale();
if self.transforms.current.is_scale_translation()
&& scale_x == scale_y
&& scale_x > 0.0
&& scale_y > 0.0
{
let (position, size, line_height) =
if self.transforms.current.is_identity() {
(text.position, text.size, text.line_height)
} else {
let position =
self.transforms.current.transform_point(text.position);
let size = Pixels(text.size.0 * scale_y);
let line_height = match text.line_height {
LineHeight::Absolute(size) => {
LineHeight::Absolute(Pixels(size.0 * scale_y))
}
LineHeight::Relative(factor) => {
LineHeight::Relative(factor)
}
};
(position, size, line_height)
};
let bounds = Rectangle {
x: position.x,
y: position.y,
width: f32::INFINITY,
height: f32::INFINITY,
};
// TODO: Honor layering!
self.primitives.push(Primitive::Text {
content: text.content,
bounds,
color: text.color,
size,
line_height,
font: text.font,
horizontal_alignment: text.horizontal_alignment,
vertical_alignment: text.vertical_alignment,
shaping: text.shaping,
clip_bounds: Rectangle::with_size(Size::INFINITY),
});
} else {
text.draw_with(|path, color| self.fill(&path, color));
}
}
#[inline]
fn translate(&mut self, translation: Vector) {
self.transforms.current.0 =
self.transforms
.current
.0
.pre_translate(lyon::math::Vector::new(
translation.x,
translation.y,
));
}
#[inline]
fn rotate(&mut self, angle: impl Into<Radians>) {
self.transforms.current.0 = self
.transforms
.current
.0
.pre_rotate(lyon::math::Angle::radians(angle.into().0));
}
#[inline]
fn scale(&mut self, scale: impl Into<f32>) {
let scale = scale.into();
self.scale_nonuniform(Vector { x: scale, y: scale });
}
#[inline]
fn scale_nonuniform(&mut self, scale: impl Into<Vector>) {
let scale = scale.into();
self.transforms.current.0 =
self.transforms.current.0.pre_scale(scale.x, scale.y);
}
fn push_transform(&mut self) {
self.transforms.previous.push(self.transforms.current);
}
fn pop_transform(&mut self) {
self.transforms.current = self.transforms.previous.pop().unwrap();
}
fn draft(&mut self, size: Size) -> Frame {
Frame::new(size)
}
fn paste(&mut self, frame: Frame, at: Point) {
let size = frame.size();
let primitives = frame.into_primitives();
let transformation = Transformation::translate(at.x, at.y);
let (text, meshes) = primitives
.into_iter()
.partition(|primitive| matches!(primitive, Primitive::Text { .. }));
self.primitives.push(Primitive::Group {
primitives: vec![
Primitive::Transform {
transformation,
content: Box::new(Primitive::Group { primitives: meshes }),
},
Primitive::Transform {
transformation,
content: Box::new(Primitive::Clip {
bounds: Rectangle::with_size(size),
content: Box::new(Primitive::Group {
primitives: text,
}),
}),
},
],
});
}
fn into_geometry(self) -> Self::Geometry {
Primitive::Group {
primitives: self.into_primitives(),
}
}
}
enum Buffer {
Solid(tessellation::VertexBuffers<mesh::SolidVertex2D, u32>),
Gradient(tessellation::VertexBuffers<mesh::GradientVertex2D, u32>),
@ -165,386 +485,6 @@ impl Transform {
gradient
}
}
impl Frame {
/// Creates a new empty [`Frame`] with the given dimensions.
///
/// The default coordinate system of a [`Frame`] has its origin at the
/// top-left corner of its bounds.
pub fn new(size: Size) -> Frame {
Frame {
size,
buffers: BufferStack::new(),
primitives: Vec::new(),
transforms: Transforms {
previous: Vec::new(),
current: Transform(lyon::math::Transform::identity()),
},
fill_tessellator: tessellation::FillTessellator::new(),
stroke_tessellator: tessellation::StrokeTessellator::new(),
}
}
/// Returns the width of the [`Frame`].
#[inline]
pub fn width(&self) -> f32 {
self.size.width
}
/// Returns the height of the [`Frame`].
#[inline]
pub fn height(&self) -> f32 {
self.size.height
}
/// Returns the dimensions of the [`Frame`].
#[inline]
pub fn size(&self) -> Size {
self.size
}
/// Returns the coordinate of the center of the [`Frame`].
#[inline]
pub fn center(&self) -> Point {
Point::new(self.size.width / 2.0, self.size.height / 2.0)
}
/// Draws the given [`Path`] on the [`Frame`] by filling it with the
/// provided style.
pub fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
let Fill { style, rule } = fill.into();
let mut buffer = self
.buffers
.get_fill(&self.transforms.current.transform_style(style));
let options = tessellation::FillOptions::default()
.with_fill_rule(into_fill_rule(rule));
if self.transforms.current.is_identity() {
self.fill_tessellator.tessellate_path(
path.raw(),
&options,
buffer.as_mut(),
)
} else {
let path = path.transform(&self.transforms.current.0);
self.fill_tessellator.tessellate_path(
path.raw(),
&options,
buffer.as_mut(),
)
}
.expect("Tessellate path.");
}
/// Draws an axis-aligned rectangle given its top-left corner coordinate and
/// its `Size` on the [`Frame`] by filling it with the provided style.
pub fn fill_rectangle(
&mut self,
top_left: Point,
size: Size,
fill: impl Into<Fill>,
) {
let Fill { style, rule } = fill.into();
let mut buffer = self
.buffers
.get_fill(&self.transforms.current.transform_style(style));
let top_left = self
.transforms
.current
.0
.transform_point(lyon::math::Point::new(top_left.x, top_left.y));
let size =
self.transforms.current.0.transform_vector(
lyon::math::Vector::new(size.width, size.height),
);
let options = tessellation::FillOptions::default()
.with_fill_rule(into_fill_rule(rule));
self.fill_tessellator
.tessellate_rectangle(
&lyon::math::Box2D::new(top_left, top_left + size),
&options,
buffer.as_mut(),
)
.expect("Fill rectangle");
}
/// Draws the stroke of the given [`Path`] on the [`Frame`] with the
/// provided style.
pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) {
let stroke = stroke.into();
let mut buffer = self
.buffers
.get_stroke(&self.transforms.current.transform_style(stroke.style));
let mut options = tessellation::StrokeOptions::default();
options.line_width = stroke.width;
options.start_cap = into_line_cap(stroke.line_cap);
options.end_cap = into_line_cap(stroke.line_cap);
options.line_join = into_line_join(stroke.line_join);
let path = if stroke.line_dash.segments.is_empty() {
Cow::Borrowed(path)
} else {
Cow::Owned(dashed(path, stroke.line_dash))
};
if self.transforms.current.is_identity() {
self.stroke_tessellator.tessellate_path(
path.raw(),
&options,
buffer.as_mut(),
)
} else {
let path = path.transform(&self.transforms.current.0);
self.stroke_tessellator.tessellate_path(
path.raw(),
&options,
buffer.as_mut(),
)
}
.expect("Stroke path");
}
/// Draws the characters of the given [`Text`] on the [`Frame`], filling
/// them with the given color.
///
/// __Warning:__ Text currently does not work well with rotations and scale
/// transforms! The position will be correctly transformed, but the
/// resulting glyphs will not be rotated or scaled properly.
///
/// Additionally, all text will be rendered on top of all the layers of
/// a `Canvas`. Therefore, it is currently only meant to be used for
/// overlays, which is the most common use case.
///
/// Support for vectorial text is planned, and should address all these
/// limitations.
pub fn fill_text(&mut self, text: impl Into<Text>) {
let text = text.into();
let (scale_x, scale_y) = self.transforms.current.scale();
if self.transforms.current.is_scale_translation()
&& scale_x == scale_y
&& scale_x > 0.0
&& scale_y > 0.0
{
let (position, size, line_height) =
if self.transforms.current.is_identity() {
(text.position, text.size, text.line_height)
} else {
let position =
self.transforms.current.transform_point(text.position);
let size = Pixels(text.size.0 * scale_y);
let line_height = match text.line_height {
LineHeight::Absolute(size) => {
LineHeight::Absolute(Pixels(size.0 * scale_y))
}
LineHeight::Relative(factor) => {
LineHeight::Relative(factor)
}
};
(position, size, line_height)
};
let bounds = Rectangle {
x: position.x,
y: position.y,
width: f32::INFINITY,
height: f32::INFINITY,
};
// TODO: Honor layering!
self.primitives.push(Primitive::Text {
content: text.content,
bounds,
color: text.color,
size,
line_height,
font: text.font,
horizontal_alignment: text.horizontal_alignment,
vertical_alignment: text.vertical_alignment,
shaping: text.shaping,
clip_bounds: Rectangle::with_size(Size::INFINITY),
});
} else {
text.draw_with(|path, color| self.fill(&path, color));
}
}
/// Stores the current transform of the [`Frame`] and executes the given
/// drawing operations, restoring the transform afterwards.
///
/// This method is useful to compose transforms and perform drawing
/// operations in different coordinate systems.
#[inline]
pub fn with_save<R>(&mut self, f: impl FnOnce(&mut Frame) -> R) -> R {
self.push_transform();
let result = f(self);
self.pop_transform();
result
}
/// Pushes the current transform in the transform stack.
pub fn push_transform(&mut self) {
self.transforms.previous.push(self.transforms.current);
}
/// Pops a transform from the transform stack and sets it as the current transform.
pub fn pop_transform(&mut self) {
self.transforms.current = self.transforms.previous.pop().unwrap();
}
/// Executes the given drawing operations within a [`Rectangle`] region,
/// clipping any geometry that overflows its bounds. Any transformations
/// performed are local to the provided closure.
///
/// This method is useful to perform drawing operations that need to be
/// clipped.
#[inline]
pub fn with_clip<R>(
&mut self,
region: Rectangle,
f: impl FnOnce(&mut Frame) -> R,
) -> R {
let mut frame = Frame::new(region.size());
let result = f(&mut frame);
let origin = Point::new(region.x, region.y);
self.clip(frame, origin);
result
}
/// Draws the clipped contents of the given [`Frame`] with origin at the given [`Point`].
pub fn clip(&mut self, frame: Frame, at: Point) {
let size = frame.size();
let primitives = frame.into_primitives();
let transformation = Transformation::translate(at.x, at.y);
let (text, meshes) = primitives
.into_iter()
.partition(|primitive| matches!(primitive, Primitive::Text { .. }));
self.primitives.push(Primitive::Group {
primitives: vec![
Primitive::Transform {
transformation,
content: Box::new(Primitive::Group { primitives: meshes }),
},
Primitive::Transform {
transformation,
content: Box::new(Primitive::Clip {
bounds: Rectangle::with_size(size),
content: Box::new(Primitive::Group {
primitives: text,
}),
}),
},
],
});
}
/// Applies a translation to the current transform of the [`Frame`].
#[inline]
pub fn translate(&mut self, translation: Vector) {
self.transforms.current.0 =
self.transforms
.current
.0
.pre_translate(lyon::math::Vector::new(
translation.x,
translation.y,
));
}
/// Applies a rotation in radians to the current transform of the [`Frame`].
#[inline]
pub fn rotate(&mut self, angle: impl Into<Radians>) {
self.transforms.current.0 = self
.transforms
.current
.0
.pre_rotate(lyon::math::Angle::radians(angle.into().0));
}
/// Applies a uniform scaling to the current transform of the [`Frame`].
#[inline]
pub fn scale(&mut self, scale: impl Into<f32>) {
let scale = scale.into();
self.scale_nonuniform(Vector { x: scale, y: scale });
}
/// Applies a non-uniform scaling to the current transform of the [`Frame`].
#[inline]
pub fn scale_nonuniform(&mut self, scale: impl Into<Vector>) {
let scale = scale.into();
self.transforms.current.0 =
self.transforms.current.0.pre_scale(scale.x, scale.y);
}
/// Produces the [`Primitive`] representing everything drawn on the [`Frame`].
pub fn into_primitive(self) -> Primitive {
Primitive::Group {
primitives: self.into_primitives(),
}
}
fn into_primitives(mut self) -> Vec<Primitive> {
for buffer in self.buffers.stack {
match buffer {
Buffer::Solid(buffer) => {
if !buffer.indices.is_empty() {
self.primitives.push(Primitive::Custom(
primitive::Custom::Mesh(Mesh::Solid {
buffers: mesh::Indexed {
vertices: buffer.vertices,
indices: buffer.indices,
},
size: self.size,
}),
));
}
}
Buffer::Gradient(buffer) => {
if !buffer.indices.is_empty() {
self.primitives.push(Primitive::Custom(
primitive::Custom::Mesh(Mesh::Gradient {
buffers: mesh::Indexed {
vertices: buffer.vertices,
indices: buffer.indices,
},
size: self.size,
}),
));
}
}
}
}
self.primitives
}
}
struct GradientVertex2DBuilder {
gradient: gradient::Packed,
}

View file

@ -28,3 +28,11 @@ impl Damage for Custom {
}
}
}
impl TryFrom<Mesh> for Custom {
type Error = &'static str;
fn try_from(mesh: Mesh) -> Result<Self, Self::Error> {
Ok(Custom::Mesh(mesh))
}
}

View file

@ -1,5 +1,5 @@
//! Draw primitives using custom pipelines.
use crate::core::{Rectangle, Size};
use crate::core::{self, Rectangle, Size};
use std::any::{Any, TypeId};
use std::collections::HashMap;
@ -58,7 +58,7 @@ pub trait Primitive: Debug + Send + Sync + 'static {
}
/// A renderer than can draw custom pipeline primitives.
pub trait Renderer: crate::core::Renderer {
pub trait Renderer: core::Renderer {
/// Draws a custom pipeline primitive.
fn draw_pipeline_primitive(
&mut self,

View file

@ -1,6 +1,6 @@
//! Configure a renderer.
use crate::core::{Font, Pixels};
use crate::graphics::Antialiasing;
use crate::graphics::{self, Antialiasing};
/// The settings of a [`Backend`].
///
@ -29,30 +29,6 @@ pub struct Settings {
pub antialiasing: Option<Antialiasing>,
}
impl Settings {
/// Creates new [`Settings`] using environment configuration.
///
/// Specifically:
///
/// - The `internal_backend` can be configured using the `WGPU_BACKEND`
/// environment variable. If the variable is not set, the primary backend
/// will be used. The following values are allowed:
/// - `vulkan`
/// - `metal`
/// - `dx12`
/// - `dx11`
/// - `gl`
/// - `webgpu`
/// - `primary`
pub fn from_env() -> Self {
Settings {
internal_backend: wgpu::util::backend_bits_from_env()
.unwrap_or(wgpu::Backends::all()),
..Self::default()
}
}
}
impl Default for Settings {
fn default() -> Settings {
Settings {
@ -64,3 +40,14 @@ impl Default for Settings {
}
}
}
impl From<graphics::Settings> for Settings {
fn from(settings: graphics::Settings) -> Self {
Self {
default_font: settings.default_font,
default_text_size: settings.default_text_size,
antialiasing: settings.antialiasing,
..Settings::default()
}
}
}

View file

@ -1,13 +1,11 @@
//! Connect a window with a renderer.
use crate::core::{Color, Size};
use crate::graphics;
use crate::graphics::color;
use crate::graphics::compositor;
use crate::graphics::{Error, Viewport};
use crate::graphics::error;
use crate::graphics::{self, Viewport};
use crate::{Backend, Primitive, Renderer, Settings};
use std::future::Future;
/// A window graphics backend for iced powered by `wgpu`.
#[allow(missing_debug_implementations)]
pub struct Compositor {
@ -20,6 +18,32 @@ pub struct Compositor {
alpha_mode: wgpu::CompositeAlphaMode,
}
/// A compositor error.
#[derive(Debug, Clone, thiserror::Error)]
pub enum Error {
/// The surface creation failed.
#[error("the surface creation failed: {0}")]
SurfaceCreationFailed(#[from] wgpu::CreateSurfaceError),
/// The surface is not compatible.
#[error("the surface is not compatible")]
IncompatibleSurface,
/// No adapter was found for the options requested.
#[error("no adapter was found for the options requested: {0:?}")]
NoAdapterFound(String),
/// No device request succeeded.
#[error("no device request succeeded: {0:?}")]
RequestDeviceFailed(Vec<(wgpu::Limits, wgpu::RequestDeviceError)>),
}
impl From<Error> for graphics::Error {
fn from(error: Error) -> Self {
Self::GraphicsAdapterNotFound {
backend: "wgpu",
reason: error::Reason::RequestFailed(error.to_string()),
}
}
}
impl Compositor {
/// Requests a new [`Compositor`] with the given [`Settings`].
///
@ -27,7 +51,7 @@ impl Compositor {
pub async fn request<W: compositor::Window>(
settings: Settings,
compatible_window: Option<W>,
) -> Option<Self> {
) -> Result<Self, Error> {
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: settings.internal_backend,
..Default::default()
@ -49,8 +73,7 @@ impl Compositor {
let compatible_surface = compatible_window
.and_then(|window| instance.create_surface(window).ok());
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
let adapter_options = wgpu::RequestAdapterOptions {
power_preference: wgpu::util::power_preference_from_env()
.unwrap_or(if settings.antialiasing.is_none() {
wgpu::PowerPreference::LowPower
@ -59,13 +82,18 @@ impl Compositor {
}),
compatible_surface: compatible_surface.as_ref(),
force_fallback_adapter: false,
})
.await?;
};
let adapter = instance
.request_adapter(&adapter_options)
.await
.ok_or(Error::NoAdapterFound(format!("{:?}", adapter_options)))?;
log::info!("Selected: {:#?}", adapter.get_info());
let (format, alpha_mode) =
compatible_surface.as_ref().and_then(|surface| {
let (format, alpha_mode) = compatible_surface
.as_ref()
.and_then(|surface| {
let capabilities = surface.get_capabilities(&adapter);
let mut formats = capabilities.formats.iter().copied();
@ -101,7 +129,8 @@ impl Compositor {
};
format.zip(Some(preferred_alpha))
})?;
})
.ok_or(Error::IncompatibleSurface)?;
log::info!(
"Selected format: {format:?} with alpha mode: {alpha_mode:?}"
@ -115,31 +144,30 @@ impl Compositor {
let limits =
[wgpu::Limits::default(), wgpu::Limits::downlevel_defaults()];
let mut limits = limits.into_iter().map(|limits| wgpu::Limits {
let limits = limits.into_iter().map(|limits| wgpu::Limits {
max_bind_groups: 2,
..limits
});
let (device, queue) =
loop {
let required_limits = limits.next()?;
let device = adapter.request_device(
let mut errors = Vec::new();
for required_limits in limits {
let result = adapter
.request_device(
&wgpu::DeviceDescriptor {
label: Some(
"iced_wgpu::window::compositor device descriptor",
),
required_features: wgpu::Features::empty(),
required_limits,
required_limits: required_limits.clone(),
},
None,
).await.ok();
)
.await;
if let Some(device) = device {
break Some(device);
}
}?;
Some(Compositor {
match result {
Ok((device, queue)) => {
return Ok(Compositor {
instance,
settings,
adapter,
@ -149,6 +177,14 @@ impl Compositor {
alpha_mode,
})
}
Err(error) => {
errors.push((required_limits, error));
}
}
}
Err(Error::RequestDeviceFailed(errors))
}
/// Creates a new rendering [`Backend`] for this [`Compositor`].
pub fn create_backend(&self) -> Backend {
@ -168,9 +204,7 @@ pub async fn new<W: compositor::Window>(
settings: Settings,
compatible_window: W,
) -> Result<Compositor, Error> {
Compositor::request(settings, Some(compatible_window))
.await
.ok_or(Error::GraphicsAdapterNotFound)
Compositor::request(settings, Some(compatible_window)).await
}
/// Presents the given primitives with the given [`Compositor`] and [`Backend`].
@ -229,15 +263,31 @@ pub fn present<T: AsRef<str>>(
}
impl graphics::Compositor for Compositor {
type Settings = Settings;
type Renderer = Renderer;
type Surface = wgpu::Surface<'static>;
fn new<W: compositor::Window>(
settings: Self::Settings,
async fn with_backend<W: compositor::Window>(
settings: graphics::Settings,
compatible_window: W,
) -> impl Future<Output = Result<Self, Error>> {
new(settings, compatible_window)
backend: Option<&str>,
) -> Result<Self, graphics::Error> {
match backend {
None | Some("wgpu") => Ok(new(
Settings {
internal_backend: wgpu::util::backend_bits_from_env()
.unwrap_or(wgpu::Backends::all()),
..settings.into()
},
compatible_window,
)
.await?),
Some(backend) => Err(graphics::Error::GraphicsAdapterNotFound {
backend: "wgpu",
reason: error::Reason::DidNotMatch {
preferred_backend: backend.to_owned(),
},
}),
}
}
fn create_renderer(&self) -> Self::Renderer {

View file

@ -6,8 +6,10 @@ mod program;
pub use event::Event;
pub use program::Program;
pub use crate::graphics::geometry::*;
pub use crate::renderer::geometry::*;
pub use crate::graphics::geometry::{
fill, gradient, path, stroke, Fill, Gradient, LineCap, LineDash, LineJoin,
Path, Stroke, Style, Text,
};
use crate::core;
use crate::core::layout::{self, Layout};
@ -21,6 +23,19 @@ use crate::graphics::geometry;
use std::marker::PhantomData;
/// A simple cache that stores generated [`Geometry`] to avoid recomputation.
///
/// A [`Cache`] will not redraw its geometry unless the dimensions of its layer
/// change or it is explicitly cleared.
pub type Cache<Renderer = crate::Renderer> = geometry::Cache<Renderer>;
/// The geometry supported by a renderer.
pub type Geometry<Renderer = crate::Renderer> =
<Renderer as geometry::Renderer>::Geometry;
/// The frame supported by a renderer.
pub type Frame<Renderer = crate::Renderer> = geometry::Frame<Renderer>;
/// A widget capable of drawing 2D graphics.
///
/// ## Drawing a simple circle
@ -210,9 +225,12 @@ where
renderer.with_transformation(
Transformation::translate(bounds.x, bounds.y),
|renderer| {
renderer.draw(
self.program.draw(state, renderer, theme, bounds, cursor),
);
let layers =
self.program.draw(state, renderer, theme, bounds, cursor);
for layer in layers {
renderer.draw_geometry(layer);
}
},
);
}

View file

@ -1,5 +1,6 @@
use crate::canvas::event::{self, Event};
use crate::canvas::mouse;
use crate::canvas::Geometry;
use crate::core::Rectangle;
use crate::graphics::geometry;
@ -52,7 +53,7 @@ where
theme: &Theme,
bounds: Rectangle,
cursor: mouse::Cursor,
) -> Vec<Renderer::Geometry>;
) -> Vec<Geometry<Renderer>>;
/// Returns the current mouse interaction of the [`Program`].
///
@ -94,7 +95,7 @@ where
theme: &Theme,
bounds: Rectangle,
cursor: mouse::Cursor,
) -> Vec<Renderer::Geometry> {
) -> Vec<Geometry<Renderer>> {
T::draw(self, state, renderer, theme, bounds, cursor)
}

View file

@ -93,7 +93,7 @@ where
{
// The raw w/h of the underlying image
let image_size = {
let Size { width, height } = renderer.dimensions(handle);
let Size { width, height } = renderer.measure_image(handle);
Size::new(width as f32, height as f32)
};
@ -130,7 +130,7 @@ pub fn draw<Renderer, Handle>(
Renderer: image::Renderer<Handle = Handle>,
Handle: Clone + Hash,
{
let Size { width, height } = renderer.dimensions(handle);
let Size { width, height } = renderer.measure_image(handle);
let image_size = Size::new(width as f32, height as f32);
let bounds = layout.bounds();
@ -148,7 +148,11 @@ pub fn draw<Renderer, Handle>(
..bounds
};
renderer.draw(handle.clone(), filter_method, drawing_bounds + offset);
renderer.draw_image(
handle.clone(),
filter_method,
drawing_bounds + offset,
);
};
if adjusted_fit.width > bounds.width || adjusted_fit.height > bounds.height

View file

@ -117,7 +117,7 @@ where
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
let Size { width, height } = renderer.dimensions(&self.handle);
let Size { width, height } = renderer.measure_image(&self.handle);
let mut size = limits.resolve(
self.width,
@ -335,8 +335,7 @@ where
renderer.with_layer(bounds, |renderer| {
renderer.with_translation(translation, |renderer| {
image::Renderer::draw(
renderer,
renderer.draw_image(
self.handle.clone(),
self.filter_method,
Rectangle {
@ -421,7 +420,7 @@ pub fn image_size<Renderer>(
where
Renderer: image::Renderer,
{
let Size { width, height } = renderer.dimensions(handle);
let Size { width, height } = renderer.measure_image(handle);
let (width, height) = {
let dimensions = (width as f32, height as f32);

View file

@ -8,7 +8,6 @@ use crate::core::{
Color, Element, Layout, Length, Point, Rectangle, Size, Theme, Vector,
Widget,
};
use crate::graphics::geometry::Renderer as _;
use crate::Renderer;
use std::cell::RefCell;
@ -142,7 +141,9 @@ impl<'a, Message, Theme> Widget<Message, Theme, Renderer>
renderer.with_translation(
bounds.position() - Point::ORIGIN,
|renderer| {
renderer.draw(vec![geometry]);
use crate::graphics::geometry::Renderer as _;
renderer.draw_geometry(geometry);
},
);
}
@ -161,11 +162,11 @@ where
/// The data of a [`QRCode`].
///
/// It stores the contents that will be displayed.
#[derive(Debug)]
#[allow(missing_debug_implementations)]
pub struct Data {
contents: Vec<qrcode::Color>,
width: usize,
cache: canvas::Cache,
cache: canvas::Cache<Renderer>,
}
impl Data {

View file

@ -108,7 +108,7 @@ where
limits: &layout::Limits,
) -> layout::Node {
// The raw w/h of the underlying image
let Size { width, height } = renderer.dimensions(&self.handle);
let Size { width, height } = renderer.measure_svg(&self.handle);
let image_size = Size::new(width as f32, height as f32);
// The size to be available to the widget prior to `Shrink`ing
@ -142,7 +142,7 @@ where
cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
let Size { width, height } = renderer.dimensions(&self.handle);
let Size { width, height } = renderer.measure_svg(&self.handle);
let image_size = Size::new(width as f32, height as f32);
let bounds = layout.bounds();
@ -169,7 +169,7 @@ where
let appearance = (self.style)(theme, status);
renderer.draw(
renderer.draw_svg(
self.handle.clone(),
appearance.color,
drawing_bounds + offset,

View file

@ -13,6 +13,7 @@ use crate::core::window;
use crate::core::{Color, Event, Point, Size, Theme};
use crate::futures::futures;
use crate::futures::{Executor, Runtime, Subscription};
use crate::graphics;
use crate::graphics::compositor::{self, Compositor};
use crate::runtime::clipboard;
use crate::runtime::program::Program;
@ -130,7 +131,7 @@ pub fn default(theme: &Theme) -> Appearance {
/// settings.
pub async fn run<A, E, C>(
settings: Settings<A::Flags>,
compositor_settings: C::Settings,
graphics_settings: graphics::Settings,
) -> Result<(), Error>
where
A: Application + 'static,
@ -219,7 +220,7 @@ where
};
}
let compositor = C::new(compositor_settings, window.clone()).await?;
let compositor = C::new(graphics_settings, window.clone()).await?;
let mut renderer = compositor.create_renderer();
for font in settings.fonts {

View file

@ -16,6 +16,7 @@ use crate::futures::futures::executor;
use crate::futures::futures::task;
use crate::futures::futures::{Future, StreamExt};
use crate::futures::{Executor, Runtime, Subscription};
use crate::graphics;
use crate::graphics::{compositor, Compositor};
use crate::multi_window::window_manager::WindowManager;
use crate::runtime::command::{self, Command};
@ -105,7 +106,7 @@ where
/// settings.
pub fn run<A, E, C>(
settings: Settings<A::Flags>,
compositor_settings: C::Settings,
graphics_settings: graphics::Settings,
) -> Result<(), Error>
where
A: Application + 'static,
@ -187,7 +188,7 @@ where
}
let mut compositor =
executor::block_on(C::new(compositor_settings, main_window.clone()))?;
executor::block_on(C::new(graphics_settings, main_window.clone()))?;
let mut window_manager = WindowManager::new();
let _ = window_manager.insert(