From 83d41dba6fa4d54d5eac28e8527cedd8bbbc2dd4 Mon Sep 17 00:00:00 2001 From: Alonso Torres Date: Wed, 26 Mar 2025 12:10:31 +0100 Subject: [PATCH] :sparkles: Serialization of grid layout data (#6148) * :sparkles: Add serializators for grid layout properties * :sparkles: Extract serializers for wasm api module --- frontend/src/app/render_wasm/api.cljs | 359 ++++++++---------- frontend/src/app/render_wasm/serializers.cljs | 253 ++++++++++++ render-wasm/docs/serialization.md | 44 ++- render-wasm/src/main.rs | 84 +++- render-wasm/src/shapes.rs | 75 +++- render-wasm/src/shapes/layouts.rs | 246 ++++++++++-- render-wasm/src/shapes/modifiers.rs | 25 +- .../src/shapes/modifiers/flex_layout.rs | 24 +- .../src/shapes/modifiers/grid_layout.rs | 64 +++- 9 files changed, 892 insertions(+), 282 deletions(-) create mode 100644 frontend/src/app/render_wasm/serializers.cljs diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index 7cfd3676d..b858cce9f 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -21,6 +21,7 @@ [app.main.store :as st] [app.main.ui.shapes.text.fontfaces :as fonts] [app.render-wasm.helpers :as h] + [app.render-wasm.serializers :as sr] [app.util.debug :as dbg] [app.util.http :as http] [app.util.webapi :as wapi] @@ -39,6 +40,7 @@ (def dpr (if use-dpr? js/window.devicePixelRatio 1.0)) + ;; Based on app.main.render/object-svg (mf/defc object-svg {::mf/props :obj} @@ -121,22 +123,9 @@ [clip-content] (h/call internal-module "_set_shape_clip_content" clip-content)) -(defn- translate-shape-type - [type] - (case type - :frame 0 - :group 1 - :bool 2 - :rect 3 - :path 4 - :text 5 - :circle 6 - :svg-raw 7 - :image 8)) - (defn set-shape-type [type] - (h/call internal-module "_set_shape_type" (translate-shape-type type))) + (h/call internal-module "_set_shape_type" (sr/translate-shape-type type))) (defn set-masked [masked] @@ -301,26 +290,6 @@ (store-image id)))))) fills)) -(defn- translate-stroke-style - [stroke-style] - (case stroke-style - :dotted 1 - :dashed 2 - :mixed 3 - 0)) - -(defn- translate-stroke-cap - [stroke-cap] - (case stroke-cap - :line-arrow 1 - :triangle-arrow 2 - :square-marker 3 - :circle-marker 4 - :diamond-marker 5 - :round 6 - :square 7 - 0)) - (defn set-shape-strokes [strokes] (h/call internal-module "_clear_shape_strokes") @@ -331,9 +300,9 @@ image (:stroke-image stroke) width (:stroke-width stroke) align (:stroke-alignment stroke) - style (-> stroke :stroke-style translate-stroke-style) - cap-start (-> stroke :stroke-cap-start translate-stroke-cap) - cap-end (-> stroke :stroke-cap-end translate-stroke-cap)] + style (-> stroke :stroke-style sr/translate-stroke-style) + cap-start (-> stroke :stroke-cap-start sr/translate-stroke-cap) + cap-end (-> stroke :stroke-cap-end sr/translate-stroke-cap)] (case align :inner (h/call internal-module "_add_shape_inner_stroke" width style cap-start cap-end) :outer (h/call internal-module "_add_shape_outer_stroke" width style cap-start cap-end) @@ -388,14 +357,7 @@ (h/call internal-module "_add_shape_stroke_solid_fill" rgba))))) strokes)) -(defn serialize-path-attrs - [svg-attrs] - (reduce - (fn [acc [key value]] - (str/concat - acc - (str/kebab key) "\0" - value "\0")) "" svg-attrs)) + (defn set-shape-path-attrs [attrs] @@ -403,7 +365,7 @@ attrs (-> attrs (dissoc :style) (merge style)) - str (serialize-path-attrs attrs) + str (sr/serialize-path-attrs attrs) size (count str) ptr (h/call internal-module "_alloc_bytes" size)] (h/call internal-module "stringToUTF8" str ptr size) @@ -426,95 +388,45 @@ (h/call internal-module "stringToUTF8" content ptr size) (h/call internal-module "_set_shape_svg_raw_content"))) -(defn- translate-blend-mode - [blend-mode] - (case blend-mode - :normal 3 - :darken 16 - :multiply 24 - :color-burn 19 - :lighten 17 - :screen 14 - :color-dodge 18 - :overlay 15 - :soft-light 21 - :hard-light 20 - :difference 22 - :exclusion 23 - :hue 25 - :saturation 26 - :color 27 - :luminosity 28 - 3)) + (defn set-shape-blend-mode [blend-mode] ;; These values correspond to skia::BlendMode representation ;; https://rust-skia.github.io/doc/skia_safe/enum.BlendMode.html - (h/call internal-module "_set_shape_blend_mode" (translate-blend-mode blend-mode))) + (h/call internal-module "_set_shape_blend_mode" (sr/translate-blend-mode blend-mode))) (defn set-shape-opacity [opacity] (h/call internal-module "_set_shape_opacity" (or opacity 1))) -(defn- translate-constraint-h - [type] - (case type - :left 0 - :right 1 - :leftright 2 - :center 3 - :scale 4)) + (defn set-constraints-h [constraint] (when constraint - (h/call internal-module "_set_shape_constraint_h" (translate-constraint-h constraint)))) - -(defn- translate-constraint-v - [type] - (case type - :top 0 - :bottom 1 - :topbottom 2 - :center 3 - :scale 4)) + (h/call internal-module "_set_shape_constraint_h" (sr/translate-constraint-h constraint)))) (defn set-constraints-v [constraint] (when constraint - (h/call internal-module "_set_shape_constraint_v" (translate-constraint-v constraint)))) + (h/call internal-module "_set_shape_constraint_v" (sr/translate-constraint-v constraint)))) (defn set-shape-hidden [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))) + (h/call internal-module "_set_shape_bool_type" (sr/translate-bool-type bool-type))) (defn set-shape-bool-content [content] (set-shape-path-content content)) -(defn- translate-blur-type - [blur-type] - (case blur-type - :layer-blur 1 - 0)) - (defn set-shape-blur [blur] - (let [type (-> blur :type translate-blur-type) + (let [type (-> blur :type sr/translate-blur-type) hidden (:hidden blur) value (:value blur)] (h/call internal-module "_set_shape_blur" type hidden value))) @@ -527,71 +439,18 @@ r4 (or (get corners 3) 0)] (h/call internal-module "_set_shape_corners" r1 r2 r3 r4))) - -(defn translate-layout-flex-dir - [flex-dir] - (case flex-dir - :row 0 - :row-reverse 1 - :column 2 - :column-reverse 3)) - -(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) + (let [dir (-> (or (dm/get-prop shape :layout-flex-dir) :row) sr/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) + align-items (-> (or (dm/get-prop shape :layout-align-items) :start) sr/translate-layout-align-items) + align-content (-> (or (dm/get-prop shape :layout-align-content) :stretch) sr/translate-layout-align-content) + justify-items (-> (or (dm/get-prop shape :layout-justify-items) :start) sr/translate-layout-justify-items) + justify-content (-> (or (dm/get-prop shape :layout-justify-content) :stretch) sr/translate-layout-justify-content) + wrap-type (-> (or (dm/get-prop shape :layout-wrap-type) :nowrap) sr/translate-layout-wrap-type) padding (dm/get-prop shape :layout-padding) padding-top (or (dm/get-prop padding :p1) 0) @@ -614,23 +473,128 @@ padding-left))) (defn set-grid-layout - [_shape]) + [shape] -(defn translate-layout-sizing - [value] - (case value - :fill 0 - :fix 1 - :auto 2)) + (let [dir (-> (or (dm/get-prop shape :layout-grid-dir) :row) sr/translate-layout-grid-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) -(defn translate-align-self - [value] - (when value - (case value - :start 0 - :end 1 - :center 2 - :stretch 3))) + align-items (-> (or (dm/get-prop shape :layout-align-items) :start) sr/translate-layout-align-items) + align-content (-> (or (dm/get-prop shape :layout-align-content) :stretch) sr/translate-layout-align-content) + justify-items (-> (or (dm/get-prop shape :layout-justify-items) :start) sr/translate-layout-justify-items) + justify-content (-> (or (dm/get-prop shape :layout-justify-content) :stretch) sr/translate-layout-justify-content) + + 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_grid_layout_data" + dir + row-gap + column-gap + align-items + align-content + justify-items + justify-content + padding-top + padding-right + padding-bottom + padding-left)) + + ;; Send Rows + (let [entry-size 5 + entries (:layout-grid-rows shape) + ptr (h/call internal-module "_alloc_bytes" (* entry-size (count entries))) + + heap + (js/Uint8Array. + (.-buffer (gobj/get ^js internal-module "HEAPU8")) + ptr + (* entry-size (count entries)))] + (loop [entries (seq entries) + offset 0] + (when-not (empty? entries) + (let [{:keys [type value]} (first entries)] + (.set heap (sr/u8 (sr/translate-grid-track-type type)) (+ offset 0)) + (.set heap (sr/f32->u8 value) (+ offset 1)) + (recur (rest entries) (+ offset entry-size))))) + (h/call internal-module "_set_grid_rows")) + + ;; Send Columns + (let [entry-size 5 + entries (:layout-grid-columns shape) + ptr (h/call internal-module "_alloc_bytes" (* entry-size (count entries))) + + heap + (js/Uint8Array. + (.-buffer (gobj/get ^js internal-module "HEAPU8")) + ptr + (* entry-size (count entries)))] + (loop [entries (seq entries) + offset 0] + (when-not (empty? entries) + (let [{:keys [type value]} (first entries)] + (.set heap (sr/u8 (sr/translate-grid-track-type type)) (+ offset 0)) + (.set heap (sr/f32->u8 value) (+ offset 1)) + (recur (rest entries) (+ offset entry-size))))) + (h/call internal-module "_set_grid_columns")) + + ;; Send cells + (let [entry-size 37 + entries (-> shape :layout-grid-cells vals) + ptr (h/call internal-module "_alloc_bytes" (* entry-size (count entries))) + + heap + (js/Uint8Array. + (.-buffer (gobj/get ^js internal-module "HEAPU8")) + ptr + (* entry-size (count entries)))] + + (loop [entries (seq entries) + offset 0] + (when-not (empty? entries) + (let [cell (first entries)] + + ;; row: [u8; 4], + (.set heap (sr/i32->u8 (:row cell)) (+ offset 0)) + + ;; row_span: [u8; 4], + (.set heap (sr/i32->u8 (:row-span cell)) (+ offset 4)) + + ;; column: [u8; 4], + (.set heap (sr/i32->u8 (:column cell)) (+ offset 8)) + + ;; column_span: [u8; 4], + (.set heap (sr/i32->u8 (:column-span cell)) (+ offset 12)) + + ;; has_align_self: u8, + (.set heap (sr/bool->u8 (some? (:align-self cell))) (+ offset 16)) + + ;; align_self: u8, + (.set heap (sr/u8 (sr/translate-align-self (:align-self cell))) (+ offset 17)) + + ;; has_justify_self: u8, + (.set heap (sr/bool->u8 (some? (:justify-self cell))) (+ offset 18)) + + ;; justify_self: u8, + (.set heap (sr/u8 (sr/translate-justify-self (:justify-self cell))) (+ offset 19)) + + ;; has_shape_id: u8, + (.set heap (sr/bool->u8 (d/not-empty? (:shapes cell))) (+ offset 20)) + + ;; shape_id_a: [u8; 4], + ;; shape_id_b: [u8; 4], + ;; shape_id_c: [u8; 4], + ;; shape_id_d: [u8; 4], + (.set heap (sr/uuid->u8 (or (-> cell :shapes first) uuid/zero)) (+ offset 21)) + + (recur (rest entries) (+ offset entry-size))))) + + (h/call internal-module "_set_grid_cells"))) (defn set-layout-child [shape] @@ -640,9 +604,9 @@ 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 :fix) translate-layout-sizing) - v-sizing (-> (dm/get-prop shape :layout-item-v-sizing) (or :fix) translate-layout-sizing) - align-self (-> (dm/get-prop shape :layout-item-align-self) translate-align-self) + h-sizing (-> (dm/get-prop shape :layout-item-h-sizing) (or :fix) sr/translate-layout-sizing) + v-sizing (-> (dm/get-prop shape :layout-item-v-sizing) (or :fix) sr/translate-layout-sizing) + align-self (-> (dm/get-prop shape :layout-item-align-self) sr/translate-align-self) max-h (dm/get-prop shape :layout-item-max-h) has-max-h (some? max-h) @@ -675,13 +639,6 @@ is-absolute z-index))) -(defn- translate-shadow-style - [style] - (case style - :drop-shadow 0 - :inner-shadow 1 - 0)) - (defn set-shape-shadows [shadows] (h/call internal-module "_clear_shape_shadows") @@ -697,7 +654,7 @@ y (dm/get-prop shadow :offset-y) spread (dm/get-prop shadow :spread) style (dm/get-prop shadow :style)] - (h/call internal-module "_add_shape_shadow" rgba blur spread x y (translate-shadow-style style) hidden) + (h/call internal-module "_add_shape_shadow" rgba blur spread x y (sr/translate-shadow-style style) hidden) (recur (inc index))))))) (defn utf8->buffer [text] @@ -898,24 +855,6 @@ (clear-drawing-cache) (request-render "set-objects"))))))) -(defn uuid->u8 - [id] - (let [buffer (uuid/get-u32 id) - u32-arr (js/Uint32Array. 4)] - (doseq [i (range 0 4)] - (aset u32-arr i (aget buffer i))) - (js/Uint8Array. (.-buffer u32-arr)))) - -(defn matrix->u8 - [{:keys [a b c d e f]}] - (let [f32-arr (js/Float32Array. 6)] - (aset f32-arr 0 a) - (aset f32-arr 1 b) - (aset f32-arr 2 c) - (aset f32-arr 3 d) - (aset f32-arr 4 e) - (aset f32-arr 5 f) - (js/Uint8Array. (.-buffer f32-arr)))) (defn data->entry [data offset] @@ -951,8 +890,8 @@ offset 0] (when-not (empty? entries) (let [{:keys [id transform]} (first entries)] - (.set heap (uuid->u8 id) offset) - (.set heap (matrix->u8 transform) (+ offset 16)) + (.set heap (sr/uuid->u8 id) offset) + (.set heap (sr/matrix->u8 transform) (+ offset 16)) (recur (rest entries) (+ offset entry-size))))) (let [result-ptr (h/call internal-module "_propagate_modifiers") @@ -991,8 +930,8 @@ offset 0] (when-not (empty? entries) (let [{:keys [id transform]} (first entries)] - (.set heap (uuid->u8 id) offset) - (.set heap (matrix->u8 transform) (+ offset 16)) + (.set heap (sr/uuid->u8 id) offset) + (.set heap (sr/matrix->u8 transform) (+ offset 16)) (recur (rest entries) (+ offset ENTRY_SIZE))))) (h/call internal-module "_set_modifiers") diff --git a/frontend/src/app/render_wasm/serializers.cljs b/frontend/src/app/render_wasm/serializers.cljs new file mode 100644 index 000000000..16acca6eb --- /dev/null +++ b/frontend/src/app/render_wasm/serializers.cljs @@ -0,0 +1,253 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.render-wasm.serializers + (:require + [app.common.uuid :as uuid] + [cuerdas.core :as str])) + +(defn u8 + [value] + (let [u8-arr (js/Uint8Array. 1)] + (aset u8-arr 0 value) + u8-arr)) + +(defn f32->u8 + [value] + (let [f32-arr (js/Float32Array. 1)] + (aset f32-arr 0 value) + (js/Uint8Array. (.-buffer f32-arr)))) + +(defn i32->u8 + [value] + (let [i32-arr (js/Int32Array. 1)] + (aset i32-arr 0 value) + (js/Uint8Array. (.-buffer i32-arr)))) + +(defn bool->u8 + [value] + (let [result (js/Uint8Array. 1)] + (aset result 0 (if value 1 0)) + result)) + +(defn uuid->u8 + [id] + (let [buffer (uuid/get-u32 id) + u32-arr (js/Uint32Array. 4)] + (aset u32-arr 0 (aget buffer 0)) + (aset u32-arr 1 (aget buffer 1)) + (aset u32-arr 2 (aget buffer 2)) + (aset u32-arr 3 (aget buffer 3)) + (js/Uint8Array. (.-buffer u32-arr)))) + +(defn matrix->u8 + [{:keys [a b c d e f]}] + (let [f32-arr (js/Float32Array. 6)] + (aset f32-arr 0 a) + (aset f32-arr 1 b) + (aset f32-arr 2 c) + (aset f32-arr 3 d) + (aset f32-arr 4 e) + (aset f32-arr 5 f) + (js/Uint8Array. (.-buffer f32-arr)))) + +(defn translate-shape-type + [type] + (case type + :frame 0 + :group 1 + :bool 2 + :rect 3 + :path 4 + :text 5 + :circle 6 + :svg-raw 7 + :image 8)) + +(defn translate-stroke-style + [stroke-style] + (case stroke-style + :dotted 1 + :dashed 2 + :mixed 3 + 0)) + +(defn translate-stroke-cap + [stroke-cap] + (case stroke-cap + :line-arrow 1 + :triangle-arrow 2 + :square-marker 3 + :circle-marker 4 + :diamond-marker 5 + :round 6 + :square 7 + 0)) + + +(defn serialize-path-attrs + [svg-attrs] + (reduce + (fn [acc [key value]] + (str/concat + acc + (str/kebab key) "\0" + value "\0")) "" svg-attrs)) + +(defn translate-blend-mode + [blend-mode] + (case blend-mode + :normal 3 + :darken 16 + :multiply 24 + :color-burn 19 + :lighten 17 + :screen 14 + :color-dodge 18 + :overlay 15 + :soft-light 21 + :hard-light 20 + :difference 22 + :exclusion 23 + :hue 25 + :saturation 26 + :color 27 + :luminosity 28 + 3)) + +(defn translate-constraint-h + [type] + (case type + :left 0 + :right 1 + :leftright 2 + :center 3 + :scale 4)) + +(defn translate-constraint-v + [type] + (case type + :top 0 + :bottom 1 + :topbottom 2 + :center 3 + :scale 4)) + +(defn translate-bool-type + [bool-type] + (case bool-type + :union 0 + :difference 1 + :intersection 2 + :exclusion 3 + 0)) + +(defn translate-blur-type + [blur-type] + (case blur-type + :layer-blur 1 + 0)) + +(defn translate-layout-flex-dir + [flex-dir] + (case flex-dir + :row 0 + :row-reverse 1 + :column 2 + :column-reverse 3)) + +(defn translate-layout-grid-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 translate-grid-track-type + [type] + (case type + :percent 0 + :flex 1 + :auto 2 + :fixed 3)) + +(defn translate-layout-sizing + [value] + (case value + :fill 0 + :fix 1 + :auto 2)) + +(defn translate-align-self + [value] + (when value + (case value + :auto 0 + :start 1 + :end 2 + :center 3 + :stretch 4))) + +(defn translate-justify-self + [value] + (when value + (case value + :auto 0 + :start 1 + :end 2 + :center 3 + :stretch 4))) + +(defn translate-shadow-style + [style] + (case style + :drop-shadow 0 + :inner-shadow 1 + 0)) + diff --git a/render-wasm/docs/serialization.md b/render-wasm/docs/serialization.md index 35fdab183..7318c62a2 100644 --- a/render-wasm/docs/serialization.md +++ b/render-wasm/docs/serialization.md @@ -142,7 +142,7 @@ Shadow styles are serialized as `u8`: ## Layout -### Direction +### Flex Direction | Value | Field | | ----- | ------------- | @@ -152,6 +152,14 @@ Shadow styles are serialized as `u8`: | 3 | ColumnReverse | | \_ | error | +### Grid Direction + +| Value | Field | +| ----- | ------------- | +| 0 | Row | +| 1 | Column | +| \_ | error | + ### Align Items | Value | Field | @@ -208,6 +216,28 @@ Shadow styles are serialized as `u8`: | 6 | Stretch | | \_ | error | +### Align Self + +| Value | Field | +| ----- | ------- | +| 0 | Auto | +| 1 | Start | +| 2 | End | +| 3 | Center | +| 4 | Stretch | +| \_ | error | + +### Justify Self + +| Value | Field | +| ----- | ------- | +| 0 | Auto | +| 1 | Start | +| 2 | End | +| 3 | Center | +| 4 | Stretch | +| \_ | error | + ### Wrap type | Value | Field | @@ -225,6 +255,18 @@ Shadow styles are serialized as `u8`: | 2 | Auto | | \_ | error | +### Grid Track Type + +| Value | Field | +| ----- | ------- | +| 0 | Percent | +| 1 | Flex | +| 2 | Auto | +| 3 | Fixed | +| \_ | error | + + + ## Font ### Style diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs index d687f2524..600b1879b 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -636,7 +636,7 @@ pub extern "C" fn set_flex_layout_data( padding_bottom: f32, padding_left: f32, ) { - let dir = shapes::Direction::from_u8(dir); + let dir = shapes::FlexDirection::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); @@ -714,13 +714,89 @@ pub extern "C" fn set_layout_child_data( } #[no_mangle] -pub extern "C" fn set_grid_layout_data() {} +pub extern "C" fn set_grid_layout_data( + dir: u8, + row_gap: f32, + column_gap: f32, + align_items: u8, + align_content: u8, + justify_items: u8, + justify_content: u8, + padding_top: f32, + padding_right: f32, + padding_bottom: f32, + padding_left: f32, +) { + let dir = shapes::GridDirection::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); + + with_current_shape!(state, |shape: &mut Shape| { + shape.set_grid_layout_data( + dir, + row_gap, + column_gap, + align_items, + align_content, + justify_items, + justify_content, + padding_top, + padding_right, + padding_bottom, + padding_left, + ); + }); +} #[no_mangle] -pub extern "C" fn add_grid_track() {} +pub extern "C" fn set_grid_columns() { + let bytes = mem::bytes(); + + let entries: Vec<_> = bytes + .chunks(size_of::()) + .map(|data| shapes::RawGridTrack::from_bytes(data.try_into().unwrap())) + .collect(); + + with_current_shape!(state, |shape: &mut Shape| { + shape.set_grid_columns(entries); + }); + + mem::free_bytes(); +} #[no_mangle] -pub extern "C" fn set_grid_cell() {} +pub extern "C" fn set_grid_rows() { + let bytes = mem::bytes(); + + let entries: Vec<_> = bytes + .chunks(size_of::()) + .map(|data| shapes::RawGridTrack::from_bytes(data.try_into().unwrap())) + .collect(); + + with_current_shape!(state, |shape: &mut Shape| { + shape.set_grid_rows(entries); + }); + + mem::free_bytes(); +} + +#[no_mangle] +pub extern "C" fn set_grid_cells() { + let bytes = mem::bytes(); + + let entries: Vec<_> = bytes + .chunks(size_of::()) + .map(|data| shapes::RawGridCell::from_bytes(data.try_into().unwrap())) + .collect(); + + with_current_shape!(state, |shape: &mut Shape| { + shape.set_grid_cells(entries); + }); + + mem::free_bytes(); +} fn main() { init_gl!(); diff --git a/render-wasm/src/shapes.rs b/render-wasm/src/shapes.rs index 55ddbd77f..be30bd310 100644 --- a/render-wasm/src/shapes.rs +++ b/render-wasm/src/shapes.rs @@ -318,7 +318,7 @@ impl Shape { pub fn set_flex_layout_data( &mut self, - direction: Direction, + direction: FlexDirection, row_gap: f32, column_gap: f32, align_items: AlignItems, @@ -334,7 +334,6 @@ impl Shape { match &mut self.shape_type { Type::Frame(data) => { let layout_data = LayoutData { - direction, align_items, align_content, justify_items, @@ -343,11 +342,12 @@ impl Shape { padding_right, padding_bottom, padding_left, + row_gap, + column_gap, }; let flex_data = FlexData { - row_gap, - column_gap, + direction, wrap_type, }; @@ -357,6 +357,73 @@ impl Shape { } } + pub fn set_grid_layout_data( + &mut self, + direction: GridDirection, + row_gap: f32, + column_gap: f32, + align_items: AlignItems, + align_content: AlignContent, + justify_items: JustifyItems, + justify_content: JustifyContent, + padding_top: f32, + padding_right: f32, + padding_bottom: f32, + padding_left: f32, + ) { + match &mut self.shape_type { + Type::Frame(data) => { + let layout_data = LayoutData { + align_items, + align_content, + justify_items, + justify_content, + padding_top, + padding_right, + padding_bottom, + padding_left, + row_gap, + column_gap, + }; + + let mut grid_data = GridData::default(); + grid_data.direction = direction; + data.layout = Some(Layout::GridLayout(layout_data, grid_data)); + } + _ => {} + } + } + + pub fn set_grid_columns(&mut self, tracks: Vec) { + let Type::Frame(frame_data) = &mut self.shape_type else { + return; + }; + let Some(Layout::GridLayout(_, grid_data)) = &mut frame_data.layout else { + return; + }; + grid_data.columns = tracks.iter().map(GridTrack::from_raw).collect(); + } + + pub fn set_grid_rows(&mut self, tracks: Vec) { + let Type::Frame(frame_data) = &mut self.shape_type else { + return; + }; + let Some(Layout::GridLayout(_, grid_data)) = &mut frame_data.layout else { + return; + }; + grid_data.rows = tracks.iter().map(GridTrack::from_raw).collect(); + } + + pub fn set_grid_cells(&mut self, cells: Vec) { + let Type::Frame(frame_data) = &mut self.shape_type else { + return; + }; + let Some(Layout::GridLayout(_, grid_data)) = &mut frame_data.layout else { + return; + }; + grid_data.cells = cells.iter().map(GridCell::from_raw).collect(); + } + pub fn set_blur(&mut self, blur_type: u8, hidden: bool, value: f32) { self.blur = Blur::new(blur_type, hidden, value); } diff --git a/render-wasm/src/shapes/layouts.rs b/render-wasm/src/shapes/layouts.rs index 3dcdf2e16..a558767ab 100644 --- a/render-wasm/src/shapes/layouts.rs +++ b/render-wasm/src/shapes/layouts.rs @@ -1,3 +1,6 @@ +use crate::utils::uuid_from_u32_quartet; +use uuid::Uuid; + #[derive(Debug, Clone, PartialEq)] #[allow(dead_code)] pub enum Layout { @@ -6,14 +9,14 @@ pub enum Layout { } #[derive(Debug, Clone, PartialEq)] -pub enum Direction { +pub enum FlexDirection { Row, RowReverse, Column, ColumnReverse, } -impl Direction { +impl FlexDirection { pub fn from_u8(value: u8) -> Self { match value { 0 => Self::Row, @@ -25,6 +28,22 @@ impl Direction { } } +#[derive(Debug, Clone, PartialEq)] +pub enum GridDirection { + Row, + Column, +} + +impl GridDirection { + pub fn from_u8(value: u8) -> Self { + match value { + 0 => Self::Row, + 1 => Self::Column, + _ => unreachable!(), + } + } +} + #[derive(Debug, Clone, PartialEq)] pub enum AlignItems { Start, @@ -132,9 +151,82 @@ impl WrapType { } } } +#[derive(Debug, Clone, PartialEq)] +pub enum GridTrackType { + Percent, + Flex, + Auto, + Fixed, +} + +impl GridTrackType { + pub fn from_u8(value: u8) -> Self { + match value { + 0 => Self::Percent, + 1 => Self::Flex, + 2 => Self::Auto, + 3 => Self::Fixed, + _ => unreachable!(), + } + } +} #[derive(Debug, Clone, PartialEq)] -pub struct GridTrack {} +pub struct GridTrack { + track_type: GridTrackType, + value: f32, +} + +impl GridTrack { + pub fn from_raw(raw: &RawGridTrack) -> Self { + Self { + track_type: GridTrackType::from_u8(raw.track_type), + value: f32::from_le_bytes(raw.value), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct GridCell { + row: i32, + row_span: i32, + column: i32, + column_span: i32, + align_self: Option, + justify_self: Option, + shape: Option, +} + +impl GridCell { + pub fn from_raw(raw: &RawGridCell) -> Self { + Self { + row: i32::from_le_bytes(raw.row), + row_span: i32::from_le_bytes(raw.row_span), + column: i32::from_le_bytes(raw.column), + column_span: i32::from_le_bytes(raw.column_span), + align_self: if raw.has_align_self == 1 { + AlignSelf::from_u8(raw.align_self) + } else { + None + }, + justify_self: if raw.has_justify_self == 1 { + JustifySelf::from_u8(raw.justify_self) + } else { + None + }, + shape: if raw.has_shape_id == 1 { + Some(uuid_from_u32_quartet( + u32::from_le_bytes(raw.shape_id_a), + u32::from_le_bytes(raw.shape_id_b), + u32::from_le_bytes(raw.shape_id_c), + u32::from_le_bytes(raw.shape_id_d), + )) + } else { + None + }, + } + } +} #[derive(Debug, Clone, PartialEq, Copy)] pub enum Sizing { @@ -156,7 +248,6 @@ impl Sizing { #[derive(Debug, Clone, PartialEq)] pub struct LayoutData { - pub direction: Direction, pub align_items: AlignItems, pub align_content: AlignContent, pub justify_items: JustifyItems, @@ -165,33 +256,13 @@ pub struct LayoutData { pub padding_right: f32, pub padding_bottom: f32, pub padding_left: f32, -} - -impl LayoutData { - pub fn is_reverse(&self) -> bool { - match &self.direction { - Direction::RowReverse | Direction::ColumnReverse => true, - _ => false, - } - } - pub fn is_row(&self) -> bool { - match &self.direction { - Direction::RowReverse | Direction::Row => true, - _ => false, - } - } - - #[allow(dead_code)] - pub fn is_column(&self) -> bool { - match &self.direction { - Direction::ColumnReverse | Direction::Column => true, - _ => false, - } - } + pub row_gap: f32, + pub column_gap: f32, } #[derive(Debug, Copy, Clone, PartialEq)] pub enum AlignSelf { + Auto, Start, End, Center, @@ -201,10 +272,33 @@ pub enum AlignSelf { impl AlignSelf { pub fn from_u8(value: u8) -> Option { match value { - 0 => Some(Self::Start), - 1 => Some(Self::End), - 2 => Some(Self::Center), - 3 => Some(Self::Stretch), + 0 => Some(Self::Auto), + 1 => Some(Self::Start), + 2 => Some(Self::End), + 3 => Some(Self::Center), + 4 => Some(Self::Stretch), + _ => None, + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum JustifySelf { + Auto, + Start, + End, + Center, + Stretch, +} + +impl JustifySelf { + pub fn from_u8(value: u8) -> Option { + match value { + 0 => Some(Self::Auto), + 1 => Some(Self::Start), + 2 => Some(Self::End), + 3 => Some(Self::Center), + 4 => Some(Self::Stretch), _ => None, } } @@ -212,11 +306,26 @@ impl AlignSelf { #[derive(Debug, Clone, PartialEq)] pub struct FlexData { - pub row_gap: f32, - pub column_gap: f32, + pub direction: FlexDirection, pub wrap_type: WrapType, } +impl FlexData { + pub fn is_reverse(&self) -> bool { + match &self.direction { + FlexDirection::RowReverse | FlexDirection::ColumnReverse => true, + _ => false, + } + } + + pub fn is_row(&self) -> bool { + match &self.direction { + FlexDirection::RowReverse | FlexDirection::Row => true, + _ => false, + } + } +} + impl FlexData { pub fn is_wrap(&self) -> bool { match self.wrap_type { @@ -228,9 +337,78 @@ impl FlexData { #[derive(Debug, Clone, PartialEq)] pub struct GridData { + pub direction: GridDirection, pub rows: Vec, pub columns: Vec, - // layout-grid-cells ;; map of id->grid-cell + pub cells: Vec, +} + +impl GridData { + pub fn default() -> Self { + Self { + direction: GridDirection::Row, + rows: vec![], + columns: vec![], + cells: vec![], + } + } +} + +#[derive(Debug)] +#[repr(C)] +pub struct RawGridTrack { + track_type: u8, + value: [u8; 4], +} + +impl RawGridTrack { + pub fn from_bytes(bytes: [u8; 5]) -> Self { + Self { + track_type: bytes[0], + value: [bytes[1], bytes[2], bytes[3], bytes[4]], + } + } +} + +#[derive(Debug)] +#[repr(C)] +pub struct RawGridCell { + row: [u8; 4], + row_span: [u8; 4], + column: [u8; 4], + column_span: [u8; 4], + has_align_self: u8, + align_self: u8, + has_justify_self: u8, + justify_self: u8, + has_shape_id: u8, + shape_id_a: [u8; 4], + shape_id_b: [u8; 4], + shape_id_c: [u8; 4], + shape_id_d: [u8; 4], +} + +impl RawGridCell { + pub fn from_bytes(bytes: [u8; 37]) -> Self { + Self { + row: [bytes[0], bytes[1], bytes[2], bytes[3]], + row_span: [bytes[4], bytes[5], bytes[6], bytes[7]], + column: [bytes[8], bytes[9], bytes[10], bytes[11]], + column_span: [bytes[12], bytes[13], bytes[14], bytes[15]], + + has_align_self: bytes[16], + align_self: bytes[17], + + has_justify_self: bytes[18], + justify_self: bytes[19], + + has_shape_id: bytes[20], + shape_id_a: [bytes[21], bytes[22], bytes[23], bytes[24]], + shape_id_b: [bytes[25], bytes[26], bytes[27], bytes[28]], + shape_id_c: [bytes[29], bytes[30], bytes[31], bytes[32]], + shape_id_d: [bytes[33], bytes[34], bytes[35], bytes[36]], + } + } } #[derive(Debug, Clone, PartialEq, Copy)] diff --git a/render-wasm/src/shapes/modifiers.rs b/render-wasm/src/shapes/modifiers.rs index d4c626d34..a5e727851 100644 --- a/render-wasm/src/shapes/modifiers.rs +++ b/render-wasm/src/shapes/modifiers.rs @@ -159,6 +159,8 @@ pub fn propagate_modifiers(state: &State, modifiers: Vec) -> Vec continue; }; + let mut reflow_parent = false; + match &shape.shape_type { Type::Frame(Frame { layout: Some(_), .. @@ -173,10 +175,17 @@ pub fn propagate_modifiers(state: &State, modifiers: Vec) -> Vec // If this is a fill layout but the parent has not been reflown yet // we wait for the next iteration for reflow skip_reflow = true; + reflow_parent = true; } } } + if shape.is_layout_vertical_auto() + || shape.is_layout_horizontal_auto() + { + reflow_parent = true; + } + if !skip_reflow { layout_reflows.push(id); } @@ -186,6 +195,7 @@ pub fn propagate_modifiers(state: &State, modifiers: Vec) -> Vec if let Some(child) = shapes.get(&shape.children[0]) { let child_bounds = bounds.find(&child); bounds.insert(shape.id, child_bounds); + reflow_parent = true; } } Type::Group(_) => { @@ -193,6 +203,7 @@ pub fn propagate_modifiers(state: &State, modifiers: Vec) -> Vec calculate_group_bounds(shape, shapes, &bounds) { bounds.insert(shape.id, shape_bounds); + reflow_parent = true; } } Type::Bool(_) => { @@ -203,6 +214,7 @@ pub fn propagate_modifiers(state: &State, modifiers: Vec) -> Vec calculate_group_bounds(shape, shapes, &bounds) { bounds.insert(shape.id, shape_bounds); + reflow_parent = true; } } _ => { @@ -211,7 +223,7 @@ pub fn propagate_modifiers(state: &State, modifiers: Vec) -> Vec } if let Some(parent) = shape.parent_id.and_then(|id| shapes.get(&id)) { - if parent.has_layout() || parent.is_group_like() { + if reflow_parent && (parent.has_layout() || parent.is_group_like()) { entries.push_back(Modifier::reflow(parent.id)); } } @@ -231,6 +243,7 @@ pub fn propagate_modifiers(state: &State, modifiers: Vec) -> Vec let Type::Frame(frame_data) = &shape.shape_type else { continue; }; + if let Some(Layout::FlexLayout(layout_data, flex_data)) = &frame_data.layout { let mut children = flex_layout::reflow_flex_layout( shape, @@ -242,11 +255,11 @@ pub fn propagate_modifiers(state: &State, modifiers: Vec) -> Vec entries.append(&mut children); } - if let Some(Layout::GridLayout(layout_data, grid_data)) = &frame_data.layout { - let mut children = - grid_layout::reflow_grid_layout(shape, layout_data, grid_data, shapes, &bounds); - entries.append(&mut children); - } + // if let Some(Layout::GridLayout(layout_data, grid_data)) = &frame_data.layout { + // let mut children = + // grid_layout::reflow_grid_layout(shape, layout_data, grid_data, shapes, &bounds); + // entries.append(&mut children); + // } reflown.insert(*id); } layout_reflows = Vec::new(); diff --git a/render-wasm/src/shapes/modifiers/flex_layout.rs b/render-wasm/src/shapes/modifiers/flex_layout.rs index fd3f1e9cf..d0b47b429 100644 --- a/render-wasm/src/shapes/modifiers/flex_layout.rs +++ b/render-wasm/src/shapes/modifiers/flex_layout.rs @@ -67,7 +67,7 @@ impl LayoutAxis { layout_data: &LayoutData, flex_data: &FlexData, ) -> Self { - if layout_data.is_row() { + if flex_data.is_row() { Self { main_size: layout_bounds.width(), across_size: layout_bounds.height(), @@ -77,8 +77,8 @@ impl LayoutAxis { padding_main_end: layout_data.padding_right, padding_across_start: layout_data.padding_top, padding_across_end: layout_data.padding_bottom, - gap_main: flex_data.column_gap, - gap_across: flex_data.row_gap, + gap_main: layout_data.column_gap, + gap_across: layout_data.row_gap, is_auto_main: shape.is_layout_horizontal_auto(), is_auto_across: shape.is_layout_vertical_auto(), } @@ -92,8 +92,8 @@ impl LayoutAxis { padding_main_end: layout_data.padding_bottom, padding_across_start: layout_data.padding_left, padding_across_end: layout_data.padding_right, - gap_main: flex_data.row_gap, - gap_across: flex_data.column_gap, + gap_main: layout_data.row_gap, + gap_across: layout_data.column_gap, is_auto_main: shape.is_layout_vertical_auto(), is_auto_across: shape.is_layout_horizontal_auto(), } @@ -121,10 +121,10 @@ struct ChildAxis { } impl ChildAxis { - fn new(child: &Shape, child_bounds: &Bounds, layout_data: &LayoutData) -> Self { + fn new(child: &Shape, child_bounds: &Bounds, flex_data: &FlexData) -> Self { let id = child.id; let layout_item = child.layout_item; - let mut result = if layout_data.is_row() { + let mut result = if flex_data.is_row() { Self { id, main_size: child_bounds.width(), @@ -176,7 +176,6 @@ fn initialize_tracks( shape: &Shape, layout_bounds: &Bounds, layout_axis: &LayoutAxis, - layout_data: &LayoutData, flex_data: &FlexData, shapes: &HashMap, bounds: &HashMap, @@ -186,7 +185,7 @@ fn initialize_tracks( let mut children = shape.children.clone(); let mut first = true; - if !layout_data.is_reverse() { + if !flex_data.is_reverse() { children.reverse(); } @@ -204,7 +203,7 @@ fn initialize_tracks( .box_bounds(&default_bounds) .unwrap_or(default_bounds); - let child_axis = ChildAxis::new(child, &child_bounds, layout_data); + let child_axis = ChildAxis::new(child, &child_bounds, flex_data); let child_main_size = child_axis.margin_main_start + child_axis.margin_main_end @@ -466,7 +465,6 @@ fn calculate_track_data( shape, layout_bounds, &layout_axis, - layout_data, flex_data, shapes, bounds, @@ -617,7 +615,7 @@ pub fn reflow_flex_layout( let child_bounds = &child_axis.bounds; let delta_v = Vector::new_points(&child_bounds.nw, &position); - let (new_width, new_height) = if layout_data.is_row() { + let (new_width, new_height) = if flex_data.is_row() { (child_axis.main_size, child_axis.across_size) } else { (child_axis.across_size, child_axis.main_size) @@ -681,7 +679,7 @@ pub fn reflow_flex_layout( 0.0 }; - let (scale_width, scale_height) = if layout_data.is_row() { + let (scale_width, scale_height) = if flex_data.is_row() { ( if layout_axis.is_auto_main { auto_main_size / width diff --git a/render-wasm/src/shapes/modifiers/grid_layout.rs b/render-wasm/src/shapes/modifiers/grid_layout.rs index 26714cbb0..3659ed7f2 100644 --- a/render-wasm/src/shapes/modifiers/grid_layout.rs +++ b/render-wasm/src/shapes/modifiers/grid_layout.rs @@ -1,15 +1,59 @@ -use crate::math::Bounds; +#![allow(dead_code, unused_variables)] +use crate::math::{Bounds, Matrix, Point, Vector, VectorExt}; use crate::shapes::{GridData, LayoutData, Modifier, Shape}; use std::collections::{HashMap, VecDeque}; use uuid::Uuid; -pub fn reflow_grid_layout( - _shape: &Shape, - _layout_data: &LayoutData, - _grid_data: &GridData, - _shapes: &HashMap, - _bounds: &HashMap, -) -> VecDeque { - // TODO - VecDeque::new() +use super::common::GetBounds; + +const MIN_SIZE: f32 = 0.01; +const MAX_SIZE: f32 = f32::INFINITY; + +struct CellData<'a> { + shape: &'a Shape, + main_size: f32, + across_size: f32, +} + +fn calculate_cell_data<'a>( + shape: &Shape, + layout_data: &LayoutData, + grid_data: &GridData, + shapes: &'a HashMap, + bounds: &HashMap, +) -> Vec> { + todo!() +} + +fn child_position(child_bounds: &Bounds, cell: &CellData) -> Point { + todo!() +} + +pub fn reflow_grid_layout<'a>( + shape: &Shape, + layout_data: &LayoutData, + grid_data: &GridData, + shapes: &'a HashMap, + bounds: &HashMap, +) -> VecDeque { + let mut result = VecDeque::new(); + + let cells = calculate_cell_data(shape, layout_data, grid_data, shapes, bounds); + + for cell in cells.iter() { + let child = cell.shape; + let child_bounds = bounds.find(child); + let position = child_position(&child_bounds, cell); + + let mut transform = Matrix::default(); + let delta_v = Vector::new_points(&child_bounds.nw, &position); + + if delta_v.x.abs() > MIN_SIZE || delta_v.y.abs() > MIN_SIZE { + transform.post_concat(&Matrix::translate(delta_v)); + } + + result.push_back(Modifier::transform(child.id, transform)); + } + + result }