Implement Canvas support for iced_tiny_skia
This commit is contained in:
parent
3f6e28fa9b
commit
5fd5d1cdf8
65 changed files with 1354 additions and 570 deletions
|
|
@ -6,6 +6,7 @@ edition = "2021"
|
|||
[features]
|
||||
image = []
|
||||
svg = []
|
||||
canvas = ["iced_native/canvas"]
|
||||
|
||||
[dependencies]
|
||||
raw-window-handle = "0.5"
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
use crate::{Color, Font, Settings, Size, Viewport};
|
||||
use crate::{Color, Font, Primitive, Settings, Size, Viewport};
|
||||
|
||||
use iced_graphics::alignment;
|
||||
use iced_graphics::backend;
|
||||
use iced_graphics::text;
|
||||
use iced_graphics::{Background, Primitive, Rectangle, Vector};
|
||||
use iced_graphics::{Background, Rectangle, Vector};
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
|
|
@ -81,7 +81,6 @@ impl Backend {
|
|||
translation: Vector,
|
||||
) {
|
||||
match primitive {
|
||||
Primitive::None => {}
|
||||
Primitive::Quad {
|
||||
bounds,
|
||||
background,
|
||||
|
|
@ -161,6 +160,38 @@ impl Backend {
|
|||
Primitive::Svg { .. } => {
|
||||
// TODO
|
||||
}
|
||||
Primitive::Fill {
|
||||
path,
|
||||
paint,
|
||||
rule,
|
||||
transform,
|
||||
} => {
|
||||
pixels.fill_path(
|
||||
path,
|
||||
paint,
|
||||
*rule,
|
||||
transform
|
||||
.post_translate(translation.x, translation.y)
|
||||
.post_scale(scale_factor, scale_factor),
|
||||
clip_mask,
|
||||
);
|
||||
}
|
||||
Primitive::Stroke {
|
||||
path,
|
||||
paint,
|
||||
stroke,
|
||||
transform,
|
||||
} => {
|
||||
pixels.stroke_path(
|
||||
path,
|
||||
paint,
|
||||
stroke,
|
||||
transform
|
||||
.post_translate(translation.x, translation.y)
|
||||
.post_scale(scale_factor, scale_factor),
|
||||
clip_mask,
|
||||
);
|
||||
}
|
||||
Primitive::Group { primitives } => {
|
||||
for primitive in primitives {
|
||||
self.draw_primitive(
|
||||
|
|
@ -196,16 +227,19 @@ impl Backend {
|
|||
translation,
|
||||
);
|
||||
}
|
||||
Primitive::Cached { cache } => {
|
||||
Primitive::Cache { content } => {
|
||||
self.draw_primitive(
|
||||
cache,
|
||||
content,
|
||||
pixels,
|
||||
clip_mask,
|
||||
scale_factor,
|
||||
translation,
|
||||
);
|
||||
}
|
||||
Primitive::SolidMesh { .. } | Primitive::GradientMesh { .. } => {}
|
||||
Primitive::SolidMesh { .. } | Primitive::GradientMesh { .. } => {
|
||||
// Not supported!
|
||||
// TODO: Draw a placeholder (?) / Log it (?)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -386,6 +420,8 @@ fn rectangular_clip_mask(
|
|||
}
|
||||
|
||||
impl iced_graphics::Backend for Backend {
|
||||
type Geometry = ();
|
||||
|
||||
fn trim_measurements(&mut self) {
|
||||
self.text_pipeline.trim_measurement_cache();
|
||||
}
|
||||
|
|
|
|||
276
tiny_skia/src/canvas.rs
Normal file
276
tiny_skia/src/canvas.rs
Normal file
|
|
@ -0,0 +1,276 @@
|
|||
use crate::{Point, Primitive, Rectangle, Size, Vector};
|
||||
|
||||
use iced_native::widget::canvas::fill::{self, Fill};
|
||||
use iced_native::widget::canvas::stroke::{self, Stroke};
|
||||
use iced_native::widget::canvas::{Path, Style, Text};
|
||||
use iced_native::Gradient;
|
||||
|
||||
pub struct Frame {
|
||||
size: Size,
|
||||
transform: tiny_skia::Transform,
|
||||
stack: Vec<tiny_skia::Transform>,
|
||||
primitives: Vec<Primitive>,
|
||||
}
|
||||
|
||||
impl Frame {
|
||||
pub fn new(size: Size) -> Self {
|
||||
Self {
|
||||
size,
|
||||
transform: tiny_skia::Transform::identity(),
|
||||
stack: Vec::new(),
|
||||
primitives: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn width(&self) -> f32 {
|
||||
self.size.width
|
||||
}
|
||||
|
||||
pub fn height(&self) -> f32 {
|
||||
self.size.height
|
||||
}
|
||||
|
||||
pub fn size(&self) -> Size {
|
||||
self.size
|
||||
}
|
||||
|
||||
pub fn center(&self) -> Point {
|
||||
Point::new(self.size.width / 2.0, self.size.height / 2.0)
|
||||
}
|
||||
|
||||
pub fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
|
||||
let path = convert_path(path);
|
||||
let fill = fill.into();
|
||||
|
||||
self.primitives.push(Primitive::Fill {
|
||||
path,
|
||||
paint: into_paint(fill.style),
|
||||
rule: into_fill_rule(fill.rule),
|
||||
transform: self.transform,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn fill_rectangle(
|
||||
&mut self,
|
||||
top_left: Point,
|
||||
size: Size,
|
||||
fill: impl Into<Fill>,
|
||||
) {
|
||||
self.fill(&Path::rectangle(top_left, size), fill);
|
||||
}
|
||||
|
||||
pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) {
|
||||
let path = convert_path(path);
|
||||
let stroke = stroke.into();
|
||||
let skia_stroke = into_stroke(&stroke);
|
||||
|
||||
self.primitives.push(Primitive::Stroke {
|
||||
path,
|
||||
paint: into_paint(stroke.style),
|
||||
stroke: skia_stroke,
|
||||
transform: self.transform,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn fill_text(&mut self, text: impl Into<Text>) {
|
||||
let text = text.into();
|
||||
|
||||
let position = if self.transform.is_identity() {
|
||||
text.position
|
||||
} else {
|
||||
let mut transformed = [tiny_skia::Point {
|
||||
x: text.position.x,
|
||||
y: text.position.y,
|
||||
}];
|
||||
|
||||
self.transform.map_points(&mut transformed);
|
||||
|
||||
Point::new(transformed[0].x, transformed[0].y)
|
||||
};
|
||||
|
||||
// TODO: Use vectorial text instead of primitive
|
||||
self.primitives.push(Primitive::Text {
|
||||
content: text.content,
|
||||
bounds: Rectangle {
|
||||
x: position.x,
|
||||
y: position.y,
|
||||
width: f32::INFINITY,
|
||||
height: f32::INFINITY,
|
||||
},
|
||||
color: text.color,
|
||||
size: text.size,
|
||||
font: text.font,
|
||||
horizontal_alignment: text.horizontal_alignment,
|
||||
vertical_alignment: text.vertical_alignment,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn push_transform(&mut self) {
|
||||
self.stack.push(self.transform);
|
||||
}
|
||||
|
||||
pub fn pop_transform(&mut self) {
|
||||
self.transform = self.stack.pop().expect("Pop transform");
|
||||
}
|
||||
|
||||
pub fn clip(&mut self, _frame: Self, _translation: Vector) {}
|
||||
|
||||
pub fn translate(&mut self, translation: Vector) {
|
||||
self.transform =
|
||||
self.transform.pre_translate(translation.x, translation.y);
|
||||
}
|
||||
|
||||
pub fn rotate(&mut self, angle: f32) {
|
||||
self.transform = self
|
||||
.transform
|
||||
.pre_concat(tiny_skia::Transform::from_rotate(angle.to_degrees()));
|
||||
}
|
||||
|
||||
pub fn scale(&mut self, scale: f32) {
|
||||
self.transform = self.transform.pre_scale(scale, scale);
|
||||
}
|
||||
|
||||
pub fn into_primitive(self) -> Primitive {
|
||||
Primitive::Clip {
|
||||
bounds: Rectangle::new(Point::ORIGIN, self.size),
|
||||
content: Box::new(Primitive::Group {
|
||||
primitives: self.primitives,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_path(path: &Path) -> tiny_skia::Path {
|
||||
use iced_native::widget::canvas::path::lyon_path;
|
||||
|
||||
let mut builder = tiny_skia::PathBuilder::new();
|
||||
let mut last_point = Default::default();
|
||||
|
||||
for event in path.raw().iter() {
|
||||
match event {
|
||||
lyon_path::Event::Begin { at } => {
|
||||
builder.move_to(at.x, at.y);
|
||||
|
||||
last_point = at;
|
||||
}
|
||||
lyon_path::Event::Line { from, to } => {
|
||||
if last_point != from {
|
||||
builder.move_to(from.x, from.y);
|
||||
}
|
||||
|
||||
builder.line_to(to.x, to.y);
|
||||
|
||||
last_point = to;
|
||||
}
|
||||
lyon_path::Event::Quadratic { from, ctrl, to } => {
|
||||
if last_point != from {
|
||||
builder.move_to(from.x, from.y);
|
||||
}
|
||||
|
||||
builder.quad_to(ctrl.x, ctrl.y, to.x, to.y);
|
||||
|
||||
last_point = to;
|
||||
}
|
||||
lyon_path::Event::Cubic {
|
||||
from,
|
||||
ctrl1,
|
||||
ctrl2,
|
||||
to,
|
||||
} => {
|
||||
if last_point != from {
|
||||
builder.move_to(from.x, from.y);
|
||||
}
|
||||
|
||||
builder
|
||||
.cubic_to(ctrl1.x, ctrl1.y, ctrl2.x, ctrl2.y, to.x, to.y);
|
||||
|
||||
last_point = to;
|
||||
}
|
||||
lyon_path::Event::End { close, .. } => {
|
||||
if close {
|
||||
builder.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
builder
|
||||
.finish()
|
||||
.expect("Convert lyon path to tiny_skia path")
|
||||
}
|
||||
|
||||
pub fn into_paint(style: Style) -> tiny_skia::Paint<'static> {
|
||||
tiny_skia::Paint {
|
||||
shader: match style {
|
||||
Style::Solid(color) => tiny_skia::Shader::SolidColor(
|
||||
tiny_skia::Color::from_rgba(color.b, color.g, color.r, color.a)
|
||||
.expect("Create color"),
|
||||
),
|
||||
Style::Gradient(gradient) => match gradient {
|
||||
Gradient::Linear(linear) => tiny_skia::LinearGradient::new(
|
||||
tiny_skia::Point {
|
||||
x: linear.start.x,
|
||||
y: linear.start.y,
|
||||
},
|
||||
tiny_skia::Point {
|
||||
x: linear.end.x,
|
||||
y: linear.end.y,
|
||||
},
|
||||
linear
|
||||
.color_stops
|
||||
.into_iter()
|
||||
.map(|stop| {
|
||||
tiny_skia::GradientStop::new(
|
||||
stop.offset,
|
||||
tiny_skia::Color::from_rgba(
|
||||
stop.color.b,
|
||||
stop.color.g,
|
||||
stop.color.r,
|
||||
stop.color.a,
|
||||
)
|
||||
.expect("Create color"),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
tiny_skia::SpreadMode::Pad,
|
||||
tiny_skia::Transform::identity(),
|
||||
)
|
||||
.expect("Create linear gradient"),
|
||||
},
|
||||
},
|
||||
anti_alias: true,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_fill_rule(rule: fill::Rule) -> tiny_skia::FillRule {
|
||||
match rule {
|
||||
fill::Rule::EvenOdd => tiny_skia::FillRule::EvenOdd,
|
||||
fill::Rule::NonZero => tiny_skia::FillRule::Winding,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_stroke(stroke: &Stroke) -> tiny_skia::Stroke {
|
||||
tiny_skia::Stroke {
|
||||
width: stroke.width,
|
||||
line_cap: match stroke.line_cap {
|
||||
stroke::LineCap::Butt => tiny_skia::LineCap::Butt,
|
||||
stroke::LineCap::Square => tiny_skia::LineCap::Square,
|
||||
stroke::LineCap::Round => tiny_skia::LineCap::Round,
|
||||
},
|
||||
line_join: match stroke.line_join {
|
||||
stroke::LineJoin::Miter => tiny_skia::LineJoin::Miter,
|
||||
stroke::LineJoin::Round => tiny_skia::LineJoin::Round,
|
||||
stroke::LineJoin::Bevel => tiny_skia::LineJoin::Bevel,
|
||||
},
|
||||
dash: if stroke.line_dash.segments.is_empty() {
|
||||
None
|
||||
} else {
|
||||
tiny_skia::StrokeDash::new(
|
||||
stroke.line_dash.segments.into(),
|
||||
stroke.line_dash.offset as f32,
|
||||
)
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
|
@ -4,10 +4,18 @@ mod backend;
|
|||
mod settings;
|
||||
mod text;
|
||||
|
||||
#[cfg(feature = "canvas")]
|
||||
pub mod canvas;
|
||||
|
||||
pub use iced_graphics::primitive;
|
||||
|
||||
pub use backend::Backend;
|
||||
pub use primitive::Primitive;
|
||||
pub use settings::Settings;
|
||||
|
||||
pub use iced_graphics::{Color, Error, Font, Point, Size, Vector, Viewport};
|
||||
pub use iced_graphics::{
|
||||
Color, Error, Font, Point, Rectangle, Size, Vector, Viewport,
|
||||
};
|
||||
|
||||
/// A [`tiny-skia`] graphics renderer for [`iced`].
|
||||
///
|
||||
|
|
|
|||
82
tiny_skia/src/primitive.rs
Normal file
82
tiny_skia/src/primitive.rs
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
use crate::{Rectangle, Vector};
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Primitive {
|
||||
/// A group of primitives
|
||||
Group {
|
||||
/// The primitives of the group
|
||||
primitives: Vec<Primitive>,
|
||||
},
|
||||
/// A clip primitive
|
||||
Clip {
|
||||
/// The bounds of the clip
|
||||
bounds: Rectangle,
|
||||
/// The content of the clip
|
||||
content: Box<Primitive>,
|
||||
},
|
||||
/// A primitive that applies a translation
|
||||
Translate {
|
||||
/// The translation vector
|
||||
translation: Vector,
|
||||
|
||||
/// The primitive to translate
|
||||
content: Box<Primitive>,
|
||||
},
|
||||
/// A cached primitive.
|
||||
///
|
||||
/// This can be useful if you are implementing a widget where primitive
|
||||
/// generation is expensive.
|
||||
Cached {
|
||||
/// The cached primitive
|
||||
cache: Arc<Primitive>,
|
||||
},
|
||||
/// A basic primitive.
|
||||
Basic(iced_graphics::Primitive),
|
||||
}
|
||||
|
||||
impl iced_graphics::backend::Primitive for Primitive {
|
||||
fn translate(self, translation: Vector) -> Self {
|
||||
Self::Translate {
|
||||
translation,
|
||||
content: Box::new(self),
|
||||
}
|
||||
}
|
||||
|
||||
fn clip(self, bounds: Rectangle) -> Self {
|
||||
Self::Clip {
|
||||
bounds,
|
||||
content: Box::new(self),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Recording(pub(crate) Vec<Primitive>);
|
||||
|
||||
impl iced_graphics::backend::Recording for Recording {
|
||||
type Primitive = Primitive;
|
||||
|
||||
fn push(&mut self, primitive: Primitive) {
|
||||
self.0.push(primitive);
|
||||
}
|
||||
|
||||
fn push_basic(&mut self, basic: iced_graphics::Primitive) {
|
||||
self.0.push(Primitive::Basic(basic));
|
||||
}
|
||||
|
||||
fn group(self) -> Self::Primitive {
|
||||
Primitive::Group { primitives: self.0 }
|
||||
}
|
||||
|
||||
fn clear(&mut self) {
|
||||
self.0.clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl Recording {
|
||||
pub fn primitives(&self) -> &[Primitive] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
use crate::{Backend, Color, Error, Renderer, Settings, Viewport};
|
||||
use crate::{Backend, Color, Error, Primitive, Renderer, Settings, Viewport};
|
||||
|
||||
use iced_graphics::window::compositor::{self, Information, SurfaceError};
|
||||
use iced_graphics::Primitive;
|
||||
|
||||
use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
|
||||
use std::marker::PhantomData;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue