mirror of
https://github.com/penpot/penpot.git
synced 2026-09-10 22:19:19 +00:00
Merge pull request #7648 from penpot/alotor-performance-improvements
✨ Add performance improvements for wasm render
This commit is contained in:
commit
aa15232cc7
@ -517,8 +517,7 @@
|
|||||||
(when verify?
|
(when verify?
|
||||||
(check-changes items))
|
(check-changes items))
|
||||||
|
|
||||||
(binding [*touched-changes* (volatile! #{})
|
(binding [*touched-changes* (volatile! #{})]
|
||||||
cts/*wasm-sync* (not cts/*wasm-sync-override*)]
|
|
||||||
(let [result (reduce #(or (process-change %1 %2) %1) data items)
|
(let [result (reduce #(or (process-change %1 %2) %1) data items)
|
||||||
result (reduce process-touched-change result @*touched-changes*)]
|
result (reduce process-touched-change result @*touched-changes*)]
|
||||||
;; Validate result shapes (only on the backend)
|
;; Validate result shapes (only on the backend)
|
||||||
|
|||||||
@ -36,12 +36,7 @@
|
|||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
[clojure.set :as set]))
|
[clojure.set :as set]))
|
||||||
|
|
||||||
(defonce ^:dynamic *wasm-sync* false)
|
(defonce ^:dynamic *shape-changes* nil)
|
||||||
|
|
||||||
;; This is a temporary workaround so the changes-builder doesn't generate updates
|
|
||||||
;; in the WASM model.
|
|
||||||
(defonce ^:dynamic *wasm-sync-override* false)
|
|
||||||
|
|
||||||
(defonce wasm-enabled? false)
|
(defonce wasm-enabled? false)
|
||||||
(defonce wasm-create-shape (constantly nil))
|
(defonce wasm-create-shape (constantly nil))
|
||||||
|
|
||||||
|
|||||||
@ -7,14 +7,18 @@
|
|||||||
(ns app.main.data.changes
|
(ns app.main.data.changes
|
||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
|
[app.common.data.macros :as dm]
|
||||||
[app.common.files.changes :as cpc]
|
[app.common.files.changes :as cpc]
|
||||||
[app.common.logging :as log]
|
[app.common.logging :as log]
|
||||||
[app.common.time :as ct]
|
[app.common.time :as ct]
|
||||||
|
[app.common.types.shape :as cts]
|
||||||
[app.common.types.shape-tree :as ctst]
|
[app.common.types.shape-tree :as ctst]
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
[app.main.data.event :as ev]
|
[app.main.data.event :as ev]
|
||||||
[app.main.data.helpers :as dsh]
|
[app.main.data.helpers :as dsh]
|
||||||
|
[app.main.features :as features]
|
||||||
[app.main.worker :as mw]
|
[app.main.worker :as mw]
|
||||||
|
[app.render-wasm.shape :as wasm.shape]
|
||||||
[beicon.v2.core :as rx]
|
[beicon.v2.core :as rx]
|
||||||
[potok.v2.core :as ptk]))
|
[potok.v2.core :as ptk]))
|
||||||
|
|
||||||
@ -99,7 +103,21 @@
|
|||||||
pids (into #{} xf:map-page-id redo-changes)]
|
pids (into #{} xf:map-page-id redo-changes)]
|
||||||
(reduce #(ctst/update-object-indices %1 %2) fdata pids)))]
|
(reduce #(ctst/update-object-indices %1 %2) fdata pids)))]
|
||||||
|
|
||||||
(update-in state [:files file-id :data] apply-changes)))))
|
(if (features/active-feature? state "render-wasm/v1")
|
||||||
|
;; Update the wasm model
|
||||||
|
(let [shape-changes (volatile! {})
|
||||||
|
|
||||||
|
state
|
||||||
|
(binding [cts/*shape-changes* shape-changes]
|
||||||
|
(update-in state [:files file-id :data] apply-changes))]
|
||||||
|
|
||||||
|
(let [objects (dm/get-in state [:files file-id :data :pages-index (:current-page-id state) :objects])]
|
||||||
|
(wasm.shape/process-shape-changes! objects @shape-changes))
|
||||||
|
|
||||||
|
state)
|
||||||
|
|
||||||
|
;; wasm renderer deactivated
|
||||||
|
(update-in state [:files file-id :data] apply-changes))))))
|
||||||
|
|
||||||
(defn commit
|
(defn commit
|
||||||
"Create a commit event instance"
|
"Create a commit event instance"
|
||||||
|
|||||||
@ -19,7 +19,7 @@
|
|||||||
[app.common.types.component :as ctk]
|
[app.common.types.component :as ctk]
|
||||||
[app.common.types.container :as ctn]
|
[app.common.types.container :as ctn]
|
||||||
[app.common.types.modifiers :as ctm]
|
[app.common.types.modifiers :as ctm]
|
||||||
[app.common.types.shape :as shape]
|
[app.common.types.path :as path]
|
||||||
[app.common.types.shape-tree :as ctst]
|
[app.common.types.shape-tree :as ctst]
|
||||||
[app.common.types.shape.attrs :refer [editable-attrs]]
|
[app.common.types.shape.attrs :refer [editable-attrs]]
|
||||||
[app.common.types.shape.layout :as ctl]
|
[app.common.types.shape.layout :as ctl]
|
||||||
@ -203,21 +203,26 @@
|
|||||||
wasm-props
|
wasm-props
|
||||||
(concat clean-props wasm-props)
|
(concat clean-props wasm-props)
|
||||||
|
|
||||||
wasm-props
|
;; Stores a map shape -> set of properties changed
|
||||||
|
;; this is the standard format used by process-shape-changes
|
||||||
|
shape-changes
|
||||||
(-> (group-by first wasm-props)
|
(-> (group-by first wasm-props)
|
||||||
(update-vals #(map second %)))]
|
(update-vals #(into #{} (map (comp :property second)) %)))
|
||||||
|
|
||||||
;; Props are grouped by id and then assoc to the shape the new value
|
;; Create a new objects only with the temporary modifications
|
||||||
(doseq [[id properties] wasm-props]
|
objects-changed
|
||||||
(let [shape
|
(->> wasm-props
|
||||||
(->> properties
|
(reduce
|
||||||
(reduce
|
(fn [objects [id properties]]
|
||||||
(fn [shape {:keys [property value]}]
|
(let [shape
|
||||||
(assoc shape property value))
|
(->> properties
|
||||||
(get objects id)))]
|
(reduce
|
||||||
|
(fn [shape {:keys [property value]}]
|
||||||
;; With the new values to the shape change multi props
|
(assoc shape property value))
|
||||||
(wasm.shape/set-wasm-multi-attrs! shape (->> properties (map :property)))))))
|
(get objects id)))]
|
||||||
|
(assoc objects id shape)))
|
||||||
|
objects))]
|
||||||
|
(wasm.shape/process-shape-changes! objects-changed shape-changes)))
|
||||||
|
|
||||||
(defn clear-local-transform []
|
(defn clear-local-transform []
|
||||||
(ptk/reify ::clear-local-transform
|
(ptk/reify ::clear-local-transform
|
||||||
@ -616,17 +621,20 @@
|
|||||||
|
|
||||||
#_:clj-kondo/ignore
|
#_:clj-kondo/ignore
|
||||||
(defn apply-wasm-modifiers
|
(defn apply-wasm-modifiers
|
||||||
[modif-tree & {:keys [ignore-constraints ignore-snap-pixel snap-ignore-axis undo-group]
|
[modif-tree & {:keys [ignore-constraints ignore-snap-pixel snap-ignore-axis undo-transation?]
|
||||||
:or {ignore-constraints false ignore-snap-pixel false snap-ignore-axis nil undo-group nil}
|
:or {ignore-constraints false ignore-snap-pixel false snap-ignore-axis nil undo-transation? true}
|
||||||
:as params}]
|
:as params}]
|
||||||
(ptk/reify ::apply-wasm-modifiesr
|
(ptk/reify ::apply-wasm-modifiesr
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state _]
|
(watch [_ state _]
|
||||||
|
(wasm.api/clean-modifiers)
|
||||||
|
(let [structure-entries (parse-structure-modifiers modif-tree)]
|
||||||
|
(wasm.api/set-structure-modifiers structure-entries))
|
||||||
|
|
||||||
(let [objects (dsh/lookup-page-objects state)
|
(let [objects (dsh/lookup-page-objects state)
|
||||||
|
|
||||||
ignore-tree
|
ignore-tree
|
||||||
(binding [shape/*wasm-sync* false]
|
(calculate-ignore-tree modif-tree objects)
|
||||||
(calculate-ignore-tree modif-tree objects))
|
|
||||||
|
|
||||||
options
|
options
|
||||||
(-> params
|
(-> params
|
||||||
@ -658,12 +666,34 @@
|
|||||||
modifiers (dm/get-in modif-tree [shape-id :modifiers])]
|
modifiers (dm/get-in modif-tree [shape-id :modifiers])]
|
||||||
(-> shape
|
(-> shape
|
||||||
(gsh/apply-transform transform)
|
(gsh/apply-transform transform)
|
||||||
(ctm/apply-structure-modifiers modifiers))))]
|
(ctm/apply-structure-modifiers modifiers))))
|
||||||
(rx/of
|
|
||||||
(clear-local-transform)
|
bool-ids
|
||||||
(ptk/event ::dwg/move-frame-guides {:ids ids :transforms transforms})
|
(into #{}
|
||||||
(ptk/event ::dwcm/move-frame-comment-threads transforms)
|
(comp
|
||||||
(dwsh/update-shapes ids update-shape options))))))
|
(mapcat (partial cfh/get-parents-with-self objects))
|
||||||
|
(filter cfh/bool-shape?)
|
||||||
|
(map :id))
|
||||||
|
ids)
|
||||||
|
|
||||||
|
undo-id (js/Symbol)]
|
||||||
|
(rx/concat
|
||||||
|
(if undo-transation?
|
||||||
|
(rx/of (dwu/start-undo-transaction undo-id))
|
||||||
|
(rx/empty))
|
||||||
|
(rx/of
|
||||||
|
(clear-local-transform)
|
||||||
|
(ptk/event ::dwg/move-frame-guides {:ids ids :transforms transforms})
|
||||||
|
(ptk/event ::dwcm/move-frame-comment-threads transforms)
|
||||||
|
(dwsh/update-shapes ids update-shape options)
|
||||||
|
|
||||||
|
;; The update to the bool path needs to be in a different operation because it
|
||||||
|
;; needs to have the updated children info
|
||||||
|
(dwsh/update-shapes bool-ids path/update-bool-shape (assoc options :with-objects? true)))
|
||||||
|
|
||||||
|
(if undo-transation?
|
||||||
|
(rx/of (dwu/commit-undo-transaction undo-id))
|
||||||
|
(rx/empty)))))))
|
||||||
|
|
||||||
(def ^:private
|
(def ^:private
|
||||||
xf-rotation-shape
|
xf-rotation-shape
|
||||||
|
|||||||
@ -78,20 +78,19 @@
|
|||||||
(not-empty))
|
(not-empty))
|
||||||
|
|
||||||
changes
|
changes
|
||||||
(binding [cts/*wasm-sync-override* true]
|
(-> (pcb/empty-changes it page-id)
|
||||||
(-> (pcb/empty-changes it page-id)
|
(pcb/set-save-undo? save-undo?)
|
||||||
(pcb/set-save-undo? save-undo?)
|
(pcb/set-stack-undo? stack-undo?)
|
||||||
(pcb/set-stack-undo? stack-undo?)
|
(cls/generate-update-shapes ids
|
||||||
(cls/generate-update-shapes ids
|
update-fn
|
||||||
update-fn
|
objects
|
||||||
objects
|
{:attrs attrs
|
||||||
{:attrs attrs
|
:changed-sub-attr changed-sub-attr
|
||||||
:changed-sub-attr changed-sub-attr
|
:ignore-tree ignore-tree
|
||||||
:ignore-tree ignore-tree
|
:ignore-touched ignore-touched
|
||||||
:ignore-touched ignore-touched
|
:with-objects? with-objects?})
|
||||||
:with-objects? with-objects?})
|
(cond-> undo-group
|
||||||
(cond-> undo-group
|
(pcb/set-undo-group undo-group)))
|
||||||
(pcb/set-undo-group undo-group))))
|
|
||||||
|
|
||||||
changes
|
changes
|
||||||
(add-undo-group changes state)]
|
(add-undo-group changes state)]
|
||||||
|
|||||||
@ -146,7 +146,7 @@
|
|||||||
(defn start-resize
|
(defn start-resize
|
||||||
"Enter mouse resize mode, until mouse button is released."
|
"Enter mouse resize mode, until mouse button is released."
|
||||||
[handler ids shape]
|
[handler ids shape]
|
||||||
(letfn [(resize [shape initial layout [point lock? center? point-snap]]
|
(letfn [(resize [shape initial layout objects [point lock? center? point-snap]]
|
||||||
(let [selrect (dm/get-prop shape :selrect)
|
(let [selrect (dm/get-prop shape :selrect)
|
||||||
width (dm/get-prop selrect :width)
|
width (dm/get-prop selrect :width)
|
||||||
height (dm/get-prop selrect :height)
|
height (dm/get-prop selrect :height)
|
||||||
@ -238,10 +238,14 @@
|
|||||||
:always
|
:always
|
||||||
(ctm/resize scalev resize-origin shape-transform shape-transform-inverse)
|
(ctm/resize scalev resize-origin shape-transform shape-transform-inverse)
|
||||||
|
|
||||||
^boolean change-width?
|
(and (ctl/any-layout-immediate-child? objects shape)
|
||||||
|
(not= (:layout-item-h-sizing shape) :fix)
|
||||||
|
^boolean change-width?)
|
||||||
(ctm/change-property :layout-item-h-sizing :fix)
|
(ctm/change-property :layout-item-h-sizing :fix)
|
||||||
|
|
||||||
^boolean change-height?
|
(and (ctl/any-layout-immediate-child? objects shape)
|
||||||
|
(not= (:layout-item-v-sizing shape) :fix)
|
||||||
|
^boolean change-height?)
|
||||||
(ctm/change-property :layout-item-v-sizing :fix)
|
(ctm/change-property :layout-item-v-sizing :fix)
|
||||||
|
|
||||||
;; Set grow-type if it should change
|
;; Set grow-type if it should change
|
||||||
@ -293,7 +297,7 @@
|
|||||||
(fn [[point _ _ :as current]]
|
(fn [[point _ _ :as current]]
|
||||||
(->> (snap/closest-snap-point page-id shapes objects layout zoom focus point)
|
(->> (snap/closest-snap-point page-id shapes objects layout zoom focus point)
|
||||||
(rx/map #(conj current %)))))
|
(rx/map #(conj current %)))))
|
||||||
(rx/map #(resize shape initial-position layout %))
|
(rx/map #(resize shape initial-position layout objects %))
|
||||||
(rx/share))
|
(rx/share))
|
||||||
|
|
||||||
modifiers-stream
|
modifiers-stream
|
||||||
@ -309,8 +313,8 @@
|
|||||||
:ignore-constraints (contains? layout :scale-text))))))
|
:ignore-constraints (contains? layout :scale-text))))))
|
||||||
(rx/take-until stopper))
|
(rx/take-until stopper))
|
||||||
|
|
||||||
;; The last event we need to use the old method so the elements are correctly positioned until
|
;; The last event we need to use the old method so the elements are correctly
|
||||||
;; all the logic is implemented in wasm
|
;; positioned until all the logic is implemented in wasm
|
||||||
(->> resize-events-stream
|
(->> resize-events-stream
|
||||||
(rx/take-until stopper)
|
(rx/take-until stopper)
|
||||||
(rx/last)
|
(rx/last)
|
||||||
@ -327,8 +331,8 @@
|
|||||||
(rx/take-until stopper)))]
|
(rx/take-until stopper)))]
|
||||||
|
|
||||||
(rx/concat
|
(rx/concat
|
||||||
;; This initial stream waits for some pixels to be move before making the resize
|
;; This initial stream waits for some pixels to be move before making the resize
|
||||||
;; if you make a click in the border will not make a resize
|
;; if you make a click in the border will not make a resize
|
||||||
(->> ms/mouse-position
|
(->> ms/mouse-position
|
||||||
(rx/map #(gpt/to-vec initial-position %))
|
(rx/map #(gpt/to-vec initial-position %))
|
||||||
(rx/map #(gpt/length %))
|
(rx/map #(gpt/length %))
|
||||||
@ -752,12 +756,6 @@
|
|||||||
(fn [[modifiers snap-ignore-axis]]
|
(fn [[modifiers snap-ignore-axis]]
|
||||||
(dwm/set-wasm-modifiers modifiers :snap-ignore-axis snap-ignore-axis))))
|
(dwm/set-wasm-modifiers modifiers :snap-ignore-axis snap-ignore-axis))))
|
||||||
|
|
||||||
(->> modifiers-stream
|
|
||||||
(rx/last)
|
|
||||||
(rx/map
|
|
||||||
(fn [[modifiers snap-ignore-axis]]
|
|
||||||
(dwm/apply-wasm-modifiers modifiers :snap-ignore-axis snap-ignore-axis))))
|
|
||||||
|
|
||||||
(->> move-stream
|
(->> move-stream
|
||||||
(rx/with-latest-from ms/mouse-position-alt)
|
(rx/with-latest-from ms/mouse-position-alt)
|
||||||
(rx/filter (fn [[_ alt?]] alt?))
|
(rx/filter (fn [[_ alt?]] alt?))
|
||||||
@ -772,14 +770,18 @@
|
|||||||
;; Last event will write the modifiers creating the changes
|
;; Last event will write the modifiers creating the changes
|
||||||
(->> move-stream
|
(->> move-stream
|
||||||
(rx/last)
|
(rx/last)
|
||||||
|
(rx/with-latest-from modifiers-stream)
|
||||||
(rx/mapcat
|
(rx/mapcat
|
||||||
(fn [[_ target-frame drop-index drop-cell]]
|
(fn [[[_ target-frame drop-index drop-cell] [modifiers snap-ignore-axis]]]
|
||||||
(let [undo-id (js/Symbol)]
|
(let [undo-id (js/Symbol)]
|
||||||
(rx/of (dwu/start-undo-transaction undo-id)
|
(rx/of
|
||||||
;; (dwm/apply-modifiers {:undo-transation? false})
|
(dwu/start-undo-transaction undo-id)
|
||||||
(move-shapes-to-frame ids target-frame drop-index drop-cell)
|
(dwm/apply-wasm-modifiers modifiers
|
||||||
(finish-transform)
|
:snap-ignore-axis snap-ignore-axis
|
||||||
(dwu/commit-undo-transaction undo-id)))))))
|
:undo-transation? false)
|
||||||
|
(move-shapes-to-frame ids target-frame drop-index drop-cell)
|
||||||
|
(finish-transform)
|
||||||
|
(dwu/commit-undo-transaction undo-id)))))))
|
||||||
|
|
||||||
(rx/merge
|
(rx/merge
|
||||||
(->> modifiers-stream
|
(->> modifiers-stream
|
||||||
|
|||||||
@ -12,7 +12,6 @@
|
|||||||
[app.common.geom.rect :as grc]
|
[app.common.geom.rect :as grc]
|
||||||
[app.common.geom.shapes :as gsh]
|
[app.common.geom.shapes :as gsh]
|
||||||
[app.common.logic.shapes :as cls]
|
[app.common.logic.shapes :as cls]
|
||||||
[app.common.types.shape :as cts]
|
|
||||||
[app.common.types.shape.layout :as ctl]
|
[app.common.types.shape.layout :as ctl]
|
||||||
[app.common.types.token :as tk]
|
[app.common.types.token :as tk]
|
||||||
[app.main.constants :refer [size-presets]]
|
[app.main.constants :refer [size-presets]]
|
||||||
@ -295,9 +294,8 @@
|
|||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(mf/deps ids)
|
(mf/deps ids)
|
||||||
(fn [value attr]
|
(fn [value attr]
|
||||||
(binding [cts/*wasm-sync* true]
|
(st/emit! (udw/trigger-bounding-box-cloaking ids)
|
||||||
(st/emit! (udw/trigger-bounding-box-cloaking ids)
|
(udw/update-dimensions ids attr value))))
|
||||||
(udw/update-dimensions ids attr value)))))
|
|
||||||
|
|
||||||
on-size-change
|
on-size-change
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
@ -306,16 +304,14 @@
|
|||||||
(if (or (string? value) (int? value))
|
(if (or (string? value) (int? value))
|
||||||
(do
|
(do
|
||||||
(st/emit! (udw/trigger-bounding-box-cloaking ids))
|
(st/emit! (udw/trigger-bounding-box-cloaking ids))
|
||||||
(binding [cts/*wasm-sync* true]
|
(run! #(do-size-change value attr) shapes))
|
||||||
(run! #(do-size-change value attr) shapes)))
|
|
||||||
(do
|
(do
|
||||||
(let [resolved-value (:resolved-value (first value))]
|
(let [resolved-value (:resolved-value (first value))]
|
||||||
(st/emit! (udw/trigger-bounding-box-cloaking ids)
|
(st/emit! (udw/trigger-bounding-box-cloaking ids)
|
||||||
(dwta/toggle-token {:token (first value)
|
(dwta/toggle-token {:token (first value)
|
||||||
:attrs #{attr}
|
:attrs #{attr}
|
||||||
:shape-ids ids}))
|
:shape-ids ids}))
|
||||||
(binding [cts/*wasm-sync* true]
|
(run! #(do-size-change resolved-value attr) shapes))))))
|
||||||
(run! #(do-size-change resolved-value attr) shapes)))))))
|
|
||||||
|
|
||||||
on-proportion-lock-change
|
on-proportion-lock-change
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
@ -337,16 +333,14 @@
|
|||||||
(if (or (string? value) (int? value))
|
(if (or (string? value) (int? value))
|
||||||
(do
|
(do
|
||||||
(st/emit! (udw/trigger-bounding-box-cloaking ids))
|
(st/emit! (udw/trigger-bounding-box-cloaking ids))
|
||||||
(binding [cts/*wasm-sync* true]
|
(run! #(do-position-change %1 value attr) shapes))
|
||||||
(run! #(do-position-change %1 value attr) shapes)))
|
|
||||||
(do
|
(do
|
||||||
(let [resolved-value (:resolved-value (first value))]
|
(let [resolved-value (:resolved-value (first value))]
|
||||||
(st/emit! (udw/trigger-bounding-box-cloaking ids)
|
(st/emit! (udw/trigger-bounding-box-cloaking ids)
|
||||||
(dwta/toggle-token {:token (first value)
|
(dwta/toggle-token {:token (first value)
|
||||||
:attrs #{attr}
|
:attrs #{attr}
|
||||||
:shape-ids ids}))
|
:shape-ids ids}))
|
||||||
(binding [cts/*wasm-sync* true]
|
(run! #(do-position-change %1 resolved-value attr) shapes))))))
|
||||||
(run! #(do-position-change %1 resolved-value attr) shapes)))))))
|
|
||||||
|
|
||||||
;; ROTATION
|
;; ROTATION
|
||||||
do-rotation-change
|
do-rotation-change
|
||||||
@ -362,16 +356,14 @@
|
|||||||
(if (or (string? value) (int? value))
|
(if (or (string? value) (int? value))
|
||||||
(do
|
(do
|
||||||
(st/emit! (udw/trigger-bounding-box-cloaking ids))
|
(st/emit! (udw/trigger-bounding-box-cloaking ids))
|
||||||
(binding [cts/*wasm-sync* true]
|
(run! #(do-rotation-change value) shapes))
|
||||||
(run! #(do-rotation-change value) shapes)))
|
|
||||||
(do
|
(do
|
||||||
(let [resolved-value (:resolved-value (first value))]
|
(let [resolved-value (:resolved-value (first value))]
|
||||||
(st/emit! (udw/trigger-bounding-box-cloaking ids)
|
(st/emit! (udw/trigger-bounding-box-cloaking ids)
|
||||||
(dwta/toggle-token {:token (first value)
|
(dwta/toggle-token {:token (first value)
|
||||||
:attrs #{:rotation}
|
:attrs #{:rotation}
|
||||||
:shape-ids ids}))
|
:shape-ids ids}))
|
||||||
(binding [cts/*wasm-sync* true]
|
(run! #(do-rotation-change resolved-value) shapes))))))
|
||||||
(run! #(do-rotation-change resolved-value) shapes)))))))
|
|
||||||
|
|
||||||
on-width-change
|
on-width-change
|
||||||
(mf/use-fn (mf/deps on-size-change) #(on-size-change % :width))
|
(mf/use-fn (mf/deps on-size-change) #(on-size-change % :width))
|
||||||
|
|||||||
@ -113,8 +113,7 @@
|
|||||||
objects-modified
|
objects-modified
|
||||||
(mf/with-memo
|
(mf/with-memo
|
||||||
[base-objects wasm-modifiers]
|
[base-objects wasm-modifiers]
|
||||||
(binding [cts/*wasm-sync* false]
|
(apply-modifiers-to-selected selected base-objects wasm-modifiers))
|
||||||
(apply-modifiers-to-selected selected base-objects wasm-modifiers)))
|
|
||||||
|
|
||||||
selected-shapes (->> selected
|
selected-shapes (->> selected
|
||||||
(into [] (keep (d/getf objects-modified)))
|
(into [] (keep (d/getf objects-modified)))
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
"A WASM based render API"
|
"A WASM based render API"
|
||||||
(:require
|
(:require
|
||||||
["react-dom/server" :as rds]
|
["react-dom/server" :as rds]
|
||||||
[app.common.data :as d :refer [not-empty?]]
|
[app.common.data :as d]
|
||||||
[app.common.data.macros :as dm]
|
[app.common.data.macros :as dm]
|
||||||
[app.common.math :as mth]
|
[app.common.math :as mth]
|
||||||
[app.common.types.fills :as types.fills]
|
[app.common.types.fills :as types.fills]
|
||||||
@ -850,20 +850,14 @@
|
|||||||
[pending]
|
[pending]
|
||||||
(let [event (js/CustomEvent. "wasm:set-objects-finished")
|
(let [event (js/CustomEvent. "wasm:set-objects-finished")
|
||||||
pending (-> (d/index-by :key :callback pending) vals)]
|
pending (-> (d/index-by :key :callback pending) vals)]
|
||||||
(if (not-empty? pending)
|
(->> (rx/from pending)
|
||||||
(->> (rx/from pending)
|
(rx/merge-map (fn [callback] (callback)))
|
||||||
(rx/merge-map (fn [callback] (callback)))
|
(rx/reduce conj [])
|
||||||
(rx/tap (fn [_] (request-render "set-objects")))
|
(rx/subs! (fn [_]
|
||||||
(rx/reduce conj [])
|
(clear-drawing-cache)
|
||||||
(rx/subs! (fn [_]
|
(request-render "pending-finished")
|
||||||
(clear-drawing-cache)
|
(h/call wasm/internal-module "_update_shape_text_layout_for_all")
|
||||||
(request-render "pending-finished")
|
(.dispatchEvent ^js js/document event))))))
|
||||||
(h/call wasm/internal-module "_update_shape_text_layout_for_all")
|
|
||||||
(.dispatchEvent ^js js/document event))))
|
|
||||||
(do
|
|
||||||
(clear-drawing-cache)
|
|
||||||
(request-render "pending-finished")
|
|
||||||
(.dispatchEvent ^js js/document event)))))
|
|
||||||
|
|
||||||
(defn process-object
|
(defn process-object
|
||||||
[shape]
|
[shape]
|
||||||
@ -1116,7 +1110,6 @@
|
|||||||
|
|
||||||
(defn calculate-bool
|
(defn calculate-bool
|
||||||
[bool-type ids]
|
[bool-type ids]
|
||||||
|
|
||||||
(let [size (mem/get-alloc-size ids UUID-U8-SIZE)
|
(let [size (mem/get-alloc-size ids UUID-U8-SIZE)
|
||||||
heap (mem/get-heap-u32)
|
heap (mem/get-heap-u32)
|
||||||
offset (mem/alloc->offset-32 size)]
|
offset (mem/alloc->offset-32 size)]
|
||||||
|
|||||||
@ -120,8 +120,11 @@
|
|||||||
(-write writer (str "#penpot/shape " (:id delegate)))))
|
(-write writer (str "#penpot/shape " (:id delegate)))))
|
||||||
|
|
||||||
;; --- SHAPE IMPL
|
;; --- SHAPE IMPL
|
||||||
|
;; When an attribute is sent to WASM it could still be pending some side operations
|
||||||
(defn- set-wasm-single-attr!
|
;; for example: font loading when changing a text, this is an async operation that will
|
||||||
|
;; resolve eventually.
|
||||||
|
;; The `set-wasm-attr!` can return a list of callbacks to be executed in a second pass.
|
||||||
|
(defn- set-wasm-attr!
|
||||||
[shape k]
|
[shape k]
|
||||||
(let [v (get shape k)
|
(let [v (get shape k)
|
||||||
id (get shape :id)]
|
id (get shape :id)]
|
||||||
@ -132,7 +135,10 @@
|
|||||||
(when (or (= v :path) (= v :bool))
|
(when (or (= v :path) (= v :bool))
|
||||||
(api/set-shape-path-content (:content shape))))
|
(api/set-shape-path-content (:content shape))))
|
||||||
:bool-type (api/set-shape-bool-type v)
|
:bool-type (api/set-shape-bool-type v)
|
||||||
:selrect (api/set-shape-selrect v)
|
:selrect (do
|
||||||
|
(api/set-shape-selrect v)
|
||||||
|
(when (= (:type shape) :svg-raw)
|
||||||
|
(api/set-shape-svg-raw-content (api/get-static-markup shape))))
|
||||||
:show-content (if (= (:type shape) :frame)
|
:show-content (if (= (:type shape) :frame)
|
||||||
(api/set-shape-clip-content (not v))
|
(api/set-shape-clip-content (not v))
|
||||||
(api/set-shape-clip-content false))
|
(api/set-shape-clip-content false))
|
||||||
@ -226,58 +232,40 @@
|
|||||||
(ctl/flex-layout? shape)
|
(ctl/flex-layout? shape)
|
||||||
(api/set-flex-layout shape)))
|
(api/set-flex-layout shape)))
|
||||||
|
|
||||||
|
;; Property not in WASM
|
||||||
nil)))
|
nil)))
|
||||||
|
|
||||||
(defn set-wasm-multi-attrs!
|
(defn process-shape!
|
||||||
[shape properties]
|
[shape properties]
|
||||||
(let [shape-id (dm/get-prop shape :id)]
|
(let [shape-id (dm/get-prop shape :id)]
|
||||||
(when (shape-in-current-page? shape-id)
|
(if (shape-in-current-page? shape-id)
|
||||||
(api/use-shape shape-id)
|
(do
|
||||||
(let [result
|
|
||||||
(->> properties
|
|
||||||
(mapcat #(set-wasm-single-attr! shape %)))
|
|
||||||
pending (-> (d/index-by :key :callback result) vals)]
|
|
||||||
(if (and pending (seq pending))
|
|
||||||
(->> (rx/from pending)
|
|
||||||
(rx/mapcat (fn [callback] (callback)))
|
|
||||||
(rx/reduce conj [])
|
|
||||||
(rx/subs!
|
|
||||||
(fn [_]
|
|
||||||
(api/update-shape-tiles)
|
|
||||||
(api/clear-drawing-cache)
|
|
||||||
(api/request-render "set-wasm-attrs-pending"))))
|
|
||||||
(do
|
|
||||||
(api/update-shape-tiles)
|
|
||||||
(api/request-render "set-wasm-attrs")))))))
|
|
||||||
|
|
||||||
(defn set-wasm-attrs!
|
|
||||||
[shape k v]
|
|
||||||
(let [shape-id (dm/get-prop shape :id)
|
|
||||||
old-value (get shape k)]
|
|
||||||
(when (and (shape-in-current-page? shape-id)
|
|
||||||
(not (identical? old-value v)))
|
|
||||||
(let [shape (assoc shape k v)]
|
|
||||||
(api/use-shape shape-id)
|
(api/use-shape shape-id)
|
||||||
(let [result (set-wasm-single-attr! shape k)
|
(->> properties
|
||||||
pending (-> (d/index-by :key :callback result) vals)]
|
(mapcat #(set-wasm-attr! shape %))
|
||||||
(if (and pending (seq pending))
|
(d/index-by :key :callback)
|
||||||
(->> (rx/from pending)
|
(vals)
|
||||||
(rx/mapcat (fn [callback] (callback)))
|
(rx/from)
|
||||||
(rx/reduce conj [])
|
(rx/mapcat (fn [callback] (callback)))
|
||||||
(rx/subs!
|
(rx/reduce conj [])))
|
||||||
(fn [_]
|
(rx/empty))))
|
||||||
(api/update-shape-tiles)
|
|
||||||
(api/clear-drawing-cache)
|
(defn process-shape-changes!
|
||||||
(api/request-render "set-wasm-attrs-pending"))))
|
[objects shape-changes]
|
||||||
(do
|
(->> (rx/from shape-changes)
|
||||||
(api/update-shape-tiles)
|
(rx/mapcat (fn [[shape-id props]] (process-shape! (get objects shape-id) props)))
|
||||||
(api/request-render "set-wasm-attrs"))))))))
|
(rx/subs!
|
||||||
|
(fn [_]
|
||||||
|
(api/update-shape-tiles)
|
||||||
|
(api/request-render "set-wasm-attrs")))))
|
||||||
|
|
||||||
|
;; `conj` empty set initialization
|
||||||
|
(def conj* (fnil conj #{}))
|
||||||
|
|
||||||
(defn- impl-assoc
|
(defn- impl-assoc
|
||||||
[self k v]
|
[self k v]
|
||||||
(when ^boolean shape/*wasm-sync*
|
(when shape/*shape-changes*
|
||||||
(binding [shape/*wasm-sync* false]
|
(vswap! shape/*shape-changes* update (:id self) conj* k))
|
||||||
(set-wasm-attrs! self k v)))
|
|
||||||
|
|
||||||
(case k
|
(case k
|
||||||
:id
|
:id
|
||||||
@ -299,10 +287,9 @@
|
|||||||
|
|
||||||
(defn- impl-dissoc
|
(defn- impl-dissoc
|
||||||
[self k]
|
[self k]
|
||||||
(when ^boolean shape/*wasm-sync*
|
(when shape/*shape-changes*
|
||||||
(binding [shape/*wasm-sync* false]
|
(vswap! shape/*shape-changes* update (:id self) conj* k))
|
||||||
(when (shape-in-current-page? (.-id ^ShapeProxy self))
|
|
||||||
(set-wasm-attrs! self k nil))))
|
|
||||||
(case k
|
(case k
|
||||||
:id
|
:id
|
||||||
(ShapeProxy. nil
|
(ShapeProxy. nil
|
||||||
|
|||||||
@ -20,10 +20,11 @@ use mem::SerializableResult;
|
|||||||
use shapes::{StructureEntry, StructureEntryType, TransformEntry};
|
use shapes::{StructureEntry, StructureEntryType, TransformEntry};
|
||||||
use skia_safe as skia;
|
use skia_safe as skia;
|
||||||
use state::State;
|
use state::State;
|
||||||
|
use std::collections::HashMap;
|
||||||
use utils::uuid_from_u32_quartet;
|
use utils::uuid_from_u32_quartet;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub(crate) static mut STATE: Option<Box<State>> = None;
|
pub(crate) static mut STATE: Option<Box<State<'static>>> = None;
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! with_state_mut {
|
macro_rules! with_state_mut {
|
||||||
@ -253,8 +254,8 @@ pub extern "C" fn set_shape_masked_group(masked: bool) {
|
|||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn set_shape_selrect(left: f32, top: f32, right: f32, bottom: f32) {
|
pub extern "C" fn set_shape_selrect(left: f32, top: f32, right: f32, bottom: f32) {
|
||||||
with_state_mut!(state, {
|
with_current_shape_mut!(state, |shape: &mut Shape| {
|
||||||
state.set_selrect_for_current_shape(left, top, right, bottom);
|
shape.set_selrect(left, top, right, bottom);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -289,15 +290,21 @@ pub extern "C" fn add_shape_child(a: u32, b: u32, c: u32, d: u32) {
|
|||||||
|
|
||||||
fn set_children_set(entries: IndexSet<Uuid>) {
|
fn set_children_set(entries: IndexSet<Uuid>) {
|
||||||
let mut deleted = IndexSet::new();
|
let mut deleted = IndexSet::new();
|
||||||
|
let mut parent_id = None;
|
||||||
|
|
||||||
with_current_shape_mut!(state, |shape: &mut Shape| {
|
with_current_shape_mut!(state, |shape: &mut Shape| {
|
||||||
|
parent_id = Some(shape.id);
|
||||||
(_, deleted) = shape.compute_children_differences(&entries);
|
(_, deleted) = shape.compute_children_differences(&entries);
|
||||||
shape.children = entries.clone();
|
shape.children = entries.clone();
|
||||||
});
|
});
|
||||||
|
|
||||||
with_state_mut!(state, {
|
with_state_mut!(state, {
|
||||||
|
let Some(parent_id) = parent_id else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
for id in deleted {
|
for id in deleted {
|
||||||
state.delete_shape(id);
|
state.delete_shape_children(parent_id, id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -494,11 +501,7 @@ pub extern "C" fn get_selection_rect() -> *mut u8 {
|
|||||||
with_state_mut!(state, {
|
with_state_mut!(state, {
|
||||||
let bbs: Vec<_> = entries
|
let bbs: Vec<_> = entries
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|id| {
|
.flat_map(|id| state.shapes.get(id).map(|b| b.bounds()))
|
||||||
let default = Matrix::default();
|
|
||||||
let modifier = state.modifiers.get(id).unwrap_or(&default);
|
|
||||||
state.shapes.get(id).map(|b| b.bounds().transform(modifier))
|
|
||||||
})
|
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let result_bound = if bbs.len() == 1 {
|
let result_bound = if bbs.len() == 1 {
|
||||||
@ -537,6 +540,8 @@ pub extern "C" fn set_structure_modifiers() {
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
with_state_mut!(state, {
|
with_state_mut!(state, {
|
||||||
|
let mut structure = HashMap::new();
|
||||||
|
let mut scale_content = HashMap::new();
|
||||||
for entry in entries {
|
for entry in entries {
|
||||||
match entry.entry_type {
|
match entry.entry_type {
|
||||||
StructureEntryType::ScaleContent => {
|
StructureEntryType::ScaleContent => {
|
||||||
@ -544,19 +549,24 @@ pub extern "C" fn set_structure_modifiers() {
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
for id in shape.all_children(&state.shapes, true, true) {
|
for id in shape.all_children(&state.shapes, true, true) {
|
||||||
state.scale_content.insert(id, entry.value);
|
scale_content.insert(id, entry.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
state.structure.entry(entry.parent).or_insert_with(Vec::new);
|
structure.entry(entry.parent).or_insert_with(Vec::new);
|
||||||
state
|
structure
|
||||||
.structure
|
|
||||||
.get_mut(&entry.parent)
|
.get_mut(&entry.parent)
|
||||||
.expect("Parent not found for entry")
|
.expect("Parent not found for entry")
|
||||||
.push(entry);
|
.push(entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !scale_content.is_empty() {
|
||||||
|
state.shapes.set_scale_content(scale_content);
|
||||||
|
}
|
||||||
|
if !structure.is_empty() {
|
||||||
|
state.shapes.set_structure(structure);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
mem::free_bytes();
|
mem::free_bytes();
|
||||||
@ -565,9 +575,7 @@ pub extern "C" fn set_structure_modifiers() {
|
|||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn clean_modifiers() {
|
pub extern "C" fn clean_modifiers() {
|
||||||
with_state_mut!(state, {
|
with_state_mut!(state, {
|
||||||
state.structure.clear();
|
state.shapes.clean_all();
|
||||||
state.scale_content.clear();
|
|
||||||
state.modifiers.clear();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -595,11 +603,16 @@ pub extern "C" fn set_modifiers() {
|
|||||||
.map(|data| TransformEntry::from_bytes(data.try_into().unwrap()))
|
.map(|data| TransformEntry::from_bytes(data.try_into().unwrap()))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
let mut modifiers = HashMap::new();
|
||||||
|
let mut ids = Vec::<Uuid>::new();
|
||||||
|
for entry in entries {
|
||||||
|
modifiers.insert(entry.id, entry.transform);
|
||||||
|
ids.push(entry.id);
|
||||||
|
}
|
||||||
|
|
||||||
with_state_mut!(state, {
|
with_state_mut!(state, {
|
||||||
for entry in entries {
|
state.set_modifiers(modifiers);
|
||||||
state.modifiers.insert(entry.id, entry.transform);
|
state.rebuild_modifier_tiles(ids);
|
||||||
}
|
|
||||||
state.rebuild_modifier_tiles();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -32,6 +32,7 @@ pub fn are_close_points(a: impl Into<(f32, f32)>, b: impl Into<(f32, f32)>) -> b
|
|||||||
is_close_to(a_x, b_x) && is_close_to(a_y, b_y)
|
is_close_to(a_x, b_x) && is_close_to(a_y, b_y)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn is_close_matrix(m: &Matrix, other: &Matrix) -> bool {
|
pub fn is_close_matrix(m: &Matrix, other: &Matrix) -> bool {
|
||||||
is_close_to(m.scale_x(), other.scale_x())
|
is_close_to(m.scale_x(), other.scale_x())
|
||||||
&& is_close_to(m.scale_y(), other.scale_y())
|
&& is_close_to(m.scale_y(), other.scale_y())
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
use super::Matrix;
|
use super::Matrix;
|
||||||
use crate::render::{RenderState, SurfaceId};
|
use crate::render::{RenderState, SurfaceId};
|
||||||
use crate::shapes::{BoolType, Path, Segment, Shape, StructureEntry, ToPath, Type};
|
use crate::shapes::{BoolType, Path, Segment, Shape, StructureEntry, ToPath, Type};
|
||||||
use crate::state::ShapesPool;
|
use crate::state::ShapesPoolRef;
|
||||||
use crate::uuid::Uuid;
|
use crate::uuid::Uuid;
|
||||||
use bezier_rs::{Bezier, BezierHandles, ProjectionOptions, TValue};
|
use bezier_rs::{Bezier, BezierHandles, ProjectionOptions, TValue};
|
||||||
use glam::DVec2;
|
use glam::DVec2;
|
||||||
@ -387,9 +387,7 @@ fn beziers_to_segments(beziers: &[(BezierSource, Bezier)]) -> Vec<Segment> {
|
|||||||
pub fn bool_from_shapes(
|
pub fn bool_from_shapes(
|
||||||
bool_type: BoolType,
|
bool_type: BoolType,
|
||||||
children_ids: &IndexSet<Uuid>,
|
children_ids: &IndexSet<Uuid>,
|
||||||
shapes: &ShapesPool,
|
shapes: ShapesPoolRef,
|
||||||
modifiers: &HashMap<Uuid, Matrix>,
|
|
||||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
|
||||||
) -> Path {
|
) -> Path {
|
||||||
if children_ids.is_empty() {
|
if children_ids.is_empty() {
|
||||||
return Path::default();
|
return Path::default();
|
||||||
@ -399,13 +397,13 @@ pub fn bool_from_shapes(
|
|||||||
return Path::default();
|
return Path::default();
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut current_path = child.to_path(shapes, modifiers, structure);
|
let mut current_path = child.to_path(shapes);
|
||||||
|
|
||||||
for idx in (0..children_ids.len() - 1).rev() {
|
for idx in (0..children_ids.len() - 1).rev() {
|
||||||
let Some(other) = shapes.get(&children_ids[idx]) else {
|
let Some(other) = shapes.get(&children_ids[idx]) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let other_path = other.to_path(shapes, modifiers, structure);
|
let other_path = other.to_path(shapes);
|
||||||
|
|
||||||
let (segs_a, segs_b) = split_segments(¤t_path, &other_path);
|
let (segs_a, segs_b) = split_segments(¤t_path, &other_path);
|
||||||
|
|
||||||
@ -422,26 +420,15 @@ pub fn bool_from_shapes(
|
|||||||
current_path
|
current_path
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_bool_to_path(
|
#[allow(dead_code)]
|
||||||
shape: &Shape,
|
pub fn update_bool_to_path(shape: &mut Shape, shapes: ShapesPoolRef) {
|
||||||
shapes: &ShapesPool,
|
let children_ids = shape.children_ids(true);
|
||||||
modifiers: &HashMap<Uuid, Matrix>,
|
|
||||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
|
||||||
) -> Shape {
|
|
||||||
let mut shape = shape.clone();
|
|
||||||
let children_ids = shape.modified_children_ids(structure.get(&shape.id), true);
|
|
||||||
|
|
||||||
let Type::Bool(bool_data) = &mut shape.shape_type else {
|
let Type::Bool(bool_data) = &mut shape.shape_type else {
|
||||||
return shape;
|
return;
|
||||||
};
|
};
|
||||||
bool_data.path = bool_from_shapes(
|
|
||||||
bool_data.bool_type,
|
bool_data.path = bool_from_shapes(bool_data.bool_type, &children_ids, shapes);
|
||||||
&children_ids,
|
|
||||||
shapes,
|
|
||||||
modifiers,
|
|
||||||
structure,
|
|
||||||
);
|
|
||||||
shape
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@ -449,15 +436,15 @@ pub fn update_bool_to_path(
|
|||||||
pub fn debug_render_bool_paths(
|
pub fn debug_render_bool_paths(
|
||||||
render_state: &mut RenderState,
|
render_state: &mut RenderState,
|
||||||
shape: &Shape,
|
shape: &Shape,
|
||||||
shapes: &ShapesPool,
|
shapes: ShapesPoolRef,
|
||||||
modifiers: &HashMap<Uuid, Matrix>,
|
_modifiers: &HashMap<Uuid, Matrix>,
|
||||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
_structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||||
) {
|
) {
|
||||||
let canvas = render_state.surfaces.canvas(SurfaceId::Strokes);
|
let canvas = render_state.surfaces.canvas(SurfaceId::Strokes);
|
||||||
|
|
||||||
let mut shape = shape.clone();
|
let mut shape = shape.clone();
|
||||||
|
|
||||||
let children_ids = shape.modified_children_ids(structure.get(&shape.id), true);
|
let children_ids = shape.children_ids(true);
|
||||||
|
|
||||||
let Type::Bool(bool_data) = &mut shape.shape_type else {
|
let Type::Bool(bool_data) = &mut shape.shape_type else {
|
||||||
return;
|
return;
|
||||||
@ -471,13 +458,13 @@ pub fn debug_render_bool_paths(
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut current_path = child.to_path(shapes, modifiers, structure);
|
let mut current_path = child.to_path(shapes);
|
||||||
|
|
||||||
for idx in (0..children_ids.len() - 1).rev() {
|
for idx in (0..children_ids.len() - 1).rev() {
|
||||||
let Some(other) = shapes.get(&children_ids[idx]) else {
|
let Some(other) = shapes.get(&children_ids[idx]) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let other_path = other.to_path(shapes, modifiers, structure);
|
let other_path = other.to_path(shapes);
|
||||||
|
|
||||||
let (segs_a, segs_b) = split_segments(¤t_path, &other_path);
|
let (segs_a, segs_b) = split_segments(¤t_path, &other_path);
|
||||||
|
|
||||||
|
|||||||
@ -14,7 +14,7 @@ mod ui;
|
|||||||
|
|
||||||
use skia_safe::{self as skia, Matrix, RRect, Rect};
|
use skia_safe::{self as skia, Matrix, RRect, Rect};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use gpu_state::GpuState;
|
use gpu_state::GpuState;
|
||||||
use options::RenderOptions;
|
use options::RenderOptions;
|
||||||
@ -22,16 +22,14 @@ pub use surfaces::{SurfaceId, Surfaces};
|
|||||||
|
|
||||||
use crate::performance;
|
use crate::performance;
|
||||||
use crate::shapes::{
|
use crate::shapes::{
|
||||||
Blur, BlurType, Corners, Fill, Shadow, Shape, SolidColor, Stroke, StructureEntry, Type,
|
all_with_ancestors, Blur, BlurType, Corners, Fill, Shadow, Shape, SolidColor, Stroke, Type,
|
||||||
};
|
};
|
||||||
use crate::state::ShapesPool;
|
use crate::state::{ShapesPoolMutRef, ShapesPoolRef};
|
||||||
use crate::tiles::{self, PendingTiles, TileRect};
|
use crate::tiles::{self, PendingTiles, TileRect};
|
||||||
use crate::uuid::Uuid;
|
use crate::uuid::Uuid;
|
||||||
use crate::view::Viewbox;
|
use crate::view::Viewbox;
|
||||||
use crate::wapi;
|
use crate::wapi;
|
||||||
|
|
||||||
use crate::math;
|
|
||||||
use crate::math::bools;
|
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
|
|
||||||
pub use fonts::*;
|
pub use fonts::*;
|
||||||
@ -62,13 +60,11 @@ impl NodeRenderState {
|
|||||||
/// Calculates the clip bounds for child elements of a given shape.
|
/// Calculates the clip bounds for child elements of a given shape.
|
||||||
///
|
///
|
||||||
/// This function determines the clipping region that should be applied to child elements
|
/// This function determines the clipping region that should be applied to child elements
|
||||||
/// when rendering. It takes into account the element's selection rectangle, transform,
|
/// when rendering. It takes into account the element's selection rectangle, transform.
|
||||||
/// and any additional modifiers.
|
|
||||||
///
|
///
|
||||||
/// # Parameters
|
/// # Parameters
|
||||||
///
|
///
|
||||||
/// * `element` - The shape element for which to calculate clip bounds
|
/// * `element` - The shape element for which to calculate clip bounds
|
||||||
/// * `modifiers` - Optional transformation matrix to apply to the bounds
|
|
||||||
/// * `offset` - Optional offset (x, y) to adjust the bounds position. When provided,
|
/// * `offset` - Optional offset (x, y) to adjust the bounds position. When provided,
|
||||||
/// the bounds are translated by the negative of this offset, effectively moving
|
/// the bounds are translated by the negative of this offset, effectively moving
|
||||||
/// the clipping region to compensate for coordinate system transformations.
|
/// the clipping region to compensate for coordinate system transformations.
|
||||||
@ -77,7 +73,6 @@ impl NodeRenderState {
|
|||||||
pub fn get_children_clip_bounds(
|
pub fn get_children_clip_bounds(
|
||||||
&self,
|
&self,
|
||||||
element: &Shape,
|
element: &Shape,
|
||||||
modifiers: Option<&Matrix>,
|
|
||||||
offset: Option<(f32, f32)>,
|
offset: Option<(f32, f32)>,
|
||||||
) -> Option<(Rect, Option<Corners>, Matrix)> {
|
) -> Option<(Rect, Option<Corners>, Matrix)> {
|
||||||
if self.id.is_nil() || !element.clip() {
|
if self.id.is_nil() || !element.clip() {
|
||||||
@ -96,10 +91,6 @@ impl NodeRenderState {
|
|||||||
transform.post_translate(bounds.center());
|
transform.post_translate(bounds.center());
|
||||||
transform.pre_translate(-bounds.center());
|
transform.pre_translate(-bounds.center());
|
||||||
|
|
||||||
if let Some(modifier) = modifiers {
|
|
||||||
transform.post_concat(modifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
let corners = match &element.shape_type {
|
let corners = match &element.shape_type {
|
||||||
Type::Rect(data) => data.corners,
|
Type::Rect(data) => data.corners,
|
||||||
Type::Frame(data) => data.corners,
|
Type::Frame(data) => data.corners,
|
||||||
@ -118,12 +109,10 @@ impl NodeRenderState {
|
|||||||
/// # Parameters
|
/// # Parameters
|
||||||
///
|
///
|
||||||
/// * `element` - The shape element for which to calculate shadow clip bounds
|
/// * `element` - The shape element for which to calculate shadow clip bounds
|
||||||
/// * `modifiers` - Optional transformation matrix to apply to the bounds
|
|
||||||
/// * `shadow` - The shadow configuration containing blur, offset, and other properties
|
/// * `shadow` - The shadow configuration containing blur, offset, and other properties
|
||||||
pub fn get_nested_shadow_clip_bounds(
|
pub fn get_nested_shadow_clip_bounds(
|
||||||
&self,
|
&self,
|
||||||
element: &Shape,
|
element: &Shape,
|
||||||
modifiers: Option<&Matrix>,
|
|
||||||
shadow: &Shadow,
|
shadow: &Shadow,
|
||||||
) -> Option<(Rect, Option<Corners>, Matrix)> {
|
) -> Option<(Rect, Option<Corners>, Matrix)> {
|
||||||
if self.id.is_nil() {
|
if self.id.is_nil() {
|
||||||
@ -141,10 +130,6 @@ impl NodeRenderState {
|
|||||||
transform.post_translate(element.center());
|
transform.post_translate(element.center());
|
||||||
transform.pre_translate(-element.center());
|
transform.pre_translate(-element.center());
|
||||||
|
|
||||||
if let Some(modifier) = modifiers {
|
|
||||||
transform.post_concat(modifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
let corners = match &element.shape_type {
|
let corners = match &element.shape_type {
|
||||||
Type::Rect(data) => data.corners,
|
Type::Rect(data) => data.corners,
|
||||||
Type::Frame(data) => data.corners,
|
Type::Frame(data) => data.corners,
|
||||||
@ -274,28 +259,6 @@ pub fn get_cache_size(viewbox: Viewbox, scale: f32) -> skia::ISize {
|
|||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_modified_child(
|
|
||||||
shape: &Shape,
|
|
||||||
shapes: &ShapesPool,
|
|
||||||
modifiers: &HashMap<Uuid, Matrix>,
|
|
||||||
) -> bool {
|
|
||||||
if modifiers.is_empty() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let ids = shape.all_children(shapes, true, false);
|
|
||||||
let default = &Matrix::default();
|
|
||||||
let parent_modifier = modifiers.get(&shape.id).unwrap_or(default);
|
|
||||||
|
|
||||||
// Returns true if the transform of any child is different to the parent's
|
|
||||||
ids.iter().any(|id| {
|
|
||||||
!math::is_close_matrix(
|
|
||||||
parent_modifier,
|
|
||||||
modifiers.get(id).unwrap_or(&Matrix::default()),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderState {
|
impl RenderState {
|
||||||
pub fn new(width: i32, height: i32) -> RenderState {
|
pub fn new(width: i32, height: i32) -> RenderState {
|
||||||
// This needs to be done once per WebGL context.
|
// This needs to be done once per WebGL context.
|
||||||
@ -475,11 +438,7 @@ impl RenderState {
|
|||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn render_shape(
|
pub fn render_shape(
|
||||||
&mut self,
|
&mut self,
|
||||||
shapes: &ShapesPool,
|
|
||||||
modifiers: &HashMap<Uuid, Matrix>,
|
|
||||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
|
||||||
shape: &Shape,
|
shape: &Shape,
|
||||||
scale_content: Option<&f32>,
|
|
||||||
clip_bounds: Option<(Rect, Option<Corners>, Matrix)>,
|
clip_bounds: Option<(Rect, Option<Corners>, Matrix)>,
|
||||||
fills_surface_id: SurfaceId,
|
fills_surface_id: SurfaceId,
|
||||||
strokes_surface_id: SurfaceId,
|
strokes_surface_id: SurfaceId,
|
||||||
@ -489,12 +448,6 @@ impl RenderState {
|
|||||||
offset: Option<(f32, f32)>,
|
offset: Option<(f32, f32)>,
|
||||||
parent_shadows: Option<Vec<skia_safe::Paint>>,
|
parent_shadows: Option<Vec<skia_safe::Paint>>,
|
||||||
) {
|
) {
|
||||||
let shape = if let Some(scale_content) = scale_content {
|
|
||||||
&shape.scale_content(*scale_content)
|
|
||||||
} else {
|
|
||||||
shape
|
|
||||||
};
|
|
||||||
|
|
||||||
let surface_ids = fills_surface_id as u32
|
let surface_ids = fills_surface_id as u32
|
||||||
| strokes_surface_id as u32
|
| strokes_surface_id as u32
|
||||||
| innershadows_surface_id as u32
|
| innershadows_surface_id as u32
|
||||||
@ -545,10 +498,6 @@ impl RenderState {
|
|||||||
// We don't want to change the value in the global state
|
// We don't want to change the value in the global state
|
||||||
let mut shape: Cow<Shape> = Cow::Borrowed(shape);
|
let mut shape: Cow<Shape> = Cow::Borrowed(shape);
|
||||||
|
|
||||||
if let Some(shape_modifiers) = modifiers.get(&shape.id) {
|
|
||||||
shape.to_mut().apply_transform(shape_modifiers);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut nested_blur_value = 0.;
|
let mut nested_blur_value = 0.;
|
||||||
for nested_blur in self.nested_blurs.iter().flatten() {
|
for nested_blur in self.nested_blurs.iter().flatten() {
|
||||||
if !nested_blur.hidden && nested_blur.blur_type == BlurType::LayerBlur {
|
if !nested_blur.hidden && nested_blur.blur_type == BlurType::LayerBlur {
|
||||||
@ -579,12 +528,12 @@ impl RenderState {
|
|||||||
|
|
||||||
match &shape.shape_type {
|
match &shape.shape_type {
|
||||||
Type::SVGRaw(sr) => {
|
Type::SVGRaw(sr) => {
|
||||||
if let Some(shape_modifiers) = modifiers.get(&shape.id) {
|
if let Some(svg_transform) = shape.svg_transform() {
|
||||||
self.surfaces
|
matrix.pre_concat(&svg_transform);
|
||||||
.canvas(fills_surface_id)
|
|
||||||
.concat(shape_modifiers);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.surfaces.canvas(fills_surface_id).concat(&matrix);
|
self.surfaces.canvas(fills_surface_id).concat(&matrix);
|
||||||
|
|
||||||
if let Some(svg) = shape.svg.as_ref() {
|
if let Some(svg) = shape.svg.as_ref() {
|
||||||
svg.render(self.surfaces.canvas(fills_surface_id))
|
svg.render(self.surfaces.canvas(fills_surface_id))
|
||||||
} else {
|
} else {
|
||||||
@ -750,20 +699,7 @@ impl RenderState {
|
|||||||
s.canvas().concat(&matrix);
|
s.canvas().concat(&matrix);
|
||||||
});
|
});
|
||||||
|
|
||||||
// For boolean shapes, there's no need to calculate children because
|
let shape = &shape;
|
||||||
// when painting the shape, the necessary path is already calculated
|
|
||||||
let shape = if let Type::Bool(_) = &shape.shape_type {
|
|
||||||
// If any child transform doesn't match the parent transform means
|
|
||||||
// that the children is transformed and we need to recalculate the
|
|
||||||
// boolean
|
|
||||||
if is_modified_child(&shape, shapes, modifiers) {
|
|
||||||
&bools::update_bool_to_path(&shape, shapes, modifiers, structure)
|
|
||||||
} else {
|
|
||||||
&shape
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
&shape
|
|
||||||
};
|
|
||||||
|
|
||||||
if shape.fills.is_empty()
|
if shape.fills.is_empty()
|
||||||
&& !matches!(shape.shape_type, Type::Group(_))
|
&& !matches!(shape.shape_type, Type::Group(_))
|
||||||
@ -836,12 +772,7 @@ impl RenderState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_from_cache(
|
pub fn render_from_cache(&mut self, shapes: ShapesPoolRef) {
|
||||||
&mut self,
|
|
||||||
shapes: &ShapesPool,
|
|
||||||
modifiers: &HashMap<Uuid, Matrix>,
|
|
||||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
|
||||||
) {
|
|
||||||
let scale = self.get_cached_scale();
|
let scale = self.get_cached_scale();
|
||||||
if let Some(snapshot) = &self.cached_target_snapshot {
|
if let Some(snapshot) = &self.cached_target_snapshot {
|
||||||
let canvas = self.surfaces.canvas(SurfaceId::Target);
|
let canvas = self.surfaces.canvas(SurfaceId::Target);
|
||||||
@ -874,21 +805,14 @@ impl RenderState {
|
|||||||
debug::render(self);
|
debug::render(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
ui::render(self, shapes, modifiers, structure);
|
ui::render(self, shapes);
|
||||||
debug::render_wasm_label(self);
|
debug::render_wasm_label(self);
|
||||||
|
|
||||||
self.flush_and_submit();
|
self.flush_and_submit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_render_loop(
|
pub fn start_render_loop(&mut self, tree: ShapesPoolRef, timestamp: i32) -> Result<(), String> {
|
||||||
&mut self,
|
|
||||||
tree: &ShapesPool,
|
|
||||||
modifiers: &HashMap<Uuid, Matrix>,
|
|
||||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
|
||||||
scale_content: &HashMap<Uuid, f32>,
|
|
||||||
timestamp: i32,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
let scale = self.get_scale();
|
let scale = self.get_scale();
|
||||||
self.tile_viewbox.update(self.viewbox, scale);
|
self.tile_viewbox.update(self.viewbox, scale);
|
||||||
|
|
||||||
@ -936,29 +860,20 @@ impl RenderState {
|
|||||||
self.current_tile = None;
|
self.current_tile = None;
|
||||||
self.render_in_progress = true;
|
self.render_in_progress = true;
|
||||||
self.apply_drawing_to_render_canvas(None);
|
self.apply_drawing_to_render_canvas(None);
|
||||||
self.process_animation_frame(tree, modifiers, structure, scale_content, timestamp)?;
|
self.process_animation_frame(tree, timestamp)?;
|
||||||
performance::end_measure!("start_render_loop");
|
performance::end_measure!("start_render_loop");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_animation_frame(
|
pub fn process_animation_frame(
|
||||||
&mut self,
|
&mut self,
|
||||||
tree: &ShapesPool,
|
tree: ShapesPoolRef,
|
||||||
modifiers: &HashMap<Uuid, Matrix>,
|
|
||||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
|
||||||
scale_content: &HashMap<Uuid, f32>,
|
|
||||||
timestamp: i32,
|
timestamp: i32,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
performance::begin_measure!("process_animation_frame");
|
performance::begin_measure!("process_animation_frame");
|
||||||
if self.render_in_progress {
|
if self.render_in_progress {
|
||||||
if tree.len() != 0 {
|
if tree.len() != 0 {
|
||||||
self.render_shape_tree_partial(
|
self.render_shape_tree_partial(tree, timestamp)?;
|
||||||
tree,
|
|
||||||
modifiers,
|
|
||||||
structure,
|
|
||||||
scale_content,
|
|
||||||
timestamp,
|
|
||||||
)?;
|
|
||||||
} else {
|
} else {
|
||||||
println!("Empty tree");
|
println!("Empty tree");
|
||||||
}
|
}
|
||||||
@ -1028,15 +943,7 @@ impl RenderState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn render_shape_exit(
|
pub fn render_shape_exit(&mut self, element: &Shape, visited_mask: bool) {
|
||||||
&mut self,
|
|
||||||
tree: &ShapesPool,
|
|
||||||
modifiers: &HashMap<Uuid, Matrix>,
|
|
||||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
|
||||||
element: &Shape,
|
|
||||||
visited_mask: bool,
|
|
||||||
scale_content: Option<&f32>,
|
|
||||||
) {
|
|
||||||
if visited_mask {
|
if visited_mask {
|
||||||
// Because masked groups needs two rendering passes (first drawing
|
// Because masked groups needs two rendering passes (first drawing
|
||||||
// the content and then drawing the mask), we need to do an
|
// the content and then drawing the mask), we need to do an
|
||||||
@ -1090,11 +997,7 @@ impl RenderState {
|
|||||||
element_strokes.to_mut().clear_shadows();
|
element_strokes.to_mut().clear_shadows();
|
||||||
element_strokes.to_mut().clip_content = false;
|
element_strokes.to_mut().clip_content = false;
|
||||||
self.render_shape(
|
self.render_shape(
|
||||||
tree,
|
|
||||||
modifiers,
|
|
||||||
structure,
|
|
||||||
&element_strokes,
|
&element_strokes,
|
||||||
scale_content,
|
|
||||||
None,
|
None,
|
||||||
SurfaceId::Fills,
|
SurfaceId::Fills,
|
||||||
SurfaceId::Strokes,
|
SurfaceId::Strokes,
|
||||||
@ -1140,13 +1043,8 @@ impl RenderState {
|
|||||||
self.get_rect_bounds(rect)
|
self.get_rect_bounds(rect)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_shape_extrect_bounds(
|
pub fn get_shape_extrect_bounds(&mut self, shape: &Shape, tree: ShapesPoolRef) -> Rect {
|
||||||
&mut self,
|
let rect = shape.extrect(tree);
|
||||||
shape: &Shape,
|
|
||||||
tree: &ShapesPool,
|
|
||||||
modifiers: &HashMap<Uuid, Matrix>,
|
|
||||||
) -> Rect {
|
|
||||||
let rect = shape.extrect(tree, modifiers);
|
|
||||||
self.get_rect_bounds(rect)
|
self.get_rect_bounds(rect)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1183,12 +1081,8 @@ impl RenderState {
|
|||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn render_drop_black_shadow(
|
fn render_drop_black_shadow(
|
||||||
&mut self,
|
&mut self,
|
||||||
shapes: &ShapesPool,
|
|
||||||
modifiers: &HashMap<Uuid, Matrix>,
|
|
||||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
|
||||||
shape: &Shape,
|
shape: &Shape,
|
||||||
shadow: &Shadow,
|
shadow: &Shadow,
|
||||||
scale_content: Option<&f32>,
|
|
||||||
clip_bounds: Option<(Rect, Option<Corners>, Matrix)>,
|
clip_bounds: Option<(Rect, Option<Corners>, Matrix)>,
|
||||||
scale: f32,
|
scale: f32,
|
||||||
translation: (f32, f32),
|
translation: (f32, f32),
|
||||||
@ -1237,11 +1131,7 @@ impl RenderState {
|
|||||||
.translate(translation);
|
.translate(translation);
|
||||||
|
|
||||||
self.render_shape(
|
self.render_shape(
|
||||||
shapes,
|
|
||||||
modifiers,
|
|
||||||
structure,
|
|
||||||
&plain_shape,
|
&plain_shape,
|
||||||
scale_content,
|
|
||||||
clip_bounds,
|
clip_bounds,
|
||||||
SurfaceId::DropShadows,
|
SurfaceId::DropShadows,
|
||||||
SurfaceId::DropShadows,
|
SurfaceId::DropShadows,
|
||||||
@ -1257,10 +1147,7 @@ impl RenderState {
|
|||||||
|
|
||||||
pub fn render_shape_tree_partial_uncached(
|
pub fn render_shape_tree_partial_uncached(
|
||||||
&mut self,
|
&mut self,
|
||||||
tree: &ShapesPool,
|
tree: ShapesPoolRef,
|
||||||
modifiers: &HashMap<Uuid, Matrix>,
|
|
||||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
|
||||||
scale_content: &HashMap<Uuid, f32>,
|
|
||||||
timestamp: i32,
|
timestamp: i32,
|
||||||
) -> Result<(bool, bool), String> {
|
) -> Result<(bool, bool), String> {
|
||||||
let mut iteration = 0;
|
let mut iteration = 0;
|
||||||
@ -1276,26 +1163,20 @@ impl RenderState {
|
|||||||
} = node_render_state;
|
} = node_render_state;
|
||||||
|
|
||||||
is_empty = false;
|
is_empty = false;
|
||||||
let element = tree.get(&node_id).ok_or(
|
|
||||||
"Error: Element with root_id {node_render_state.id} not found in the tree."
|
let element = tree.get(&node_id).ok_or(format!(
|
||||||
.to_string(),
|
"Error: Element with root_id {} not found in the tree.",
|
||||||
)?;
|
node_render_state.id
|
||||||
|
))?;
|
||||||
|
|
||||||
// If the shape is not in the tile set, then we update
|
// If the shape is not in the tile set, then we update
|
||||||
// it.
|
// it.
|
||||||
if self.tiles.get_tiles_of(node_id).is_none() {
|
if self.tiles.get_tiles_of(node_id).is_none() {
|
||||||
self.update_tile_for(element, tree, modifiers);
|
self.update_tile_for(element, tree);
|
||||||
}
|
}
|
||||||
|
|
||||||
if visited_children {
|
if visited_children {
|
||||||
self.render_shape_exit(
|
self.render_shape_exit(element, visited_mask);
|
||||||
tree,
|
|
||||||
modifiers,
|
|
||||||
structure,
|
|
||||||
element,
|
|
||||||
visited_mask,
|
|
||||||
scale_content.get(&element.id),
|
|
||||||
);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1303,18 +1184,14 @@ impl RenderState {
|
|||||||
let transformed_element: Cow<Shape> = Cow::Borrowed(element);
|
let transformed_element: Cow<Shape> = Cow::Borrowed(element);
|
||||||
|
|
||||||
let is_visible = transformed_element
|
let is_visible = transformed_element
|
||||||
.extrect(tree, modifiers)
|
.extrect(tree)
|
||||||
.intersects(self.render_area)
|
.intersects(self.render_area)
|
||||||
&& !transformed_element.hidden
|
&& !transformed_element.hidden
|
||||||
&& !transformed_element.visually_insignificant(
|
&& !transformed_element.visually_insignificant(self.get_scale(), tree);
|
||||||
self.get_scale(),
|
|
||||||
tree,
|
|
||||||
modifiers,
|
|
||||||
);
|
|
||||||
|
|
||||||
if self.options.is_debug_visible() {
|
if self.options.is_debug_visible() {
|
||||||
let shape_extrect_bounds =
|
let shape_extrect_bounds =
|
||||||
self.get_shape_extrect_bounds(&transformed_element, tree, modifiers);
|
self.get_shape_extrect_bounds(&transformed_element, tree);
|
||||||
debug::render_debug_shape(self, None, Some(shape_extrect_bounds));
|
debug::render_debug_shape(self, None, Some(shape_extrect_bounds));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1359,12 +1236,8 @@ impl RenderState {
|
|||||||
|
|
||||||
// First pass: Render shadow in black to establish alpha mask
|
// First pass: Render shadow in black to establish alpha mask
|
||||||
self.render_drop_black_shadow(
|
self.render_drop_black_shadow(
|
||||||
tree,
|
|
||||||
modifiers,
|
|
||||||
structure,
|
|
||||||
element,
|
element,
|
||||||
shadow,
|
shadow,
|
||||||
scale_content.get(&element.id),
|
|
||||||
clip_bounds,
|
clip_bounds,
|
||||||
scale,
|
scale,
|
||||||
translation,
|
translation,
|
||||||
@ -1377,20 +1250,13 @@ impl RenderState {
|
|||||||
if shadow_shape.hidden {
|
if shadow_shape.hidden {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let clip_bounds = node_render_state.get_nested_shadow_clip_bounds(
|
let clip_bounds = node_render_state
|
||||||
element,
|
.get_nested_shadow_clip_bounds(element, shadow);
|
||||||
modifiers.get(&element.id),
|
|
||||||
shadow,
|
|
||||||
);
|
|
||||||
|
|
||||||
if !matches!(shadow_shape.shape_type, Type::Text(_)) {
|
if !matches!(shadow_shape.shape_type, Type::Text(_)) {
|
||||||
self.render_drop_black_shadow(
|
self.render_drop_black_shadow(
|
||||||
tree,
|
|
||||||
modifiers,
|
|
||||||
structure,
|
|
||||||
shadow_shape,
|
shadow_shape,
|
||||||
shadow,
|
shadow,
|
||||||
scale_content.get(&element.id),
|
|
||||||
clip_bounds,
|
clip_bounds,
|
||||||
scale,
|
scale,
|
||||||
translation,
|
translation,
|
||||||
@ -1423,11 +1289,7 @@ impl RenderState {
|
|||||||
new_shadow_paint.set_blend_mode(skia::BlendMode::SrcOver);
|
new_shadow_paint.set_blend_mode(skia::BlendMode::SrcOver);
|
||||||
|
|
||||||
self.render_shape(
|
self.render_shape(
|
||||||
tree,
|
|
||||||
modifiers,
|
|
||||||
structure,
|
|
||||||
shadow_shape,
|
shadow_shape,
|
||||||
scale_content.get(&element.id),
|
|
||||||
clip_bounds,
|
clip_bounds,
|
||||||
SurfaceId::DropShadows,
|
SurfaceId::DropShadows,
|
||||||
SurfaceId::DropShadows,
|
SurfaceId::DropShadows,
|
||||||
@ -1464,11 +1326,7 @@ impl RenderState {
|
|||||||
.clear(skia::Color::TRANSPARENT);
|
.clear(skia::Color::TRANSPARENT);
|
||||||
|
|
||||||
self.render_shape(
|
self.render_shape(
|
||||||
tree,
|
|
||||||
modifiers,
|
|
||||||
structure,
|
|
||||||
element,
|
element,
|
||||||
scale_content.get(&element.id),
|
|
||||||
clip_bounds,
|
clip_bounds,
|
||||||
SurfaceId::Fills,
|
SurfaceId::Fills,
|
||||||
SurfaceId::Strokes,
|
SurfaceId::Strokes,
|
||||||
@ -1505,14 +1363,10 @@ impl RenderState {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if element.is_recursive() {
|
if element.is_recursive() {
|
||||||
let children_clip_bounds = node_render_state.get_children_clip_bounds(
|
let children_clip_bounds =
|
||||||
element,
|
node_render_state.get_children_clip_bounds(element, None);
|
||||||
modifiers.get(&element.id),
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut children_ids =
|
let mut children_ids = element.children_ids(false);
|
||||||
element.modified_children_ids(structure.get(&element.id), false);
|
|
||||||
|
|
||||||
// Z-index ordering on Layouts
|
// Z-index ordering on Layouts
|
||||||
if element.has_layout() {
|
if element.has_layout() {
|
||||||
@ -1545,10 +1399,7 @@ impl RenderState {
|
|||||||
|
|
||||||
pub fn render_shape_tree_partial(
|
pub fn render_shape_tree_partial(
|
||||||
&mut self,
|
&mut self,
|
||||||
tree: &ShapesPool,
|
tree: ShapesPoolRef,
|
||||||
modifiers: &HashMap<Uuid, Matrix>,
|
|
||||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
|
||||||
scale_content: &HashMap<Uuid, f32>,
|
|
||||||
timestamp: i32,
|
timestamp: i32,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let mut should_stop = false;
|
let mut should_stop = false;
|
||||||
@ -1574,13 +1425,8 @@ impl RenderState {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
performance::begin_measure!("render_shape_tree::uncached");
|
performance::begin_measure!("render_shape_tree::uncached");
|
||||||
let (is_empty, early_return) = self.render_shape_tree_partial_uncached(
|
let (is_empty, early_return) =
|
||||||
tree,
|
self.render_shape_tree_partial_uncached(tree, timestamp)?;
|
||||||
modifiers,
|
|
||||||
structure,
|
|
||||||
scale_content,
|
|
||||||
timestamp,
|
|
||||||
)?;
|
|
||||||
if early_return {
|
if early_return {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@ -1614,7 +1460,7 @@ impl RenderState {
|
|||||||
let Some(root) = tree.get(&Uuid::nil()) else {
|
let Some(root) = tree.get(&Uuid::nil()) else {
|
||||||
return Err(String::from("Root shape not found"));
|
return Err(String::from("Root shape not found"));
|
||||||
};
|
};
|
||||||
let root_ids = root.modified_children_ids(structure.get(&root.id), false);
|
let root_ids = root.children_ids(false);
|
||||||
|
|
||||||
// If we finish processing every node rendering is complete
|
// If we finish processing every node rendering is complete
|
||||||
// let's check if there are more pending nodes
|
// let's check if there are more pending nodes
|
||||||
@ -1657,29 +1503,20 @@ impl RenderState {
|
|||||||
debug::render(self);
|
debug::render(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
ui::render(self, tree, modifiers, structure);
|
ui::render(self, tree);
|
||||||
debug::render_wasm_label(self);
|
debug::render_wasm_label(self);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_tiles_for_shape(
|
pub fn get_tiles_for_shape(&mut self, shape: &Shape, tree: ShapesPoolRef) -> TileRect {
|
||||||
&mut self,
|
let extrect = shape.extrect(tree);
|
||||||
shape: &Shape,
|
|
||||||
tree: &ShapesPool,
|
|
||||||
modifiers: &HashMap<Uuid, Matrix>,
|
|
||||||
) -> TileRect {
|
|
||||||
let tile_size = tiles::get_tile_size(self.get_scale());
|
let tile_size = tiles::get_tile_size(self.get_scale());
|
||||||
tiles::get_tiles_for_rect(shape.extrect(tree, modifiers), tile_size)
|
tiles::get_tiles_for_rect(extrect, tile_size)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_tile_for(
|
pub fn update_tile_for(&mut self, shape: &Shape, tree: ShapesPoolRef) {
|
||||||
&mut self,
|
let TileRect(rsx, rsy, rex, rey) = self.get_tiles_for_shape(shape, tree);
|
||||||
shape: &Shape,
|
|
||||||
tree: &ShapesPool,
|
|
||||||
modifiers: &HashMap<Uuid, Matrix>,
|
|
||||||
) {
|
|
||||||
let TileRect(rsx, rsy, rex, rey) = self.get_tiles_for_shape(shape, tree, modifiers);
|
|
||||||
let old_tiles: HashSet<tiles::Tile> = self
|
let old_tiles: HashSet<tiles::Tile> = self
|
||||||
.tiles
|
.tiles
|
||||||
.get_tiles_of(shape.id)
|
.get_tiles_of(shape.id)
|
||||||
@ -1695,6 +1532,7 @@ impl RenderState {
|
|||||||
|
|
||||||
// Then, add the shape to the new tiles
|
// Then, add the shape to the new tiles
|
||||||
for tile in new_tiles {
|
for tile in new_tiles {
|
||||||
|
self.remove_cached_tile_shape(tile, shape.id);
|
||||||
self.tiles.add_shape_at(tile, shape.id);
|
self.tiles.add_shape_at(tile, shape.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1706,27 +1544,18 @@ impl RenderState {
|
|||||||
self.tiles.remove_shape_at(tile, id);
|
self.tiles.remove_shape_at(tile, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rebuild_tiles_shallow(
|
pub fn rebuild_tiles_shallow(&mut self, tree: ShapesPoolRef) {
|
||||||
&mut self,
|
|
||||||
tree: &ShapesPool,
|
|
||||||
modifiers: &HashMap<Uuid, Matrix>,
|
|
||||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
|
||||||
) {
|
|
||||||
performance::begin_measure!("rebuild_tiles_shallow");
|
performance::begin_measure!("rebuild_tiles_shallow");
|
||||||
self.tiles.invalidate();
|
self.tiles.invalidate();
|
||||||
self.surfaces.remove_cached_tiles(self.background_color);
|
self.surfaces.remove_cached_tiles(self.background_color);
|
||||||
let mut nodes = vec![Uuid::nil()];
|
let mut nodes = vec![Uuid::nil()];
|
||||||
while let Some(shape_id) = nodes.pop() {
|
while let Some(shape_id) = nodes.pop() {
|
||||||
if let Some(shape) = tree.get(&shape_id) {
|
if let Some(shape) = tree.get(&shape_id) {
|
||||||
let mut shape: Cow<Shape> = Cow::Borrowed(shape);
|
|
||||||
if shape_id != Uuid::nil() {
|
if shape_id != Uuid::nil() {
|
||||||
if let Some(modifier) = modifiers.get(&shape_id) {
|
self.update_tile_for(shape, tree);
|
||||||
shape.to_mut().apply_transform(modifier);
|
|
||||||
}
|
|
||||||
self.update_tile_for(&shape, tree, modifiers);
|
|
||||||
} else {
|
} else {
|
||||||
// We only need to rebuild tiles from the first level.
|
// We only need to rebuild tiles from the first level.
|
||||||
let children = shape.modified_children_ids(structure.get(&shape.id), false);
|
let children = shape.children_ids(false);
|
||||||
for child_id in children.iter() {
|
for child_id in children.iter() {
|
||||||
nodes.push(*child_id);
|
nodes.push(*child_id);
|
||||||
}
|
}
|
||||||
@ -1736,27 +1565,18 @@ impl RenderState {
|
|||||||
performance::end_measure!("rebuild_tiles_shallow");
|
performance::end_measure!("rebuild_tiles_shallow");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rebuild_tiles(
|
pub fn rebuild_tiles(&mut self, tree: ShapesPoolRef) {
|
||||||
&mut self,
|
|
||||||
tree: &ShapesPool,
|
|
||||||
modifiers: &HashMap<Uuid, Matrix>,
|
|
||||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
|
||||||
) {
|
|
||||||
performance::begin_measure!("rebuild_tiles");
|
performance::begin_measure!("rebuild_tiles");
|
||||||
self.tiles.invalidate();
|
self.tiles.invalidate();
|
||||||
self.surfaces.remove_cached_tiles(self.background_color);
|
self.surfaces.remove_cached_tiles(self.background_color);
|
||||||
let mut nodes = vec![Uuid::nil()];
|
let mut nodes = vec![Uuid::nil()];
|
||||||
while let Some(shape_id) = nodes.pop() {
|
while let Some(shape_id) = nodes.pop() {
|
||||||
if let Some(shape) = tree.get(&shape_id) {
|
if let Some(shape) = tree.get(&shape_id) {
|
||||||
let mut shape: Cow<Shape> = Cow::Borrowed(shape);
|
|
||||||
if shape_id != Uuid::nil() {
|
if shape_id != Uuid::nil() {
|
||||||
if let Some(modifier) = modifiers.get(&shape_id) {
|
self.update_tile_for(shape, tree);
|
||||||
shape.to_mut().apply_transform(modifier);
|
|
||||||
}
|
|
||||||
self.update_tile_for(&shape, tree, modifiers);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let children = shape.modified_children_ids(structure.get(&shape.id), false);
|
let children = shape.children_ids(false);
|
||||||
for child_id in children.iter() {
|
for child_id in children.iter() {
|
||||||
nodes.push(*child_id);
|
nodes.push(*child_id);
|
||||||
}
|
}
|
||||||
@ -1776,64 +1596,26 @@ impl RenderState {
|
|||||||
pub fn invalidate_and_update_tiles(
|
pub fn invalidate_and_update_tiles(
|
||||||
&mut self,
|
&mut self,
|
||||||
shape_ids: &IndexSet<Uuid>,
|
shape_ids: &IndexSet<Uuid>,
|
||||||
tree: &mut ShapesPool,
|
tree: ShapesPoolMutRef<'_>,
|
||||||
modifiers: &HashMap<Uuid, Matrix>,
|
|
||||||
) {
|
) {
|
||||||
for shape_id in shape_ids {
|
for shape_id in shape_ids {
|
||||||
if let Some(shape) = tree.get_mut(shape_id) {
|
|
||||||
shape.invalidate_extrect();
|
|
||||||
}
|
|
||||||
if let Some(shape) = tree.get(shape_id) {
|
if let Some(shape) = tree.get(shape_id) {
|
||||||
if !shape.id.is_nil() {
|
if !shape.id.is_nil() {
|
||||||
self.update_tile_for(shape, tree, modifiers);
|
self.update_tile_for(shape, tree);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Processes all ancestors of a shape, invalidating their extended rectangles and updating their tiles
|
|
||||||
///
|
|
||||||
/// When a shape changes, all its ancestors need to have their extended rectangles recalculated
|
|
||||||
/// because they may contain the changed shape. This function:
|
|
||||||
/// 1. Computes all ancestors of the shape
|
|
||||||
/// 2. Invalidates the extrect cache for each ancestor
|
|
||||||
/// 3. Updates the tiles for each ancestor to ensure proper rendering
|
|
||||||
pub fn process_shape_ancestors(
|
|
||||||
&mut self,
|
|
||||||
shape: &Shape,
|
|
||||||
tree: &mut ShapesPool,
|
|
||||||
modifiers: &HashMap<Uuid, Matrix>,
|
|
||||||
) {
|
|
||||||
let ancestors = shape.all_ancestors(tree, false);
|
|
||||||
self.invalidate_and_update_tiles(&ancestors, tree, modifiers);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Rebuilds tiles for shapes with modifiers and processes their ancestors
|
/// Rebuilds tiles for shapes with modifiers and processes their ancestors
|
||||||
///
|
///
|
||||||
/// This function applies transformation modifiers to shapes and updates their tiles.
|
/// This function applies transformation modifiers to shapes and updates their tiles.
|
||||||
/// Additionally, it processes all ancestors of modified shapes to ensure their
|
/// Additionally, it processes all ancestors of modified shapes to ensure their
|
||||||
/// extended rectangles are properly recalculated and their tiles are updated.
|
/// extended rectangles are properly recalculated and their tiles are updated.
|
||||||
/// This is crucial for frames and groups that contain transformed children.
|
/// This is crucial for frames and groups that contain transformed children.
|
||||||
pub fn rebuild_modifier_tiles(
|
pub fn rebuild_modifier_tiles(&mut self, tree: ShapesPoolMutRef<'_>, ids: Vec<Uuid>) {
|
||||||
&mut self,
|
let ancestors = all_with_ancestors(&ids, tree, false);
|
||||||
tree: &mut ShapesPool,
|
self.invalidate_and_update_tiles(&ancestors, tree);
|
||||||
modifiers: &HashMap<Uuid, Matrix>,
|
|
||||||
) {
|
|
||||||
let mut ancestors = IndexSet::new();
|
|
||||||
for (uuid, matrix) in modifiers {
|
|
||||||
let mut shape = {
|
|
||||||
let Some(shape) = tree.get(uuid) else {
|
|
||||||
panic!("Invalid current shape")
|
|
||||||
};
|
|
||||||
let shape: Cow<Shape> = Cow::Borrowed(shape);
|
|
||||||
shape
|
|
||||||
};
|
|
||||||
|
|
||||||
shape.to_mut().apply_transform(matrix);
|
|
||||||
ancestors.insert(*uuid);
|
|
||||||
ancestors.extend(shape.all_ancestors(tree, false));
|
|
||||||
}
|
|
||||||
self.invalidate_and_update_tiles(&ancestors, tree, modifiers);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_scale(&self) -> f32 {
|
pub fn get_scale(&self) -> f32 {
|
||||||
|
|||||||
@ -1,21 +1,12 @@
|
|||||||
use skia_safe::{self as skia};
|
use skia_safe::{self as skia};
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use crate::math::{Matrix, Rect};
|
use crate::math::Rect;
|
||||||
use crate::shapes::modifiers::grid_layout::grid_cell_data;
|
use crate::shapes::modifiers::grid_layout::grid_cell_data;
|
||||||
use crate::shapes::{Shape, StructureEntry};
|
use crate::shapes::Shape;
|
||||||
use crate::state::ShapesPool;
|
use crate::state::ShapesPoolRef;
|
||||||
use crate::uuid::Uuid;
|
|
||||||
|
|
||||||
pub fn render_overlay(
|
pub fn render_overlay(zoom: f32, canvas: &skia::Canvas, shape: &Shape, shapes: ShapesPoolRef) {
|
||||||
zoom: f32,
|
let cells = grid_cell_data(shape, shapes, true);
|
||||||
canvas: &skia::Canvas,
|
|
||||||
shape: &Shape,
|
|
||||||
shapes: &ShapesPool,
|
|
||||||
modifiers: &HashMap<Uuid, Matrix>,
|
|
||||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
|
||||||
) {
|
|
||||||
let cells = grid_cell_data(shape, shapes, modifiers, structure, true);
|
|
||||||
|
|
||||||
let mut paint = skia::Paint::default();
|
let mut paint = skia::Paint::default();
|
||||||
paint.set_style(skia::PaintStyle::Stroke);
|
paint.set_style(skia::PaintStyle::Stroke);
|
||||||
|
|||||||
@ -1,18 +1,9 @@
|
|||||||
use skia_safe::{self as skia, Color4f};
|
use skia_safe::{self as skia, Color4f};
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use super::{RenderState, ShapesPool, SurfaceId};
|
use super::{RenderState, ShapesPoolRef, SurfaceId};
|
||||||
use crate::math::Matrix;
|
|
||||||
use crate::render::grid_layout;
|
use crate::render::grid_layout;
|
||||||
use crate::shapes::StructureEntry;
|
|
||||||
use crate::uuid::Uuid;
|
|
||||||
|
|
||||||
pub fn render(
|
pub fn render(render_state: &mut RenderState, shapes: ShapesPoolRef) {
|
||||||
render_state: &mut RenderState,
|
|
||||||
shapes: &ShapesPool,
|
|
||||||
modifiers: &HashMap<Uuid, Matrix>,
|
|
||||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
|
||||||
) {
|
|
||||||
let canvas = render_state.surfaces.canvas(SurfaceId::UI);
|
let canvas = render_state.surfaces.canvas(SurfaceId::UI);
|
||||||
|
|
||||||
canvas.clear(Color4f::new(0.0, 0.0, 0.0, 0.0));
|
canvas.clear(Color4f::new(0.0, 0.0, 0.0, 0.0));
|
||||||
@ -29,7 +20,7 @@ pub fn render(
|
|||||||
|
|
||||||
if let Some(id) = render_state.show_grid {
|
if let Some(id) = render_state.show_grid {
|
||||||
if let Some(shape) = shapes.get(&id) {
|
if let Some(shape) = shapes.get(&id) {
|
||||||
grid_layout::render_overlay(zoom, canvas, shape, shapes, modifiers, structure);
|
grid_layout::render_overlay(zoom, canvas, shape, shapes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@ use skia_safe::{self as skia};
|
|||||||
use crate::uuid::Uuid;
|
use crate::uuid::Uuid;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::cell::OnceCell;
|
use std::cell::OnceCell;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::HashSet;
|
||||||
use std::iter::once;
|
use std::iter::once;
|
||||||
|
|
||||||
mod blend;
|
mod blend;
|
||||||
@ -47,10 +47,11 @@ pub use svgraw::*;
|
|||||||
pub use text::*;
|
pub use text::*;
|
||||||
pub use transform::*;
|
pub use transform::*;
|
||||||
|
|
||||||
|
use crate::math::bools as math_bools;
|
||||||
use crate::math::{self, Bounds, Matrix, Point};
|
use crate::math::{self, Bounds, Matrix, Point};
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
|
|
||||||
use crate::state::ShapesPool;
|
use crate::state::ShapesPoolRef;
|
||||||
|
|
||||||
const MIN_VISIBLE_SIZE: f32 = 2.0;
|
const MIN_VISIBLE_SIZE: f32 = 2.0;
|
||||||
const ANTIALIAS_THRESHOLD: f32 = 15.0;
|
const ANTIALIAS_THRESHOLD: f32 = 15.0;
|
||||||
@ -180,6 +181,62 @@ pub struct Shape {
|
|||||||
pub shadows: Vec<Shadow>,
|
pub shadows: Vec<Shadow>,
|
||||||
pub layout_item: Option<LayoutItem>,
|
pub layout_item: Option<LayoutItem>,
|
||||||
pub extrect: OnceCell<math::Rect>,
|
pub extrect: OnceCell<math::Rect>,
|
||||||
|
pub bounds: OnceCell<math::Bounds>,
|
||||||
|
pub svg_transform: Option<Matrix>,
|
||||||
|
pub ignore_constraints: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns all ancestor shapes of this shape, traversing up the parent hierarchy
|
||||||
|
//
|
||||||
|
// This function walks up the parent chain starting from this shape's parent,
|
||||||
|
// collecting all ancestor IDs. It stops when it reaches a nil UUID or when
|
||||||
|
// an ancestor is hidden (unless include_hidden is true).
|
||||||
|
//
|
||||||
|
// # Arguments
|
||||||
|
// * `shapes` - The shapes pool containing all shapes
|
||||||
|
// * `include_hidden` - Whether to include hidden ancestors in the result
|
||||||
|
//
|
||||||
|
// # Returns
|
||||||
|
// A set of ancestor UUIDs in traversal order (closest ancestor first)
|
||||||
|
pub fn all_with_ancestors(
|
||||||
|
shapes: &[Uuid],
|
||||||
|
shapes_pool: ShapesPoolRef,
|
||||||
|
include_hidden: bool,
|
||||||
|
) -> IndexSet<Uuid> {
|
||||||
|
let mut pending = Vec::from_iter(shapes.iter());
|
||||||
|
let mut result = IndexSet::new();
|
||||||
|
|
||||||
|
while !pending.is_empty() {
|
||||||
|
let Some(current_id) = pending.pop() else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
|
||||||
|
result.insert(*current_id);
|
||||||
|
|
||||||
|
let Some(parent_id) = shapes_pool.get(current_id).and_then(|s| s.parent_id) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
if parent_id == Uuid::nil() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.contains(&parent_id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the ancestor is hidden
|
||||||
|
let Some(parent) = shapes_pool.get(&parent_id) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
if !include_hidden && parent.hidden() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
pending.push(&parent.id);
|
||||||
|
}
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Shape {
|
impl Shape {
|
||||||
@ -207,36 +264,36 @@ impl Shape {
|
|||||||
shadows: Vec::with_capacity(1),
|
shadows: Vec::with_capacity(1),
|
||||||
layout_item: None,
|
layout_item: None,
|
||||||
extrect: OnceCell::new(),
|
extrect: OnceCell::new(),
|
||||||
|
bounds: OnceCell::new(),
|
||||||
|
svg_transform: None,
|
||||||
|
ignore_constraints: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn scale_content(&self, value: f32) -> Self {
|
pub fn scale_content(&mut self, value: f32) {
|
||||||
let mut result = self.clone();
|
self.ignore_constraints = true;
|
||||||
result.shape_type.scale_content(value);
|
self.shape_type.scale_content(value);
|
||||||
result
|
self.strokes.iter_mut().for_each(|s| s.scale_content(value));
|
||||||
.strokes
|
|
||||||
.iter_mut()
|
|
||||||
.for_each(|s| s.scale_content(value));
|
|
||||||
result
|
|
||||||
.shadows
|
|
||||||
.iter_mut()
|
|
||||||
.for_each(|s| s.scale_content(value));
|
|
||||||
|
|
||||||
if let Some(blur) = result.blur.as_mut() {
|
self.shadows.iter_mut().for_each(|s| s.scale_content(value));
|
||||||
|
|
||||||
|
if let Some(blur) = self.blur.as_mut() {
|
||||||
blur.scale_content(value);
|
blur.scale_content(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
result
|
self.layout_item
|
||||||
.layout_item
|
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.for_each(|i| i.scale_content(value));
|
.for_each(|i| i.scale_content(value));
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn invalidate_extrect(&mut self) {
|
pub fn invalidate_extrect(&mut self) {
|
||||||
self.extrect = OnceCell::new();
|
self.extrect = OnceCell::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn invalidate_bounds(&mut self) {
|
||||||
|
self.bounds = OnceCell::new();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_parent(&mut self, id: Uuid) {
|
pub fn set_parent(&mut self, id: Uuid) {
|
||||||
self.parent_id = Some(id);
|
self.parent_id = Some(id);
|
||||||
}
|
}
|
||||||
@ -250,6 +307,10 @@ impl Shape {
|
|||||||
matches!(self.shape_type, Type::Frame(_))
|
matches!(self.shape_type, Type::Frame(_))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_bool(&self) -> bool {
|
||||||
|
matches!(self.shape_type, Type::Bool(_))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_group_like(&self) -> bool {
|
pub fn is_group_like(&self) -> bool {
|
||||||
matches!(self.shape_type, Type::Group(_)) || matches!(self.shape_type, Type::Bool(_))
|
matches!(self.shape_type, Type::Group(_)) || matches!(self.shape_type, Type::Bool(_))
|
||||||
}
|
}
|
||||||
@ -266,6 +327,7 @@ impl Shape {
|
|||||||
|
|
||||||
pub fn set_selrect(&mut self, left: f32, top: f32, right: f32, bottom: f32) {
|
pub fn set_selrect(&mut self, left: f32, top: f32, right: f32, bottom: f32) {
|
||||||
self.invalidate_extrect();
|
self.invalidate_extrect();
|
||||||
|
self.invalidate_bounds();
|
||||||
self.selrect.set_ltrb(left, top, right, bottom);
|
self.selrect.set_ltrb(left, top, right, bottom);
|
||||||
if let Type::Text(ref mut text) = self.shape_type {
|
if let Type::Text(ref mut text) = self.shape_type {
|
||||||
text.update_layout(self.selrect);
|
text.update_layout(self.selrect);
|
||||||
@ -328,6 +390,10 @@ impl Shape {
|
|||||||
self.hidden = value;
|
self.hidden = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn svg_transform(&self) -> Option<Matrix> {
|
||||||
|
self.svg_transform
|
||||||
|
}
|
||||||
|
|
||||||
// FIXME: These arguments could be grouped or simplified
|
// FIXME: These arguments could be grouped or simplified
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn set_flex_layout_child_data(
|
pub fn set_flex_layout_child_data(
|
||||||
@ -617,13 +683,8 @@ impl Shape {
|
|||||||
self.selrect.width()
|
self.selrect.width()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn visually_insignificant(
|
pub fn visually_insignificant(&self, scale: f32, shapes_pool: ShapesPoolRef) -> bool {
|
||||||
&self,
|
let extrect = self.extrect(shapes_pool);
|
||||||
scale: f32,
|
|
||||||
shapes_pool: &ShapesPool,
|
|
||||||
modifiers: &HashMap<Uuid, Matrix>,
|
|
||||||
) -> bool {
|
|
||||||
let extrect = self.extrect(shapes_pool, modifiers);
|
|
||||||
extrect.width() * scale < MIN_VISIBLE_SIZE && extrect.height() * scale < MIN_VISIBLE_SIZE
|
extrect.width() * scale < MIN_VISIBLE_SIZE && extrect.height() * scale < MIN_VISIBLE_SIZE
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -632,8 +693,7 @@ impl Shape {
|
|||||||
|| self.selrect.height() * scale > ANTIALIAS_THRESHOLD
|
|| self.selrect.height() * scale > ANTIALIAS_THRESHOLD
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Maybe store this inside the shape
|
pub fn calculate_bounds(&self) -> Bounds {
|
||||||
pub fn bounds(&self) -> Bounds {
|
|
||||||
let mut bounds = Bounds::new(
|
let mut bounds = Bounds::new(
|
||||||
Point::new(self.selrect.x(), self.selrect.y()),
|
Point::new(self.selrect.x(), self.selrect.y()),
|
||||||
Point::new(self.selrect.x() + self.selrect.width(), self.selrect.y()),
|
Point::new(self.selrect.x() + self.selrect.width(), self.selrect.y()),
|
||||||
@ -659,18 +719,18 @@ impl Shape {
|
|||||||
bounds
|
bounds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn bounds(&self) -> Bounds {
|
||||||
|
*self.bounds.get_or_init(|| self.calculate_bounds())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn selrect(&self) -> math::Rect {
|
pub fn selrect(&self) -> math::Rect {
|
||||||
self.selrect
|
self.selrect
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extrect(
|
pub fn extrect(&self, shapes_pool: ShapesPoolRef) -> math::Rect {
|
||||||
&self,
|
|
||||||
shapes_pool: &ShapesPool,
|
|
||||||
modifiers: &HashMap<Uuid, Matrix>,
|
|
||||||
) -> math::Rect {
|
|
||||||
*self
|
*self
|
||||||
.extrect
|
.extrect
|
||||||
.get_or_init(|| self.calculate_extrect(shapes_pool, modifiers))
|
.get_or_init(|| self.calculate_extrect(shapes_pool))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_text_content(&self) -> &TextContent {
|
pub fn get_text_content(&self) -> &TextContent {
|
||||||
@ -783,8 +843,7 @@ impl Shape {
|
|||||||
fn apply_children_bounds(
|
fn apply_children_bounds(
|
||||||
&self,
|
&self,
|
||||||
mut rect: math::Rect,
|
mut rect: math::Rect,
|
||||||
shapes_pool: &ShapesPool,
|
shapes_pool: ShapesPoolRef,
|
||||||
modifiers: &HashMap<Uuid, Matrix>,
|
|
||||||
) -> math::Rect {
|
) -> math::Rect {
|
||||||
let include_children = match self.shape_type {
|
let include_children = match self.shape_type {
|
||||||
Type::Group(_) => true,
|
Type::Group(_) => true,
|
||||||
@ -795,15 +854,7 @@ impl Shape {
|
|||||||
if include_children {
|
if include_children {
|
||||||
for child_id in self.children_ids(false) {
|
for child_id in self.children_ids(false) {
|
||||||
if let Some(child_shape) = shapes_pool.get(&child_id) {
|
if let Some(child_shape) = shapes_pool.get(&child_id) {
|
||||||
// Create a copy of the child shape to apply any transformations
|
rect.join(child_shape.extrect(shapes_pool));
|
||||||
let mut transformed_element: Cow<Shape> = Cow::Borrowed(child_shape);
|
|
||||||
if let Some(modifier) = modifiers.get(&child_id) {
|
|
||||||
transformed_element.to_mut().apply_transform(modifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the child's extended rectangle and join it with the container's rectangle
|
|
||||||
let child_extrect = transformed_element.extrect(shapes_pool, modifiers);
|
|
||||||
rect.join(child_extrect);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -811,12 +862,8 @@ impl Shape {
|
|||||||
rect
|
rect
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn calculate_extrect(
|
pub fn calculate_extrect(&self, shapes_pool: ShapesPoolRef) -> math::Rect {
|
||||||
&self,
|
let shape = self;
|
||||||
shapes_pool: &ShapesPool,
|
|
||||||
modifiers: &HashMap<Uuid, Matrix>,
|
|
||||||
) -> math::Rect {
|
|
||||||
let shape = self.transformed(modifiers.get(&self.id));
|
|
||||||
let max_stroke = Stroke::max_bounds_width(shape.strokes.iter(), shape.is_open());
|
let max_stroke = Stroke::max_bounds_width(shape.strokes.iter(), shape.is_open());
|
||||||
|
|
||||||
let mut rect = match &shape.shape_type {
|
let mut rect = match &shape.shape_type {
|
||||||
@ -830,7 +877,7 @@ impl Shape {
|
|||||||
}
|
}
|
||||||
Type::Text(text_content) => {
|
Type::Text(text_content) => {
|
||||||
// FIXME: we need to recalculate the text bounds here because the shape's selrect
|
// FIXME: we need to recalculate the text bounds here because the shape's selrect
|
||||||
let text_bounds = text_content.calculate_bounds(&shape);
|
let text_bounds = text_content.calculate_bounds(shape);
|
||||||
text_bounds.to_rect()
|
text_bounds.to_rect()
|
||||||
}
|
}
|
||||||
_ => shape.bounds().to_rect(),
|
_ => shape.bounds().to_rect(),
|
||||||
@ -839,7 +886,7 @@ impl Shape {
|
|||||||
rect = self.apply_stroke_bounds(rect, max_stroke);
|
rect = self.apply_stroke_bounds(rect, max_stroke);
|
||||||
rect = self.apply_shadow_bounds(rect);
|
rect = self.apply_shadow_bounds(rect);
|
||||||
rect = self.apply_blur_bounds(rect);
|
rect = self.apply_blur_bounds(rect);
|
||||||
rect = self.apply_children_bounds(rect, shapes_pool, modifiers);
|
rect = self.apply_children_bounds(rect, shapes_pool);
|
||||||
|
|
||||||
rect
|
rect
|
||||||
}
|
}
|
||||||
@ -860,6 +907,7 @@ impl Shape {
|
|||||||
self.children.first()
|
self.children.first()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Review this to use children_ids_iter instead
|
||||||
pub fn children_ids(&self, include_hidden: bool) -> IndexSet<Uuid> {
|
pub fn children_ids(&self, include_hidden: bool) -> IndexSet<Uuid> {
|
||||||
if include_hidden {
|
if include_hidden {
|
||||||
return self.children.clone().into_iter().rev().collect();
|
return self.children.clone().into_iter().rev().collect();
|
||||||
@ -883,9 +931,27 @@ impl Shape {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn children_ids_iter(&self, include_hidden: bool) -> Box<dyn Iterator<Item = &Uuid> + '_> {
|
||||||
|
if include_hidden {
|
||||||
|
return Box::new(self.children.iter().rev());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Type::Bool(_) = self.shape_type {
|
||||||
|
Box::new([].iter())
|
||||||
|
} else if let Type::Group(group) = self.shape_type {
|
||||||
|
if group.masked {
|
||||||
|
Box::new(self.children.iter().rev().take(self.children.len() - 1))
|
||||||
|
} else {
|
||||||
|
Box::new(self.children.iter().rev())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Box::new(self.children.iter().rev())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn all_children(
|
pub fn all_children(
|
||||||
&self,
|
&self,
|
||||||
shapes: &ShapesPool,
|
shapes: ShapesPoolRef,
|
||||||
include_hidden: bool,
|
include_hidden: bool,
|
||||||
include_self: bool,
|
include_self: bool,
|
||||||
) -> IndexSet<Uuid> {
|
) -> IndexSet<Uuid> {
|
||||||
@ -906,47 +972,6 @@ impl Shape {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns all ancestor shapes of this shape, traversing up the parent hierarchy
|
|
||||||
///
|
|
||||||
/// This function walks up the parent chain starting from this shape's parent,
|
|
||||||
/// collecting all ancestor IDs. It stops when it reaches a nil UUID or when
|
|
||||||
/// an ancestor is hidden (unless include_hidden is true).
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
/// * `shapes` - The shapes pool containing all shapes
|
|
||||||
/// * `include_hidden` - Whether to include hidden ancestors in the result
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
/// A set of ancestor UUIDs in traversal order (closest ancestor first)
|
|
||||||
pub fn all_ancestors(&self, shapes: &ShapesPool, include_hidden: bool) -> IndexSet<Uuid> {
|
|
||||||
let mut ancestors = IndexSet::new();
|
|
||||||
let mut current_id = self.id;
|
|
||||||
|
|
||||||
// Traverse upwards using parent_id
|
|
||||||
while let Some(parent_id) = shapes.get(¤t_id).and_then(|s| s.parent_id) {
|
|
||||||
// If the parent_id is the zero UUID, there are no more ancestors
|
|
||||||
if parent_id == Uuid::nil() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the ancestor is hidden
|
|
||||||
if let Some(parent) = shapes.get(&parent_id) {
|
|
||||||
if !include_hidden && parent.hidden() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
ancestors.insert(parent_id);
|
|
||||||
current_id = parent_id;
|
|
||||||
} else {
|
|
||||||
// FIXME: This should panic! I've removed it temporarily until
|
|
||||||
// we fix the problems with shapes without parents.
|
|
||||||
// panic!("Parent can't be found");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ancestors
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_matrix(&self) -> Matrix {
|
pub fn get_matrix(&self) -> Matrix {
|
||||||
let mut matrix = Matrix::new_identity();
|
let mut matrix = Matrix::new_identity();
|
||||||
matrix.post_translate(self.left_top());
|
matrix.post_translate(self.left_top());
|
||||||
@ -954,7 +979,7 @@ impl Shape {
|
|||||||
matrix
|
matrix
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_concatenated_matrix(&self, shapes: &ShapesPool) -> Matrix {
|
pub fn get_concatenated_matrix(&self, shapes: ShapesPoolRef) -> Matrix {
|
||||||
let mut matrix = Matrix::new_identity();
|
let mut matrix = Matrix::new_identity();
|
||||||
let mut current_id = self.id;
|
let mut current_id = self.id;
|
||||||
while let Some(parent_id) = shapes.get(¤t_id).and_then(|s| s.parent_id) {
|
while let Some(parent_id) = shapes.get(¤t_id).and_then(|s| s.parent_id) {
|
||||||
@ -1124,22 +1149,62 @@ impl Shape {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply_transform(&mut self, transform: &Matrix) {
|
pub fn apply_transform(&mut self, transform: &Matrix) {
|
||||||
self.invalidate_extrect();
|
|
||||||
self.transform_selrect(transform);
|
self.transform_selrect(transform);
|
||||||
|
|
||||||
|
// TODO: See if we can change this invalidation to a transformation
|
||||||
|
self.invalidate_extrect();
|
||||||
|
self.invalidate_bounds();
|
||||||
|
|
||||||
if let shape_type @ (Type::Path(_) | Type::Bool(_)) = &mut self.shape_type {
|
if let shape_type @ (Type::Path(_) | Type::Bool(_)) = &mut self.shape_type {
|
||||||
if let Some(path) = shape_type.path_mut() {
|
if let Some(path) = shape_type.path_mut() {
|
||||||
path.transform(transform);
|
path.transform(transform);
|
||||||
}
|
}
|
||||||
} else if let Type::Text(text) = &mut self.shape_type {
|
} else if let Type::Text(text) = &mut self.shape_type {
|
||||||
text.transform(transform);
|
text.transform(transform);
|
||||||
|
} else if let Type::SVGRaw(_) = &mut self.shape_type {
|
||||||
|
self.svg_transform = Some(*transform);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn transformed(&self, transform: Option<&Matrix>) -> Self {
|
pub fn apply_structure(&mut self, structure: &Vec<StructureEntry>) {
|
||||||
|
let mut result: Vec<Uuid> = Vec::from_iter(self.children.iter().copied());
|
||||||
|
let mut to_remove = HashSet::<&Uuid>::new();
|
||||||
|
|
||||||
|
for st in structure {
|
||||||
|
match st.entry_type {
|
||||||
|
StructureEntryType::AddChild => {
|
||||||
|
result.insert(st.index as usize, st.id);
|
||||||
|
}
|
||||||
|
StructureEntryType::RemoveChild => {
|
||||||
|
to_remove.insert(&st.id);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.children = result
|
||||||
|
.iter()
|
||||||
|
.filter(|id| !to_remove.contains(id))
|
||||||
|
.copied()
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transformed(
|
||||||
|
&self,
|
||||||
|
shapes_pool: ShapesPoolRef,
|
||||||
|
transform: Option<&Matrix>,
|
||||||
|
structure: Option<&Vec<StructureEntry>>,
|
||||||
|
) -> Self {
|
||||||
let mut shape: Cow<Shape> = Cow::Borrowed(self);
|
let mut shape: Cow<Shape> = Cow::Borrowed(self);
|
||||||
if let Some(transform) = transform {
|
if let Some(transform) = transform {
|
||||||
shape.to_mut().apply_transform(transform);
|
shape.to_mut().apply_transform(transform);
|
||||||
}
|
}
|
||||||
|
if let Some(structure) = structure {
|
||||||
|
shape.to_mut().apply_structure(structure);
|
||||||
|
}
|
||||||
|
if self.is_bool() {
|
||||||
|
math_bools::update_bool_to_path(shape.to_mut(), shapes_pool)
|
||||||
|
}
|
||||||
shape.into_owned()
|
shape.into_owned()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1195,43 +1260,6 @@ impl Shape {
|
|||||||
.count()
|
.count()
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
Returns the list of children taking into account the structure modifiers
|
|
||||||
*/
|
|
||||||
pub fn modified_children_ids(
|
|
||||||
&self,
|
|
||||||
structure: Option<&Vec<StructureEntry>>,
|
|
||||||
include_hidden: bool,
|
|
||||||
) -> IndexSet<Uuid> {
|
|
||||||
if let Some(structure) = structure {
|
|
||||||
let mut result: Vec<Uuid> =
|
|
||||||
Vec::from_iter(self.children_ids(include_hidden).iter().copied());
|
|
||||||
let mut to_remove = HashSet::<&Uuid>::new();
|
|
||||||
|
|
||||||
for st in structure {
|
|
||||||
match st.entry_type {
|
|
||||||
StructureEntryType::AddChild => {
|
|
||||||
result.insert(result.len() - st.index as usize, st.id);
|
|
||||||
}
|
|
||||||
StructureEntryType::RemoveChild => {
|
|
||||||
to_remove.insert(&st.id);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let ret: IndexSet<Uuid> = result
|
|
||||||
.iter()
|
|
||||||
.filter(|id| !to_remove.contains(id))
|
|
||||||
.copied()
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
ret
|
|
||||||
} else {
|
|
||||||
self.children_ids(include_hidden)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn drop_shadow_paints(&self) -> Vec<skia_safe::Paint> {
|
pub fn drop_shadow_paints(&self) -> Vec<skia_safe::Paint> {
|
||||||
let drop_shadows: Vec<&Shadow> = self.drop_shadows_visible().collect();
|
let drop_shadows: Vec<&Shadow> = self.drop_shadows_visible().collect();
|
||||||
|
|
||||||
|
|||||||
@ -10,24 +10,21 @@ use crate::math::{self as math, bools, identitish, Bounds, Matrix, Point};
|
|||||||
use common::GetBounds;
|
use common::GetBounds;
|
||||||
|
|
||||||
use crate::shapes::{
|
use crate::shapes::{
|
||||||
ConstraintH, ConstraintV, Frame, Group, GrowType, Layout, Modifier, Shape, StructureEntry,
|
ConstraintH, ConstraintV, Frame, Group, GrowType, Layout, Modifier, Shape, TransformEntry, Type,
|
||||||
TransformEntry, Type,
|
|
||||||
};
|
};
|
||||||
use crate::state::{ShapesPool, State};
|
use crate::state::{ShapesPoolRef, State};
|
||||||
use crate::uuid::Uuid;
|
use crate::uuid::Uuid;
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn propagate_children(
|
fn propagate_children(
|
||||||
shape: &Shape,
|
shape: &Shape,
|
||||||
shapes: &ShapesPool,
|
shapes: ShapesPoolRef,
|
||||||
parent_bounds_before: &Bounds,
|
parent_bounds_before: &Bounds,
|
||||||
parent_bounds_after: &Bounds,
|
parent_bounds_after: &Bounds,
|
||||||
transform: Matrix,
|
transform: Matrix,
|
||||||
bounds: &HashMap<Uuid, Bounds>,
|
bounds: &HashMap<Uuid, Bounds>,
|
||||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
|
||||||
scale_content: &HashMap<Uuid, f32>,
|
|
||||||
) -> VecDeque<Modifier> {
|
) -> VecDeque<Modifier> {
|
||||||
let children_ids = shape.modified_children_ids(structure.get(&shape.id), true);
|
let children_ids = shape.children_ids(true);
|
||||||
|
|
||||||
if children_ids.is_empty() || identitish(&transform) {
|
if children_ids.is_empty() || identitish(&transform) {
|
||||||
return VecDeque::new();
|
return VecDeque::new();
|
||||||
@ -40,8 +37,6 @@ fn propagate_children(
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let ignore_constraints = scale_content.contains_key(child_id);
|
|
||||||
|
|
||||||
let child_bounds = bounds.find(child);
|
let child_bounds = bounds.find(child);
|
||||||
|
|
||||||
let constraint_h = match &shape.shape_type {
|
let constraint_h = match &shape.shape_type {
|
||||||
@ -79,7 +74,7 @@ fn propagate_children(
|
|||||||
constraint_h,
|
constraint_h,
|
||||||
constraint_v,
|
constraint_v,
|
||||||
transform,
|
transform,
|
||||||
ignore_constraints,
|
child.ignore_constraints,
|
||||||
);
|
);
|
||||||
|
|
||||||
result.push_back(Modifier::transform(*child_id, transform));
|
result.push_back(Modifier::transform(*child_id, transform));
|
||||||
@ -88,18 +83,15 @@ fn propagate_children(
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: PERFORMANCE
|
|
||||||
fn calculate_group_bounds(
|
fn calculate_group_bounds(
|
||||||
shape: &Shape,
|
shape: &Shape,
|
||||||
shapes: &ShapesPool,
|
shapes: ShapesPoolRef,
|
||||||
bounds: &HashMap<Uuid, Bounds>,
|
bounds: &HashMap<Uuid, Bounds>,
|
||||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
|
||||||
) -> Option<Bounds> {
|
) -> Option<Bounds> {
|
||||||
let shape_bounds = bounds.find(shape);
|
let shape_bounds = bounds.find(shape);
|
||||||
let mut result = Vec::<Point>::new();
|
let mut result = Vec::<Point>::new();
|
||||||
|
|
||||||
let children_ids = shape.modified_children_ids(structure.get(&shape.id), true);
|
for child_id in shape.children_ids_iter(true) {
|
||||||
for child_id in children_ids.iter() {
|
|
||||||
let Some(child) = shapes.get(child_id) else {
|
let Some(child) = shapes.get(child_id) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
@ -107,33 +99,29 @@ fn calculate_group_bounds(
|
|||||||
let child_bounds = bounds.find(child);
|
let child_bounds = bounds.find(child);
|
||||||
result.append(&mut child_bounds.points());
|
result.append(&mut child_bounds.points());
|
||||||
}
|
}
|
||||||
|
|
||||||
shape_bounds.with_points(result)
|
shape_bounds.with_points(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn calculate_bool_bounds(
|
fn calculate_bool_bounds(
|
||||||
shape: &Shape,
|
shape: &Shape,
|
||||||
shapes: &ShapesPool,
|
shapes: ShapesPoolRef,
|
||||||
bounds: &HashMap<Uuid, Bounds>,
|
bounds: &HashMap<Uuid, Bounds>,
|
||||||
modifiers: &HashMap<Uuid, Matrix>,
|
modifiers: &HashMap<Uuid, Matrix>,
|
||||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
|
||||||
) -> Option<Bounds> {
|
) -> Option<Bounds> {
|
||||||
let shape_bounds = bounds.find(shape);
|
let shape_bounds = bounds.find(shape);
|
||||||
let children_ids = shape.modified_children_ids(structure.get(&shape.id), true);
|
let children_ids = shape.children_ids(true);
|
||||||
|
|
||||||
let Type::Bool(bool_data) = &shape.shape_type else {
|
let Type::Bool(bool_data) = &shape.shape_type else {
|
||||||
return Some(shape_bounds);
|
return Some(shape_bounds);
|
||||||
};
|
};
|
||||||
|
|
||||||
let path = bools::bool_from_shapes(
|
let mut subtree = shapes.subtree(&shape.id);
|
||||||
bool_data.bool_type,
|
subtree.set_modifiers(modifiers.clone());
|
||||||
&children_ids,
|
|
||||||
shapes,
|
|
||||||
modifiers,
|
|
||||||
structure,
|
|
||||||
);
|
|
||||||
|
|
||||||
Some(path.bounds())
|
let path = bools::bool_from_shapes(bool_data.bool_type, &children_ids, &subtree);
|
||||||
|
let result = path.bounds();
|
||||||
|
|
||||||
|
Some(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_pixel_precision(transform: &mut Matrix, bounds: &mut Bounds) {
|
fn set_pixel_precision(transform: &mut Matrix, bounds: &mut Bounds) {
|
||||||
@ -238,8 +226,6 @@ fn propagate_transform(
|
|||||||
&shape_bounds_after,
|
&shape_bounds_after,
|
||||||
transform,
|
transform,
|
||||||
bounds,
|
bounds,
|
||||||
&state.structure,
|
|
||||||
&state.scale_content,
|
|
||||||
);
|
);
|
||||||
entries.append(&mut children);
|
entries.append(&mut children);
|
||||||
}
|
}
|
||||||
@ -277,55 +263,56 @@ fn propagate_reflow(
|
|||||||
let shapes = &state.shapes;
|
let shapes = &state.shapes;
|
||||||
let mut reflow_parent = false;
|
let mut reflow_parent = false;
|
||||||
|
|
||||||
|
if reflown.contains(id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
match &shape.shape_type {
|
match &shape.shape_type {
|
||||||
Type::Frame(Frame {
|
Type::Frame(Frame {
|
||||||
layout: Some(_), ..
|
layout: Some(_), ..
|
||||||
}) => {
|
}) => {
|
||||||
if !reflown.contains(id) {
|
let mut skip_reflow = false;
|
||||||
let mut skip_reflow = false;
|
if shape.is_layout_horizontal_fill() || shape.is_layout_vertical_fill() {
|
||||||
if shape.is_layout_horizontal_fill() || shape.is_layout_vertical_fill() {
|
if let Some(parent_id) = shape.parent_id {
|
||||||
if let Some(parent_id) = shape.parent_id {
|
if !reflown.contains(&parent_id) {
|
||||||
if !reflown.contains(&parent_id) {
|
// If this is a fill layout but the parent has not been reflown yet
|
||||||
// If this is a fill layout but the parent has not been reflown yet
|
// we wait for the next iteration for reflow
|
||||||
// we wait for the next iteration for reflow
|
skip_reflow = true;
|
||||||
skip_reflow = true;
|
reflow_parent = true;
|
||||||
reflow_parent = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if shape.is_layout_vertical_auto() || shape.is_layout_horizontal_auto() {
|
if shape.is_layout_vertical_auto() || shape.is_layout_horizontal_auto() {
|
||||||
reflow_parent = true;
|
reflow_parent = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !skip_reflow {
|
if !skip_reflow {
|
||||||
layout_reflows.push(*id);
|
layout_reflows.push(*id);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Type::Group(Group { masked: true }) => {
|
Type::Group(Group { masked: true }) => {
|
||||||
let children_ids = shape.modified_children_ids(state.structure.get(&shape.id), true);
|
let children_ids = shape.children_ids(true);
|
||||||
if let Some(child) = shapes.get(&children_ids[0]) {
|
if let Some(child) = shapes.get(&children_ids[0]) {
|
||||||
let child_bounds = bounds.find(child);
|
let child_bounds = bounds.find(child);
|
||||||
bounds.insert(shape.id, child_bounds);
|
bounds.insert(shape.id, child_bounds);
|
||||||
reflow_parent = true;
|
reflow_parent = true;
|
||||||
}
|
}
|
||||||
|
reflown.insert(*id);
|
||||||
}
|
}
|
||||||
Type::Group(_) => {
|
Type::Group(_) => {
|
||||||
if let Some(shape_bounds) =
|
if let Some(shape_bounds) = calculate_group_bounds(shape, shapes, bounds) {
|
||||||
calculate_group_bounds(shape, shapes, bounds, &state.structure)
|
|
||||||
{
|
|
||||||
bounds.insert(shape.id, shape_bounds);
|
bounds.insert(shape.id, shape_bounds);
|
||||||
reflow_parent = true;
|
reflow_parent = true;
|
||||||
}
|
}
|
||||||
|
reflown.insert(*id);
|
||||||
}
|
}
|
||||||
Type::Bool(_) => {
|
Type::Bool(_) => {
|
||||||
if let Some(shape_bounds) =
|
if let Some(shape_bounds) = calculate_bool_bounds(shape, shapes, bounds, modifiers) {
|
||||||
calculate_bool_bounds(shape, shapes, bounds, modifiers, &state.structure)
|
|
||||||
{
|
|
||||||
bounds.insert(shape.id, shape_bounds);
|
bounds.insert(shape.id, shape_bounds);
|
||||||
reflow_parent = true;
|
reflow_parent = true;
|
||||||
}
|
}
|
||||||
|
reflown.insert(*id);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// Other shapes don't have to be reflown
|
// Other shapes don't have to be reflown
|
||||||
@ -352,35 +339,17 @@ fn reflow_shape(
|
|||||||
|
|
||||||
let shapes = &state.shapes;
|
let shapes = &state.shapes;
|
||||||
|
|
||||||
let shape = if let Some(scale_content) = state.scale_content.get(id) {
|
|
||||||
&shape.scale_content(*scale_content)
|
|
||||||
} else {
|
|
||||||
shape
|
|
||||||
};
|
|
||||||
|
|
||||||
let Type::Frame(frame_data) = &shape.shape_type else {
|
let Type::Frame(frame_data) = &shape.shape_type else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(Layout::FlexLayout(layout_data, flex_data)) = &frame_data.layout {
|
if let Some(Layout::FlexLayout(layout_data, flex_data)) = &frame_data.layout {
|
||||||
let mut children = flex_layout::reflow_flex_layout(
|
let mut children =
|
||||||
shape,
|
flex_layout::reflow_flex_layout(shape, layout_data, flex_data, shapes, bounds);
|
||||||
layout_data,
|
|
||||||
flex_data,
|
|
||||||
shapes,
|
|
||||||
bounds,
|
|
||||||
&state.structure,
|
|
||||||
);
|
|
||||||
entries.append(&mut children);
|
entries.append(&mut children);
|
||||||
} else if let Some(Layout::GridLayout(layout_data, grid_data)) = &frame_data.layout {
|
} else if let Some(Layout::GridLayout(layout_data, grid_data)) = &frame_data.layout {
|
||||||
let mut children = grid_layout::reflow_grid_layout(
|
let mut children =
|
||||||
shape,
|
grid_layout::reflow_grid_layout(shape, layout_data, grid_data, shapes, bounds);
|
||||||
layout_data,
|
|
||||||
grid_data,
|
|
||||||
shapes,
|
|
||||||
bounds,
|
|
||||||
&state.structure,
|
|
||||||
);
|
|
||||||
entries.append(&mut children);
|
entries.append(&mut children);
|
||||||
}
|
}
|
||||||
reflown.insert(*id);
|
reflown.insert(*id);
|
||||||
@ -396,12 +365,6 @@ pub fn propagate_modifiers(
|
|||||||
.map(|entry| Modifier::Transform(entry.clone()))
|
.map(|entry| Modifier::Transform(entry.clone()))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
for id in state.structure.keys() {
|
|
||||||
if id != &Uuid::nil() {
|
|
||||||
entries.push_back(Modifier::Reflow(*id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut modifiers = HashMap::<Uuid, Matrix>::new();
|
let mut modifiers = HashMap::<Uuid, Matrix>::new();
|
||||||
let mut bounds = HashMap::<Uuid, Bounds>::new();
|
let mut bounds = HashMap::<Uuid, Bounds>::new();
|
||||||
let mut reflown = HashSet::<Uuid>::new();
|
let mut reflown = HashSet::<Uuid>::new();
|
||||||
@ -456,6 +419,7 @@ mod tests {
|
|||||||
|
|
||||||
use crate::math::{Matrix, Point};
|
use crate::math::{Matrix, Point};
|
||||||
use crate::shapes::*;
|
use crate::shapes::*;
|
||||||
|
use crate::state::ShapesPool;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_propagate_shape() {
|
fn test_propagate_shape() {
|
||||||
@ -494,8 +458,6 @@ mod tests {
|
|||||||
&bounds_after,
|
&bounds_after,
|
||||||
transform,
|
transform,
|
||||||
&HashMap::new(),
|
&HashMap::new(),
|
||||||
&HashMap::new(),
|
|
||||||
&HashMap::new(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(result.len(), 1);
|
assert_eq!(result.len(), 1);
|
||||||
@ -526,8 +488,7 @@ mod tests {
|
|||||||
|
|
||||||
let parent = shapes.get(&parent_id).unwrap();
|
let parent = shapes.get(&parent_id).unwrap();
|
||||||
|
|
||||||
let bounds =
|
let bounds = calculate_group_bounds(parent, &shapes, &HashMap::new()).unwrap();
|
||||||
calculate_group_bounds(parent, &shapes, &HashMap::new(), &HashMap::new()).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(bounds.width(), 3.0);
|
assert_eq!(bounds.width(), 3.0);
|
||||||
assert_eq!(bounds.height(), 3.0);
|
assert_eq!(bounds.height(), 3.0);
|
||||||
|
|||||||
@ -2,9 +2,9 @@
|
|||||||
use crate::math::{self as math, Bounds, Matrix, Point, Vector, VectorExt};
|
use crate::math::{self as math, Bounds, Matrix, Point, Vector, VectorExt};
|
||||||
use crate::shapes::{
|
use crate::shapes::{
|
||||||
AlignContent, AlignItems, AlignSelf, FlexData, JustifyContent, LayoutData, LayoutItem,
|
AlignContent, AlignItems, AlignSelf, FlexData, JustifyContent, LayoutData, LayoutItem,
|
||||||
Modifier, Shape, StructureEntry,
|
Modifier, Shape,
|
||||||
};
|
};
|
||||||
use crate::state::ShapesPool;
|
use crate::state::ShapesPoolRef;
|
||||||
use crate::uuid::Uuid;
|
use crate::uuid::Uuid;
|
||||||
|
|
||||||
use std::collections::{HashMap, VecDeque};
|
use std::collections::{HashMap, VecDeque};
|
||||||
@ -179,13 +179,12 @@ fn initialize_tracks(
|
|||||||
layout_bounds: &Bounds,
|
layout_bounds: &Bounds,
|
||||||
layout_axis: &LayoutAxis,
|
layout_axis: &LayoutAxis,
|
||||||
flex_data: &FlexData,
|
flex_data: &FlexData,
|
||||||
shapes: &ShapesPool,
|
shapes: ShapesPoolRef,
|
||||||
bounds: &HashMap<Uuid, Bounds>,
|
bounds: &HashMap<Uuid, Bounds>,
|
||||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
|
||||||
) -> Vec<TrackData> {
|
) -> Vec<TrackData> {
|
||||||
let mut tracks = Vec::<TrackData>::new();
|
let mut tracks = Vec::<TrackData>::new();
|
||||||
let mut current_track = TrackData::default();
|
let mut current_track = TrackData::default();
|
||||||
let mut children = shape.modified_children_ids(structure.get(&shape.id), true);
|
let mut children = shape.children_ids(true);
|
||||||
let mut first = true;
|
let mut first = true;
|
||||||
|
|
||||||
if flex_data.is_reverse() {
|
if flex_data.is_reverse() {
|
||||||
@ -433,9 +432,8 @@ fn calculate_track_data(
|
|||||||
layout_data: &LayoutData,
|
layout_data: &LayoutData,
|
||||||
flex_data: &FlexData,
|
flex_data: &FlexData,
|
||||||
layout_bounds: &Bounds,
|
layout_bounds: &Bounds,
|
||||||
shapes: &ShapesPool,
|
shapes: ShapesPoolRef,
|
||||||
bounds: &HashMap<Uuid, Bounds>,
|
bounds: &HashMap<Uuid, Bounds>,
|
||||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
|
||||||
) -> Vec<TrackData> {
|
) -> Vec<TrackData> {
|
||||||
let layout_axis = LayoutAxis::new(shape, layout_bounds, layout_data, flex_data);
|
let layout_axis = LayoutAxis::new(shape, layout_bounds, layout_data, flex_data);
|
||||||
let mut tracks = initialize_tracks(
|
let mut tracks = initialize_tracks(
|
||||||
@ -445,7 +443,6 @@ fn calculate_track_data(
|
|||||||
flex_data,
|
flex_data,
|
||||||
shapes,
|
shapes,
|
||||||
bounds,
|
bounds,
|
||||||
structure,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
distribute_fill_main_space(&layout_axis, &mut tracks);
|
distribute_fill_main_space(&layout_axis, &mut tracks);
|
||||||
@ -574,22 +571,13 @@ pub fn reflow_flex_layout(
|
|||||||
shape: &Shape,
|
shape: &Shape,
|
||||||
layout_data: &LayoutData,
|
layout_data: &LayoutData,
|
||||||
flex_data: &FlexData,
|
flex_data: &FlexData,
|
||||||
shapes: &ShapesPool,
|
shapes: ShapesPoolRef,
|
||||||
bounds: &mut HashMap<Uuid, Bounds>,
|
bounds: &mut HashMap<Uuid, Bounds>,
|
||||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
|
||||||
) -> VecDeque<Modifier> {
|
) -> VecDeque<Modifier> {
|
||||||
let mut result = VecDeque::new();
|
let mut result = VecDeque::new();
|
||||||
let layout_bounds = &bounds.find(shape);
|
let layout_bounds = &bounds.find(shape);
|
||||||
let layout_axis = LayoutAxis::new(shape, layout_bounds, layout_data, flex_data);
|
let layout_axis = LayoutAxis::new(shape, layout_bounds, layout_data, flex_data);
|
||||||
let tracks = calculate_track_data(
|
let tracks = calculate_track_data(shape, layout_data, flex_data, layout_bounds, shapes, bounds);
|
||||||
shape,
|
|
||||||
layout_data,
|
|
||||||
flex_data,
|
|
||||||
layout_bounds,
|
|
||||||
shapes,
|
|
||||||
bounds,
|
|
||||||
structure,
|
|
||||||
);
|
|
||||||
|
|
||||||
for track in tracks.iter() {
|
for track in tracks.iter() {
|
||||||
let total_shapes_size = track.shapes.iter().map(|s| s.main_size).sum::<f32>();
|
let total_shapes_size = track.shapes.iter().map(|s| s.main_size).sum::<f32>();
|
||||||
|
|||||||
@ -2,9 +2,9 @@ use crate::math::{self as math, intersect_rays, Bounds, Matrix, Point, Ray, Vect
|
|||||||
use crate::shapes::{
|
use crate::shapes::{
|
||||||
AlignContent, AlignItems, AlignSelf, Frame, GridCell, GridData, GridTrack, GridTrackType,
|
AlignContent, AlignItems, AlignSelf, Frame, GridCell, GridData, GridTrack, GridTrackType,
|
||||||
JustifyContent, JustifyItems, JustifySelf, Layout, LayoutData, LayoutItem, Modifier, Shape,
|
JustifyContent, JustifyItems, JustifySelf, Layout, LayoutData, LayoutItem, Modifier, Shape,
|
||||||
StructureEntry, Type,
|
Type,
|
||||||
};
|
};
|
||||||
use crate::state::ShapesPool;
|
use crate::state::ShapesPoolRef;
|
||||||
use crate::uuid::Uuid;
|
use crate::uuid::Uuid;
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
use std::collections::{HashMap, VecDeque};
|
use std::collections::{HashMap, VecDeque};
|
||||||
@ -45,7 +45,7 @@ pub fn calculate_tracks(
|
|||||||
grid_data: &GridData,
|
grid_data: &GridData,
|
||||||
layout_bounds: &Bounds,
|
layout_bounds: &Bounds,
|
||||||
cells: &Vec<GridCell>,
|
cells: &Vec<GridCell>,
|
||||||
shapes: &ShapesPool,
|
shapes: ShapesPoolRef,
|
||||||
bounds: &HashMap<Uuid, Bounds>,
|
bounds: &HashMap<Uuid, Bounds>,
|
||||||
) -> Vec<TrackData> {
|
) -> Vec<TrackData> {
|
||||||
let layout_size = if is_column {
|
let layout_size = if is_column {
|
||||||
@ -122,7 +122,7 @@ fn set_auto_base_size(
|
|||||||
column: bool,
|
column: bool,
|
||||||
tracks: &mut [TrackData],
|
tracks: &mut [TrackData],
|
||||||
cells: &Vec<GridCell>,
|
cells: &Vec<GridCell>,
|
||||||
shapes: &ShapesPool,
|
shapes: ShapesPoolRef,
|
||||||
bounds: &HashMap<Uuid, Bounds>,
|
bounds: &HashMap<Uuid, Bounds>,
|
||||||
) {
|
) {
|
||||||
for cell in cells {
|
for cell in cells {
|
||||||
@ -173,7 +173,7 @@ fn set_auto_multi_span(
|
|||||||
column: bool,
|
column: bool,
|
||||||
tracks: &mut [TrackData],
|
tracks: &mut [TrackData],
|
||||||
cells: &[GridCell],
|
cells: &[GridCell],
|
||||||
shapes: &ShapesPool,
|
shapes: ShapesPoolRef,
|
||||||
bounds: &HashMap<Uuid, Bounds>,
|
bounds: &HashMap<Uuid, Bounds>,
|
||||||
) {
|
) {
|
||||||
// Remove groups with flex (will be set in flex_multi_span)
|
// Remove groups with flex (will be set in flex_multi_span)
|
||||||
@ -248,7 +248,7 @@ fn set_flex_multi_span(
|
|||||||
layout_data: &LayoutData,
|
layout_data: &LayoutData,
|
||||||
tracks: &mut [TrackData],
|
tracks: &mut [TrackData],
|
||||||
cells: &[GridCell],
|
cells: &[GridCell],
|
||||||
shapes: &ShapesPool,
|
shapes: ShapesPoolRef,
|
||||||
bounds: &HashMap<Uuid, Bounds>,
|
bounds: &HashMap<Uuid, Bounds>,
|
||||||
) {
|
) {
|
||||||
// Remove groups without flex
|
// Remove groups without flex
|
||||||
@ -539,7 +539,7 @@ fn cell_bounds(
|
|||||||
pub fn create_cell_data<'a>(
|
pub fn create_cell_data<'a>(
|
||||||
layout_bounds: &Bounds,
|
layout_bounds: &Bounds,
|
||||||
children: &IndexSet<Uuid>,
|
children: &IndexSet<Uuid>,
|
||||||
shapes: &'a ShapesPool,
|
shapes: ShapesPoolRef<'a>,
|
||||||
cells: &Vec<GridCell>,
|
cells: &Vec<GridCell>,
|
||||||
column_tracks: &[TrackData],
|
column_tracks: &[TrackData],
|
||||||
row_tracks: &[TrackData],
|
row_tracks: &[TrackData],
|
||||||
@ -602,9 +602,7 @@ pub fn create_cell_data<'a>(
|
|||||||
|
|
||||||
pub fn grid_cell_data<'a>(
|
pub fn grid_cell_data<'a>(
|
||||||
shape: &Shape,
|
shape: &Shape,
|
||||||
shapes: &'a ShapesPool,
|
shapes: ShapesPoolRef<'a>,
|
||||||
modifiers: &HashMap<Uuid, Matrix>,
|
|
||||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
|
||||||
allow_empty: bool,
|
allow_empty: bool,
|
||||||
) -> Vec<CellData<'a>> {
|
) -> Vec<CellData<'a>> {
|
||||||
let Type::Frame(Frame {
|
let Type::Frame(Frame {
|
||||||
@ -616,26 +614,8 @@ pub fn grid_cell_data<'a>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let bounds = &mut HashMap::<Uuid, Bounds>::new();
|
let bounds = &mut HashMap::<Uuid, Bounds>::new();
|
||||||
|
|
||||||
let shape = &mut shape.clone();
|
|
||||||
if let Some(modifiers) = modifiers.get(&shape.id) {
|
|
||||||
shape.apply_transform(modifiers);
|
|
||||||
}
|
|
||||||
|
|
||||||
let layout_bounds = shape.bounds();
|
let layout_bounds = shape.bounds();
|
||||||
let children = shape.modified_children_ids(structure.get(&shape.id), false);
|
let children = shape.children_ids(false);
|
||||||
|
|
||||||
for child_id in children.iter() {
|
|
||||||
let Some(child) = shapes.get(child_id) else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(modifier) = modifiers.get(child_id) {
|
|
||||||
let mut b = bounds.find(child);
|
|
||||||
b.transform_mut(modifier);
|
|
||||||
bounds.insert(*child_id, b);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let column_tracks = calculate_tracks(
|
let column_tracks = calculate_tracks(
|
||||||
true,
|
true,
|
||||||
@ -723,13 +703,12 @@ pub fn reflow_grid_layout(
|
|||||||
shape: &Shape,
|
shape: &Shape,
|
||||||
layout_data: &LayoutData,
|
layout_data: &LayoutData,
|
||||||
grid_data: &GridData,
|
grid_data: &GridData,
|
||||||
shapes: &ShapesPool,
|
shapes: ShapesPoolRef,
|
||||||
bounds: &mut HashMap<Uuid, Bounds>,
|
bounds: &mut HashMap<Uuid, Bounds>,
|
||||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
|
||||||
) -> VecDeque<Modifier> {
|
) -> VecDeque<Modifier> {
|
||||||
let mut result = VecDeque::new();
|
let mut result = VecDeque::new();
|
||||||
let layout_bounds = bounds.find(shape);
|
let layout_bounds = bounds.find(shape);
|
||||||
let children = shape.modified_children_ids(structure.get(&shape.id), true);
|
let children = shape.children_ids(true);
|
||||||
|
|
||||||
let column_tracks = calculate_tracks(
|
let column_tracks = calculate_tracks(
|
||||||
true,
|
true,
|
||||||
|
|||||||
@ -1,22 +1,13 @@
|
|||||||
use skia_safe::Matrix;
|
use super::{Corners, Path, Segment, Shape, Type};
|
||||||
|
|
||||||
use super::{Corners, Path, Segment, Shape, StructureEntry, Type};
|
|
||||||
use crate::math;
|
use crate::math;
|
||||||
|
|
||||||
use crate::shapes::text_paths::TextPaths;
|
use crate::shapes::text_paths::TextPaths;
|
||||||
use crate::state::ShapesPool;
|
use crate::state::ShapesPoolRef;
|
||||||
use crate::uuid::Uuid;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
const BEZIER_CIRCLE_C: f32 = 0.551_915_05;
|
const BEZIER_CIRCLE_C: f32 = 0.551_915_05;
|
||||||
|
|
||||||
pub trait ToPath {
|
pub trait ToPath {
|
||||||
fn to_path(
|
fn to_path(&self, shapes: ShapesPoolRef) -> Path;
|
||||||
&self,
|
|
||||||
shapes: &ShapesPool,
|
|
||||||
modifiers: &HashMap<Uuid, Matrix>,
|
|
||||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
|
||||||
) -> Path;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum CornerType {
|
enum CornerType {
|
||||||
@ -180,34 +171,28 @@ fn transform_segments(segments: Vec<Segment>, shape: &Shape) -> Vec<Segment> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ToPath for Shape {
|
impl ToPath for Shape {
|
||||||
fn to_path(
|
fn to_path(&self, shapes: ShapesPoolRef) -> Path {
|
||||||
&self,
|
match &self.shape_type {
|
||||||
shapes: &ShapesPool,
|
|
||||||
modifiers: &HashMap<Uuid, Matrix>,
|
|
||||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
|
||||||
) -> Path {
|
|
||||||
let shape = self.transformed(modifiers.get(&self.id));
|
|
||||||
match shape.shape_type {
|
|
||||||
Type::Frame(ref frame) => {
|
Type::Frame(ref frame) => {
|
||||||
let children = shape.modified_children_ids(structure.get(&shape.id), true);
|
let children = self.children_ids(true);
|
||||||
let mut result = Path::new(rect_segments(&shape, frame.corners));
|
let mut result = Path::new(rect_segments(self, frame.corners));
|
||||||
for id in children {
|
for id in children {
|
||||||
let Some(shape) = shapes.get(&id) else {
|
let Some(shape) = shapes.get(&id) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
result = join_paths(result, shape.to_path(shapes, modifiers, structure));
|
result = join_paths(result, shape.to_path(shapes));
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
Type::Group(_) => {
|
Type::Group(_) => {
|
||||||
let children = shape.modified_children_ids(structure.get(&shape.id), true);
|
let children = self.children_ids(true);
|
||||||
let mut result = Path::default();
|
let mut result = Path::default();
|
||||||
for id in children {
|
for id in children {
|
||||||
let Some(shape) = shapes.get(&id) else {
|
let Some(shape) = shapes.get(&id) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
result = join_paths(result, shape.to_path(shapes, modifiers, structure));
|
result = join_paths(result, shape.to_path(shapes));
|
||||||
}
|
}
|
||||||
// Force closure of the group path
|
// Force closure of the group path
|
||||||
let mut segments = result.segments().clone();
|
let mut segments = result.segments().clone();
|
||||||
@ -215,13 +200,13 @@ impl ToPath for Shape {
|
|||||||
Path::new(segments)
|
Path::new(segments)
|
||||||
}
|
}
|
||||||
|
|
||||||
Type::Bool(bool_data) => bool_data.path,
|
Type::Bool(bool_data) => bool_data.path.clone(),
|
||||||
|
|
||||||
Type::Rect(ref rect) => Path::new(rect_segments(&shape, rect.corners)),
|
Type::Rect(ref rect) => Path::new(rect_segments(self, rect.corners)),
|
||||||
|
|
||||||
Type::Path(path_data) => path_data,
|
Type::Path(path_data) => path_data.clone(),
|
||||||
|
|
||||||
Type::Circle => Path::new(circle_segments(&shape)),
|
Type::Circle => Path::new(circle_segments(self)),
|
||||||
|
|
||||||
Type::SVGRaw(_) => Path::default(),
|
Type::SVGRaw(_) => Path::default(),
|
||||||
|
|
||||||
@ -232,7 +217,7 @@ impl ToPath for Shape {
|
|||||||
result = join_paths(result, Path::from_skia_path(path));
|
result = join_paths(result, Path::from_skia_path(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
Path::new(transform_segments(result.segments().clone(), &shape))
|
Path::new(transform_segments(result.segments().clone(), self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,12 +3,11 @@ use std::collections::HashMap;
|
|||||||
|
|
||||||
mod shapes_pool;
|
mod shapes_pool;
|
||||||
mod text_editor;
|
mod text_editor;
|
||||||
pub use shapes_pool::*;
|
pub use shapes_pool::{ShapesPool, ShapesPoolMutRef, ShapesPoolRef};
|
||||||
pub use text_editor::*;
|
pub use text_editor::*;
|
||||||
|
|
||||||
use crate::render::RenderState;
|
use crate::render::RenderState;
|
||||||
use crate::shapes::Shape;
|
use crate::shapes::Shape;
|
||||||
use crate::shapes::StructureEntry;
|
|
||||||
use crate::tiles;
|
use crate::tiles;
|
||||||
use crate::uuid::Uuid;
|
use crate::uuid::Uuid;
|
||||||
|
|
||||||
@ -19,26 +18,20 @@ use crate::shapes::modifiers::grid_layout::grid_cell_data;
|
|||||||
/// It is created by [init] and passed to the other exported functions.
|
/// It is created by [init] and passed to the other exported functions.
|
||||||
/// Note that rust-skia data structures are not thread safe, so a state
|
/// Note that rust-skia data structures are not thread safe, so a state
|
||||||
/// must not be shared between different Web Workers.
|
/// must not be shared between different Web Workers.
|
||||||
pub(crate) struct State {
|
pub(crate) struct State<'a> {
|
||||||
pub render_state: RenderState,
|
pub render_state: RenderState,
|
||||||
pub text_editor_state: TextEditorState,
|
pub text_editor_state: TextEditorState,
|
||||||
pub current_id: Option<Uuid>,
|
pub current_id: Option<Uuid>,
|
||||||
pub shapes: ShapesPool,
|
pub shapes: ShapesPool<'a>,
|
||||||
pub modifiers: HashMap<Uuid, skia::Matrix>,
|
|
||||||
pub scale_content: HashMap<Uuid, f32>,
|
|
||||||
pub structure: HashMap<Uuid, Vec<StructureEntry>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl<'a> State<'a> {
|
||||||
pub fn new(width: i32, height: i32) -> Self {
|
pub fn new(width: i32, height: i32) -> Self {
|
||||||
State {
|
State {
|
||||||
render_state: RenderState::new(width, height),
|
render_state: RenderState::new(width, height),
|
||||||
text_editor_state: TextEditorState::new(),
|
text_editor_state: TextEditorState::new(),
|
||||||
current_id: None,
|
current_id: None,
|
||||||
shapes: ShapesPool::new(),
|
shapes: ShapesPool::new(),
|
||||||
modifiers: HashMap::new(),
|
|
||||||
scale_content: HashMap::new(),
|
|
||||||
structure: HashMap::new(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,29 +58,18 @@ impl State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_from_cache(&mut self) {
|
pub fn render_from_cache(&mut self) {
|
||||||
self.render_state
|
self.render_state.render_from_cache(&self.shapes);
|
||||||
.render_from_cache(&self.shapes, &self.modifiers, &self.structure);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_render_loop(&mut self, timestamp: i32) -> Result<(), String> {
|
pub fn start_render_loop(&mut self, timestamp: i32) -> Result<(), String> {
|
||||||
self.render_state.start_render_loop(
|
self.render_state
|
||||||
&self.shapes,
|
.start_render_loop(&self.shapes, timestamp)?;
|
||||||
&self.modifiers,
|
|
||||||
&self.structure,
|
|
||||||
&self.scale_content,
|
|
||||||
timestamp,
|
|
||||||
)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_animation_frame(&mut self, timestamp: i32) -> Result<(), String> {
|
pub fn process_animation_frame(&mut self, timestamp: i32) -> Result<(), String> {
|
||||||
self.render_state.process_animation_frame(
|
self.render_state
|
||||||
&self.shapes,
|
.process_animation_frame(&self.shapes, timestamp)?;
|
||||||
&self.modifiers,
|
|
||||||
&self.structure,
|
|
||||||
&self.scale_content,
|
|
||||||
timestamp,
|
|
||||||
)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,12 +92,16 @@ impl State {
|
|||||||
self.current_id = Some(id);
|
self.current_id = Some(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_shape(&mut self, id: Uuid) {
|
pub fn delete_shape_children(&mut self, parent_id: Uuid, id: Uuid) {
|
||||||
// We don't really do a self.shapes.remove so that redo/undo keep working
|
// We don't really do a self.shapes.remove so that redo/undo keep working
|
||||||
if let Some(shape) = self.shapes.get(&id) {
|
let Some(shape) = self.shapes.get(&id) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Only remove the children when is being deleted from the owner
|
||||||
|
if shape.parent_id.is_none() || shape.parent_id == Some(parent_id) {
|
||||||
let tiles::TileRect(rsx, rsy, rex, rey) =
|
let tiles::TileRect(rsx, rsy, rex, rey) =
|
||||||
self.render_state
|
self.render_state.get_tiles_for_shape(shape, &self.shapes);
|
||||||
.get_tiles_for_shape(shape, &self.shapes, &self.modifiers);
|
|
||||||
for x in rsx..=rex {
|
for x in rsx..=rex {
|
||||||
for y in rsy..=rey {
|
for y in rsy..=rey {
|
||||||
let tile = tiles::Tile(x, y);
|
let tile = tiles::Tile(x, y);
|
||||||
@ -148,6 +134,8 @@ impl State {
|
|||||||
panic!("Invalid current shape")
|
panic!("Invalid current shape")
|
||||||
};
|
};
|
||||||
shape.set_parent(id);
|
shape.set_parent(id);
|
||||||
|
|
||||||
|
// TODO this clone doesn't seem necessary
|
||||||
shape.clone()
|
shape.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -157,28 +145,9 @@ impl State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the selection rectangle for the current shape and processes its ancestors
|
|
||||||
///
|
|
||||||
/// When a shape's selection rectangle changes, all its ancestors need to have their
|
|
||||||
/// extended rectangles recalculated because the shape's bounds may have changed.
|
|
||||||
/// This ensures proper rendering of frames and groups containing the modified shape.
|
|
||||||
// FIXME: PERFORMANCE
|
|
||||||
pub fn set_selrect_for_current_shape(&mut self, left: f32, top: f32, right: f32, bottom: f32) {
|
|
||||||
let shape = {
|
|
||||||
let Some(shape) = self.current_shape_mut() else {
|
|
||||||
panic!("Invalid current shape")
|
|
||||||
};
|
|
||||||
shape.set_selrect(left, top, right, bottom);
|
|
||||||
shape.clone()
|
|
||||||
};
|
|
||||||
self.render_state
|
|
||||||
.process_shape_ancestors(&shape, &mut self.shapes, &self.modifiers);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_tile_for_shape(&mut self, shape_id: Uuid) {
|
pub fn update_tile_for_shape(&mut self, shape_id: Uuid) {
|
||||||
if let Some(shape) = self.shapes.get(&shape_id) {
|
if let Some(shape) = self.shapes.get(&shape_id) {
|
||||||
self.render_state
|
self.render_state.update_tile_for(shape, &self.shapes);
|
||||||
.update_tile_for(shape, &self.shapes, &self.modifiers);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,25 +155,32 @@ impl State {
|
|||||||
let Some(shape) = self.current_shape() else {
|
let Some(shape) = self.current_shape() else {
|
||||||
panic!("Invalid current shape")
|
panic!("Invalid current shape")
|
||||||
};
|
};
|
||||||
|
// TODO: Remove this clone
|
||||||
if !shape.id.is_nil() {
|
if !shape.id.is_nil() {
|
||||||
self.render_state
|
self.render_state
|
||||||
.update_tile_for(&shape.clone(), &self.shapes, &self.modifiers);
|
.update_tile_for(&shape.clone(), &self.shapes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rebuild_tiles_shallow(&mut self) {
|
pub fn rebuild_tiles_shallow(&mut self) {
|
||||||
self.render_state
|
self.render_state.rebuild_tiles_shallow(&self.shapes);
|
||||||
.rebuild_tiles_shallow(&self.shapes, &self.modifiers, &self.structure);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rebuild_tiles(&mut self) {
|
pub fn rebuild_tiles(&mut self) {
|
||||||
self.render_state
|
self.render_state.rebuild_tiles(&self.shapes);
|
||||||
.rebuild_tiles(&self.shapes, &self.modifiers, &self.structure);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rebuild_modifier_tiles(&mut self) {
|
pub fn rebuild_modifier_tiles(&mut self, ids: Vec<Uuid>) {
|
||||||
self.render_state
|
// SAFETY: We're extending the lifetime of the mutable borrow to 'a.
|
||||||
.rebuild_modifier_tiles(&mut self.shapes, &self.modifiers);
|
// This is safe because:
|
||||||
|
// 1. shapes has lifetime 'a in the struct
|
||||||
|
// 2. The reference won't outlive the struct
|
||||||
|
// 3. No other references to shapes exist during this call
|
||||||
|
unsafe {
|
||||||
|
let shapes_ptr = &mut self.shapes as *mut ShapesPool<'a>;
|
||||||
|
self.render_state
|
||||||
|
.rebuild_modifier_tiles(&mut *shapes_ptr, ids);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn font_collection(&self) -> &FontCollection {
|
pub fn font_collection(&self) -> &FontCollection {
|
||||||
@ -216,7 +192,7 @@ impl State {
|
|||||||
let bounds = shape.bounds();
|
let bounds = shape.bounds();
|
||||||
let position = Point::new(pos_x, pos_y);
|
let position = Point::new(pos_x, pos_y);
|
||||||
|
|
||||||
let cells = grid_cell_data(shape, &self.shapes, &self.modifiers, &self.structure, true);
|
let cells = grid_cell_data(shape, &self.shapes, true);
|
||||||
|
|
||||||
for cell in cells {
|
for cell in cells {
|
||||||
let points = &[
|
let points = &[
|
||||||
@ -235,4 +211,8 @@ impl State {
|
|||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_modifiers(&mut self, modifiers: HashMap<Uuid, skia::Matrix>) {
|
||||||
|
self.shapes.set_modifiers(modifiers);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,14 +2,20 @@ use std::collections::HashMap;
|
|||||||
use std::iter;
|
use std::iter;
|
||||||
|
|
||||||
use crate::performance;
|
use crate::performance;
|
||||||
|
use crate::shapes;
|
||||||
use crate::shapes::Shape;
|
use crate::shapes::Shape;
|
||||||
use crate::uuid::Uuid;
|
use crate::uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::shapes::StructureEntry;
|
||||||
|
use crate::skia;
|
||||||
|
|
||||||
|
use std::cell::OnceCell;
|
||||||
|
|
||||||
const SHAPES_POOL_ALLOC_MULTIPLIER: f32 = 1.3;
|
const SHAPES_POOL_ALLOC_MULTIPLIER: f32 = 1.3;
|
||||||
|
|
||||||
/// A pool allocator for `Shape` objects that attempts to minimize memory reallocations.
|
/// A pool allocator for `Shape` objects that attempts to minimize memory reallocations.
|
||||||
///
|
///
|
||||||
/// `ShapesPool` pre-allocates a contiguous vector of `Shape` instances,
|
/// `ShapesPoolImpl` pre-allocates a contiguous vector of `Shape` instances,
|
||||||
/// which can be reused and indexed efficiently. This design helps avoid
|
/// which can be reused and indexed efficiently. This design helps avoid
|
||||||
/// memory reallocation overhead by reserving enough space in advance.
|
/// memory reallocation overhead by reserving enough space in advance.
|
||||||
///
|
///
|
||||||
@ -18,18 +24,34 @@ const SHAPES_POOL_ALLOC_MULTIPLIER: f32 = 1.3;
|
|||||||
/// Shapes are stored in a `Vec<Shape>`, which keeps the `Shape` instances
|
/// Shapes are stored in a `Vec<Shape>`, which keeps the `Shape` instances
|
||||||
/// in a contiguous memory block.
|
/// in a contiguous memory block.
|
||||||
///
|
///
|
||||||
pub struct ShapesPool {
|
pub struct ShapesPoolImpl<'a> {
|
||||||
shapes: Vec<Shape>,
|
shapes: Vec<Shape>,
|
||||||
shapes_uuid_to_idx: HashMap<Uuid, usize>,
|
|
||||||
counter: usize,
|
counter: usize,
|
||||||
|
|
||||||
|
shapes_uuid_to_idx: HashMap<&'a Uuid, usize>,
|
||||||
|
|
||||||
|
modified_shape_cache: HashMap<&'a Uuid, OnceCell<Shape>>,
|
||||||
|
modifiers: HashMap<&'a Uuid, skia::Matrix>,
|
||||||
|
structure: HashMap<&'a Uuid, Vec<StructureEntry>>,
|
||||||
|
scale_content: HashMap<&'a Uuid, f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ShapesPool {
|
// Type aliases to avoid writing lifetimes everywhere
|
||||||
|
pub type ShapesPool<'a> = ShapesPoolImpl<'a>;
|
||||||
|
pub type ShapesPoolRef<'a> = &'a ShapesPoolImpl<'a>;
|
||||||
|
pub type ShapesPoolMutRef<'a> = &'a mut ShapesPoolImpl<'a>;
|
||||||
|
|
||||||
|
impl<'a> ShapesPoolImpl<'a> {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
ShapesPool {
|
ShapesPoolImpl {
|
||||||
shapes: vec![],
|
shapes: vec![],
|
||||||
counter: 0,
|
counter: 0,
|
||||||
shapes_uuid_to_idx: HashMap::default(),
|
shapes_uuid_to_idx: HashMap::default(),
|
||||||
|
|
||||||
|
modified_shape_cache: HashMap::default(),
|
||||||
|
modifiers: HashMap::default(),
|
||||||
|
structure: HashMap::default(),
|
||||||
|
scale_content: HashMap::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,22 +65,133 @@ impl ShapesPool {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reserve exact capacity to avoid any future reallocations
|
||||||
|
// This is critical because we store &'a Uuid references that would be invalidated
|
||||||
|
let target_capacity = (capacity as f32 * SHAPES_POOL_ALLOC_MULTIPLIER) as usize;
|
||||||
|
self.shapes
|
||||||
|
.reserve_exact(target_capacity.saturating_sub(self.shapes.len()));
|
||||||
|
|
||||||
self.shapes
|
self.shapes
|
||||||
.extend(iter::repeat_with(|| Shape::new(Uuid::nil())).take(additional as usize));
|
.extend(iter::repeat_with(|| Shape::new(Uuid::nil())).take(additional as usize));
|
||||||
performance::end_measure!("shapes_pool_initialize");
|
performance::end_measure!("shapes_pool_initialize");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_shape(&mut self, id: Uuid) -> &mut Shape {
|
pub fn add_shape(&mut self, id: Uuid) -> &mut Shape {
|
||||||
if self.counter >= self.shapes.len() {
|
let did_reallocate = if self.counter >= self.shapes.len() {
|
||||||
|
// We need more space. Check if we'll need to reallocate the Vec.
|
||||||
|
let current_capacity = self.shapes.capacity();
|
||||||
let additional = (self.shapes.len() as f32 * SHAPES_POOL_ALLOC_MULTIPLIER) as usize;
|
let additional = (self.shapes.len() as f32 * SHAPES_POOL_ALLOC_MULTIPLIER) as usize;
|
||||||
|
let needed_capacity = self.shapes.len() + additional;
|
||||||
|
|
||||||
|
let will_reallocate = needed_capacity > current_capacity;
|
||||||
|
|
||||||
|
if will_reallocate {
|
||||||
|
// Reserve extra space to minimize future reallocations
|
||||||
|
let extra_reserve = (needed_capacity as f32 * 0.5) as usize;
|
||||||
|
self.shapes
|
||||||
|
.reserve(needed_capacity + extra_reserve - current_capacity);
|
||||||
|
}
|
||||||
|
|
||||||
self.shapes
|
self.shapes
|
||||||
.extend(iter::repeat_with(|| Shape::new(Uuid::nil())).take(additional));
|
.extend(iter::repeat_with(|| Shape::new(Uuid::nil())).take(additional));
|
||||||
}
|
|
||||||
let new_shape = &mut self.shapes[self.counter];
|
will_reallocate
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
let idx = self.counter;
|
||||||
|
let new_shape = &mut self.shapes[idx];
|
||||||
new_shape.id = id;
|
new_shape.id = id;
|
||||||
self.shapes_uuid_to_idx.insert(id, self.counter);
|
|
||||||
|
// Get a reference to the id field in the shape with lifetime 'a
|
||||||
|
// SAFETY: This is safe because:
|
||||||
|
// 1. We pre-allocate enough capacity to avoid Vec reallocation
|
||||||
|
// 2. The shape and its id field won't move within the Vec
|
||||||
|
// 3. The reference won't outlive the ShapesPoolImpl
|
||||||
|
let id_ref: &'a Uuid = unsafe { &*(&self.shapes[idx].id as *const Uuid) };
|
||||||
|
|
||||||
|
self.shapes_uuid_to_idx.insert(id_ref, idx);
|
||||||
self.counter += 1;
|
self.counter += 1;
|
||||||
new_shape
|
|
||||||
|
// If the Vec reallocated, we need to rebuild all references in the HashMaps
|
||||||
|
// because the old references point to deallocated memory
|
||||||
|
if did_reallocate {
|
||||||
|
self.rebuild_references();
|
||||||
|
}
|
||||||
|
|
||||||
|
&mut self.shapes[idx]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Rebuilds all &'a Uuid references in the HashMaps after a Vec reallocation.
|
||||||
|
/// This is necessary because Vec reallocation invalidates all existing references.
|
||||||
|
fn rebuild_references(&mut self) {
|
||||||
|
// Rebuild shapes_uuid_to_idx with fresh references
|
||||||
|
let mut new_map = HashMap::with_capacity(self.shapes_uuid_to_idx.len());
|
||||||
|
for (_, idx) in self.shapes_uuid_to_idx.drain() {
|
||||||
|
let id_ref: &'a Uuid = unsafe { &*(&self.shapes[idx].id as *const Uuid) };
|
||||||
|
new_map.insert(id_ref, idx);
|
||||||
|
}
|
||||||
|
self.shapes_uuid_to_idx = new_map;
|
||||||
|
|
||||||
|
// Rebuild modifiers with fresh references
|
||||||
|
if !self.modifiers.is_empty() {
|
||||||
|
let old_modifiers: Vec<(Uuid, skia::Matrix)> = self
|
||||||
|
.modifiers
|
||||||
|
.drain()
|
||||||
|
.map(|(uuid_ref, matrix)| (*uuid_ref, matrix))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
for (uuid, matrix) in old_modifiers {
|
||||||
|
if let Some(uuid_ref) = self.get_uuid_ref(&uuid) {
|
||||||
|
self.modifiers.insert(uuid_ref, matrix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rebuild structure with fresh references
|
||||||
|
if !self.structure.is_empty() {
|
||||||
|
let old_structure: Vec<(Uuid, Vec<StructureEntry>)> = self
|
||||||
|
.structure
|
||||||
|
.drain()
|
||||||
|
.map(|(uuid_ref, entries)| (*uuid_ref, entries))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
for (uuid, entries) in old_structure {
|
||||||
|
if let Some(uuid_ref) = self.get_uuid_ref(&uuid) {
|
||||||
|
self.structure.insert(uuid_ref, entries);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rebuild scale_content with fresh references
|
||||||
|
if !self.scale_content.is_empty() {
|
||||||
|
let old_scale_content: Vec<(Uuid, f32)> = self
|
||||||
|
.scale_content
|
||||||
|
.drain()
|
||||||
|
.map(|(uuid_ref, scale)| (*uuid_ref, scale))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
for (uuid, scale) in old_scale_content {
|
||||||
|
if let Some(uuid_ref) = self.get_uuid_ref(&uuid) {
|
||||||
|
self.scale_content.insert(uuid_ref, scale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Rebuild modified_shape_cache with fresh references
|
||||||
|
if !self.modified_shape_cache.is_empty() {
|
||||||
|
let old_cache: Vec<(Uuid, OnceCell<Shape>)> = self
|
||||||
|
.modified_shape_cache
|
||||||
|
.drain()
|
||||||
|
.map(|(uuid_ref, cell)| (*uuid_ref, cell))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
for (uuid, cell) in old_cache {
|
||||||
|
if let Some(uuid_ref) = self.get_uuid_ref(&uuid) {
|
||||||
|
self.modified_shape_cache.insert(uuid_ref, cell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
@ -66,17 +199,57 @@ impl ShapesPool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn has(&self, id: &Uuid) -> bool {
|
pub fn has(&self, id: &Uuid) -> bool {
|
||||||
self.shapes_uuid_to_idx.contains_key(id)
|
self.shapes_uuid_to_idx.contains_key(&id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_mut(&mut self, id: &Uuid) -> Option<&mut Shape> {
|
pub fn get_mut(&mut self, id: &Uuid) -> Option<&mut Shape> {
|
||||||
let idx = *self.shapes_uuid_to_idx.get(id)?;
|
let idx = *self.shapes_uuid_to_idx.get(&id)?;
|
||||||
Some(&mut self.shapes[idx])
|
Some(&mut self.shapes[idx])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(&self, id: &Uuid) -> Option<&Shape> {
|
pub fn get(&self, id: &Uuid) -> Option<&'a Shape> {
|
||||||
let idx = *self.shapes_uuid_to_idx.get(id)?;
|
let idx = *self.shapes_uuid_to_idx.get(&id)?;
|
||||||
Some(&self.shapes[idx])
|
|
||||||
|
// SAFETY: We're extending the lifetimes to 'a.
|
||||||
|
// This is safe because:
|
||||||
|
// 1. All internal HashMaps and the shapes Vec have fields with lifetime 'a
|
||||||
|
// 2. The shape at idx won't be moved or reallocated (pre-allocated Vec)
|
||||||
|
// 3. The id is stored in shapes[idx].id which has lifetime 'a
|
||||||
|
// 4. The references won't outlive the ShapesPoolImpl
|
||||||
|
unsafe {
|
||||||
|
let shape_ptr = &self.shapes[idx] as *const Shape;
|
||||||
|
let modifiers_ptr = &self.modifiers as *const HashMap<&'a Uuid, skia::Matrix>;
|
||||||
|
let structure_ptr = &self.structure as *const HashMap<&'a Uuid, Vec<StructureEntry>>;
|
||||||
|
let scale_content_ptr = &self.scale_content as *const HashMap<&'a Uuid, f32>;
|
||||||
|
let cache_ptr = &self.modified_shape_cache as *const HashMap<&'a Uuid, OnceCell<Shape>>;
|
||||||
|
|
||||||
|
// Extend the lifetime of id to 'a - safe because it's the same Uuid stored in shapes[idx].id
|
||||||
|
let id_ref: &'a Uuid = &*(id as *const Uuid);
|
||||||
|
|
||||||
|
if self.to_update_bool(&*shape_ptr)
|
||||||
|
|| (*modifiers_ptr).contains_key(&id_ref)
|
||||||
|
|| (*structure_ptr).contains_key(&id_ref)
|
||||||
|
|| (*scale_content_ptr).contains_key(&id_ref)
|
||||||
|
{
|
||||||
|
if let Some(cell) = (*cache_ptr).get(&id_ref) {
|
||||||
|
Some(cell.get_or_init(|| {
|
||||||
|
let mut shape = (*shape_ptr).transformed(
|
||||||
|
self,
|
||||||
|
(*modifiers_ptr).get(&id_ref),
|
||||||
|
(*structure_ptr).get(&id_ref),
|
||||||
|
);
|
||||||
|
if let Some(scale) = (*scale_content_ptr).get(&id_ref) {
|
||||||
|
shape.scale_content(*scale);
|
||||||
|
}
|
||||||
|
shape
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
Some(&*shape_ptr)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Some(&*shape_ptr)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@ -87,4 +260,135 @@ impl ShapesPool {
|
|||||||
pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, Shape> {
|
pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, Shape> {
|
||||||
self.shapes.iter_mut()
|
self.shapes.iter_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn clean_shape_cache(&mut self) {
|
||||||
|
self.modified_shape_cache.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_modifiers(&mut self, modifiers: HashMap<Uuid, skia::Matrix>) {
|
||||||
|
// Convert HashMap<Uuid, V> to HashMap<&'a Uuid, V> using references from shapes and
|
||||||
|
// Initialize the cache cells because later we don't want to have the mutable pointer
|
||||||
|
|
||||||
|
let mut ids = Vec::<Uuid>::new();
|
||||||
|
|
||||||
|
let mut modifiers_with_refs = HashMap::with_capacity(modifiers.len());
|
||||||
|
for (uuid, matrix) in modifiers {
|
||||||
|
if let Some(uuid_ref) = self.get_uuid_ref(&uuid) {
|
||||||
|
// self.modified_shape_cache.insert(uuid_ref, OnceCell::new());
|
||||||
|
modifiers_with_refs.insert(uuid_ref, matrix);
|
||||||
|
ids.push(*uuid_ref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.modifiers = modifiers_with_refs;
|
||||||
|
|
||||||
|
let all_ids = shapes::all_with_ancestors(&ids, self, true);
|
||||||
|
for uuid in all_ids {
|
||||||
|
if let Some(uuid_ref) = self.get_uuid_ref(&uuid) {
|
||||||
|
self.modified_shape_cache.insert(uuid_ref, OnceCell::new());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_structure(&mut self, structure: HashMap<Uuid, Vec<StructureEntry>>) {
|
||||||
|
// Convert HashMap<Uuid, V> to HashMap<&'a Uuid, V> using references from shapes and
|
||||||
|
// Initialize the cache cells because later we don't want to have the mutable pointer
|
||||||
|
let mut structure_with_refs = HashMap::with_capacity(structure.len());
|
||||||
|
let mut ids = Vec::<Uuid>::new();
|
||||||
|
|
||||||
|
for (uuid, entries) in structure {
|
||||||
|
if let Some(uuid_ref) = self.get_uuid_ref(&uuid) {
|
||||||
|
structure_with_refs.insert(uuid_ref, entries);
|
||||||
|
ids.push(*uuid_ref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.structure = structure_with_refs;
|
||||||
|
|
||||||
|
let all_ids = shapes::all_with_ancestors(&ids, self, true);
|
||||||
|
for uuid in all_ids {
|
||||||
|
if let Some(uuid_ref) = self.get_uuid_ref(&uuid) {
|
||||||
|
self.modified_shape_cache.insert(uuid_ref, OnceCell::new());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_scale_content(&mut self, scale_content: HashMap<Uuid, f32>) {
|
||||||
|
// Convert HashMap<Uuid, V> to HashMap<&'a Uuid, V> using references from shapes and
|
||||||
|
// Initialize the cache cells because later we don't want to have the mutable pointer
|
||||||
|
let mut scale_content_with_refs = HashMap::with_capacity(scale_content.len());
|
||||||
|
let mut ids = Vec::<Uuid>::new();
|
||||||
|
|
||||||
|
for (uuid, value) in scale_content {
|
||||||
|
if let Some(uuid_ref) = self.get_uuid_ref(&uuid) {
|
||||||
|
scale_content_with_refs.insert(uuid_ref, value);
|
||||||
|
ids.push(*uuid_ref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.scale_content = scale_content_with_refs;
|
||||||
|
|
||||||
|
let all_ids = shapes::all_with_ancestors(&ids, self, true);
|
||||||
|
for uuid in all_ids {
|
||||||
|
if let Some(uuid_ref) = self.get_uuid_ref(&uuid) {
|
||||||
|
self.modified_shape_cache.insert(uuid_ref, OnceCell::new());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clean_all(&mut self) {
|
||||||
|
self.clean_shape_cache();
|
||||||
|
self.modifiers = HashMap::default();
|
||||||
|
self.structure = HashMap::default();
|
||||||
|
self.scale_content = HashMap::default();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a reference to the Uuid stored in a shape, if it exists
|
||||||
|
pub fn get_uuid_ref(&self, id: &Uuid) -> Option<&'a Uuid> {
|
||||||
|
let idx = *self.shapes_uuid_to_idx.get(&id)?;
|
||||||
|
// SAFETY: We're returning a reference with lifetime 'a to a Uuid stored
|
||||||
|
// in the shapes Vec. This is safe because the Vec is stable (pre-allocated)
|
||||||
|
// and won't be reallocated.
|
||||||
|
unsafe { Some(&*(&self.shapes[idx].id as *const Uuid)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn subtree(&self, id: &Uuid) -> ShapesPoolImpl<'a> {
|
||||||
|
let Some(shape) = self.get(id) else {
|
||||||
|
panic!("Subtree not found");
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: Maybe create all_children_iter
|
||||||
|
let all_children = shape.all_children(self, true, true);
|
||||||
|
|
||||||
|
let mut shapes = vec![];
|
||||||
|
let mut idx = 0;
|
||||||
|
let mut shapes_uuid_to_idx = HashMap::default();
|
||||||
|
|
||||||
|
for id in all_children.iter() {
|
||||||
|
let Some(shape) = self.get(id) else {
|
||||||
|
panic!("Not found");
|
||||||
|
};
|
||||||
|
shapes.push(shape.clone());
|
||||||
|
|
||||||
|
let id_ref: &'a Uuid = unsafe { &*(&self.shapes[idx].id as *const Uuid) };
|
||||||
|
shapes_uuid_to_idx.insert(id_ref, idx);
|
||||||
|
idx += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut result = ShapesPoolImpl {
|
||||||
|
shapes,
|
||||||
|
counter: idx,
|
||||||
|
shapes_uuid_to_idx,
|
||||||
|
modified_shape_cache: HashMap::default(),
|
||||||
|
modifiers: HashMap::default(),
|
||||||
|
structure: HashMap::default(),
|
||||||
|
scale_content: HashMap::default(),
|
||||||
|
};
|
||||||
|
result.rebuild_references();
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_update_bool(&self, shape: &Shape) -> bool {
|
||||||
|
// TODO: Check if any of the children is in the modifiers with a
|
||||||
|
// different matrix than the current one.
|
||||||
|
shape.is_bool()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
use crate::uuid::Uuid;
|
use crate::uuid::Uuid;
|
||||||
use crate::view::Viewbox;
|
use crate::view::Viewbox;
|
||||||
use indexmap::IndexSet;
|
|
||||||
use skia_safe as skia;
|
use skia_safe as skia;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
@ -114,7 +113,7 @@ pub fn get_tile_rect(tile: Tile, scale: f32) -> skia::Rect {
|
|||||||
|
|
||||||
// This structure is usseful to keep all the shape uuids by shape id.
|
// This structure is usseful to keep all the shape uuids by shape id.
|
||||||
pub struct TileHashMap {
|
pub struct TileHashMap {
|
||||||
grid: HashMap<Tile, IndexSet<Uuid>>,
|
grid: HashMap<Tile, HashSet<Uuid>>,
|
||||||
index: HashMap<Uuid, HashSet<Tile>>,
|
index: HashMap<Uuid, HashSet<Tile>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,13 +125,13 @@ impl TileHashMap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_shapes_at(&mut self, tile: Tile) -> Option<&IndexSet<Uuid>> {
|
pub fn get_shapes_at(&mut self, tile: Tile) -> Option<&HashSet<Uuid>> {
|
||||||
self.grid.get(&tile)
|
self.grid.get(&tile)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_shape_at(&mut self, tile: Tile, id: Uuid) {
|
pub fn remove_shape_at(&mut self, tile: Tile, id: Uuid) {
|
||||||
if let Some(shapes) = self.grid.get_mut(&tile) {
|
if let Some(shapes) = self.grid.get_mut(&tile) {
|
||||||
shapes.shift_remove(&id);
|
shapes.remove(&id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(tiles) = self.index.get_mut(&id) {
|
if let Some(tiles) = self.index.get_mut(&id) {
|
||||||
|
|||||||
@ -213,7 +213,7 @@ pub extern "C" fn set_shape_path_content() {
|
|||||||
pub extern "C" fn current_to_path() -> *mut u8 {
|
pub extern "C" fn current_to_path() -> *mut u8 {
|
||||||
let mut result = Vec::<RawSegmentData>::default();
|
let mut result = Vec::<RawSegmentData>::default();
|
||||||
with_current_shape!(state, |shape: &Shape| {
|
with_current_shape!(state, |shape: &Shape| {
|
||||||
let path = shape.to_path(&state.shapes, &state.modifiers, &state.structure);
|
let path = shape.to_path(&state.shapes);
|
||||||
result = path
|
result = path
|
||||||
.segments()
|
.segments()
|
||||||
.iter()
|
.iter()
|
||||||
|
|||||||
@ -57,13 +57,7 @@ pub extern "C" fn calculate_bool(raw_bool_type: u8) -> *mut u8 {
|
|||||||
let bool_type = RawBoolType::from(raw_bool_type).into();
|
let bool_type = RawBoolType::from(raw_bool_type).into();
|
||||||
let result;
|
let result;
|
||||||
with_state!(state, {
|
with_state!(state, {
|
||||||
let path = math::bools::bool_from_shapes(
|
let path = math::bools::bool_from_shapes(bool_type, &entries, &state.shapes);
|
||||||
bool_type,
|
|
||||||
&entries,
|
|
||||||
&state.shapes,
|
|
||||||
&state.modifiers,
|
|
||||||
&state.structure,
|
|
||||||
);
|
|
||||||
result = path
|
result = path
|
||||||
.segments()
|
.segments()
|
||||||
.iter()
|
.iter()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user