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] [id]
(ptk/reify ::clean-text-modifier (ptk/reify ::clean-text-modifier
ptk/WatchEvent ptk/WatchEvent
(watch [_ _ _] (watch [_ state _]
(->> (rx/of #(update % :workspace-text-modifier dissoc id)) (let [current-value (dm/get-in state [:workspace-text-modifier id])]
;; We delay a bit the change so there is no weird transition to the user ;; We only dissocc the value when hasn't change after a time
(rx/delay 50))))) (->> (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 (defn remove-text-modifier
[id] [id]

View file

@ -601,9 +601,17 @@
move-events (->> stream move-events (->> stream
(rx/filter (ptk/type? ::nudge-selected-shapes)) (rx/filter (ptk/type? ::nudge-selected-shapes))
(rx/filter #(= direction (deref %)))) (rx/filter #(= direction (deref %))))
stopper (->> move-events
(rx/debounce 100) stopper
(rx/take 1)) (->> 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))) scale (if shift? (gpt/point (or (:big nudge) 10)) (gpt/point (or (:small nudge) 1)))
mov-vec (gpt/multiply (get-displacement direction) scale)] mov-vec (gpt/multiply (get-displacement direction) scale)]

View file

@ -21,6 +21,16 @@
[v] [v]
(instance? KeyboardEvent 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]) (defrecord MouseEvent [type ctrl shift alt meta])
(defn mouse-event? (defn mouse-event?

View file

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

View file

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

View file

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

View file

@ -63,7 +63,8 @@
[shape-id] [shape-id]
(when (some? 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)) load-fonts (->> text-nodes (map resolve-font))
process-text-node process-text-node