0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-04-04 19:11:20 -05:00

Improved text rendering performance

This commit is contained in:
alonso.torres 2023-01-04 16:12:00 +01:00
parent 837b52aea1
commit 84e9f69213
7 changed files with 148 additions and 85 deletions

View file

@ -415,10 +415,14 @@
[id]
(ptk/reify ::clean-text-modifier
ptk/WatchEvent
(watch [_ _ _]
(->> (rx/of #(update % :workspace-text-modifier dissoc id))
;; We delay a bit the change so there is no weird transition to the user
(rx/delay 50)))))
(watch [_ state _]
(let [current-value (dm/get-in state [:workspace-text-modifier id])]
;; We only dissocc the value when hasn't change after a time
(->> (rx/of (fn [state]
(cond-> state
(identical? (dm/get-in state [:workspace-text-modifier id]) current-value)
(update :workspace-text-modifier dissoc id))))
(rx/delay 100))))))
(defn remove-text-modifier
[id]

View file

@ -601,9 +601,17 @@
move-events (->> stream
(rx/filter (ptk/type? ::nudge-selected-shapes))
(rx/filter #(= direction (deref %))))
stopper (->> move-events
(rx/debounce 100)
(rx/take 1))
stopper
(->> move-events
;; We stop when there's been 1s without movement or after 250ms after a key-up
(rx/switch-map #(rx/merge
(rx/timer 1000)
(->> stream
(rx/filter ms/key-up?)
(rx/delay 250))))
(rx/take 1))
scale (if shift? (gpt/point (or (:big nudge) 10)) (gpt/point (or (:small nudge) 1)))
mov-vec (gpt/multiply (get-displacement direction) scale)]

View file

@ -21,6 +21,16 @@
[v]
(instance? KeyboardEvent v))
(defn key-up?
[v]
(and (keyboard-event? v)
(= :up (:type v))))
(defn key-down?
[v]
(and (keyboard-event? v)
(= :down (:type v))))
(defrecord MouseEvent [type ctrl shift alt meta])
(defn mouse-event?

View file

@ -30,29 +30,16 @@
[promesa.core :as p]
[rumext.v2 :as mf]))
(defn strip-position-data [shape]
(-> shape
(cond-> (some? (meta (:position-data shape)))
(with-meta (meta (:position-data shape))))
(dissoc :position-data)))
(defn fix-position [shape modifier]
(let [shape' (gsh/transform-shape shape modifier)
(defn fix-position [shape]
(let [modifiers (:modifiers shape)
shape' (gsh/transform-shape shape modifiers)
;; We need to remove the movement because the dynamic modifiers will have move it
deltav (gpt/to-vec (gpt/point (:selrect shape'))
(gpt/point (:selrect shape)))]
(-> shape
(gsh/transform-shape (ctm/move modifier deltav))
(mdwm/update-grow-type shape))))
(defn process-shape [modifiers {:keys [id] :as shape}]
(let [modifier (dm/get-in modifiers [id :modifiers])]
(-> shape
(cond-> (and (some? modifier) (not (ctm/only-move? modifier)))
(fix-position modifier))
(cond-> (nil? (:position-data shape))
(assoc :migrate true))
strip-position-data)))
(gsh/transform-shape (ctm/move modifiers deltav))
(mdwm/update-grow-type shape)
(dissoc :modifiers))))
(defn- update-with-editor-state
"Updates the shape with the current state in the editor"
@ -87,6 +74,7 @@
(not (mth/almost-zero? height))
(not migrate))
(st/emit! (dwt/resize-text id width height)))))
(st/emit! (dwt/clean-text-modifier id))))
(defn- update-text-modifier
@ -136,8 +124,8 @@
(or (identical? shape other)
(and
;; Check if both shapes are equivalent removing their geometry data
(= (dissoc shape :migrate :points :selrect :height :width :x :y)
(dissoc other :migrate :points :selrect :height :width :x :y))
(= (dissoc shape :migrate :points :selrect :height :width :x :y :position-data :modifiers)
(dissoc other :migrate :points :selrect :height :width :x :y :position-data :modifiers))
;; Check if the position and size is close. If any of these changes the shape has changed
;; and if not there is no geometry relevant change
@ -146,55 +134,69 @@
(mth/close? (:width shape) (:width other))
(mth/close? (:height shape) (:height other)))))
(mf/defc viewport-texts-wrapper
{::mf/wrap-props false
::mf/wrap [mf/memo #(mf/deferred % ts/idle-then-raf)]}
(mf/defc text-changes-renderer
{::mf/wrap-props false}
[props]
(let [text-shapes (obj/get props "text-shapes")
modifiers (obj/get props "modifiers")
prev-modifiers (hooks/use-previous modifiers)
prev-text-shapes (hooks/use-previous text-shapes)
;; A change in position-data won't be a "real" change
text-change?
(fn [id]
(let [new-shape (get text-shapes id)
old-shape (get prev-text-shapes id)
old-modifiers (ctm/select-geometry (get prev-modifiers id))
new-modifiers (ctm/select-geometry (get modifiers id))
remote? (some? (-> new-shape meta :session-id))]
(or (and (not remote?)
(or (and (not remote?) ;; changes caused by a remote peer are not re-calculated
(not (text-properties-equal? old-shape new-shape)))
(and (not= new-modifiers old-modifiers)
(or (ctm/empty? new-modifiers)
(ctm/empty? old-modifiers)))
(and (not= new-modifiers old-modifiers)
(or (not (ctm/only-move? new-modifiers))
(not (ctm/only-move? old-modifiers))))
;; When the position data is nil we force to recalculate
(:migrate new-shape))))
(nil? (:position-data new-shape)))))
changed-texts
(mf/use-memo
(mf/deps text-shapes modifiers)
(mf/deps text-shapes)
#(->> (keys text-shapes)
(filter text-change?)
(map (d/getf text-shapes))))
handle-update-modifier (mf/use-callback update-text-modifier)
handle-update-shape (mf/use-callback update-text-shape)]
[:*
[:.text-changes-renderer
(for [{:keys [id] :as shape} changed-texts]
[:& text-container {:shape shape
:on-update (if (some? (get modifiers (:id shape)))
handle-update-modifier
handle-update-shape)
:key (str (dm/str "text-container-" id))}])]))
[:& text-container {:key (str (dm/str "text-container-" id))
:shape shape
:on-update handle-update-shape}])]))
(mf/defc text-modifiers-renderer
{::mf/wrap-props false}
[props]
(let [text-shapes (-> (obj/get props "text-shapes")
(update-vals fix-position))
prev-text-shapes (hooks/use-previous text-shapes)
text-change?
(fn [id]
(let [new-shape (get text-shapes id)
old-shape (get prev-text-shapes id)]
(and
(some? new-shape)
(some? old-shape)
(not (text-properties-equal? old-shape new-shape)))))
changed-texts
(mf/use-memo
(mf/deps text-shapes)
#(->> (keys text-shapes)
(filter text-change?)
(map (d/getf text-shapes))))
handle-update-shape (mf/use-callback update-text-modifier)]
[:.text-changes-renderer
(for [{:keys [id] :as shape} changed-texts]
[:& text-container {:key (str (dm/str "text-container-" id))
:shape shape
:on-update handle-update-shape}])]))
(mf/defc viewport-text-editing
{::mf/wrap-props false}
@ -256,21 +258,30 @@
text-shapes
(mf/use-memo
(mf/deps objects)
#(into {} (filter (comp cph/text-shape? second)) objects))
(fn []
(into {} (filter (comp cph/text-shape? second)) objects)))
text-shapes
(mf/use-memo
(mf/deps text-shapes modifiers)
#(update-vals text-shapes (partial process-shape modifiers)))
(hooks/use-equal-memo text-shapes)
editing-shape (get text-shapes edition)
;; This memo is necessary so the viewport-text-wrapper memoize its props correctly
text-shapes-wrapper
text-shapes-changes
(mf/use-memo
(mf/deps text-shapes edition)
(fn []
(dissoc text-shapes edition)))]
(-> text-shapes
(dissoc edition))))
text-shapes-modifiers
(mf/use-memo
(mf/deps modifiers text-shapes)
(fn []
(into {}
(keep (fn [[id modifiers]]
(when-let [shape (get text-shapes id)]
(vector id (merge shape modifiers)))))
modifiers)))]
;; We only need the effect to run on "mount" because the next fonts will be changed when the texts are
;; edited
@ -284,5 +295,5 @@
(when editing-shape
[:& viewport-text-editing {:shape editing-shape}])
[:& viewport-texts-wrapper {:text-shapes text-shapes-wrapper
:modifiers modifiers}]]))
[:& text-modifiers-renderer {:text-shapes text-shapes-modifiers}]
[:& text-changes-renderer {:text-shapes text-shapes-changes}]]))

View file

@ -28,28 +28,40 @@
has-value? (not (nil? blur))
multiple? (= blur :multiple)
change! (fn [update-fn] (st/emit! (dch/update-shapes ids update-fn)))
change!
(mf/use-callback
(mf/deps ids)
(fn [update-fn]
(st/emit! (dch/update-shapes ids update-fn))))
handle-add
(fn []
(change! #(assoc % :blur (create-blur))))
(mf/use-callback
(mf/deps change!)
(fn []
(change! #(assoc % :blur (create-blur)))))
handle-delete
(fn []
(change! #(dissoc % :blur)))
(mf/use-callback
(mf/deps change!)
(fn []
(change! #(dissoc % :blur))))
handle-change
(fn [value]
(change! #(cond-> %
(not (contains? % :blur))
(assoc :blur (create-blur))
(mf/use-callback
(mf/deps change!)
(fn [value]
(change! #(cond-> %
(not (contains? % :blur))
(assoc :blur (create-blur))
:always
(assoc-in [:blur :value] value))))
:always
(assoc-in [:blur :value] value)))))
handle-toggle-visibility
(fn []
(change! #(update-in % [:blur :hidden] not)))]
(mf/use-callback
(mf/deps change!)
(fn []
(change! #(update-in % [:blur :hidden] not))))]
[:div.element-set
[:div.element-set-title

View file

@ -12,6 +12,7 @@
[app.common.geom.shapes :as gsh]
[app.common.pages.helpers :as cph]
[app.common.types.shape.layout :as ctl]
[app.main.data.workspace.modifiers :as dwm]
[app.main.refs :as refs]
[app.main.ui.context :as ctx]
[app.main.ui.hooks :as ui-hooks]
@ -48,6 +49,20 @@
;; --- Viewport
(defn apply-modifiers-to-selected
[selected objects text-modifiers modifiers]
(into []
(comp
(keep (d/getf objects))
(map (fn [{:keys [id] :as shape}]
(cond-> shape
(and (cph/text-shape? shape) (contains? text-modifiers id))
(dwm/apply-text-modifier (get text-modifiers id))
(contains? modifiers id)
(gsh/transform-shape (dm/get-in modifiers [id :modifiers]))))))
selected))
(mf/defc viewport
[{:keys [wlocal wglobal selected layout file] :as props}]
(let [;; When adding data from workspace-local revisit `app.main.ui.workspace` to check
@ -80,6 +95,7 @@
base-objects (-> objects (ui-hooks/with-focus-objects focus))
modifiers (mf/deref refs/workspace-modifiers)
text-modifiers (mf/deref refs/workspace-text-modifier)
objects-modified (mf/with-memo [base-objects modifiers]
(gsh/apply-objects-modifiers base-objects modifiers selected))
@ -120,7 +136,8 @@
drawing-tool (:tool drawing)
drawing-obj (:object drawing)
selected-shapes (into [] (keep (d/getf objects-modified)) selected)
selected-shapes (apply-modifiers-to-selected selected base-objects text-modifiers modifiers)
selected-frames (into #{} (map :frame-id) selected-shapes)
;; Only when we have all the selected shapes in one frame
@ -303,7 +320,7 @@
outlined-frame (get objects outlined-frame-id)]
[:*
[:& outline/shape-outlines
{:objects objects-modified
{:objects base-objects
:hover #{outlined-frame-id}
:zoom zoom
:modifiers modifiers}]
@ -443,25 +460,25 @@
;; DEBUG LAYOUT DROP-ZONES
(when (debug? :layout-drop-zones)
[:& wvd/debug-drop-zones {:selected-shapes selected-shapes
:objects objects-modified
:objects base-objects
:hover-top-frame-id @hover-top-frame-id
:zoom zoom}])
(when (debug? :layout-content-bounds)
[:& wvd/debug-content-bounds {:selected-shapes selected-shapes
:objects objects-modified
:objects base-objects
:hover-top-frame-id @hover-top-frame-id
:zoom zoom}])
(when (debug? :layout-lines)
[:& wvd/debug-layout-lines {:selected-shapes selected-shapes
:objects objects-modified
:objects base-objects
:hover-top-frame-id @hover-top-frame-id
:zoom zoom}])
(when (debug? :parent-bounds)
[:& wvd/debug-parent-bounds {:selected-shapes selected-shapes
:objects objects-modified
:objects base-objects
:hover-top-frame-id @hover-top-frame-id
:zoom zoom}])

View file

@ -63,7 +63,8 @@
[shape-id]
(when (some? shape-id)
(let [text-nodes (dom/query-all (dm/str "#html-text-node-" shape-id " .text-node"))
(let [text-nodes (-> (dom/query (dm/fmt "#html-text-node-%" shape-id))
(dom/query-all ".text-node"))
load-fonts (->> text-nodes (map resolve-font))
process-text-node