mirror of
https://github.com/penpot/penpot.git
synced 2025-12-12 06:24:17 +01:00
Merge pull request #7648 from penpot/alotor-performance-improvements
✨ Add performance improvements for wasm render
This commit is contained in:
@@ -517,8 +517,7 @@
|
||||
(when verify?
|
||||
(check-changes items))
|
||||
|
||||
(binding [*touched-changes* (volatile! #{})
|
||||
cts/*wasm-sync* (not cts/*wasm-sync-override*)]
|
||||
(binding [*touched-changes* (volatile! #{})]
|
||||
(let [result (reduce #(or (process-change %1 %2) %1) data items)
|
||||
result (reduce process-touched-change result @*touched-changes*)]
|
||||
;; Validate result shapes (only on the backend)
|
||||
|
||||
@@ -36,12 +36,7 @@
|
||||
[app.common.uuid :as uuid]
|
||||
[clojure.set :as set]))
|
||||
|
||||
(defonce ^:dynamic *wasm-sync* false)
|
||||
|
||||
;; This is a temporary workaround so the changes-builder doesn't generate updates
|
||||
;; in the WASM model.
|
||||
(defonce ^:dynamic *wasm-sync-override* false)
|
||||
|
||||
(defonce ^:dynamic *shape-changes* nil)
|
||||
(defonce wasm-enabled? false)
|
||||
(defonce wasm-create-shape (constantly nil))
|
||||
|
||||
|
||||
@@ -7,14 +7,18 @@
|
||||
(ns app.main.data.changes
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.files.changes :as cpc]
|
||||
[app.common.logging :as log]
|
||||
[app.common.time :as ct]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.helpers :as dsh]
|
||||
[app.main.features :as features]
|
||||
[app.main.worker :as mw]
|
||||
[app.render-wasm.shape :as wasm.shape]
|
||||
[beicon.v2.core :as rx]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
@@ -99,7 +103,21 @@
|
||||
pids (into #{} xf:map-page-id redo-changes)]
|
||||
(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
|
||||
"Create a commit event instance"
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
[app.common.types.component :as ctk]
|
||||
[app.common.types.container :as ctn]
|
||||
[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.attrs :refer [editable-attrs]]
|
||||
[app.common.types.shape.layout :as ctl]
|
||||
@@ -203,21 +203,26 @@
|
||||
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)
|
||||
(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
|
||||
(doseq [[id properties] wasm-props]
|
||||
(let [shape
|
||||
(->> properties
|
||||
(reduce
|
||||
(fn [shape {:keys [property value]}]
|
||||
(assoc shape property value))
|
||||
(get objects id)))]
|
||||
|
||||
;; With the new values to the shape change multi props
|
||||
(wasm.shape/set-wasm-multi-attrs! shape (->> properties (map :property)))))))
|
||||
;; Create a new objects only with the temporary modifications
|
||||
objects-changed
|
||||
(->> wasm-props
|
||||
(reduce
|
||||
(fn [objects [id properties]]
|
||||
(let [shape
|
||||
(->> properties
|
||||
(reduce
|
||||
(fn [shape {:keys [property value]}]
|
||||
(assoc shape property value))
|
||||
(get objects id)))]
|
||||
(assoc objects id shape)))
|
||||
objects))]
|
||||
(wasm.shape/process-shape-changes! objects-changed shape-changes)))
|
||||
|
||||
(defn clear-local-transform []
|
||||
(ptk/reify ::clear-local-transform
|
||||
@@ -616,17 +621,20 @@
|
||||
|
||||
#_:clj-kondo/ignore
|
||||
(defn apply-wasm-modifiers
|
||||
[modif-tree & {:keys [ignore-constraints ignore-snap-pixel snap-ignore-axis undo-group]
|
||||
:or {ignore-constraints false ignore-snap-pixel false snap-ignore-axis nil undo-group nil}
|
||||
[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-transation? true}
|
||||
:as params}]
|
||||
(ptk/reify ::apply-wasm-modifiesr
|
||||
ptk/WatchEvent
|
||||
(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)
|
||||
|
||||
ignore-tree
|
||||
(binding [shape/*wasm-sync* false]
|
||||
(calculate-ignore-tree modif-tree objects))
|
||||
(calculate-ignore-tree modif-tree objects)
|
||||
|
||||
options
|
||||
(-> params
|
||||
@@ -658,12 +666,34 @@
|
||||
modifiers (dm/get-in modif-tree [shape-id :modifiers])]
|
||||
(-> shape
|
||||
(gsh/apply-transform transform)
|
||||
(ctm/apply-structure-modifiers modifiers))))]
|
||||
(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))))))
|
||||
(ctm/apply-structure-modifiers modifiers))))
|
||||
|
||||
bool-ids
|
||||
(into #{}
|
||||
(comp
|
||||
(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
|
||||
xf-rotation-shape
|
||||
|
||||
@@ -78,20 +78,19 @@
|
||||
(not-empty))
|
||||
|
||||
changes
|
||||
(binding [cts/*wasm-sync-override* true]
|
||||
(-> (pcb/empty-changes it page-id)
|
||||
(pcb/set-save-undo? save-undo?)
|
||||
(pcb/set-stack-undo? stack-undo?)
|
||||
(cls/generate-update-shapes ids
|
||||
update-fn
|
||||
objects
|
||||
{:attrs attrs
|
||||
:changed-sub-attr changed-sub-attr
|
||||
:ignore-tree ignore-tree
|
||||
:ignore-touched ignore-touched
|
||||
:with-objects? with-objects?})
|
||||
(cond-> undo-group
|
||||
(pcb/set-undo-group undo-group))))
|
||||
(-> (pcb/empty-changes it page-id)
|
||||
(pcb/set-save-undo? save-undo?)
|
||||
(pcb/set-stack-undo? stack-undo?)
|
||||
(cls/generate-update-shapes ids
|
||||
update-fn
|
||||
objects
|
||||
{:attrs attrs
|
||||
:changed-sub-attr changed-sub-attr
|
||||
:ignore-tree ignore-tree
|
||||
:ignore-touched ignore-touched
|
||||
:with-objects? with-objects?})
|
||||
(cond-> undo-group
|
||||
(pcb/set-undo-group undo-group)))
|
||||
|
||||
changes
|
||||
(add-undo-group changes state)]
|
||||
|
||||
@@ -146,7 +146,7 @@
|
||||
(defn start-resize
|
||||
"Enter mouse resize mode, until mouse button is released."
|
||||
[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)
|
||||
width (dm/get-prop selrect :width)
|
||||
height (dm/get-prop selrect :height)
|
||||
@@ -238,10 +238,14 @@
|
||||
:always
|
||||
(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)
|
||||
|
||||
^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)
|
||||
|
||||
;; Set grow-type if it should change
|
||||
@@ -293,7 +297,7 @@
|
||||
(fn [[point _ _ :as current]]
|
||||
(->> (snap/closest-snap-point page-id shapes objects layout zoom focus point)
|
||||
(rx/map #(conj current %)))))
|
||||
(rx/map #(resize shape initial-position layout %))
|
||||
(rx/map #(resize shape initial-position layout objects %))
|
||||
(rx/share))
|
||||
|
||||
modifiers-stream
|
||||
@@ -309,8 +313,8 @@
|
||||
:ignore-constraints (contains? layout :scale-text))))))
|
||||
(rx/take-until stopper))
|
||||
|
||||
;; The last event we need to use the old method so the elements are correctly positioned until
|
||||
;; all the logic is implemented in wasm
|
||||
;; The last event we need to use the old method so the elements are correctly
|
||||
;; positioned until all the logic is implemented in wasm
|
||||
(->> resize-events-stream
|
||||
(rx/take-until stopper)
|
||||
(rx/last)
|
||||
@@ -327,8 +331,8 @@
|
||||
(rx/take-until stopper)))]
|
||||
|
||||
(rx/concat
|
||||
;; 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
|
||||
;; 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
|
||||
(->> ms/mouse-position
|
||||
(rx/map #(gpt/to-vec initial-position %))
|
||||
(rx/map #(gpt/length %))
|
||||
@@ -752,12 +756,6 @@
|
||||
(fn [[modifiers 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
|
||||
(rx/with-latest-from ms/mouse-position-alt)
|
||||
(rx/filter (fn [[_ alt?]] alt?))
|
||||
@@ -772,14 +770,18 @@
|
||||
;; Last event will write the modifiers creating the changes
|
||||
(->> move-stream
|
||||
(rx/last)
|
||||
(rx/with-latest-from modifiers-stream)
|
||||
(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)]
|
||||
(rx/of (dwu/start-undo-transaction undo-id)
|
||||
;; (dwm/apply-modifiers {:undo-transation? false})
|
||||
(move-shapes-to-frame ids target-frame drop-index drop-cell)
|
||||
(finish-transform)
|
||||
(dwu/commit-undo-transaction undo-id)))))))
|
||||
(rx/of
|
||||
(dwu/start-undo-transaction undo-id)
|
||||
(dwm/apply-wasm-modifiers modifiers
|
||||
:snap-ignore-axis snap-ignore-axis
|
||||
:undo-transation? false)
|
||||
(move-shapes-to-frame ids target-frame drop-index drop-cell)
|
||||
(finish-transform)
|
||||
(dwu/commit-undo-transaction undo-id)))))))
|
||||
|
||||
(rx/merge
|
||||
(->> modifiers-stream
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
[app.common.geom.rect :as grc]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.logic.shapes :as cls]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.shape.layout :as ctl]
|
||||
[app.common.types.token :as tk]
|
||||
[app.main.constants :refer [size-presets]]
|
||||
@@ -295,9 +294,8 @@
|
||||
(mf/use-fn
|
||||
(mf/deps ids)
|
||||
(fn [value attr]
|
||||
(binding [cts/*wasm-sync* true]
|
||||
(st/emit! (udw/trigger-bounding-box-cloaking ids)
|
||||
(udw/update-dimensions ids attr value)))))
|
||||
(st/emit! (udw/trigger-bounding-box-cloaking ids)
|
||||
(udw/update-dimensions ids attr value))))
|
||||
|
||||
on-size-change
|
||||
(mf/use-fn
|
||||
@@ -306,16 +304,14 @@
|
||||
(if (or (string? value) (int? value))
|
||||
(do
|
||||
(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
|
||||
(let [resolved-value (:resolved-value (first value))]
|
||||
(st/emit! (udw/trigger-bounding-box-cloaking ids)
|
||||
(dwta/toggle-token {:token (first value)
|
||||
:attrs #{attr}
|
||||
: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
|
||||
(mf/use-fn
|
||||
@@ -337,16 +333,14 @@
|
||||
(if (or (string? value) (int? value))
|
||||
(do
|
||||
(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
|
||||
(let [resolved-value (:resolved-value (first value))]
|
||||
(st/emit! (udw/trigger-bounding-box-cloaking ids)
|
||||
(dwta/toggle-token {:token (first value)
|
||||
:attrs #{attr}
|
||||
: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
|
||||
do-rotation-change
|
||||
@@ -362,16 +356,14 @@
|
||||
(if (or (string? value) (int? value))
|
||||
(do
|
||||
(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
|
||||
(let [resolved-value (:resolved-value (first value))]
|
||||
(st/emit! (udw/trigger-bounding-box-cloaking ids)
|
||||
(dwta/toggle-token {:token (first value)
|
||||
:attrs #{:rotation}
|
||||
: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
|
||||
(mf/use-fn (mf/deps on-size-change) #(on-size-change % :width))
|
||||
|
||||
@@ -113,8 +113,7 @@
|
||||
objects-modified
|
||||
(mf/with-memo
|
||||
[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
|
||||
(into [] (keep (d/getf objects-modified)))
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"A WASM based render API"
|
||||
(:require
|
||||
["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.math :as mth]
|
||||
[app.common.types.fills :as types.fills]
|
||||
@@ -850,20 +850,14 @@
|
||||
[pending]
|
||||
(let [event (js/CustomEvent. "wasm:set-objects-finished")
|
||||
pending (-> (d/index-by :key :callback pending) vals)]
|
||||
(if (not-empty? pending)
|
||||
(->> (rx/from pending)
|
||||
(rx/merge-map (fn [callback] (callback)))
|
||||
(rx/tap (fn [_] (request-render "set-objects")))
|
||||
(rx/reduce conj [])
|
||||
(rx/subs! (fn [_]
|
||||
(clear-drawing-cache)
|
||||
(request-render "pending-finished")
|
||||
(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)))))
|
||||
(->> (rx/from pending)
|
||||
(rx/merge-map (fn [callback] (callback)))
|
||||
(rx/reduce conj [])
|
||||
(rx/subs! (fn [_]
|
||||
(clear-drawing-cache)
|
||||
(request-render "pending-finished")
|
||||
(h/call wasm/internal-module "_update_shape_text_layout_for_all")
|
||||
(.dispatchEvent ^js js/document event))))))
|
||||
|
||||
(defn process-object
|
||||
[shape]
|
||||
@@ -1116,7 +1110,6 @@
|
||||
|
||||
(defn calculate-bool
|
||||
[bool-type ids]
|
||||
|
||||
(let [size (mem/get-alloc-size ids UUID-U8-SIZE)
|
||||
heap (mem/get-heap-u32)
|
||||
offset (mem/alloc->offset-32 size)]
|
||||
|
||||
@@ -120,8 +120,11 @@
|
||||
(-write writer (str "#penpot/shape " (:id delegate)))))
|
||||
|
||||
;; --- SHAPE IMPL
|
||||
|
||||
(defn- set-wasm-single-attr!
|
||||
;; When an attribute is sent to WASM it could still be pending some side operations
|
||||
;; 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]
|
||||
(let [v (get shape k)
|
||||
id (get shape :id)]
|
||||
@@ -132,7 +135,10 @@
|
||||
(when (or (= v :path) (= v :bool))
|
||||
(api/set-shape-path-content (:content shape))))
|
||||
: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)
|
||||
(api/set-shape-clip-content (not v))
|
||||
(api/set-shape-clip-content false))
|
||||
@@ -226,58 +232,40 @@
|
||||
(ctl/flex-layout? shape)
|
||||
(api/set-flex-layout shape)))
|
||||
|
||||
;; Property not in WASM
|
||||
nil)))
|
||||
|
||||
(defn set-wasm-multi-attrs!
|
||||
(defn process-shape!
|
||||
[shape properties]
|
||||
(let [shape-id (dm/get-prop shape :id)]
|
||||
(when (shape-in-current-page? shape-id)
|
||||
(api/use-shape shape-id)
|
||||
(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)]
|
||||
(if (shape-in-current-page? shape-id)
|
||||
(do
|
||||
(api/use-shape shape-id)
|
||||
(let [result (set-wasm-single-attr! shape k)
|
||||
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"))))))))
|
||||
(->> properties
|
||||
(mapcat #(set-wasm-attr! shape %))
|
||||
(d/index-by :key :callback)
|
||||
(vals)
|
||||
(rx/from)
|
||||
(rx/mapcat (fn [callback] (callback)))
|
||||
(rx/reduce conj [])))
|
||||
(rx/empty))))
|
||||
|
||||
(defn process-shape-changes!
|
||||
[objects shape-changes]
|
||||
(->> (rx/from shape-changes)
|
||||
(rx/mapcat (fn [[shape-id props]] (process-shape! (get objects shape-id) props)))
|
||||
(rx/subs!
|
||||
(fn [_]
|
||||
(api/update-shape-tiles)
|
||||
(api/request-render "set-wasm-attrs")))))
|
||||
|
||||
;; `conj` empty set initialization
|
||||
(def conj* (fnil conj #{}))
|
||||
|
||||
(defn- impl-assoc
|
||||
[self k v]
|
||||
(when ^boolean shape/*wasm-sync*
|
||||
(binding [shape/*wasm-sync* false]
|
||||
(set-wasm-attrs! self k v)))
|
||||
(when shape/*shape-changes*
|
||||
(vswap! shape/*shape-changes* update (:id self) conj* k))
|
||||
|
||||
(case k
|
||||
:id
|
||||
@@ -299,10 +287,9 @@
|
||||
|
||||
(defn- impl-dissoc
|
||||
[self k]
|
||||
(when ^boolean shape/*wasm-sync*
|
||||
(binding [shape/*wasm-sync* false]
|
||||
(when (shape-in-current-page? (.-id ^ShapeProxy self))
|
||||
(set-wasm-attrs! self k nil))))
|
||||
(when shape/*shape-changes*
|
||||
(vswap! shape/*shape-changes* update (:id self) conj* k))
|
||||
|
||||
(case k
|
||||
:id
|
||||
(ShapeProxy. nil
|
||||
|
||||
@@ -20,10 +20,11 @@ use mem::SerializableResult;
|
||||
use shapes::{StructureEntry, StructureEntryType, TransformEntry};
|
||||
use skia_safe as skia;
|
||||
use state::State;
|
||||
use std::collections::HashMap;
|
||||
use utils::uuid_from_u32_quartet;
|
||||
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_rules! with_state_mut {
|
||||
@@ -253,8 +254,8 @@ pub extern "C" fn set_shape_masked_group(masked: bool) {
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn set_shape_selrect(left: f32, top: f32, right: f32, bottom: f32) {
|
||||
with_state_mut!(state, {
|
||||
state.set_selrect_for_current_shape(left, top, right, bottom);
|
||||
with_current_shape_mut!(state, |shape: &mut Shape| {
|
||||
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>) {
|
||||
let mut deleted = IndexSet::new();
|
||||
let mut parent_id = None;
|
||||
|
||||
with_current_shape_mut!(state, |shape: &mut Shape| {
|
||||
parent_id = Some(shape.id);
|
||||
(_, deleted) = shape.compute_children_differences(&entries);
|
||||
shape.children = entries.clone();
|
||||
});
|
||||
|
||||
with_state_mut!(state, {
|
||||
let Some(parent_id) = parent_id else {
|
||||
return;
|
||||
};
|
||||
|
||||
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, {
|
||||
let bbs: Vec<_> = entries
|
||||
.iter()
|
||||
.flat_map(|id| {
|
||||
let default = Matrix::default();
|
||||
let modifier = state.modifiers.get(id).unwrap_or(&default);
|
||||
state.shapes.get(id).map(|b| b.bounds().transform(modifier))
|
||||
})
|
||||
.flat_map(|id| state.shapes.get(id).map(|b| b.bounds()))
|
||||
.collect();
|
||||
|
||||
let result_bound = if bbs.len() == 1 {
|
||||
@@ -537,6 +540,8 @@ pub extern "C" fn set_structure_modifiers() {
|
||||
.collect();
|
||||
|
||||
with_state_mut!(state, {
|
||||
let mut structure = HashMap::new();
|
||||
let mut scale_content = HashMap::new();
|
||||
for entry in entries {
|
||||
match entry.entry_type {
|
||||
StructureEntryType::ScaleContent => {
|
||||
@@ -544,19 +549,24 @@ pub extern "C" fn set_structure_modifiers() {
|
||||
continue;
|
||||
};
|
||||
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);
|
||||
state
|
||||
.structure
|
||||
structure.entry(entry.parent).or_insert_with(Vec::new);
|
||||
structure
|
||||
.get_mut(&entry.parent)
|
||||
.expect("Parent not found for 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();
|
||||
@@ -565,9 +575,7 @@ pub extern "C" fn set_structure_modifiers() {
|
||||
#[no_mangle]
|
||||
pub extern "C" fn clean_modifiers() {
|
||||
with_state_mut!(state, {
|
||||
state.structure.clear();
|
||||
state.scale_content.clear();
|
||||
state.modifiers.clear();
|
||||
state.shapes.clean_all();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -595,11 +603,16 @@ pub extern "C" fn set_modifiers() {
|
||||
.map(|data| TransformEntry::from_bytes(data.try_into().unwrap()))
|
||||
.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, {
|
||||
for entry in entries {
|
||||
state.modifiers.insert(entry.id, entry.transform);
|
||||
}
|
||||
state.rebuild_modifier_tiles();
|
||||
state.set_modifiers(modifiers);
|
||||
state.rebuild_modifier_tiles(ids);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn is_close_matrix(m: &Matrix, other: &Matrix) -> bool {
|
||||
is_close_to(m.scale_x(), other.scale_x())
|
||||
&& is_close_to(m.scale_y(), other.scale_y())
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::Matrix;
|
||||
use crate::render::{RenderState, SurfaceId};
|
||||
use crate::shapes::{BoolType, Path, Segment, Shape, StructureEntry, ToPath, Type};
|
||||
use crate::state::ShapesPool;
|
||||
use crate::state::ShapesPoolRef;
|
||||
use crate::uuid::Uuid;
|
||||
use bezier_rs::{Bezier, BezierHandles, ProjectionOptions, TValue};
|
||||
use glam::DVec2;
|
||||
@@ -387,9 +387,7 @@ fn beziers_to_segments(beziers: &[(BezierSource, Bezier)]) -> Vec<Segment> {
|
||||
pub fn bool_from_shapes(
|
||||
bool_type: BoolType,
|
||||
children_ids: &IndexSet<Uuid>,
|
||||
shapes: &ShapesPool,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
shapes: ShapesPoolRef,
|
||||
) -> Path {
|
||||
if children_ids.is_empty() {
|
||||
return Path::default();
|
||||
@@ -399,13 +397,13 @@ pub fn bool_from_shapes(
|
||||
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() {
|
||||
let Some(other) = shapes.get(&children_ids[idx]) else {
|
||||
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);
|
||||
|
||||
@@ -422,26 +420,15 @@ pub fn bool_from_shapes(
|
||||
current_path
|
||||
}
|
||||
|
||||
pub fn update_bool_to_path(
|
||||
shape: &Shape,
|
||||
shapes: &ShapesPool,
|
||||
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);
|
||||
#[allow(dead_code)]
|
||||
pub fn update_bool_to_path(shape: &mut Shape, shapes: ShapesPoolRef) {
|
||||
let children_ids = shape.children_ids(true);
|
||||
|
||||
let Type::Bool(bool_data) = &mut shape.shape_type else {
|
||||
return shape;
|
||||
return;
|
||||
};
|
||||
bool_data.path = bool_from_shapes(
|
||||
bool_data.bool_type,
|
||||
&children_ids,
|
||||
shapes,
|
||||
modifiers,
|
||||
structure,
|
||||
);
|
||||
shape
|
||||
|
||||
bool_data.path = bool_from_shapes(bool_data.bool_type, &children_ids, shapes);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
@@ -449,15 +436,15 @@ pub fn update_bool_to_path(
|
||||
pub fn debug_render_bool_paths(
|
||||
render_state: &mut RenderState,
|
||||
shape: &Shape,
|
||||
shapes: &ShapesPool,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
shapes: ShapesPoolRef,
|
||||
_modifiers: &HashMap<Uuid, Matrix>,
|
||||
_structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
) {
|
||||
let canvas = render_state.surfaces.canvas(SurfaceId::Strokes);
|
||||
|
||||
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 {
|
||||
return;
|
||||
@@ -471,13 +458,13 @@ pub fn debug_render_bool_paths(
|
||||
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() {
|
||||
let Some(other) = shapes.get(&children_ids[idx]) else {
|
||||
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);
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ mod ui;
|
||||
|
||||
use skia_safe::{self as skia, Matrix, RRect, Rect};
|
||||
use std::borrow::Cow;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::collections::HashSet;
|
||||
|
||||
use gpu_state::GpuState;
|
||||
use options::RenderOptions;
|
||||
@@ -22,16 +22,14 @@ pub use surfaces::{SurfaceId, Surfaces};
|
||||
|
||||
use crate::performance;
|
||||
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::uuid::Uuid;
|
||||
use crate::view::Viewbox;
|
||||
use crate::wapi;
|
||||
|
||||
use crate::math;
|
||||
use crate::math::bools;
|
||||
use indexmap::IndexSet;
|
||||
|
||||
pub use fonts::*;
|
||||
@@ -62,13 +60,11 @@ impl NodeRenderState {
|
||||
/// Calculates the clip bounds for child elements of a given shape.
|
||||
///
|
||||
/// 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,
|
||||
/// and any additional modifiers.
|
||||
/// when rendering. It takes into account the element's selection rectangle, transform.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `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,
|
||||
/// the bounds are translated by the negative of this offset, effectively moving
|
||||
/// the clipping region to compensate for coordinate system transformations.
|
||||
@@ -77,7 +73,6 @@ impl NodeRenderState {
|
||||
pub fn get_children_clip_bounds(
|
||||
&self,
|
||||
element: &Shape,
|
||||
modifiers: Option<&Matrix>,
|
||||
offset: Option<(f32, f32)>,
|
||||
) -> Option<(Rect, Option<Corners>, Matrix)> {
|
||||
if self.id.is_nil() || !element.clip() {
|
||||
@@ -96,10 +91,6 @@ impl NodeRenderState {
|
||||
transform.post_translate(bounds.center());
|
||||
transform.pre_translate(-bounds.center());
|
||||
|
||||
if let Some(modifier) = modifiers {
|
||||
transform.post_concat(modifier);
|
||||
}
|
||||
|
||||
let corners = match &element.shape_type {
|
||||
Type::Rect(data) => data.corners,
|
||||
Type::Frame(data) => data.corners,
|
||||
@@ -118,12 +109,10 @@ impl NodeRenderState {
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `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
|
||||
pub fn get_nested_shadow_clip_bounds(
|
||||
&self,
|
||||
element: &Shape,
|
||||
modifiers: Option<&Matrix>,
|
||||
shadow: &Shadow,
|
||||
) -> Option<(Rect, Option<Corners>, Matrix)> {
|
||||
if self.id.is_nil() {
|
||||
@@ -141,10 +130,6 @@ impl NodeRenderState {
|
||||
transform.post_translate(element.center());
|
||||
transform.pre_translate(-element.center());
|
||||
|
||||
if let Some(modifier) = modifiers {
|
||||
transform.post_concat(modifier);
|
||||
}
|
||||
|
||||
let corners = match &element.shape_type {
|
||||
Type::Rect(data) => data.corners,
|
||||
Type::Frame(data) => data.corners,
|
||||
@@ -274,28 +259,6 @@ pub fn get_cache_size(viewbox: Viewbox, scale: f32) -> skia::ISize {
|
||||
.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 {
|
||||
pub fn new(width: i32, height: i32) -> RenderState {
|
||||
// This needs to be done once per WebGL context.
|
||||
@@ -475,11 +438,7 @@ impl RenderState {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn render_shape(
|
||||
&mut self,
|
||||
shapes: &ShapesPool,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
shape: &Shape,
|
||||
scale_content: Option<&f32>,
|
||||
clip_bounds: Option<(Rect, Option<Corners>, Matrix)>,
|
||||
fills_surface_id: SurfaceId,
|
||||
strokes_surface_id: SurfaceId,
|
||||
@@ -489,12 +448,6 @@ impl RenderState {
|
||||
offset: Option<(f32, f32)>,
|
||||
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
|
||||
| strokes_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
|
||||
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.;
|
||||
for nested_blur in self.nested_blurs.iter().flatten() {
|
||||
if !nested_blur.hidden && nested_blur.blur_type == BlurType::LayerBlur {
|
||||
@@ -579,12 +528,12 @@ impl RenderState {
|
||||
|
||||
match &shape.shape_type {
|
||||
Type::SVGRaw(sr) => {
|
||||
if let Some(shape_modifiers) = modifiers.get(&shape.id) {
|
||||
self.surfaces
|
||||
.canvas(fills_surface_id)
|
||||
.concat(shape_modifiers);
|
||||
if let Some(svg_transform) = shape.svg_transform() {
|
||||
matrix.pre_concat(&svg_transform);
|
||||
}
|
||||
|
||||
self.surfaces.canvas(fills_surface_id).concat(&matrix);
|
||||
|
||||
if let Some(svg) = shape.svg.as_ref() {
|
||||
svg.render(self.surfaces.canvas(fills_surface_id))
|
||||
} else {
|
||||
@@ -750,20 +699,7 @@ impl RenderState {
|
||||
s.canvas().concat(&matrix);
|
||||
});
|
||||
|
||||
// For boolean shapes, there's no need to calculate children because
|
||||
// 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
|
||||
};
|
||||
let shape = &shape;
|
||||
|
||||
if shape.fills.is_empty()
|
||||
&& !matches!(shape.shape_type, Type::Group(_))
|
||||
@@ -836,12 +772,7 @@ impl RenderState {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_from_cache(
|
||||
&mut self,
|
||||
shapes: &ShapesPool,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
) {
|
||||
pub fn render_from_cache(&mut self, shapes: ShapesPoolRef) {
|
||||
let scale = self.get_cached_scale();
|
||||
if let Some(snapshot) = &self.cached_target_snapshot {
|
||||
let canvas = self.surfaces.canvas(SurfaceId::Target);
|
||||
@@ -874,21 +805,14 @@ impl RenderState {
|
||||
debug::render(self);
|
||||
}
|
||||
|
||||
ui::render(self, shapes, modifiers, structure);
|
||||
ui::render(self, shapes);
|
||||
debug::render_wasm_label(self);
|
||||
|
||||
self.flush_and_submit();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_render_loop(
|
||||
&mut self,
|
||||
tree: &ShapesPool,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
scale_content: &HashMap<Uuid, f32>,
|
||||
timestamp: i32,
|
||||
) -> Result<(), String> {
|
||||
pub fn start_render_loop(&mut self, tree: ShapesPoolRef, timestamp: i32) -> Result<(), String> {
|
||||
let scale = self.get_scale();
|
||||
self.tile_viewbox.update(self.viewbox, scale);
|
||||
|
||||
@@ -936,29 +860,20 @@ impl RenderState {
|
||||
self.current_tile = None;
|
||||
self.render_in_progress = true;
|
||||
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");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn process_animation_frame(
|
||||
&mut self,
|
||||
tree: &ShapesPool,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
scale_content: &HashMap<Uuid, f32>,
|
||||
tree: ShapesPoolRef,
|
||||
timestamp: i32,
|
||||
) -> Result<(), String> {
|
||||
performance::begin_measure!("process_animation_frame");
|
||||
if self.render_in_progress {
|
||||
if tree.len() != 0 {
|
||||
self.render_shape_tree_partial(
|
||||
tree,
|
||||
modifiers,
|
||||
structure,
|
||||
scale_content,
|
||||
timestamp,
|
||||
)?;
|
||||
self.render_shape_tree_partial(tree, timestamp)?;
|
||||
} else {
|
||||
println!("Empty tree");
|
||||
}
|
||||
@@ -1028,15 +943,7 @@ impl RenderState {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn render_shape_exit(
|
||||
&mut self,
|
||||
tree: &ShapesPool,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
element: &Shape,
|
||||
visited_mask: bool,
|
||||
scale_content: Option<&f32>,
|
||||
) {
|
||||
pub fn render_shape_exit(&mut self, element: &Shape, visited_mask: bool) {
|
||||
if visited_mask {
|
||||
// Because masked groups needs two rendering passes (first drawing
|
||||
// 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().clip_content = false;
|
||||
self.render_shape(
|
||||
tree,
|
||||
modifiers,
|
||||
structure,
|
||||
&element_strokes,
|
||||
scale_content,
|
||||
None,
|
||||
SurfaceId::Fills,
|
||||
SurfaceId::Strokes,
|
||||
@@ -1140,13 +1043,8 @@ impl RenderState {
|
||||
self.get_rect_bounds(rect)
|
||||
}
|
||||
|
||||
pub fn get_shape_extrect_bounds(
|
||||
&mut self,
|
||||
shape: &Shape,
|
||||
tree: &ShapesPool,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
) -> Rect {
|
||||
let rect = shape.extrect(tree, modifiers);
|
||||
pub fn get_shape_extrect_bounds(&mut self, shape: &Shape, tree: ShapesPoolRef) -> Rect {
|
||||
let rect = shape.extrect(tree);
|
||||
self.get_rect_bounds(rect)
|
||||
}
|
||||
|
||||
@@ -1183,12 +1081,8 @@ impl RenderState {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn render_drop_black_shadow(
|
||||
&mut self,
|
||||
shapes: &ShapesPool,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
shape: &Shape,
|
||||
shadow: &Shadow,
|
||||
scale_content: Option<&f32>,
|
||||
clip_bounds: Option<(Rect, Option<Corners>, Matrix)>,
|
||||
scale: f32,
|
||||
translation: (f32, f32),
|
||||
@@ -1237,11 +1131,7 @@ impl RenderState {
|
||||
.translate(translation);
|
||||
|
||||
self.render_shape(
|
||||
shapes,
|
||||
modifiers,
|
||||
structure,
|
||||
&plain_shape,
|
||||
scale_content,
|
||||
clip_bounds,
|
||||
SurfaceId::DropShadows,
|
||||
SurfaceId::DropShadows,
|
||||
@@ -1257,10 +1147,7 @@ impl RenderState {
|
||||
|
||||
pub fn render_shape_tree_partial_uncached(
|
||||
&mut self,
|
||||
tree: &ShapesPool,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
scale_content: &HashMap<Uuid, f32>,
|
||||
tree: ShapesPoolRef,
|
||||
timestamp: i32,
|
||||
) -> Result<(bool, bool), String> {
|
||||
let mut iteration = 0;
|
||||
@@ -1276,26 +1163,20 @@ impl RenderState {
|
||||
} = node_render_state;
|
||||
|
||||
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."
|
||||
.to_string(),
|
||||
)?;
|
||||
|
||||
let element = tree.get(&node_id).ok_or(format!(
|
||||
"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
|
||||
// it.
|
||||
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 {
|
||||
self.render_shape_exit(
|
||||
tree,
|
||||
modifiers,
|
||||
structure,
|
||||
element,
|
||||
visited_mask,
|
||||
scale_content.get(&element.id),
|
||||
);
|
||||
self.render_shape_exit(element, visited_mask);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1303,18 +1184,14 @@ impl RenderState {
|
||||
let transformed_element: Cow<Shape> = Cow::Borrowed(element);
|
||||
|
||||
let is_visible = transformed_element
|
||||
.extrect(tree, modifiers)
|
||||
.extrect(tree)
|
||||
.intersects(self.render_area)
|
||||
&& !transformed_element.hidden
|
||||
&& !transformed_element.visually_insignificant(
|
||||
self.get_scale(),
|
||||
tree,
|
||||
modifiers,
|
||||
);
|
||||
&& !transformed_element.visually_insignificant(self.get_scale(), tree);
|
||||
|
||||
if self.options.is_debug_visible() {
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -1359,12 +1236,8 @@ impl RenderState {
|
||||
|
||||
// First pass: Render shadow in black to establish alpha mask
|
||||
self.render_drop_black_shadow(
|
||||
tree,
|
||||
modifiers,
|
||||
structure,
|
||||
element,
|
||||
shadow,
|
||||
scale_content.get(&element.id),
|
||||
clip_bounds,
|
||||
scale,
|
||||
translation,
|
||||
@@ -1377,20 +1250,13 @@ impl RenderState {
|
||||
if shadow_shape.hidden {
|
||||
continue;
|
||||
}
|
||||
let clip_bounds = node_render_state.get_nested_shadow_clip_bounds(
|
||||
element,
|
||||
modifiers.get(&element.id),
|
||||
shadow,
|
||||
);
|
||||
let clip_bounds = node_render_state
|
||||
.get_nested_shadow_clip_bounds(element, shadow);
|
||||
|
||||
if !matches!(shadow_shape.shape_type, Type::Text(_)) {
|
||||
self.render_drop_black_shadow(
|
||||
tree,
|
||||
modifiers,
|
||||
structure,
|
||||
shadow_shape,
|
||||
shadow,
|
||||
scale_content.get(&element.id),
|
||||
clip_bounds,
|
||||
scale,
|
||||
translation,
|
||||
@@ -1423,11 +1289,7 @@ impl RenderState {
|
||||
new_shadow_paint.set_blend_mode(skia::BlendMode::SrcOver);
|
||||
|
||||
self.render_shape(
|
||||
tree,
|
||||
modifiers,
|
||||
structure,
|
||||
shadow_shape,
|
||||
scale_content.get(&element.id),
|
||||
clip_bounds,
|
||||
SurfaceId::DropShadows,
|
||||
SurfaceId::DropShadows,
|
||||
@@ -1464,11 +1326,7 @@ impl RenderState {
|
||||
.clear(skia::Color::TRANSPARENT);
|
||||
|
||||
self.render_shape(
|
||||
tree,
|
||||
modifiers,
|
||||
structure,
|
||||
element,
|
||||
scale_content.get(&element.id),
|
||||
clip_bounds,
|
||||
SurfaceId::Fills,
|
||||
SurfaceId::Strokes,
|
||||
@@ -1505,14 +1363,10 @@ impl RenderState {
|
||||
});
|
||||
|
||||
if element.is_recursive() {
|
||||
let children_clip_bounds = node_render_state.get_children_clip_bounds(
|
||||
element,
|
||||
modifiers.get(&element.id),
|
||||
None,
|
||||
);
|
||||
let children_clip_bounds =
|
||||
node_render_state.get_children_clip_bounds(element, None);
|
||||
|
||||
let mut children_ids =
|
||||
element.modified_children_ids(structure.get(&element.id), false);
|
||||
let mut children_ids = element.children_ids(false);
|
||||
|
||||
// Z-index ordering on Layouts
|
||||
if element.has_layout() {
|
||||
@@ -1545,10 +1399,7 @@ impl RenderState {
|
||||
|
||||
pub fn render_shape_tree_partial(
|
||||
&mut self,
|
||||
tree: &ShapesPool,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
scale_content: &HashMap<Uuid, f32>,
|
||||
tree: ShapesPoolRef,
|
||||
timestamp: i32,
|
||||
) -> Result<(), String> {
|
||||
let mut should_stop = false;
|
||||
@@ -1574,13 +1425,8 @@ impl RenderState {
|
||||
}
|
||||
} else {
|
||||
performance::begin_measure!("render_shape_tree::uncached");
|
||||
let (is_empty, early_return) = self.render_shape_tree_partial_uncached(
|
||||
tree,
|
||||
modifiers,
|
||||
structure,
|
||||
scale_content,
|
||||
timestamp,
|
||||
)?;
|
||||
let (is_empty, early_return) =
|
||||
self.render_shape_tree_partial_uncached(tree, timestamp)?;
|
||||
if early_return {
|
||||
return Ok(());
|
||||
}
|
||||
@@ -1614,7 +1460,7 @@ impl RenderState {
|
||||
let Some(root) = tree.get(&Uuid::nil()) else {
|
||||
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
|
||||
// let's check if there are more pending nodes
|
||||
@@ -1657,29 +1503,20 @@ impl RenderState {
|
||||
debug::render(self);
|
||||
}
|
||||
|
||||
ui::render(self, tree, modifiers, structure);
|
||||
ui::render(self, tree);
|
||||
debug::render_wasm_label(self);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_tiles_for_shape(
|
||||
&mut self,
|
||||
shape: &Shape,
|
||||
tree: &ShapesPool,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
) -> TileRect {
|
||||
pub fn get_tiles_for_shape(&mut self, shape: &Shape, tree: ShapesPoolRef) -> TileRect {
|
||||
let extrect = shape.extrect(tree);
|
||||
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(
|
||||
&mut self,
|
||||
shape: &Shape,
|
||||
tree: &ShapesPool,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
) {
|
||||
let TileRect(rsx, rsy, rex, rey) = self.get_tiles_for_shape(shape, tree, modifiers);
|
||||
pub fn update_tile_for(&mut self, shape: &Shape, tree: ShapesPoolRef) {
|
||||
let TileRect(rsx, rsy, rex, rey) = self.get_tiles_for_shape(shape, tree);
|
||||
let old_tiles: HashSet<tiles::Tile> = self
|
||||
.tiles
|
||||
.get_tiles_of(shape.id)
|
||||
@@ -1695,6 +1532,7 @@ impl RenderState {
|
||||
|
||||
// Then, add the shape to the new tiles
|
||||
for tile in new_tiles {
|
||||
self.remove_cached_tile_shape(tile, shape.id);
|
||||
self.tiles.add_shape_at(tile, shape.id);
|
||||
}
|
||||
}
|
||||
@@ -1706,27 +1544,18 @@ impl RenderState {
|
||||
self.tiles.remove_shape_at(tile, id);
|
||||
}
|
||||
|
||||
pub fn rebuild_tiles_shallow(
|
||||
&mut self,
|
||||
tree: &ShapesPool,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
) {
|
||||
pub fn rebuild_tiles_shallow(&mut self, tree: ShapesPoolRef) {
|
||||
performance::begin_measure!("rebuild_tiles_shallow");
|
||||
self.tiles.invalidate();
|
||||
self.surfaces.remove_cached_tiles(self.background_color);
|
||||
let mut nodes = vec![Uuid::nil()];
|
||||
while let Some(shape_id) = nodes.pop() {
|
||||
if let Some(shape) = tree.get(&shape_id) {
|
||||
let mut shape: Cow<Shape> = Cow::Borrowed(shape);
|
||||
if shape_id != Uuid::nil() {
|
||||
if let Some(modifier) = modifiers.get(&shape_id) {
|
||||
shape.to_mut().apply_transform(modifier);
|
||||
}
|
||||
self.update_tile_for(&shape, tree, modifiers);
|
||||
self.update_tile_for(shape, tree);
|
||||
} else {
|
||||
// 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() {
|
||||
nodes.push(*child_id);
|
||||
}
|
||||
@@ -1736,27 +1565,18 @@ impl RenderState {
|
||||
performance::end_measure!("rebuild_tiles_shallow");
|
||||
}
|
||||
|
||||
pub fn rebuild_tiles(
|
||||
&mut self,
|
||||
tree: &ShapesPool,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
) {
|
||||
pub fn rebuild_tiles(&mut self, tree: ShapesPoolRef) {
|
||||
performance::begin_measure!("rebuild_tiles");
|
||||
self.tiles.invalidate();
|
||||
self.surfaces.remove_cached_tiles(self.background_color);
|
||||
let mut nodes = vec![Uuid::nil()];
|
||||
while let Some(shape_id) = nodes.pop() {
|
||||
if let Some(shape) = tree.get(&shape_id) {
|
||||
let mut shape: Cow<Shape> = Cow::Borrowed(shape);
|
||||
if shape_id != Uuid::nil() {
|
||||
if let Some(modifier) = modifiers.get(&shape_id) {
|
||||
shape.to_mut().apply_transform(modifier);
|
||||
}
|
||||
self.update_tile_for(&shape, tree, modifiers);
|
||||
self.update_tile_for(shape, tree);
|
||||
}
|
||||
|
||||
let children = shape.modified_children_ids(structure.get(&shape.id), false);
|
||||
let children = shape.children_ids(false);
|
||||
for child_id in children.iter() {
|
||||
nodes.push(*child_id);
|
||||
}
|
||||
@@ -1776,64 +1596,26 @@ impl RenderState {
|
||||
pub fn invalidate_and_update_tiles(
|
||||
&mut self,
|
||||
shape_ids: &IndexSet<Uuid>,
|
||||
tree: &mut ShapesPool,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
tree: ShapesPoolMutRef<'_>,
|
||||
) {
|
||||
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 !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
|
||||
///
|
||||
/// This function applies transformation modifiers to shapes and updates their tiles.
|
||||
/// Additionally, it processes all ancestors of modified shapes to ensure their
|
||||
/// extended rectangles are properly recalculated and their tiles are updated.
|
||||
/// This is crucial for frames and groups that contain transformed children.
|
||||
pub fn rebuild_modifier_tiles(
|
||||
&mut self,
|
||||
tree: &mut ShapesPool,
|
||||
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 rebuild_modifier_tiles(&mut self, tree: ShapesPoolMutRef<'_>, ids: Vec<Uuid>) {
|
||||
let ancestors = all_with_ancestors(&ids, tree, false);
|
||||
self.invalidate_and_update_tiles(&ancestors, tree);
|
||||
}
|
||||
|
||||
pub fn get_scale(&self) -> f32 {
|
||||
|
||||
@@ -1,21 +1,12 @@
|
||||
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::{Shape, StructureEntry};
|
||||
use crate::state::ShapesPool;
|
||||
use crate::uuid::Uuid;
|
||||
use crate::shapes::Shape;
|
||||
use crate::state::ShapesPoolRef;
|
||||
|
||||
pub fn render_overlay(
|
||||
zoom: f32,
|
||||
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);
|
||||
pub fn render_overlay(zoom: f32, canvas: &skia::Canvas, shape: &Shape, shapes: ShapesPoolRef) {
|
||||
let cells = grid_cell_data(shape, shapes, true);
|
||||
|
||||
let mut paint = skia::Paint::default();
|
||||
paint.set_style(skia::PaintStyle::Stroke);
|
||||
|
||||
@@ -1,18 +1,9 @@
|
||||
use skia_safe::{self as skia, Color4f};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::{RenderState, ShapesPool, SurfaceId};
|
||||
use crate::math::Matrix;
|
||||
use super::{RenderState, ShapesPoolRef, SurfaceId};
|
||||
use crate::render::grid_layout;
|
||||
use crate::shapes::StructureEntry;
|
||||
use crate::uuid::Uuid;
|
||||
|
||||
pub fn render(
|
||||
render_state: &mut RenderState,
|
||||
shapes: &ShapesPool,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
) {
|
||||
pub fn render(render_state: &mut RenderState, shapes: ShapesPoolRef) {
|
||||
let canvas = render_state.surfaces.canvas(SurfaceId::UI);
|
||||
|
||||
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(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 std::borrow::Cow;
|
||||
use std::cell::OnceCell;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::collections::HashSet;
|
||||
use std::iter::once;
|
||||
|
||||
mod blend;
|
||||
@@ -47,10 +47,11 @@ pub use svgraw::*;
|
||||
pub use text::*;
|
||||
pub use transform::*;
|
||||
|
||||
use crate::math::bools as math_bools;
|
||||
use crate::math::{self, Bounds, Matrix, Point};
|
||||
use indexmap::IndexSet;
|
||||
|
||||
use crate::state::ShapesPool;
|
||||
use crate::state::ShapesPoolRef;
|
||||
|
||||
const MIN_VISIBLE_SIZE: f32 = 2.0;
|
||||
const ANTIALIAS_THRESHOLD: f32 = 15.0;
|
||||
@@ -180,6 +181,62 @@ pub struct Shape {
|
||||
pub shadows: Vec<Shadow>,
|
||||
pub layout_item: Option<LayoutItem>,
|
||||
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 {
|
||||
@@ -207,36 +264,36 @@ impl Shape {
|
||||
shadows: Vec::with_capacity(1),
|
||||
layout_item: None,
|
||||
extrect: OnceCell::new(),
|
||||
bounds: OnceCell::new(),
|
||||
svg_transform: None,
|
||||
ignore_constraints: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scale_content(&self, value: f32) -> Self {
|
||||
let mut result = self.clone();
|
||||
result.shape_type.scale_content(value);
|
||||
result
|
||||
.strokes
|
||||
.iter_mut()
|
||||
.for_each(|s| s.scale_content(value));
|
||||
result
|
||||
.shadows
|
||||
.iter_mut()
|
||||
.for_each(|s| s.scale_content(value));
|
||||
pub fn scale_content(&mut self, value: f32) {
|
||||
self.ignore_constraints = true;
|
||||
self.shape_type.scale_content(value);
|
||||
self.strokes.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);
|
||||
}
|
||||
|
||||
result
|
||||
.layout_item
|
||||
self.layout_item
|
||||
.iter_mut()
|
||||
.for_each(|i| i.scale_content(value));
|
||||
result
|
||||
}
|
||||
|
||||
pub fn invalidate_extrect(&mut self) {
|
||||
self.extrect = OnceCell::new();
|
||||
}
|
||||
|
||||
pub fn invalidate_bounds(&mut self) {
|
||||
self.bounds = OnceCell::new();
|
||||
}
|
||||
|
||||
pub fn set_parent(&mut self, id: Uuid) {
|
||||
self.parent_id = Some(id);
|
||||
}
|
||||
@@ -250,6 +307,10 @@ impl Shape {
|
||||
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 {
|
||||
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) {
|
||||
self.invalidate_extrect();
|
||||
self.invalidate_bounds();
|
||||
self.selrect.set_ltrb(left, top, right, bottom);
|
||||
if let Type::Text(ref mut text) = self.shape_type {
|
||||
text.update_layout(self.selrect);
|
||||
@@ -328,6 +390,10 @@ impl Shape {
|
||||
self.hidden = value;
|
||||
}
|
||||
|
||||
pub fn svg_transform(&self) -> Option<Matrix> {
|
||||
self.svg_transform
|
||||
}
|
||||
|
||||
// FIXME: These arguments could be grouped or simplified
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn set_flex_layout_child_data(
|
||||
@@ -617,13 +683,8 @@ impl Shape {
|
||||
self.selrect.width()
|
||||
}
|
||||
|
||||
pub fn visually_insignificant(
|
||||
&self,
|
||||
scale: f32,
|
||||
shapes_pool: &ShapesPool,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
) -> bool {
|
||||
let extrect = self.extrect(shapes_pool, modifiers);
|
||||
pub fn visually_insignificant(&self, scale: f32, shapes_pool: ShapesPoolRef) -> bool {
|
||||
let extrect = self.extrect(shapes_pool);
|
||||
extrect.width() * scale < MIN_VISIBLE_SIZE && extrect.height() * scale < MIN_VISIBLE_SIZE
|
||||
}
|
||||
|
||||
@@ -632,8 +693,7 @@ impl Shape {
|
||||
|| self.selrect.height() * scale > ANTIALIAS_THRESHOLD
|
||||
}
|
||||
|
||||
// TODO: Maybe store this inside the shape
|
||||
pub fn bounds(&self) -> Bounds {
|
||||
pub fn calculate_bounds(&self) -> Bounds {
|
||||
let mut bounds = Bounds::new(
|
||||
Point::new(self.selrect.x(), self.selrect.y()),
|
||||
Point::new(self.selrect.x() + self.selrect.width(), self.selrect.y()),
|
||||
@@ -659,18 +719,18 @@ impl Shape {
|
||||
bounds
|
||||
}
|
||||
|
||||
pub fn bounds(&self) -> Bounds {
|
||||
*self.bounds.get_or_init(|| self.calculate_bounds())
|
||||
}
|
||||
|
||||
pub fn selrect(&self) -> math::Rect {
|
||||
self.selrect
|
||||
}
|
||||
|
||||
pub fn extrect(
|
||||
&self,
|
||||
shapes_pool: &ShapesPool,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
) -> math::Rect {
|
||||
pub fn extrect(&self, shapes_pool: ShapesPoolRef) -> math::Rect {
|
||||
*self
|
||||
.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 {
|
||||
@@ -783,8 +843,7 @@ impl Shape {
|
||||
fn apply_children_bounds(
|
||||
&self,
|
||||
mut rect: math::Rect,
|
||||
shapes_pool: &ShapesPool,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
shapes_pool: ShapesPoolRef,
|
||||
) -> math::Rect {
|
||||
let include_children = match self.shape_type {
|
||||
Type::Group(_) => true,
|
||||
@@ -795,15 +854,7 @@ impl Shape {
|
||||
if include_children {
|
||||
for child_id in self.children_ids(false) {
|
||||
if let Some(child_shape) = shapes_pool.get(&child_id) {
|
||||
// Create a copy of the child shape to apply any transformations
|
||||
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);
|
||||
rect.join(child_shape.extrect(shapes_pool));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -811,12 +862,8 @@ impl Shape {
|
||||
rect
|
||||
}
|
||||
|
||||
pub fn calculate_extrect(
|
||||
&self,
|
||||
shapes_pool: &ShapesPool,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
) -> math::Rect {
|
||||
let shape = self.transformed(modifiers.get(&self.id));
|
||||
pub fn calculate_extrect(&self, shapes_pool: ShapesPoolRef) -> math::Rect {
|
||||
let shape = self;
|
||||
let max_stroke = Stroke::max_bounds_width(shape.strokes.iter(), shape.is_open());
|
||||
|
||||
let mut rect = match &shape.shape_type {
|
||||
@@ -830,7 +877,7 @@ impl Shape {
|
||||
}
|
||||
Type::Text(text_content) => {
|
||||
// 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()
|
||||
}
|
||||
_ => shape.bounds().to_rect(),
|
||||
@@ -839,7 +886,7 @@ impl Shape {
|
||||
rect = self.apply_stroke_bounds(rect, max_stroke);
|
||||
rect = self.apply_shadow_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
|
||||
}
|
||||
@@ -860,6 +907,7 @@ impl Shape {
|
||||
self.children.first()
|
||||
}
|
||||
|
||||
// TODO: Review this to use children_ids_iter instead
|
||||
pub fn children_ids(&self, include_hidden: bool) -> IndexSet<Uuid> {
|
||||
if include_hidden {
|
||||
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(
|
||||
&self,
|
||||
shapes: &ShapesPool,
|
||||
shapes: ShapesPoolRef,
|
||||
include_hidden: bool,
|
||||
include_self: bool,
|
||||
) -> 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 {
|
||||
let mut matrix = Matrix::new_identity();
|
||||
matrix.post_translate(self.left_top());
|
||||
@@ -954,7 +979,7 @@ impl Shape {
|
||||
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 current_id = self.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) {
|
||||
self.invalidate_extrect();
|
||||
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 Some(path) = shape_type.path_mut() {
|
||||
path.transform(transform);
|
||||
}
|
||||
} else if let Type::Text(text) = &mut self.shape_type {
|
||||
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);
|
||||
if let Some(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()
|
||||
}
|
||||
|
||||
@@ -1195,43 +1260,6 @@ impl Shape {
|
||||
.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> {
|
||||
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 crate::shapes::{
|
||||
ConstraintH, ConstraintV, Frame, Group, GrowType, Layout, Modifier, Shape, StructureEntry,
|
||||
TransformEntry, Type,
|
||||
ConstraintH, ConstraintV, Frame, Group, GrowType, Layout, Modifier, Shape, TransformEntry, Type,
|
||||
};
|
||||
use crate::state::{ShapesPool, State};
|
||||
use crate::state::{ShapesPoolRef, State};
|
||||
use crate::uuid::Uuid;
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn propagate_children(
|
||||
shape: &Shape,
|
||||
shapes: &ShapesPool,
|
||||
shapes: ShapesPoolRef,
|
||||
parent_bounds_before: &Bounds,
|
||||
parent_bounds_after: &Bounds,
|
||||
transform: Matrix,
|
||||
bounds: &HashMap<Uuid, Bounds>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
scale_content: &HashMap<Uuid, f32>,
|
||||
) -> 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) {
|
||||
return VecDeque::new();
|
||||
@@ -40,8 +37,6 @@ fn propagate_children(
|
||||
continue;
|
||||
};
|
||||
|
||||
let ignore_constraints = scale_content.contains_key(child_id);
|
||||
|
||||
let child_bounds = bounds.find(child);
|
||||
|
||||
let constraint_h = match &shape.shape_type {
|
||||
@@ -79,7 +74,7 @@ fn propagate_children(
|
||||
constraint_h,
|
||||
constraint_v,
|
||||
transform,
|
||||
ignore_constraints,
|
||||
child.ignore_constraints,
|
||||
);
|
||||
|
||||
result.push_back(Modifier::transform(*child_id, transform));
|
||||
@@ -88,18 +83,15 @@ fn propagate_children(
|
||||
result
|
||||
}
|
||||
|
||||
// FIXME: PERFORMANCE
|
||||
fn calculate_group_bounds(
|
||||
shape: &Shape,
|
||||
shapes: &ShapesPool,
|
||||
shapes: ShapesPoolRef,
|
||||
bounds: &HashMap<Uuid, Bounds>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
) -> Option<Bounds> {
|
||||
let shape_bounds = bounds.find(shape);
|
||||
let mut result = Vec::<Point>::new();
|
||||
|
||||
let children_ids = shape.modified_children_ids(structure.get(&shape.id), true);
|
||||
for child_id in children_ids.iter() {
|
||||
for child_id in shape.children_ids_iter(true) {
|
||||
let Some(child) = shapes.get(child_id) else {
|
||||
continue;
|
||||
};
|
||||
@@ -107,33 +99,29 @@ fn calculate_group_bounds(
|
||||
let child_bounds = bounds.find(child);
|
||||
result.append(&mut child_bounds.points());
|
||||
}
|
||||
|
||||
shape_bounds.with_points(result)
|
||||
}
|
||||
|
||||
fn calculate_bool_bounds(
|
||||
shape: &Shape,
|
||||
shapes: &ShapesPool,
|
||||
shapes: ShapesPoolRef,
|
||||
bounds: &HashMap<Uuid, Bounds>,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
) -> Option<Bounds> {
|
||||
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 {
|
||||
return Some(shape_bounds);
|
||||
};
|
||||
|
||||
let path = bools::bool_from_shapes(
|
||||
bool_data.bool_type,
|
||||
&children_ids,
|
||||
shapes,
|
||||
modifiers,
|
||||
structure,
|
||||
);
|
||||
let mut subtree = shapes.subtree(&shape.id);
|
||||
subtree.set_modifiers(modifiers.clone());
|
||||
|
||||
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) {
|
||||
@@ -238,8 +226,6 @@ fn propagate_transform(
|
||||
&shape_bounds_after,
|
||||
transform,
|
||||
bounds,
|
||||
&state.structure,
|
||||
&state.scale_content,
|
||||
);
|
||||
entries.append(&mut children);
|
||||
}
|
||||
@@ -277,55 +263,56 @@ fn propagate_reflow(
|
||||
let shapes = &state.shapes;
|
||||
let mut reflow_parent = false;
|
||||
|
||||
if reflown.contains(id) {
|
||||
return;
|
||||
}
|
||||
|
||||
match &shape.shape_type {
|
||||
Type::Frame(Frame {
|
||||
layout: Some(_), ..
|
||||
}) => {
|
||||
if !reflown.contains(id) {
|
||||
let mut skip_reflow = false;
|
||||
if shape.is_layout_horizontal_fill() || shape.is_layout_vertical_fill() {
|
||||
if let Some(parent_id) = shape.parent_id {
|
||||
if !reflown.contains(&parent_id) {
|
||||
// If this is a fill layout but the parent has not been reflown yet
|
||||
// we wait for the next iteration for reflow
|
||||
skip_reflow = true;
|
||||
reflow_parent = true;
|
||||
}
|
||||
let mut skip_reflow = false;
|
||||
if shape.is_layout_horizontal_fill() || shape.is_layout_vertical_fill() {
|
||||
if let Some(parent_id) = shape.parent_id {
|
||||
if !reflown.contains(&parent_id) {
|
||||
// If this is a fill layout but the parent has not been reflown yet
|
||||
// we wait for the next iteration for reflow
|
||||
skip_reflow = true;
|
||||
reflow_parent = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if shape.is_layout_vertical_auto() || shape.is_layout_horizontal_auto() {
|
||||
reflow_parent = true;
|
||||
}
|
||||
if shape.is_layout_vertical_auto() || shape.is_layout_horizontal_auto() {
|
||||
reflow_parent = true;
|
||||
}
|
||||
|
||||
if !skip_reflow {
|
||||
layout_reflows.push(*id);
|
||||
}
|
||||
if !skip_reflow {
|
||||
layout_reflows.push(*id);
|
||||
}
|
||||
}
|
||||
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]) {
|
||||
let child_bounds = bounds.find(child);
|
||||
bounds.insert(shape.id, child_bounds);
|
||||
reflow_parent = true;
|
||||
}
|
||||
reflown.insert(*id);
|
||||
}
|
||||
Type::Group(_) => {
|
||||
if let Some(shape_bounds) =
|
||||
calculate_group_bounds(shape, shapes, bounds, &state.structure)
|
||||
{
|
||||
if let Some(shape_bounds) = calculate_group_bounds(shape, shapes, bounds) {
|
||||
bounds.insert(shape.id, shape_bounds);
|
||||
reflow_parent = true;
|
||||
}
|
||||
reflown.insert(*id);
|
||||
}
|
||||
Type::Bool(_) => {
|
||||
if let Some(shape_bounds) =
|
||||
calculate_bool_bounds(shape, shapes, bounds, modifiers, &state.structure)
|
||||
{
|
||||
if let Some(shape_bounds) = calculate_bool_bounds(shape, shapes, bounds, modifiers) {
|
||||
bounds.insert(shape.id, shape_bounds);
|
||||
reflow_parent = true;
|
||||
}
|
||||
reflown.insert(*id);
|
||||
}
|
||||
_ => {
|
||||
// Other shapes don't have to be reflown
|
||||
@@ -352,35 +339,17 @@ fn reflow_shape(
|
||||
|
||||
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 {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(Layout::FlexLayout(layout_data, flex_data)) = &frame_data.layout {
|
||||
let mut children = flex_layout::reflow_flex_layout(
|
||||
shape,
|
||||
layout_data,
|
||||
flex_data,
|
||||
shapes,
|
||||
bounds,
|
||||
&state.structure,
|
||||
);
|
||||
let mut children =
|
||||
flex_layout::reflow_flex_layout(shape, layout_data, flex_data, shapes, bounds);
|
||||
entries.append(&mut children);
|
||||
} else if let Some(Layout::GridLayout(layout_data, grid_data)) = &frame_data.layout {
|
||||
let mut children = grid_layout::reflow_grid_layout(
|
||||
shape,
|
||||
layout_data,
|
||||
grid_data,
|
||||
shapes,
|
||||
bounds,
|
||||
&state.structure,
|
||||
);
|
||||
let mut children =
|
||||
grid_layout::reflow_grid_layout(shape, layout_data, grid_data, shapes, bounds);
|
||||
entries.append(&mut children);
|
||||
}
|
||||
reflown.insert(*id);
|
||||
@@ -396,12 +365,6 @@ pub fn propagate_modifiers(
|
||||
.map(|entry| Modifier::Transform(entry.clone()))
|
||||
.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 bounds = HashMap::<Uuid, Bounds>::new();
|
||||
let mut reflown = HashSet::<Uuid>::new();
|
||||
@@ -456,6 +419,7 @@ mod tests {
|
||||
|
||||
use crate::math::{Matrix, Point};
|
||||
use crate::shapes::*;
|
||||
use crate::state::ShapesPool;
|
||||
|
||||
#[test]
|
||||
fn test_propagate_shape() {
|
||||
@@ -494,8 +458,6 @@ mod tests {
|
||||
&bounds_after,
|
||||
transform,
|
||||
&HashMap::new(),
|
||||
&HashMap::new(),
|
||||
&HashMap::new(),
|
||||
);
|
||||
|
||||
assert_eq!(result.len(), 1);
|
||||
@@ -526,8 +488,7 @@ mod tests {
|
||||
|
||||
let parent = shapes.get(&parent_id).unwrap();
|
||||
|
||||
let bounds =
|
||||
calculate_group_bounds(parent, &shapes, &HashMap::new(), &HashMap::new()).unwrap();
|
||||
let bounds = calculate_group_bounds(parent, &shapes, &HashMap::new()).unwrap();
|
||||
|
||||
assert_eq!(bounds.width(), 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::shapes::{
|
||||
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 std::collections::{HashMap, VecDeque};
|
||||
@@ -179,13 +179,12 @@ fn initialize_tracks(
|
||||
layout_bounds: &Bounds,
|
||||
layout_axis: &LayoutAxis,
|
||||
flex_data: &FlexData,
|
||||
shapes: &ShapesPool,
|
||||
shapes: ShapesPoolRef,
|
||||
bounds: &HashMap<Uuid, Bounds>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
) -> Vec<TrackData> {
|
||||
let mut tracks = Vec::<TrackData>::new();
|
||||
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;
|
||||
|
||||
if flex_data.is_reverse() {
|
||||
@@ -433,9 +432,8 @@ fn calculate_track_data(
|
||||
layout_data: &LayoutData,
|
||||
flex_data: &FlexData,
|
||||
layout_bounds: &Bounds,
|
||||
shapes: &ShapesPool,
|
||||
shapes: ShapesPoolRef,
|
||||
bounds: &HashMap<Uuid, Bounds>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
) -> Vec<TrackData> {
|
||||
let layout_axis = LayoutAxis::new(shape, layout_bounds, layout_data, flex_data);
|
||||
let mut tracks = initialize_tracks(
|
||||
@@ -445,7 +443,6 @@ fn calculate_track_data(
|
||||
flex_data,
|
||||
shapes,
|
||||
bounds,
|
||||
structure,
|
||||
);
|
||||
|
||||
distribute_fill_main_space(&layout_axis, &mut tracks);
|
||||
@@ -574,22 +571,13 @@ pub fn reflow_flex_layout(
|
||||
shape: &Shape,
|
||||
layout_data: &LayoutData,
|
||||
flex_data: &FlexData,
|
||||
shapes: &ShapesPool,
|
||||
shapes: ShapesPoolRef,
|
||||
bounds: &mut HashMap<Uuid, Bounds>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
) -> VecDeque<Modifier> {
|
||||
let mut result = VecDeque::new();
|
||||
let layout_bounds = &bounds.find(shape);
|
||||
let layout_axis = LayoutAxis::new(shape, layout_bounds, layout_data, flex_data);
|
||||
let tracks = calculate_track_data(
|
||||
shape,
|
||||
layout_data,
|
||||
flex_data,
|
||||
layout_bounds,
|
||||
shapes,
|
||||
bounds,
|
||||
structure,
|
||||
);
|
||||
let tracks = calculate_track_data(shape, layout_data, flex_data, layout_bounds, shapes, bounds);
|
||||
|
||||
for track in tracks.iter() {
|
||||
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::{
|
||||
AlignContent, AlignItems, AlignSelf, Frame, GridCell, GridData, GridTrack, GridTrackType,
|
||||
JustifyContent, JustifyItems, JustifySelf, Layout, LayoutData, LayoutItem, Modifier, Shape,
|
||||
StructureEntry, Type,
|
||||
Type,
|
||||
};
|
||||
use crate::state::ShapesPool;
|
||||
use crate::state::ShapesPoolRef;
|
||||
use crate::uuid::Uuid;
|
||||
use indexmap::IndexSet;
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
@@ -45,7 +45,7 @@ pub fn calculate_tracks(
|
||||
grid_data: &GridData,
|
||||
layout_bounds: &Bounds,
|
||||
cells: &Vec<GridCell>,
|
||||
shapes: &ShapesPool,
|
||||
shapes: ShapesPoolRef,
|
||||
bounds: &HashMap<Uuid, Bounds>,
|
||||
) -> Vec<TrackData> {
|
||||
let layout_size = if is_column {
|
||||
@@ -122,7 +122,7 @@ fn set_auto_base_size(
|
||||
column: bool,
|
||||
tracks: &mut [TrackData],
|
||||
cells: &Vec<GridCell>,
|
||||
shapes: &ShapesPool,
|
||||
shapes: ShapesPoolRef,
|
||||
bounds: &HashMap<Uuid, Bounds>,
|
||||
) {
|
||||
for cell in cells {
|
||||
@@ -173,7 +173,7 @@ fn set_auto_multi_span(
|
||||
column: bool,
|
||||
tracks: &mut [TrackData],
|
||||
cells: &[GridCell],
|
||||
shapes: &ShapesPool,
|
||||
shapes: ShapesPoolRef,
|
||||
bounds: &HashMap<Uuid, Bounds>,
|
||||
) {
|
||||
// Remove groups with flex (will be set in flex_multi_span)
|
||||
@@ -248,7 +248,7 @@ fn set_flex_multi_span(
|
||||
layout_data: &LayoutData,
|
||||
tracks: &mut [TrackData],
|
||||
cells: &[GridCell],
|
||||
shapes: &ShapesPool,
|
||||
shapes: ShapesPoolRef,
|
||||
bounds: &HashMap<Uuid, Bounds>,
|
||||
) {
|
||||
// Remove groups without flex
|
||||
@@ -539,7 +539,7 @@ fn cell_bounds(
|
||||
pub fn create_cell_data<'a>(
|
||||
layout_bounds: &Bounds,
|
||||
children: &IndexSet<Uuid>,
|
||||
shapes: &'a ShapesPool,
|
||||
shapes: ShapesPoolRef<'a>,
|
||||
cells: &Vec<GridCell>,
|
||||
column_tracks: &[TrackData],
|
||||
row_tracks: &[TrackData],
|
||||
@@ -602,9 +602,7 @@ pub fn create_cell_data<'a>(
|
||||
|
||||
pub fn grid_cell_data<'a>(
|
||||
shape: &Shape,
|
||||
shapes: &'a ShapesPool,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
shapes: ShapesPoolRef<'a>,
|
||||
allow_empty: bool,
|
||||
) -> Vec<CellData<'a>> {
|
||||
let Type::Frame(Frame {
|
||||
@@ -616,26 +614,8 @@ pub fn grid_cell_data<'a>(
|
||||
};
|
||||
|
||||
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 children = shape.modified_children_ids(structure.get(&shape.id), 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 children = shape.children_ids(false);
|
||||
|
||||
let column_tracks = calculate_tracks(
|
||||
true,
|
||||
@@ -723,13 +703,12 @@ pub fn reflow_grid_layout(
|
||||
shape: &Shape,
|
||||
layout_data: &LayoutData,
|
||||
grid_data: &GridData,
|
||||
shapes: &ShapesPool,
|
||||
shapes: ShapesPoolRef,
|
||||
bounds: &mut HashMap<Uuid, Bounds>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
) -> VecDeque<Modifier> {
|
||||
let mut result = VecDeque::new();
|
||||
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(
|
||||
true,
|
||||
|
||||
@@ -1,22 +1,13 @@
|
||||
use skia_safe::Matrix;
|
||||
|
||||
use super::{Corners, Path, Segment, Shape, StructureEntry, Type};
|
||||
use super::{Corners, Path, Segment, Shape, Type};
|
||||
use crate::math;
|
||||
|
||||
use crate::shapes::text_paths::TextPaths;
|
||||
use crate::state::ShapesPool;
|
||||
use crate::uuid::Uuid;
|
||||
use std::collections::HashMap;
|
||||
use crate::state::ShapesPoolRef;
|
||||
|
||||
const BEZIER_CIRCLE_C: f32 = 0.551_915_05;
|
||||
|
||||
pub trait ToPath {
|
||||
fn to_path(
|
||||
&self,
|
||||
shapes: &ShapesPool,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
) -> Path;
|
||||
fn to_path(&self, shapes: ShapesPoolRef) -> Path;
|
||||
}
|
||||
|
||||
enum CornerType {
|
||||
@@ -180,34 +171,28 @@ fn transform_segments(segments: Vec<Segment>, shape: &Shape) -> Vec<Segment> {
|
||||
}
|
||||
|
||||
impl ToPath for Shape {
|
||||
fn to_path(
|
||||
&self,
|
||||
shapes: &ShapesPool,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
) -> Path {
|
||||
let shape = self.transformed(modifiers.get(&self.id));
|
||||
match shape.shape_type {
|
||||
fn to_path(&self, shapes: ShapesPoolRef) -> Path {
|
||||
match &self.shape_type {
|
||||
Type::Frame(ref frame) => {
|
||||
let children = shape.modified_children_ids(structure.get(&shape.id), true);
|
||||
let mut result = Path::new(rect_segments(&shape, frame.corners));
|
||||
let children = self.children_ids(true);
|
||||
let mut result = Path::new(rect_segments(self, frame.corners));
|
||||
for id in children {
|
||||
let Some(shape) = shapes.get(&id) else {
|
||||
continue;
|
||||
};
|
||||
result = join_paths(result, shape.to_path(shapes, modifiers, structure));
|
||||
result = join_paths(result, shape.to_path(shapes));
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
Type::Group(_) => {
|
||||
let children = shape.modified_children_ids(structure.get(&shape.id), true);
|
||||
let children = self.children_ids(true);
|
||||
let mut result = Path::default();
|
||||
for id in children {
|
||||
let Some(shape) = shapes.get(&id) else {
|
||||
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
|
||||
let mut segments = result.segments().clone();
|
||||
@@ -215,13 +200,13 @@ impl ToPath for Shape {
|
||||
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(),
|
||||
|
||||
@@ -232,7 +217,7 @@ impl ToPath for Shape {
|
||||
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 text_editor;
|
||||
pub use shapes_pool::*;
|
||||
pub use shapes_pool::{ShapesPool, ShapesPoolMutRef, ShapesPoolRef};
|
||||
pub use text_editor::*;
|
||||
|
||||
use crate::render::RenderState;
|
||||
use crate::shapes::Shape;
|
||||
use crate::shapes::StructureEntry;
|
||||
use crate::tiles;
|
||||
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.
|
||||
/// Note that rust-skia data structures are not thread safe, so a state
|
||||
/// must not be shared between different Web Workers.
|
||||
pub(crate) struct State {
|
||||
pub(crate) struct State<'a> {
|
||||
pub render_state: RenderState,
|
||||
pub text_editor_state: TextEditorState,
|
||||
pub current_id: Option<Uuid>,
|
||||
pub shapes: ShapesPool,
|
||||
pub modifiers: HashMap<Uuid, skia::Matrix>,
|
||||
pub scale_content: HashMap<Uuid, f32>,
|
||||
pub structure: HashMap<Uuid, Vec<StructureEntry>>,
|
||||
pub shapes: ShapesPool<'a>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
impl<'a> State<'a> {
|
||||
pub fn new(width: i32, height: i32) -> Self {
|
||||
State {
|
||||
render_state: RenderState::new(width, height),
|
||||
text_editor_state: TextEditorState::new(),
|
||||
current_id: None,
|
||||
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) {
|
||||
self.render_state
|
||||
.render_from_cache(&self.shapes, &self.modifiers, &self.structure);
|
||||
self.render_state.render_from_cache(&self.shapes);
|
||||
}
|
||||
|
||||
pub fn start_render_loop(&mut self, timestamp: i32) -> Result<(), String> {
|
||||
self.render_state.start_render_loop(
|
||||
&self.shapes,
|
||||
&self.modifiers,
|
||||
&self.structure,
|
||||
&self.scale_content,
|
||||
timestamp,
|
||||
)?;
|
||||
self.render_state
|
||||
.start_render_loop(&self.shapes, timestamp)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn process_animation_frame(&mut self, timestamp: i32) -> Result<(), String> {
|
||||
self.render_state.process_animation_frame(
|
||||
&self.shapes,
|
||||
&self.modifiers,
|
||||
&self.structure,
|
||||
&self.scale_content,
|
||||
timestamp,
|
||||
)?;
|
||||
self.render_state
|
||||
.process_animation_frame(&self.shapes, timestamp)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -110,12 +92,16 @@ impl State {
|
||||
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
|
||||
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) =
|
||||
self.render_state
|
||||
.get_tiles_for_shape(shape, &self.shapes, &self.modifiers);
|
||||
self.render_state.get_tiles_for_shape(shape, &self.shapes);
|
||||
for x in rsx..=rex {
|
||||
for y in rsy..=rey {
|
||||
let tile = tiles::Tile(x, y);
|
||||
@@ -148,6 +134,8 @@ impl State {
|
||||
panic!("Invalid current shape")
|
||||
};
|
||||
shape.set_parent(id);
|
||||
|
||||
// TODO this clone doesn't seem necessary
|
||||
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) {
|
||||
if let Some(shape) = self.shapes.get(&shape_id) {
|
||||
self.render_state
|
||||
.update_tile_for(shape, &self.shapes, &self.modifiers);
|
||||
self.render_state.update_tile_for(shape, &self.shapes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,25 +155,32 @@ impl State {
|
||||
let Some(shape) = self.current_shape() else {
|
||||
panic!("Invalid current shape")
|
||||
};
|
||||
// TODO: Remove this clone
|
||||
if !shape.id.is_nil() {
|
||||
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) {
|
||||
self.render_state
|
||||
.rebuild_tiles_shallow(&self.shapes, &self.modifiers, &self.structure);
|
||||
self.render_state.rebuild_tiles_shallow(&self.shapes);
|
||||
}
|
||||
|
||||
pub fn rebuild_tiles(&mut self) {
|
||||
self.render_state
|
||||
.rebuild_tiles(&self.shapes, &self.modifiers, &self.structure);
|
||||
self.render_state.rebuild_tiles(&self.shapes);
|
||||
}
|
||||
|
||||
pub fn rebuild_modifier_tiles(&mut self) {
|
||||
self.render_state
|
||||
.rebuild_modifier_tiles(&mut self.shapes, &self.modifiers);
|
||||
pub fn rebuild_modifier_tiles(&mut self, ids: Vec<Uuid>) {
|
||||
// SAFETY: We're extending the lifetime of the mutable borrow to 'a.
|
||||
// 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 {
|
||||
@@ -216,7 +192,7 @@ impl State {
|
||||
let bounds = shape.bounds();
|
||||
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 {
|
||||
let points = &[
|
||||
@@ -235,4 +211,8 @@ impl State {
|
||||
|
||||
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 crate::performance;
|
||||
use crate::shapes;
|
||||
use crate::shapes::Shape;
|
||||
use crate::uuid::Uuid;
|
||||
|
||||
use crate::shapes::StructureEntry;
|
||||
use crate::skia;
|
||||
|
||||
use std::cell::OnceCell;
|
||||
|
||||
const SHAPES_POOL_ALLOC_MULTIPLIER: f32 = 1.3;
|
||||
|
||||
/// 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
|
||||
/// 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
|
||||
/// in a contiguous memory block.
|
||||
///
|
||||
pub struct ShapesPool {
|
||||
pub struct ShapesPoolImpl<'a> {
|
||||
shapes: Vec<Shape>,
|
||||
shapes_uuid_to_idx: HashMap<Uuid, 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 {
|
||||
ShapesPool {
|
||||
ShapesPoolImpl {
|
||||
shapes: vec![],
|
||||
counter: 0,
|
||||
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;
|
||||
}
|
||||
|
||||
// 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
|
||||
.extend(iter::repeat_with(|| Shape::new(Uuid::nil())).take(additional as usize));
|
||||
performance::end_measure!("shapes_pool_initialize");
|
||||
}
|
||||
|
||||
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 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
|
||||
.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;
|
||||
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;
|
||||
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 {
|
||||
@@ -66,17 +199,57 @@ impl ShapesPool {
|
||||
}
|
||||
|
||||
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> {
|
||||
let idx = *self.shapes_uuid_to_idx.get(id)?;
|
||||
let idx = *self.shapes_uuid_to_idx.get(&id)?;
|
||||
Some(&mut self.shapes[idx])
|
||||
}
|
||||
|
||||
pub fn get(&self, id: &Uuid) -> Option<&Shape> {
|
||||
let idx = *self.shapes_uuid_to_idx.get(id)?;
|
||||
Some(&self.shapes[idx])
|
||||
pub fn get(&self, id: &Uuid) -> Option<&'a Shape> {
|
||||
let idx = *self.shapes_uuid_to_idx.get(&id)?;
|
||||
|
||||
// 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)]
|
||||
@@ -87,4 +260,135 @@ impl ShapesPool {
|
||||
pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, Shape> {
|
||||
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::view::Viewbox;
|
||||
use indexmap::IndexSet;
|
||||
use skia_safe as skia;
|
||||
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.
|
||||
pub struct TileHashMap {
|
||||
grid: HashMap<Tile, IndexSet<Uuid>>,
|
||||
grid: HashMap<Tile, HashSet<Uuid>>,
|
||||
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)
|
||||
}
|
||||
|
||||
pub fn remove_shape_at(&mut self, tile: Tile, id: Uuid) {
|
||||
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) {
|
||||
|
||||
@@ -213,7 +213,7 @@ pub extern "C" fn set_shape_path_content() {
|
||||
pub extern "C" fn current_to_path() -> *mut u8 {
|
||||
let mut result = Vec::<RawSegmentData>::default();
|
||||
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
|
||||
.segments()
|
||||
.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 result;
|
||||
with_state!(state, {
|
||||
let path = math::bools::bool_from_shapes(
|
||||
bool_type,
|
||||
&entries,
|
||||
&state.shapes,
|
||||
&state.modifiers,
|
||||
&state.structure,
|
||||
);
|
||||
let path = math::bools::bool_from_shapes(bool_type, &entries, &state.shapes);
|
||||
result = path
|
||||
.segments()
|
||||
.iter()
|
||||
|
||||
Reference in New Issue
Block a user