Merge branch 'master' into beacon

This commit is contained in:
Héctor Ramón Jiménez 2024-05-09 12:32:25 +02:00
commit aaf396256e
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
284 changed files with 18747 additions and 15450 deletions

View file

@ -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>;
}

189
graphics/src/cache.rs Normal file
View file

@ -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 {
}
}

View file

@ -1,29 +1,40 @@
//! 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 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,
) -> Result<Self, Error>;
) -> 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`].
fn create_renderer(&self) -> Self::Renderer;
@ -51,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
@ -90,6 +109,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 {
@ -119,3 +144,69 @@ pub struct Information {
/// Contains the graphics backend.
pub backend: String,
}
#[cfg(debug_assertions)]
impl Compositor for () {
type Renderer = ();
type Surface = ();
async fn with_backend<W: Window + Clone>(
_settings: Settings,
_compatible_window: W,
_preffered_backend: Option<&str>,
) -> Result<Self, Error> {
Ok(())
}
fn create_renderer(&self) -> Self::Renderer {}
fn create_surface<W: Window + Clone>(
&mut self,
_window: W,
_width: u32,
_height: u32,
) -> Self::Surface {
}
fn configure_surface(
&mut self,
_surface: &mut Self::Surface,
_width: u32,
_height: u32,
) {
}
fn 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(
&mut self,
_renderer: &mut Self::Renderer,
_surface: &mut Self::Surface,
_viewport: &Viewport,
_background_color: Color,
) -> Result<(), SurfaceError> {
Ok(())
}
fn screenshot(
&mut self,
_renderer: &mut Self::Renderer,
_surface: &mut Self::Surface,
_viewport: &Viewport,
_background_color: Color,
) -> Vec<u8> {
vec![]
}
}
#[cfg(debug_assertions)]
impl Default for () {
type Compositor = ();
}

View file

@ -1,196 +1,14 @@
//! Track and compute the damage of graphical primitives.
use crate::core::alignment;
use crate::core::{Rectangle, Size};
use crate::Primitive;
//! Compute the damage between frames.
use crate::core::{Point, Rectangle};
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>],
/// 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() {

View file

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

View file

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

View file

@ -0,0 +1,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()
}
}

View file

@ -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 {}
}

View file

@ -1,14 +1,121 @@
//! Load and operate on images.
use crate::core::image::{Data, Handle};
use bitflags::bitflags;
#[cfg(feature = "image")]
pub use ::image as image_rs;
use crate::core::{image, svg, Color, Radians, Rectangle};
/// 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);
}
image
..
} => (*width, *height, pixels.clone()),
};
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,
),
))
}
}

144
graphics/src/layer.rs Normal file
View file

@ -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()
}
}

View file

@ -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;

View file

@ -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 { size, .. } | Self::Gradient { size, .. } => {
Rectangle::with_size(*size)
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 { 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);
}

View file

@ -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),
}
}
}

View file

@ -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,
});
}
}

29
graphics/src/settings.rs Normal file
View file

@ -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,
}
}
}

View file

@ -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.

View file

@ -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,

View file

@ -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 },
);
}
}
}

View file

@ -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();