diff options
Diffstat (limited to 'graphics/src')
| -rw-r--r-- | graphics/src/backend.rs | 32 | ||||
| -rw-r--r-- | graphics/src/cache.rs | 189 | ||||
| -rw-r--r-- | graphics/src/compositor.rs | 108 | ||||
| -rw-r--r-- | graphics/src/damage.rs | 240 | ||||
| -rw-r--r-- | graphics/src/error.rs | 27 | ||||
| -rw-r--r-- | graphics/src/geometry.rs | 31 | ||||
| -rw-r--r-- | graphics/src/geometry/cache.rs | 116 | ||||
| -rw-r--r-- | graphics/src/geometry/frame.rs | 249 | ||||
| -rw-r--r-- | graphics/src/image.rs | 208 | ||||
| -rw-r--r-- | graphics/src/layer.rs | 144 | ||||
| -rw-r--r-- | graphics/src/lib.rs | 29 | ||||
| -rw-r--r-- | graphics/src/mesh.rs | 100 | ||||
| -rw-r--r-- | graphics/src/primitive.rs | 160 | ||||
| -rw-r--r-- | graphics/src/renderer.rs | 252 | ||||
| -rw-r--r-- | graphics/src/settings.rs | 29 | ||||
| -rw-r--r-- | graphics/src/text.rs | 129 | ||||
| -rw-r--r-- | graphics/src/text/cache.rs | 16 | ||||
| -rw-r--r-- | graphics/src/text/editor.rs | 12 | ||||
| -rw-r--r-- | graphics/src/text/paragraph.rs | 4 | 
19 files changed, 1291 insertions, 784 deletions
| diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs deleted file mode 100644 index 10eb337f..00000000 --- a/graphics/src/backend.rs +++ /dev/null @@ -1,32 +0,0 @@ -//! Write a graphics backend. -use crate::core::image; -use crate::core::svg; -use crate::core::Size; - -use std::borrow::Cow; - -/// The graphics backend of a [`Renderer`]. -/// -/// [`Renderer`]: crate::Renderer -pub trait Backend { -    /// The custom kind of primitives this [`Backend`] supports. -    type Primitive; -} - -/// A graphics backend that supports text rendering. -pub trait Text { -    /// Loads a font from its bytes. -    fn load_font(&mut self, font: Cow<'static, [u8]>); -} - -/// A graphics backend that supports image rendering. -pub trait Image { -    /// Returns the dimensions of the provided image. -    fn dimensions(&self, handle: &image::Handle) -> Size<u32>; -} - -/// A graphics backend that supports SVG rendering. -pub trait Svg { -    /// Returns the viewport dimensions of the provided SVG. -    fn viewport_dimensions(&self, handle: &svg::Handle) -> Size<u32>; -} diff --git a/graphics/src/cache.rs b/graphics/src/cache.rs new file mode 100644 index 00000000..bbba79eb --- /dev/null +++ b/graphics/src/cache.rs @@ -0,0 +1,189 @@ +//! Cache computations and efficiently reuse them. +use std::cell::RefCell; +use std::fmt; +use std::sync::atomic::{self, AtomicU64}; + +/// A simple cache that stores generated values to avoid recomputation. +/// +/// Keeps track of the last generated value after clearing. +pub struct Cache<T> { +    group: Group, +    state: RefCell<State<T>>, +} + +impl<T> Cache<T> { +    /// Creates a new empty [`Cache`]. +    pub fn new() -> Self { +        Cache { +            group: Group::singleton(), +            state: RefCell::new(State::Empty { previous: None }), +        } +    } + +    /// Creates a new empty [`Cache`] with the given [`Group`]. +    /// +    /// Caches within the same group may reuse internal rendering storage. +    /// +    /// You should generally group caches that are likely to change +    /// together. +    pub fn with_group(group: Group) -> Self { +        assert!( +            !group.is_singleton(), +            "The group {group:?} cannot be shared!" +        ); + +        Cache { +            group, +            state: RefCell::new(State::Empty { previous: None }), +        } +    } + +    /// Returns the [`Group`] of the [`Cache`]. +    pub fn group(&self) -> Group { +        self.group +    } + +    /// Puts the given value in the [`Cache`]. +    /// +    /// Notice that, given this is a cache, a mutable reference is not +    /// necessary to call this method. You can safely update the cache in +    /// rendering code. +    pub fn put(&self, value: T) { +        *self.state.borrow_mut() = State::Filled { current: value }; +    } + +    /// Returns a reference cell to the internal [`State`] of the [`Cache`]. +    pub fn state(&self) -> &RefCell<State<T>> { +        &self.state +    } + +    /// Clears the [`Cache`]. +    pub fn clear(&self) +    where +        T: Clone, +    { +        use std::ops::Deref; + +        let previous = match self.state.borrow().deref() { +            State::Empty { previous } => previous.clone(), +            State::Filled { current } => Some(current.clone()), +        }; + +        *self.state.borrow_mut() = State::Empty { previous }; +    } +} + +/// A cache group. +/// +/// Caches that share the same group generally change together. +/// +/// A cache group can be used to implement certain performance +/// optimizations during rendering, like batching or sharing atlases. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Group { +    id: u64, +    is_singleton: bool, +} + +impl Group { +    /// Generates a new unique cache [`Group`]. +    pub fn unique() -> Self { +        static NEXT: AtomicU64 = AtomicU64::new(0); + +        Self { +            id: NEXT.fetch_add(1, atomic::Ordering::Relaxed), +            is_singleton: false, +        } +    } + +    /// Returns `true` if the [`Group`] can only ever have a +    /// single [`Cache`] in it. +    /// +    /// This is the default kind of [`Group`] assigned when using +    /// [`Cache::new`]. +    /// +    /// Knowing that a [`Group`] will never be shared may be +    /// useful for rendering backends to perform additional +    /// optimizations. +    pub fn is_singleton(self) -> bool { +        self.is_singleton +    } + +    fn singleton() -> Self { +        Self { +            is_singleton: true, +            ..Self::unique() +        } +    } +} + +impl<T> fmt::Debug for Cache<T> +where +    T: fmt::Debug, +{ +    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +        use std::ops::Deref; + +        let state = self.state.borrow(); + +        match state.deref() { +            State::Empty { previous } => { +                write!(f, "Cache::Empty {{ previous: {previous:?} }}") +            } +            State::Filled { current } => { +                write!(f, "Cache::Filled {{ current: {current:?} }}") +            } +        } +    } +} + +impl<T> Default for Cache<T> { +    fn default() -> Self { +        Self::new() +    } +} + +/// The state of a [`Cache`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum State<T> { +    /// The [`Cache`] is empty. +    Empty { +        /// The previous value of the [`Cache`]. +        previous: Option<T>, +    }, +    /// The [`Cache`] is filled. +    Filled { +        /// The current value of the [`Cache`] +        current: T, +    }, +} + +/// A piece of data that can be cached. +pub trait Cached: Sized { +    /// The type of cache produced. +    type Cache: Clone; + +    /// 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, group: Group, previous: Option<Self::Cache>) -> Self::Cache; +} + +#[cfg(debug_assertions)] +impl Cached for () { +    type Cache = (); + +    fn load(_cache: &Self::Cache) -> Self {} + +    fn cache( +        self, +        _group: Group, +        _previous: Option<Self::Cache>, +    ) -> Self::Cache { +    } +} diff --git a/graphics/src/compositor.rs b/graphics/src/compositor.rs index 91951a8e..47521eb0 100644 --- a/graphics/src/compositor.rs +++ b/graphics/src/compositor.rs @@ -1,29 +1,39 @@  //! 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;  use thiserror::Error; +use std::borrow::Cow; +use std::future::Future; +  /// 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`]. @@ -52,6 +62,14 @@ pub trait Compositor: Sized {      /// Returns [`Information`] used by this [`Compositor`].      fn fetch_information(&self) -> Information; +    /// Loads a font from its bytes. +    fn load_font(&mut self, font: Cow<'static, [u8]>) { +        crate::text::font_system() +            .write() +            .expect("Write to font system") +            .load_font(font); +    } +      /// Presents the [`Renderer`] primitives to the next frame of the given [`Surface`].      ///      /// [`Renderer`]: Self::Renderer @@ -93,6 +111,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 +146,71 @@ 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 load_font(&mut self, _font: Cow<'static, [u8]>) {} + +    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 = (); +} diff --git a/graphics/src/damage.rs b/graphics/src/damage.rs index 8edf69d7..17d60451 100644 --- a/graphics/src/damage.rs +++ b/graphics/src/damage.rs @@ -1,196 +1,14 @@ -//! Track and compute the damage of graphical primitives. -use crate::core::alignment; -use crate::core::{Rectangle, Size}; -use crate::Primitive; - -use std::sync::Arc; - -/// A type that has some damage bounds. -pub trait Damage: PartialEq { -    /// Returns the bounds of the [`Damage`]. -    fn bounds(&self) -> Rectangle; -} - -impl<T: Damage> Damage for Primitive<T> { -    fn bounds(&self) -> Rectangle { -        match self { -            Self::Text { -                bounds, -                horizontal_alignment, -                vertical_alignment, -                .. -            } => { -                let mut bounds = *bounds; - -                bounds.x = match horizontal_alignment { -                    alignment::Horizontal::Left => bounds.x, -                    alignment::Horizontal::Center => { -                        bounds.x - bounds.width / 2.0 -                    } -                    alignment::Horizontal::Right => bounds.x - bounds.width, -                }; - -                bounds.y = match vertical_alignment { -                    alignment::Vertical::Top => bounds.y, -                    alignment::Vertical::Center => { -                        bounds.y - bounds.height / 2.0 -                    } -                    alignment::Vertical::Bottom => bounds.y - bounds.height, -                }; - -                bounds.expand(1.5) -            } -            Self::Paragraph { -                paragraph, -                position, -                .. -            } => { -                let mut bounds = -                    Rectangle::new(*position, paragraph.min_bounds); - -                bounds.x = match paragraph.horizontal_alignment { -                    alignment::Horizontal::Left => bounds.x, -                    alignment::Horizontal::Center => { -                        bounds.x - bounds.width / 2.0 -                    } -                    alignment::Horizontal::Right => bounds.x - bounds.width, -                }; - -                bounds.y = match paragraph.vertical_alignment { -                    alignment::Vertical::Top => bounds.y, -                    alignment::Vertical::Center => { -                        bounds.y - bounds.height / 2.0 -                    } -                    alignment::Vertical::Bottom => bounds.y - bounds.height, -                }; - -                bounds.expand(1.5) -            } -            Self::Editor { -                editor, position, .. -            } => { -                let bounds = Rectangle::new(*position, editor.bounds); - -                bounds.expand(1.5) -            } -            Self::RawText(raw) => { -                // TODO: Add `size` field to `raw` to compute more accurate -                // damage bounds (?) -                raw.clip_bounds.expand(1.5) -            } -            Self::Quad { bounds, shadow, .. } if shadow.color.a > 0.0 => { -                let bounds_with_shadow = Rectangle { -                    x: bounds.x + shadow.offset.x.min(0.0) - shadow.blur_radius, -                    y: bounds.y + shadow.offset.y.min(0.0) - shadow.blur_radius, -                    width: bounds.width -                        + shadow.offset.x.abs() -                        + shadow.blur_radius * 2.0, -                    height: bounds.height -                        + shadow.offset.y.abs() -                        + shadow.blur_radius * 2.0, -                }; - -                bounds_with_shadow.expand(1.0) -            } -            Self::Quad { bounds, .. } -            | Self::Image { bounds, .. } -            | Self::Svg { bounds, .. } => bounds.expand(1.0), -            Self::Clip { bounds, .. } => bounds.expand(1.0), -            Self::Group { primitives } => primitives -                .iter() -                .map(Self::bounds) -                .fold(Rectangle::with_size(Size::ZERO), |a, b| { -                    Rectangle::union(&a, &b) -                }), -            Self::Transform { -                transformation, -                content, -            } => content.bounds() * *transformation, -            Self::Cache { content } => content.bounds(), -            Self::Custom(custom) => custom.bounds(), -        } -    } -} - -fn regions<T: Damage>(a: &Primitive<T>, b: &Primitive<T>) -> Vec<Rectangle> { -    match (a, b) { -        ( -            Primitive::Group { -                primitives: primitives_a, -            }, -            Primitive::Group { -                primitives: primitives_b, -            }, -        ) => return list(primitives_a, primitives_b), -        ( -            Primitive::Clip { -                bounds: bounds_a, -                content: content_a, -                .. -            }, -            Primitive::Clip { -                bounds: bounds_b, -                content: content_b, -                .. -            }, -        ) => { -            if bounds_a == bounds_b { -                return regions(content_a, content_b) -                    .into_iter() -                    .filter_map(|r| r.intersection(&bounds_a.expand(1.0))) -                    .collect(); -            } else { -                return vec![bounds_a.expand(1.0), bounds_b.expand(1.0)]; -            } -        } -        ( -            Primitive::Transform { -                transformation: transformation_a, -                content: content_a, -            }, -            Primitive::Transform { -                transformation: transformation_b, -                content: content_b, -            }, -        ) => { -            if transformation_a == transformation_b { -                return regions(content_a, content_b) -                    .into_iter() -                    .map(|r| r * *transformation_a) -                    .collect(); -            } -        } -        ( -            Primitive::Cache { content: content_a }, -            Primitive::Cache { content: content_b }, -        ) => { -            if Arc::ptr_eq(content_a, content_b) { -                return vec![]; -            } -        } -        _ if a == b => return vec![], -        _ => {} -    } - -    let bounds_a = a.bounds(); -    let bounds_b = b.bounds(); - -    if bounds_a == bounds_b { -        vec![bounds_a] -    } else { -        vec![bounds_a, bounds_b] -    } -} - -/// Computes the damage regions between the two given lists of primitives. -pub fn list<T: Damage>( -    previous: &[Primitive<T>], -    current: &[Primitive<T>], +//! Compute the damage between frames. +use crate::core::{Point, Rectangle}; + +/// Diffs the damage regions given some previous and current primitives. +pub fn diff<T>( +    previous: &[T], +    current: &[T], +    bounds: impl Fn(&T) -> Vec<Rectangle>, +    diff: impl Fn(&T, &T) -> Vec<Rectangle>,  ) -> Vec<Rectangle> { -    let damage = previous -        .iter() -        .zip(current) -        .flat_map(|(a, b)| regions(a, b)); +    let damage = previous.iter().zip(current).flat_map(|(a, b)| diff(a, b));      if previous.len() == current.len() {          damage.collect() @@ -203,39 +21,45 @@ pub fn list<T: Damage>(          // Extend damage by the added/removed primitives          damage -            .chain(bigger[smaller.len()..].iter().map(Damage::bounds)) +            .chain(bigger[smaller.len()..].iter().flat_map(bounds))              .collect()      }  } +/// Computes the damage regions given some previous and current primitives. +pub fn list<T>( +    previous: &[T], +    current: &[T], +    bounds: impl Fn(&T) -> Vec<Rectangle>, +    are_equal: impl Fn(&T, &T) -> bool, +) -> Vec<Rectangle> { +    diff(previous, current, &bounds, |a, b| { +        if are_equal(a, b) { +            vec![] +        } else { +            bounds(a).into_iter().chain(bounds(b)).collect() +        } +    }) +} +  /// Groups the given damage regions that are close together inside the given  /// bounds. -pub fn group( -    mut damage: Vec<Rectangle>, -    scale_factor: f32, -    bounds: Size<u32>, -) -> Vec<Rectangle> { +pub fn group(mut damage: Vec<Rectangle>, bounds: Rectangle) -> Vec<Rectangle> {      use std::cmp::Ordering;      const AREA_THRESHOLD: f32 = 20_000.0; -    let bounds = Rectangle { -        x: 0.0, -        y: 0.0, -        width: bounds.width as f32, -        height: bounds.height as f32, -    }; -      damage.sort_by(|a, b| { -        a.x.partial_cmp(&b.x) +        a.center() +            .distance(Point::ORIGIN) +            .partial_cmp(&b.center().distance(Point::ORIGIN))              .unwrap_or(Ordering::Equal) -            .then_with(|| a.y.partial_cmp(&b.y).unwrap_or(Ordering::Equal))      });      let mut output = Vec::new();      let mut scaled = damage          .into_iter() -        .filter_map(|region| (region * scale_factor).intersection(&bounds)) +        .filter_map(|region| region.intersection(&bounds))          .filter(|region| region.width >= 1.0 && region.height >= 1.0);      if let Some(mut current) = scaled.next() { diff --git a/graphics/src/error.rs b/graphics/src/error.rs index c6ea98a3..6ea1d3a4 100644 --- a/graphics/src/error.rs +++ b/graphics/src/error.rs @@ -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),  } diff --git a/graphics/src/geometry.rs b/graphics/src/geometry.rs index d7d6a0aa..ab4a7a36 100644 --- a/graphics/src/geometry.rs +++ b/graphics/src/geometry.rs @@ -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,30 @@ pub use text::Text;  pub use crate::gradient::{self, Gradient}; +use crate::cache::Cached; +use crate::core::{self, Size}; +  /// A renderer capable of drawing some [`Self::Geometry`]. -pub trait Renderer: crate::core::Renderer { +pub trait Renderer: core::Renderer {      /// The kind of geometry this renderer can draw. -    type Geometry; +    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); +} + +#[cfg(debug_assertions)] +impl Renderer for () { +    type Geometry = (); +    type Frame = (); + +    fn new_frame(&self, _size: Size) -> Self::Frame {} -    /// Draws the given layers of [`Self::Geometry`]. -    fn draw(&mut self, layers: Vec<Self::Geometry>); +    fn draw_geometry(&mut self, _geometry: Self::Geometry) {}  } diff --git a/graphics/src/geometry/cache.rs b/graphics/src/geometry/cache.rs new file mode 100644 index 00000000..d70cee0b --- /dev/null +++ b/graphics/src/geometry/cache.rs @@ -0,0 +1,116 @@ +use crate::cache::{self, Cached}; +use crate::core::Size; +use crate::geometry::{self, Frame}; + +pub use cache::Group; + +/// 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, +{ +    raw: crate::Cache<Data<<Renderer::Geometry as Cached>::Cache>>, +} + +#[derive(Debug, Clone)] +struct Data<T> { +    bounds: Size, +    geometry: T, +} + +impl<Renderer> Cache<Renderer> +where +    Renderer: geometry::Renderer, +{ +    /// Creates a new empty [`Cache`]. +    pub fn new() -> Self { +        Cache { +            raw: cache::Cache::new(), +        } +    } + +    /// Creates a new empty [`Cache`] with the given [`Group`]. +    /// +    /// Caches within the same group may reuse internal rendering storage. +    /// +    /// You should generally group caches that are likely to change +    /// together. +    pub fn with_group(group: Group) -> Self { +        Cache { +            raw: crate::Cache::with_group(group), +        } +    } + +    /// Clears the [`Cache`], forcing a redraw the next time it is used. +    pub fn clear(&self) { +        self.raw.clear(); +    } + +    /// 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; + +        let state = self.raw.state(); + +        let previous = match state.borrow().deref() { +            cache::State::Empty { previous } => { +                previous.as_ref().map(|data| data.geometry.clone()) +            } +            cache::State::Filled { current } => { +                if current.bounds == bounds { +                    return Cached::load(¤t.geometry); +                } + +                Some(current.geometry.clone()) +            } +        }; + +        let mut frame = Frame::new(renderer, bounds); +        draw_fn(&mut frame); + +        let geometry = frame.into_geometry().cache(self.raw.group(), previous); +        let result = Cached::load(&geometry); + +        *state.borrow_mut() = cache::State::Filled { +            current: Data { bounds, geometry }, +        }; + +        result +    } +} + +impl<Renderer> std::fmt::Debug for Cache<Renderer> +where +    Renderer: geometry::Renderer, +    <Renderer::Geometry as Cached>::Cache: std::fmt::Debug, +{ +    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +        write!(f, "{:?}", &self.raw) +    } +} + +impl<Renderer> Default for Cache<Renderer> +where +    Renderer: geometry::Renderer, +{ +    fn default() -> Self { +        Self::new() +    } +} diff --git a/graphics/src/geometry/frame.rs b/graphics/src/geometry/frame.rs new file mode 100644 index 00000000..377589d7 --- /dev/null +++ b/graphics/src/geometry/frame.rs @@ -0,0 +1,249 @@ +//! 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); + +        let result = f(&mut frame); + +        self.paste(frame, Point::new(region.x, region.y)); + +        result +    } + +    /// Creates a new [`Frame`] with the given [`Size`]. +    /// +    /// Draw its contents back to this [`Frame`] with [`paste`]. +    /// +    /// [`paste`]: Self::paste +    fn draft(&mut self, clip_bounds: Rectangle) -> Self { +        Self { +            raw: self.raw.draft(clip_bounds), +        } +    } + +    /// Draws the contents of the given [`Frame`] with origin at the given [`Point`]. +    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, clip_bounds: Rectangle) -> 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, _clip_bounds: Rectangle) -> 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 {} +} diff --git a/graphics/src/image.rs b/graphics/src/image.rs index d89caace..318592be 100644 --- a/graphics/src/image.rs +++ b/graphics/src/image.rs @@ -1,14 +1,121 @@  //! Load and operate on images. -use crate::core::image::{Data, Handle}; +#[cfg(feature = "image")] +pub use ::image as image_rs; -use bitflags::bitflags; +use crate::core::{image, svg, Color, Radians, Rectangle}; -pub use ::image as image_rs; +/// A raster or vector image. +#[derive(Debug, Clone, PartialEq)] +pub enum Image { +    /// A raster image. +    Raster { +        /// The handle of a raster image. +        handle: image::Handle, + +        /// The filter method of a raster image. +        filter_method: image::FilterMethod, + +        /// The bounds of the image. +        bounds: Rectangle, + +        /// The rotation of the image. +        rotation: Radians, + +        /// The opacity of the image. +        opacity: f32, +    }, +    /// A vector image. +    Vector { +        /// The handle of a vector image. +        handle: svg::Handle, + +        /// The [`Color`] filter +        color: Option<Color>, + +        /// The bounds of the image. +        bounds: Rectangle, + +        /// The rotation of the image. +        rotation: Radians, + +        /// The opacity of the image. +        opacity: f32, +    }, +} + +impl Image { +    /// Returns the bounds of the [`Image`]. +    pub fn bounds(&self) -> Rectangle { +        match self { +            Image::Raster { +                bounds, rotation, .. +            } +            | Image::Vector { +                bounds, rotation, .. +            } => bounds.rotate(*rotation), +        } +    } +} +#[cfg(feature = "image")]  /// Tries to load an image by its [`Handle`]. -pub fn load(handle: &Handle) -> image_rs::ImageResult<image_rs::DynamicImage> { -    match handle.data() { -        Data::Path(path) => { +/// +/// [`Handle`]: image::Handle +pub fn load( +    handle: &image::Handle, +) -> ::image::ImageResult<::image::ImageBuffer<::image::Rgba<u8>, image::Bytes>> +{ +    use bitflags::bitflags; + +    bitflags! { +        struct Operation: u8 { +            const FLIP_HORIZONTALLY = 0b001; +            const ROTATE_180 = 0b010; +            const FLIP_DIAGONALLY = 0b100; +        } +    } + +    impl Operation { +        // Meaning of the returned value is described e.g. at: +        // https://magnushoff.com/articles/jpeg-orientation/ +        fn from_exif<R>(reader: &mut R) -> Result<Self, exif::Error> +        where +            R: std::io::BufRead + std::io::Seek, +        { +            let exif = exif::Reader::new().read_from_container(reader)?; + +            Ok(exif +                .get_field(exif::Tag::Orientation, exif::In::PRIMARY) +                .and_then(|field| field.value.get_uint(0)) +                .and_then(|value| u8::try_from(value).ok()) +                .and_then(|value| Self::from_bits(value.saturating_sub(1))) +                .unwrap_or_else(Self::empty)) +        } + +        fn perform( +            self, +            mut image: ::image::DynamicImage, +        ) -> ::image::DynamicImage { +            use ::image::imageops; + +            if self.contains(Self::FLIP_DIAGONALLY) { +                imageops::flip_vertical_in_place(&mut image); +            } + +            if self.contains(Self::ROTATE_180) { +                imageops::rotate180_in_place(&mut image); +            } + +            if self.contains(Self::FLIP_HORIZONTALLY) { +                imageops::flip_horizontal_in_place(&mut image); +            } + +            image +        } +    } + +    let (width, height, pixels) = match handle { +        image::Handle::Path(_, path) => {              let image = ::image::open(path)?;              let operation = std::fs::File::open(path) @@ -17,79 +124,44 @@ pub fn load(handle: &Handle) -> image_rs::ImageResult<image_rs::DynamicImage> {                  .and_then(|mut reader| Operation::from_exif(&mut reader).ok())                  .unwrap_or_else(Operation::empty); -            Ok(operation.perform(image)) +            let rgba = operation.perform(image).into_rgba8(); + +            ( +                rgba.width(), +                rgba.height(), +                image::Bytes::from(rgba.into_raw()), +            )          } -        Data::Bytes(bytes) => { +        image::Handle::Bytes(_, bytes) => {              let image = ::image::load_from_memory(bytes)?;              let operation =                  Operation::from_exif(&mut std::io::Cursor::new(bytes))                      .ok()                      .unwrap_or_else(Operation::empty); -            Ok(operation.perform(image)) +            let rgba = operation.perform(image).into_rgba8(); + +            ( +                rgba.width(), +                rgba.height(), +                image::Bytes::from(rgba.into_raw()), +            )          } -        Data::Rgba { +        image::Handle::Rgba {              width,              height,              pixels, -        } => { -            if let Some(image) = image_rs::ImageBuffer::from_vec( -                *width, -                *height, -                pixels.to_vec(), -            ) { -                Ok(image_rs::DynamicImage::ImageRgba8(image)) -            } else { -                Err(image_rs::error::ImageError::Limits( -                    image_rs::error::LimitError::from_kind( -                        image_rs::error::LimitErrorKind::DimensionError, -                    ), -                )) -            } -        } -    } -} - -bitflags! { -    struct Operation: u8 { -        const FLIP_HORIZONTALLY = 0b001; -        const ROTATE_180 = 0b010; -        const FLIP_DIAGONALLY = 0b100; -    } -} - -impl Operation { -    // Meaning of the returned value is described e.g. at: -    // https://magnushoff.com/articles/jpeg-orientation/ -    fn from_exif<R>(reader: &mut R) -> Result<Self, exif::Error> -    where -        R: std::io::BufRead + std::io::Seek, -    { -        let exif = exif::Reader::new().read_from_container(reader)?; - -        Ok(exif -            .get_field(exif::Tag::Orientation, exif::In::PRIMARY) -            .and_then(|field| field.value.get_uint(0)) -            .and_then(|value| u8::try_from(value).ok()) -            .and_then(|value| Self::from_bits(value.saturating_sub(1))) -            .unwrap_or_else(Self::empty)) -    } - -    fn perform(self, mut image: image::DynamicImage) -> image::DynamicImage { -        use image::imageops; - -        if self.contains(Self::FLIP_DIAGONALLY) { -            imageops::flip_vertical_in_place(&mut image); -        } - -        if self.contains(Self::ROTATE_180) { -            imageops::rotate180_in_place(&mut image); -        } - -        if self.contains(Self::FLIP_HORIZONTALLY) { -            imageops::flip_horizontal_in_place(&mut image); -        } +            .. +        } => (*width, *height, pixels.clone()), +    }; -        image +    if let Some(image) = ::image::ImageBuffer::from_raw(width, height, pixels) { +        Ok(image) +    } else { +        Err(::image::error::ImageError::Limits( +            ::image::error::LimitError::from_kind( +                ::image::error::LimitErrorKind::DimensionError, +            ), +        ))      }  } diff --git a/graphics/src/layer.rs b/graphics/src/layer.rs new file mode 100644 index 00000000..c9a818fb --- /dev/null +++ b/graphics/src/layer.rs @@ -0,0 +1,144 @@ +//! Draw and stack layers of graphical primitives. +use crate::core::{Rectangle, Transformation}; + +/// A layer of graphical primitives. +/// +/// Layers normally dictate a set of primitives that are +/// rendered in a specific order. +pub trait Layer: Default { +    /// Creates a new [`Layer`] with the given bounds. +    fn with_bounds(bounds: Rectangle) -> Self; + +    /// Flushes and settles any pending group of primitives in the [`Layer`]. +    /// +    /// This will be called when a [`Layer`] is finished. It allows layers to efficiently +    /// record primitives together and defer grouping until the end. +    fn flush(&mut self); + +    /// Resizes the [`Layer`] to the given bounds. +    fn resize(&mut self, bounds: Rectangle); + +    /// Clears all the layers contents and resets its bounds. +    fn reset(&mut self); +} + +/// A stack of layers used for drawing. +#[derive(Debug)] +pub struct Stack<T: Layer> { +    layers: Vec<T>, +    transformations: Vec<Transformation>, +    previous: Vec<usize>, +    current: usize, +    active_count: usize, +} + +impl<T: Layer> Stack<T> { +    /// Creates a new empty [`Stack`]. +    pub fn new() -> Self { +        Self { +            layers: vec![T::default()], +            transformations: vec![Transformation::IDENTITY], +            previous: vec![], +            current: 0, +            active_count: 1, +        } +    } + +    /// Returns a mutable reference to the current [`Layer`] of the [`Stack`], together with +    /// the current [`Transformation`]. +    #[inline] +    pub fn current_mut(&mut self) -> (&mut T, Transformation) { +        let transformation = self.transformation(); + +        (&mut self.layers[self.current], transformation) +    } + +    /// Returns the current [`Transformation`] of the [`Stack`]. +    #[inline] +    pub fn transformation(&self) -> Transformation { +        self.transformations.last().copied().unwrap() +    } + +    /// Pushes a new clipping region in the [`Stack`]; creating a new layer in the +    /// process. +    pub fn push_clip(&mut self, bounds: Rectangle) { +        self.previous.push(self.current); + +        self.current = self.active_count; +        self.active_count += 1; + +        let bounds = bounds * self.transformation(); + +        if self.current == self.layers.len() { +            self.layers.push(T::with_bounds(bounds)); +        } else { +            self.layers[self.current].resize(bounds); +        } +    } + +    /// Pops the current clipping region from the [`Stack`] and restores the previous one. +    /// +    /// The current layer will be recorded for drawing. +    pub fn pop_clip(&mut self) { +        self.flush(); + +        self.current = self.previous.pop().unwrap(); +    } + +    /// Pushes a new [`Transformation`] in the [`Stack`]. +    /// +    /// Future drawing operations will be affected by this new [`Transformation`] until +    /// it is popped using [`pop_transformation`]. +    /// +    /// [`pop_transformation`]: Self::pop_transformation +    pub fn push_transformation(&mut self, transformation: Transformation) { +        self.transformations +            .push(self.transformation() * transformation); +    } + +    /// Pops the current [`Transformation`] in the [`Stack`]. +    pub fn pop_transformation(&mut self) { +        let _ = self.transformations.pop(); +    } + +    /// Returns an iterator over mutable references to the layers in the [`Stack`]. +    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> { +        self.flush(); + +        self.layers[..self.active_count].iter_mut() +    } + +    /// Returns an iterator over immutable references to the layers in the [`Stack`]. +    pub fn iter(&self) -> impl Iterator<Item = &T> { +        self.layers[..self.active_count].iter() +    } + +    /// Returns the slice of layers in the [`Stack`]. +    pub fn as_slice(&self) -> &[T] { +        &self.layers[..self.active_count] +    } + +    /// Flushes and settles any primitives in the current layer of the [`Stack`]. +    pub fn flush(&mut self) { +        self.layers[self.current].flush(); +    } + +    /// Clears the layers of the [`Stack`], allowing reuse. +    /// +    /// This will normally keep layer allocations for future drawing operations. +    pub fn clear(&mut self) { +        for layer in self.layers[..self.active_count].iter_mut() { +            layer.reset(); +        } + +        self.current = 0; +        self.active_count = 1; +        self.previous.clear(); +    } +} + +impl<T: Layer> Default for Stack<T> { +    fn default() -> Self { +        Self::new() +    } +} diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index aa9d00e8..b5ef55e7 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -7,44 +7,35 @@  #![doc(      html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"  )] -#![forbid(rust_2018_idioms)] -#![deny( -    missing_debug_implementations, -    missing_docs, -    unsafe_code, -    unused_results, -    rustdoc::broken_intra_doc_links -)]  #![cfg_attr(docsrs, feature(doc_auto_cfg))]  mod antialiasing; -mod error; -mod primitive; +mod settings;  mod viewport; -pub mod backend; +pub mod cache;  pub mod color;  pub mod compositor;  pub mod damage; +pub mod error;  pub mod gradient; +pub mod image; +pub mod layer;  pub mod mesh; -pub mod renderer;  pub mod text;  #[cfg(feature = "geometry")]  pub mod geometry; -#[cfg(feature = "image")] -pub mod image; -  pub use antialiasing::Antialiasing; -pub use backend::Backend; +pub use cache::Cache;  pub use compositor::Compositor; -pub use damage::Damage;  pub use error::Error;  pub use gradient::Gradient; +pub use image::Image; +pub use layer::Layer;  pub use mesh::Mesh; -pub use primitive::Primitive; -pub use renderer::Renderer; +pub use settings::Settings; +pub use text::Text;  pub use viewport::Viewport;  pub use iced_core as core; diff --git a/graphics/src/mesh.rs b/graphics/src/mesh.rs index 041986cf..76602319 100644 --- a/graphics/src/mesh.rs +++ b/graphics/src/mesh.rs @@ -1,8 +1,7 @@  //! Draw triangles!  use crate::color; -use crate::core::{Rectangle, Size}; +use crate::core::{Rectangle, Transformation};  use crate::gradient; -use crate::Damage;  use bytemuck::{Pod, Zeroable}; @@ -14,29 +13,55 @@ pub enum Mesh {          /// The vertices and indices of the mesh.          buffers: Indexed<SolidVertex2D>, -        /// The size of the drawable region of the mesh. -        /// -        /// Any geometry that falls out of this region will be clipped. -        size: Size, +        /// The [`Transformation`] for the vertices of the [`Mesh`]. +        transformation: Transformation, + +        /// The clip bounds of the [`Mesh`]. +        clip_bounds: Rectangle,      },      /// A mesh with a gradient.      Gradient {          /// The vertices and indices of the mesh.          buffers: Indexed<GradientVertex2D>, -        /// The size of the drawable region of the mesh. -        /// -        /// Any geometry that falls out of this region will be clipped. -        size: Size, +        /// The [`Transformation`] for the vertices of the [`Mesh`]. +        transformation: Transformation, + +        /// The clip bounds of the [`Mesh`]. +        clip_bounds: Rectangle,      },  } -impl Damage for Mesh { -    fn bounds(&self) -> Rectangle { +impl Mesh { +    /// Returns the indices of the [`Mesh`]. +    pub fn indices(&self) -> &[u32] { +        match self { +            Self::Solid { buffers, .. } => &buffers.indices, +            Self::Gradient { buffers, .. } => &buffers.indices, +        } +    } + +    /// Returns the [`Transformation`] of the [`Mesh`]. +    pub fn transformation(&self) -> Transformation {          match self { -            Self::Solid { size, .. } | Self::Gradient { size, .. } => { -                Rectangle::with_size(*size) +            Self::Solid { transformation, .. } +            | Self::Gradient { transformation, .. } => *transformation, +        } +    } + +    /// Returns the clip bounds of the [`Mesh`]. +    pub fn clip_bounds(&self) -> Rectangle { +        match self { +            Self::Solid { +                clip_bounds, +                transformation, +                ..              } +            | Self::Gradient { +                clip_bounds, +                transformation, +                .. +            } => *clip_bounds * *transformation,          }      }  } @@ -74,3 +99,50 @@ pub struct GradientVertex2D {      /// The packed vertex data of the gradient.      pub gradient: gradient::Packed,  } + +/// The result of counting the attributes of a set of meshes. +#[derive(Debug, Clone, Copy, Default)] +pub struct AttributeCount { +    /// The total amount of solid vertices. +    pub solid_vertices: usize, + +    /// The total amount of solid meshes. +    pub solids: usize, + +    /// The total amount of gradient vertices. +    pub gradient_vertices: usize, + +    /// The total amount of gradient meshes. +    pub gradients: usize, + +    /// The total amount of indices. +    pub indices: usize, +} + +/// Returns the number of total vertices & total indices of all [`Mesh`]es. +pub fn attribute_count_of(meshes: &[Mesh]) -> AttributeCount { +    meshes +        .iter() +        .fold(AttributeCount::default(), |mut count, mesh| { +            match mesh { +                Mesh::Solid { buffers, .. } => { +                    count.solids += 1; +                    count.solid_vertices += buffers.vertices.len(); +                    count.indices += buffers.indices.len(); +                } +                Mesh::Gradient { buffers, .. } => { +                    count.gradients += 1; +                    count.gradient_vertices += buffers.vertices.len(); +                    count.indices += buffers.indices.len(); +                } +            } + +            count +        }) +} + +/// A renderer capable of drawing a [`Mesh`]. +pub trait Renderer { +    /// Draws the given [`Mesh`]. +    fn draw_mesh(&mut self, mesh: Mesh); +} diff --git a/graphics/src/primitive.rs b/graphics/src/primitive.rs deleted file mode 100644 index 6929b0a1..00000000 --- a/graphics/src/primitive.rs +++ /dev/null @@ -1,160 +0,0 @@ -//! Draw using different graphical primitives. -use crate::core::alignment; -use crate::core::image; -use crate::core::svg; -use crate::core::text; -use crate::core::{ -    Background, Border, Color, Font, Pixels, Point, Rectangle, Shadow, -    Transformation, Vector, -}; -use crate::text::editor; -use crate::text::paragraph; - -use std::sync::Arc; - -/// A rendering primitive. -#[derive(Debug, Clone, PartialEq)] -pub enum Primitive<T> { -    /// A text primitive -    Text { -        /// The contents of the text. -        content: String, -        /// The bounds of the text. -        bounds: Rectangle, -        /// The color of the text. -        color: Color, -        /// The size of the text in logical pixels. -        size: Pixels, -        /// The line height of the text. -        line_height: text::LineHeight, -        /// The font of the text. -        font: Font, -        /// The horizontal alignment of the text. -        horizontal_alignment: alignment::Horizontal, -        /// The vertical alignment of the text. -        vertical_alignment: alignment::Vertical, -        /// The shaping strategy of the text. -        shaping: text::Shaping, -        /// The clip bounds of the text. -        clip_bounds: Rectangle, -    }, -    /// A paragraph primitive -    Paragraph { -        /// The [`paragraph::Weak`] reference. -        paragraph: paragraph::Weak, -        /// The position of the paragraph. -        position: Point, -        /// The color of the paragraph. -        color: Color, -        /// The clip bounds of the paragraph. -        clip_bounds: Rectangle, -    }, -    /// An editor primitive -    Editor { -        /// The [`editor::Weak`] reference. -        editor: editor::Weak, -        /// The position of the editor. -        position: Point, -        /// The color of the editor. -        color: Color, -        /// The clip bounds of the editor. -        clip_bounds: Rectangle, -    }, -    /// A raw `cosmic-text` primitive -    RawText(crate::text::Raw), -    /// A quad primitive -    Quad { -        /// The bounds of the quad -        bounds: Rectangle, -        /// The background of the quad -        background: Background, -        /// The [`Border`] of the quad -        border: Border, -        /// The [`Shadow`] of the quad -        shadow: Shadow, -    }, -    /// An image primitive -    Image { -        /// The handle of the image -        handle: image::Handle, -        /// The filter method of the image -        filter_method: image::FilterMethod, -        /// The bounds of the image -        bounds: Rectangle, -    }, -    /// An SVG primitive -    Svg { -        /// The path of the SVG file -        handle: svg::Handle, - -        /// The [`Color`] filter -        color: Option<Color>, - -        /// The bounds of the viewport -        bounds: Rectangle, -    }, -    /// A group of primitives -    Group { -        /// The primitives of the group -        primitives: Vec<Primitive<T>>, -    }, -    /// A clip primitive -    Clip { -        /// The bounds of the clip -        bounds: Rectangle, -        /// The content of the clip -        content: Box<Primitive<T>>, -    }, -    /// A primitive that applies a [`Transformation`] -    Transform { -        /// The [`Transformation`] -        transformation: Transformation, - -        /// The primitive to transform -        content: Box<Primitive<T>>, -    }, -    /// A cached primitive. -    /// -    /// This can be useful if you are implementing a widget where primitive -    /// generation is expensive. -    Cache { -        /// The cached primitive -        content: Arc<Primitive<T>>, -    }, -    /// A backend-specific primitive. -    Custom(T), -} - -impl<T> Primitive<T> { -    /// Groups the current [`Primitive`]. -    pub fn group(primitives: Vec<Self>) -> Self { -        Self::Group { primitives } -    } - -    /// Clips the current [`Primitive`]. -    pub fn clip(self, bounds: Rectangle) -> Self { -        Self::Clip { -            bounds, -            content: Box::new(self), -        } -    } - -    /// Translates the current [`Primitive`]. -    pub fn translate(self, translation: Vector) -> Self { -        Self::Transform { -            transformation: Transformation::translate( -                translation.x, -                translation.y, -            ), -            content: Box::new(self), -        } -    } - -    /// Transforms the current [`Primitive`]. -    pub fn transform(self, transformation: Transformation) -> Self { -        Self::Transform { -            transformation, -            content: Box::new(self), -        } -    } -} diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs deleted file mode 100644 index 143f348b..00000000 --- a/graphics/src/renderer.rs +++ /dev/null @@ -1,252 +0,0 @@ -//! Create a renderer from a [`Backend`]. -use crate::backend::{self, Backend}; -use crate::core; -use crate::core::image; -use crate::core::renderer; -use crate::core::svg; -use crate::core::text::Text; -use crate::core::{ -    Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation, -}; -use crate::text; -use crate::Primitive; - -use std::borrow::Cow; - -/// A backend-agnostic renderer that supports all the built-in widgets. -#[derive(Debug)] -pub struct Renderer<B: Backend> { -    backend: B, -    default_font: Font, -    default_text_size: Pixels, -    primitives: Vec<Primitive<B::Primitive>>, -} - -impl<B: Backend> Renderer<B> { -    /// Creates a new [`Renderer`] from the given [`Backend`]. -    pub fn new( -        backend: B, -        default_font: Font, -        default_text_size: Pixels, -    ) -> Self { -        Self { -            backend, -            default_font, -            default_text_size, -            primitives: Vec::new(), -        } -    } - -    /// Returns a reference to the [`Backend`] of the [`Renderer`]. -    pub fn backend(&self) -> &B { -        &self.backend -    } - -    /// Enqueues the given [`Primitive`] in the [`Renderer`] for drawing. -    pub fn draw_primitive(&mut self, primitive: Primitive<B::Primitive>) { -        self.primitives.push(primitive); -    } - -    /// Runs the given closure with the [`Backend`] and the recorded primitives -    /// of the [`Renderer`]. -    pub fn with_primitives<O>( -        &mut self, -        f: impl FnOnce(&mut B, &[Primitive<B::Primitive>]) -> O, -    ) -> 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); - -        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) -    } - -    /// 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); - -        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, -        quad: renderer::Quad, -        background: impl Into<Background>, -    ) { -        self.primitives.push(Primitive::Quad { -            bounds: quad.bounds, -            background: background.into(), -            border: quad.border, -            shadow: quad.shadow, -        }); -    } - -    fn clear(&mut self) { -        self.primitives.clear(); -    } -} - -impl<B> core::text::Renderer for Renderer<B> -where -    B: Backend + backend::Text, -{ -    type Font = Font; -    type Paragraph = text::Paragraph; -    type Editor = text::Editor; - -    const ICON_FONT: Font = Font::with_name("Iced-Icons"); -    const CHECKMARK_ICON: char = '\u{f00c}'; -    const ARROW_DOWN_ICON: char = '\u{e800}'; - -    fn default_font(&self) -> Self::Font { -        self.default_font -    } - -    fn default_size(&self) -> Pixels { -        self.default_text_size -    } - -    fn load_font(&mut self, bytes: Cow<'static, [u8]>) { -        self.backend.load_font(bytes); -    } - -    fn fill_paragraph( -        &mut self, -        paragraph: &Self::Paragraph, -        position: Point, -        color: Color, -        clip_bounds: Rectangle, -    ) { -        self.primitives.push(Primitive::Paragraph { -            paragraph: paragraph.downgrade(), -            position, -            color, -            clip_bounds, -        }); -    } - -    fn fill_editor( -        &mut self, -        editor: &Self::Editor, -        position: Point, -        color: Color, -        clip_bounds: Rectangle, -    ) { -        self.primitives.push(Primitive::Editor { -            editor: editor.downgrade(), -            position, -            color, -            clip_bounds, -        }); -    } - -    fn fill_text( -        &mut self, -        text: Text<'_, Self::Font>, -        position: Point, -        color: Color, -        clip_bounds: Rectangle, -    ) { -        self.primitives.push(Primitive::Text { -            content: text.content.to_string(), -            bounds: Rectangle::new(position, text.bounds), -            size: text.size, -            line_height: text.line_height, -            color, -            font: text.font, -            horizontal_alignment: text.horizontal_alignment, -            vertical_alignment: text.vertical_alignment, -            shaping: text.shaping, -            clip_bounds, -        }); -    } -} - -impl<B> image::Renderer for Renderer<B> -where -    B: Backend + backend::Image, -{ -    type Handle = image::Handle; - -    fn dimensions(&self, handle: &image::Handle) -> Size<u32> { -        self.backend().dimensions(handle) -    } - -    fn draw( -        &mut self, -        handle: image::Handle, -        filter_method: image::FilterMethod, -        bounds: Rectangle, -    ) { -        self.primitives.push(Primitive::Image { -            handle, -            filter_method, -            bounds, -        }); -    } -} - -impl<B> svg::Renderer for Renderer<B> -where -    B: Backend + backend::Svg, -{ -    fn dimensions(&self, handle: &svg::Handle) -> Size<u32> { -        self.backend().viewport_dimensions(handle) -    } - -    fn draw( -        &mut self, -        handle: svg::Handle, -        color: Option<Color>, -        bounds: Rectangle, -    ) { -        self.primitives.push(Primitive::Svg { -            handle, -            color, -            bounds, -        }); -    } -} diff --git a/graphics/src/settings.rs b/graphics/src/settings.rs new file mode 100644 index 00000000..2e8275c6 --- /dev/null +++ b/graphics/src/settings.rs @@ -0,0 +1,29 @@ +use crate::core::{Font, Pixels}; +use crate::Antialiasing; + +/// The settings of a renderer. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Settings { +    /// The default [`Font`] to use. +    pub default_font: Font, + +    /// The default size of text. +    /// +    /// By default, it will be set to `16.0`. +    pub default_text_size: Pixels, + +    /// The antialiasing strategy that will be used for triangle primitives. +    /// +    /// By default, it is `None`. +    pub antialiasing: Option<Antialiasing>, +} + +impl Default for Settings { +    fn default() -> Settings { +        Settings { +            default_font: Font::default(), +            default_text_size: Pixels(16.0), +            antialiasing: None, +        } +    } +} diff --git a/graphics/src/text.rs b/graphics/src/text.rs index 0310ead7..30269e69 100644 --- a/graphics/src/text.rs +++ b/graphics/src/text.rs @@ -9,14 +9,141 @@ pub use paragraph::Paragraph;  pub use cosmic_text; +use crate::core::alignment;  use crate::core::font::{self, Font};  use crate::core::text::Shaping; -use crate::core::{Color, Point, Rectangle, Size}; +use crate::core::{Color, Pixels, Point, Rectangle, Size, Transformation};  use once_cell::sync::OnceCell;  use std::borrow::Cow;  use std::sync::{Arc, RwLock, Weak}; +/// A text primitive. +#[derive(Debug, Clone, PartialEq)] +pub enum Text { +    /// A paragraph. +    #[allow(missing_docs)] +    Paragraph { +        paragraph: paragraph::Weak, +        position: Point, +        color: Color, +        clip_bounds: Rectangle, +        transformation: Transformation, +    }, +    /// An editor. +    #[allow(missing_docs)] +    Editor { +        editor: editor::Weak, +        position: Point, +        color: Color, +        clip_bounds: Rectangle, +        transformation: Transformation, +    }, +    /// Some cached text. +    Cached { +        /// The contents of the text. +        content: String, +        /// The bounds of the text. +        bounds: Rectangle, +        /// The color of the text. +        color: Color, +        /// The size of the text in logical pixels. +        size: Pixels, +        /// The line height of the text. +        line_height: Pixels, +        /// The font of the text. +        font: Font, +        /// The horizontal alignment of the text. +        horizontal_alignment: alignment::Horizontal, +        /// The vertical alignment of the text. +        vertical_alignment: alignment::Vertical, +        /// The shaping strategy of the text. +        shaping: Shaping, +        /// The clip bounds of the text. +        clip_bounds: Rectangle, +    }, +    /// Some raw text. +    #[allow(missing_docs)] +    Raw { +        raw: Raw, +        transformation: Transformation, +    }, +} + +impl Text { +    /// Returns the visible bounds of the [`Text`]. +    pub fn visible_bounds(&self) -> Option<Rectangle> { +        let (bounds, horizontal_alignment, vertical_alignment) = match self { +            Text::Paragraph { +                position, +                paragraph, +                clip_bounds, +                transformation, +                .. +            } => ( +                Rectangle::new(*position, paragraph.min_bounds) +                    .intersection(clip_bounds) +                    .map(|bounds| bounds * *transformation), +                Some(paragraph.horizontal_alignment), +                Some(paragraph.vertical_alignment), +            ), +            Text::Editor { +                editor, +                position, +                clip_bounds, +                transformation, +                .. +            } => ( +                Rectangle::new(*position, editor.bounds) +                    .intersection(clip_bounds) +                    .map(|bounds| bounds * *transformation), +                None, +                None, +            ), +            Text::Cached { +                bounds, +                clip_bounds, +                horizontal_alignment, +                vertical_alignment, +                .. +            } => ( +                bounds.intersection(clip_bounds), +                Some(*horizontal_alignment), +                Some(*vertical_alignment), +            ), +            Text::Raw { raw, .. } => (Some(raw.clip_bounds), None, None), +        }; + +        let mut bounds = bounds?; + +        if let Some(alignment) = horizontal_alignment { +            match alignment { +                alignment::Horizontal::Left => {} +                alignment::Horizontal::Center => { +                    bounds.x -= bounds.width / 2.0; +                } +                alignment::Horizontal::Right => { +                    bounds.x -= bounds.width; +                } +            } +        } + +        if let Some(alignment) = vertical_alignment { +            match alignment { +                alignment::Vertical::Top => {} +                alignment::Vertical::Center => { +                    bounds.y -= bounds.height / 2.0; +                } +                alignment::Vertical::Bottom => { +                    bounds.y -= bounds.height; +                } +            } +        } + +        Some(bounds) +    } +} +  /// The regular variant of the [Fira Sans] font.  ///  /// It is loaded as part of the default fonts in Wasm builds. diff --git a/graphics/src/text/cache.rs b/graphics/src/text/cache.rs index 7fb33567..822b61c4 100644 --- a/graphics/src/text/cache.rs +++ b/graphics/src/text/cache.rs @@ -2,22 +2,18 @@  use crate::core::{Font, Size};  use crate::text; -use rustc_hash::{FxHashMap, FxHashSet}; +use rustc_hash::{FxHashMap, FxHashSet, FxHasher};  use std::collections::hash_map; -use std::hash::{BuildHasher, Hash, Hasher}; +use std::hash::{Hash, Hasher};  /// A store of recently used sections of text. -#[allow(missing_debug_implementations)] -#[derive(Default)] +#[derive(Debug, Default)]  pub struct Cache {      entries: FxHashMap<KeyHash, Entry>,      aliases: FxHashMap<KeyHash, KeyHash>,      recently_used: FxHashSet<KeyHash>, -    hasher: HashBuilder,  } -type HashBuilder = xxhash_rust::xxh3::Xxh3Builder; -  impl Cache {      /// Creates a new empty [`Cache`].      pub fn new() -> Self { @@ -35,7 +31,7 @@ impl Cache {          font_system: &mut cosmic_text::FontSystem,          key: Key<'_>,      ) -> (KeyHash, &mut Entry) { -        let hash = key.hash(self.hasher.build_hasher()); +        let hash = key.hash(FxHasher::default());          if let Some(hash) = self.aliases.get(&hash) {              let _ = self.recently_used.insert(*hash); @@ -77,7 +73,7 @@ impl Cache {              ] {                  if key.bounds != bounds {                      let _ = self.aliases.insert( -                        Key { bounds, ..key }.hash(self.hasher.build_hasher()), +                        Key { bounds, ..key }.hash(FxHasher::default()),                          hash,                      );                  } @@ -138,7 +134,7 @@ impl Key<'_> {  pub type KeyHash = u64;  /// A cache entry. -#[allow(missing_debug_implementations)] +#[derive(Debug)]  pub struct Entry {      /// The buffer of text, ready for drawing.      pub buffer: cosmic_text::Buffer, diff --git a/graphics/src/text/editor.rs b/graphics/src/text/editor.rs index c488a51c..4b8f0f2a 100644 --- a/graphics/src/text/editor.rs +++ b/graphics/src/text/editor.rs @@ -456,10 +456,14 @@ impl editor::Editor for Editor {                  }              }              Action::Scroll { lines } => { -                editor.action( -                    font_system.raw(), -                    cosmic_text::Action::Scroll { lines }, -                ); +                let (_, height) = editor.buffer().size(); + +                if height < i32::MAX as f32 { +                    editor.action( +                        font_system.raw(), +                        cosmic_text::Action::Scroll { lines }, +                    ); +                }              }          } diff --git a/graphics/src/text/paragraph.rs b/graphics/src/text/paragraph.rs index 5d027542..31a323ac 100644 --- a/graphics/src/text/paragraph.rs +++ b/graphics/src/text/paragraph.rs @@ -61,7 +61,7 @@ impl Paragraph {  impl core::text::Paragraph for Paragraph {      type Font = Font; -    fn with_text(text: Text<'_, Font>) -> Self { +    fn with_text(text: Text<&str>) -> Self {          log::trace!("Allocating paragraph: {}", text.content);          let mut font_system = @@ -146,7 +146,7 @@ impl core::text::Paragraph for Paragraph {          }      } -    fn compare(&self, text: Text<'_, Font>) -> core::text::Difference { +    fn compare(&self, text: Text<&str>) -> core::text::Difference {          let font_system = text::font_system().read().expect("Read font system");          let paragraph = self.internal();          let metrics = paragraph.buffer.metrics(); | 
