mirror of
https://github.com/penpot/penpot.git
synced 2025-05-11 10:36:38 +02:00
✨ Serialize layout data
This commit is contained in:
parent
b4f6177be7
commit
80d5272248
17 changed files with 1006 additions and 375 deletions
|
@ -12,6 +12,7 @@
|
|||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.math :as mth]
|
||||
[app.common.svg.path :as path]
|
||||
[app.common.types.shape.layout :as ctl]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
[app.main.refs :as refs]
|
||||
|
@ -120,23 +121,12 @@
|
|||
:image 8))
|
||||
|
||||
(defn set-shape-type
|
||||
[type {:keys [masked]}]
|
||||
(h/call internal-module "_set_shape_type" (translate-shape-type type))
|
||||
(cond
|
||||
(= type :circle)
|
||||
(h/call internal-module "_set_shape_kind_circle")
|
||||
[type]
|
||||
(h/call internal-module "_set_shape_type" (translate-shape-type type)))
|
||||
|
||||
(= type :path)
|
||||
(h/call internal-module "_set_shape_kind_path")
|
||||
|
||||
(= type :bool)
|
||||
(h/call internal-module "_set_shape_kind_bool")
|
||||
|
||||
(= type :group)
|
||||
(h/call internal-module "_set_shape_kind_group" masked)
|
||||
|
||||
:else
|
||||
(h/call internal-module "_set_shape_kind_rect")))
|
||||
(defn set-masked
|
||||
[masked]
|
||||
(h/call internal-module "_set_shape_masked_group" masked))
|
||||
|
||||
(defn set-shape-selrect
|
||||
[selrect]
|
||||
|
@ -509,6 +499,139 @@
|
|||
(h/call internal-module "_set_shape_corners" r1 r2 r3 r4)))
|
||||
|
||||
|
||||
(defn translate-layout-flex-dir
|
||||
[flex-dir]
|
||||
(case flex-dir
|
||||
:row 0
|
||||
:column 1))
|
||||
|
||||
(defn translate-layout-align-items
|
||||
[align-items]
|
||||
(case align-items
|
||||
:start 0
|
||||
:end 1
|
||||
:center 2
|
||||
:stretch 3))
|
||||
|
||||
(defn translate-layout-align-content
|
||||
[align-content]
|
||||
(case align-content
|
||||
:start 0
|
||||
:end 1
|
||||
:center 2
|
||||
:space-between 3
|
||||
:space-around 4
|
||||
:space-evenly 5
|
||||
:stretch 6))
|
||||
|
||||
(defn translate-layout-justify-items
|
||||
[justify-items]
|
||||
(case justify-items
|
||||
:start 0
|
||||
:end 1
|
||||
:center 2
|
||||
:stretch 3))
|
||||
|
||||
(defn translate-layout-justify-content
|
||||
[justify-content]
|
||||
(case justify-content
|
||||
:start 0
|
||||
:end 1
|
||||
:center 2
|
||||
:space-between 3
|
||||
:space-around 4
|
||||
:space-evenly 5
|
||||
:stretch 6))
|
||||
|
||||
(defn translate-layout-wrap-type
|
||||
[wrap-type]
|
||||
(case wrap-type
|
||||
:wrap 0
|
||||
:nowrap 1))
|
||||
|
||||
(defn set-flex-layout
|
||||
[shape]
|
||||
(let [dir (-> (or (dm/get-prop shape :layout-flex-dir) :row) translate-layout-flex-dir)
|
||||
gap (dm/get-prop shape :layout-gap)
|
||||
row-gap (or (dm/get-prop gap :row-gap) 0)
|
||||
column-gap (or (dm/get-prop gap :column-gap) 0)
|
||||
|
||||
align-items (-> (or (dm/get-prop shape :layout-align-items) :start) translate-layout-align-items)
|
||||
align-content (-> (or (dm/get-prop shape :layout-align-content) :stretch) translate-layout-align-content)
|
||||
justify-items (-> (or (dm/get-prop shape :layout-justify-items) :start) translate-layout-justify-items)
|
||||
justify-content (-> (or (dm/get-prop shape :layout-justify-content) :stretch) translate-layout-justify-content)
|
||||
wrap-type (-> (or (dm/get-prop shape :layout-wrap-type) :nowrap) translate-layout-wrap-type)
|
||||
|
||||
padding (dm/get-prop shape :layout-padding)
|
||||
padding-top (or (dm/get-prop padding :p1) 0)
|
||||
padding-right (or (dm/get-prop padding :p2) 0)
|
||||
padding-bottom (or (dm/get-prop padding :p3) 0)
|
||||
padding-left (or (dm/get-prop padding :p4) 0)]
|
||||
(h/call internal-module
|
||||
"_set_flex_layout_data"
|
||||
dir
|
||||
row-gap
|
||||
column-gap
|
||||
align-items
|
||||
align-content
|
||||
justify-items
|
||||
justify-content
|
||||
wrap-type
|
||||
padding-top
|
||||
padding-right
|
||||
padding-bottom
|
||||
padding-left)))
|
||||
|
||||
(defn set-grid-layout
|
||||
[_shape])
|
||||
|
||||
(defn translate-layout-sizing
|
||||
[value]
|
||||
(case value
|
||||
:fill 0
|
||||
:fix 1
|
||||
:auto 2))
|
||||
|
||||
(defn set-layout-child
|
||||
[shape]
|
||||
(let [margins (dm/get-prop shape :layout-item-margin)
|
||||
margin-top (or (dm/get-prop margins :m1) 0)
|
||||
margin-right (or (dm/get-prop margins :m2) 0)
|
||||
margin-bottom (or (dm/get-prop margins :m3) 0)
|
||||
margin-left (or (dm/get-prop margins :m4) 0)
|
||||
|
||||
h-sizing (-> (dm/get-prop shape :layout-item-h-sizing) (or :auto) translate-layout-sizing)
|
||||
v-sizing (-> (dm/get-prop shape :layout-item-v-sizing) (or :auto) translate-layout-sizing)
|
||||
|
||||
max-h (dm/get-prop shape :layout-item-max-h)
|
||||
has-max-h (some? max-h)
|
||||
min-h (dm/get-prop shape :layout-item-min-h)
|
||||
has-min-h (some? min-h)
|
||||
max-w (dm/get-prop shape :layout-item-max-w)
|
||||
has-max-w (some? max-w)
|
||||
min-w (dm/get-prop shape :layout-item-min-w)
|
||||
has-min-w (some? min-w)
|
||||
is-absolute (boolean (dm/get-prop shape :layout-item-absolute))
|
||||
z-index (-> (dm/get-prop shape :layout-item-z-index) (or 0))]
|
||||
(h/call internal-module
|
||||
"_set_layout_child_data"
|
||||
margin-top
|
||||
margin-right
|
||||
margin-bottom
|
||||
margin-left
|
||||
h-sizing
|
||||
v-sizing
|
||||
has-max-h
|
||||
(or max-h 0)
|
||||
has-min-h
|
||||
(or min-h 0)
|
||||
has-max-w
|
||||
(or max-w 0)
|
||||
has-min-w
|
||||
(or min-w 0)
|
||||
is-absolute
|
||||
z-index)))
|
||||
|
||||
(defn- translate-shadow-style
|
||||
[style]
|
||||
(case style
|
||||
|
@ -581,7 +704,7 @@
|
|||
shadows (dm/get-prop shape :shadow)]
|
||||
|
||||
(use-shape id)
|
||||
(set-shape-type type {:masked masked})
|
||||
(set-shape-type type)
|
||||
(set-shape-clip-content clip-content)
|
||||
(set-shape-selrect selrect)
|
||||
(set-constraints-h constraint-h)
|
||||
|
@ -592,6 +715,8 @@
|
|||
(set-shape-opacity opacity)
|
||||
(set-shape-hidden hidden)
|
||||
(set-shape-children children)
|
||||
(when (and (= type :group) masked)
|
||||
(set-masked masked))
|
||||
(when (some? blur)
|
||||
(set-shape-blur blur))
|
||||
(when (and (some? content) (= type :path))
|
||||
|
@ -602,6 +727,16 @@
|
|||
(when (some? bool-content) (set-shape-bool-content bool-content))
|
||||
(when (some? corners) (set-shape-corners corners))
|
||||
(when (some? shadows) (set-shape-shadows shadows))
|
||||
|
||||
(when (ctl/any-layout-immediate-child? objects shape)
|
||||
(set-layout-child shape))
|
||||
|
||||
(when (ctl/flex-layout? shape)
|
||||
(set-flex-layout shape))
|
||||
|
||||
(when (ctl/grid-layout? shape)
|
||||
(set-grid-layout shape))
|
||||
|
||||
(let [pending' (concat (set-shape-fills fills) (set-shape-strokes strokes))]
|
||||
(recur (inc index) (into pending pending'))))
|
||||
pending))]
|
||||
|
|
|
@ -110,35 +110,37 @@
|
|||
[self k v]
|
||||
(when ^boolean shape/*wasm-sync*
|
||||
(api/use-shape (:id self))
|
||||
(let [masked (:masked-group self)]
|
||||
(case k
|
||||
:type (api/set-shape-type v {:masked masked})
|
||||
:bool-type (api/set-shape-bool-type v)
|
||||
:bool-content (api/set-shape-bool-content v)
|
||||
:selrect (api/set-shape-selrect v)
|
||||
:show-content (if (= (:type self) :frame)
|
||||
(api/set-shape-clip-content (not v))
|
||||
(api/set-shape-clip-content false))
|
||||
:rotation (api/set-shape-rotation v)
|
||||
:transform (api/set-shape-transform v)
|
||||
:fills (api/set-shape-fills v)
|
||||
:strokes (api/set-shape-strokes v)
|
||||
:blend-mode (api/set-shape-blend-mode v)
|
||||
:opacity (api/set-shape-opacity v)
|
||||
:hidden (api/set-shape-hidden v)
|
||||
:shapes (api/set-shape-children v)
|
||||
:blur (api/set-shape-blur v)
|
||||
:svg-attrs (when (= (:type self) :path)
|
||||
(api/set-shape-path-attrs v))
|
||||
:constraints-h (api/set-constraints-h v)
|
||||
:constraints-v (api/set-constraints-v v)
|
||||
:content (cond
|
||||
(= (:type self) :path)
|
||||
(api/set-shape-path-content v)
|
||||
(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 (if (= (:type self) :frame)
|
||||
(api/set-shape-clip-content (not v))
|
||||
(api/set-shape-clip-content false))
|
||||
:rotation (api/set-shape-rotation v)
|
||||
:transform (api/set-shape-transform v)
|
||||
:fills (api/set-shape-fills v)
|
||||
:strokes (api/set-shape-strokes v)
|
||||
:blend-mode (api/set-shape-blend-mode v)
|
||||
:opacity (api/set-shape-opacity v)
|
||||
:hidden (api/set-shape-hidden v)
|
||||
:shapes (api/set-shape-children v)
|
||||
:blur (api/set-shape-blur v)
|
||||
:constraints-h (api/set-constraints-h v)
|
||||
:constraints-v (api/set-constraints-v v)
|
||||
|
||||
(= (:type self) :svg-raw)
|
||||
(api/set-shape-svg-raw-content (api/get-static-markup self)))
|
||||
nil))
|
||||
:svg-attrs (when (= (:type self) :path)
|
||||
(api/set-shape-path-attrs v))
|
||||
:masked-group (when (and (= (:type self) :group) (:masked-group self))
|
||||
(api/set-masked (:masked-group self)))
|
||||
:content (cond
|
||||
(= (:type self) :path)
|
||||
(api/set-shape-path-content v)
|
||||
|
||||
(= (:type self) :svg-raw)
|
||||
(api/set-shape-svg-raw-content (api/get-static-markup self)))
|
||||
nil)
|
||||
;; when something synced with wasm
|
||||
;; is modified, we need to request
|
||||
;; a new render.
|
||||
|
|
|
@ -142,3 +142,75 @@ Shadow styles are serialized as `u8`:
|
|||
| 0 | Drop Shadow |
|
||||
| 1 | Inner Shadow |
|
||||
| \_ | Drop Shadow |
|
||||
|
||||
## Layout - Direction
|
||||
|
||||
| Value | Field |
|
||||
| ----- | -------|
|
||||
| 0 | Row |
|
||||
| 1 | Column |
|
||||
| \_ | error |
|
||||
|
||||
## Layout - Align Items
|
||||
|
||||
| Value | Field |
|
||||
| ----- | --------|
|
||||
| 0 | Start |
|
||||
| 1 | End |
|
||||
| 2 | Center |
|
||||
| 3 | Stretch |
|
||||
| \_ | error |
|
||||
|
||||
## Layout - Align Content
|
||||
|
||||
| Value | Field |
|
||||
| ----- | ------------- |
|
||||
| 0 | Start |
|
||||
| 1 | End |
|
||||
| 2 | Center |
|
||||
| 3 | Space between |
|
||||
| 4 | Space around |
|
||||
| 5 | Space evenly |
|
||||
| 6 | Stretch |
|
||||
| \_ | error |
|
||||
|
||||
## Layout - Justify items
|
||||
|
||||
| Value | Field |
|
||||
| ----- | --------|
|
||||
| 0 | Start |
|
||||
| 1 | End |
|
||||
| 2 | Center |
|
||||
| 3 | Stretch |
|
||||
| \_ | error |
|
||||
|
||||
## Layout - Justify content
|
||||
|
||||
|
||||
| Value | Field |
|
||||
| ----- | ------------- |
|
||||
| 0 | Start |
|
||||
| 1 | End |
|
||||
| 2 | Center |
|
||||
| 3 | Space between |
|
||||
| 4 | Space around |
|
||||
| 5 | Space evenly |
|
||||
| 6 | Stretch |
|
||||
| \_ | error |
|
||||
|
||||
## Layout - Wrap type
|
||||
|
||||
| Value | Field |
|
||||
| ----- | ------- |
|
||||
| 0 | Wrap |
|
||||
| 1 | No Wrap |
|
||||
| \_ | error |
|
||||
|
||||
## Layout - Sizing
|
||||
|
||||
| Value | Field |
|
||||
| ----- | ------|
|
||||
| 0 | Fill |
|
||||
| 1 | Fix |
|
||||
| 2 | Auto |
|
||||
| \_ | error |
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use skia::Rect;
|
||||
use skia_safe as skia;
|
||||
|
||||
mod debug;
|
||||
|
@ -11,7 +10,7 @@ mod utils;
|
|||
mod view;
|
||||
|
||||
use crate::mem::SerializableResult;
|
||||
use crate::shapes::{BoolType, ConstraintH, ConstraintV, Group, Kind, Path, TransformEntry, Type};
|
||||
use crate::shapes::{BoolType, ConstraintH, ConstraintV, TransformEntry, Type};
|
||||
|
||||
use crate::state::State;
|
||||
use crate::utils::uuid_from_u32_quartet;
|
||||
|
@ -131,50 +130,10 @@ pub extern "C" fn use_shape(a: u32, b: u32, c: u32, d: u32) {
|
|||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn set_shape_kind_group(masked: bool) {
|
||||
pub extern "C" fn set_shape_masked_group(masked: bool) {
|
||||
let state = unsafe { STATE.as_mut() }.expect("Got an invalid state pointer");
|
||||
if let Some(shape) = state.current_shape() {
|
||||
shape.set_kind(Kind::Group(Group::new(masked)));
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn set_shape_kind_circle() {
|
||||
let state = unsafe { STATE.as_mut() }.expect("Got an invalid state pointer");
|
||||
|
||||
if let Some(shape) = state.current_shape() {
|
||||
shape.set_kind(Kind::Circle(Rect::new_empty()));
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn set_shape_kind_rect() {
|
||||
let state = unsafe { STATE.as_mut() }.expect("Got an invalid state pointer");
|
||||
|
||||
if let Some(shape) = state.current_shape() {
|
||||
match shape.kind() {
|
||||
Kind::Rect(_, _) => {}
|
||||
_ => shape.set_kind(Kind::Rect(Rect::new_empty(), None)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub 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() {
|
||||
shape.set_kind(Kind::Path(Path::default()));
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub 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())),
|
||||
}
|
||||
shape.set_masked(masked);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -702,6 +661,101 @@ pub extern "C" fn clear_shape_shadows() {
|
|||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn set_flex_layout_data(
|
||||
dir: u8,
|
||||
row_gap: f32,
|
||||
column_gap: f32,
|
||||
align_items: u8,
|
||||
align_content: u8,
|
||||
justify_items: u8,
|
||||
justify_content: u8,
|
||||
wrap_type: u8,
|
||||
padding_top: f32,
|
||||
padding_right: f32,
|
||||
padding_bottom: f32,
|
||||
padding_left: f32,
|
||||
) {
|
||||
let dir = shapes::Direction::from_u8(dir);
|
||||
let align_items = shapes::AlignItems::from_u8(align_items);
|
||||
let align_content = shapes::AlignContent::from_u8(align_content);
|
||||
let justify_items = shapes::JustifyItems::from_u8(justify_items);
|
||||
let justify_content = shapes::JustifyContent::from_u8(justify_content);
|
||||
let wrap_type = shapes::WrapType::from_u8(wrap_type);
|
||||
|
||||
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
||||
if let Some(shape) = state.current_shape() {
|
||||
shape.set_flex_layout_data(
|
||||
dir,
|
||||
row_gap,
|
||||
column_gap,
|
||||
align_items,
|
||||
align_content,
|
||||
justify_items,
|
||||
justify_content,
|
||||
wrap_type,
|
||||
padding_top,
|
||||
padding_right,
|
||||
padding_bottom,
|
||||
padding_left,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn set_layout_child_data(
|
||||
margin_top: f32,
|
||||
margin_right: f32,
|
||||
margin_bottom: f32,
|
||||
margin_left: f32,
|
||||
h_sizing: u8,
|
||||
v_sizing: u8,
|
||||
has_max_h: bool,
|
||||
max_h: f32,
|
||||
has_min_h: bool,
|
||||
min_h: f32,
|
||||
has_max_w: bool,
|
||||
max_w: f32,
|
||||
has_min_w: bool,
|
||||
min_w: f32,
|
||||
is_absolute: bool,
|
||||
z_index: i32,
|
||||
) {
|
||||
let h_sizing = shapes::Sizing::from_u8(h_sizing);
|
||||
let v_sizing = shapes::Sizing::from_u8(v_sizing);
|
||||
let max_h = if has_max_h { Some(max_h) } else { None };
|
||||
let min_h = if has_min_h { Some(min_h) } else { None };
|
||||
let max_w = if has_max_w { Some(max_w) } else { None };
|
||||
let min_w = if has_min_w { Some(min_w) } else { None };
|
||||
|
||||
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
||||
if let Some(shape) = state.current_shape() {
|
||||
shape.set_flex_layout_child_data(
|
||||
margin_top,
|
||||
margin_right,
|
||||
margin_bottom,
|
||||
margin_left,
|
||||
h_sizing,
|
||||
v_sizing,
|
||||
max_h,
|
||||
min_h,
|
||||
max_w,
|
||||
min_w,
|
||||
is_absolute,
|
||||
z_index,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn set_grid_layout_data() {}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn add_grid_track() {}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn set_grid_cell() {}
|
||||
|
||||
fn main() {
|
||||
init_gl();
|
||||
}
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
use skia_safe::{Matrix, Point, Vector};
|
||||
use skia_safe as skia;
|
||||
|
||||
pub type Rect = skia::Rect;
|
||||
pub type Matrix = skia::Matrix;
|
||||
pub type Vector = skia::Vector;
|
||||
pub type Point = skia::Point;
|
||||
|
||||
pub trait VectorExt {
|
||||
fn new_points(a: &Point, b: &Point) -> Vector;
|
||||
|
|
|
@ -15,7 +15,7 @@ mod shadows;
|
|||
mod strokes;
|
||||
mod surfaces;
|
||||
|
||||
use crate::shapes::{Corners, Kind, Shape};
|
||||
use crate::shapes::{Corners, Shape, Type};
|
||||
use cache::CachedSurfaceImage;
|
||||
use gpu_state::GpuState;
|
||||
use options::RenderOptions;
|
||||
|
@ -289,8 +289,8 @@ impl RenderState {
|
|||
matrix.post_translate(center);
|
||||
matrix.pre_translate(-center);
|
||||
|
||||
match &shape.kind {
|
||||
Kind::SVGRaw(sr) => {
|
||||
match &shape.shape_type {
|
||||
Type::SVGRaw(sr) => {
|
||||
if let Some(modifiers) = modifiers {
|
||||
self.surfaces.shape.canvas().concat(&modifiers);
|
||||
}
|
||||
|
@ -490,8 +490,8 @@ impl RenderState {
|
|||
// the content and the second one rendering the mask so we need to do
|
||||
// an extra save_layer to keep all the masked group separate from other
|
||||
// already drawn elements.
|
||||
match element.kind {
|
||||
Kind::Group(group) => {
|
||||
match element.shape_type {
|
||||
Type::Group(group) => {
|
||||
if group.masked {
|
||||
let paint = skia::Paint::default();
|
||||
let layer_rec = skia::canvas::SaveLayerRec::default().paint(&paint);
|
||||
|
@ -528,8 +528,8 @@ impl RenderState {
|
|||
// Because masked groups needs two rendering passes (first drawing
|
||||
// the content and then drawing the mask), we need to do an
|
||||
// extra restore.
|
||||
match element.kind {
|
||||
Kind::Group(group) => {
|
||||
match element.shape_type {
|
||||
Type::Group(group) => {
|
||||
if group.masked {
|
||||
self.surfaces.current.canvas().restore();
|
||||
}
|
||||
|
@ -567,8 +567,8 @@ impl RenderState {
|
|||
let render_complete = self.viewbox.area.contains(element.selrect());
|
||||
if visited_children {
|
||||
if !visited_mask {
|
||||
match element.kind {
|
||||
Kind::Group(group) => {
|
||||
match element.shape_type {
|
||||
Type::Group(group) => {
|
||||
// When we're dealing with masked groups we need to
|
||||
// do a separate extra step to draw the mask (the last
|
||||
// element of a masked group) and blend (using
|
||||
|
@ -637,8 +637,9 @@ impl RenderState {
|
|||
if let Some(modifiers) = modifiers.get(&element.id) {
|
||||
transform.post_concat(&modifiers);
|
||||
}
|
||||
let corners = match element.kind {
|
||||
Kind::Rect(_, corners) => corners,
|
||||
let corners = match &element.shape_type {
|
||||
Type::Rect(data) => data.corners,
|
||||
Type::Frame(data) => data.corners,
|
||||
_ => None,
|
||||
};
|
||||
(bounds, corners, transform)
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
use crate::shapes::{Fill, ImageFill, Kind, Shape};
|
||||
use skia_safe::{self as skia, RRect, Rect};
|
||||
use crate::shapes::{Fill, ImageFill, Shape, Type};
|
||||
use skia_safe::{self as skia, RRect};
|
||||
|
||||
use super::RenderState;
|
||||
use crate::math::Rect;
|
||||
|
||||
fn draw_image_fill_in_container(
|
||||
render_state: &mut RenderState,
|
||||
|
@ -16,7 +17,6 @@ fn draw_image_fill_in_container(
|
|||
|
||||
let size = image_fill.size();
|
||||
let canvas = render_state.surfaces.shape.canvas();
|
||||
let kind = &shape.kind;
|
||||
let container = &shape.selrect;
|
||||
let path_transform = shape.to_path_transform();
|
||||
let paint = fill.to_paint(container);
|
||||
|
@ -54,28 +54,33 @@ fn draw_image_fill_in_container(
|
|||
canvas.save();
|
||||
|
||||
// Set the clipping rectangle to the container bounds
|
||||
match kind {
|
||||
Kind::Rect(_, _) => {
|
||||
match &shape.shape_type {
|
||||
Type::Rect(_) | Type::Frame(_) => {
|
||||
canvas.clip_rect(container, skia::ClipOp::Intersect, true);
|
||||
}
|
||||
Kind::Circle(_) => {
|
||||
Type::Circle => {
|
||||
let mut oval_path = skia::Path::new();
|
||||
oval_path.add_oval(container, None);
|
||||
canvas.clip_path(&oval_path, skia::ClipOp::Intersect, true);
|
||||
}
|
||||
Kind::Path(path) | Kind::Bool(_, path) => {
|
||||
if let Some(path_transform) = path_transform {
|
||||
canvas.clip_path(
|
||||
&path.to_skia_path().transform(&path_transform),
|
||||
skia::ClipOp::Intersect,
|
||||
true,
|
||||
);
|
||||
shape_type @ (Type::Path(_) | Type::Bool(_)) => {
|
||||
if let Some(path) = shape_type.path() {
|
||||
if let Some(path_transform) = path_transform {
|
||||
canvas.clip_path(
|
||||
&path.to_skia_path().transform(&path_transform),
|
||||
skia::ClipOp::Intersect,
|
||||
true,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Kind::SVGRaw(_) => {
|
||||
Type::SVGRaw(_) => {
|
||||
canvas.clip_rect(container, skia::ClipOp::Intersect, true);
|
||||
}
|
||||
Kind::Group(_) => unreachable!("A group should not have fills"),
|
||||
Type::Text => {
|
||||
// TODO: Text fill
|
||||
}
|
||||
Type::Group(_) => unreachable!("A group should not have fills"),
|
||||
}
|
||||
|
||||
// Draw the image with the calculated destination rectangle
|
||||
|
@ -94,31 +99,34 @@ pub fn render(render_state: &mut RenderState, shape: &Shape, fill: &Fill) {
|
|||
let canvas = render_state.surfaces.shape.canvas();
|
||||
let selrect = shape.selrect;
|
||||
let path_transform = shape.to_path_transform();
|
||||
let kind = &shape.kind;
|
||||
match (fill, kind) {
|
||||
|
||||
match (fill, &shape.shape_type) {
|
||||
(Fill::Image(image_fill), _) => {
|
||||
draw_image_fill_in_container(render_state, shape, fill, image_fill);
|
||||
}
|
||||
(_, Kind::Rect(rect, None)) => {
|
||||
canvas.draw_rect(rect, &fill.to_paint(&selrect));
|
||||
(_, Type::Rect(_) | Type::Frame(_)) => {
|
||||
if let Some(corners) = shape.shape_type.corners() {
|
||||
let rrect = RRect::new_rect_radii(selrect, &corners);
|
||||
canvas.draw_rrect(rrect, &fill.to_paint(&selrect));
|
||||
} else {
|
||||
canvas.draw_rect(selrect, &fill.to_paint(&selrect));
|
||||
}
|
||||
}
|
||||
(_, Kind::Rect(rect, Some(corners))) => {
|
||||
let rrect = RRect::new_rect_radii(rect, &corners);
|
||||
canvas.draw_rrect(rrect, &fill.to_paint(&selrect));
|
||||
(_, Type::Circle) => {
|
||||
canvas.draw_oval(selrect, &fill.to_paint(&selrect));
|
||||
}
|
||||
(_, Kind::Circle(rect)) => {
|
||||
canvas.draw_oval(rect, &fill.to_paint(&selrect));
|
||||
}
|
||||
(_, Kind::Path(path)) | (_, Kind::Bool(_, path)) => {
|
||||
let svg_attrs = &shape.svg_attrs;
|
||||
let mut skia_path = &mut path.to_skia_path();
|
||||
(_, Type::Path(_)) | (_, Type::Bool(_)) => {
|
||||
if let Some(path) = &shape.shape_type.path() {
|
||||
let svg_attrs = &shape.svg_attrs;
|
||||
let mut skia_path = &mut path.to_skia_path();
|
||||
|
||||
if let Some(path_transform) = path_transform {
|
||||
skia_path = skia_path.transform(&path_transform);
|
||||
if let Some("evenodd") = svg_attrs.get("fill-rule").map(String::as_str) {
|
||||
skia_path.set_fill_type(skia::PathFillType::EvenOdd);
|
||||
if let Some(path_transform) = path_transform {
|
||||
skia_path = skia_path.transform(&path_transform);
|
||||
if let Some("evenodd") = svg_attrs.get("fill-rule").map(String::as_str) {
|
||||
skia_path.set_fill_type(skia::PathFillType::EvenOdd);
|
||||
}
|
||||
canvas.draw_path(&skia_path, &fill.to_paint(&selrect));
|
||||
}
|
||||
canvas.draw_path(&skia_path, &fill.to_paint(&selrect));
|
||||
}
|
||||
}
|
||||
(_, _) => unreachable!("This shape should not have fills"),
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use crate::shapes::{Corners, Fill, ImageFill, Kind, Path, Shape, Stroke, StrokeCap, StrokeKind};
|
||||
use skia::Rect;
|
||||
use crate::math::{Matrix, Point, Rect};
|
||||
|
||||
use crate::shapes::{Corners, Fill, ImageFill, Path, Shape, Stroke, StrokeCap, StrokeKind, Type};
|
||||
use skia_safe::{self as skia, RRect};
|
||||
|
||||
use super::RenderState;
|
||||
|
@ -55,7 +56,7 @@ fn draw_stroke_on_path(
|
|||
stroke: &Stroke,
|
||||
path: &Path,
|
||||
selrect: &Rect,
|
||||
path_transform: Option<&skia::Matrix>,
|
||||
path_transform: Option<&Matrix>,
|
||||
svg_attrs: &HashMap<String, String>,
|
||||
scale: f32,
|
||||
) {
|
||||
|
@ -110,8 +111,8 @@ fn handle_stroke_cap(
|
|||
cap: StrokeCap,
|
||||
width: f32,
|
||||
paint: &mut skia::Paint,
|
||||
p1: &skia::Point,
|
||||
p2: &skia::Point,
|
||||
p1: &Point,
|
||||
p2: &Point,
|
||||
) {
|
||||
paint.set_style(skia::PaintStyle::Fill);
|
||||
paint.set_blend_mode(skia::BlendMode::Src);
|
||||
|
@ -154,7 +155,7 @@ fn handle_stroke_caps(
|
|||
dpr_scale: f32,
|
||||
) {
|
||||
let points_count = path.count_points();
|
||||
let mut points = vec![skia::Point::default(); points_count];
|
||||
let mut points = vec![Point::default(); points_count];
|
||||
let c_points = path.get_points(&mut points);
|
||||
|
||||
// Closed shapes don't have caps
|
||||
|
@ -186,8 +187,8 @@ fn handle_stroke_caps(
|
|||
fn draw_square_cap(
|
||||
canvas: &skia::Canvas,
|
||||
paint: &skia::Paint,
|
||||
center: &skia::Point,
|
||||
direction: &skia::Point,
|
||||
center: &Point,
|
||||
direction: &Point,
|
||||
size: f32,
|
||||
extra_rotation: f32,
|
||||
) {
|
||||
|
@ -195,27 +196,27 @@ fn draw_square_cap(
|
|||
let dy = direction.y - center.y;
|
||||
let angle = dy.atan2(dx);
|
||||
|
||||
let mut matrix = skia::Matrix::new_identity();
|
||||
let mut matrix = Matrix::new_identity();
|
||||
matrix.pre_rotate(
|
||||
angle.to_degrees() + extra_rotation,
|
||||
skia::Point::new(center.x, center.y),
|
||||
Point::new(center.x, center.y),
|
||||
);
|
||||
|
||||
let half_size = size / 2.0;
|
||||
let rect = skia::Rect::from_xywh(center.x - half_size, center.y - half_size, size, size);
|
||||
let rect = Rect::from_xywh(center.x - half_size, center.y - half_size, size, size);
|
||||
|
||||
let points = [
|
||||
skia::Point::new(rect.left(), rect.top()),
|
||||
skia::Point::new(rect.right(), rect.top()),
|
||||
skia::Point::new(rect.right(), rect.bottom()),
|
||||
skia::Point::new(rect.left(), rect.bottom()),
|
||||
Point::new(rect.left(), rect.top()),
|
||||
Point::new(rect.right(), rect.top()),
|
||||
Point::new(rect.right(), rect.bottom()),
|
||||
Point::new(rect.left(), rect.bottom()),
|
||||
];
|
||||
|
||||
let mut transformed_points = points.clone();
|
||||
matrix.map_points(&mut transformed_points, &points);
|
||||
|
||||
let mut path = skia::Path::new();
|
||||
path.move_to(skia::Point::new(center.x, center.y));
|
||||
path.move_to(Point::new(center.x, center.y));
|
||||
path.move_to(transformed_points[0]);
|
||||
path.line_to(transformed_points[1]);
|
||||
path.line_to(transformed_points[2]);
|
||||
|
@ -227,25 +228,22 @@ fn draw_square_cap(
|
|||
fn draw_arrow_cap(
|
||||
canvas: &skia::Canvas,
|
||||
paint: &skia::Paint,
|
||||
center: &skia::Point,
|
||||
direction: &skia::Point,
|
||||
center: &Point,
|
||||
direction: &Point,
|
||||
size: f32,
|
||||
) {
|
||||
let dx = direction.x - center.x;
|
||||
let dy = direction.y - center.y;
|
||||
let angle = dy.atan2(dx);
|
||||
|
||||
let mut matrix = skia::Matrix::new_identity();
|
||||
matrix.pre_rotate(
|
||||
angle.to_degrees() - 90.,
|
||||
skia::Point::new(center.x, center.y),
|
||||
);
|
||||
let mut matrix = Matrix::new_identity();
|
||||
matrix.pre_rotate(angle.to_degrees() - 90., Point::new(center.x, center.y));
|
||||
|
||||
let half_height = size / 2.;
|
||||
let points = [
|
||||
skia::Point::new(center.x, center.y - half_height),
|
||||
skia::Point::new(center.x - size, center.y + half_height),
|
||||
skia::Point::new(center.x + size, center.y + half_height),
|
||||
Point::new(center.x, center.y - half_height),
|
||||
Point::new(center.x - size, center.y + half_height),
|
||||
Point::new(center.x + size, center.y + half_height),
|
||||
];
|
||||
|
||||
let mut transformed_points = points.clone();
|
||||
|
@ -255,7 +253,7 @@ fn draw_arrow_cap(
|
|||
path.move_to(transformed_points[1]);
|
||||
path.line_to(transformed_points[0]);
|
||||
path.line_to(transformed_points[2]);
|
||||
path.move_to(skia::Point::new(center.x, center.y));
|
||||
path.move_to(Point::new(center.x, center.y));
|
||||
path.line_to(transformed_points[0]);
|
||||
|
||||
canvas.draw_path(&path, paint);
|
||||
|
@ -264,25 +262,22 @@ fn draw_arrow_cap(
|
|||
fn draw_triangle_cap(
|
||||
canvas: &skia::Canvas,
|
||||
paint: &skia::Paint,
|
||||
center: &skia::Point,
|
||||
direction: &skia::Point,
|
||||
center: &Point,
|
||||
direction: &Point,
|
||||
size: f32,
|
||||
) {
|
||||
let dx = direction.x - center.x;
|
||||
let dy = direction.y - center.y;
|
||||
let angle = dy.atan2(dx);
|
||||
|
||||
let mut matrix = skia::Matrix::new_identity();
|
||||
matrix.pre_rotate(
|
||||
angle.to_degrees() - 90.,
|
||||
skia::Point::new(center.x, center.y),
|
||||
);
|
||||
let mut matrix = Matrix::new_identity();
|
||||
matrix.pre_rotate(angle.to_degrees() - 90., Point::new(center.x, center.y));
|
||||
|
||||
let half_height = size / 2.;
|
||||
let points = [
|
||||
skia::Point::new(center.x, center.y - half_height),
|
||||
skia::Point::new(center.x - size, center.y + half_height),
|
||||
skia::Point::new(center.x + size, center.y + half_height),
|
||||
Point::new(center.x, center.y - half_height),
|
||||
Point::new(center.x - size, center.y + half_height),
|
||||
Point::new(center.x + size, center.y + half_height),
|
||||
];
|
||||
|
||||
let mut transformed_points = points.clone();
|
||||
|
@ -336,7 +331,6 @@ fn draw_image_stroke_in_container(
|
|||
|
||||
let size = image_fill.size();
|
||||
let canvas = render_state.surfaces.shape.canvas();
|
||||
let kind = &shape.kind;
|
||||
let container = &shape.selrect;
|
||||
let path_transform = shape.to_path_transform();
|
||||
let svg_attrs = &shape.svg_attrs;
|
||||
|
@ -349,56 +343,65 @@ fn draw_image_stroke_in_container(
|
|||
let layer_rec = skia::canvas::SaveLayerRec::default().paint(&pb);
|
||||
canvas.save_layer(&layer_rec);
|
||||
|
||||
// Draw the stroke based on the kind, we are using this stroke as a "selector" of the area of the image we want to show.
|
||||
// Draw the stroke based on the shape type, we are using this stroke as
|
||||
// a "selector" of the area of the image we want to show.
|
||||
let outer_rect = stroke.outer_rect(container);
|
||||
match kind {
|
||||
Kind::Rect(rect, corners) => draw_stroke_on_rect(
|
||||
canvas,
|
||||
stroke,
|
||||
rect,
|
||||
&outer_rect,
|
||||
corners,
|
||||
svg_attrs,
|
||||
dpr_scale,
|
||||
),
|
||||
Kind::Circle(rect) => {
|
||||
draw_stroke_on_circle(canvas, stroke, rect, &outer_rect, svg_attrs, dpr_scale)
|
||||
}
|
||||
Kind::SVGRaw(_) | Kind::Group(_) => unreachable!("This shape should not have strokes"),
|
||||
Kind::Path(p) | Kind::Bool(_, p) => {
|
||||
canvas.save();
|
||||
let mut path = p.to_skia_path();
|
||||
path.transform(&path_transform.unwrap());
|
||||
let stroke_kind = stroke.render_kind(p.is_open());
|
||||
match stroke_kind {
|
||||
StrokeKind::InnerStroke => {
|
||||
canvas.clip_path(&path, skia::ClipOp::Intersect, true);
|
||||
}
|
||||
StrokeKind::CenterStroke => {}
|
||||
StrokeKind::OuterStroke => {
|
||||
canvas.clip_path(&path, skia::ClipOp::Difference, true);
|
||||
}
|
||||
}
|
||||
let is_open = p.is_open();
|
||||
let mut paint = stroke.to_stroked_paint(is_open, &outer_rect, svg_attrs, dpr_scale);
|
||||
canvas.draw_path(&path, &paint);
|
||||
canvas.restore();
|
||||
if stroke.render_kind(is_open) == StrokeKind::OuterStroke {
|
||||
// Small extra inner stroke to overlap with the fill and avoid unnecesary artifacts
|
||||
paint.set_stroke_width(1. / dpr_scale);
|
||||
canvas.draw_path(&path, &paint);
|
||||
}
|
||||
handle_stroke_caps(
|
||||
&mut path,
|
||||
stroke,
|
||||
&outer_rect,
|
||||
|
||||
match &shape.shape_type {
|
||||
shape_type @ (Type::Rect(_) | Type::Frame(_)) => {
|
||||
draw_stroke_on_rect(
|
||||
canvas,
|
||||
is_open,
|
||||
stroke,
|
||||
container,
|
||||
&outer_rect,
|
||||
&shape_type.corners(),
|
||||
svg_attrs,
|
||||
dpr_scale,
|
||||
);
|
||||
}
|
||||
Type::Circle => {
|
||||
draw_stroke_on_circle(canvas, stroke, container, &outer_rect, svg_attrs, dpr_scale)
|
||||
}
|
||||
|
||||
shape_type @ (Type::Path(_) | Type::Bool(_)) => {
|
||||
if let Some(p) = shape_type.path() {
|
||||
canvas.save();
|
||||
let mut path = p.to_skia_path();
|
||||
path.transform(&path_transform.unwrap());
|
||||
let stroke_kind = stroke.render_kind(p.is_open());
|
||||
match stroke_kind {
|
||||
StrokeKind::InnerStroke => {
|
||||
canvas.clip_path(&path, skia::ClipOp::Intersect, true);
|
||||
}
|
||||
StrokeKind::CenterStroke => {}
|
||||
StrokeKind::OuterStroke => {
|
||||
canvas.clip_path(&path, skia::ClipOp::Difference, true);
|
||||
}
|
||||
}
|
||||
let is_open = p.is_open();
|
||||
let mut paint = stroke.to_stroked_paint(is_open, &outer_rect, svg_attrs, dpr_scale);
|
||||
canvas.draw_path(&path, &paint);
|
||||
canvas.restore();
|
||||
if stroke.render_kind(is_open) == StrokeKind::OuterStroke {
|
||||
// Small extra inner stroke to overlap with the fill and avoid unnecesary artifacts
|
||||
paint.set_stroke_width(1. / dpr_scale);
|
||||
canvas.draw_path(&path, &paint);
|
||||
}
|
||||
handle_stroke_caps(
|
||||
&mut path,
|
||||
stroke,
|
||||
&outer_rect,
|
||||
canvas,
|
||||
is_open,
|
||||
svg_attrs,
|
||||
dpr_scale,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_ => unreachable!("This shape should not have strokes"),
|
||||
}
|
||||
|
||||
// Draw the image. We are using now the SrcIn blend mode, so the rendered piece of image will the area of the stroke over the image.
|
||||
let mut image_paint = skia::Paint::default();
|
||||
image_paint.set_blend_mode(skia::BlendMode::SrcIn);
|
||||
|
@ -410,7 +413,7 @@ fn draw_image_stroke_in_container(
|
|||
canvas.draw_image_rect(image.unwrap(), None, dest_rect, &image_paint);
|
||||
|
||||
// Clear outer stroke for paths if necessary. When adding an outer stroke we need to empty the stroke added too in the inner area.
|
||||
if let Kind::Path(p) = kind {
|
||||
if let Type::Path(p) = &shape.shape_type {
|
||||
if stroke.render_kind(p.is_open()) == StrokeKind::OuterStroke {
|
||||
let mut path = p.to_skia_path();
|
||||
path.transform(&path_transform.unwrap());
|
||||
|
@ -433,31 +436,41 @@ pub fn render(render_state: &mut RenderState, shape: &Shape, stroke: &Stroke) {
|
|||
let dpr_scale = render_state.viewbox.zoom * render_state.options.dpr();
|
||||
let selrect = shape.selrect;
|
||||
let path_transform = shape.to_path_transform();
|
||||
let kind = &shape.kind;
|
||||
let svg_attrs = &shape.svg_attrs;
|
||||
|
||||
if let Fill::Image(image_fill) = &stroke.fill {
|
||||
draw_image_stroke_in_container(render_state, shape, stroke, image_fill);
|
||||
} else {
|
||||
match kind {
|
||||
Kind::Rect(rect, corners) => draw_stroke_on_rect(
|
||||
canvas, stroke, rect, &selrect, corners, svg_attrs, dpr_scale,
|
||||
),
|
||||
Kind::Circle(rect) => {
|
||||
draw_stroke_on_circle(canvas, stroke, rect, &selrect, &svg_attrs, dpr_scale)
|
||||
}
|
||||
Kind::Path(path) | Kind::Bool(_, path) => {
|
||||
let svg_attrs = &shape.svg_attrs;
|
||||
draw_stroke_on_path(
|
||||
match &shape.shape_type {
|
||||
shape_type @ (Type::Rect(_) | Type::Frame(_)) => {
|
||||
draw_stroke_on_rect(
|
||||
canvas,
|
||||
stroke,
|
||||
path,
|
||||
&selrect,
|
||||
path_transform.as_ref(),
|
||||
&selrect,
|
||||
&shape_type.corners(),
|
||||
svg_attrs,
|
||||
dpr_scale,
|
||||
);
|
||||
}
|
||||
Kind::SVGRaw(_) | Kind::Group(_) => unreachable!("This shape should not have strokes"),
|
||||
Type::Circle => {
|
||||
draw_stroke_on_circle(canvas, stroke, &selrect, &selrect, &svg_attrs, dpr_scale)
|
||||
}
|
||||
shape_type @ (Type::Path(_) | Type::Bool(_)) => {
|
||||
if let Some(path) = shape_type.path() {
|
||||
let svg_attrs = &shape.svg_attrs;
|
||||
draw_stroke_on_path(
|
||||
canvas,
|
||||
stroke,
|
||||
path,
|
||||
&selrect,
|
||||
path_transform.as_ref(),
|
||||
svg_attrs,
|
||||
dpr_scale,
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => unreachable!("This shape should not have strokes"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use skia_safe::{self as skia, Matrix, Point, Rect};
|
||||
use skia_safe::{self as skia};
|
||||
|
||||
use std::collections::HashMap;
|
||||
use uuid::Uuid;
|
||||
|
@ -7,10 +7,14 @@ use crate::render::BlendMode;
|
|||
|
||||
mod blurs;
|
||||
mod bools;
|
||||
mod corners;
|
||||
mod fills;
|
||||
mod frames;
|
||||
mod groups;
|
||||
mod layouts;
|
||||
mod modifiers;
|
||||
mod paths;
|
||||
mod rects;
|
||||
mod shadows;
|
||||
mod strokes;
|
||||
mod svgraw;
|
||||
|
@ -18,58 +22,84 @@ mod transform;
|
|||
|
||||
pub use blurs::*;
|
||||
pub use bools::*;
|
||||
pub use corners::*;
|
||||
pub use fills::*;
|
||||
pub use frames::*;
|
||||
pub use groups::*;
|
||||
pub use layouts::*;
|
||||
pub use modifiers::*;
|
||||
pub use paths::*;
|
||||
pub use rects::*;
|
||||
pub use shadows::*;
|
||||
pub use strokes::*;
|
||||
pub use svgraw::*;
|
||||
pub use transform::*;
|
||||
|
||||
use crate::math::Bounds;
|
||||
|
||||
pub type CornerRadius = Point;
|
||||
pub type Corners = [CornerRadius; 4];
|
||||
use crate::math;
|
||||
use crate::math::{Bounds, Matrix, Point};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Type {
|
||||
Frame,
|
||||
Group,
|
||||
Bool,
|
||||
Rect,
|
||||
Path,
|
||||
Frame(Frame),
|
||||
Group(Group),
|
||||
Bool(Bool),
|
||||
Rect(Rect),
|
||||
Path(Path),
|
||||
Text,
|
||||
Circle,
|
||||
SvgRaw,
|
||||
Image,
|
||||
SVGRaw(SVGRaw),
|
||||
}
|
||||
|
||||
impl Type {
|
||||
pub fn from(value: u8) -> Self {
|
||||
match value {
|
||||
0 => Type::Frame,
|
||||
1 => Type::Group,
|
||||
2 => Type::Bool,
|
||||
3 => Type::Rect,
|
||||
4 => Type::Path,
|
||||
0 => Type::Frame(Frame::default()),
|
||||
1 => Type::Group(Group::default()),
|
||||
2 => Type::Bool(Bool::default()),
|
||||
3 => Type::Rect(Rect::default()),
|
||||
4 => Type::Path(Path::default()),
|
||||
5 => Type::Text,
|
||||
6 => Type::Circle,
|
||||
7 => Type::SvgRaw,
|
||||
8 => Type::Image,
|
||||
_ => Type::Rect,
|
||||
7 => Type::SVGRaw(SVGRaw::default()),
|
||||
_ => Type::Rect(Rect::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Kind {
|
||||
Rect(Rect, Option<Corners>),
|
||||
Circle(Rect),
|
||||
Path(Path),
|
||||
Bool(BoolType, Path),
|
||||
SVGRaw(SVGRaw),
|
||||
Group(Group),
|
||||
pub fn corners(&self) -> Option<Corners> {
|
||||
match self {
|
||||
Type::Rect(Rect { corners, .. }) => *corners,
|
||||
Type::Frame(Frame { corners, .. }) => *corners,
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_corners(&mut self, corners: Corners) {
|
||||
match self {
|
||||
Type::Rect(data) => {
|
||||
data.corners = Some(corners);
|
||||
}
|
||||
Type::Frame(data) => {
|
||||
data.corners = Some(corners);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn path(&self) -> Option<&Path> {
|
||||
match self {
|
||||
Type::Path(path) => Some(path),
|
||||
Type::Bool(Bool { path, .. }) => Some(path),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn path_mut(&mut self) -> Option<&mut Path> {
|
||||
match self {
|
||||
Type::Path(path) => Some(path),
|
||||
Type::Bool(Bool { path, .. }) => Some(path),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Copy)]
|
||||
|
@ -124,8 +154,7 @@ pub struct Shape {
|
|||
pub id: Uuid,
|
||||
pub shape_type: Type,
|
||||
pub children: Vec<Uuid>,
|
||||
pub kind: Kind,
|
||||
pub selrect: Rect,
|
||||
pub selrect: math::Rect,
|
||||
pub transform: Matrix,
|
||||
pub rotation: f32,
|
||||
pub constraint_h: Option<ConstraintH>,
|
||||
|
@ -139,17 +168,17 @@ pub struct Shape {
|
|||
pub hidden: bool,
|
||||
pub svg: Option<skia::svg::Dom>,
|
||||
pub svg_attrs: HashMap<String, String>,
|
||||
shadows: Vec<Shadow>,
|
||||
pub shadows: Vec<Shadow>,
|
||||
pub layout_item: Option<LayoutItem>,
|
||||
}
|
||||
|
||||
impl Shape {
|
||||
pub fn new(id: Uuid) -> Self {
|
||||
Self {
|
||||
id,
|
||||
shape_type: Type::Rect,
|
||||
shape_type: Type::Rect(Rect::default()),
|
||||
children: Vec::<Uuid>::new(),
|
||||
kind: Kind::Rect(Rect::new_empty(), None),
|
||||
selrect: Rect::new_empty(),
|
||||
selrect: math::Rect::new_empty(),
|
||||
transform: Matrix::default(),
|
||||
rotation: 0.,
|
||||
constraint_h: None,
|
||||
|
@ -164,6 +193,7 @@ impl Shape {
|
|||
svg: None,
|
||||
svg_attrs: HashMap::new(),
|
||||
shadows: vec![],
|
||||
layout_item: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -172,28 +202,20 @@ impl Shape {
|
|||
}
|
||||
|
||||
pub fn is_frame(&self) -> bool {
|
||||
self.shape_type == Type::Frame
|
||||
matches!(self.shape_type, Type::Frame(_))
|
||||
}
|
||||
|
||||
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 {
|
||||
Kind::Rect(_, corners) => {
|
||||
self.kind = Kind::Rect(self.selrect.to_owned(), corners);
|
||||
}
|
||||
Kind::Circle(_) => {
|
||||
self.kind = Kind::Circle(self.selrect.to_owned());
|
||||
}
|
||||
|
||||
pub fn set_masked(&mut self, masked: bool) {
|
||||
match &mut self.shape_type {
|
||||
Type::Group(data) => {
|
||||
data.masked = masked;
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn set_kind(&mut self, kind: Kind) {
|
||||
self.kind = kind;
|
||||
}
|
||||
|
||||
pub fn kind(&self) -> Kind {
|
||||
self.kind.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_clip(&mut self, value: bool) {
|
||||
|
@ -232,6 +254,78 @@ impl Shape {
|
|||
self.hidden = value;
|
||||
}
|
||||
|
||||
pub fn set_flex_layout_child_data(
|
||||
&mut self,
|
||||
margin_top: f32,
|
||||
margin_right: f32,
|
||||
margin_bottom: f32,
|
||||
margin_left: f32,
|
||||
h_sizing: Sizing,
|
||||
v_sizing: Sizing,
|
||||
max_h: Option<f32>,
|
||||
min_h: Option<f32>,
|
||||
max_w: Option<f32>,
|
||||
min_w: Option<f32>,
|
||||
is_absolute: bool,
|
||||
z_index: i32,
|
||||
) {
|
||||
self.layout_item = Some(LayoutItem {
|
||||
margin_top,
|
||||
margin_right,
|
||||
margin_bottom,
|
||||
margin_left,
|
||||
h_sizing,
|
||||
v_sizing,
|
||||
max_h,
|
||||
min_h,
|
||||
max_w,
|
||||
min_w,
|
||||
is_absolute,
|
||||
z_index,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn set_flex_layout_data(
|
||||
&mut self,
|
||||
direction: Direction,
|
||||
row_gap: f32,
|
||||
column_gap: f32,
|
||||
align_items: AlignItems,
|
||||
align_content: AlignContent,
|
||||
justify_items: JustifyItems,
|
||||
justify_content: JustifyContent,
|
||||
wrap_type: WrapType,
|
||||
padding_top: f32,
|
||||
padding_right: f32,
|
||||
padding_bottom: f32,
|
||||
padding_left: f32,
|
||||
) {
|
||||
match &mut self.shape_type {
|
||||
Type::Frame(data) => {
|
||||
let layout_data = LayoutData {
|
||||
direction,
|
||||
align_items,
|
||||
align_content,
|
||||
justify_items,
|
||||
justify_content,
|
||||
padding_top,
|
||||
padding_right,
|
||||
padding_bottom,
|
||||
padding_left,
|
||||
};
|
||||
|
||||
let flex_data = FlexData {
|
||||
row_gap,
|
||||
column_gap,
|
||||
wrap_type,
|
||||
};
|
||||
|
||||
data.layout = Some(Layout::FlexLayout(layout_data, flex_data));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_blur(&mut self, blur_type: u8, hidden: bool, value: f32) {
|
||||
self.blur = Blur::new(blur_type, hidden, value);
|
||||
}
|
||||
|
@ -306,31 +400,34 @@ impl Shape {
|
|||
}
|
||||
|
||||
pub fn set_path_segments(&mut self, buffer: Vec<RawPathData>) -> Result<(), String> {
|
||||
let p = Path::try_from(buffer)?;
|
||||
let kind = match &self.kind {
|
||||
Kind::Bool(bool_type, _) => Kind::Bool(*bool_type, p),
|
||||
_ => Kind::Path(p),
|
||||
};
|
||||
self.kind = kind;
|
||||
let path = Path::try_from(buffer)?;
|
||||
|
||||
match &mut self.shape_type {
|
||||
Type::Bool(Bool { bool_type, .. }) => {
|
||||
self.shape_type = Type::Bool(Bool {
|
||||
bool_type: *bool_type,
|
||||
path,
|
||||
});
|
||||
}
|
||||
Type::Path(_) => {
|
||||
self.shape_type = Type::Path(path);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_path_attr(&mut self, name: String, value: String) {
|
||||
match &mut self.kind {
|
||||
Kind::Path(_) => {
|
||||
match self.shape_type {
|
||||
Type::Path(_) => {
|
||||
self.set_svg_attr(name, value);
|
||||
}
|
||||
Kind::Rect(_, _)
|
||||
| Kind::Circle(_)
|
||||
| Kind::SVGRaw(_)
|
||||
| Kind::Bool(_, _)
|
||||
| Kind::Group(_) => unreachable!("This shape should have path attrs"),
|
||||
_ => unreachable!("This shape should have path attrs"),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn set_svg_raw_content(&mut self, content: String) -> Result<(), String> {
|
||||
self.kind = Kind::SVGRaw(SVGRaw::from_content(content));
|
||||
self.shape_type = Type::SVGRaw(SVGRaw::from_content(content));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -339,37 +436,21 @@ impl Shape {
|
|||
}
|
||||
|
||||
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.shape_type = match &self.shape_type {
|
||||
Type::Bool(Bool { path, .. }) => Type::Bool(Bool {
|
||||
bool_type,
|
||||
path: path.clone(),
|
||||
}),
|
||||
_ => Type::Bool(Bool {
|
||||
bool_type,
|
||||
path: Path::default(),
|
||||
}),
|
||||
};
|
||||
|
||||
self.kind = kind;
|
||||
}
|
||||
|
||||
pub fn set_corners(&mut self, raw_corners: (f32, f32, f32, f32)) {
|
||||
match self.kind {
|
||||
Kind::Rect(_, _) => {
|
||||
let (r1, r2, r3, r4) = raw_corners;
|
||||
let are_straight_corners = r1.abs() <= f32::EPSILON
|
||||
&& r2.abs() <= f32::EPSILON
|
||||
&& r3.abs() <= f32::EPSILON
|
||||
&& r4.abs() <= f32::EPSILON;
|
||||
|
||||
let corners = if are_straight_corners {
|
||||
None
|
||||
} else {
|
||||
Some([
|
||||
(r1, r1).into(),
|
||||
(r2, r2).into(),
|
||||
(r3, r3).into(),
|
||||
(r4, r4).into(),
|
||||
])
|
||||
};
|
||||
|
||||
self.kind = Kind::Rect(self.selrect, corners);
|
||||
}
|
||||
_ => {}
|
||||
if let Some(corners) = make_corners(raw_corners) {
|
||||
self.shape_type.set_corners(corners);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -415,7 +496,7 @@ impl Shape {
|
|||
bounds
|
||||
}
|
||||
|
||||
pub fn selrect(&self) -> Rect {
|
||||
pub fn selrect(&self) -> math::Rect {
|
||||
self.selrect
|
||||
}
|
||||
|
||||
|
@ -432,9 +513,9 @@ impl Shape {
|
|||
}
|
||||
|
||||
pub fn children_ids(&self) -> Vec<Uuid> {
|
||||
if let Kind::Bool(_, _) = self.kind {
|
||||
if let Type::Bool(_) = self.shape_type {
|
||||
vec![]
|
||||
} else if let Kind::Group(group) = self.kind {
|
||||
} else if let Type::Group(group) = self.shape_type {
|
||||
if group.masked {
|
||||
self.children[1..self.children.len()].to_vec()
|
||||
} else {
|
||||
|
@ -462,7 +543,7 @@ impl Shape {
|
|||
}
|
||||
|
||||
pub fn is_recursive(&self) -> bool {
|
||||
!matches!(self.kind, Kind::SVGRaw(_))
|
||||
!matches!(self.shape_type, Type::SVGRaw(_))
|
||||
}
|
||||
|
||||
pub fn add_shadow(&mut self, shadow: Shadow) {
|
||||
|
@ -486,14 +567,13 @@ impl Shape {
|
|||
}
|
||||
|
||||
pub fn to_path_transform(&self) -> Option<Matrix> {
|
||||
match self.kind {
|
||||
Kind::Path(_) | Kind::Bool(_, _) => {
|
||||
match self.shape_type {
|
||||
Type::Path(_) | Type::Bool(_) => {
|
||||
let center = self.center();
|
||||
let mut matrix = Matrix::new_identity();
|
||||
matrix.pre_translate(center);
|
||||
matrix.pre_concat(&self.transform.invert()?);
|
||||
matrix.pre_translate(-center);
|
||||
|
||||
Some(matrix)
|
||||
}
|
||||
_ => None,
|
||||
|
@ -510,7 +590,7 @@ impl Shape {
|
|||
let width = bounds.width();
|
||||
let height = bounds.height();
|
||||
|
||||
self.selrect = Rect::from_xywh(
|
||||
self.selrect = math::Rect::from_xywh(
|
||||
center.x - width / 2.0,
|
||||
center.y - height / 2.0,
|
||||
width,
|
||||
|
@ -519,28 +599,12 @@ impl Shape {
|
|||
}
|
||||
|
||||
pub fn apply_transform(&mut self, transform: &Matrix) {
|
||||
match &self.kind {
|
||||
Kind::Rect(_, c) => {
|
||||
let c = c.clone();
|
||||
self.transform_selrect(&transform);
|
||||
self.kind = Kind::Rect(self.selrect, c);
|
||||
}
|
||||
Kind::Circle(_) => {
|
||||
self.transform_selrect(&transform);
|
||||
self.kind = Kind::Circle(self.selrect);
|
||||
}
|
||||
Kind::Path(path) => {
|
||||
let mut path = path.clone();
|
||||
self.transform_selrect(&transform);
|
||||
path.transform(&transform);
|
||||
self.kind = Kind::Path(path);
|
||||
}
|
||||
Kind::Bool(bool_type, path) => {
|
||||
let bool_type = *bool_type;
|
||||
let mut path = path.clone();
|
||||
self.transform_selrect(&transform);
|
||||
path.transform(&transform);
|
||||
self.kind = Kind::Bool(bool_type, path);
|
||||
self.transform_selrect(&transform);
|
||||
match &mut self.shape_type {
|
||||
shape_type @ (Type::Path(_) | Type::Bool(_)) => {
|
||||
if let Some(path) = shape_type.path_mut() {
|
||||
path.transform(&transform);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
@ -565,19 +629,45 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_apply_transform() {
|
||||
let mut shape = Shape::new(Uuid::new_v4());
|
||||
shape.set_shape_type(Type::Rect);
|
||||
shape.set_selrect(0.0, 10.0, 10.0, 0.0);
|
||||
shape.apply_transform(Matrix::scale((2.0, 2.0)));
|
||||
|
||||
match shape.kind {
|
||||
Kind::Rect(r, _) => {
|
||||
//println!(">>>{r:?}");
|
||||
assert_eq!(r.width(), 20.0);
|
||||
assert_eq!(r.height(), 20.0);
|
||||
}
|
||||
_ => assert!(false),
|
||||
fn test_set_corners() {
|
||||
let mut shape = any_shape();
|
||||
shape.set_corners((10.0, 20.0, 30.0, 40.0));
|
||||
if let Type::Rect(Rect { corners, .. }) = shape.shape_type {
|
||||
assert_eq!(
|
||||
corners,
|
||||
Some([
|
||||
Point { x: 10.0, y: 10.0 },
|
||||
Point { x: 20.0, y: 20.0 },
|
||||
Point { x: 30.0, y: 30.0 },
|
||||
Point { x: 40.0, y: 40.0 }
|
||||
])
|
||||
);
|
||||
} else {
|
||||
assert!(false);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_masked() {
|
||||
let mut shape = any_shape();
|
||||
shape.set_shape_type(Type::Group(Group { masked: false }));
|
||||
shape.set_masked(true);
|
||||
|
||||
if let Type::Group(Group { masked, .. }) = shape.shape_type {
|
||||
assert_eq!(masked, true);
|
||||
} else {
|
||||
assert!(false);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_apply_transform() {
|
||||
let mut shape = Shape::new(Uuid::new_v4());
|
||||
shape.set_shape_type(Type::Rect(Rect::default()));
|
||||
shape.set_selrect(0.0, 10.0, 10.0, 0.0);
|
||||
shape.apply_transform(&Matrix::scale((2.0, 2.0)));
|
||||
|
||||
assert_eq!(shape.selrect().width(), 20.0);
|
||||
assert_eq!(shape.selrect().height(), 20.0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,11 @@
|
|||
use super::Path;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Bool {
|
||||
pub bool_type: BoolType,
|
||||
pub path: Path,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum BoolType {
|
||||
Union,
|
||||
|
@ -6,6 +14,15 @@ pub enum BoolType {
|
|||
Exclusion,
|
||||
}
|
||||
|
||||
impl Default for Bool {
|
||||
fn default() -> Self {
|
||||
Bool {
|
||||
bool_type: BoolType::Union,
|
||||
path: Path::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for BoolType {
|
||||
fn from(value: u8) -> Self {
|
||||
match value {
|
||||
|
|
23
render-wasm/src/shapes/corners.rs
Normal file
23
render-wasm/src/shapes/corners.rs
Normal file
|
@ -0,0 +1,23 @@
|
|||
use skia_safe::Point;
|
||||
|
||||
pub type CornerRadius = Point;
|
||||
pub type Corners = [CornerRadius; 4];
|
||||
|
||||
pub fn make_corners(raw_corners: (f32, f32, f32, f32)) -> Option<Corners> {
|
||||
let (r1, r2, r3, r4) = raw_corners;
|
||||
let are_straight_corners = r1.abs() <= f32::EPSILON
|
||||
&& r2.abs() <= f32::EPSILON
|
||||
&& r3.abs() <= f32::EPSILON
|
||||
&& r4.abs() <= f32::EPSILON;
|
||||
|
||||
if are_straight_corners {
|
||||
None
|
||||
} else {
|
||||
Some([
|
||||
(r1, r1).into(),
|
||||
(r2, r2).into(),
|
||||
(r3, r3).into(),
|
||||
(r4, r4).into(),
|
||||
])
|
||||
}
|
||||
}
|
8
render-wasm/src/shapes/frames.rs
Normal file
8
render-wasm/src/shapes/frames.rs
Normal file
|
@ -0,0 +1,8 @@
|
|||
use super::Corners;
|
||||
use super::Layout;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct Frame {
|
||||
pub corners: Option<Corners>,
|
||||
pub layout: Option<Layout>,
|
||||
}
|
|
@ -1,10 +1,4 @@
|
|||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
||||
pub struct Group {
|
||||
pub masked: bool,
|
||||
}
|
||||
|
||||
impl Group {
|
||||
pub fn new(masked: bool) -> Self {
|
||||
Group { masked }
|
||||
}
|
||||
}
|
||||
|
|
195
render-wasm/src/shapes/layouts.rs
Normal file
195
render-wasm/src/shapes/layouts.rs
Normal file
|
@ -0,0 +1,195 @@
|
|||
#![allow(dead_code)]
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Layout {
|
||||
FlexLayout(LayoutData, FlexData),
|
||||
GridLayout(LayoutData, GridData),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Direction {
|
||||
Row,
|
||||
Column,
|
||||
}
|
||||
|
||||
impl Direction {
|
||||
pub fn from_u8(value: u8) -> Self {
|
||||
match value {
|
||||
0 => Self::Row,
|
||||
1 => Self::Column,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum AlignItems {
|
||||
Start,
|
||||
End,
|
||||
Center,
|
||||
Stretch,
|
||||
}
|
||||
|
||||
impl AlignItems {
|
||||
pub fn from_u8(value: u8) -> Self {
|
||||
match value {
|
||||
0 => Self::Start,
|
||||
1 => Self::End,
|
||||
2 => Self::Center,
|
||||
3 => Self::Stretch,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum AlignContent {
|
||||
Start,
|
||||
End,
|
||||
Center,
|
||||
SpaceBetween,
|
||||
SpaceAround,
|
||||
SpaceEvenly,
|
||||
Stretch,
|
||||
}
|
||||
|
||||
impl AlignContent {
|
||||
pub fn from_u8(value: u8) -> Self {
|
||||
match value {
|
||||
0 => Self::Start,
|
||||
1 => Self::End,
|
||||
2 => Self::Center,
|
||||
3 => Self::SpaceBetween,
|
||||
4 => Self::SpaceAround,
|
||||
5 => Self::SpaceEvenly,
|
||||
6 => Self::Stretch,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum JustifyItems {
|
||||
Start,
|
||||
End,
|
||||
Center,
|
||||
Stretch,
|
||||
}
|
||||
|
||||
impl JustifyItems {
|
||||
pub fn from_u8(value: u8) -> Self {
|
||||
match value {
|
||||
0 => Self::Start,
|
||||
1 => Self::End,
|
||||
2 => Self::Center,
|
||||
3 => Self::Stretch,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum JustifyContent {
|
||||
Start,
|
||||
End,
|
||||
Center,
|
||||
SpaceBetween,
|
||||
SpaceAround,
|
||||
SpaceEvenly,
|
||||
Stretch,
|
||||
}
|
||||
|
||||
impl JustifyContent {
|
||||
pub fn from_u8(value: u8) -> Self {
|
||||
match value {
|
||||
0 => Self::Start,
|
||||
1 => Self::End,
|
||||
2 => Self::Center,
|
||||
3 => Self::SpaceBetween,
|
||||
4 => Self::SpaceAround,
|
||||
5 => Self::SpaceEvenly,
|
||||
6 => Self::Stretch,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum WrapType {
|
||||
Wrap,
|
||||
NoWrap,
|
||||
}
|
||||
|
||||
impl WrapType {
|
||||
pub fn from_u8(value: u8) -> Self {
|
||||
match value {
|
||||
0 => Self::Wrap,
|
||||
1 => Self::NoWrap,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct GridTrack {}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Sizing {
|
||||
Fill,
|
||||
Fix,
|
||||
Auto,
|
||||
}
|
||||
|
||||
impl Sizing {
|
||||
pub fn from_u8(value: u8) -> Self {
|
||||
match value {
|
||||
0 => Self::Fill,
|
||||
1 => Self::Fix,
|
||||
2 => Self::Auto,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct LayoutData {
|
||||
pub direction: Direction,
|
||||
pub align_items: AlignItems,
|
||||
pub align_content: AlignContent,
|
||||
pub justify_items: JustifyItems,
|
||||
pub justify_content: JustifyContent,
|
||||
pub padding_top: f32,
|
||||
pub padding_right: f32,
|
||||
pub padding_bottom: f32,
|
||||
pub padding_left: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct FlexData {
|
||||
pub row_gap: f32,
|
||||
pub column_gap: f32,
|
||||
pub wrap_type: WrapType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct GridData {
|
||||
pub rows: Vec<GridTrack>,
|
||||
pub columns: Vec<GridTrack>,
|
||||
// layout-grid-cells ;; map of id->grid-cell
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct LayoutItem {
|
||||
pub margin_top: f32,
|
||||
pub margin_right: f32,
|
||||
pub margin_bottom: f32,
|
||||
pub margin_left: f32,
|
||||
pub h_sizing: Sizing,
|
||||
pub v_sizing: Sizing,
|
||||
pub max_h: Option<f32>,
|
||||
pub min_h: Option<f32>,
|
||||
pub max_w: Option<f32>,
|
||||
pub min_w: Option<f32>,
|
||||
pub is_absolute: bool,
|
||||
pub z_index: i32,
|
||||
}
|
|
@ -221,8 +221,8 @@ pub fn propagate_modifiers(state: &State, modifiers: Vec<TransformEntry>) -> Vec
|
|||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::shapes::Type;
|
||||
use skia::Point;
|
||||
use crate::math::{Matrix, Point};
|
||||
use crate::shapes::*;
|
||||
|
||||
#[test]
|
||||
fn test_propagate_shape() {
|
||||
|
@ -235,7 +235,7 @@ mod tests {
|
|||
|
||||
let parent_id = Uuid::new_v4();
|
||||
let mut parent = Shape::new(parent_id);
|
||||
parent.set_shape_type(Type::Group);
|
||||
parent.set_shape_type(Type::Group(Group::default()));
|
||||
parent.add_child(child_id);
|
||||
parent.set_selrect(1.0, 1.0, 5.0, 5.0);
|
||||
shapes.insert(parent_id, parent.clone());
|
||||
|
|
6
render-wasm/src/shapes/rects.rs
Normal file
6
render-wasm/src/shapes/rects.rs
Normal file
|
@ -0,0 +1,6 @@
|
|||
use super::Corners;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct Rect {
|
||||
pub corners: Option<Corners>,
|
||||
}
|
|
@ -8,3 +8,11 @@ impl SVGRaw {
|
|||
SVGRaw { content: svg }
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SVGRaw {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
content: String::from(""),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue