diff --git a/common/src/app/common/geom/rect.cljc b/common/src/app/common/geom/rect.cljc index cd9bf6f06..adfa3114c 100644 --- a/common/src/app/common/geom/rect.cljc +++ b/common/src/app/common/geom/rect.cljc @@ -275,21 +275,19 @@ [a b] (mth/almost-zero? (- a b))) -;; FIXME: performance (defn overlaps-rects? "Check for two rects to overlap. Rects won't overlap only if one of them is fully to the left or the top" [rect-a rect-b] + (let [x1a (dm/get-prop rect-a :x) + y1a (dm/get-prop rect-a :y) + x2a (+ x1a (dm/get-prop rect-a :width)) + y2a (+ y1a (dm/get-prop rect-a :height)) - (let [x1a (:x rect-a) - y1a (:y rect-a) - x2a (+ (:x rect-a) (:width rect-a)) - y2a (+ (:y rect-a) (:height rect-a)) - - x1b (:x rect-b) - y1b (:y rect-b) - x2b (+ (:x rect-b) (:width rect-b)) - y2b (+ (:y rect-b) (:height rect-b))] + x1b (dm/get-prop rect-b :x) + y1b (dm/get-prop rect-b :y) + x2b (+ x1b (dm/get-prop rect-b :width)) + y2b (+ y1b (dm/get-prop rect-b :height))] (and (or (> x2a x1b) (s= x2a x1b)) (or (>= x2b x1a) (s= x2b x1a)) diff --git a/common/src/app/common/geom/shapes/intersect.cljc b/common/src/app/common/geom/shapes/intersect.cljc index c9e89dd6c..74da2cc3e 100644 --- a/common/src/app/common/geom/shapes/intersect.cljc +++ b/common/src/app/common/geom/shapes/intersect.cljc @@ -307,12 +307,12 @@ (defn overlaps? "General case to check for overlapping between shapes and a rectangle" [shape rect] - (let [stroke-width (/ (or (:stroke-width shape) 0) 2) - rect (-> rect - (update :x - stroke-width) - (update :y - stroke-width) - (update :width + (* 2 stroke-width)) - (update :height + (* 2 stroke-width)))] + (let [swidth (/ (or (:stroke-width shape) 0) 2) + rect (-> rect + (update :x - swidth) + (update :y - swidth) + (update :width + (* 2 swidth)) + (update :height + (* 2 swidth)))] (or (not shape) (cond (cph/path-shape? shape) diff --git a/frontend/src/app/main/ui/context.cljs b/frontend/src/app/main/ui/context.cljs index c0d1aa00e..a365ce9f3 100644 --- a/frontend/src/app/main/ui/context.cljs +++ b/frontend/src/app/main/ui/context.cljs @@ -16,6 +16,7 @@ (def current-project-id (mf/create-context nil)) (def current-page-id (mf/create-context nil)) (def current-file-id (mf/create-context nil)) +(def current-vbox (mf/create-context nil)) (def active-frames (mf/create-context nil)) (def render-thumbnails (mf/create-context nil)) diff --git a/frontend/src/app/main/ui/workspace/shapes.cljs b/frontend/src/app/main/ui/workspace/shapes.cljs index 3ea8abc7e..3d4d42f78 100644 --- a/frontend/src/app/main/ui/workspace/shapes.cljs +++ b/frontend/src/app/main/ui/workspace/shapes.cljs @@ -13,6 +13,7 @@ common." (:require [app.common.data.macros :as dm] + [app.common.geom.rect :as grc] [app.common.pages.helpers :as cph] [app.common.uuid :as uuid] [app.main.ui.context :as ctx] @@ -49,15 +50,14 @@ (let [objects (obj/get props "objects") active-frames (obj/get props "active-frames") shapes (cph/get-immediate-children objects) + vbox (mf/use-ctx ctx/current-vbox) - ;; vbox (mf/use-ctx ctx/current-vbox) - ;; shapes (mf/with-memo [shapes vbox] - ;; (if (some? vbox) - ;; (filter (fn [shape] - ;; (grc/overlaps-rects? vbox (dm/get-prop shape :selrect))) - ;; shapes) - ;; shapes)) - ] + shapes (mf/with-memo [shapes vbox] + (if (some? vbox) + (filter (fn [shape] + (grc/overlaps-rects? vbox (dm/get-prop shape :selrect))) + shapes) + shapes))] [:g {:id (dm/str "shape-" uuid/zero)} [:& (mf/provider ctx/active-frames) {:value active-frames} diff --git a/frontend/src/app/main/ui/workspace/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/shapes/frame.cljs index c210ef5d8..0fc57d3e6 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame.cljs @@ -73,6 +73,7 @@ (fdm/use-dynamic-modifiers objects (mf/ref-val node-ref) modifiers) [:& frame-shape {:shape shape :ref node-ref}])))) + (defn root-frame-wrapper-factory [shape-wrapper] @@ -86,9 +87,9 @@ thumbnail? (unchecked-get props "thumbnail?") page-id (mf/use-ctx ctx/current-page-id) - frame-id (:id shape) + frame-id (dm/get-prop shape :id) - objects (wsh/lookup-page-objects @st/state) + objects (wsh/lookup-page-objects @st/state page-id) node-ref (mf/use-ref nil) root-ref (mf/use-ref nil) @@ -114,6 +115,7 @@ on-frame-load (fns/use-node-store node-ref rendered-ref thumbnail? render-frame?) + ] (fdm/use-dynamic-modifiers objects (mf/ref-val node-ref) modifiers) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs index b67905296..5a90e4ab0 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs @@ -225,8 +225,8 @@ :spell-check false :on-change on-filter-change}] (when (and recent-fonts show-recent) - [:hr] - [* + [:* + [:hr] [:p.title (tr "workspace.options.recent-fonts")] (for [[idx font] (d/enumerate recent-fonts)] [:& font-item {:key (dm/str "font-" idx) diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 275d9f4a0..d2982a10c 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -88,6 +88,8 @@ show-distances? picking-color?]} wglobal + vbox' (mf/use-debounce 100 vbox) + ;; CONTEXT page-id (mf/use-ctx ctx/current-page-id) @@ -323,12 +325,13 @@ :y (:y vbox 0) :fill background}] - [:& (mf/provider use/include-metadata-ctx) {:value (debug? :show-export-metadata)} - [:& (mf/provider embed/context) {:value true} - ;; Render root shape - [:& shapes/root-shape {:key page-id - :objects base-objects - :active-frames @active-frames}]]]] + [:& (mf/provider ctx/current-vbox) {:value vbox'} + [:& (mf/provider use/include-metadata-ctx) {:value (debug? :show-export-metadata)} + [:& (mf/provider embed/context) {:value true} + ;; Render root shape + [:& shapes/root-shape {:key page-id + :objects base-objects + :active-frames @active-frames}]]]]] [:svg.viewport-controls {:xmlns "http://www.w3.org/2000/svg" diff --git a/frontend/src/app/util/dom.cljs b/frontend/src/app/util/dom.cljs index a4db28c8b..94552f818 100644 --- a/frontend/src/app/util/dom.cljs +++ b/frontend/src/app/util/dom.cljs @@ -409,16 +409,25 @@ (defn bounding-rect->rect [rect] (when (some? rect) - {:x (or (.-left rect) (:left rect) 0) - :y (or (.-top rect) (:top rect) 0) - :width (or (.-width rect) (:width rect) 1) - :height (or (.-height rect) (:height rect) 1)})) + (grc/make-rect + (or (.-left rect) (:left rect) 0) + (or (.-top rect) (:top rect) 0) + (or (.-width rect) (:width rect) 1) + (or (.-height rect) (:height rect) 1)))) (defn get-window-size [] {:width (.-innerWidth ^js js/window) :height (.-innerHeight ^js js/window)}) +(defn get-computed-styles + [node] + (js/getComputedStyle node)) + +(defn get-property-value + [o prop] + (.getPropertyValue ^js o prop)) + (defn focus! [^js node] (when (some? node) diff --git a/frontend/src/app/util/text_svg_position.cljs b/frontend/src/app/util/text_svg_position.cljs index a79f49b06..7047413ff 100644 --- a/frontend/src/app/util/text_svg_position.cljs +++ b/frontend/src/app/util/text_svg_position.cljs @@ -12,22 +12,21 @@ [app.main.fonts :as fonts] [app.util.dom :as dom] [app.util.text-position-data :as tpd] + [cuerdas.core :as str] [promesa.core :as p])) (defn parse-text-nodes "Given a text node retrieves the rectangles for everyone of its paragraphs and its text." [parent-node direction text-node text-align] - (letfn [(parse-entry [^js entry] (when (some? (.-position entry)) {:node (.-node entry) :position (dom/bounding-rect->rect (.-position entry)) :text (.-text entry) :direction direction}))] - (into - [] - (keep parse-entry) - (tpd/parse-text-nodes parent-node text-node text-align)))) + (into [] + (keep parse-entry) + (tpd/parse-text-nodes parent-node text-node text-align)))) (def load-promises (atom {})) @@ -40,76 +39,76 @@ load-promise))) (defn resolve-font - [^js node] + [node] + (let [styles (dom/get-computed-styles node) + font (dom/get-property-value styles "font") + font (if (or (not font) (empty? font)) + ;; Firefox 95 won't return the font correctly. + ;; We can get the font shorthand with the font-size + font-family + (str/ffmt "% %" + (dom/get-property-value styles "font-size") + (dom/get-property-value styles "font-family")) + font) - (let [styles (js/getComputedStyle node) - font (.getPropertyValue styles "font") - font (if (or (not font) (empty? font)) - ;; Firefox 95 won't return the font correctly. - ;; We can get the font shorthand with the font-size + font-family - (dm/str (.getPropertyValue styles "font-size") - " " - (.getPropertyValue styles "font-family")) - font) + font-id (dom/get-property-value styles "--font-id")] - font-id (.getPropertyValue styles "--font-id")] + (->> (fonts/ensure-loaded! font-id) + (p/fmap (fn [] + (when-not ^boolean (dom/check-font? font) + (load-font font)))) + (p/merr (fn [_cause] + (js/console.error (str/ffmt "Cannot load font %" font-id)) + (p/resolved nil)))))) - (-> (fonts/ensure-loaded! font-id) - (p/then #(when (not (dom/check-font? font)) - (load-font font))) - (p/catch #(.error js/console (dm/str "Cannot load font " font-id) %))))) + +(defn- process-text-node + [parent-node] + (let [root (dom/get-parent-with-selector parent-node ".text-node-html") + paragraph (dom/get-parent-with-selector parent-node ".paragraph") + shape-x (d/parse-double (dom/get-attribute root "data-x")) + shape-y (d/parse-double (dom/get-attribute root "data-y")) + direction (.-direction ^js (dom/get-computed-styles parent-node)) + text-align (.-textAlign ^js (dom/get-computed-styles paragraph))] + + (sequence + (comp + (mapcat #(parse-text-nodes parent-node direction % text-align)) + (map #(-> % + (update-in [:position :x] + shape-x) + (update-in [:position :y] + shape-y)))) + (seq (.-childNodes parent-node))))) (defn- calc-text-node-positions [shape-id] - - (when (some? shape-id) - (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 - (fn [parent-node] - (let [root (dom/get-parent-with-selector parent-node ".text-node-html") - paragraph (dom/get-parent-with-selector parent-node ".paragraph") - shape-x (-> (dom/get-attribute root "data-x") d/parse-double) - shape-y (-> (dom/get-attribute root "data-y") d/parse-double) - direction (.-direction (js/getComputedStyle parent-node)) - text-align (.-textAlign (js/getComputedStyle paragraph))] - - (->> (.-childNodes parent-node) - (mapcat #(parse-text-nodes parent-node direction % text-align)) - (mapv #(-> % - (update-in [:position :x] + shape-x) - (update-in [:position :y] + shape-y))))))] - (-> (p/all load-fonts) - (p/then - (fn [] - (->> text-nodes (mapcat process-text-node)))))))) + (let [text-nodes (-> (dom/query (dm/fmt "#html-text-node-%" shape-id)) + (dom/query-all ".text-node"))] + (->> (p/all (map resolve-font text-nodes)) + (p/fmap #(mapcat process-text-node text-nodes))))) (defn calc-position-data [shape-id] - (when (some? shape-id) - (p/let [text-data (calc-text-node-positions shape-id)] - (->> text-data - (mapv (fn [{:keys [node position text direction]}] - (let [{:keys [x y width height]} position - styles (js/getComputedStyle ^js node) - get (fn [prop] - (let [value (.getPropertyValue styles prop)] - (when (and value (not= value "")) - value)))] - (d/without-nils - {:x x - :y (+ y height) - :width width - :height height - :direction direction - :font-family (str (get "font-family")) - :font-size (str (get "font-size")) - :font-weight (str (get "font-weight")) - :text-transform (str (get "text-transform")) - :text-decoration (str (get "text-decoration")) - :letter-spacing (str (get "letter-spacing")) - :font-style (str (get "font-style")) - :fills (transit/decode-str (get "--fills")) - :text text})))))))) + (letfn [(get-prop [styles prop] + (let [value (.getPropertyValue styles prop)] + (when (and (some? value) (not= value "")) + value))) + + (transform-data [{:keys [node position text direction]}] + (let [styles (dom/get-computed-styles node) + position (assoc position :y (+ (dm/get-prop position :y) + (dm/get-prop position :height)))] + (into position (filter val) + {:direction direction + :font-family (dm/str (get-prop styles "font-family")) + :font-size (dm/str (get-prop styles "font-size")) + :font-weight (dm/str (get-prop styles "font-weight")) + :text-transform (dm/str (get-prop styles "text-transform")) + :text-decoration (dm/str (get-prop styles "text-decoration")) + :letter-spacing (dm/str (get-prop styles "letter-spacing")) + :font-style (dm/str (get-prop styles "font-style")) + :fills (transit/decode-str (get-prop styles "--fills")) + :text text})))] + + (when (some? shape-id) + (->> (calc-text-node-positions shape-id) + (p/fmap (fn [text-data] + (mapv transform-data text-data)))))))