From 508bf86302dedd7fd3f7aa05db32083f16f9e1d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=BC=E6=A1=91?= <33797740+boenfu@users.noreply.github.com> Date: Sun, 13 Aug 2023 13:00:15 +0800 Subject: [PATCH] feat: v-group (#7) * feat: playground support url code template * feat: v-group basic usage * feat: group basic shape clip --- soft-skia-wasm/src/lib.rs | 283 ++++++------ soft-skia/src/instance.rs | 10 +- soft-skia/src/lib.rs | 3 +- soft-skia/src/provider.rs | 135 ++++++ soft-skia/src/shape.rs | 439 ++++++++++++++----- soft-skia/src/tree.rs | 206 +++++++-- vue-playground/src/App.vue | 77 +++- vue-skia-framework/components/VCircle.vue | 4 +- vue-skia-framework/components/VGroup.vue | 44 ++ vue-skia-framework/components/VGroupClip.vue | 15 + vue-skia-framework/components/VLine.vue | 4 +- vue-skia-framework/components/VPoints.vue | 6 +- vue-skia-framework/components/VRect.vue | 4 +- vue-skia-framework/components/VRoundRect.vue | 4 +- vue-skia-framework/main.js | 3 +- vue-skia-framework/plugin/index.ts | 91 +++- vue-skia-framework/surface/index.ts | 2 +- vue-skia-framework/type.ts | 1 + 18 files changed, 1010 insertions(+), 321 deletions(-) create mode 100644 soft-skia/src/provider.rs create mode 100644 vue-skia-framework/components/VGroup.vue create mode 100644 vue-skia-framework/components/VGroupClip.vue diff --git a/soft-skia-wasm/src/lib.rs b/soft-skia-wasm/src/lib.rs index e4df79e..3be1240 100644 --- a/soft-skia-wasm/src/lib.rs +++ b/soft-skia-wasm/src/lib.rs @@ -1,10 +1,13 @@ extern crate soft_skia; mod utils; +use std::collections::HashMap; + use base64; +use soft_skia::provider::{Providers, Group, Provider, GroupClip}; use wasm_bindgen::prelude::*; use soft_skia::instance::Instance; -use soft_skia::shape::{Circle, Line, Points, RoundRect, Shapes, PaintStyle}; +use soft_skia::shape::{Circle, Line, Points, RoundRect, Shapes, PaintStyle, DrawContext}; use soft_skia::shape::Rect; use soft_skia::shape::ColorU8; use soft_skia::tree::Node; @@ -29,8 +32,8 @@ pub struct WASMRectAttr { height: u32, x: u32, y: u32, - color: String, - style: String, + color: Option, + style: Option, } #[derive(Serialize, Deserialize, Debug)] @@ -38,8 +41,8 @@ pub struct WASMCircleAttr { cx: u32, cy: u32, r: u32, - color: String, - style: String, + color: Option, + style: Option, } #[derive(Serialize, Deserialize, Debug)] @@ -49,24 +52,39 @@ pub struct WASMRoundRectAttr { r: u32, x: u32, y: u32, - color: String, - style: String, + color: Option, + style: Option, } #[derive(Serialize, Deserialize, Debug)] pub struct WASMLineAttr { p1: [u32; 2], p2: [u32; 2], - color: String, - stroke_width: u32 + color: Option, + stroke_width: Option } #[derive(Serialize, Deserialize, Debug)] pub struct WASMPointsAttr { points: Vec<[u32; 2]>, - color: String, - stroke_width: u32, - style: String, + color: Option, + stroke_width: Option, + style: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct WASMGroupAttr { + x: Option, + y: Option, + color: Option, + style: Option, + stroke_width: Option, + invert_clip: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct WASMGroupClipAttr { + clip: usize, } #[derive(Serialize, Deserialize, Debug)] @@ -76,6 +94,8 @@ pub enum WASMShapesAttr { RR(WASMRoundRectAttr), L(WASMLineAttr), P(WASMPointsAttr), + G(WASMGroupAttr), + GC(WASMGroupClipAttr), } #[derive(Serialize, Deserialize, Debug)] @@ -132,146 +152,139 @@ impl SoftSkiaWASM { } fn recursive_rasterization_node_to_pixmap(node: &mut Node, pixmap: &mut Pixmap) -> () { - for item in node.children_iter_mut() { - item.shape.draw(pixmap); - Self::recursive_rasterization_node_to_pixmap(&mut (*item), pixmap); - } - } - + let context = node.provider.as_ref().and_then(|p| p.get_context()); - #[wasm_bindgen(js_name = setShapeBySerde)] - pub fn set_shape_by_serde(&mut self, id: usize, value: JsValue) { - let message: WASMShape = serde_wasm_bindgen::from_value(value).unwrap(); + for item in node.children.iter_mut() { + let inactive = *context + .and_then(|c| c.inactive_nodes_map.as_ref().and_then(|m| m.get(&item.id))) + .unwrap_or(&false); - match message.attr { - WASMShapesAttr::R(WASMRectAttr{ width, height, x, y , color, style}) => { + if inactive { + continue; + } - let mut parser_input = ParserInput::new(&color); - let mut parser = Parser::new(&mut parser_input); - let color = CSSColor::parse(&mut parser); - - match color { - Ok(CSSColor::RGBA(rgba)) => { - drop(parser_input); - let style = match style.as_str() { - "stroke" => { - PaintStyle::Stroke - }, - "fill" => { - PaintStyle::Fill - }, - _ => { - PaintStyle::Stroke + item.draw(pixmap, context); + + if let Some(provider) = item.provider.as_mut() { + provider.set_context(pixmap, node.provider.as_ref()); + + // 这段感觉放这有点重,想搬走 + match provider { + Providers::G(group) => { + if let Some(clip) = &group.clip { + if let Some(clip_id) = clip.id { + if let Some(clip_path) = item + .children + .iter_mut() + .find(|n| n.id == clip_id) + .and_then(|n| Some(n.shape.get_path(group.context.as_ref().unwrap()))) + { + group.set_context_mask(pixmap, &clip_path); + } } - }; - self.0.set_shape_to_child(id, Shapes::R(Rect { x, y, width, height, color: ColorU8::from_rgba(rgba.red, rgba.green, rgba.blue, rgba.alpha), style })) - } - _ => { - // + } } } - }, - WASMShapesAttr::C(WASMCircleAttr{ cx, cy, r, color, style }) => { + } - let mut parser_input = ParserInput::new(&color); - let mut parser = Parser::new(&mut parser_input); - let color = CSSColor::parse(&mut parser); - - match color { - Ok(CSSColor::RGBA(rgba)) => { - drop(parser_input); - let style = match style.as_str() { - "stroke" => { - PaintStyle::Stroke - }, - "fill" => { - PaintStyle::Fill - }, - _ => { - PaintStyle::Stroke - } - }; - self.0.set_shape_to_child(id, Shapes::C(Circle { cx, cy, r, color: ColorU8::from_rgba(rgba.red, rgba.green, rgba.blue, rgba.alpha), style })) - } - _ => { - // - } - } + Self::recursive_rasterization_node_to_pixmap(item, pixmap); + } + } + + #[wasm_bindgen(js_name = setAttrBySerde)] + pub fn set_attr_by_serde(&mut self, id: usize, value: JsValue) { + let message: WASMShape = serde_wasm_bindgen::from_value(value).unwrap(); + match message.attr { + WASMShapesAttr::R(WASMRectAttr{ width, height, x, y , color, style}) => { + let color = parse_color(color); + let style = parse_style(style); + self.0.set_shape_to_child(id, Shapes::R(Rect { x, y, width, height, color, style })) + }, + WASMShapesAttr::C(WASMCircleAttr{ cx, cy, r, color, style }) => { + let color = parse_color(color); + let style = parse_style(style); + self.0.set_shape_to_child(id, Shapes::C(Circle { cx, cy, r, color, style })) }, WASMShapesAttr::RR(WASMRoundRectAttr{ width, height, r, x, y , color, style}) => { + let color = parse_color(color); + let style = parse_style(style); - let mut parser_input = ParserInput::new(&color); - let mut parser = Parser::new(&mut parser_input); - let color = CSSColor::parse(&mut parser); - - match color { - Ok(CSSColor::RGBA(rgba)) => { - drop(parser_input); - let style = match style.as_str() { - "stroke" => { - PaintStyle::Stroke - }, - "fill" => { - PaintStyle::Fill - }, - _ => { - PaintStyle::Stroke - } - }; - self.0.set_shape_to_child(id, Shapes::RR(RoundRect { x, y, r, width, height, color: ColorU8::from_rgba(rgba.red, rgba.green, rgba.blue, rgba.alpha), style })) - } - _ => { - // - } - } - + self.0.set_shape_to_child(id, Shapes::RR(RoundRect { x, y, r, width, height, color, style })) }, WASMShapesAttr::L(WASMLineAttr{ p1, p2, stroke_width, color}) => { - - let mut parser_input = ParserInput::new(&color); - let mut parser = Parser::new(&mut parser_input); - let color = CSSColor::parse(&mut parser); - - match color { - Ok(CSSColor::RGBA(rgba)) => { - drop(parser_input); - self.0.set_shape_to_child(id, Shapes::L(Line { p1, p2, stroke_width, color: ColorU8::from_rgba(rgba.red, rgba.green, rgba.blue, rgba.alpha) })) - } - _ => { - // - } - } - + let color = parse_color(color); + self.0.set_shape_to_child(id, Shapes::L(Line { p1, p2, stroke_width, color })) }, WASMShapesAttr::P(WASMPointsAttr{ points , color, stroke_width, style }) => { - - let mut parser_input = ParserInput::new(&color); - let mut parser = Parser::new(&mut parser_input); - let color = CSSColor::parse(&mut parser); - - match color { - Ok(CSSColor::RGBA(rgba)) => { - drop(parser_input); - let style = match style.as_str() { - "stroke" => { - PaintStyle::Stroke - }, - "fill" => { - PaintStyle::Fill - }, - _ => { - PaintStyle::Stroke - } - }; - self.0.set_shape_to_child(id, Shapes::P(Points { points, stroke_width, color: ColorU8::from_rgba(rgba.red, rgba.green, rgba.blue, rgba.alpha), style })) - } - _ => { - // - } - } + let color = parse_color(color); + let style = parse_style(style); + self.0.set_shape_to_child(id, Shapes::P(Points { points, stroke_width, color, style })) + }, + WASMShapesAttr::G(WASMGroupAttr { + x, + y, + color, + stroke_width, + style, + invert_clip, + }) => { + let color = parse_color(color); + let style = parse_style(style); + let mut clip = GroupClip::default(); + clip.invert = invert_clip; + + self.0.set_provider_to_child( + id, + Providers::G(Group { + x, + y, + color, + style, + stroke_width, + clip: Some(clip), + context: None, + }), + ) }, + WASMShapesAttr::GC(WASMGroupClipAttr { clip }) => { + let provider = self + .0 + .get_tree_node_by_id(id) + .unwrap() + .provider + .as_mut() + .unwrap(); + + match provider { + Providers::G(ref mut group) => group.set_clip_id(clip), + } + } }; } } + +fn parse_color(color: Option) -> Option { + if let Some(color_str) = color { + let mut parser_input = ParserInput::new(&color_str); + let mut parser = Parser::new(&mut parser_input); + + if let Ok(css_color) = CSSColor::parse(&mut parser) { + if let CSSColor::RGBA(rgba) = css_color { + return Some(ColorU8::from_rgba( + rgba.red, rgba.green, rgba.blue, rgba.alpha, + )); + } + } + } + None +} + +fn parse_style(style: Option) -> Option { + match style.as_deref() { + Some("stroke") => Some(PaintStyle::Stroke), + Some("fill") => Some(PaintStyle::Fill), + _ => None, + } +} \ No newline at end of file diff --git a/soft-skia/src/instance.rs b/soft-skia/src/instance.rs index 8506fa5..fa4df1e 100644 --- a/soft-skia/src/instance.rs +++ b/soft-skia/src/instance.rs @@ -1,5 +1,6 @@ use std::collections::HashMap; +use crate::provider::Providers; use crate::tree::Tree; use crate::tree::Node; use crate::shape::Shapes; @@ -41,6 +42,11 @@ impl Instance { child.shape = shape; } + pub fn set_provider_to_child(&mut self, child_id: usize, provider: Providers) { + let mut child = self.get_tree_node_by_id(child_id).unwrap(); + child.provider = Some(provider); + } + pub fn remove_child_from_container(&mut self, child_id: usize, container_id: usize) { let container = self.get_tree_node_by_id(container_id).unwrap(); container.remove_child_by_id(child_id); @@ -116,7 +122,7 @@ mod test { } } - instance.set_shape_to_child(1, Shapes::R(Rect { x: 20, y: 20, width: 200, height: 200, color: ColorU8::from_rgba(0, 100, 0, 255), style: PaintStyle::Fill })); + instance.set_shape_to_child(1, Shapes::R(Rect { x: 20, y: 20, width: 200, height: 200, color: Some(ColorU8::from_rgba(0, 100, 0, 255)), style: None })); match instance.get_tree_node_by_id(1).unwrap().shape { Shapes::R(Rect { x, y, width, height, color, style }) => { @@ -124,7 +130,7 @@ mod test { assert_eq!(y, 20); assert_eq!(width, 200); assert_eq!(height, 200); - assert_eq!(color.green(), 100); + assert_eq!(color.unwrap().green(), 100); }, _ => { panic!() diff --git a/soft-skia/src/lib.rs b/soft-skia/src/lib.rs index d697f26..f4e3d10 100644 --- a/soft-skia/src/lib.rs +++ b/soft-skia/src/lib.rs @@ -1,3 +1,4 @@ pub mod tree; pub mod shape; -pub mod instance; \ No newline at end of file +pub mod instance; +pub mod provider; \ No newline at end of file diff --git a/soft-skia/src/provider.rs b/soft-skia/src/provider.rs new file mode 100644 index 0000000..a790679 --- /dev/null +++ b/soft-skia/src/provider.rs @@ -0,0 +1,135 @@ +use std::collections::HashMap; + +use tiny_skia::{ColorU8, FillRule, Pixmap, Transform}; +pub use tiny_skia::{Mask, Path}; + +use crate::shape::{DrawContext, PaintStyle}; + +#[derive(Debug)] +pub enum Providers { + G(Group), +} + +pub trait Provider { + fn default() -> Self; + fn get_context(&self) -> Option<&DrawContext>; + fn set_context(&mut self, pixmap: &mut Pixmap, parent: Option<&Providers>) -> (); +} + +impl Providers { + pub fn get_context(&self) -> Option<&DrawContext> { + match self { + Providers::G(group) => group.get_context(), + } + } + pub fn set_context(&mut self, pixmap: &mut Pixmap, parent: Option<&Providers>) -> () { + match self { + Providers::G(group) => group.set_context(pixmap, parent), + } + } +} + +#[derive(Debug)] +pub struct GroupClip { + pub id: Option, + pub invert: Option, +} + +impl GroupClip { + pub fn default() -> Self { + GroupClip { + id: None, + invert: None, + } + } +} + +#[derive(Debug)] +pub struct Group { + pub x: Option, + pub y: Option, + pub color: Option, + pub style: Option, + pub stroke_width: Option, + pub clip: Option, + pub context: Option, +} + +impl Provider for Group { + fn default() -> Self { + Group { + x: None, + y: None, + color: None, + style: None, + stroke_width: None, + clip: None, + context: None, + } + } + + fn get_context(&self) -> Option<&DrawContext> { + return self.context.as_ref(); + } + + fn set_context(&mut self, _pixmap: &mut Pixmap, parent: Option<&Providers>) { + let mut inactive_nodes_map = HashMap::new(); + + if let Some(clip_id) = self.clip.as_ref().and_then(|c| c.id) { + inactive_nodes_map.insert(clip_id, true); + } + + let mut context = DrawContext { + offset_x: self.x.unwrap_or(0), + offset_y: self.y.unwrap_or(0), + color: self.color, + style: self.style, + stroke_width: self.stroke_width, + mask: None, + inactive_nodes_map: Some(inactive_nodes_map), + }; + + match parent { + Some(Providers::G(group)) => { + if let Some(parent_context) = group.context.as_ref() { + context.offset_x = parent_context.offset_x + context.offset_x; + context.offset_y = parent_context.offset_y + context.offset_y; + context.color = context.color.or(parent_context.color); + context.style = context.style.or(parent_context.style); + context.stroke_width = context.stroke_width.or(parent_context.stroke_width); + } + } + _ => {} + }; + + self.context = Some(context); + } +} + +impl Group { + pub fn set_clip_id(&mut self, id: usize) { + if self.clip.is_none() { + self.clip = Some(GroupClip::default()); + } + + self.clip.as_mut().unwrap().id = Some(id); + } + + pub fn set_context_mask(&mut self, pixmap: &mut Pixmap, path: &Path) { + let mut mask: Option = None; + + if let Some(GroupClip { invert, .. }) = &self.clip { + let mut clip_mask = Mask::new(pixmap.width(), pixmap.height()).unwrap(); + + clip_mask.fill_path(path, FillRule::Winding, true, Transform::default()); + + if let Some(true) = invert { + clip_mask.invert(); + } + + mask = Some(clip_mask); + } + + self.context.as_mut().unwrap().mask = mask; + } +} diff --git a/soft-skia/src/shape.rs b/soft-skia/src/shape.rs index fa810b6..cb83810 100644 --- a/soft-skia/src/shape.rs +++ b/soft-skia/src/shape.rs @@ -1,5 +1,7 @@ -pub use tiny_skia::{ColorU8, Paint, PathBuilder, Pixmap, Stroke, Transform, FillRule}; -use tiny_skia::{LineCap, LineJoin}; +use std::collections::HashMap; + +pub use tiny_skia::{ColorU8, FillRule, Mask, Paint, PathBuilder, Pixmap, Stroke, Transform}; +use tiny_skia::{LineCap, LineJoin, Path}; #[derive(Debug)] pub enum Shapes { @@ -10,15 +12,41 @@ pub enum Shapes { P(Points), } +#[derive(Debug)] +pub struct DrawContext { + pub offset_x: u32, + pub offset_y: u32, + pub color: Option, + pub style: Option, + pub stroke_width: Option, + pub mask: Option, + pub inactive_nodes_map: Option>, +} + +impl DrawContext { + pub fn default() -> Self { + DrawContext { + offset_x: 0, + offset_y: 0, + color: None, + style: None, + stroke_width: None, + mask: None, + inactive_nodes_map: None, + } + } +} + pub trait Shape { fn default() -> Self; - fn draw(&self, pixmap: &mut Pixmap) -> (); + fn draw(&self, pixmap: &mut Pixmap, context: &DrawContext) -> (); + fn get_path(&self, context: &DrawContext) -> Path; } #[derive(Debug, Clone, Copy)] pub enum PaintStyle { Stroke, - Fill + Fill, } #[derive(Debug)] @@ -27,8 +55,8 @@ pub struct Rect { pub y: u32, pub width: u32, pub height: u32, - pub color: ColorU8, - pub style: PaintStyle, + pub color: Option, + pub style: Option, } #[derive(Debug)] @@ -36,8 +64,8 @@ pub struct Circle { pub cx: u32, pub cy: u32, pub r: u32, - pub color: ColorU8, - pub style: PaintStyle, + pub color: Option, + pub style: Option, } #[derive(Debug)] @@ -47,62 +75,90 @@ pub struct RoundRect { pub width: u32, pub height: u32, pub r: u32, - pub color: ColorU8, - pub style: PaintStyle, + pub color: Option, + pub style: Option, } #[derive(Debug)] pub struct Line { pub p1: [u32; 2], pub p2: [u32; 2], - pub color: ColorU8, - pub stroke_width: u32 + pub color: Option, + pub stroke_width: Option, } #[derive(Debug)] pub struct Points { pub points: Vec<[u32; 2]>, - pub color: ColorU8, - pub stroke_width: u32, - pub style: PaintStyle, + pub color: Option, + pub stroke_width: Option, + pub style: Option, } impl Shapes { - pub fn draw(&self, pixmap: &mut Pixmap) -> () { + pub fn draw(&self, pixmap: &mut Pixmap, context: &DrawContext) -> () { + match self { + Shapes::R(rect) => rect.draw(pixmap, context), + Shapes::C(circle) => circle.draw(pixmap, context), + Shapes::RR(round_rect) => round_rect.draw(pixmap, context), + Shapes::L(line) => line.draw(pixmap, context), + Shapes::P(points) => points.draw(pixmap, context), + } + } + + pub fn get_path(&self, context: &DrawContext) -> Path { match self { - Shapes::R(rect) => rect.draw(pixmap), - Shapes::C(circle) => circle.draw(pixmap), - Shapes::RR(round_rect) => round_rect.draw(pixmap), - Shapes::L(line) => line.draw(pixmap), - Shapes::P(points) => points.draw(pixmap), + Shapes::R(rect) => rect.get_path(context), + Shapes::C(circle) => circle.get_path(context), + Shapes::RR(round_rect) => round_rect.get_path(context), + Shapes::L(line) => line.get_path(context), + Shapes::P(points) => points.get_path(context), } } } impl Shape for Rect { fn default() -> Self { - Rect { x: 0, y: 0, width: 0, height: 0, color: ColorU8::from_rgba(0, 0, 0, 255), style: PaintStyle::Fill } + Rect { + x: 0, + y: 0, + width: 0, + height: 0, + color: None, + style: None, + } } - fn draw(&self, pixmap: &mut Pixmap) -> () { - let mut pb = PathBuilder::new(); - let mut paint = Paint::default(); + fn draw(&self, pixmap: &mut Pixmap, context: &DrawContext) -> () { + let DrawContext { + color, + style, + stroke_width, + mask, + .. + } = context; - pb.move_to(self.x as f32, self.y as f32); - pb.line_to((self.x + self.width) as f32, self.y as f32); - pb.line_to((self.x + self.width) as f32, (self.y + self.height) as f32); - pb.line_to(self.x as f32, (self.y + self.height) as f32); - pb.line_to(self.x as f32, self.y as f32); - pb.close(); + let path = self.get_path(context); + + let mut paint = Paint::default(); + let color = self + .color + .unwrap_or(color.unwrap_or(ColorU8::from_rgba(0, 0, 0, 255))); + let style = self.style.unwrap_or(style.unwrap_or(PaintStyle::Fill)); + let mask = mask.as_ref(); - let path = pb.finish().unwrap(); - paint.set_color_rgba8(self.color.red(), self.color.green(), self.color.blue(), self.color.alpha()); + paint.set_color_rgba8(color.red(), color.green(), color.blue(), color.alpha()); - match self.style { + match style { PaintStyle::Stroke => { - let stroke = Stroke::default(); - pixmap.stroke_path(&path, &paint, &stroke, Transform::identity(), None); - }, + let mut stroke = Stroke::default(); + + if let &Some(w) = stroke_width { + stroke.width = w as f32 + } + + pixmap.stroke_path(&path, &paint, &stroke, Transform::identity(), mask); + } PaintStyle::Fill => { paint.anti_alias = true; pixmap.fill_path( @@ -110,13 +166,31 @@ impl Shape for Rect { &paint, FillRule::Winding, Transform::identity(), - None, + mask, ); - - }, + } _ => {} } + } + fn get_path( + &self, + DrawContext { + offset_x, offset_y, .. + }: &DrawContext, + ) -> Path { + let mut pb = PathBuilder::new(); + let x = self.x + offset_x; + let y = self.y + offset_y; + + pb.move_to(x as f32, y as f32); + pb.line_to((x + self.width) as f32, y as f32); + pb.line_to((x + self.width) as f32, (y + self.height) as f32); + pb.line_to(x as f32, (y + self.height) as f32); + pb.line_to(x as f32, y as f32); + pb.close(); + + pb.finish().unwrap() } } @@ -125,23 +199,37 @@ impl Shape for Circle { todo!() } - fn draw(&self, pixmap: &mut Pixmap) -> () { + fn draw(&self, pixmap: &mut Pixmap, context: &DrawContext) -> () { + let DrawContext { + color, + style, + stroke_width, + mask, + .. + } = context; + + let path = self.get_path(context); + let mut paint = Paint::default(); - let mut pb = PathBuilder::new(); + let color = self + .color + .unwrap_or(color.unwrap_or(ColorU8::from_rgba(0, 0, 0, 255))); + let style = self.style.unwrap_or(style.unwrap_or(PaintStyle::Fill)); + let mask = mask.as_ref(); - paint.set_color_rgba8(self.color.red(), self.color.green(), self.color.blue(), self.color.alpha()); + paint.set_color_rgba8(color.red(), color.green(), color.blue(), color.alpha()); paint.anti_alias = true; - pb.push_circle(self.cx as f32, self.cy as f32, self.r as f32); - pb.close(); + match style { + PaintStyle::Stroke => { + let mut stroke = Stroke::default(); - let path = pb.finish().unwrap(); + if let &Some(w) = stroke_width { + stroke.width = w as f32 + } - match self.style { - PaintStyle::Stroke => { - let stroke = Stroke::default(); - pixmap.stroke_path(&path, &paint, &stroke, Transform::identity(), None); - }, + pixmap.stroke_path(&path, &paint, &stroke, Transform::identity(), mask); + } PaintStyle::Fill => { paint.anti_alias = true; pixmap.fill_path( @@ -149,13 +237,28 @@ impl Shape for Circle { &paint, FillRule::Winding, Transform::identity(), - None, + mask, ); - - }, + } _ => {} } } + + fn get_path( + &self, + DrawContext { + offset_x, offset_y, .. + }: &DrawContext, + ) -> Path { + let mut pb: PathBuilder = PathBuilder::new(); + let x = self.cx + offset_x; + let y = self.cy + offset_y; + + pb.push_circle(x as f32, y as f32, self.r as f32); + pb.close(); + + pb.finish().unwrap() + } } impl Shape for RoundRect { @@ -163,31 +266,37 @@ impl Shape for RoundRect { todo!() } - fn draw(&self, pixmap: &mut Pixmap) -> () { + fn draw(&self, pixmap: &mut Pixmap, context: &DrawContext) -> () { + let DrawContext { + color, + style, + stroke_width, + mask, + .. + } = context; + + let path = self.get_path(context); + let mut paint = Paint::default(); - let mut pb = PathBuilder::new(); + let color = self + .color + .unwrap_or(color.unwrap_or(ColorU8::from_rgba(0, 0, 0, 255))); + let style = self.style.unwrap_or(style.unwrap_or(PaintStyle::Fill)); + let mask = mask.as_ref(); - paint.set_color_rgba8(self.color.red(), self.color.green(), self.color.blue(), self.color.alpha()); + paint.set_color_rgba8(color.red(), color.green(), color.blue(), color.alpha()); paint.anti_alias = true; - pb.move_to((self.x + self.r) as f32, self.y as f32); - pb.line_to((self.x + self.width - self.r) as f32, self.y as f32); - pb.quad_to((self.x + self.width) as f32, self.y as f32, (self.x + self.width) as f32, (self.y + self.r) as f32); - pb.line_to((self.x + self.width) as f32, (self.y + self.height - self.r) as f32); - pb.quad_to((self.x + self.width) as f32, (self.y + self.height) as f32, (self.x + self.width - self.r) as f32, (self.y + self.height) as f32); - pb.line_to((self.x + self.r) as f32, (self.y + self.height) as f32); - pb.quad_to(self.x as f32, (self.y + self.height) as f32, self.x as f32, (self.y + self.height - self.r) as f32); - pb.line_to(self.x as f32, (self.y + self.r) as f32); - pb.quad_to(self.x as f32, self.y as f32, (self.x + self.r) as f32, self.y as f32); - pb.close(); + match style { + PaintStyle::Stroke => { + let mut stroke = Stroke::default(); - let path = pb.finish().unwrap(); + if let &Some(w) = stroke_width { + stroke.width = w as f32 + } - match self.style { - PaintStyle::Stroke => { - let stroke = Stroke::default(); - pixmap.stroke_path(&path, &paint, &stroke, Transform::identity(), None); - }, + pixmap.stroke_path(&path, &paint, &stroke, Transform::identity(), mask); + } PaintStyle::Fill => { paint.anti_alias = true; pixmap.fill_path( @@ -195,40 +304,102 @@ impl Shape for RoundRect { &paint, FillRule::Winding, Transform::identity(), - None, + mask, ); - - }, + } _ => {} } } -} + fn get_path( + &self, + DrawContext { + offset_x, offset_y, .. + }: &DrawContext, + ) -> Path { + let mut pb = PathBuilder::new(); + let x = self.x + offset_x; + let y = self.y + offset_y; + + pb.move_to((x + self.r) as f32, y as f32); + pb.line_to((x + self.width - self.r) as f32, y as f32); + pb.quad_to( + (x + self.width) as f32, + y as f32, + (x + self.width) as f32, + (y + self.r) as f32, + ); + pb.line_to((x + self.width) as f32, (y + self.height - self.r) as f32); + pb.quad_to( + (x + self.width) as f32, + (y + self.height) as f32, + (x + self.width - self.r) as f32, + (y + self.height) as f32, + ); + pb.line_to((x + self.r) as f32, (y + self.height) as f32); + pb.quad_to( + x as f32, + (y + self.height) as f32, + x as f32, + (y + self.height - self.r) as f32, + ); + pb.line_to(x as f32, (y + self.r) as f32); + pb.quad_to(x as f32, y as f32, (x + self.r) as f32, y as f32); + pb.close(); + + pb.finish().unwrap() + } +} impl Shape for Line { fn default() -> Self { todo!() } - fn draw(&self, pixmap: &mut Pixmap) -> () { - let mut pb = PathBuilder::new(); - let mut paint = Paint::default(); + fn draw(&self, pixmap: &mut Pixmap, context: &DrawContext) -> () { + let DrawContext { + color, + stroke_width, + mask, + .. + } = context; - pb.move_to(self.p1[0] as f32, self.p1[1] as f32); - pb.line_to(self.p2[0] as f32, self.p2[1] as f32); - pb.close(); + let path = self.get_path(context); + + let mut paint = Paint::default(); + let color = self + .color + .unwrap_or(color.unwrap_or(ColorU8::from_rgba(0, 0, 0, 255))); + let stroke_width = self.stroke_width.unwrap_or(stroke_width.unwrap_or(1)); + let mask = mask.as_ref(); - let path = pb.finish().unwrap(); let stroke = Stroke { - width: self.stroke_width as f32, + width: stroke_width as f32, miter_limit: 4.0, line_cap: LineCap::Butt, line_join: LineJoin::Miter, dash: None, }; - paint.set_color_rgba8(self.color.red(), self.color.green(), self.color.blue(), self.color.alpha()); - pixmap.stroke_path(&path, &paint, &stroke, Transform::identity(), None); + paint.set_color_rgba8(color.red(), color.green(), color.blue(), color.alpha()); + pixmap.stroke_path(&path, &paint, &stroke, Transform::identity(), mask); + } + + fn get_path( + &self, + DrawContext { + offset_x, offset_y, .. + }: &DrawContext, + ) -> Path { + let mut pb = PathBuilder::new(); + let p1 = [self.p1[0] + offset_x, self.p1[1] + offset_y]; + let p2 = [self.p2[0] + offset_x, self.p2[1] + offset_y]; + + pb.move_to(p1[0] as f32, p1[1] as f32); + pb.line_to(p2[0] as f32, p2[1] as f32); + pb.close(); + + pb.finish().unwrap() } } @@ -237,30 +408,38 @@ impl Shape for Points { todo!() } - fn draw(&self, pixmap: &mut Pixmap) -> () { - let mut pb = PathBuilder::new(); - let mut paint = Paint::default(); - paint.set_color_rgba8(self.color.red(), self.color.green(), self.color.blue(), self.color.alpha()); + fn draw(&self, pixmap: &mut Pixmap, context: &DrawContext) -> () { + let DrawContext { + color, + style, + stroke_width, + mask, + .. + } = context; - pb.move_to(self.points[0][0] as f32, self.points[0][1] as f32); - for i in 1..self.points.len() { - pb.line_to(self.points[i][0] as f32, self.points[i][1] as f32); - } - pb.close(); + let path = self.get_path(context); + + let mut paint = Paint::default(); + let color = self + .color + .unwrap_or(color.unwrap_or(ColorU8::from_rgba(0, 0, 0, 255))); + let style = self.style.unwrap_or(style.unwrap_or(PaintStyle::Fill)); + let stroke_width = self.stroke_width.unwrap_or(stroke_width.unwrap_or(1)); + let mask = mask.as_ref(); - let path = pb.finish().unwrap(); + paint.set_color_rgba8(color.red(), color.green(), color.blue(), color.alpha()); - match self.style { + match style { PaintStyle::Stroke => { let stroke = Stroke { - width: self.stroke_width as f32, + width: stroke_width as f32, miter_limit: 4.0, line_cap: LineCap::Butt, line_join: LineJoin::Miter, dash: None, }; - pixmap.stroke_path(&path, &paint, &stroke, Transform::identity(), None); - }, + pixmap.stroke_path(&path, &paint, &stroke, Transform::identity(), mask); + } PaintStyle::Fill => { paint.anti_alias = true; pixmap.fill_path( @@ -268,36 +447,74 @@ impl Shape for Points { &paint, FillRule::Winding, Transform::identity(), - None, + mask, ); - - }, + } _ => {} } } + + fn get_path( + &self, + DrawContext { + offset_x, offset_y, .. + }: &DrawContext, + ) -> Path { + let mut pb = PathBuilder::new(); + + pb.move_to( + (self.points[0][0] + offset_x) as f32, + (self.points[0][1] + offset_y) as f32, + ); + for i in 1..self.points.len() { + pb.line_to( + (self.points[i][0] + offset_x) as f32, + (self.points[i][1] + offset_y) as f32, + ); + } + + pb.close(); + + pb.finish().unwrap() + } } #[cfg(test)] mod test { - use crate::shape::Rect; + use super::{ColorU8, FillRule, Paint, PathBuilder, Pixmap, Stroke, Transform}; use crate::shape::Circle; + use crate::shape::DrawContext; use crate::shape::PaintStyle; + use crate::shape::Rect; use crate::shape::Shape; - use super::{ColorU8, Paint, PathBuilder, Pixmap, Stroke, Transform, FillRule}; #[test] fn test_draw_rect() { let mut pixmap = Pixmap::new(400 as u32, 400 as u32).unwrap(); - let shape_0 = Rect { x: 20, y: 20, width: 200, height: 200, color: ColorU8::from_rgba(0, 255, 0, 200), style: PaintStyle::Fill }; - let shape_1 = Rect { x: 120, y: 80, width: 200, height: 200, color: ColorU8::from_rgba(0, 255, 0, 200), style: PaintStyle::Fill }; + let shape_0 = Rect { + x: 20, + y: 20, + width: 200, + height: 200, + color: None, + style: None, + }; + let shape_1 = Rect { + x: 120, + y: 80, + width: 200, + height: 200, + color: None, + style: None, + }; - shape_0.draw(&mut pixmap); - shape_1.draw(&mut pixmap); + shape_0.draw(&mut pixmap, &DrawContext::default()); + shape_1.draw(&mut pixmap, &DrawContext::default()); let data = pixmap.clone().encode_png().unwrap(); let data_url = base64::encode(&data); println!("{}", format!("data:image/png;base64,{}", data_url)); } -} \ No newline at end of file +} diff --git a/soft-skia/src/tree.rs b/soft-skia/src/tree.rs index 575b9e7..70ef3c9 100644 --- a/soft-skia/src/tree.rs +++ b/soft-skia/src/tree.rs @@ -1,27 +1,36 @@ -use std::slice::Iter; -use std::slice::IterMut; -use tiny_skia::{ColorU8}; -use crate::shape::Rect; +use crate::provider::Providers; use crate::shape::Circle; +use crate::shape::Rect; use crate::shape::Shape; +use crate::shape::DrawContext; use crate::shape::Shapes; +use std::slice::Iter; +use std::slice::IterMut; +use tiny_skia::ColorU8; +use tiny_skia::Pixmap; #[derive(Debug)] pub struct Tree { - root: Box + root: Box, } #[derive(Debug)] pub struct Node { pub id: usize, pub shape: Shapes, - pub children: Vec> + pub provider: Option, + pub children: Vec>, } impl Tree { pub fn default(id: usize) -> Self { Tree { - root: Box::new(Node { id, shape: Shapes::R(Rect::default()), children: Vec::new() }) + root: Box::new(Node { + id, + shape: Shapes::R(Rect::default()), + children: Vec::new(), + provider: None, + }), } } @@ -32,21 +41,33 @@ impl Tree { impl Node { pub fn default(id: usize, shape: Shapes) -> Self { - Node { id, shape, children: Vec::new() } + Node { + id, + shape, + children: Vec::new(), + provider: None, + } + } + + pub fn draw(&self, pixmap: &mut Pixmap, context: Option<&DrawContext>) { + self.shape + .draw(pixmap, context.unwrap_or(&DrawContext::default())); } pub fn append_node(&mut self, node: Node) { - self.children.push(Box::new(node)); + self.append_boxed_node(Box::new(node)); } pub fn insert_node_before_id(&mut self, before_id: usize, node: Node) { - let before_index = self.children.iter().position(|t| t.id == before_id).unwrap(); - self.children.insert(before_index, Box::new(node)); + self.insert_boxed_node_before_id(before_id, Box::new(node)); } pub fn insert_boxed_node_before_id(&mut self, before_id: usize, boxed_node: Box) { - let before_index = self.children.iter().position(|t| t.id == before_id).unwrap(); - self.children.insert(before_index, boxed_node); + if let Some(before_index) = self.children.iter().position(|t| t.id == before_id) { + self.children.insert(before_index, boxed_node); + } else { + self.append_boxed_node(boxed_node) + } } pub fn append_boxed_node(&mut self, boxed_node: Box) { @@ -86,12 +107,12 @@ impl Node { mod test { use crate::shape::PaintStyle; - use super::Tree; - use super::Node; - use super::Shapes; - use super::Rect; use super::Circle; use super::ColorU8; + use super::Node; + use super::Rect; + use super::Shapes; + use super::Tree; #[test] fn test_tree() { @@ -105,21 +126,42 @@ mod test { assert_eq!(root.id, 10086); match root.shape { - Shapes::R(Rect { x, y, width, height, color, style }) => { + Shapes::R(Rect { + x, + y, + width, + height, + color, + style, + }) => { assert_eq!(width, 0); assert_eq!(height, 0); - }, + } _ => { panic!() } } - root.shape = Shapes::R(Rect { x: 0, y: 0, width: 400, height: 400, color: ColorU8::from_rgba(0, 0, 0, 255), style: PaintStyle::Fill }); + root.shape = Shapes::R(Rect { + x: 0, + y: 0, + width: 400, + height: 400, + color: None, + style: None, + }); match root.shape { - Shapes::R(Rect { x, y, width, height, color, style: PaintStyle::Fill }) => { + Shapes::R(Rect { + x, + y, + width, + height, + color, + style: None, + }) => { assert_eq!(width, 400); assert_eq!(height, 400); - }, + } _ => { panic!() } @@ -128,19 +170,75 @@ mod test { #[test] fn test_node() { - let mut node = Node { id: 0, shape: Shapes::R(Rect { x: 0, y: 0, width: 400, height: 400, color: ColorU8::from_rgba(0, 0, 0, 255), style: PaintStyle::Fill }), children: Vec::new() }; + let mut node = Node { + id: 0, + shape: Shapes::R(Rect { + x: 0, + y: 0, + width: 400, + height: 400, + color: None, + style: None, + }), + children: Vec::new(), + provider: None, + }; assert_eq!(node.id, 0); assert_eq!(node.get_children_len(), 0); - node.append_node(Node { id: 1, shape: Shapes::C(Circle { cx: 100, cy: 100, r: 50, color: ColorU8::from_rgba(0, 0, 0, 255), style: PaintStyle::Fill }), children: Vec::new() }); + node.append_node(Node { + id: 1, + shape: Shapes::C(Circle { + cx: 100, + cy: 100, + r: 50, + color: None, + style: None, + }), + children: Vec::new(), + provider: None, + }); assert_eq!(node.get_children_len(), 1); - node.append_node(Node { id: 2, shape: Shapes::C(Circle { cx: 100, cy: 100, r: 50, color: ColorU8::from_rgba(0, 0, 0, 255), style: PaintStyle::Fill }), children: Vec::new() }); + node.append_node(Node { + id: 2, + shape: Shapes::C(Circle { + cx: 100, + cy: 100, + r: 50, + color: None, + style: None, + }), + children: Vec::new(), + provider: None, + }); assert_eq!(node.get_children_len(), 2); - node.append_boxed_node(Box::new(Node { id: 3, shape: Shapes::C(Circle { cx: 100, cy: 100, r: 50, color: ColorU8::from_rgba(0, 0, 0, 255), style: PaintStyle::Fill }), children: Vec::new() })); - node.append_boxed_node(Box::new(Node { id: 4, shape: Shapes::C(Circle { cx: 100, cy: 100, r: 50, color: ColorU8::from_rgba(0, 0, 0, 255), style: PaintStyle::Fill }), children: Vec::new() })); + node.append_boxed_node(Box::new(Node { + id: 3, + shape: Shapes::C(Circle { + cx: 100, + cy: 100, + r: 50, + color: None, + style: None, + }), + children: Vec::new(), + provider: None, + })); + node.append_boxed_node(Box::new(Node { + id: 4, + shape: Shapes::C(Circle { + cx: 100, + cy: 100, + r: 50, + color: None, + style: None, + }), + children: Vec::new(), + provider: None, + })); assert_eq!(node.get_children_len(), 4); let child_index_0 = node.get_child_by_index(0).unwrap(); @@ -162,13 +260,41 @@ mod test { assert_eq!(node.children[0].id, 1); assert_eq!(node.children[1].id, 4); - node.insert_node_before_id(4, Node { id: 200, shape: Shapes::C(Circle { cx: 100, cy: 100, r: 50, color: ColorU8::from_rgba(0, 0, 0, 255), style: PaintStyle::Fill }), children: Vec::new() }); + node.insert_node_before_id( + 4, + Node { + id: 200, + shape: Shapes::C(Circle { + cx: 100, + cy: 100, + r: 50, + color: None, + style: None, + }), + children: Vec::new(), + provider: None, + }, + ); assert_eq!(node.get_children_len(), 3); assert_eq!(node.children[0].id, 1); assert_eq!(node.children[1].id, 200); assert_eq!(node.children[2].id, 4); - node.insert_node_before_id(4, Node { id: 300, shape: Shapes::C(Circle { cx: 100, cy: 100, r: 50, color: ColorU8::from_rgba(0, 0, 0, 255), style: PaintStyle::Fill }), children: Vec::new() }); + node.insert_node_before_id( + 4, + Node { + id: 300, + shape: Shapes::C(Circle { + cx: 100, + cy: 100, + r: 50, + color: None, + style: None, + }), + children: Vec::new(), + provider: None, + }, + ); assert_eq!(node.get_children_len(), 4); assert_eq!(node.children[0].id, 1); assert_eq!(node.children[1].id, 200); @@ -177,22 +303,34 @@ mod test { for item in node.children_iter_mut() { match item.shape { - Shapes::C(Circle { ref mut cx, cy, r, color, style }) => { + Shapes::C(Circle { + ref mut cx, + cy, + r, + color, + style, + }) => { assert_eq!(*cx, 100); *cx = 200; assert_eq!(*cx, 200); - }, + } _ => panic!(), } } for item in node.children_iter() { match item.shape { - Shapes::C(Circle { cx, cy, r, color, style }) => { + Shapes::C(Circle { + cx, + cy, + r, + color, + style, + }) => { assert_eq!(cx, 200); - }, + } _ => panic!(), } } } -} \ No newline at end of file +} diff --git a/vue-playground/src/App.vue b/vue-playground/src/App.vue index 3b19475..6d13dc8 100644 --- a/vue-playground/src/App.vue +++ b/vue-playground/src/App.vue @@ -12,16 +12,26 @@ This super cool editor is based on vue-live !

-
You can edit this ->
- +
+ You can edit this + -> +
+
- +