mirror of
https://github.com/penpot/penpot.git
synced 2025-02-20 22:06:07 -05:00
🎉 Implement boolean operations (wasm)
This commit is contained in:
parent
1514faca55
commit
4e5f67676c
8 changed files with 126 additions and 16 deletions
|
@ -93,6 +93,9 @@
|
|||
(= type :path)
|
||||
(h/call internal-module "_set_shape_kind_path")
|
||||
|
||||
(= type :bool)
|
||||
(h/call internal-module "_set_shape_kind_bool")
|
||||
|
||||
:else
|
||||
(h/call internal-module "_set_shape_kind_rect")))
|
||||
|
||||
|
@ -343,6 +346,24 @@
|
|||
[hidden]
|
||||
(h/call internal-module "_set_shape_hidden" hidden))
|
||||
|
||||
|
||||
(defn- translate-bool-type
|
||||
[bool-type]
|
||||
(case bool-type
|
||||
:union 0
|
||||
:difference 1
|
||||
:intersection 2
|
||||
:exclusion 3
|
||||
0))
|
||||
|
||||
(defn set-shape-bool-type
|
||||
[bool-type]
|
||||
(h/call internal-module "_set_shape_bool_type" (translate-bool-type bool-type)))
|
||||
|
||||
(defn set-shape-bool-content
|
||||
[content]
|
||||
(set-shape-path-content content))
|
||||
|
||||
(def debounce-render-without-cache (fns/debounce render-without-cache 100))
|
||||
|
||||
(defn set-view
|
||||
|
@ -373,7 +394,8 @@
|
|||
blend-mode (dm/get-prop shape :blend-mode)
|
||||
opacity (dm/get-prop shape :opacity)
|
||||
hidden (dm/get-prop shape :hidden)
|
||||
content (dm/get-prop shape :content)]
|
||||
content (dm/get-prop shape :content)
|
||||
bool-content (dm/get-prop shape :bool-content)]
|
||||
|
||||
(use-shape id)
|
||||
(set-shape-type type)
|
||||
|
@ -386,6 +408,7 @@
|
|||
(set-shape-opacity opacity)
|
||||
(set-shape-hidden hidden)
|
||||
(when (and (some? content) (= type :path)) (set-shape-path-content content))
|
||||
(when (some? bool-content) (set-shape-bool-content bool-content))
|
||||
(let [pending' (concat (set-shape-fills fills) (set-shape-strokes strokes))]
|
||||
(recur (inc index) (into pending pending'))))
|
||||
pending))]
|
||||
|
|
|
@ -112,6 +112,8 @@
|
|||
(api/use-shape (:id self))
|
||||
(case k
|
||||
:type (api/set-shape-type v)
|
||||
:bool-type (api/set-shape-bool-type v)
|
||||
:bool-content (api/set-shape-bool-content v)
|
||||
:selrect (api/set-shape-selrect v)
|
||||
:show-content (api/set-shape-clip-content (not v))
|
||||
:rotation (api/set-shape-rotation v)
|
||||
|
|
|
@ -40,7 +40,7 @@ Gradient stops are serialized in a `Uint8Array`, each stop taking **5 bytes**.
|
|||
|
||||
**Stop offset** is the offset, being integer values ranging from `0` to `100` (both inclusive).
|
||||
|
||||
## StrokeCap
|
||||
## Stroke Caps
|
||||
|
||||
Stroke caps are serialized as `u8`:
|
||||
|
||||
|
@ -53,9 +53,9 @@ Stroke caps are serialized as `u8`:
|
|||
| 5 | Diamond |
|
||||
| 6 | Round |
|
||||
| 7 | Square |
|
||||
| _ | None |
|
||||
| \_ | None |
|
||||
|
||||
## StrokeStyle
|
||||
## Stroke Sytles
|
||||
|
||||
Stroke styles are serialized as `u8`:
|
||||
|
||||
|
@ -64,4 +64,16 @@ Stroke styles are serialized as `u8`:
|
|||
| 1 | Dotted |
|
||||
| 2 | Dashed |
|
||||
| 3 | Mixed |
|
||||
| _ | Solid |
|
||||
| \_ | Solid |
|
||||
|
||||
## Bool Operations
|
||||
|
||||
Bool operations (`bool-type`) are serialized as `u8`:
|
||||
|
||||
| Value | Field |
|
||||
| ----- | ------------ |
|
||||
| 0 | Union |
|
||||
| 1 | Difference |
|
||||
| 2 | Intersection |
|
||||
| 3 | Exclusion |
|
||||
| \_ | Union |
|
||||
|
|
|
@ -7,8 +7,7 @@ mod state;
|
|||
mod utils;
|
||||
mod view;
|
||||
|
||||
use crate::shapes::Kind;
|
||||
use crate::shapes::Path;
|
||||
use crate::shapes::{BoolType, Kind, Path};
|
||||
use skia_safe as skia;
|
||||
|
||||
use crate::state::State;
|
||||
|
@ -134,8 +133,26 @@ pub unsafe extern "C" fn set_shape_kind_rect() {
|
|||
pub unsafe extern "C" fn set_shape_kind_path() {
|
||||
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
||||
if let Some(shape) = state.current_shape() {
|
||||
let p = Path::try_from(Vec::new()).unwrap();
|
||||
shape.set_kind(Kind::Path(p));
|
||||
shape.set_kind(Kind::Path(Path::default()));
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn set_shape_kind_bool() {
|
||||
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
||||
if let Some(shape) = state.current_shape() {
|
||||
match shape.kind() {
|
||||
Kind::Bool(_, _) => {}
|
||||
_ => shape.set_kind(Kind::Bool(BoolType::default(), Path::default())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn set_shape_bool_type(raw_bool_type: u8) {
|
||||
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
||||
if let Some(shape) = state.current_shape() {
|
||||
shape.set_bool_type(BoolType::from(raw_bool_type))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ use uuid::Uuid;
|
|||
|
||||
use crate::render::{BlendMode, Renderable};
|
||||
|
||||
mod bools;
|
||||
mod fills;
|
||||
mod images;
|
||||
mod matrix;
|
||||
|
@ -11,6 +12,7 @@ mod paths;
|
|||
mod renderable;
|
||||
mod strokes;
|
||||
|
||||
pub use bools::*;
|
||||
pub use fills::*;
|
||||
pub use images::*;
|
||||
use matrix::*;
|
||||
|
@ -22,6 +24,7 @@ pub enum Kind {
|
|||
Rect(math::Rect),
|
||||
Circle(math::Rect),
|
||||
Path(Path),
|
||||
Bool(BoolType, Path),
|
||||
}
|
||||
|
||||
pub type Color = skia::Color;
|
||||
|
@ -61,6 +64,10 @@ impl Shape {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn kind(&self) -> Kind {
|
||||
self.kind.clone()
|
||||
}
|
||||
|
||||
pub fn set_selrect(&mut self, left: f32, top: f32, right: f32, bottom: f32) {
|
||||
self.selrect.set_ltrb(left, top, right, bottom);
|
||||
match self.kind {
|
||||
|
@ -169,7 +176,12 @@ impl Shape {
|
|||
|
||||
pub fn set_path_segments(&mut self, buffer: Vec<RawPathData>) -> Result<(), String> {
|
||||
let p = Path::try_from(buffer)?;
|
||||
self.kind = Kind::Path(p);
|
||||
let kind = match &self.kind {
|
||||
Kind::Bool(bool_type, _) => Kind::Bool(*bool_type, p),
|
||||
_ => Kind::Path(p),
|
||||
};
|
||||
self.kind = kind;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -177,9 +189,18 @@ impl Shape {
|
|||
self.blend_mode = mode;
|
||||
}
|
||||
|
||||
pub fn set_bool_type(&mut self, bool_type: BoolType) {
|
||||
let kind = match &self.kind {
|
||||
Kind::Bool(_, path) => Kind::Bool(bool_type, path.clone()),
|
||||
_ => Kind::Bool(bool_type, Path::default()),
|
||||
};
|
||||
|
||||
self.kind = kind;
|
||||
}
|
||||
|
||||
fn to_path_transform(&self) -> Option<skia::Matrix> {
|
||||
match self.kind {
|
||||
Kind::Path(_) => {
|
||||
Kind::Path(_) | Kind::Bool(_, _) => {
|
||||
let center = self.bounds().center();
|
||||
let mut matrix = skia::Matrix::new_identity();
|
||||
matrix.pre_translate(center);
|
||||
|
|
25
render-wasm/src/shapes/bools.rs
Normal file
25
render-wasm/src/shapes/bools.rs
Normal file
|
@ -0,0 +1,25 @@
|
|||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum BoolType {
|
||||
Union,
|
||||
Difference,
|
||||
Intersection,
|
||||
Exclusion,
|
||||
}
|
||||
|
||||
impl From<u8> for BoolType {
|
||||
fn from(value: u8) -> Self {
|
||||
match value {
|
||||
0 => Self::Union,
|
||||
1 => Self::Difference,
|
||||
2 => Self::Intersection,
|
||||
3 => Self::Exclusion,
|
||||
_ => Self::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for BoolType {
|
||||
fn default() -> Self {
|
||||
Self::Union
|
||||
}
|
||||
}
|
|
@ -88,6 +88,12 @@ fn starts_and_ends_at_same_point(path: &skia::Path) -> bool {
|
|||
start_point == end_point
|
||||
}
|
||||
|
||||
impl Default for Path {
|
||||
fn default() -> Self {
|
||||
Path::try_from(Vec::new()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<RawPathData>> for Path {
|
||||
type Error = String;
|
||||
|
||||
|
|
|
@ -64,7 +64,11 @@ impl Renderable for Shape {
|
|||
}
|
||||
|
||||
fn children_ids(&self) -> Vec<Uuid> {
|
||||
self.children.clone()
|
||||
if let Kind::Bool(_, _) = self.kind {
|
||||
vec![]
|
||||
} else {
|
||||
self.children.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -97,7 +101,7 @@ fn render_fill(
|
|||
(_, Kind::Circle(rect)) => {
|
||||
surface.canvas().draw_oval(rect, &fill.to_paint(&selrect));
|
||||
}
|
||||
(_, Kind::Path(path)) => {
|
||||
(_, Kind::Path(path)) | (_, Kind::Bool(_, path)) => {
|
||||
surface.canvas().draw_path(
|
||||
&path.to_skia_path().transform(path_transform.unwrap()),
|
||||
&fill.to_paint(&selrect),
|
||||
|
@ -130,7 +134,7 @@ fn render_stroke(
|
|||
match kind {
|
||||
Kind::Rect(rect) => draw_stroke_on_rect(surface.canvas(), stroke, rect, &selrect),
|
||||
Kind::Circle(rect) => draw_stroke_on_circle(surface.canvas(), stroke, rect, &selrect),
|
||||
Kind::Path(path) => {
|
||||
Kind::Path(path) | Kind::Bool(_, path) => {
|
||||
draw_stroke_on_path(surface.canvas(), stroke, path, &selrect, path_transform);
|
||||
}
|
||||
}
|
||||
|
@ -439,7 +443,7 @@ pub fn draw_image_fill_in_container(
|
|||
oval_path.add_oval(container, None);
|
||||
canvas.clip_path(&oval_path, skia::ClipOp::Intersect, true);
|
||||
}
|
||||
Kind::Path(p) => {
|
||||
Kind::Path(p) | Kind::Bool(_, p) => {
|
||||
canvas.clip_path(
|
||||
&p.to_skia_path().transform(path_transform.unwrap()),
|
||||
skia::ClipOp::Intersect,
|
||||
|
@ -476,7 +480,7 @@ pub fn draw_image_stroke_in_container(
|
|||
match kind {
|
||||
Kind::Rect(rect) => draw_stroke_on_rect(canvas, stroke, rect, &outer_rect),
|
||||
Kind::Circle(rect) => draw_stroke_on_circle(canvas, stroke, rect, &outer_rect),
|
||||
Kind::Path(p) => {
|
||||
Kind::Path(p) | Kind::Bool(_, p) => {
|
||||
let mut path = p.to_skia_path();
|
||||
path.transform(path_transform.unwrap());
|
||||
let stroke_kind = stroke.render_kind(p.is_open());
|
||||
|
|
Loading…
Add table
Reference in a new issue