summaryrefslogtreecommitdiffstats
path: root/graphics/src
diff options
context:
space:
mode:
Diffstat (limited to 'graphics/src')
-rw-r--r--graphics/src/backend.rs32
-rw-r--r--graphics/src/cache.rs189
-rw-r--r--graphics/src/compositor.rs108
-rw-r--r--graphics/src/damage.rs240
-rw-r--r--graphics/src/error.rs27
-rw-r--r--graphics/src/geometry.rs31
-rw-r--r--graphics/src/geometry/cache.rs116
-rw-r--r--graphics/src/geometry/frame.rs249
-rw-r--r--graphics/src/image.rs208
-rw-r--r--graphics/src/layer.rs144
-rw-r--r--graphics/src/lib.rs29
-rw-r--r--graphics/src/mesh.rs100
-rw-r--r--graphics/src/primitive.rs160
-rw-r--r--graphics/src/renderer.rs252
-rw-r--r--graphics/src/settings.rs29
-rw-r--r--graphics/src/text.rs129
-rw-r--r--graphics/src/text/cache.rs16
-rw-r--r--graphics/src/text/editor.rs12
-rw-r--r--graphics/src/text/paragraph.rs4
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(&current.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();