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:
parent
837b52aea1
commit
84e9f69213
7 changed files with 148 additions and 85 deletions
|
@ -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]
|
||||
|
|
|
@ -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)]
|
||||
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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}]]))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}])
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue