From 6cc48b5c62bac287b8f9f1c79c1fb7486c51b18f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 3 Mar 2023 04:57:55 +0100 Subject: Move `Canvas` and `QRCode` to `iced` crate Rename `canvas` modules to `geometry` in graphics subcrates --- tiny_skia/src/geometry.rs | 287 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 287 insertions(+) create mode 100644 tiny_skia/src/geometry.rs (limited to 'tiny_skia/src/geometry.rs') diff --git a/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs new file mode 100644 index 00000000..73fc1ebd --- /dev/null +++ b/tiny_skia/src/geometry.rs @@ -0,0 +1,287 @@ +use crate::{Point, Primitive, Rectangle, Size, Vector}; + +use iced_graphics::geometry::fill::{self, Fill}; +use iced_graphics::geometry::stroke::{self, Stroke}; +use iced_graphics::geometry::{Path, Style, Text}; +use iced_graphics::Gradient; + +pub struct Frame { + size: Size, + transform: tiny_skia::Transform, + stack: Vec, + primitives: Vec, +} + +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) { + 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, + ) { + let path = convert_path(&Path::rectangle(top_left, size)); + let fill = fill.into(); + + self.primitives.push(Primitive::Fill { + path, + paint: tiny_skia::Paint { + anti_alias: false, + ..into_paint(fill.style) + }, + rule: into_fill_rule(fill.rule), + transform: self.transform, + }); + } + + pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into>) { + 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) { + 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_graphics::geometry::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() + } +} -- cgit From 3a0d34c0240f4421737a6a08761f99d6f8140d02 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 4 Mar 2023 05:37:11 +0100 Subject: Create `iced_widget` subcrate and re-organize the whole codebase --- tiny_skia/src/geometry.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'tiny_skia/src/geometry.rs') diff --git a/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs index 73fc1ebd..c66621dd 100644 --- a/tiny_skia/src/geometry.rs +++ b/tiny_skia/src/geometry.rs @@ -1,9 +1,9 @@ -use crate::{Point, Primitive, Rectangle, Size, Vector}; - -use iced_graphics::geometry::fill::{self, Fill}; -use iced_graphics::geometry::stroke::{self, Stroke}; -use iced_graphics::geometry::{Path, Style, Text}; -use iced_graphics::Gradient; +use crate::core::Gradient; +use crate::core::{Point, Rectangle, Size, Vector}; +use crate::graphics::geometry::fill::{self, Fill}; +use crate::graphics::geometry::stroke::{self, Stroke}; +use crate::graphics::geometry::{Path, Style, Text}; +use crate::graphics::Primitive; pub struct Frame { size: Size, -- cgit From 04c0ba04bf8574acdcbd5ad9fa20ac9c863f6087 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 4 Apr 2023 01:47:18 +0200 Subject: Warn about invalid paths in `iced_tiny_skia` instead of panicking --- tiny_skia/src/geometry.rs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) (limited to 'tiny_skia/src/geometry.rs') diff --git a/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs index c66621dd..4e3941f3 100644 --- a/tiny_skia/src/geometry.rs +++ b/tiny_skia/src/geometry.rs @@ -39,7 +39,7 @@ impl Frame { } pub fn fill(&mut self, path: &Path, fill: impl Into) { - let path = convert_path(path); + let Some(path) = convert_path(path) else { return }; let fill = fill.into(); self.primitives.push(Primitive::Fill { @@ -56,7 +56,7 @@ impl Frame { size: Size, fill: impl Into, ) { - let path = convert_path(&Path::rectangle(top_left, size)); + let Some(path) = convert_path(&Path::rectangle(top_left, size)) else { return }; let fill = fill.into(); self.primitives.push(Primitive::Fill { @@ -71,7 +71,8 @@ impl Frame { } pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into>) { - let path = convert_path(path); + let Some(path) = convert_path(path) else { return }; + let stroke = stroke.into(); let skia_stroke = into_stroke(&stroke); @@ -151,7 +152,7 @@ impl Frame { } } -fn convert_path(path: &Path) -> tiny_skia::Path { +fn convert_path(path: &Path) -> Option { use iced_graphics::geometry::path::lyon_path; let mut builder = tiny_skia::PathBuilder::new(); @@ -205,9 +206,14 @@ fn convert_path(path: &Path) -> tiny_skia::Path { } } - builder - .finish() - .expect("Convert lyon path to tiny_skia path") + let result = builder.finish(); + + #[cfg(debug_assertions)] + if result.is_none() { + log::warn!("Invalid path: {:?}", path.raw()); + } + + result } pub fn into_paint(style: Style) -> tiny_skia::Paint<'static> { -- cgit From 6fae8bf6cbe7155bcee42eaeba68e31564df057c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 4 Apr 2023 01:47:58 +0200 Subject: Implement `Frame::clip` for `iced_tiny_skia` --- tiny_skia/src/geometry.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'tiny_skia/src/geometry.rs') diff --git a/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs index 4e3941f3..508965ad 100644 --- a/tiny_skia/src/geometry.rs +++ b/tiny_skia/src/geometry.rs @@ -125,7 +125,12 @@ impl Frame { self.transform = self.stack.pop().expect("Pop transform"); } - pub fn clip(&mut self, _frame: Self, _translation: Vector) {} + pub fn clip(&mut self, frame: Self, translation: Vector) { + self.primitives.push(Primitive::Translate { + translation, + content: Box::new(frame.into_primitive()), + }); + } pub fn translate(&mut self, translation: Vector) { self.transform = -- cgit From 33b5a900197e2798a393d6d9a0834039666eddbb Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 19 Apr 2023 01:19:56 +0200 Subject: Make basic text shaping the default shaping strategy --- tiny_skia/src/geometry.rs | 1 + 1 file changed, 1 insertion(+) (limited to 'tiny_skia/src/geometry.rs') diff --git a/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs index 508965ad..7cdac1c8 100644 --- a/tiny_skia/src/geometry.rs +++ b/tiny_skia/src/geometry.rs @@ -114,6 +114,7 @@ impl Frame { font: text.font, horizontal_alignment: text.horizontal_alignment, vertical_alignment: text.vertical_alignment, + advanced_shape: text.advanced_shape, }); } -- cgit From 4bd290afe7d81d9aaf7467b3ce91491f6600261a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 19 Apr 2023 02:00:45 +0200 Subject: Introduce `text::Shaping` enum and replace magic boolean --- tiny_skia/src/geometry.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tiny_skia/src/geometry.rs') diff --git a/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs index 7cdac1c8..7963fd89 100644 --- a/tiny_skia/src/geometry.rs +++ b/tiny_skia/src/geometry.rs @@ -114,7 +114,7 @@ impl Frame { font: text.font, horizontal_alignment: text.horizontal_alignment, vertical_alignment: text.vertical_alignment, - advanced_shape: text.advanced_shape, + shaping: text.shaping, }); } -- cgit From 9499a8f9e6f9971dedfae563cb133232aa3cebc2 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 4 May 2023 13:00:16 +0200 Subject: Support configurable `LineHeight` in text widgets --- tiny_skia/src/geometry.rs | 1 + 1 file changed, 1 insertion(+) (limited to 'tiny_skia/src/geometry.rs') diff --git a/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs index 7963fd89..a445b561 100644 --- a/tiny_skia/src/geometry.rs +++ b/tiny_skia/src/geometry.rs @@ -111,6 +111,7 @@ impl Frame { }, color: text.color, size: text.size, + line_height: text.line_height, font: text.font, horizontal_alignment: text.horizontal_alignment, vertical_alignment: text.vertical_alignment, -- cgit From de638f44a5c62459008a5c024b39c2443b72bf25 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 11 May 2023 15:37:56 +0200 Subject: Write missing documentation in `iced_wgpu` --- tiny_skia/src/geometry.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'tiny_skia/src/geometry.rs') diff --git a/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs index a445b561..a1fd7b60 100644 --- a/tiny_skia/src/geometry.rs +++ b/tiny_skia/src/geometry.rs @@ -127,9 +127,9 @@ impl Frame { self.transform = self.stack.pop().expect("Pop transform"); } - pub fn clip(&mut self, frame: Self, translation: Vector) { + pub fn clip(&mut self, frame: Self, at: Point) { self.primitives.push(Primitive::Translate { - translation, + translation: Vector::new(at.x, at.y), content: Box::new(frame.into_primitive()), }); } -- cgit