Merge pull request #2351 from iced-rs/custom-renderer-injection
Type-Driven Renderer Fallback
This commit is contained in:
commit
a2a8381a49
56 changed files with 1994 additions and 1555 deletions
|
|
@ -32,6 +32,7 @@ glyphon.workspace = true
|
|||
guillotiere.workspace = true
|
||||
log.workspace = true
|
||||
once_cell.workspace = true
|
||||
thiserror.workspace = true
|
||||
wgpu.workspace = true
|
||||
|
||||
lyon.workspace = true
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use crate::primitive::{self, Primitive};
|
|||
use crate::quad;
|
||||
use crate::text;
|
||||
use crate::triangle;
|
||||
use crate::window;
|
||||
use crate::{Layer, Settings};
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
|
|
@ -371,8 +372,9 @@ impl Backend {
|
|||
}
|
||||
}
|
||||
|
||||
impl crate::graphics::Backend for Backend {
|
||||
impl backend::Backend for Backend {
|
||||
type Primitive = primitive::Custom;
|
||||
type Compositor = window::Compositor;
|
||||
}
|
||||
|
||||
impl backend::Text for Backend {
|
||||
|
|
@ -397,3 +399,12 @@ impl backend::Svg for Backend {
|
|||
self.image_pipeline.viewport_dimensions(handle)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "geometry")]
|
||||
impl crate::graphics::geometry::Backend for Backend {
|
||||
type Frame = crate::geometry::Frame;
|
||||
|
||||
fn new_frame(&self, size: Size) -> Self::Frame {
|
||||
crate::geometry::Frame::new(size)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use crate::core::{
|
|||
use crate::graphics::color;
|
||||
use crate::graphics::geometry::fill::{self, Fill};
|
||||
use crate::graphics::geometry::{
|
||||
LineCap, LineDash, LineJoin, Path, Stroke, Style, Text,
|
||||
self, LineCap, LineDash, LineJoin, Path, Stroke, Style, Text,
|
||||
};
|
||||
use crate::graphics::gradient::{self, Gradient};
|
||||
use crate::graphics::mesh::{self, Mesh};
|
||||
|
|
@ -14,6 +14,7 @@ use crate::primitive::{self, Primitive};
|
|||
|
||||
use lyon::geom::euclid;
|
||||
use lyon::tessellation;
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// A frame for drawing some geometry.
|
||||
|
|
@ -27,6 +28,325 @@ pub struct Frame {
|
|||
stroke_tessellator: tessellation::StrokeTessellator,
|
||||
}
|
||||
|
||||
impl Frame {
|
||||
/// Creates a new [`Frame`] with the given [`Size`].
|
||||
pub fn new(size: Size) -> Frame {
|
||||
Frame {
|
||||
size,
|
||||
buffers: BufferStack::new(),
|
||||
primitives: Vec::new(),
|
||||
transforms: Transforms {
|
||||
previous: Vec::new(),
|
||||
current: Transform(lyon::math::Transform::identity()),
|
||||
},
|
||||
fill_tessellator: tessellation::FillTessellator::new(),
|
||||
stroke_tessellator: tessellation::StrokeTessellator::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn into_primitives(mut self) -> Vec<Primitive> {
|
||||
for buffer in self.buffers.stack {
|
||||
match buffer {
|
||||
Buffer::Solid(buffer) => {
|
||||
if !buffer.indices.is_empty() {
|
||||
self.primitives.push(Primitive::Custom(
|
||||
primitive::Custom::Mesh(Mesh::Solid {
|
||||
buffers: mesh::Indexed {
|
||||
vertices: buffer.vertices,
|
||||
indices: buffer.indices,
|
||||
},
|
||||
size: self.size,
|
||||
}),
|
||||
));
|
||||
}
|
||||
}
|
||||
Buffer::Gradient(buffer) => {
|
||||
if !buffer.indices.is_empty() {
|
||||
self.primitives.push(Primitive::Custom(
|
||||
primitive::Custom::Mesh(Mesh::Gradient {
|
||||
buffers: mesh::Indexed {
|
||||
vertices: buffer.vertices,
|
||||
indices: buffer.indices,
|
||||
},
|
||||
size: self.size,
|
||||
}),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.primitives
|
||||
}
|
||||
}
|
||||
|
||||
impl geometry::frame::Backend for Frame {
|
||||
type Geometry = Primitive;
|
||||
|
||||
/// Creates a new empty [`Frame`] with the given dimensions.
|
||||
///
|
||||
/// The default coordinate system of a [`Frame`] has its origin at the
|
||||
/// top-left corner of its bounds.
|
||||
|
||||
#[inline]
|
||||
fn width(&self) -> f32 {
|
||||
self.size.width
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn height(&self) -> f32 {
|
||||
self.size.height
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size(&self) -> Size {
|
||||
self.size
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn center(&self) -> Point {
|
||||
Point::new(self.size.width / 2.0, self.size.height / 2.0)
|
||||
}
|
||||
|
||||
fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
|
||||
let Fill { style, rule } = fill.into();
|
||||
|
||||
let mut buffer = self
|
||||
.buffers
|
||||
.get_fill(&self.transforms.current.transform_style(style));
|
||||
|
||||
let options = tessellation::FillOptions::default()
|
||||
.with_fill_rule(into_fill_rule(rule));
|
||||
|
||||
if self.transforms.current.is_identity() {
|
||||
self.fill_tessellator.tessellate_path(
|
||||
path.raw(),
|
||||
&options,
|
||||
buffer.as_mut(),
|
||||
)
|
||||
} else {
|
||||
let path = path.transform(&self.transforms.current.0);
|
||||
|
||||
self.fill_tessellator.tessellate_path(
|
||||
path.raw(),
|
||||
&options,
|
||||
buffer.as_mut(),
|
||||
)
|
||||
}
|
||||
.expect("Tessellate path.");
|
||||
}
|
||||
|
||||
fn fill_rectangle(
|
||||
&mut self,
|
||||
top_left: Point,
|
||||
size: Size,
|
||||
fill: impl Into<Fill>,
|
||||
) {
|
||||
let Fill { style, rule } = fill.into();
|
||||
|
||||
let mut buffer = self
|
||||
.buffers
|
||||
.get_fill(&self.transforms.current.transform_style(style));
|
||||
|
||||
let top_left = self
|
||||
.transforms
|
||||
.current
|
||||
.0
|
||||
.transform_point(lyon::math::Point::new(top_left.x, top_left.y));
|
||||
|
||||
let size =
|
||||
self.transforms.current.0.transform_vector(
|
||||
lyon::math::Vector::new(size.width, size.height),
|
||||
);
|
||||
|
||||
let options = tessellation::FillOptions::default()
|
||||
.with_fill_rule(into_fill_rule(rule));
|
||||
|
||||
self.fill_tessellator
|
||||
.tessellate_rectangle(
|
||||
&lyon::math::Box2D::new(top_left, top_left + size),
|
||||
&options,
|
||||
buffer.as_mut(),
|
||||
)
|
||||
.expect("Fill rectangle");
|
||||
}
|
||||
|
||||
fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) {
|
||||
let stroke = stroke.into();
|
||||
|
||||
let mut buffer = self
|
||||
.buffers
|
||||
.get_stroke(&self.transforms.current.transform_style(stroke.style));
|
||||
|
||||
let mut options = tessellation::StrokeOptions::default();
|
||||
options.line_width = stroke.width;
|
||||
options.start_cap = into_line_cap(stroke.line_cap);
|
||||
options.end_cap = into_line_cap(stroke.line_cap);
|
||||
options.line_join = into_line_join(stroke.line_join);
|
||||
|
||||
let path = if stroke.line_dash.segments.is_empty() {
|
||||
Cow::Borrowed(path)
|
||||
} else {
|
||||
Cow::Owned(dashed(path, stroke.line_dash))
|
||||
};
|
||||
|
||||
if self.transforms.current.is_identity() {
|
||||
self.stroke_tessellator.tessellate_path(
|
||||
path.raw(),
|
||||
&options,
|
||||
buffer.as_mut(),
|
||||
)
|
||||
} else {
|
||||
let path = path.transform(&self.transforms.current.0);
|
||||
|
||||
self.stroke_tessellator.tessellate_path(
|
||||
path.raw(),
|
||||
&options,
|
||||
buffer.as_mut(),
|
||||
)
|
||||
}
|
||||
.expect("Stroke path");
|
||||
}
|
||||
|
||||
fn fill_text(&mut self, text: impl Into<Text>) {
|
||||
let text = text.into();
|
||||
|
||||
let (scale_x, scale_y) = self.transforms.current.scale();
|
||||
|
||||
if self.transforms.current.is_scale_translation()
|
||||
&& scale_x == scale_y
|
||||
&& scale_x > 0.0
|
||||
&& scale_y > 0.0
|
||||
{
|
||||
let (position, size, line_height) =
|
||||
if self.transforms.current.is_identity() {
|
||||
(text.position, text.size, text.line_height)
|
||||
} else {
|
||||
let position =
|
||||
self.transforms.current.transform_point(text.position);
|
||||
|
||||
let size = Pixels(text.size.0 * scale_y);
|
||||
|
||||
let line_height = match text.line_height {
|
||||
LineHeight::Absolute(size) => {
|
||||
LineHeight::Absolute(Pixels(size.0 * scale_y))
|
||||
}
|
||||
LineHeight::Relative(factor) => {
|
||||
LineHeight::Relative(factor)
|
||||
}
|
||||
};
|
||||
|
||||
(position, size, line_height)
|
||||
};
|
||||
|
||||
let bounds = Rectangle {
|
||||
x: position.x,
|
||||
y: position.y,
|
||||
width: f32::INFINITY,
|
||||
height: f32::INFINITY,
|
||||
};
|
||||
|
||||
// TODO: Honor layering!
|
||||
self.primitives.push(Primitive::Text {
|
||||
content: text.content,
|
||||
bounds,
|
||||
color: text.color,
|
||||
size,
|
||||
line_height,
|
||||
font: text.font,
|
||||
horizontal_alignment: text.horizontal_alignment,
|
||||
vertical_alignment: text.vertical_alignment,
|
||||
shaping: text.shaping,
|
||||
clip_bounds: Rectangle::with_size(Size::INFINITY),
|
||||
});
|
||||
} else {
|
||||
text.draw_with(|path, color| self.fill(&path, color));
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn translate(&mut self, translation: Vector) {
|
||||
self.transforms.current.0 =
|
||||
self.transforms
|
||||
.current
|
||||
.0
|
||||
.pre_translate(lyon::math::Vector::new(
|
||||
translation.x,
|
||||
translation.y,
|
||||
));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn rotate(&mut self, angle: impl Into<Radians>) {
|
||||
self.transforms.current.0 = self
|
||||
.transforms
|
||||
.current
|
||||
.0
|
||||
.pre_rotate(lyon::math::Angle::radians(angle.into().0));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn scale(&mut self, scale: impl Into<f32>) {
|
||||
let scale = scale.into();
|
||||
|
||||
self.scale_nonuniform(Vector { x: scale, y: scale });
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn scale_nonuniform(&mut self, scale: impl Into<Vector>) {
|
||||
let scale = scale.into();
|
||||
|
||||
self.transforms.current.0 =
|
||||
self.transforms.current.0.pre_scale(scale.x, scale.y);
|
||||
}
|
||||
|
||||
fn push_transform(&mut self) {
|
||||
self.transforms.previous.push(self.transforms.current);
|
||||
}
|
||||
|
||||
fn pop_transform(&mut self) {
|
||||
self.transforms.current = self.transforms.previous.pop().unwrap();
|
||||
}
|
||||
|
||||
fn draft(&mut self, size: Size) -> Frame {
|
||||
Frame::new(size)
|
||||
}
|
||||
|
||||
fn paste(&mut self, frame: Frame, at: Point) {
|
||||
let size = frame.size();
|
||||
let primitives = frame.into_primitives();
|
||||
let transformation = Transformation::translate(at.x, at.y);
|
||||
|
||||
let (text, meshes) = primitives
|
||||
.into_iter()
|
||||
.partition(|primitive| matches!(primitive, Primitive::Text { .. }));
|
||||
|
||||
self.primitives.push(Primitive::Group {
|
||||
primitives: vec![
|
||||
Primitive::Transform {
|
||||
transformation,
|
||||
content: Box::new(Primitive::Group { primitives: meshes }),
|
||||
},
|
||||
Primitive::Transform {
|
||||
transformation,
|
||||
content: Box::new(Primitive::Clip {
|
||||
bounds: Rectangle::with_size(size),
|
||||
content: Box::new(Primitive::Group {
|
||||
primitives: text,
|
||||
}),
|
||||
}),
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
fn into_geometry(self) -> Self::Geometry {
|
||||
Primitive::Group {
|
||||
primitives: self.into_primitives(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum Buffer {
|
||||
Solid(tessellation::VertexBuffers<mesh::SolidVertex2D, u32>),
|
||||
Gradient(tessellation::VertexBuffers<mesh::GradientVertex2D, u32>),
|
||||
|
|
@ -165,386 +485,6 @@ impl Transform {
|
|||
gradient
|
||||
}
|
||||
}
|
||||
|
||||
impl Frame {
|
||||
/// Creates a new empty [`Frame`] with the given dimensions.
|
||||
///
|
||||
/// The default coordinate system of a [`Frame`] has its origin at the
|
||||
/// top-left corner of its bounds.
|
||||
pub fn new(size: Size) -> Frame {
|
||||
Frame {
|
||||
size,
|
||||
buffers: BufferStack::new(),
|
||||
primitives: Vec::new(),
|
||||
transforms: Transforms {
|
||||
previous: Vec::new(),
|
||||
current: Transform(lyon::math::Transform::identity()),
|
||||
},
|
||||
fill_tessellator: tessellation::FillTessellator::new(),
|
||||
stroke_tessellator: tessellation::StrokeTessellator::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the width of the [`Frame`].
|
||||
#[inline]
|
||||
pub fn width(&self) -> f32 {
|
||||
self.size.width
|
||||
}
|
||||
|
||||
/// Returns the height of the [`Frame`].
|
||||
#[inline]
|
||||
pub fn height(&self) -> f32 {
|
||||
self.size.height
|
||||
}
|
||||
|
||||
/// Returns the dimensions of the [`Frame`].
|
||||
#[inline]
|
||||
pub fn size(&self) -> Size {
|
||||
self.size
|
||||
}
|
||||
|
||||
/// Returns the coordinate of the center of the [`Frame`].
|
||||
#[inline]
|
||||
pub fn center(&self) -> Point {
|
||||
Point::new(self.size.width / 2.0, self.size.height / 2.0)
|
||||
}
|
||||
|
||||
/// Draws the given [`Path`] on the [`Frame`] by filling it with the
|
||||
/// provided style.
|
||||
pub fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
|
||||
let Fill { style, rule } = fill.into();
|
||||
|
||||
let mut buffer = self
|
||||
.buffers
|
||||
.get_fill(&self.transforms.current.transform_style(style));
|
||||
|
||||
let options = tessellation::FillOptions::default()
|
||||
.with_fill_rule(into_fill_rule(rule));
|
||||
|
||||
if self.transforms.current.is_identity() {
|
||||
self.fill_tessellator.tessellate_path(
|
||||
path.raw(),
|
||||
&options,
|
||||
buffer.as_mut(),
|
||||
)
|
||||
} else {
|
||||
let path = path.transform(&self.transforms.current.0);
|
||||
|
||||
self.fill_tessellator.tessellate_path(
|
||||
path.raw(),
|
||||
&options,
|
||||
buffer.as_mut(),
|
||||
)
|
||||
}
|
||||
.expect("Tessellate path.");
|
||||
}
|
||||
|
||||
/// Draws an axis-aligned rectangle given its top-left corner coordinate and
|
||||
/// its `Size` on the [`Frame`] by filling it with the provided style.
|
||||
pub fn fill_rectangle(
|
||||
&mut self,
|
||||
top_left: Point,
|
||||
size: Size,
|
||||
fill: impl Into<Fill>,
|
||||
) {
|
||||
let Fill { style, rule } = fill.into();
|
||||
|
||||
let mut buffer = self
|
||||
.buffers
|
||||
.get_fill(&self.transforms.current.transform_style(style));
|
||||
|
||||
let top_left = self
|
||||
.transforms
|
||||
.current
|
||||
.0
|
||||
.transform_point(lyon::math::Point::new(top_left.x, top_left.y));
|
||||
|
||||
let size =
|
||||
self.transforms.current.0.transform_vector(
|
||||
lyon::math::Vector::new(size.width, size.height),
|
||||
);
|
||||
|
||||
let options = tessellation::FillOptions::default()
|
||||
.with_fill_rule(into_fill_rule(rule));
|
||||
|
||||
self.fill_tessellator
|
||||
.tessellate_rectangle(
|
||||
&lyon::math::Box2D::new(top_left, top_left + size),
|
||||
&options,
|
||||
buffer.as_mut(),
|
||||
)
|
||||
.expect("Fill rectangle");
|
||||
}
|
||||
|
||||
/// Draws the stroke of the given [`Path`] on the [`Frame`] with the
|
||||
/// provided style.
|
||||
pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) {
|
||||
let stroke = stroke.into();
|
||||
|
||||
let mut buffer = self
|
||||
.buffers
|
||||
.get_stroke(&self.transforms.current.transform_style(stroke.style));
|
||||
|
||||
let mut options = tessellation::StrokeOptions::default();
|
||||
options.line_width = stroke.width;
|
||||
options.start_cap = into_line_cap(stroke.line_cap);
|
||||
options.end_cap = into_line_cap(stroke.line_cap);
|
||||
options.line_join = into_line_join(stroke.line_join);
|
||||
|
||||
let path = if stroke.line_dash.segments.is_empty() {
|
||||
Cow::Borrowed(path)
|
||||
} else {
|
||||
Cow::Owned(dashed(path, stroke.line_dash))
|
||||
};
|
||||
|
||||
if self.transforms.current.is_identity() {
|
||||
self.stroke_tessellator.tessellate_path(
|
||||
path.raw(),
|
||||
&options,
|
||||
buffer.as_mut(),
|
||||
)
|
||||
} else {
|
||||
let path = path.transform(&self.transforms.current.0);
|
||||
|
||||
self.stroke_tessellator.tessellate_path(
|
||||
path.raw(),
|
||||
&options,
|
||||
buffer.as_mut(),
|
||||
)
|
||||
}
|
||||
.expect("Stroke path");
|
||||
}
|
||||
|
||||
/// Draws the characters of the given [`Text`] on the [`Frame`], filling
|
||||
/// them with the given color.
|
||||
///
|
||||
/// __Warning:__ Text currently does not work well with rotations and scale
|
||||
/// transforms! The position will be correctly transformed, but the
|
||||
/// resulting glyphs will not be rotated or scaled properly.
|
||||
///
|
||||
/// Additionally, all text will be rendered on top of all the layers of
|
||||
/// a `Canvas`. Therefore, it is currently only meant to be used for
|
||||
/// overlays, which is the most common use case.
|
||||
///
|
||||
/// Support for vectorial text is planned, and should address all these
|
||||
/// limitations.
|
||||
pub fn fill_text(&mut self, text: impl Into<Text>) {
|
||||
let text = text.into();
|
||||
|
||||
let (scale_x, scale_y) = self.transforms.current.scale();
|
||||
|
||||
if self.transforms.current.is_scale_translation()
|
||||
&& scale_x == scale_y
|
||||
&& scale_x > 0.0
|
||||
&& scale_y > 0.0
|
||||
{
|
||||
let (position, size, line_height) =
|
||||
if self.transforms.current.is_identity() {
|
||||
(text.position, text.size, text.line_height)
|
||||
} else {
|
||||
let position =
|
||||
self.transforms.current.transform_point(text.position);
|
||||
|
||||
let size = Pixels(text.size.0 * scale_y);
|
||||
|
||||
let line_height = match text.line_height {
|
||||
LineHeight::Absolute(size) => {
|
||||
LineHeight::Absolute(Pixels(size.0 * scale_y))
|
||||
}
|
||||
LineHeight::Relative(factor) => {
|
||||
LineHeight::Relative(factor)
|
||||
}
|
||||
};
|
||||
|
||||
(position, size, line_height)
|
||||
};
|
||||
|
||||
let bounds = Rectangle {
|
||||
x: position.x,
|
||||
y: position.y,
|
||||
width: f32::INFINITY,
|
||||
height: f32::INFINITY,
|
||||
};
|
||||
|
||||
// TODO: Honor layering!
|
||||
self.primitives.push(Primitive::Text {
|
||||
content: text.content,
|
||||
bounds,
|
||||
color: text.color,
|
||||
size,
|
||||
line_height,
|
||||
font: text.font,
|
||||
horizontal_alignment: text.horizontal_alignment,
|
||||
vertical_alignment: text.vertical_alignment,
|
||||
shaping: text.shaping,
|
||||
clip_bounds: Rectangle::with_size(Size::INFINITY),
|
||||
});
|
||||
} else {
|
||||
text.draw_with(|path, color| self.fill(&path, color));
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores the current transform of the [`Frame`] and executes the given
|
||||
/// drawing operations, restoring the transform afterwards.
|
||||
///
|
||||
/// This method is useful to compose transforms and perform drawing
|
||||
/// operations in different coordinate systems.
|
||||
#[inline]
|
||||
pub fn with_save<R>(&mut self, f: impl FnOnce(&mut Frame) -> R) -> R {
|
||||
self.push_transform();
|
||||
|
||||
let result = f(self);
|
||||
|
||||
self.pop_transform();
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Pushes the current transform in the transform stack.
|
||||
pub fn push_transform(&mut self) {
|
||||
self.transforms.previous.push(self.transforms.current);
|
||||
}
|
||||
|
||||
/// Pops a transform from the transform stack and sets it as the current transform.
|
||||
pub fn pop_transform(&mut self) {
|
||||
self.transforms.current = self.transforms.previous.pop().unwrap();
|
||||
}
|
||||
|
||||
/// Executes the given drawing operations within a [`Rectangle`] region,
|
||||
/// clipping any geometry that overflows its bounds. Any transformations
|
||||
/// performed are local to the provided closure.
|
||||
///
|
||||
/// This method is useful to perform drawing operations that need to be
|
||||
/// clipped.
|
||||
#[inline]
|
||||
pub fn with_clip<R>(
|
||||
&mut self,
|
||||
region: Rectangle,
|
||||
f: impl FnOnce(&mut Frame) -> R,
|
||||
) -> R {
|
||||
let mut frame = Frame::new(region.size());
|
||||
|
||||
let result = f(&mut frame);
|
||||
|
||||
let origin = Point::new(region.x, region.y);
|
||||
|
||||
self.clip(frame, origin);
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Draws the clipped contents of the given [`Frame`] with origin at the given [`Point`].
|
||||
pub fn clip(&mut self, frame: Frame, at: Point) {
|
||||
let size = frame.size();
|
||||
let primitives = frame.into_primitives();
|
||||
let transformation = Transformation::translate(at.x, at.y);
|
||||
|
||||
let (text, meshes) = primitives
|
||||
.into_iter()
|
||||
.partition(|primitive| matches!(primitive, Primitive::Text { .. }));
|
||||
|
||||
self.primitives.push(Primitive::Group {
|
||||
primitives: vec![
|
||||
Primitive::Transform {
|
||||
transformation,
|
||||
content: Box::new(Primitive::Group { primitives: meshes }),
|
||||
},
|
||||
Primitive::Transform {
|
||||
transformation,
|
||||
content: Box::new(Primitive::Clip {
|
||||
bounds: Rectangle::with_size(size),
|
||||
content: Box::new(Primitive::Group {
|
||||
primitives: text,
|
||||
}),
|
||||
}),
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
/// Applies a translation to the current transform of the [`Frame`].
|
||||
#[inline]
|
||||
pub fn translate(&mut self, translation: Vector) {
|
||||
self.transforms.current.0 =
|
||||
self.transforms
|
||||
.current
|
||||
.0
|
||||
.pre_translate(lyon::math::Vector::new(
|
||||
translation.x,
|
||||
translation.y,
|
||||
));
|
||||
}
|
||||
|
||||
/// Applies a rotation in radians to the current transform of the [`Frame`].
|
||||
#[inline]
|
||||
pub fn rotate(&mut self, angle: impl Into<Radians>) {
|
||||
self.transforms.current.0 = self
|
||||
.transforms
|
||||
.current
|
||||
.0
|
||||
.pre_rotate(lyon::math::Angle::radians(angle.into().0));
|
||||
}
|
||||
|
||||
/// Applies a uniform scaling to the current transform of the [`Frame`].
|
||||
#[inline]
|
||||
pub fn scale(&mut self, scale: impl Into<f32>) {
|
||||
let scale = scale.into();
|
||||
|
||||
self.scale_nonuniform(Vector { x: scale, y: scale });
|
||||
}
|
||||
|
||||
/// Applies a non-uniform scaling to the current transform of the [`Frame`].
|
||||
#[inline]
|
||||
pub fn scale_nonuniform(&mut self, scale: impl Into<Vector>) {
|
||||
let scale = scale.into();
|
||||
|
||||
self.transforms.current.0 =
|
||||
self.transforms.current.0.pre_scale(scale.x, scale.y);
|
||||
}
|
||||
|
||||
/// Produces the [`Primitive`] representing everything drawn on the [`Frame`].
|
||||
pub fn into_primitive(self) -> Primitive {
|
||||
Primitive::Group {
|
||||
primitives: self.into_primitives(),
|
||||
}
|
||||
}
|
||||
|
||||
fn into_primitives(mut self) -> Vec<Primitive> {
|
||||
for buffer in self.buffers.stack {
|
||||
match buffer {
|
||||
Buffer::Solid(buffer) => {
|
||||
if !buffer.indices.is_empty() {
|
||||
self.primitives.push(Primitive::Custom(
|
||||
primitive::Custom::Mesh(Mesh::Solid {
|
||||
buffers: mesh::Indexed {
|
||||
vertices: buffer.vertices,
|
||||
indices: buffer.indices,
|
||||
},
|
||||
size: self.size,
|
||||
}),
|
||||
));
|
||||
}
|
||||
}
|
||||
Buffer::Gradient(buffer) => {
|
||||
if !buffer.indices.is_empty() {
|
||||
self.primitives.push(Primitive::Custom(
|
||||
primitive::Custom::Mesh(Mesh::Gradient {
|
||||
buffers: mesh::Indexed {
|
||||
vertices: buffer.vertices,
|
||||
indices: buffer.indices,
|
||||
},
|
||||
size: self.size,
|
||||
}),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.primitives
|
||||
}
|
||||
}
|
||||
|
||||
struct GradientVertex2DBuilder {
|
||||
gradient: gradient::Packed,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,3 +28,11 @@ impl Damage for Custom {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Mesh> for Custom {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(mesh: Mesh) -> Result<Self, Self::Error> {
|
||||
Ok(Custom::Mesh(mesh))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
//! Draw primitives using custom pipelines.
|
||||
use crate::core::{Rectangle, Size};
|
||||
use crate::core::{self, Rectangle, Size};
|
||||
|
||||
use std::any::{Any, TypeId};
|
||||
use std::collections::HashMap;
|
||||
|
|
@ -58,7 +58,7 @@ pub trait Primitive: Debug + Send + Sync + 'static {
|
|||
}
|
||||
|
||||
/// A renderer than can draw custom pipeline primitives.
|
||||
pub trait Renderer: crate::core::Renderer {
|
||||
pub trait Renderer: core::Renderer {
|
||||
/// Draws a custom pipeline primitive.
|
||||
fn draw_pipeline_primitive(
|
||||
&mut self,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
//! Configure a renderer.
|
||||
use crate::core::{Font, Pixels};
|
||||
use crate::graphics::Antialiasing;
|
||||
use crate::graphics::{self, Antialiasing};
|
||||
|
||||
/// The settings of a [`Backend`].
|
||||
///
|
||||
|
|
@ -29,30 +29,6 @@ pub struct Settings {
|
|||
pub antialiasing: Option<Antialiasing>,
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
/// Creates new [`Settings`] using environment configuration.
|
||||
///
|
||||
/// Specifically:
|
||||
///
|
||||
/// - The `internal_backend` can be configured using the `WGPU_BACKEND`
|
||||
/// environment variable. If the variable is not set, the primary backend
|
||||
/// will be used. The following values are allowed:
|
||||
/// - `vulkan`
|
||||
/// - `metal`
|
||||
/// - `dx12`
|
||||
/// - `dx11`
|
||||
/// - `gl`
|
||||
/// - `webgpu`
|
||||
/// - `primary`
|
||||
pub fn from_env() -> Self {
|
||||
Settings {
|
||||
internal_backend: wgpu::util::backend_bits_from_env()
|
||||
.unwrap_or(wgpu::Backends::all()),
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
fn default() -> Settings {
|
||||
Settings {
|
||||
|
|
@ -64,3 +40,14 @@ impl Default for Settings {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<graphics::Settings> for Settings {
|
||||
fn from(settings: graphics::Settings) -> Self {
|
||||
Self {
|
||||
default_font: settings.default_font,
|
||||
default_text_size: settings.default_text_size,
|
||||
antialiasing: settings.antialiasing,
|
||||
..Settings::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
//! Connect a window with a renderer.
|
||||
use crate::core::{Color, Size};
|
||||
use crate::graphics;
|
||||
use crate::graphics::color;
|
||||
use crate::graphics::compositor;
|
||||
use crate::graphics::{Error, Viewport};
|
||||
use crate::graphics::error;
|
||||
use crate::graphics::{self, Viewport};
|
||||
use crate::{Backend, Primitive, Renderer, Settings};
|
||||
|
||||
use std::future::Future;
|
||||
|
||||
/// A window graphics backend for iced powered by `wgpu`.
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Compositor {
|
||||
|
|
@ -20,6 +18,32 @@ pub struct Compositor {
|
|||
alpha_mode: wgpu::CompositeAlphaMode,
|
||||
}
|
||||
|
||||
/// A compositor error.
|
||||
#[derive(Debug, Clone, thiserror::Error)]
|
||||
pub enum Error {
|
||||
/// The surface creation failed.
|
||||
#[error("the surface creation failed: {0}")]
|
||||
SurfaceCreationFailed(#[from] wgpu::CreateSurfaceError),
|
||||
/// The surface is not compatible.
|
||||
#[error("the surface is not compatible")]
|
||||
IncompatibleSurface,
|
||||
/// No adapter was found for the options requested.
|
||||
#[error("no adapter was found for the options requested: {0:?}")]
|
||||
NoAdapterFound(String),
|
||||
/// No device request succeeded.
|
||||
#[error("no device request succeeded: {0:?}")]
|
||||
RequestDeviceFailed(Vec<(wgpu::Limits, wgpu::RequestDeviceError)>),
|
||||
}
|
||||
|
||||
impl From<Error> for graphics::Error {
|
||||
fn from(error: Error) -> Self {
|
||||
Self::GraphicsAdapterNotFound {
|
||||
backend: "wgpu",
|
||||
reason: error::Reason::RequestFailed(error.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Compositor {
|
||||
/// Requests a new [`Compositor`] with the given [`Settings`].
|
||||
///
|
||||
|
|
@ -27,7 +51,7 @@ impl Compositor {
|
|||
pub async fn request<W: compositor::Window>(
|
||||
settings: Settings,
|
||||
compatible_window: Option<W>,
|
||||
) -> Option<Self> {
|
||||
) -> Result<Self, Error> {
|
||||
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
||||
backends: settings.internal_backend,
|
||||
..Default::default()
|
||||
|
|
@ -49,23 +73,27 @@ impl Compositor {
|
|||
let compatible_surface = compatible_window
|
||||
.and_then(|window| instance.create_surface(window).ok());
|
||||
|
||||
let adapter_options = wgpu::RequestAdapterOptions {
|
||||
power_preference: wgpu::util::power_preference_from_env()
|
||||
.unwrap_or(if settings.antialiasing.is_none() {
|
||||
wgpu::PowerPreference::LowPower
|
||||
} else {
|
||||
wgpu::PowerPreference::HighPerformance
|
||||
}),
|
||||
compatible_surface: compatible_surface.as_ref(),
|
||||
force_fallback_adapter: false,
|
||||
};
|
||||
|
||||
let adapter = instance
|
||||
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||
power_preference: wgpu::util::power_preference_from_env()
|
||||
.unwrap_or(if settings.antialiasing.is_none() {
|
||||
wgpu::PowerPreference::LowPower
|
||||
} else {
|
||||
wgpu::PowerPreference::HighPerformance
|
||||
}),
|
||||
compatible_surface: compatible_surface.as_ref(),
|
||||
force_fallback_adapter: false,
|
||||
})
|
||||
.await?;
|
||||
.request_adapter(&adapter_options)
|
||||
.await
|
||||
.ok_or(Error::NoAdapterFound(format!("{:?}", adapter_options)))?;
|
||||
|
||||
log::info!("Selected: {:#?}", adapter.get_info());
|
||||
|
||||
let (format, alpha_mode) =
|
||||
compatible_surface.as_ref().and_then(|surface| {
|
||||
let (format, alpha_mode) = compatible_surface
|
||||
.as_ref()
|
||||
.and_then(|surface| {
|
||||
let capabilities = surface.get_capabilities(&adapter);
|
||||
|
||||
let mut formats = capabilities.formats.iter().copied();
|
||||
|
|
@ -101,7 +129,8 @@ impl Compositor {
|
|||
};
|
||||
|
||||
format.zip(Some(preferred_alpha))
|
||||
})?;
|
||||
})
|
||||
.ok_or(Error::IncompatibleSurface)?;
|
||||
|
||||
log::info!(
|
||||
"Selected format: {format:?} with alpha mode: {alpha_mode:?}"
|
||||
|
|
@ -115,39 +144,46 @@ impl Compositor {
|
|||
let limits =
|
||||
[wgpu::Limits::default(), wgpu::Limits::downlevel_defaults()];
|
||||
|
||||
let mut limits = limits.into_iter().map(|limits| wgpu::Limits {
|
||||
let limits = limits.into_iter().map(|limits| wgpu::Limits {
|
||||
max_bind_groups: 2,
|
||||
..limits
|
||||
});
|
||||
|
||||
let (device, queue) =
|
||||
loop {
|
||||
let required_limits = limits.next()?;
|
||||
let device = adapter.request_device(
|
||||
let mut errors = Vec::new();
|
||||
|
||||
for required_limits in limits {
|
||||
let result = adapter
|
||||
.request_device(
|
||||
&wgpu::DeviceDescriptor {
|
||||
label: Some(
|
||||
"iced_wgpu::window::compositor device descriptor",
|
||||
),
|
||||
required_features: wgpu::Features::empty(),
|
||||
required_limits,
|
||||
required_limits: required_limits.clone(),
|
||||
},
|
||||
None,
|
||||
).await.ok();
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Some(device) = device {
|
||||
break Some(device);
|
||||
match result {
|
||||
Ok((device, queue)) => {
|
||||
return Ok(Compositor {
|
||||
instance,
|
||||
settings,
|
||||
adapter,
|
||||
device,
|
||||
queue,
|
||||
format,
|
||||
alpha_mode,
|
||||
})
|
||||
}
|
||||
}?;
|
||||
Err(error) => {
|
||||
errors.push((required_limits, error));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(Compositor {
|
||||
instance,
|
||||
settings,
|
||||
adapter,
|
||||
device,
|
||||
queue,
|
||||
format,
|
||||
alpha_mode,
|
||||
})
|
||||
Err(Error::RequestDeviceFailed(errors))
|
||||
}
|
||||
|
||||
/// Creates a new rendering [`Backend`] for this [`Compositor`].
|
||||
|
|
@ -168,9 +204,7 @@ pub async fn new<W: compositor::Window>(
|
|||
settings: Settings,
|
||||
compatible_window: W,
|
||||
) -> Result<Compositor, Error> {
|
||||
Compositor::request(settings, Some(compatible_window))
|
||||
.await
|
||||
.ok_or(Error::GraphicsAdapterNotFound)
|
||||
Compositor::request(settings, Some(compatible_window)).await
|
||||
}
|
||||
|
||||
/// Presents the given primitives with the given [`Compositor`] and [`Backend`].
|
||||
|
|
@ -229,15 +263,31 @@ pub fn present<T: AsRef<str>>(
|
|||
}
|
||||
|
||||
impl graphics::Compositor for Compositor {
|
||||
type Settings = Settings;
|
||||
type Renderer = Renderer;
|
||||
type Surface = wgpu::Surface<'static>;
|
||||
|
||||
fn new<W: compositor::Window>(
|
||||
settings: Self::Settings,
|
||||
async fn with_backend<W: compositor::Window>(
|
||||
settings: graphics::Settings,
|
||||
compatible_window: W,
|
||||
) -> impl Future<Output = Result<Self, Error>> {
|
||||
new(settings, compatible_window)
|
||||
backend: Option<&str>,
|
||||
) -> Result<Self, graphics::Error> {
|
||||
match backend {
|
||||
None | Some("wgpu") => Ok(new(
|
||||
Settings {
|
||||
internal_backend: wgpu::util::backend_bits_from_env()
|
||||
.unwrap_or(wgpu::Backends::all()),
|
||||
..settings.into()
|
||||
},
|
||||
compatible_window,
|
||||
)
|
||||
.await?),
|
||||
Some(backend) => Err(graphics::Error::GraphicsAdapterNotFound {
|
||||
backend: "wgpu",
|
||||
reason: error::Reason::DidNotMatch {
|
||||
preferred_backend: backend.to_owned(),
|
||||
},
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn create_renderer(&self) -> Self::Renderer {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue