summaryrefslogtreecommitdiffstats
path: root/tiny_skia
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón Jiménez <hector0193@gmail.com>2023-03-01 21:34:26 +0100
committerLibravatar Héctor Ramón Jiménez <hector0193@gmail.com>2023-03-01 21:34:26 +0100
commit5fd5d1cdf8e5354788dc40729c4565ef377d3bba (patch)
tree0921efc7dc13a3050e03482147a791f85515f1f2 /tiny_skia
parent3f6e28fa9b1b8d911f765c9efb5249a9e0c942d5 (diff)
downloadiced-5fd5d1cdf8e5354788dc40729c4565ef377d3bba.tar.gz
iced-5fd5d1cdf8e5354788dc40729c4565ef377d3bba.tar.bz2
iced-5fd5d1cdf8e5354788dc40729c4565ef377d3bba.zip
Implement `Canvas` support for `iced_tiny_skia`
Diffstat (limited to 'tiny_skia')
-rw-r--r--tiny_skia/Cargo.toml1
-rw-r--r--tiny_skia/src/backend.rs48
-rw-r--r--tiny_skia/src/canvas.rs276
-rw-r--r--tiny_skia/src/lib.rs10
-rw-r--r--tiny_skia/src/primitive.rs82
-rw-r--r--tiny_skia/src/window/compositor.rs3
6 files changed, 411 insertions, 9 deletions
diff --git a/tiny_skia/Cargo.toml b/tiny_skia/Cargo.toml
index 781e7d34..5f39fce2 100644
--- a/tiny_skia/Cargo.toml
+++ b/tiny_skia/Cargo.toml
@@ -6,6 +6,7 @@ edition = "2021"
[features]
image = []
svg = []
+canvas = ["iced_native/canvas"]
[dependencies]
raw-window-handle = "0.5"
diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs
index 66d83221..e08cede7 100644
--- a/tiny_skia/src/backend.rs
+++ b/tiny_skia/src/backend.rs
@@ -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();
}
diff --git a/tiny_skia/src/canvas.rs b/tiny_skia/src/canvas.rs
new file mode 100644
index 00000000..c3b8b316
--- /dev/null
+++ b/tiny_skia/src/canvas.rs
@@ -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()
+ }
+}
diff --git a/tiny_skia/src/lib.rs b/tiny_skia/src/lib.rs
index 420a1ffb..e66e6412 100644
--- a/tiny_skia/src/lib.rs
+++ b/tiny_skia/src/lib.rs
@@ -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`].
///
diff --git a/tiny_skia/src/primitive.rs b/tiny_skia/src/primitive.rs
new file mode 100644
index 00000000..22daaedc
--- /dev/null
+++ b/tiny_skia/src/primitive.rs
@@ -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
+ }
+}
diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs
index 2bd5831e..08159cd8 100644
--- a/tiny_skia/src/window/compositor.rs
+++ b/tiny_skia/src/window/compositor.rs
@@ -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;