Merge branch 'master' into beacon
This commit is contained in:
commit
aaf396256e
284 changed files with 18747 additions and 15450 deletions
|
|
@ -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
189
graphics/src/cache.rs
Normal 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 {
|
||||
}
|
||||
}
|
||||
|
|
@ -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 = ();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {}
|
||||
}
|
||||
|
|
|
|||
116
graphics/src/geometry/cache.rs
Normal file
116
graphics/src/geometry/cache.rs
Normal 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(¤t.geometry);
|
||||
}
|
||||
|
||||
Some(current.geometry.clone())
|
||||
}
|
||||
};
|
||||
|
||||
let mut frame = Frame::new(renderer, bounds);
|
||||
draw_fn(&mut frame);
|
||||
|
||||
let geometry = frame.into_geometry().cache(self.raw.group(), previous);
|
||||
let result = Cached::load(&geometry);
|
||||
|
||||
*state.borrow_mut() = cache::State::Filled {
|
||||
current: Data { bounds, geometry },
|
||||
};
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl<Renderer> std::fmt::Debug for Cache<Renderer>
|
||||
where
|
||||
Renderer: geometry::Renderer,
|
||||
<Renderer::Geometry as Cached>::Cache: std::fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:?}", &self.raw)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Renderer> Default for Cache<Renderer>
|
||||
where
|
||||
Renderer: geometry::Renderer,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
249
graphics/src/geometry/frame.rs
Normal file
249
graphics/src/geometry/frame.rs
Normal 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 {}
|
||||
}
|
||||
|
|
@ -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
144
graphics/src/layer.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
29
graphics/src/settings.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue