mirror of
https://github.com/penpot/penpot.git
synced 2025-06-21 15:47:00 +02:00
Merge pull request #1424 from penpot/performance
Performance improvements
This commit is contained in:
commit
ae9b95f81b
61 changed files with 1375 additions and 938 deletions
|
@ -260,6 +260,7 @@
|
||||||
|
|
||||||
(def ^:private sql:team-shared-files
|
(def ^:private sql:team-shared-files
|
||||||
"select f.id,
|
"select f.id,
|
||||||
|
f.revn,
|
||||||
f.project_id,
|
f.project_id,
|
||||||
f.created_at,
|
f.created_at,
|
||||||
f.modified_at,
|
f.modified_at,
|
||||||
|
@ -330,6 +331,7 @@
|
||||||
(def sql:team-recent-files
|
(def sql:team-recent-files
|
||||||
"with recent_files as (
|
"with recent_files as (
|
||||||
select f.id,
|
select f.id,
|
||||||
|
f.revn,
|
||||||
f.project_id,
|
f.project_id,
|
||||||
f.created_at,
|
f.created_at,
|
||||||
f.modified_at,
|
f.modified_at,
|
||||||
|
|
|
@ -51,7 +51,7 @@
|
||||||
(get-in file [:data :components (:current-component-id file) :objects])
|
(get-in file [:data :components (:current-component-id file) :objects])
|
||||||
(get-in file [:data :pages-index (:current-page-id file) :objects]))))
|
(get-in file [:data :pages-index (:current-page-id file) :objects]))))
|
||||||
|
|
||||||
(defn- lookup-shape [file shape-id]
|
(defn lookup-shape [file shape-id]
|
||||||
(-> (lookup-objects file)
|
(-> (lookup-objects file)
|
||||||
(get shape-id)))
|
(get shape-id)))
|
||||||
|
|
||||||
|
@ -321,16 +321,11 @@
|
||||||
(update :parent-stack pop))))
|
(update :parent-stack pop))))
|
||||||
|
|
||||||
(defn create-shape [file type data]
|
(defn create-shape [file type data]
|
||||||
(let [frame-id (:current-frame-id file)
|
(let [obj (-> (init/make-minimal-shape type)
|
||||||
frame (when-not (= frame-id root-frame)
|
|
||||||
(lookup-shape file frame-id))
|
|
||||||
obj (-> (init/make-minimal-shape type)
|
|
||||||
(merge data)
|
(merge data)
|
||||||
(check-name file :type)
|
(check-name file :type)
|
||||||
(setup-selrect)
|
(setup-selrect)
|
||||||
(d/without-nils))
|
(d/without-nils))]
|
||||||
obj (cond-> obj
|
|
||||||
frame (gsh/translate-from-frame frame))]
|
|
||||||
(-> file
|
(-> file
|
||||||
(commit-shape obj)
|
(commit-shape obj)
|
||||||
(assoc :last-id (:id obj))
|
(assoc :last-id (:id obj))
|
||||||
|
|
|
@ -36,6 +36,33 @@
|
||||||
(apply matrix params)))
|
(apply matrix params)))
|
||||||
|
|
||||||
(defn multiply
|
(defn multiply
|
||||||
|
([m1 m2]
|
||||||
|
(let [m1a (.-a m1)
|
||||||
|
m1b (.-b m1)
|
||||||
|
m1c (.-c m1)
|
||||||
|
m1d (.-d m1)
|
||||||
|
m1e (.-e m1)
|
||||||
|
m1f (.-f m1)
|
||||||
|
|
||||||
|
m2a (.-a m2)
|
||||||
|
m2b (.-b m2)
|
||||||
|
m2c (.-c m2)
|
||||||
|
m2d (.-d m2)
|
||||||
|
m2e (.-e m2)
|
||||||
|
m2f (.-f m2)]
|
||||||
|
|
||||||
|
(Matrix.
|
||||||
|
(+ (* m1a m2a) (* m1c m2b))
|
||||||
|
(+ (* m1b m2a) (* m1d m2b))
|
||||||
|
(+ (* m1a m2c) (* m1c m2d))
|
||||||
|
(+ (* m1b m2c) (* m1d m2d))
|
||||||
|
(+ (* m1a m2e) (* m1c m2f) m1e)
|
||||||
|
(+ (* m1b m2e) (* m1d m2f) m1f))))
|
||||||
|
|
||||||
|
([m1 m2 & others]
|
||||||
|
(reduce multiply (multiply m1 m2) others)))
|
||||||
|
|
||||||
|
(defn -old-multiply
|
||||||
([{m1a :a m1b :b m1c :c m1d :d m1e :e m1f :f}
|
([{m1a :a m1b :b m1c :c m1d :d m1e :e m1f :f}
|
||||||
{m2a :a m2b :b m2c :c m2d :d m2e :e m2f :f}]
|
{m2a :a m2b :b m2c :c m2d :d m2e :e m2f :f}]
|
||||||
(Matrix.
|
(Matrix.
|
||||||
|
@ -46,20 +73,15 @@
|
||||||
(+ (* m1a m2e) (* m1c m2f) m1e)
|
(+ (* m1a m2e) (* m1c m2f) m1e)
|
||||||
(+ (* m1b m2e) (* m1d m2f) m1f)))
|
(+ (* m1b m2e) (* m1d m2f) m1f)))
|
||||||
([m1 m2 & others]
|
([m1 m2 & others]
|
||||||
(reduce multiply (multiply m1 m2) others)))
|
(reduce multiply (-old-multiply m1 m2) others)))
|
||||||
|
|
||||||
(defn add-translate
|
(defn add-translate
|
||||||
"Given two TRANSLATE matrixes (only e and f have significative
|
"Given two TRANSLATE matrixes (only e and f have significative
|
||||||
values), combine them. Quicker than multiplying them, for this
|
values), combine them. Quicker than multiplying them, for this
|
||||||
precise case."
|
precise case."
|
||||||
([{m1e :e m1f :f} {m2e :e m2f :f}]
|
([{m1e :e m1f :f} {m2e :e m2f :f}]
|
||||||
(Matrix.
|
(Matrix. 1 0 0 1 (+ m1e m2e) (+ m1f m2f)))
|
||||||
1
|
|
||||||
0
|
|
||||||
0
|
|
||||||
1
|
|
||||||
(+ m1e m2e)
|
|
||||||
(+ m1f m2f)))
|
|
||||||
([m1 m2 & others]
|
([m1 m2 & others]
|
||||||
(reduce add-translate (add-translate m1 m2) others)))
|
(reduce add-translate (add-translate m1 m2) others)))
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
[app.common.geom.point :as gpt]
|
[app.common.geom.point :as gpt]
|
||||||
[app.common.geom.shapes.bool :as gsb]
|
[app.common.geom.shapes.bool :as gsb]
|
||||||
[app.common.geom.shapes.common :as gco]
|
[app.common.geom.shapes.common :as gco]
|
||||||
|
[app.common.geom.shapes.constraints :as gct]
|
||||||
[app.common.geom.shapes.intersect :as gin]
|
[app.common.geom.shapes.intersect :as gin]
|
||||||
[app.common.geom.shapes.path :as gsp]
|
[app.common.geom.shapes.path :as gsp]
|
||||||
[app.common.geom.shapes.rect :as gpr]
|
[app.common.geom.shapes.rect :as gpr]
|
||||||
|
@ -163,8 +164,12 @@
|
||||||
(d/export gtr/rotation-modifiers)
|
(d/export gtr/rotation-modifiers)
|
||||||
(d/export gtr/merge-modifiers)
|
(d/export gtr/merge-modifiers)
|
||||||
(d/export gtr/transform-shape)
|
(d/export gtr/transform-shape)
|
||||||
(d/export gtr/calc-transformed-parent-rect)
|
(d/export gtr/transform-selrect)
|
||||||
(d/export gtr/calc-child-modifiers)
|
(d/export gtr/modifiers->transform)
|
||||||
|
(d/export gtr/empty-modifiers?)
|
||||||
|
|
||||||
|
;; Constratins
|
||||||
|
(d/export gct/calc-child-modifiers)
|
||||||
|
|
||||||
;; PATHS
|
;; PATHS
|
||||||
(d/export gsp/content->selrect)
|
(d/export gsp/content->selrect)
|
||||||
|
@ -179,3 +184,4 @@
|
||||||
|
|
||||||
;; Bool
|
;; Bool
|
||||||
(d/export gsb/update-bool-selrect)
|
(d/export gsb/update-bool-selrect)
|
||||||
|
(d/export gsb/calc-bool-content)
|
||||||
|
|
|
@ -6,21 +6,31 @@
|
||||||
|
|
||||||
(ns app.common.geom.shapes.bool
|
(ns app.common.geom.shapes.bool
|
||||||
(:require
|
(:require
|
||||||
|
[app.common.data :as d]
|
||||||
[app.common.geom.shapes.path :as gsp]
|
[app.common.geom.shapes.path :as gsp]
|
||||||
[app.common.geom.shapes.rect :as gpr]
|
[app.common.geom.shapes.rect :as gpr]
|
||||||
[app.common.geom.shapes.transforms :as gtr]
|
[app.common.geom.shapes.transforms :as gtr]
|
||||||
[app.common.path.bool :as pb]
|
[app.common.path.bool :as pb]
|
||||||
[app.common.path.shapes-to-path :as stp]))
|
[app.common.path.shapes-to-path :as stp]))
|
||||||
|
|
||||||
|
(defn calc-bool-content
|
||||||
|
[shape objects]
|
||||||
|
|
||||||
|
(let [extract-content-xf
|
||||||
|
(comp (map (d/getf objects))
|
||||||
|
(filter (comp not :hidden))
|
||||||
|
(map #(stp/convert-to-path % objects))
|
||||||
|
(map :content))
|
||||||
|
|
||||||
|
shapes-content
|
||||||
|
(into [] extract-content-xf (:shapes shape))]
|
||||||
|
(pb/content-bool (:bool-type shape) shapes-content)))
|
||||||
|
|
||||||
(defn update-bool-selrect
|
(defn update-bool-selrect
|
||||||
"Calculates the selrect+points for the boolean shape"
|
"Calculates the selrect+points for the boolean shape"
|
||||||
[shape children objects]
|
[shape children objects]
|
||||||
|
|
||||||
(let [content (->> children
|
(let [content (calc-bool-content shape objects)
|
||||||
(map #(stp/convert-to-path % objects))
|
|
||||||
(mapv :content)
|
|
||||||
(pb/content-bool (:bool-type shape)))
|
|
||||||
|
|
||||||
[points selrect]
|
[points selrect]
|
||||||
(if (empty? content)
|
(if (empty? content)
|
||||||
(let [selrect (gtr/selection-rect children)
|
(let [selrect (gtr/selection-rect children)
|
||||||
|
@ -29,4 +39,6 @@
|
||||||
(gsp/content->points+selrect shape content))]
|
(gsp/content->points+selrect shape content))]
|
||||||
(-> shape
|
(-> shape
|
||||||
(assoc :selrect selrect)
|
(assoc :selrect selrect)
|
||||||
(assoc :points points))))
|
(assoc :points points)
|
||||||
|
(assoc :bool-content content))))
|
||||||
|
|
||||||
|
|
|
@ -50,6 +50,22 @@
|
||||||
:width width
|
:width width
|
||||||
:height height})
|
:height height})
|
||||||
|
|
||||||
|
(defn make-centered-selrect
|
||||||
|
"Creates a rect given a center and a width and height"
|
||||||
|
[center width height]
|
||||||
|
(let [x1 (- (:x center) (/ width 2.0))
|
||||||
|
y1 (- (:y center) (/ height 2.0))
|
||||||
|
x2 (+ x1 width)
|
||||||
|
y2 (+ y1 height)]
|
||||||
|
{:x x1
|
||||||
|
:y y1
|
||||||
|
:x1 x1
|
||||||
|
:x2 x2
|
||||||
|
:y1 y1
|
||||||
|
:y2 y2
|
||||||
|
:width width
|
||||||
|
:height height}))
|
||||||
|
|
||||||
(defn transform-points
|
(defn transform-points
|
||||||
([points matrix]
|
([points matrix]
|
||||||
(transform-points points nil matrix))
|
(transform-points points nil matrix))
|
||||||
|
|
182
common/src/app/common/geom/shapes/constraints.cljc
Normal file
182
common/src/app/common/geom/shapes/constraints.cljc
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
;;
|
||||||
|
;; Copyright (c) UXBOX Labs SL
|
||||||
|
|
||||||
|
(ns app.common.geom.shapes.constraints
|
||||||
|
(:require
|
||||||
|
[app.common.geom.matrix :as gmt]
|
||||||
|
[app.common.geom.point :as gpt]
|
||||||
|
[app.common.geom.shapes.common :as gco]
|
||||||
|
[app.common.geom.shapes.transforms :as gtr]
|
||||||
|
[app.common.math :as mth]
|
||||||
|
[app.common.pages.spec :as spec]))
|
||||||
|
|
||||||
|
;; Auxiliary methods to work in an specifica axis
|
||||||
|
(defn get-delta-start [axis rect tr-rect]
|
||||||
|
(if (= :x axis)
|
||||||
|
(- (:x1 tr-rect) (:x1 rect))
|
||||||
|
(- (:y1 tr-rect) (:y1 rect))))
|
||||||
|
|
||||||
|
(defn get-delta-end [axis rect tr-rect]
|
||||||
|
(if (= :x axis)
|
||||||
|
(- (:x2 tr-rect) (:x2 rect))
|
||||||
|
(- (:y2 tr-rect) (:y2 rect))))
|
||||||
|
|
||||||
|
(defn get-delta-size [axis rect tr-rect]
|
||||||
|
(if (= :x axis)
|
||||||
|
(- (:width tr-rect) (:width rect))
|
||||||
|
(- (:height tr-rect) (:height rect))))
|
||||||
|
|
||||||
|
(defn get-delta-center [axis center tr-center]
|
||||||
|
(if (= :x axis)
|
||||||
|
(- (:x tr-center) (:x center))
|
||||||
|
(- (:y tr-center) (:y center))))
|
||||||
|
|
||||||
|
(defn get-displacement
|
||||||
|
([axis delta]
|
||||||
|
(get-displacement axis delta 0 0))
|
||||||
|
|
||||||
|
([axis delta init-x init-y]
|
||||||
|
(if (= :x axis)
|
||||||
|
(gpt/point (+ init-x delta) init-y)
|
||||||
|
(gpt/point init-x (+ init-y delta)))))
|
||||||
|
|
||||||
|
(defn get-scale [axis scale]
|
||||||
|
(if (= :x axis)
|
||||||
|
(gpt/point scale 1)
|
||||||
|
(gpt/point 1 scale)))
|
||||||
|
|
||||||
|
(defn get-size [axis rect]
|
||||||
|
(if (= :x axis)
|
||||||
|
(:width rect)
|
||||||
|
(:height rect)))
|
||||||
|
|
||||||
|
;; Constraint function definitions
|
||||||
|
|
||||||
|
(defmulti constraint-modifier (fn [type & _] type))
|
||||||
|
|
||||||
|
(defmethod constraint-modifier :start
|
||||||
|
[_ axis parent _ _ transformed-parent-rect]
|
||||||
|
|
||||||
|
(let [parent-rect (:selrect parent)
|
||||||
|
delta-start (get-delta-start axis parent-rect transformed-parent-rect)]
|
||||||
|
(if-not (mth/almost-zero? delta-start)
|
||||||
|
{:displacement (get-displacement axis delta-start)}
|
||||||
|
{})))
|
||||||
|
|
||||||
|
(defmethod constraint-modifier :end
|
||||||
|
[_ axis parent _ _ transformed-parent-rect]
|
||||||
|
(let [parent-rect (:selrect parent)
|
||||||
|
delta-end (get-delta-end axis parent-rect transformed-parent-rect)]
|
||||||
|
(if-not (mth/almost-zero? delta-end)
|
||||||
|
{:displacement (get-displacement axis delta-end)}
|
||||||
|
{})))
|
||||||
|
|
||||||
|
(defmethod constraint-modifier :fixed
|
||||||
|
[_ axis parent child _ transformed-parent-rect]
|
||||||
|
(let [parent-rect (:selrect parent)
|
||||||
|
child-rect (:selrect child)
|
||||||
|
|
||||||
|
delta-start (get-delta-start axis parent-rect transformed-parent-rect)
|
||||||
|
delta-size (get-delta-size axis parent-rect transformed-parent-rect)
|
||||||
|
child-size (get-size axis child-rect)
|
||||||
|
child-center (gco/center-rect child-rect)]
|
||||||
|
(if (or (not (mth/almost-zero? delta-start))
|
||||||
|
(not (mth/almost-zero? delta-size)))
|
||||||
|
|
||||||
|
{:displacement (get-displacement axis delta-start)
|
||||||
|
:resize-origin (-> (get-displacement axis delta-start (:x1 child-rect) (:y1 child-rect))
|
||||||
|
(gtr/transform-point-center child-center (:transform child (gmt/matrix))))
|
||||||
|
:resize-vector (get-scale axis (/ (+ child-size delta-size) child-size))}
|
||||||
|
{})))
|
||||||
|
|
||||||
|
(defmethod constraint-modifier :center
|
||||||
|
[_ axis parent _ _ transformed-parent-rect]
|
||||||
|
(let [parent-rect (:selrect parent)
|
||||||
|
parent-center (gco/center-rect parent-rect)
|
||||||
|
transformed-parent-center (gco/center-rect transformed-parent-rect)
|
||||||
|
delta-center (get-delta-center axis parent-center transformed-parent-center)]
|
||||||
|
(if-not (mth/almost-zero? delta-center)
|
||||||
|
{:displacement (get-displacement axis delta-center)}
|
||||||
|
{})))
|
||||||
|
|
||||||
|
(defmethod constraint-modifier :scale
|
||||||
|
[_ axis _ _ modifiers _]
|
||||||
|
(let [{:keys [resize-vector resize-vector-2 displacement]} modifiers]
|
||||||
|
(cond-> {}
|
||||||
|
(and (some? resize-vector)
|
||||||
|
(not (mth/close? (axis resize-vector) 1)))
|
||||||
|
(assoc :resize-origin (:resize-origin modifiers)
|
||||||
|
:resize-vector (if (= :x axis)
|
||||||
|
(gpt/point (:x resize-vector) 1)
|
||||||
|
(gpt/point 1 (:y resize-vector))))
|
||||||
|
|
||||||
|
(and (= :y axis) (some? resize-vector-2)
|
||||||
|
(not (mth/close? (:y resize-vector-2) 1)))
|
||||||
|
(assoc :resize-origin (:resize-origin-2 modifiers)
|
||||||
|
:resize-vector (gpt/point 1 (:y resize-vector-2)))
|
||||||
|
|
||||||
|
(some? displacement)
|
||||||
|
(assoc :displacement
|
||||||
|
(get-displacement axis (-> (gpt/point 0 0)
|
||||||
|
(gpt/transform displacement)
|
||||||
|
(gpt/transform (:resize-transform-inverse modifiers (gmt/matrix)))
|
||||||
|
axis))))))
|
||||||
|
|
||||||
|
(defmethod constraint-modifier :default [_ _ _ _ _]
|
||||||
|
{})
|
||||||
|
|
||||||
|
(def const->type+axis
|
||||||
|
{:left :start
|
||||||
|
:top :start
|
||||||
|
:right :end
|
||||||
|
:bottom :end
|
||||||
|
:leftright :fixed
|
||||||
|
:topbottom :fixed
|
||||||
|
:center :center
|
||||||
|
:scale :scale})
|
||||||
|
|
||||||
|
(defn calc-child-modifiers
|
||||||
|
[parent child modifiers ignore-constraints transformed-parent-rect]
|
||||||
|
(let [constraints-h
|
||||||
|
(if-not ignore-constraints
|
||||||
|
(:constraints-h child (spec/default-constraints-h child))
|
||||||
|
:scale)
|
||||||
|
|
||||||
|
constraints-v
|
||||||
|
(if-not ignore-constraints
|
||||||
|
(:constraints-v child (spec/default-constraints-v child))
|
||||||
|
:scale)
|
||||||
|
|
||||||
|
modifiers-h (constraint-modifier (constraints-h const->type+axis) :x parent child modifiers transformed-parent-rect)
|
||||||
|
modifiers-v (constraint-modifier (constraints-v const->type+axis) :y parent child modifiers transformed-parent-rect)]
|
||||||
|
|
||||||
|
;; Build final child modifiers. Apply transform again to the result, to get the
|
||||||
|
;; real modifiers that need to be applied to the child, including rotation as needed.
|
||||||
|
(cond-> {}
|
||||||
|
(or (contains? modifiers-h :displacement)
|
||||||
|
(contains? modifiers-v :displacement))
|
||||||
|
(assoc :displacement (cond-> (gpt/point (get-in modifiers-h [:displacement :x] 0)
|
||||||
|
(get-in modifiers-v [:displacement :y] 0))
|
||||||
|
(some? (:resize-transform modifiers))
|
||||||
|
(gpt/transform (:resize-transform modifiers))
|
||||||
|
|
||||||
|
:always
|
||||||
|
(gmt/translate-matrix)))
|
||||||
|
|
||||||
|
(:resize-vector modifiers-h)
|
||||||
|
(assoc :resize-origin (:resize-origin modifiers-h)
|
||||||
|
:resize-vector (gpt/point (get-in modifiers-h [:resize-vector :x] 1)
|
||||||
|
(get-in modifiers-h [:resize-vector :y] 1)))
|
||||||
|
|
||||||
|
(:resize-vector modifiers-v)
|
||||||
|
(assoc :resize-origin-2 (:resize-origin modifiers-v)
|
||||||
|
:resize-vector-2 (gpt/point (get-in modifiers-v [:resize-vector :x] 1)
|
||||||
|
(get-in modifiers-v [:resize-vector :y] 1)))
|
||||||
|
|
||||||
|
(:resize-transform modifiers)
|
||||||
|
(assoc :resize-transform (:resize-transform modifiers)
|
||||||
|
:resize-transform-inverse (:resize-transform-inverse modifiers)))))
|
||||||
|
|
|
@ -196,8 +196,8 @@
|
||||||
[point {:keys [cx cy rx ry transform]}]
|
[point {:keys [cx cy rx ry transform]}]
|
||||||
|
|
||||||
(let [center (gpt/point cx cy)
|
(let [center (gpt/point cx cy)
|
||||||
transform (gmt/transform-in center transform)
|
transform (when (some? transform) (gmt/transform-in center transform))
|
||||||
{px :x py :y} (gpt/transform point transform)
|
{px :x py :y} (if (some? transform) (gpt/transform point transform) point)
|
||||||
;; Ellipse inequality formula
|
;; Ellipse inequality formula
|
||||||
;; https://en.wikipedia.org/wiki/Ellipse#Shifted_ellipse
|
;; https://en.wikipedia.org/wiki/Ellipse#Shifted_ellipse
|
||||||
v (+ (/ (mth/sq (- px cx))
|
v (+ (/ (mth/sq (- px cx))
|
||||||
|
@ -256,10 +256,10 @@
|
||||||
"Checks if a set of lines intersect with an ellipse in any point"
|
"Checks if a set of lines intersect with an ellipse in any point"
|
||||||
[rect-lines {:keys [cx cy transform] :as ellipse-data}]
|
[rect-lines {:keys [cx cy transform] :as ellipse-data}]
|
||||||
(let [center (gpt/point cx cy)
|
(let [center (gpt/point cx cy)
|
||||||
transform (gmt/transform-in center transform)]
|
transform (when (some? transform) (gmt/transform-in center transform))]
|
||||||
(some (fn [[p1 p2]]
|
(some (fn [[p1 p2]]
|
||||||
(let [p1 (gpt/transform p1 transform)
|
(let [p1 (if (some? transform) (gpt/transform p1 transform) p1)
|
||||||
p2 (gpt/transform p2 transform)]
|
p2 (if (some? transform) (gpt/transform p2 transform) p2)]
|
||||||
(intersects-line-ellipse? [p1 p2] ellipse-data))) rect-lines)))
|
(intersects-line-ellipse? [p1 p2] ellipse-data))) rect-lines)))
|
||||||
|
|
||||||
(defn overlaps-ellipse?
|
(defn overlaps-ellipse?
|
||||||
|
|
|
@ -279,11 +279,19 @@
|
||||||
(filterv #(and (>= % 0) (<= % 1)))))))
|
(filterv #(and (>= % 0) (<= % 1)))))))
|
||||||
|
|
||||||
(defn command->point
|
(defn command->point
|
||||||
([command] (command->point command nil))
|
([command]
|
||||||
([{params :params} coord]
|
(command->point command nil))
|
||||||
(let [prefix (if coord (name coord) "")
|
|
||||||
xkey (keyword (str prefix "x"))
|
([command coord]
|
||||||
ykey (keyword (str prefix "y"))
|
(let [params (:params command)
|
||||||
|
xkey (case coord
|
||||||
|
:c1 :c1x
|
||||||
|
:c2 :c2x
|
||||||
|
:x)
|
||||||
|
ykey (case coord
|
||||||
|
:c1 :c1y
|
||||||
|
:c2 :c2y
|
||||||
|
:y)
|
||||||
x (get params xkey)
|
x (get params xkey)
|
||||||
y (get params ykey)]
|
y (get params ykey)]
|
||||||
(when (and (some? x) (some? y))
|
(when (and (some? x) (some? y))
|
||||||
|
@ -322,7 +330,7 @@
|
||||||
(command->point command :c1)
|
(command->point command :c1)
|
||||||
(command->point command :c2)]]
|
(command->point command :c2)]]
|
||||||
(->> (curve-extremities curve)
|
(->> (curve-extremities curve)
|
||||||
(map #(curve-values curve %)))))
|
(mapv #(curve-values curve %)))))
|
||||||
[])
|
[])
|
||||||
selrect (gpr/points->selrect points)]
|
selrect (gpr/points->selrect points)]
|
||||||
(-> selrect
|
(-> selrect
|
||||||
|
@ -361,25 +369,24 @@
|
||||||
(update :height #(if (mth/almost-zero? %) 1 %)))))
|
(update :height #(if (mth/almost-zero? %) 1 %)))))
|
||||||
|
|
||||||
(defn move-content [content move-vec]
|
(defn move-content [content move-vec]
|
||||||
(let [set-tr (fn [params px py]
|
(let [dx (:x move-vec)
|
||||||
(let [tr-point (-> (gpt/point (get params px) (get params py))
|
dy (:y move-vec)
|
||||||
(gpt/add move-vec))]
|
|
||||||
(assoc params
|
set-tr
|
||||||
px (:x tr-point)
|
(fn [params px py]
|
||||||
py (:y tr-point))))
|
(assoc params
|
||||||
|
px (+ (get params px) dx)
|
||||||
|
py (+ (get params py) dy)))
|
||||||
|
|
||||||
transform-params
|
transform-params
|
||||||
(fn [{:keys [x c1x c2x] :as params}]
|
(fn [params]
|
||||||
(cond-> params
|
(cond-> params
|
||||||
(not (nil? x)) (set-tr :x :y)
|
(contains? params :x) (set-tr :x :y)
|
||||||
(not (nil? c1x)) (set-tr :c1x :c1y)
|
(contains? params :c1x) (set-tr :c1x :c1y)
|
||||||
(not (nil? c2x)) (set-tr :c2x :c2y)))]
|
(contains? params :c2x) (set-tr :c2x :c2y)))]
|
||||||
|
|
||||||
(->> content
|
(->> content
|
||||||
(mapv (fn [cmd]
|
(mapv #(d/update-when % :params transform-params)))))
|
||||||
(cond-> cmd
|
|
||||||
(map? cmd)
|
|
||||||
(update :params transform-params)))))))
|
|
||||||
|
|
||||||
(defn transform-content
|
(defn transform-content
|
||||||
[content transform]
|
[content transform]
|
||||||
|
@ -393,11 +400,13 @@
|
||||||
transform-params
|
transform-params
|
||||||
(fn [{:keys [x c1x c2x] :as params}]
|
(fn [{:keys [x c1x c2x] :as params}]
|
||||||
(cond-> params
|
(cond-> params
|
||||||
(not (nil? x)) (set-tr :x :y)
|
(some? x) (set-tr :x :y)
|
||||||
(not (nil? c1x)) (set-tr :c1x :c1y)
|
(some? c1x) (set-tr :c1x :c1y)
|
||||||
(not (nil? c2x)) (set-tr :c2x :c2y)))]
|
(some? c2x) (set-tr :c2x :c2y)))]
|
||||||
|
|
||||||
(mapv #(update % :params transform-params) content)))
|
(into []
|
||||||
|
(map #(update % :params transform-params))
|
||||||
|
content)))
|
||||||
|
|
||||||
(defn segments->content
|
(defn segments->content
|
||||||
([segments]
|
([segments]
|
||||||
|
@ -675,8 +684,6 @@
|
||||||
|
|
||||||
(curve-roots c2' :y)))
|
(curve-roots c2' :y)))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(defn ray-line-intersect
|
(defn ray-line-intersect
|
||||||
[point [a b :as line]]
|
[point [a b :as line]]
|
||||||
|
|
||||||
|
@ -707,20 +714,19 @@
|
||||||
[[l1-t] [l2-t]])))
|
[[l1-t] [l2-t]])))
|
||||||
|
|
||||||
(defn ray-curve-intersect
|
(defn ray-curve-intersect
|
||||||
[ray-line c2]
|
[ray-line curve]
|
||||||
|
|
||||||
(let [;; ray-line [point (gpt/point (inc (:x point)) (:y point))]
|
(let [curve-ts (->> (line-curve-crossing ray-line curve)
|
||||||
curve-ts (->> (line-curve-crossing ray-line c2)
|
(filterv #(let [curve-v (curve-values curve %)
|
||||||
(filterv #(let [curve-v (curve-values c2 %)
|
curve-tg (curve-tangent curve %)
|
||||||
curve-tg (curve-tangent c2 %)
|
|
||||||
curve-tg-angle (gpt/angle curve-tg)
|
curve-tg-angle (gpt/angle curve-tg)
|
||||||
ray-t (get-line-tval ray-line curve-v)]
|
ray-t (get-line-tval ray-line curve-v)]
|
||||||
(and (> ray-t 0)
|
(and (> ray-t 0)
|
||||||
(> (mth/abs (- curve-tg-angle 180)) 0.01)
|
(> (mth/abs (- curve-tg-angle 180)) 0.01)
|
||||||
(> (mth/abs (- curve-tg-angle 0)) 0.01)) )))]
|
(> (mth/abs (- curve-tg-angle 0)) 0.01)) )))]
|
||||||
(->> curve-ts
|
(->> curve-ts
|
||||||
(mapv #(vector (curve-values c2 %)
|
(mapv #(vector (curve-values curve %)
|
||||||
(curve-windup c2 %))))))
|
(curve-windup curve %))))))
|
||||||
|
|
||||||
(defn line-curve-intersect
|
(defn line-curve-intersect
|
||||||
[l1 c2]
|
[l1 c2]
|
||||||
|
@ -816,32 +822,58 @@
|
||||||
(->> content
|
(->> content
|
||||||
(some inside-border?))))
|
(some inside-border?))))
|
||||||
|
|
||||||
(defn is-point-in-content?
|
(defn close-content
|
||||||
[point content]
|
[content]
|
||||||
(let [selrect (content->selrect content)
|
(into []
|
||||||
ray-line [point (gpt/point (inc (:x point)) (:y point))]
|
(comp (filter sp/is-closed?)
|
||||||
|
(mapcat :data))
|
||||||
|
(->> content
|
||||||
|
(sp/close-subpaths)
|
||||||
|
(sp/get-subpaths))))
|
||||||
|
|
||||||
closed-content
|
|
||||||
(into []
|
(defn ray-overlaps?
|
||||||
(comp (filter sp/is-closed?)
|
[ray-point {selrect :selrect}]
|
||||||
(mapcat :data))
|
(and (>= (:y ray-point) (:y1 selrect))
|
||||||
(->> content
|
(<= (:y ray-point) (:y2 selrect))))
|
||||||
(sp/close-subpaths)
|
|
||||||
(sp/get-subpaths)))
|
(defn content->geom-data
|
||||||
|
[content]
|
||||||
|
|
||||||
|
(->> content
|
||||||
|
(close-content)
|
||||||
|
(filter #(not= (= :line-to (:command %))
|
||||||
|
(= :curve-to (:command %))))
|
||||||
|
(mapv (fn [segment]
|
||||||
|
{:command (:command segment)
|
||||||
|
:segment segment
|
||||||
|
:geom (if (= :line-to (:command segment))
|
||||||
|
(command->line segment)
|
||||||
|
(command->bezier segment))
|
||||||
|
:selrect (command->selrect segment)}))))
|
||||||
|
|
||||||
|
(defn is-point-in-geom-data?
|
||||||
|
[point content-geom]
|
||||||
|
|
||||||
|
(let [ray-line [point (gpt/point (inc (:x point)) (:y point))]
|
||||||
|
|
||||||
cast-ray
|
cast-ray
|
||||||
(fn [cmd]
|
(fn [data]
|
||||||
(case (:command cmd)
|
(case (:command data)
|
||||||
:line-to (ray-line-intersect point (command->line cmd))
|
:line-to
|
||||||
:curve-to (ray-curve-intersect ray-line (command->bezier cmd))
|
(ray-line-intersect point (:geom data))
|
||||||
#_:else []))]
|
|
||||||
|
|
||||||
(and (gpr/contains-point? selrect point)
|
:curve-to
|
||||||
(->> closed-content
|
(ray-curve-intersect ray-line (:geom data))
|
||||||
(mapcat cast-ray)
|
|
||||||
(map second)
|
#_:default []))]
|
||||||
(reduce +)
|
|
||||||
(not= 0)))))
|
(->> content-geom
|
||||||
|
(filter (partial ray-overlaps? point))
|
||||||
|
(mapcat cast-ray)
|
||||||
|
(map second)
|
||||||
|
(reduce +)
|
||||||
|
(not= 0))))
|
||||||
|
|
||||||
(defn split-line-to
|
(defn split-line-to
|
||||||
"Given a point and a line-to command will create a two new line-to commands
|
"Given a point and a line-to command will create a two new line-to commands
|
||||||
|
|
|
@ -129,3 +129,11 @@
|
||||||
(<= (:x2 sr2) (:x2 sr1))
|
(<= (:x2 sr2) (:x2 sr1))
|
||||||
(>= (:y1 sr2) (:y1 sr1))
|
(>= (:y1 sr2) (:y1 sr1))
|
||||||
(<= (:y2 sr2) (:y2 sr1))))
|
(<= (:y2 sr2) (:y2 sr1))))
|
||||||
|
|
||||||
|
(defn round-selrect
|
||||||
|
[selrect]
|
||||||
|
(-> selrect
|
||||||
|
(update :x mth/round)
|
||||||
|
(update :y mth/round)
|
||||||
|
(update :width mth/round)
|
||||||
|
(update :height mth/round)))
|
||||||
|
|
|
@ -14,20 +14,26 @@
|
||||||
[app.common.geom.shapes.path :as gpa]
|
[app.common.geom.shapes.path :as gpa]
|
||||||
[app.common.geom.shapes.rect :as gpr]
|
[app.common.geom.shapes.rect :as gpr]
|
||||||
[app.common.math :as mth]
|
[app.common.math :as mth]
|
||||||
[app.common.pages.spec :as spec]
|
|
||||||
[app.common.spec :as us]
|
[app.common.spec :as us]
|
||||||
[app.common.text :as txt]))
|
[app.common.text :as txt]))
|
||||||
|
|
||||||
|
(def ^:dynamic *skip-adjust* false)
|
||||||
|
|
||||||
;; --- Relative Movement
|
;; --- Relative Movement
|
||||||
|
|
||||||
(defn- move-selrect [selrect {dx :x dy :y}]
|
(defn- move-selrect [selrect pt]
|
||||||
(-> selrect
|
(when (and (some? selrect) (some? pt))
|
||||||
(d/update-when :x + dx)
|
(let [dx (.-x pt)
|
||||||
(d/update-when :y + dy)
|
dy (.-y pt)
|
||||||
(d/update-when :x1 + dx)
|
{:keys [x y x1 y1 x2 y2 width height]} selrect]
|
||||||
(d/update-when :y1 + dy)
|
{:x (if (some? x) (+ dx x) x)
|
||||||
(d/update-when :x2 + dx)
|
:y (if (some? y) (+ dy y) y)
|
||||||
(d/update-when :y2 + dy)))
|
:x1 (if (some? x1) (+ dx x1) x1)
|
||||||
|
:y1 (if (some? y1) (+ dy y1) y1)
|
||||||
|
:x2 (if (some? x2) (+ dx x2) x2)
|
||||||
|
:y2 (if (some? y2) (+ dy y2) y2)
|
||||||
|
:width width
|
||||||
|
:height height})))
|
||||||
|
|
||||||
(defn- move-points [points move-vec]
|
(defn- move-points [points move-vec]
|
||||||
(->> points
|
(->> points
|
||||||
|
@ -46,6 +52,8 @@
|
||||||
(update :points move-points move-vec)
|
(update :points move-points move-vec)
|
||||||
(d/update-when :x + dx)
|
(d/update-when :x + dx)
|
||||||
(d/update-when :y + dy)
|
(d/update-when :y + dy)
|
||||||
|
(cond-> (= :bool (:type shape))
|
||||||
|
(update :bool-content gpa/move-content move-vec))
|
||||||
(cond-> (= :path (:type shape))
|
(cond-> (= :path (:type shape))
|
||||||
(update :content gpa/move-content move-vec)))))
|
(update :content gpa/move-content move-vec)))))
|
||||||
|
|
||||||
|
@ -171,9 +179,11 @@
|
||||||
|
|
||||||
(defn calculate-adjust-matrix
|
(defn calculate-adjust-matrix
|
||||||
"Calculates a matrix that is a series of transformations we have to do to the transformed rectangle so that
|
"Calculates a matrix that is a series of transformations we have to do to the transformed rectangle so that
|
||||||
after applying them the end result is the `shape-pathn-temp`.
|
after applying them the end result is the `shape-path-temp`.
|
||||||
This is compose of three transformations: skew, resize and rotation"
|
This is compose of three transformations: skew, resize and rotation"
|
||||||
([points-temp points-rec] (calculate-adjust-matrix points-temp points-rec false false))
|
([points-temp points-rec]
|
||||||
|
(calculate-adjust-matrix points-temp points-rec false false))
|
||||||
|
|
||||||
([points-temp points-rec flip-x flip-y]
|
([points-temp points-rec flip-x flip-y]
|
||||||
(let [center (gco/center-points points-temp)
|
(let [center (gco/center-points points-temp)
|
||||||
|
|
||||||
|
@ -211,64 +221,76 @@
|
||||||
stretch-matrix (gmt/multiply (gmt/rotate-matrix rotation-angle) stretch-matrix)
|
stretch-matrix (gmt/multiply (gmt/rotate-matrix rotation-angle) stretch-matrix)
|
||||||
|
|
||||||
;; This is the inverse to be able to remove the transformation
|
;; This is the inverse to be able to remove the transformation
|
||||||
stretch-matrix-inverse (-> (gmt/matrix)
|
stretch-matrix-inverse
|
||||||
(gmt/scale (gpt/point (/ 1 w3) (/ 1 h3)))
|
(gmt/multiply (gmt/scale-matrix (gpt/point (/ 1 w3) (/ 1 h3)))
|
||||||
(gmt/skew (- skew-angle) 0)
|
(gmt/skew-matrix (- skew-angle) 0)
|
||||||
(gmt/rotate (- rotation-angle)))]
|
(gmt/rotate-matrix (- rotation-angle)))]
|
||||||
[stretch-matrix stretch-matrix-inverse rotation-angle])))
|
[stretch-matrix stretch-matrix-inverse rotation-angle])))
|
||||||
|
|
||||||
(defn- apply-transform
|
(defn is-rotated?
|
||||||
"Given a new set of points transformed, set up the rectangle so it keeps
|
[[a b _c _d]]
|
||||||
its properties. We adjust de x,y,width,height and create a custom transform"
|
;; true if either a-b or c-d are parallel to the axis
|
||||||
[shape transform round-coords?]
|
(not (mth/close? (:y a) (:y b))))
|
||||||
;; FIXME: Improve performance
|
|
||||||
(let [points (-> shape :points (gco/transform-points transform))
|
|
||||||
center (gco/center-points points)
|
|
||||||
|
|
||||||
;; Reverse the current transformation stack to get the base rectangle
|
(defn- adjust-rotated-transform
|
||||||
tr-inverse (:transform-inverse shape (gmt/matrix))
|
[{:keys [transform transform-inverse flip-x flip-y]} points]
|
||||||
|
(let [center (gco/center-points points)
|
||||||
|
|
||||||
points-temp (gco/transform-points points center tr-inverse)
|
points-temp (cond-> points
|
||||||
|
(some? transform-inverse)
|
||||||
|
(gco/transform-points center transform-inverse))
|
||||||
points-temp-dim (calculate-dimensions points-temp)
|
points-temp-dim (calculate-dimensions points-temp)
|
||||||
|
|
||||||
;; This rectangle is the new data for the current rectangle. We want to change our rectangle
|
;; This rectangle is the new data for the current rectangle. We want to change our rectangle
|
||||||
;; to have this width, height, x, y
|
;; to have this width, height, x, y
|
||||||
rect-shape (-> (gco/make-centered-rect
|
new-width (max 1 (:width points-temp-dim))
|
||||||
center
|
new-height (max 1 (:height points-temp-dim))
|
||||||
(:width points-temp-dim)
|
selrect (gco/make-centered-selrect center new-width new-height)
|
||||||
(:height points-temp-dim))
|
|
||||||
(update :width max 1)
|
|
||||||
(update :height max 1))
|
|
||||||
|
|
||||||
rect-points (gpr/rect->points rect-shape)
|
rect-points (gpr/rect->points selrect)
|
||||||
|
[matrix matrix-inverse] (calculate-adjust-matrix points-temp rect-points flip-x flip-y)]
|
||||||
|
|
||||||
[matrix matrix-inverse] (calculate-adjust-matrix points-temp rect-points (:flip-x shape) (:flip-y shape))
|
[selrect
|
||||||
|
(if transform (gmt/multiply transform matrix) matrix)
|
||||||
|
(if transform-inverse (gmt/multiply matrix-inverse transform-inverse) matrix-inverse)]))
|
||||||
|
|
||||||
rect-shape (cond-> rect-shape
|
(defn- apply-transform
|
||||||
round-coords?
|
"Given a new set of points transformed, set up the rectangle so it keeps
|
||||||
(-> (update :x mth/round)
|
its properties. We adjust de x,y,width,height and create a custom transform"
|
||||||
(update :y mth/round)
|
[shape transform-mtx round-coords?]
|
||||||
(update :width mth/round)
|
|
||||||
(update :height mth/round)))
|
|
||||||
|
|
||||||
shape (cond
|
(let [points' (:points shape)
|
||||||
(= :path (:type shape))
|
points (gco/transform-points points' transform-mtx)
|
||||||
(-> shape
|
bool? (= (:type shape) :bool)
|
||||||
(update :content #(gpa/transform-content % transform)))
|
path? (= (:type shape) :path)
|
||||||
|
rotated? (is-rotated? points)
|
||||||
|
|
||||||
:else
|
[selrect transform transform-inverse]
|
||||||
(-> shape
|
(if (not rotated?)
|
||||||
(merge rect-shape)))
|
[(gpr/points->selrect points) nil nil]
|
||||||
|
(adjust-rotated-transform shape points))
|
||||||
|
|
||||||
|
selrect (cond-> selrect
|
||||||
|
round-coords? gpr/round-selrect)
|
||||||
|
|
||||||
|
;; Redondear los points?
|
||||||
base-rotation (or (:rotation shape) 0)
|
base-rotation (or (:rotation shape) 0)
|
||||||
modif-rotation (or (get-in shape [:modifiers :rotation]) 0)]
|
modif-rotation (or (get-in shape [:modifiers :rotation]) 0)
|
||||||
|
rotation (mod (+ base-rotation modif-rotation) 360)]
|
||||||
|
|
||||||
(as-> shape $
|
(-> shape
|
||||||
(update $ :transform #(gmt/multiply (or % (gmt/matrix)) matrix))
|
(cond-> bool?
|
||||||
(update $ :transform-inverse #(gmt/multiply matrix-inverse (or % (gmt/matrix))))
|
(update :bool-content gpa/transform-content transform-mtx))
|
||||||
(assoc $ :points (into [] points))
|
(cond-> path?
|
||||||
(assoc $ :selrect (gpr/rect->selrect rect-shape))
|
(update :content gpa/transform-content transform-mtx))
|
||||||
(assoc $ :rotation (mod (+ base-rotation modif-rotation) 360)))))
|
(cond-> (not path?)
|
||||||
|
(-> (merge (select-keys selrect [:x :y :width :height]))))
|
||||||
|
(cond-> transform
|
||||||
|
(-> (assoc :transform transform)
|
||||||
|
(assoc :transform-inverse transform-inverse)))
|
||||||
|
(assoc :selrect selrect)
|
||||||
|
(assoc :points points)
|
||||||
|
(assoc :rotation rotation))))
|
||||||
|
|
||||||
(defn- update-group-viewbox
|
(defn- update-group-viewbox
|
||||||
"Updates the viewbox for groups imported from SVG's"
|
"Updates the viewbox for groups imported from SVG's"
|
||||||
|
@ -402,53 +424,54 @@
|
||||||
|
|
||||||
(def merge-modifiers (memoize merge-modifiers*))
|
(def merge-modifiers (memoize merge-modifiers*))
|
||||||
|
|
||||||
(defn- modifiers->transform
|
(defn modifiers->transform
|
||||||
[center modifiers]
|
([modifiers]
|
||||||
(let [ds-modifier (:displacement modifiers (gmt/matrix))
|
(modifiers->transform nil modifiers))
|
||||||
{res-x :x res-y :y} (:resize-vector modifiers (gpt/point 1 1))
|
|
||||||
{res-x-2 :x res-y-2 :y} (:resize-vector-2 modifiers (gpt/point 1 1))
|
|
||||||
|
|
||||||
;; Normalize x/y vector coordinates because scale by 0 is infinite
|
([center modifiers]
|
||||||
res-x (normalize-scale res-x)
|
(let [displacement (:displacement modifiers)
|
||||||
res-y (normalize-scale res-y)
|
resize-v1 (:resize-vector modifiers)
|
||||||
resize (gpt/point res-x res-y)
|
resize-v2 (:resize-vector-2 modifiers)
|
||||||
|
origin-1 (:resize-origin modifiers (gpt/point))
|
||||||
|
origin-2 (:resize-origin-2 modifiers (gpt/point))
|
||||||
|
|
||||||
res-x-2 (normalize-scale res-x-2)
|
;; Normalize x/y vector coordinates because scale by 0 is infinite
|
||||||
res-y-2 (normalize-scale res-y-2)
|
resize-1 (when (some? resize-v1)
|
||||||
resize-2 (gpt/point res-x-2 res-y-2)
|
(gpt/point (normalize-scale (:x resize-v1))
|
||||||
|
(normalize-scale (:y resize-v1))))
|
||||||
|
|
||||||
origin (:resize-origin modifiers (gpt/point 0 0))
|
resize-2 (when (some? resize-v2)
|
||||||
origin-2 (:resize-origin-2 modifiers (gpt/point 0 0))
|
(gpt/point (normalize-scale (:x resize-v2))
|
||||||
|
(normalize-scale (:y resize-v2))))
|
||||||
|
|
||||||
resize-transform (:resize-transform modifiers (gmt/matrix))
|
|
||||||
resize-transform-inverse (:resize-transform-inverse modifiers (gmt/matrix))
|
|
||||||
rt-modif (or (:rotation modifiers) 0)
|
|
||||||
|
|
||||||
center (gpt/transform center ds-modifier)
|
resize-transform (:resize-transform modifiers (gmt/matrix))
|
||||||
|
resize-transform-inverse (:resize-transform-inverse modifiers (gmt/matrix))
|
||||||
|
|
||||||
transform (-> (gmt/matrix)
|
rt-modif (:rotation modifiers)]
|
||||||
|
|
||||||
;; Applies the current resize transformation
|
(cond-> (gmt/matrix)
|
||||||
(gmt/translate origin)
|
(some? displacement)
|
||||||
(gmt/multiply resize-transform)
|
(gmt/multiply displacement)
|
||||||
(gmt/scale resize)
|
|
||||||
(gmt/multiply resize-transform-inverse)
|
|
||||||
(gmt/translate (gpt/negate origin))
|
|
||||||
|
|
||||||
(gmt/translate origin-2)
|
(some? resize-1)
|
||||||
(gmt/multiply resize-transform)
|
(-> (gmt/translate origin-1)
|
||||||
(gmt/scale resize-2)
|
(gmt/multiply resize-transform)
|
||||||
(gmt/multiply resize-transform-inverse)
|
(gmt/scale resize-1)
|
||||||
(gmt/translate (gpt/negate origin-2))
|
(gmt/multiply resize-transform-inverse)
|
||||||
|
(gmt/translate (gpt/negate origin-1)))
|
||||||
|
|
||||||
;; Applies the stacked transformations
|
(some? resize-2)
|
||||||
(gmt/translate center)
|
(-> (gmt/translate origin-2)
|
||||||
(gmt/multiply (gmt/rotate-matrix rt-modif))
|
(gmt/multiply resize-transform)
|
||||||
(gmt/translate (gpt/negate center))
|
(gmt/scale resize-2)
|
||||||
|
(gmt/multiply resize-transform-inverse)
|
||||||
|
(gmt/translate (gpt/negate origin-2)))
|
||||||
|
|
||||||
;; Displacement
|
(some? rt-modif)
|
||||||
(gmt/multiply ds-modifier))]
|
(-> (gmt/translate center)
|
||||||
transform))
|
(gmt/multiply (gmt/rotate-matrix rt-modif))
|
||||||
|
(gmt/translate (gpt/negate center)))))))
|
||||||
|
|
||||||
(defn- set-flip [shape modifiers]
|
(defn- set-flip [shape modifiers]
|
||||||
(let [rx (or (get-in modifiers [:resize-vector :x])
|
(let [rx (or (get-in modifiers [:resize-vector :x])
|
||||||
|
@ -492,50 +515,58 @@
|
||||||
%)))
|
%)))
|
||||||
shape))
|
shape))
|
||||||
|
|
||||||
(defn -transform-shape
|
(defn apply-modifiers
|
||||||
[shape {:keys [round-coords?]
|
[shape modifiers round-coords?]
|
||||||
:or {round-coords? true}}]
|
(let [center (gco/center-shape shape)
|
||||||
(if (and (contains? shape :modifiers) (empty-modifiers? (:modifiers shape)))
|
transform (modifiers->transform center modifiers)]
|
||||||
(dissoc shape :modifiers)
|
(apply-transform shape transform round-coords?)))
|
||||||
(let [shape (apply-displacement shape)
|
|
||||||
center (gco/center-shape shape)
|
|
||||||
modifiers (:modifiers shape)]
|
|
||||||
(if (and (not (empty-modifiers? modifiers)) center)
|
|
||||||
(let [transform (modifiers->transform center modifiers)]
|
|
||||||
(-> shape
|
|
||||||
(set-flip modifiers)
|
|
||||||
(apply-transform transform round-coords?)
|
|
||||||
(apply-text-resize modifiers)
|
|
||||||
(dissoc :modifiers)))
|
|
||||||
shape))))
|
|
||||||
|
|
||||||
(def transform-shape* (memoize -transform-shape))
|
|
||||||
|
|
||||||
(defn transform-shape
|
(defn transform-shape
|
||||||
([shape]
|
([shape]
|
||||||
(transform-shape* shape nil))
|
(transform-shape shape nil))
|
||||||
([shape options]
|
|
||||||
(transform-shape* shape options)))
|
|
||||||
|
|
||||||
(defn calc-transformed-parent-rect
|
([shape {:keys [round-coords?] :or {round-coords? true}}]
|
||||||
[{:keys [selrect] :as shape} {:keys [displacement resize-transform-inverse resize-vector resize-origin resize-vector-2 resize-origin-2]}]
|
(let [modifiers (:modifiers shape)]
|
||||||
|
(cond
|
||||||
|
(nil? modifiers)
|
||||||
|
shape
|
||||||
|
|
||||||
|
(empty-modifiers? modifiers)
|
||||||
|
(dissoc shape :modifiers)
|
||||||
|
|
||||||
|
:else
|
||||||
|
(let [shape (apply-displacement shape)
|
||||||
|
modifiers (:modifiers shape)]
|
||||||
|
(cond-> shape
|
||||||
|
(not (empty-modifiers? modifiers))
|
||||||
|
(-> (set-flip modifiers)
|
||||||
|
(apply-modifiers modifiers round-coords?)
|
||||||
|
(apply-text-resize modifiers))
|
||||||
|
|
||||||
|
:always
|
||||||
|
(dissoc :modifiers)))))))
|
||||||
|
|
||||||
|
(defn transform-selrect
|
||||||
|
[selrect {:keys [displacement resize-transform-inverse resize-vector resize-origin resize-vector-2 resize-origin-2]}]
|
||||||
|
|
||||||
|
;; FIXME: Improve Performance
|
||||||
(let [resize-transform-inverse (or resize-transform-inverse (gmt/matrix))
|
(let [resize-transform-inverse (or resize-transform-inverse (gmt/matrix))
|
||||||
|
|
||||||
displacement
|
displacement
|
||||||
(when (some? displacement)
|
(when (some? displacement)
|
||||||
(-> (gpt/point 0 0)
|
(gmt/multiply resize-transform-inverse displacement)
|
||||||
|
#_(-> (gpt/point 0 0)
|
||||||
(gpt/transform displacement)
|
(gpt/transform displacement)
|
||||||
(gpt/transform resize-transform-inverse)
|
(gpt/transform resize-transform-inverse)
|
||||||
(gmt/translate-matrix)))
|
(gmt/translate-matrix)))
|
||||||
|
|
||||||
resize-origin
|
resize-origin
|
||||||
(when (some? resize-origin)
|
(when (some? resize-origin)
|
||||||
(transform-point-center resize-origin (gco/center-shape shape) resize-transform-inverse))
|
(transform-point-center resize-origin (gco/center-selrect selrect) resize-transform-inverse))
|
||||||
|
|
||||||
resize-origin-2
|
resize-origin-2
|
||||||
(when (some? resize-origin-2)
|
(when (some? resize-origin-2)
|
||||||
(transform-point-center resize-origin-2 (gco/center-shape shape) resize-transform-inverse))]
|
(transform-point-center resize-origin-2 (gco/center-selrect selrect) resize-transform-inverse))]
|
||||||
|
|
||||||
(if (and (nil? displacement) (nil? resize-origin) (nil? resize-origin-2))
|
(if (and (nil? displacement) (nil? resize-origin) (nil? resize-origin-2))
|
||||||
selrect
|
selrect
|
||||||
|
@ -556,177 +587,11 @@
|
||||||
:always
|
:always
|
||||||
(gpr/points->selrect)))))
|
(gpr/points->selrect)))))
|
||||||
|
|
||||||
(defn calc-child-modifiers
|
|
||||||
"Given the modifiers to apply to the parent, calculate the corresponding
|
|
||||||
modifiers for the child, depending on the child constraints."
|
|
||||||
([parent child parent-modifiers ignore-constraints]
|
|
||||||
(let [transformed-parent-rect (calc-transformed-parent-rect parent parent-modifiers )]
|
|
||||||
(calc-child-modifiers parent child parent-modifiers ignore-constraints transformed-parent-rect)))
|
|
||||||
|
|
||||||
([parent child parent-modifiers ignore-constraints transformed-parent-rect]
|
|
||||||
(let [parent-rect (:selrect parent)
|
|
||||||
child-rect (:selrect child)
|
|
||||||
|
|
||||||
;; Apply the modifiers to the parent's selrect, to check the difference with
|
|
||||||
;; the original, and calculate child transformations from this.
|
|
||||||
;;
|
|
||||||
;; Note that a shape's selrect is always "horizontal" (i.e. without applying
|
|
||||||
;; the shape transform, that may include some rotation and skew). Thus, to
|
|
||||||
;; apply the modifiers, we first apply to them the transform-inverse.
|
|
||||||
|
|
||||||
;; Calculate the modifiers in the horizontal and vertical directions
|
|
||||||
;; depending on the child constraints.
|
|
||||||
constraints-h (if-not ignore-constraints
|
|
||||||
(get child :constraints-h (spec/default-constraints-h child))
|
|
||||||
:scale)
|
|
||||||
constraints-v (if-not ignore-constraints
|
|
||||||
(get child :constraints-v (spec/default-constraints-v child))
|
|
||||||
:scale)
|
|
||||||
|
|
||||||
modifiers-h (case constraints-h
|
|
||||||
:left
|
|
||||||
(let [delta-left (- (:x1 transformed-parent-rect) (:x1 parent-rect))]
|
|
||||||
|
|
||||||
(if-not (mth/almost-zero? delta-left)
|
|
||||||
{:displacement (gpt/point delta-left 0)} ;; we convert to matrix below
|
|
||||||
{}))
|
|
||||||
|
|
||||||
:right
|
|
||||||
(let [delta-right (- (:x2 transformed-parent-rect) (:x2 parent-rect))]
|
|
||||||
(if-not (mth/almost-zero? delta-right)
|
|
||||||
{:displacement (gpt/point delta-right 0)}
|
|
||||||
{}))
|
|
||||||
|
|
||||||
:leftright
|
|
||||||
(let [delta-left (- (:x1 transformed-parent-rect) (:x1 parent-rect))
|
|
||||||
delta-width (- (:width transformed-parent-rect) (:width parent-rect))]
|
|
||||||
(if (or (not (mth/almost-zero? delta-left))
|
|
||||||
(not (mth/almost-zero? delta-width)))
|
|
||||||
{:displacement (gpt/point delta-left 0)
|
|
||||||
:resize-origin (-> (gpt/point (+ (:x1 child-rect) delta-left)
|
|
||||||
(:y1 child-rect))
|
|
||||||
(transform-point-center
|
|
||||||
(gco/center-rect child-rect)
|
|
||||||
(:transform child (gmt/matrix))))
|
|
||||||
:resize-vector (gpt/point (/ (+ (:width child-rect) delta-width)
|
|
||||||
(:width child-rect)) 1)}
|
|
||||||
{}))
|
|
||||||
|
|
||||||
:center
|
|
||||||
(let [parent-center (gco/center-rect parent-rect)
|
|
||||||
transformed-parent-center (gco/center-rect transformed-parent-rect)
|
|
||||||
delta-center (- (:x transformed-parent-center) (:x parent-center))]
|
|
||||||
(if-not (mth/almost-zero? delta-center)
|
|
||||||
{:displacement (gpt/point delta-center 0)}
|
|
||||||
{}))
|
|
||||||
|
|
||||||
:scale
|
|
||||||
(cond-> {}
|
|
||||||
(and (:resize-vector parent-modifiers)
|
|
||||||
(not (mth/close? (:x (:resize-vector parent-modifiers)) 1)))
|
|
||||||
(assoc :resize-origin (:resize-origin parent-modifiers)
|
|
||||||
:resize-vector (gpt/point (:x (:resize-vector parent-modifiers)) 1))
|
|
||||||
|
|
||||||
;; resize-vector-2 is always for vertical modifiers, so no need to
|
|
||||||
;; check it here.
|
|
||||||
|
|
||||||
(:displacement parent-modifiers)
|
|
||||||
(assoc :displacement
|
|
||||||
(gpt/point (-> (gpt/point 0 0)
|
|
||||||
(gpt/transform (:displacement parent-modifiers))
|
|
||||||
(gpt/transform (:resize-transform-inverse parent-modifiers (gmt/matrix)))
|
|
||||||
(:x))
|
|
||||||
0)))
|
|
||||||
{})
|
|
||||||
|
|
||||||
modifiers-v (case constraints-v
|
|
||||||
:top
|
|
||||||
(let [delta-top (- (:y1 transformed-parent-rect) (:y1 parent-rect))]
|
|
||||||
(if-not (mth/almost-zero? delta-top)
|
|
||||||
{:displacement (gpt/point 0 delta-top)} ;; we convert to matrix below
|
|
||||||
{}))
|
|
||||||
|
|
||||||
:bottom
|
|
||||||
(let [delta-bottom (- (:y2 transformed-parent-rect) (:y2 parent-rect))]
|
|
||||||
(if-not (mth/almost-zero? delta-bottom)
|
|
||||||
{:displacement (gpt/point 0 delta-bottom)}
|
|
||||||
{}))
|
|
||||||
|
|
||||||
:topbottom
|
|
||||||
(let [delta-top (- (:y1 transformed-parent-rect) (:y1 parent-rect))
|
|
||||||
delta-height (- (:height transformed-parent-rect) (:height parent-rect))]
|
|
||||||
(if (or (not (mth/almost-zero? delta-top))
|
|
||||||
(not (mth/almost-zero? delta-height)))
|
|
||||||
{:displacement (gpt/point 0 delta-top)
|
|
||||||
:resize-origin (-> (gpt/point (:x1 child-rect)
|
|
||||||
(+ (:y1 child-rect) delta-top))
|
|
||||||
(transform-point-center
|
|
||||||
(gco/center-rect child-rect)
|
|
||||||
(:transform child (gmt/matrix))))
|
|
||||||
:resize-vector (gpt/point 1 (/ (+ (:height child-rect) delta-height)
|
|
||||||
(:height child-rect)))}
|
|
||||||
{}))
|
|
||||||
|
|
||||||
:center
|
|
||||||
(let [parent-center (gco/center-rect parent-rect)
|
|
||||||
transformed-parent-center (gco/center-rect transformed-parent-rect)
|
|
||||||
delta-center (- (:y transformed-parent-center) (:y parent-center))]
|
|
||||||
(if-not (mth/almost-zero? delta-center)
|
|
||||||
{:displacement (gpt/point 0 delta-center)}
|
|
||||||
{}))
|
|
||||||
|
|
||||||
:scale
|
|
||||||
(cond-> {}
|
|
||||||
(and (:resize-vector parent-modifiers)
|
|
||||||
(not (mth/close? (:y (:resize-vector parent-modifiers)) 1)))
|
|
||||||
(assoc :resize-origin (:resize-origin parent-modifiers)
|
|
||||||
:resize-vector (gpt/point 1 (:y (:resize-vector parent-modifiers))))
|
|
||||||
|
|
||||||
;; If there is a resize-vector-2, this means that we come from a recursive
|
|
||||||
;; call, and the resize-vector has no vertical data, so we may override it.
|
|
||||||
(and (:resize-vector-2 parent-modifiers)
|
|
||||||
(not (mth/close? (:y (:resize-vector-2 parent-modifiers)) 1)))
|
|
||||||
(assoc :resize-origin (:resize-origin-2 parent-modifiers)
|
|
||||||
:resize-vector (gpt/point 1 (:y (:resize-vector-2 parent-modifiers))))
|
|
||||||
|
|
||||||
(:displacement parent-modifiers)
|
|
||||||
(assoc :displacement
|
|
||||||
(gpt/point 0 (-> (gpt/point 0 0)
|
|
||||||
(gpt/transform (:displacement parent-modifiers))
|
|
||||||
(gpt/transform (:resize-transform-inverse parent-modifiers (gmt/matrix)))
|
|
||||||
(:y)))))
|
|
||||||
{})]
|
|
||||||
|
|
||||||
;; Build final child modifiers. Apply transform again to the result, to get the
|
|
||||||
;; real modifiers that need to be applied to the child, including rotation as needed.
|
|
||||||
(cond-> {}
|
|
||||||
(or (:displacement modifiers-h) (:displacement modifiers-v))
|
|
||||||
(assoc :displacement (gmt/translate-matrix
|
|
||||||
(-> (gpt/point (get (:displacement modifiers-h) :x 0)
|
|
||||||
(get (:displacement modifiers-v) :y 0))
|
|
||||||
(gpt/transform
|
|
||||||
(:resize-transform parent-modifiers (gmt/matrix))))))
|
|
||||||
|
|
||||||
(:resize-vector modifiers-h)
|
|
||||||
(assoc :resize-origin (:resize-origin modifiers-h)
|
|
||||||
:resize-vector (gpt/point (get (:resize-vector modifiers-h) :x 1)
|
|
||||||
(get (:resize-vector modifiers-h) :y 1)))
|
|
||||||
|
|
||||||
(:resize-vector modifiers-v)
|
|
||||||
(assoc :resize-origin-2 (:resize-origin modifiers-v)
|
|
||||||
:resize-vector-2 (gpt/point (get (:resize-vector modifiers-v) :x 1)
|
|
||||||
(get (:resize-vector modifiers-v) :y 1)))
|
|
||||||
|
|
||||||
(:resize-transform parent-modifiers)
|
|
||||||
(assoc :resize-transform (:resize-transform parent-modifiers)
|
|
||||||
:resize-transform-inverse (:resize-transform-inverse parent-modifiers))))))
|
|
||||||
|
|
||||||
|
|
||||||
(defn selection-rect
|
(defn selection-rect
|
||||||
"Returns a rect that contains all the shapes and is aware of the
|
"Returns a rect that contains all the shapes and is aware of the
|
||||||
rotation of each shape. Mainly used for multiple selection."
|
rotation of each shape. Mainly used for multiple selection."
|
||||||
[shapes]
|
[shapes]
|
||||||
(->> shapes
|
(->> shapes
|
||||||
(transform-shape)
|
(map (comp gpr/points->selrect :points transform-shape))
|
||||||
(map (comp gpr/points->selrect :points))
|
|
||||||
(gpr/join-selrects)))
|
(gpr/join-selrects)))
|
||||||
|
|
|
@ -46,13 +46,14 @@
|
||||||
(defn get-root-shape
|
(defn get-root-shape
|
||||||
"Get the root shape linked to a component for this shape, if any"
|
"Get the root shape linked to a component for this shape, if any"
|
||||||
[shape objects]
|
[shape objects]
|
||||||
(if-not (:shape-ref shape)
|
|
||||||
nil
|
(cond
|
||||||
(if (:component-root? shape)
|
(some? (:component-root? shape))
|
||||||
shape
|
shape
|
||||||
(if-let [parent-id (:parent-id shape)]
|
|
||||||
(get-root-shape (get objects parent-id) objects)
|
(some? (:shape-ref shape))
|
||||||
nil))))
|
(recur (get objects (:parent-id shape))
|
||||||
|
objects)))
|
||||||
|
|
||||||
(defn make-container
|
(defn make-container
|
||||||
[page-or-component type]
|
[page-or-component type]
|
||||||
|
|
|
@ -91,55 +91,55 @@
|
||||||
:else
|
:else
|
||||||
[[] []]))
|
[[] []]))
|
||||||
|
|
||||||
(defn split
|
|
||||||
[seg-1 seg-2]
|
|
||||||
(let [r1 (gsp/command->selrect seg-1)
|
|
||||||
r2 (gsp/command->selrect seg-2)]
|
|
||||||
(if (not (gpr/overlaps-rects? r1 r2))
|
|
||||||
[[seg-1] [seg-2]]
|
|
||||||
(let [[ts-seg-1 ts-seg-2] (split-ts seg-1 seg-2)]
|
|
||||||
[(-> (split-command seg-1 ts-seg-1) (add-previous (:prev seg-1)))
|
|
||||||
(-> (split-command seg-2 ts-seg-2) (add-previous (:prev seg-2)))]))))
|
|
||||||
|
|
||||||
(defn content-intersect-split
|
(defn content-intersect-split
|
||||||
[content-a content-b]
|
[content-a content-b sr-a sr-b]
|
||||||
|
|
||||||
(let [cache (atom {})]
|
(let [command->selrect (memoize gsp/command->selrect)]
|
||||||
(letfn [(split-cache [seg-1 seg-2]
|
|
||||||
(cond
|
|
||||||
(contains? @cache [seg-1 seg-2])
|
|
||||||
(first (get @cache [seg-1 seg-2]))
|
|
||||||
|
|
||||||
(contains? @cache [seg-2 seg-1])
|
(letfn [(overlap-segment-selrect?
|
||||||
(second (get @cache [seg-2 seg-1]))
|
[segment selrect]
|
||||||
|
(if (= :move-to (:command segment))
|
||||||
|
false
|
||||||
|
(let [r1 (command->selrect segment)]
|
||||||
|
(gpr/overlaps-rects? r1 selrect))))
|
||||||
|
|
||||||
:else
|
(overlap-segments?
|
||||||
(let [value (split seg-1 seg-2)]
|
[seg-1 seg-2]
|
||||||
(swap! cache assoc [seg-1 seg-2] value)
|
(if (or (= :move-to (:command seg-1))
|
||||||
(first value))))
|
(= :move-to (:command seg-2)))
|
||||||
|
false
|
||||||
|
(let [r1 (command->selrect seg-1)
|
||||||
|
r2 (command->selrect seg-2)]
|
||||||
|
(gpr/overlaps-rects? r1 r2))))
|
||||||
|
|
||||||
|
(split
|
||||||
|
[seg-1 seg-2]
|
||||||
|
(if (not (overlap-segments? seg-1 seg-2))
|
||||||
|
[seg-1]
|
||||||
|
(let [[ts-seg-1 _] (split-ts seg-1 seg-2)]
|
||||||
|
(-> (split-command seg-1 ts-seg-1)
|
||||||
|
(add-previous (:prev seg-1))))))
|
||||||
|
|
||||||
(split-segment-on-content
|
(split-segment-on-content
|
||||||
[segment content]
|
[segment content content-sr]
|
||||||
|
|
||||||
(loop [current (first content)
|
(if (overlap-segment-selrect? segment content-sr)
|
||||||
content (rest content)
|
(->> content
|
||||||
result [segment]]
|
(filter #(overlap-segments? segment %))
|
||||||
|
(reduce
|
||||||
(if (nil? current)
|
(fn [result current]
|
||||||
result
|
(into [] (mapcat #(split % current)) result))
|
||||||
(let [result (->> result (into [] (mapcat #(split-cache % current))))]
|
[segment]))
|
||||||
(recur (first content)
|
[segment]))
|
||||||
(rest content)
|
|
||||||
result)))))
|
|
||||||
|
|
||||||
(split-content
|
(split-content
|
||||||
[content-a content-b]
|
[content-a content-b sr-b]
|
||||||
(into []
|
(into []
|
||||||
(mapcat #(split-segment-on-content % content-b))
|
(mapcat #(split-segment-on-content % content-b sr-b))
|
||||||
content-a))]
|
content-a))]
|
||||||
|
|
||||||
[(split-content content-a content-b)
|
[(split-content content-a content-b sr-b)
|
||||||
(split-content content-b content-a)])))
|
(split-content content-b content-a sr-a)])))
|
||||||
|
|
||||||
(defn is-segment?
|
(defn is-segment?
|
||||||
[cmd]
|
[cmd]
|
||||||
|
@ -147,7 +147,7 @@
|
||||||
(contains? #{:line-to :curve-to} (:command cmd))))
|
(contains? #{:line-to :curve-to} (:command cmd))))
|
||||||
|
|
||||||
(defn contains-segment?
|
(defn contains-segment?
|
||||||
[segment content]
|
[segment content content-sr content-geom]
|
||||||
|
|
||||||
(let [point (case (:command segment)
|
(let [point (case (:command segment)
|
||||||
:line-to (-> (gsp/command->line segment)
|
:line-to (-> (gsp/command->line segment)
|
||||||
|
@ -156,11 +156,13 @@
|
||||||
:curve-to (-> (gsp/command->bezier segment)
|
:curve-to (-> (gsp/command->bezier segment)
|
||||||
(gsp/curve-values 0.5)))]
|
(gsp/curve-values 0.5)))]
|
||||||
|
|
||||||
(or (gsp/is-point-in-content? point content)
|
(and (gpr/contains-point? content-sr point)
|
||||||
(gsp/is-point-in-border? point content))))
|
(or
|
||||||
|
(gsp/is-point-in-geom-data? point content-geom)
|
||||||
|
(gsp/is-point-in-border? point content)))))
|
||||||
|
|
||||||
(defn inside-segment?
|
(defn inside-segment?
|
||||||
[segment content]
|
[segment content-sr content-geom]
|
||||||
(let [point (case (:command segment)
|
(let [point (case (:command segment)
|
||||||
:line-to (-> (gsp/command->line segment)
|
:line-to (-> (gsp/command->line segment)
|
||||||
(gsp/line-values 0.5))
|
(gsp/line-values 0.5))
|
||||||
|
@ -168,7 +170,8 @@
|
||||||
:curve-to (-> (gsp/command->bezier segment)
|
:curve-to (-> (gsp/command->bezier segment)
|
||||||
(gsp/curve-values 0.5)))]
|
(gsp/curve-values 0.5)))]
|
||||||
|
|
||||||
(gsp/is-point-in-content? point content)))
|
(and (gpr/contains-point? content-sr point)
|
||||||
|
(gsp/is-point-in-geom-data? point content-geom))))
|
||||||
|
|
||||||
(defn overlap-segment?
|
(defn overlap-segment?
|
||||||
"Finds if the current segment is overlapping against other
|
"Finds if the current segment is overlapping against other
|
||||||
|
@ -209,49 +212,59 @@
|
||||||
(d/seek overlap-single?)
|
(d/seek overlap-single?)
|
||||||
(some?))))
|
(some?))))
|
||||||
|
|
||||||
(defn create-union [content-a content-a-split content-b content-b-split]
|
(defn create-union [content-a content-a-split content-b content-b-split sr-a sr-b]
|
||||||
;; Pick all segments in content-a that are not inside content-b
|
;; Pick all segments in content-a that are not inside content-b
|
||||||
;; Pick all segments in content-b that are not inside content-a
|
;; Pick all segments in content-b that are not inside content-a
|
||||||
(let [content
|
(let [content-a-geom (gsp/content->geom-data content-a)
|
||||||
|
content-b-geom (gsp/content->geom-data content-b)
|
||||||
|
|
||||||
|
content
|
||||||
(concat
|
(concat
|
||||||
(->> content-a-split (filter #(not (contains-segment? % content-b))))
|
(->> content-a-split (filter #(not (contains-segment? % content-b sr-b content-b-geom))))
|
||||||
(->> content-b-split (filter #(not (contains-segment? % content-a)))))
|
(->> content-b-split (filter #(not (contains-segment? % content-a sr-a content-a-geom)))))
|
||||||
|
|
||||||
|
content-geom (gsp/content->geom-data content)
|
||||||
|
|
||||||
|
content-sr (gsp/content->selrect content)
|
||||||
|
|
||||||
;; Overlapping segments should be added when they are part of the border
|
;; Overlapping segments should be added when they are part of the border
|
||||||
border-content
|
border-content
|
||||||
(->> content-b-split
|
(->> content-b-split
|
||||||
(filter #(and (contains-segment? % content-a)
|
(filter #(and (contains-segment? % content-a sr-a content-a-geom)
|
||||||
(overlap-segment? % content-a-split)
|
(overlap-segment? % content-a-split)
|
||||||
(not (inside-segment? % content)))))]
|
(not (inside-segment? % content-sr content-geom)))))]
|
||||||
|
|
||||||
;; Ensure that the output is always a vector
|
;; Ensure that the output is always a vector
|
||||||
(d/concat-vec content border-content)))
|
(d/concat-vec content border-content)))
|
||||||
|
|
||||||
(defn create-difference [content-a content-a-split content-b content-b-split]
|
(defn create-difference [content-a content-a-split content-b content-b-split sr-a sr-b]
|
||||||
;; Pick all segments in content-a that are not inside content-b
|
;; Pick all segments in content-a that are not inside content-b
|
||||||
;; Pick all segments in content b that are inside content-a
|
;; Pick all segments in content b that are inside content-a
|
||||||
;; removing overlapping
|
;; removing overlapping
|
||||||
(d/concat-vec
|
(let [content-a-geom (gsp/content->geom-data content-a)
|
||||||
(->> content-a-split (filter #(not (contains-segment? % content-b))))
|
content-b-geom (gsp/content->geom-data content-b)]
|
||||||
|
(d/concat-vec
|
||||||
|
(->> content-a-split (filter #(not (contains-segment? % content-b sr-b content-b-geom))))
|
||||||
|
|
||||||
;; Reverse second content so we can have holes inside other shapes
|
;; Reverse second content so we can have holes inside other shapes
|
||||||
(->> content-b-split
|
(->> content-b-split
|
||||||
(filter #(and (contains-segment? % content-a)
|
(filter #(and (contains-segment? % content-a sr-a content-a-geom)
|
||||||
(not (overlap-segment? % content-a-split)))))))
|
(not (overlap-segment? % content-a-split))))))))
|
||||||
|
|
||||||
(defn create-intersection [content-a content-a-split content-b content-b-split]
|
(defn create-intersection [content-a content-a-split content-b content-b-split sr-a sr-b]
|
||||||
;; Pick all segments in content-a that are inside content-b
|
;; Pick all segments in content-a that are inside content-b
|
||||||
;; Pick all segments in content-b that are inside content-a
|
;; Pick all segments in content-b that are inside content-a
|
||||||
(d/concat-vec
|
(let [content-a-geom (gsp/content->geom-data content-a)
|
||||||
(->> content-a-split (filter #(contains-segment? % content-b)))
|
content-b-geom (gsp/content->geom-data content-b)]
|
||||||
(->> content-b-split (filter #(contains-segment? % content-a)))))
|
(d/concat-vec
|
||||||
|
(->> content-a-split (filter #(contains-segment? % content-b sr-b content-b-geom)))
|
||||||
|
(->> content-b-split (filter #(contains-segment? % content-a sr-a content-a-geom))))))
|
||||||
|
|
||||||
|
|
||||||
(defn create-exclusion [content-a content-b]
|
(defn create-exclusion [content-a content-b]
|
||||||
;; Pick all segments
|
;; Pick all segments
|
||||||
(d/concat-vec content-a content-b))
|
(d/concat-vec content-a content-b))
|
||||||
|
|
||||||
|
|
||||||
(defn fix-move-to
|
(defn fix-move-to
|
||||||
[content]
|
[content]
|
||||||
;; Remove the field `:prev` and makes the necessaries `move-to`
|
;; Remove the field `:prev` and makes the necessaries `move-to`
|
||||||
|
@ -284,16 +297,19 @@
|
||||||
(ups/reverse-content))
|
(ups/reverse-content))
|
||||||
(add-previous))
|
(add-previous))
|
||||||
|
|
||||||
|
sr-a (gsp/content->selrect content-a)
|
||||||
|
sr-b (gsp/content->selrect content-b)
|
||||||
|
|
||||||
;; Split content in new segments in the intersection with the other path
|
;; Split content in new segments in the intersection with the other path
|
||||||
[content-a-split content-b-split] (content-intersect-split content-a content-b)
|
[content-a-split content-b-split] (content-intersect-split content-a content-b sr-a sr-b)
|
||||||
content-a-split (->> content-a-split add-previous (filter is-segment?))
|
content-a-split (->> content-a-split add-previous (filter is-segment?))
|
||||||
content-b-split (->> content-b-split add-previous (filter is-segment?))
|
content-b-split (->> content-b-split add-previous (filter is-segment?))
|
||||||
|
|
||||||
bool-content
|
bool-content
|
||||||
(case bool-type
|
(case bool-type
|
||||||
:union (create-union content-a content-a-split content-b content-b-split)
|
:union (create-union content-a content-a-split content-b content-b-split sr-a sr-b)
|
||||||
:difference (create-difference content-a content-a-split content-b content-b-split)
|
:difference (create-difference content-a content-a-split content-b content-b-split sr-a sr-b)
|
||||||
:intersection (create-intersection content-a content-a-split content-b content-b-split)
|
:intersection (create-intersection content-a content-a-split content-b content-b-split sr-a sr-b)
|
||||||
:exclude (create-exclusion content-a-split content-b-split))]
|
:exclude (create-exclusion content-a-split content-b-split))]
|
||||||
|
|
||||||
(->> (fix-move-to bool-content)
|
(->> (fix-move-to bool-content)
|
||||||
|
|
|
@ -21,14 +21,15 @@
|
||||||
[key]
|
[key]
|
||||||
(- (timestamp) (get @measures key)))
|
(- (timestamp) (get @measures key)))
|
||||||
|
|
||||||
#?(:cljs
|
(defn benchmark
|
||||||
(defn benchmark
|
"A helper function for perform a unitari benchmark on JS/CLJS. It
|
||||||
"A helper function for perform a unitari benchmark on JS/CLJS. It
|
|
||||||
uses browser native api so it only suitable to be executed in
|
uses browser native api so it only suitable to be executed in
|
||||||
browser."
|
browser."
|
||||||
[& {:keys [f iterations name]
|
[& _options]
|
||||||
:or {iterations 10000}}]
|
#?(:cljs
|
||||||
(let [end-mark (str name ":end")]
|
(let [{:keys [f iterations name]
|
||||||
|
:or {iterations 10000}} _options
|
||||||
|
end-mark (str name ":end")]
|
||||||
(println "=> benchmarking:" name)
|
(println "=> benchmarking:" name)
|
||||||
(println "--> warming up:" iterations)
|
(println "--> warming up:" iterations)
|
||||||
(loop [i iterations]
|
(loop [i iterations]
|
||||||
|
@ -51,3 +52,4 @@
|
||||||
#js {:duration duration
|
#js {:duration duration
|
||||||
:avg avg}))))
|
:avg avg}))))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -181,9 +181,11 @@
|
||||||
shape-before (-> (create-test-shape type {:modifiers modifiers})
|
shape-before (-> (create-test-shape type {:modifiers modifiers})
|
||||||
(assoc :selrect selrect))
|
(assoc :selrect selrect))
|
||||||
shape-after (gsh/transform-shape shape-before {:round-coords? false})]
|
shape-after (gsh/transform-shape shape-before {:round-coords? false})]
|
||||||
(= (:selrect shape-before) (:selrect shape-after)))
|
|
||||||
|
|
||||||
:rect {:x 0 :y 0 :width ##Inf :height ##Inf}
|
(= (:selrect shape-before)
|
||||||
:path {:x 0 :y 0 :width ##Inf :height ##Inf}
|
(:selrect shape-after)))
|
||||||
|
|
||||||
|
:rect {:x 0 :y 0 :x1 0 :y1 0 :x2 ##Inf :y2 ##Inf :width ##Inf :height ##Inf}
|
||||||
|
:path {:x 0 :y 0 :x1 0 :y1 0 :x2 ##Inf :y2 ##Inf :width ##Inf :height ##Inf}
|
||||||
:rect nil
|
:rect nil
|
||||||
:path nil)))
|
:path nil)))
|
||||||
|
|
|
@ -311,6 +311,9 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
background-color: $color-canvas;
|
background-color: $color-canvas;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
.img-th {
|
.img-th {
|
||||||
height: auto;
|
height: auto;
|
||||||
|
@ -321,6 +324,10 @@
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
svg#loader-pencil {
|
||||||
|
fill: $color-gray-20;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
[app.main.ui.confirm]
|
[app.main.ui.confirm]
|
||||||
[app.main.ui.modal :refer [modal]]
|
[app.main.ui.modal :refer [modal]]
|
||||||
[app.main.ui.routes :as rt]
|
[app.main.ui.routes :as rt]
|
||||||
[app.main.worker]
|
[app.main.worker :as worker]
|
||||||
[app.util.dom :as dom]
|
[app.util.dom :as dom]
|
||||||
[app.util.i18n :as i18n]
|
[app.util.i18n :as i18n]
|
||||||
[app.util.theme :as theme]
|
[app.util.theme :as theme]
|
||||||
|
@ -60,6 +60,7 @@
|
||||||
|
|
||||||
(defn ^:export init
|
(defn ^:export init
|
||||||
[]
|
[]
|
||||||
|
(worker/init!)
|
||||||
(sentry/init!)
|
(sentry/init!)
|
||||||
(i18n/init! cf/translations)
|
(i18n/init! cf/translations)
|
||||||
(theme/init! cf/themes)
|
(theme/init! cf/themes)
|
||||||
|
|
|
@ -252,7 +252,6 @@
|
||||||
(ptk/reify ::select-next-frame
|
(ptk/reify ::select-next-frame
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state _]
|
(watch [_ state _]
|
||||||
(prn "select-next-frame")
|
|
||||||
(let [route (:route state)
|
(let [route (:route state)
|
||||||
pparams (:path-params route)
|
pparams (:path-params route)
|
||||||
qparams (:query-params route)
|
qparams (:query-params route)
|
||||||
|
|
|
@ -24,10 +24,11 @@
|
||||||
[app.config :as cfg]
|
[app.config :as cfg]
|
||||||
[app.main.data.events :as ev]
|
[app.main.data.events :as ev]
|
||||||
[app.main.data.messages :as dm]
|
[app.main.data.messages :as dm]
|
||||||
[app.main.data.workspace.booleans :as dwb]
|
[app.main.data.workspace.bool :as dwb]
|
||||||
[app.main.data.workspace.changes :as dch]
|
[app.main.data.workspace.changes :as dch]
|
||||||
[app.main.data.workspace.common :as dwc]
|
[app.main.data.workspace.common :as dwc]
|
||||||
[app.main.data.workspace.drawing :as dwd]
|
[app.main.data.workspace.drawing :as dwd]
|
||||||
|
[app.main.data.workspace.fix-bool-contents :as fbc]
|
||||||
[app.main.data.workspace.groups :as dwg]
|
[app.main.data.workspace.groups :as dwg]
|
||||||
[app.main.data.workspace.interactions :as dwi]
|
[app.main.data.workspace.interactions :as dwi]
|
||||||
[app.main.data.workspace.libraries :as dwl]
|
[app.main.data.workspace.libraries :as dwl]
|
||||||
|
@ -213,8 +214,11 @@
|
||||||
(or (not ignore-until)
|
(or (not ignore-until)
|
||||||
(> (:modified-at %) ignore-until)))
|
(> (:modified-at %) ignore-until)))
|
||||||
libraries)]
|
libraries)]
|
||||||
(when needs-update?
|
(rx/merge
|
||||||
(rx/of (dwl/notify-sync-file file-id)))))))
|
(rx/of (fbc/fix-bool-contents))
|
||||||
|
(if needs-update?
|
||||||
|
(rx/of (dwl/notify-sync-file file-id))
|
||||||
|
(rx/empty)))))))
|
||||||
|
|
||||||
(defn finalize-file
|
(defn finalize-file
|
||||||
[_project-id file-id]
|
[_project-id file-id]
|
||||||
|
@ -307,7 +311,7 @@
|
||||||
[page-id]
|
[page-id]
|
||||||
(ptk/reify ::duplicate-page
|
(ptk/reify ::duplicate-page
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [this state _]
|
(watch [it state _]
|
||||||
(let [id (uuid/next)
|
(let [id (uuid/next)
|
||||||
pages (get-in state [:workspace-data :pages-index])
|
pages (get-in state [:workspace-data :pages-index])
|
||||||
unames (dwc/retrieve-used-names pages)
|
unames (dwc/retrieve-used-names pages)
|
||||||
|
@ -322,7 +326,7 @@
|
||||||
:id id}]
|
:id id}]
|
||||||
(rx/of (dch/commit-changes {:redo-changes [rchange]
|
(rx/of (dch/commit-changes {:redo-changes [rchange]
|
||||||
:undo-changes [uchange]
|
:undo-changes [uchange]
|
||||||
:origin this}))))))
|
:origin it}))))))
|
||||||
|
|
||||||
(s/def ::rename-page
|
(s/def ::rename-page
|
||||||
(s/keys :req-un [::id ::name]))
|
(s/keys :req-un [::id ::name]))
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
;;
|
;;
|
||||||
;; Copyright (c) UXBOX Labs SL
|
;; Copyright (c) UXBOX Labs SL
|
||||||
|
|
||||||
(ns app.main.data.workspace.booleans
|
(ns app.main.data.workspace.bool
|
||||||
(:require
|
(:require
|
||||||
[app.common.colors :as clr]
|
[app.common.colors :as clr]
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
|
@ -38,17 +38,20 @@
|
||||||
(and (contains? head :svg-attrs) (nil? (:fill-color head)))
|
(and (contains? head :svg-attrs) (nil? (:fill-color head)))
|
||||||
(assoc :fill-color clr/black))
|
(assoc :fill-color clr/black))
|
||||||
|
|
||||||
head-data (select-keys head stp/style-properties)]
|
head-data (select-keys head stp/style-properties)
|
||||||
[(-> {:id (uuid/next)
|
|
||||||
:type :bool
|
bool-shape
|
||||||
:bool-type bool-type
|
(-> {:id (uuid/next)
|
||||||
:frame-id (:frame-id head)
|
:type :bool
|
||||||
:parent-id (:parent-id head)
|
:bool-type bool-type
|
||||||
:name name
|
:frame-id (:frame-id head)
|
||||||
:shapes []}
|
:parent-id (:parent-id head)
|
||||||
(merge head-data)
|
:name name
|
||||||
(gsh/update-bool-selrect shapes objects))
|
:shapes (->> shapes (mapv :id))}
|
||||||
(cp/position-on-parent (:id head) objects)]))
|
(merge head-data)
|
||||||
|
(gsh/update-bool-selrect shapes objects))]
|
||||||
|
|
||||||
|
[bool-shape (cp/position-on-parent (:id head) objects)]))
|
||||||
|
|
||||||
(defn group->bool
|
(defn group->bool
|
||||||
[group bool-type objects]
|
[group bool-type objects]
|
94
frontend/src/app/main/data/workspace/fix_bool_contents.cljs
Normal file
94
frontend/src/app/main/data/workspace/fix_bool_contents.cljs
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
;;
|
||||||
|
;; Copyright (c) UXBOX Labs SL
|
||||||
|
|
||||||
|
(ns app.main.data.workspace.fix-bool-contents
|
||||||
|
(:require
|
||||||
|
[app.common.data :as d]
|
||||||
|
[app.common.geom.shapes :as gsh]
|
||||||
|
[app.main.data.workspace.changes :as dch]
|
||||||
|
[app.main.data.workspace.state-helpers :as wsh]
|
||||||
|
[beicon.core :as rx]
|
||||||
|
[potok.core :as ptk]))
|
||||||
|
|
||||||
|
;; This event will update the file so the boolean data has a pre-generated path data
|
||||||
|
;; to increase performance.
|
||||||
|
;; For new shapes this will be generated in the :reg-objects but we need to do this for
|
||||||
|
;; old files.
|
||||||
|
|
||||||
|
;; FIXME: Remove me after June 2022
|
||||||
|
|
||||||
|
(defn fix-bool-contents
|
||||||
|
"This event will calculate the bool content and update the page. This is kind of a 'addhoc' migration
|
||||||
|
to fill the optional value 'bool-content'"
|
||||||
|
[]
|
||||||
|
|
||||||
|
(letfn [(should-migrate-shape? [shape]
|
||||||
|
(and (= :bool (:type shape)) (not (contains? shape :bool-content))))
|
||||||
|
|
||||||
|
(should-migrate-component? [component]
|
||||||
|
(->> (:objects component)
|
||||||
|
(vals)
|
||||||
|
(d/seek should-migrate-shape?)))
|
||||||
|
|
||||||
|
(update-shape [shape objects]
|
||||||
|
(cond-> shape
|
||||||
|
(should-migrate-shape? shape)
|
||||||
|
(assoc :bool-content (gsh/calc-bool-content shape objects))))
|
||||||
|
|
||||||
|
(migrate-component [component]
|
||||||
|
(-> component
|
||||||
|
(update
|
||||||
|
:objects
|
||||||
|
(fn [objects]
|
||||||
|
(d/mapm #(update-shape %2 objects) objects)))))
|
||||||
|
|
||||||
|
(update-library
|
||||||
|
[library]
|
||||||
|
(-> library
|
||||||
|
(d/update-in-when
|
||||||
|
[:data :components]
|
||||||
|
(fn [components]
|
||||||
|
(d/mapm #(migrate-component %2) components)))))]
|
||||||
|
|
||||||
|
(ptk/reify ::fix-bool-contents
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
;; Update (only-local) the imported libraries
|
||||||
|
(-> state
|
||||||
|
(d/update-when
|
||||||
|
:workspace-libraries
|
||||||
|
(fn [libraries] (d/mapm #(update-library %2) libraries)))))
|
||||||
|
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [it state _]
|
||||||
|
(let [objects (wsh/lookup-page-objects state)
|
||||||
|
|
||||||
|
ids (into #{}
|
||||||
|
(comp (filter should-migrate-shape?) (map :id))
|
||||||
|
(vals objects))
|
||||||
|
|
||||||
|
components (->> (wsh/lookup-local-components state)
|
||||||
|
(vals)
|
||||||
|
(filter should-migrate-component?))
|
||||||
|
|
||||||
|
component-changes
|
||||||
|
(into []
|
||||||
|
(map (fn [component]
|
||||||
|
{:type :mod-component
|
||||||
|
:id (:id component)
|
||||||
|
:objects (-> component migrate-component :objects)}))
|
||||||
|
components)]
|
||||||
|
|
||||||
|
(rx/of (dch/update-shapes ids #(update-shape % objects) {:reg-objects? false
|
||||||
|
:save-undo? false
|
||||||
|
:ignore-tree true}))
|
||||||
|
|
||||||
|
(if (empty? component-changes)
|
||||||
|
(rx/empty)
|
||||||
|
(rx/of (dch/commit-changes {:origin it
|
||||||
|
:redo-changes component-changes
|
||||||
|
:undo-changes []
|
||||||
|
:save-undo? false}))))))))
|
|
@ -269,19 +269,19 @@
|
||||||
:type "keyup"
|
:type "keyup"
|
||||||
:fn #(st/emit! (dw/toggle-distances-display false))}
|
:fn #(st/emit! (dw/toggle-distances-display false))}
|
||||||
|
|
||||||
:boolean-union {:tooltip (ds/meta (ds/alt "U"))
|
:bool-union {:tooltip (ds/meta (ds/alt "U"))
|
||||||
:command (ds/c-mod "alt+u")
|
:command (ds/c-mod "alt+u")
|
||||||
:fn #(st/emit! (dw/create-bool :union))}
|
:fn #(st/emit! (dw/create-bool :union))}
|
||||||
|
|
||||||
:boolean-difference {:tooltip (ds/meta (ds/alt "D"))
|
:bool-difference {:tooltip (ds/meta (ds/alt "D"))
|
||||||
:command (ds/c-mod "alt+d")
|
:command (ds/c-mod "alt+d")
|
||||||
:fn #(st/emit! (dw/create-bool :difference))}
|
:fn #(st/emit! (dw/create-bool :difference))}
|
||||||
|
|
||||||
:boolean-intersection {:tooltip (ds/meta (ds/alt "I"))
|
:bool-intersection {:tooltip (ds/meta (ds/alt "I"))
|
||||||
:command (ds/c-mod "alt+i")
|
:command (ds/c-mod "alt+i")
|
||||||
:fn #(st/emit! (dw/create-bool :intersection))}
|
:fn #(st/emit! (dw/create-bool :intersection))}
|
||||||
|
|
||||||
:boolean-exclude {:tooltip (ds/meta (ds/alt "E"))
|
:bool-exclude {:tooltip (ds/meta (ds/alt "E"))
|
||||||
:command (ds/c-mod "alt+e")
|
:command (ds/c-mod "alt+e")
|
||||||
:fn #(st/emit! (dw/create-bool :exclude))}
|
:fn #(st/emit! (dw/create-bool :exclude))}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,10 @@
|
||||||
([state component-id]
|
([state component-id]
|
||||||
(get-in state [:workspace-data :components component-id :objects])))
|
(get-in state [:workspace-data :components component-id :objects])))
|
||||||
|
|
||||||
|
(defn lookup-local-components
|
||||||
|
([state]
|
||||||
|
(get-in state [:workspace-data :components])))
|
||||||
|
|
||||||
(defn lookup-selected
|
(defn lookup-selected
|
||||||
([state]
|
([state]
|
||||||
(lookup-selected state nil))
|
(lookup-selected state nil))
|
||||||
|
|
|
@ -106,9 +106,9 @@
|
||||||
;; apply-modifiers event is done, that consolidates all modifiers into the base
|
;; apply-modifiers event is done, that consolidates all modifiers into the base
|
||||||
;; geometric attributes of the shapes.
|
;; geometric attributes of the shapes.
|
||||||
|
|
||||||
(declare set-modifiers-recursive)
|
|
||||||
(declare set-local-displacement)
|
|
||||||
(declare clear-local-transform)
|
(declare clear-local-transform)
|
||||||
|
(declare set-modifiers-recursive)
|
||||||
|
(declare get-ignore-tree)
|
||||||
|
|
||||||
(defn- set-modifiers
|
(defn- set-modifiers
|
||||||
([ids] (set-modifiers ids nil false))
|
([ids] (set-modifiers ids nil false))
|
||||||
|
@ -119,21 +119,17 @@
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
(let [modifiers (or modifiers (get-in state [:workspace-local :modifiers] {}))
|
(let [modifiers (or modifiers (get-in state [:workspace-local :modifiers] {}))
|
||||||
page-id (:current-page-id state)
|
page-id (:current-page-id state)
|
||||||
objects (wsh/lookup-page-objects state page-id)
|
objects (wsh/lookup-page-objects state page-id)
|
||||||
ids (into #{} (remove #(get-in objects [% :blocked] false)) ids)]
|
ids (into #{} (remove #(get-in objects [% :blocked] false)) ids)
|
||||||
|
|
||||||
(reduce (fn [state id]
|
setup-modifiers
|
||||||
(update state :workspace-modifiers
|
(fn [state id]
|
||||||
#(set-modifiers-recursive %
|
(let [shape (get objects id)]
|
||||||
objects
|
(update state :workspace-modifiers
|
||||||
(get objects id)
|
#(set-modifiers-recursive % objects shape modifiers ignore-constraints))))]
|
||||||
modifiers
|
|
||||||
nil
|
(reduce setup-modifiers state ids))))))
|
||||||
nil
|
|
||||||
ignore-constraints)))
|
|
||||||
state
|
|
||||||
ids))))))
|
|
||||||
|
|
||||||
;; Rotation use different algorithm to calculate children modifiers (and do not use child constraints).
|
;; Rotation use different algorithm to calculate children modifiers (and do not use child constraints).
|
||||||
(defn- set-rotation-modifiers
|
(defn- set-rotation-modifiers
|
||||||
|
@ -169,15 +165,14 @@
|
||||||
children-ids (->> ids (mapcat #(cp/get-children % objects)))
|
children-ids (->> ids (mapcat #(cp/get-children % objects)))
|
||||||
ids-with-children (d/concat-vec children-ids ids)
|
ids-with-children (d/concat-vec children-ids ids)
|
||||||
object-modifiers (get state :workspace-modifiers)
|
object-modifiers (get state :workspace-modifiers)
|
||||||
ignore-tree (d/mapm #(get-in %2 [:modifiers :ignore-geometry?]) object-modifiers)]
|
ignore-tree (get-ignore-tree object-modifiers objects ids)]
|
||||||
|
|
||||||
(rx/of (dwu/start-undo-transaction)
|
(rx/of (dwu/start-undo-transaction)
|
||||||
(dch/update-shapes
|
(dch/update-shapes
|
||||||
ids-with-children
|
ids-with-children
|
||||||
(fn [shape]
|
(fn [shape]
|
||||||
(-> shape
|
(let [modif (get object-modifiers (:id shape))]
|
||||||
(merge (get object-modifiers (:id shape)))
|
(gsh/transform-shape (merge shape modif))))
|
||||||
(gsh/transform-shape)))
|
|
||||||
{:reg-objects? true
|
{:reg-objects? true
|
||||||
:ignore-tree ignore-tree
|
:ignore-tree ignore-tree
|
||||||
;; Attributes that can change in the transform. This way we don't have to check
|
;; Attributes that can change in the transform. This way we don't have to check
|
||||||
|
@ -198,62 +193,51 @@
|
||||||
"If the shape is a component instance, check its relative position respect the
|
"If the shape is a component instance, check its relative position respect the
|
||||||
root of the component, and see if it changes after applying a transformation."
|
root of the component, and see if it changes after applying a transformation."
|
||||||
[shape root transformed-shape transformed-root objects]
|
[shape root transformed-shape transformed-root objects]
|
||||||
(let [root (cond
|
(let [root
|
||||||
(:component-root? shape)
|
(cond
|
||||||
shape
|
(:component-root? shape)
|
||||||
|
shape
|
||||||
|
|
||||||
(nil? root)
|
(nil? root)
|
||||||
(cp/get-root-shape shape objects)
|
(cp/get-root-shape shape objects)
|
||||||
|
|
||||||
:else root)
|
:else root)
|
||||||
|
|
||||||
transformed-root (cond
|
transformed-root
|
||||||
(:component-root? transformed-shape)
|
(cond
|
||||||
transformed-shape
|
(:component-root? transformed-shape)
|
||||||
|
transformed-shape
|
||||||
|
|
||||||
(nil? transformed-root)
|
(nil? transformed-root)
|
||||||
(cp/get-root-shape transformed-shape objects)
|
(cp/get-root-shape transformed-shape objects)
|
||||||
|
|
||||||
:else transformed-root)
|
:else transformed-root)
|
||||||
|
|
||||||
shape-delta (when root
|
shape-delta
|
||||||
(gpt/point (- (:x shape) (:x root))
|
(when root
|
||||||
(- (:y shape) (:y root))))
|
(gpt/point (- (:x shape) (:x root))
|
||||||
|
(- (:y shape) (:y root))))
|
||||||
|
|
||||||
transformed-shape-delta (when transformed-root
|
transformed-shape-delta
|
||||||
(gpt/point (- (:x transformed-shape) (:x transformed-root))
|
(when transformed-root
|
||||||
(- (:y transformed-shape) (:y transformed-root))))
|
(gpt/point (- (:x transformed-shape) (:x transformed-root))
|
||||||
|
(- (:y transformed-shape) (:y transformed-root))))
|
||||||
|
|
||||||
ignore-geometry? (= shape-delta transformed-shape-delta)]
|
ignore-geometry? (= shape-delta transformed-shape-delta)]
|
||||||
|
|
||||||
[root transformed-root ignore-geometry?]))
|
[root transformed-root ignore-geometry?]))
|
||||||
|
|
||||||
(defn- set-modifiers-recursive
|
(defn- set-modifiers-recursive
|
||||||
[modif-tree objects shape modifiers root transformed-root ignore-constraints]
|
[modif-tree objects shape modifiers ignore-constraints]
|
||||||
(let [children (map (d/getf objects) (:shapes shape))
|
(let [children (map (d/getf objects) (:shapes shape))
|
||||||
|
transformed-rect (gsh/transform-selrect (:selrect shape) modifiers)
|
||||||
transformed-shape (gsh/transform-shape (assoc shape :modifiers modifiers))
|
|
||||||
|
|
||||||
[root transformed-root ignore-geometry?]
|
|
||||||
(check-delta shape root transformed-shape transformed-root objects)
|
|
||||||
|
|
||||||
modifiers (assoc modifiers :ignore-geometry? ignore-geometry?)
|
|
||||||
|
|
||||||
transformed-rect (gsh/calc-transformed-parent-rect shape modifiers)
|
|
||||||
|
|
||||||
set-child
|
set-child
|
||||||
(fn [modif-tree child]
|
(fn [modif-tree child]
|
||||||
(let [child-modifiers
|
(let [child-modifiers (gsh/calc-child-modifiers shape child modifiers ignore-constraints transformed-rect)]
|
||||||
(gsh/calc-child-modifiers shape child modifiers ignore-constraints transformed-rect)]
|
|
||||||
|
|
||||||
(cond-> modif-tree
|
(cond-> modif-tree
|
||||||
(d/not-empty? (d/without-keys child-modifiers [:ignore-geometry?]))
|
(not (gsh/empty-modifiers? child-modifiers))
|
||||||
(set-modifiers-recursive objects
|
(set-modifiers-recursive objects child child-modifiers ignore-constraints))))
|
||||||
child
|
|
||||||
child-modifiers
|
|
||||||
root
|
|
||||||
transformed-root
|
|
||||||
ignore-constraints))))
|
|
||||||
|
|
||||||
modif-tree
|
modif-tree
|
||||||
(-> modif-tree
|
(-> modif-tree
|
||||||
|
@ -261,13 +245,27 @@
|
||||||
|
|
||||||
(reduce set-child modif-tree children)))
|
(reduce set-child modif-tree children)))
|
||||||
|
|
||||||
(defn- set-local-displacement [point]
|
(defn- get-ignore-tree
|
||||||
(ptk/reify ::start-local-displacement
|
"Retrieves a map with the flag `ignore-tree` given a tree of modifiers"
|
||||||
ptk/UpdateEvent
|
([modif-tree objects shape]
|
||||||
(update [_ state]
|
(get-ignore-tree modif-tree objects shape nil nil {}))
|
||||||
(let [mtx (gmt/translate-matrix point)]
|
|
||||||
(-> state
|
([modif-tree objects shape root transformed-root ignore-tree]
|
||||||
(assoc-in [:workspace-local :modifiers] {:displacement mtx}))))))
|
(let [children (map (d/getf objects) (:shapes shape))
|
||||||
|
|
||||||
|
shape-id (:id shape)
|
||||||
|
transformed-shape (gsh/transform-shape (merge shape (get modif-tree shape-id)))
|
||||||
|
|
||||||
|
[root transformed-root ignore-geometry?]
|
||||||
|
(check-delta shape root transformed-shape transformed-root objects)
|
||||||
|
|
||||||
|
ignore-tree (assoc ignore-tree shape-id ignore-geometry?)
|
||||||
|
|
||||||
|
set-child
|
||||||
|
(fn [modif-tree child]
|
||||||
|
(get-ignore-tree modif-tree objects child root transformed-root ignore-tree))]
|
||||||
|
|
||||||
|
(reduce set-child ignore-tree children))))
|
||||||
|
|
||||||
(defn- clear-local-transform []
|
(defn- clear-local-transform []
|
||||||
(ptk/reify ::clear-local-transform
|
(ptk/reify ::clear-local-transform
|
||||||
|
@ -275,7 +273,7 @@
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
(-> state
|
(-> state
|
||||||
(dissoc :workspace-modifiers)
|
(dissoc :workspace-modifiers)
|
||||||
(update :workspace-local dissoc :modifiers :current-move-selected)))))
|
(update :workspace-local dissoc :current-move-selected)))))
|
||||||
|
|
||||||
|
|
||||||
;; -- Resize --------------------------------------------------------
|
;; -- Resize --------------------------------------------------------
|
||||||
|
@ -410,26 +408,13 @@
|
||||||
(let [shape (get objects id)
|
(let [shape (get objects id)
|
||||||
modifiers (gsh/resize-modifiers shape attr value)]
|
modifiers (gsh/resize-modifiers shape attr value)]
|
||||||
(update state :workspace-modifiers
|
(update state :workspace-modifiers
|
||||||
#(set-modifiers-recursive %
|
#(set-modifiers-recursive % objects shape modifiers false))))
|
||||||
objects
|
|
||||||
shape
|
|
||||||
modifiers
|
|
||||||
nil
|
|
||||||
nil
|
|
||||||
false))))
|
|
||||||
state
|
state
|
||||||
ids)))
|
ids)))
|
||||||
|
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state _]
|
(watch [_ _ _]
|
||||||
(let [page-id (:current-page-id state)
|
(rx/of (apply-modifiers ids)))))
|
||||||
objects (wsh/lookup-page-objects state page-id)
|
|
||||||
|
|
||||||
;; TODO: looks completly redundant operation because
|
|
||||||
;; apply-modifiers already finds all children.
|
|
||||||
ids (d/concat-vec ids (mapcat #(cp/get-children % objects) ids))]
|
|
||||||
(rx/of (apply-modifiers ids))))))
|
|
||||||
|
|
||||||
|
|
||||||
;; -- Rotate --------------------------------------------------------
|
;; -- Rotate --------------------------------------------------------
|
||||||
|
|
||||||
|
@ -579,11 +564,11 @@
|
||||||
(->> position
|
(->> position
|
||||||
(rx/with-latest vector snap-delta)
|
(rx/with-latest vector snap-delta)
|
||||||
(rx/map snap/correct-snap-point)
|
(rx/map snap/correct-snap-point)
|
||||||
(rx/map set-local-displacement)
|
(rx/map #(hash-map :displacement (gmt/translate-matrix %)))
|
||||||
|
(rx/map (partial set-modifiers ids))
|
||||||
(rx/take-until stopper))
|
(rx/take-until stopper))
|
||||||
|
|
||||||
(rx/of (set-modifiers ids)
|
(rx/of (apply-modifiers ids)
|
||||||
(apply-modifiers ids)
|
|
||||||
(calculate-frame-for-move ids)
|
(calculate-frame-for-move ids)
|
||||||
(finish-transform)))))))))
|
(finish-transform)))))))))
|
||||||
|
|
||||||
|
@ -626,11 +611,11 @@
|
||||||
(->> move-events
|
(->> move-events
|
||||||
(rx/take-until stopper)
|
(rx/take-until stopper)
|
||||||
(rx/scan #(gpt/add %1 mov-vec) (gpt/point 0 0))
|
(rx/scan #(gpt/add %1 mov-vec) (gpt/point 0 0))
|
||||||
(rx/map set-local-displacement))
|
(rx/map #(hash-map :displacement (gmt/translate-matrix %)))
|
||||||
|
(rx/map (partial set-modifiers selected)))
|
||||||
(rx/of (move-selected direction shift?)))
|
(rx/of (move-selected direction shift?)))
|
||||||
|
|
||||||
(rx/of (set-modifiers selected)
|
(rx/of (apply-modifiers selected)
|
||||||
(apply-modifiers selected)
|
|
||||||
(finish-transform))))
|
(finish-transform))))
|
||||||
(rx/empty))))))
|
(rx/empty))))))
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,7 @@
|
||||||
[app.util.object :as obj]
|
[app.util.object :as obj]
|
||||||
[app.util.timers :as ts]
|
[app.util.timers :as ts]
|
||||||
[cuerdas.core :as str]
|
[cuerdas.core :as str]
|
||||||
|
[debug :refer [debug?]]
|
||||||
[rumext.alpha :as mf]))
|
[rumext.alpha :as mf]))
|
||||||
|
|
||||||
(def ^:private default-color clr/canvas)
|
(def ^:private default-color clr/canvas)
|
||||||
|
@ -76,10 +77,9 @@
|
||||||
(let [shape-wrapper (shape-wrapper-factory objects)
|
(let [shape-wrapper (shape-wrapper-factory objects)
|
||||||
group-shape (group/group-shape shape-wrapper)]
|
group-shape (group/group-shape shape-wrapper)]
|
||||||
(mf/fnc group-wrapper
|
(mf/fnc group-wrapper
|
||||||
[{:keys [shape frame] :as props}]
|
[{:keys [shape] :as props}]
|
||||||
(let [childs (mapv #(get objects %) (:shapes shape))]
|
(let [childs (mapv #(get objects %) (:shapes shape))]
|
||||||
[:& group-shape {:frame frame
|
[:& group-shape {:shape shape
|
||||||
:shape shape
|
|
||||||
:is-child-selected? true
|
:is-child-selected? true
|
||||||
:childs childs}]))))
|
:childs childs}]))))
|
||||||
|
|
||||||
|
@ -88,11 +88,10 @@
|
||||||
(let [shape-wrapper (shape-wrapper-factory objects)
|
(let [shape-wrapper (shape-wrapper-factory objects)
|
||||||
bool-shape (bool/bool-shape shape-wrapper)]
|
bool-shape (bool/bool-shape shape-wrapper)]
|
||||||
(mf/fnc bool-wrapper
|
(mf/fnc bool-wrapper
|
||||||
[{:keys [shape frame] :as props}]
|
[{:keys [shape] :as props}]
|
||||||
(let [childs (->> (cp/get-children (:id shape) objects)
|
(let [childs (->> (cp/get-children (:id shape) objects)
|
||||||
(select-keys objects))]
|
(select-keys objects))]
|
||||||
[:& bool-shape {:frame frame
|
[:& bool-shape {:shape shape
|
||||||
:shape shape
|
|
||||||
:childs childs}]))))
|
:childs childs}]))))
|
||||||
|
|
||||||
(defn svg-raw-wrapper-factory
|
(defn svg-raw-wrapper-factory
|
||||||
|
@ -100,18 +99,16 @@
|
||||||
(let [shape-wrapper (shape-wrapper-factory objects)
|
(let [shape-wrapper (shape-wrapper-factory objects)
|
||||||
svg-raw-shape (svg-raw/svg-raw-shape shape-wrapper)]
|
svg-raw-shape (svg-raw/svg-raw-shape shape-wrapper)]
|
||||||
(mf/fnc svg-raw-wrapper
|
(mf/fnc svg-raw-wrapper
|
||||||
[{:keys [shape frame] :as props}]
|
[{:keys [shape] :as props}]
|
||||||
(let [childs (mapv #(get objects %) (:shapes shape))]
|
(let [childs (mapv #(get objects %) (:shapes shape))]
|
||||||
(if (and (map? (:content shape))
|
(if (and (map? (:content shape))
|
||||||
(or (= :svg (get-in shape [:content :tag]))
|
(or (= :svg (get-in shape [:content :tag]))
|
||||||
(contains? shape :svg-attrs)))
|
(contains? shape :svg-attrs)))
|
||||||
[:> shape-container {:shape shape}
|
[:> shape-container {:shape shape}
|
||||||
[:& svg-raw-shape {:frame frame
|
[:& svg-raw-shape {:shape shape
|
||||||
:shape shape
|
|
||||||
:childs childs}]]
|
:childs childs}]]
|
||||||
|
|
||||||
[:& svg-raw-shape {:frame frame
|
[:& svg-raw-shape {:shape shape
|
||||||
:shape shape
|
|
||||||
:childs childs}])))))
|
:childs childs}])))))
|
||||||
|
|
||||||
(defn shape-wrapper-factory
|
(defn shape-wrapper-factory
|
||||||
|
@ -123,8 +120,7 @@
|
||||||
bool-wrapper (mf/use-memo (mf/deps objects) #(bool-wrapper-factory objects))
|
bool-wrapper (mf/use-memo (mf/deps objects) #(bool-wrapper-factory objects))
|
||||||
frame-wrapper (mf/use-memo (mf/deps objects) #(frame-wrapper-factory objects))]
|
frame-wrapper (mf/use-memo (mf/deps objects) #(frame-wrapper-factory objects))]
|
||||||
(when (and shape (not (:hidden shape)))
|
(when (and shape (not (:hidden shape)))
|
||||||
(let [shape (-> (gsh/transform-shape shape)
|
(let [shape (gsh/transform-shape shape)
|
||||||
(gsh/translate-to-frame frame))
|
|
||||||
opts #js {:shape shape}
|
opts #js {:shape shape}
|
||||||
svg-raw? (= :svg-raw (:type shape))]
|
svg-raw? (= :svg-raw (:type shape))]
|
||||||
(if-not svg-raw?
|
(if-not svg-raw?
|
||||||
|
@ -198,7 +194,7 @@
|
||||||
:width (:width item)
|
:width (:width item)
|
||||||
:height (:height item)
|
:height (:height item)
|
||||||
;; DEBUG
|
;; DEBUG
|
||||||
;; :style {:filter "sepia(1)"}
|
:style {:filter (when (debug? :thumbnails) "sepia(1)")}
|
||||||
}]
|
}]
|
||||||
frame?
|
frame?
|
||||||
[:& frame-wrapper {:shape item
|
[:& frame-wrapper {:shape item
|
||||||
|
@ -241,8 +237,7 @@
|
||||||
[:& wrapper {:shape frame :view-box vbox}]]))
|
[:& wrapper {:shape frame :view-box vbox}]]))
|
||||||
|
|
||||||
(mf/defc component-svg
|
(mf/defc component-svg
|
||||||
{::mf/wrap [mf/memo
|
{::mf/wrap [mf/memo #(mf/deferred % ts/idle-then-raf)]}
|
||||||
#(mf/deferred % ts/idle-then-raf)]}
|
|
||||||
[{:keys [objects group zoom] :or {zoom 1} :as props}]
|
[{:keys [objects group zoom] :or {zoom 1} :as props}]
|
||||||
(let [modifier (-> (gpt/point (:x group) (:y group))
|
(let [modifier (-> (gpt/point (:x group) (:y group))
|
||||||
(gpt/negate)
|
(gpt/negate)
|
||||||
|
@ -253,17 +248,21 @@
|
||||||
include-metadata? (mf/use-ctx use/include-metadata-ctx)
|
include-metadata? (mf/use-ctx use/include-metadata-ctx)
|
||||||
|
|
||||||
modifier-ids (concat [group-id] (cp/get-children group-id objects))
|
modifier-ids (concat [group-id] (cp/get-children group-id objects))
|
||||||
|
|
||||||
update-fn #(assoc-in %1 [%2 :modifiers :displacement] modifier)
|
update-fn #(assoc-in %1 [%2 :modifiers :displacement] modifier)
|
||||||
objects (reduce update-fn objects modifier-ids)
|
modifiers (reduce update-fn {} modifier-ids)
|
||||||
group (assoc-in group [:modifiers :displacement] modifier)
|
objects (gsh/merge-modifiers objects modifiers)
|
||||||
|
|
||||||
|
group (get objects group-id)
|
||||||
|
|
||||||
width (* (:width group) zoom)
|
width (* (:width group) zoom)
|
||||||
height (* (:height group) zoom)
|
height (* (:height group) zoom)
|
||||||
vbox (str "0 0 " (:width group 0)
|
vbox (str "0 0 " (:width group 0)
|
||||||
" " (:height group 0))
|
" " (:height group 0))
|
||||||
wrapper (mf/use-memo
|
group-wrapper
|
||||||
(mf/deps objects)
|
(mf/use-memo
|
||||||
#(group-wrapper-factory objects))]
|
(mf/deps objects)
|
||||||
|
#(group-wrapper-factory objects))]
|
||||||
|
|
||||||
[:svg {:view-box vbox
|
[:svg {:view-box vbox
|
||||||
:width width
|
:width width
|
||||||
|
@ -273,7 +272,7 @@
|
||||||
:xmlnsXlink "http://www.w3.org/1999/xlink"
|
:xmlnsXlink "http://www.w3.org/1999/xlink"
|
||||||
:xmlns:penpot (when include-metadata? "https://penpot.app/xmlns")}
|
:xmlns:penpot (when include-metadata? "https://penpot.app/xmlns")}
|
||||||
[:> shape-container {:shape group}
|
[:> shape-container {:shape group}
|
||||||
[:& wrapper {:shape group :view-box vbox}]]]))
|
[:& group-wrapper {:shape group :view-box vbox}]]]))
|
||||||
|
|
||||||
(mf/defc component-symbol
|
(mf/defc component-symbol
|
||||||
[{:keys [id data] :as props}]
|
[{:keys [id data] :as props}]
|
||||||
|
|
|
@ -116,6 +116,14 @@
|
||||||
:show-distances?])
|
:show-distances?])
|
||||||
workspace-local =))
|
workspace-local =))
|
||||||
|
|
||||||
|
(def interactions-data
|
||||||
|
(l/derived #(select-keys % [:editing-interaction-index
|
||||||
|
:draw-interaction-to
|
||||||
|
:draw-interaction-to-frame
|
||||||
|
:move-overlay-to
|
||||||
|
:move-overlay-index])
|
||||||
|
workspace-local =))
|
||||||
|
|
||||||
(def local-displacement
|
(def local-displacement
|
||||||
(l/derived #(select-keys % [:modifiers :selected])
|
(l/derived #(select-keys % [:modifiers :selected])
|
||||||
workspace-local =))
|
workspace-local =))
|
||||||
|
@ -232,23 +240,12 @@
|
||||||
(l/derived #(get % id) workspace-page-objects))
|
(l/derived #(get % id) workspace-page-objects))
|
||||||
|
|
||||||
(defn objects-by-id
|
(defn objects-by-id
|
||||||
([ids]
|
[ids]
|
||||||
(objects-by-id ids nil))
|
(let [selector
|
||||||
|
(fn [state]
|
||||||
([ids {:keys [with-modifiers?]
|
(let [objects (wsh/lookup-page-objects state)]
|
||||||
:or { with-modifiers? false }}]
|
(into [] (keep (d/getf objects)) ids)))]
|
||||||
(let [selector
|
(l/derived selector st/state =)))
|
||||||
(fn [state]
|
|
||||||
(let [objects (wsh/lookup-page-objects state)
|
|
||||||
modifiers (:workspace-modifiers state)
|
|
||||||
;; FIXME: Improve performance
|
|
||||||
objects (cond-> objects
|
|
||||||
with-modifiers?
|
|
||||||
(gsh/merge-modifiers modifiers))
|
|
||||||
xform (comp (map (d/getf objects))
|
|
||||||
(remove nil?))]
|
|
||||||
(into [] xform ids)))]
|
|
||||||
(l/derived selector st/state =))))
|
|
||||||
|
|
||||||
(defn- set-content-modifiers [state]
|
(defn- set-content-modifiers [state]
|
||||||
(fn [id shape]
|
(fn [id shape]
|
||||||
|
@ -257,21 +254,11 @@
|
||||||
(update shape :content upc/apply-content-modifiers content-modifiers)
|
(update shape :content upc/apply-content-modifiers content-modifiers)
|
||||||
shape))))
|
shape))))
|
||||||
|
|
||||||
(defn select-children [id]
|
(defn select-bool-children [id]
|
||||||
(let [selector
|
(let [selector
|
||||||
(fn [state]
|
(fn [state]
|
||||||
(let [objects (wsh/lookup-page-objects state)
|
(let [objects (wsh/lookup-page-objects state)
|
||||||
|
modifiers (:workspace-modifiers state)]
|
||||||
modifiers (-> (:workspace-modifiers state))
|
|
||||||
{selected :selected disp-modifiers :modifiers}
|
|
||||||
(-> (:workspace-local state)
|
|
||||||
(select-keys [:modifiers :selected]))
|
|
||||||
|
|
||||||
modifiers
|
|
||||||
(d/deep-merge
|
|
||||||
modifiers
|
|
||||||
(into {} (map #(vector % {:modifiers disp-modifiers})) selected))]
|
|
||||||
|
|
||||||
(as-> (cp/select-children id objects) $
|
(as-> (cp/select-children id objects) $
|
||||||
(gsh/merge-modifiers $ modifiers)
|
(gsh/merge-modifiers $ modifiers)
|
||||||
(d/mapm (set-content-modifiers state) $))))]
|
(d/mapm (set-content-modifiers state) $))))]
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
[app.util.dom.dnd :as dnd]
|
[app.util.dom.dnd :as dnd]
|
||||||
[app.util.i18n :as i18n :refer [tr]]
|
[app.util.i18n :as i18n :refer [tr]]
|
||||||
[app.util.keyboard :as kbd]
|
[app.util.keyboard :as kbd]
|
||||||
|
[app.util.storage :as stg]
|
||||||
[app.util.time :as dt]
|
[app.util.time :as dt]
|
||||||
[app.util.timers :as ts]
|
[app.util.timers :as ts]
|
||||||
[app.util.webapi :as wapi]
|
[app.util.webapi :as wapi]
|
||||||
|
@ -30,22 +31,55 @@
|
||||||
|
|
||||||
;; --- Grid Item Thumbnail
|
;; --- Grid Item Thumbnail
|
||||||
|
|
||||||
|
(defn use-thumbnail-cache
|
||||||
|
"Creates some hooks to handle the files thumbnails cache"
|
||||||
|
[file]
|
||||||
|
|
||||||
|
(let [get-thumbnail
|
||||||
|
(mf/use-callback
|
||||||
|
(mf/deps file)
|
||||||
|
(fn []
|
||||||
|
(let [[revn thumb-data] (get-in @stg/storage [:thumbnails (:id file)])]
|
||||||
|
(when (= revn (:revn file))
|
||||||
|
thumb-data))))
|
||||||
|
|
||||||
|
cache-thumbnail
|
||||||
|
(mf/use-callback
|
||||||
|
(mf/deps file)
|
||||||
|
(fn [thumb-data]
|
||||||
|
(swap! stg/storage #(assoc-in % [:thumbnails (:id file)] [(:revn file) thumb-data]))))
|
||||||
|
|
||||||
|
generate-thumbnail
|
||||||
|
(mf/use-callback
|
||||||
|
(mf/deps file)
|
||||||
|
(fn []
|
||||||
|
(let [thumb-data (get-thumbnail)]
|
||||||
|
(if (some? thumb-data)
|
||||||
|
(rx/of thumb-data)
|
||||||
|
(->> (wrk/ask! {:cmd :thumbnails/generate
|
||||||
|
:file-id (:id file)
|
||||||
|
:page-id (get-in file [:data :pages 0])})
|
||||||
|
(rx/tap cache-thumbnail))))))]
|
||||||
|
|
||||||
|
generate-thumbnail))
|
||||||
|
|
||||||
(mf/defc grid-item-thumbnail
|
(mf/defc grid-item-thumbnail
|
||||||
{::mf/wrap [mf/memo]}
|
{::mf/wrap [mf/memo]}
|
||||||
[{:keys [file] :as props}]
|
[{:keys [file] :as props}]
|
||||||
(let [container (mf/use-ref)]
|
(let [container (mf/use-ref)
|
||||||
|
generate-thumbnail (use-thumbnail-cache file)]
|
||||||
|
|
||||||
(mf/use-effect
|
(mf/use-effect
|
||||||
(mf/deps (:id file))
|
(mf/deps file)
|
||||||
(fn []
|
(fn []
|
||||||
(->> (wrk/ask! {:cmd :thumbnails/generate
|
(->> (generate-thumbnail)
|
||||||
:file-id (:id file)
|
|
||||||
:page-id (get-in file [:data :pages 0])})
|
|
||||||
(rx/subs (fn [{:keys [svg fonts]}]
|
(rx/subs (fn [{:keys [svg fonts]}]
|
||||||
(run! fonts/ensure-loaded! fonts)
|
(run! fonts/ensure-loaded! fonts)
|
||||||
(when-let [node (mf/ref-val container)]
|
(when-let [node (mf/ref-val container)]
|
||||||
(set! (.-innerHTML ^js node) svg)))))))
|
(set! (.-innerHTML ^js node) svg)))))))
|
||||||
[:div.grid-item-th {:style {:background-color (get-in file [:data :options :background])}
|
[:div.grid-item-th {:style {:background-color (get-in file [:data :options :background])}
|
||||||
:ref container}]))
|
:ref container}
|
||||||
|
i/loader-pencil]))
|
||||||
|
|
||||||
;; --- Grid Item
|
;; --- Grid Item
|
||||||
|
|
||||||
|
|
|
@ -29,11 +29,11 @@
|
||||||
(def auto-fix (icon-xref :auto-fix))
|
(def auto-fix (icon-xref :auto-fix))
|
||||||
(def auto-height (icon-xref :auto-height))
|
(def auto-height (icon-xref :auto-height))
|
||||||
(def auto-width (icon-xref :auto-width))
|
(def auto-width (icon-xref :auto-width))
|
||||||
(def boolean-difference (icon-xref :boolean-difference))
|
(def bool-difference (icon-xref :boolean-difference))
|
||||||
(def boolean-exclude (icon-xref :boolean-exclude))
|
(def bool-exclude (icon-xref :boolean-exclude))
|
||||||
(def boolean-flatten (icon-xref :boolean-flatten))
|
(def bool-flatten (icon-xref :boolean-flatten))
|
||||||
(def boolean-intersection (icon-xref :boolean-intersection))
|
(def bool-intersection (icon-xref :boolean-intersection))
|
||||||
(def boolean-union (icon-xref :boolean-union))
|
(def bool-union (icon-xref :boolean-union))
|
||||||
(def box (icon-xref :box))
|
(def box (icon-xref :box))
|
||||||
(def chain (icon-xref :chain))
|
(def chain (icon-xref :chain))
|
||||||
(def chat (icon-xref :chat))
|
(def chat (icon-xref :chat))
|
||||||
|
|
|
@ -8,107 +8,41 @@
|
||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.geom.shapes :as gsh]
|
[app.common.geom.shapes :as gsh]
|
||||||
[app.common.geom.shapes.path :as gsp]
|
|
||||||
[app.common.path.bool :as pb]
|
|
||||||
[app.common.path.shapes-to-path :as stp]
|
|
||||||
[app.main.ui.hooks :refer [use-equal-memo]]
|
[app.main.ui.hooks :refer [use-equal-memo]]
|
||||||
[app.main.ui.shapes.export :as use]
|
[app.main.ui.shapes.export :as use]
|
||||||
[app.main.ui.shapes.path :refer [path-shape]]
|
[app.main.ui.shapes.path :refer [path-shape]]
|
||||||
[app.util.object :as obj]
|
[app.util.object :as obj]
|
||||||
[rumext.alpha :as mf]))
|
[rumext.alpha :as mf]))
|
||||||
|
|
||||||
(mf/defc debug-bool
|
|
||||||
{::mf/wrap-props false}
|
|
||||||
[props]
|
|
||||||
|
|
||||||
(let [frame (obj/get props "frame")
|
|
||||||
shape (obj/get props "shape")
|
|
||||||
childs (obj/get props "childs")
|
|
||||||
|
|
||||||
[content-a content-b]
|
|
||||||
(mf/use-memo
|
|
||||||
(mf/deps shape childs)
|
|
||||||
(fn []
|
|
||||||
(let [childs (d/mapm #(-> %2 (gsh/translate-to-frame frame) gsh/transform-shape) childs)
|
|
||||||
[content-a content-b]
|
|
||||||
(->> (:shapes shape)
|
|
||||||
(map #(get childs %))
|
|
||||||
(filter #(not (:hidden %)))
|
|
||||||
(map #(stp/convert-to-path % childs))
|
|
||||||
(map :content)
|
|
||||||
(map pb/close-paths)
|
|
||||||
(map pb/add-previous))]
|
|
||||||
(pb/content-intersect-split content-a content-b))))]
|
|
||||||
[:g.debug-bool
|
|
||||||
[:g.shape-a
|
|
||||||
[:& path-shape {:shape (-> shape
|
|
||||||
(assoc :type :path)
|
|
||||||
(assoc :stroke-color "blue")
|
|
||||||
(assoc :stroke-opacity 1)
|
|
||||||
(assoc :stroke-width 1)
|
|
||||||
(assoc :stroke-style :solid)
|
|
||||||
(dissoc :fill-color :fill-opacity)
|
|
||||||
(assoc :content content-b))
|
|
||||||
:frame frame}]
|
|
||||||
(for [{:keys [x y]} (gsp/content->points (pb/close-paths content-b))]
|
|
||||||
[:circle {:cx x
|
|
||||||
:cy y
|
|
||||||
:r 2.5
|
|
||||||
:style {:fill "blue"}}])]
|
|
||||||
|
|
||||||
[:g.shape-b
|
|
||||||
[:& path-shape {:shape (-> shape
|
|
||||||
(assoc :type :path)
|
|
||||||
(assoc :stroke-color "red")
|
|
||||||
(assoc :stroke-opacity 1)
|
|
||||||
(assoc :stroke-width 0.5)
|
|
||||||
(assoc :stroke-style :solid)
|
|
||||||
(dissoc :fill-color :fill-opacity)
|
|
||||||
(assoc :content content-a))
|
|
||||||
:frame frame}]
|
|
||||||
(for [{:keys [x y]} (gsp/content->points (pb/close-paths content-a))]
|
|
||||||
[:circle {:cx x
|
|
||||||
:cy y
|
|
||||||
:r 1.25
|
|
||||||
:style {:fill "red"}}])]])
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
(defn bool-shape
|
(defn bool-shape
|
||||||
[shape-wrapper]
|
[shape-wrapper]
|
||||||
(mf/fnc bool-shape
|
(mf/fnc bool-shape
|
||||||
{::mf/wrap-props false}
|
{::mf/wrap-props false}
|
||||||
[props]
|
[props]
|
||||||
(let [frame (obj/get props "frame")
|
(let [shape (obj/get props "shape")
|
||||||
shape (obj/get props "shape")
|
|
||||||
childs (obj/get props "childs")
|
childs (obj/get props "childs")
|
||||||
|
|
||||||
childs (use-equal-memo childs)
|
childs (use-equal-memo childs)
|
||||||
|
|
||||||
include-metadata? (mf/use-ctx use/include-metadata-ctx)
|
include-metadata? (mf/use-ctx use/include-metadata-ctx)
|
||||||
|
|
||||||
bool-content
|
bool-content
|
||||||
(mf/use-memo
|
(mf/use-memo
|
||||||
(mf/deps shape childs)
|
(mf/deps shape childs)
|
||||||
(fn []
|
(fn []
|
||||||
(let [childs (d/mapm #(-> %2 gsh/transform-shape (gsh/translate-to-frame frame)) childs)]
|
(cond
|
||||||
(->> (:shapes shape)
|
(some? (:bool-content shape))
|
||||||
(map #(get childs %))
|
(:bool-content shape)
|
||||||
(filter #(not (:hidden %)))
|
|
||||||
(map #(stp/convert-to-path % childs))
|
(some? childs)
|
||||||
(mapv :content)
|
(->> childs
|
||||||
(pb/content-bool (:bool-type shape))))))]
|
(d/mapm #(gsh/transform-shape %2))
|
||||||
|
(gsh/calc-bool-content shape)))))]
|
||||||
|
|
||||||
[:*
|
[:*
|
||||||
[:& path-shape {:shape (assoc shape :content bool-content)}]
|
(when (some? bool-content)
|
||||||
|
[:& path-shape {:shape (assoc shape :content bool-content)}])
|
||||||
|
|
||||||
(when include-metadata?
|
(when include-metadata?
|
||||||
[:> "penpot:bool" {}
|
[:> "penpot:bool" {}
|
||||||
(for [item (->> (:shapes shape) (mapv #(get childs %)))]
|
(for [item (->> (:shapes shape) (mapv #(get childs %)))]
|
||||||
[:& shape-wrapper {:frame frame
|
[:& shape-wrapper {:shape item
|
||||||
:shape item
|
:key (:id item)}])])])))
|
||||||
:key (:id item)}])])
|
|
||||||
|
|
||||||
#_[:& debug-bool {:frame frame
|
|
||||||
:shape shape
|
|
||||||
:childs childs}]])))
|
|
||||||
|
|
|
@ -25,7 +25,12 @@
|
||||||
(let [;; When not active the embedding we return the URI
|
(let [;; When not active the embedding we return the URI
|
||||||
url-mapping (fn [obs]
|
url-mapping (fn [obs]
|
||||||
(if embed?
|
(if embed?
|
||||||
(rx/merge-map http/fetch-data-uri obs)
|
(->> obs
|
||||||
|
(rx/merge-map
|
||||||
|
(fn [uri]
|
||||||
|
(->> (http/fetch-data-uri uri true)
|
||||||
|
;; If fetching give an error we store the URI as its `data-uri`
|
||||||
|
(rx/catch #(rx/of (hash-map uri uri)))))))
|
||||||
(rx/map identity obs)))
|
(rx/map identity obs)))
|
||||||
|
|
||||||
sub (->> (rx/from urls)
|
sub (->> (rx/from urls)
|
||||||
|
|
|
@ -113,7 +113,7 @@
|
||||||
filter-x (min x (+ x offset-x (- spread) (- blur) -5))
|
filter-x (min x (+ x offset-x (- spread) (- blur) -5))
|
||||||
filter-y (min y (+ y offset-y (- spread) (- blur) -5))
|
filter-y (min y (+ y offset-y (- spread) (- blur) -5))
|
||||||
filter-width (+ width (mth/abs offset-x) (* spread 2) (* blur 2) 10)
|
filter-width (+ width (mth/abs offset-x) (* spread 2) (* blur 2) 10)
|
||||||
filter-height (+ height (mth/abs offset-x) (* spread 2) (* blur 2) 10)]
|
filter-height (+ height (mth/abs offset-y) (* spread 2) (* blur 2) 10)]
|
||||||
{:x1 filter-x
|
{:x1 filter-x
|
||||||
:y1 filter-y
|
:y1 filter-y
|
||||||
:x2 (+ filter-x filter-width)
|
:x2 (+ filter-x filter-width)
|
||||||
|
@ -208,26 +208,31 @@
|
||||||
margin (gsh/shape-stroke-margin shape stroke-width)]
|
margin (gsh/shape-stroke-margin shape stroke-width)]
|
||||||
(+ stroke-width margin)))
|
(+ stroke-width margin)))
|
||||||
|
|
||||||
|
(defn change-filter-in
|
||||||
|
"Adds the previous filter as `filter-in` parameter"
|
||||||
|
[filters]
|
||||||
|
(map #(assoc %1 :filter-in %2) filters (cons nil (map :id filters))))
|
||||||
|
|
||||||
(mf/defc filters
|
(mf/defc filters
|
||||||
[{:keys [filter-id shape]}]
|
[{:keys [filter-id shape]}]
|
||||||
|
|
||||||
(let [filters (shape->filters shape)
|
(let [filters (-> shape shape->filters change-filter-in)
|
||||||
|
bounds (get-filters-bounds shape filters (or (-> shape :blur :value) 0))
|
||||||
;; Adds the previous filter as `filter-in` parameter
|
padding (calculate-padding shape)
|
||||||
filters (map #(assoc %1 :filter-in %2) filters (cons nil (map :id filters)))
|
selrect (:selrect shape)
|
||||||
bounds (get-filters-bounds shape filters (or (-> shape :blur :value) 0))
|
filter-x (/ (- (:x bounds) (:x selrect) padding) (:width selrect))
|
||||||
padding (calculate-padding shape)]
|
filter-y (/ (- (:y bounds) (:y selrect) padding) (:height selrect))
|
||||||
|
filter-width (/ (+ (:width bounds) (* 2 padding)) (:width selrect))
|
||||||
|
filter-height (/ (+ (:height bounds) (* 2 padding)) (:height selrect))]
|
||||||
[:*
|
[:*
|
||||||
(when (> (count filters) 2)
|
(when (> (count filters) 2)
|
||||||
[:filter {:id filter-id
|
[:filter {:id filter-id
|
||||||
:x (- (:x bounds) padding)
|
:x filter-x
|
||||||
:y (- (:y bounds) padding)
|
:y filter-y
|
||||||
:width (+ (:width bounds) (* 2 padding))
|
:width filter-width
|
||||||
:height (+ (:height bounds) (* 2 padding))
|
:height filter-height
|
||||||
:filterUnits "userSpaceOnUse"
|
:filterUnits "objectBoundingBox"
|
||||||
:color-interpolation-filters "sRGB"}
|
:color-interpolation-filters "sRGB"}
|
||||||
|
|
||||||
(for [entry filters]
|
(for [entry filters]
|
||||||
[:& filter-entry {:entry entry}])])]))
|
[:& filter-entry {:entry entry}])])]))
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,22 @@
|
||||||
[app.util.object :as obj]
|
[app.util.object :as obj]
|
||||||
[rumext.alpha :as mf]))
|
[rumext.alpha :as mf]))
|
||||||
|
|
||||||
|
(defn frame-clip-id
|
||||||
|
[shape render-id]
|
||||||
|
(str "frame-clip-" (:id shape) "-" render-id))
|
||||||
|
|
||||||
|
(defn frame-clip-url
|
||||||
|
[shape render-id]
|
||||||
|
(when (= :frame (:type shape))
|
||||||
|
(str "url(#" (frame-clip-id shape render-id) ")")))
|
||||||
|
|
||||||
|
(mf/defc frame-clip-def
|
||||||
|
[{:keys [shape render-id]}]
|
||||||
|
(when (= :frame (:type shape))
|
||||||
|
(let [{:keys [x y width height]} shape]
|
||||||
|
[:clipPath {:id (frame-clip-id shape render-id) :class "frame-clip"}
|
||||||
|
[:rect {:x x :y y :width width :height height}]])))
|
||||||
|
|
||||||
(defn frame-shape
|
(defn frame-shape
|
||||||
[shape-wrapper]
|
[shape-wrapper]
|
||||||
(mf/fnc frame-shape
|
(mf/fnc frame-shape
|
||||||
|
@ -17,7 +33,7 @@
|
||||||
[props]
|
[props]
|
||||||
(let [childs (unchecked-get props "childs")
|
(let [childs (unchecked-get props "childs")
|
||||||
shape (unchecked-get props "shape")
|
shape (unchecked-get props "shape")
|
||||||
{:keys [width height]} shape
|
{:keys [x y width height]} shape
|
||||||
|
|
||||||
has-background? (or (some? (:fill-color shape))
|
has-background? (or (some? (:fill-color shape))
|
||||||
(some? (:fill-color-gradient shape)))
|
(some? (:fill-color-gradient shape)))
|
||||||
|
@ -25,8 +41,8 @@
|
||||||
|
|
||||||
props (-> (attrs/extract-style-attrs shape)
|
props (-> (attrs/extract-style-attrs shape)
|
||||||
(obj/merge!
|
(obj/merge!
|
||||||
#js {:x 0
|
#js {:x x
|
||||||
:y 0
|
:y y
|
||||||
:width width
|
:width width
|
||||||
:height height
|
:height height
|
||||||
:className "frame-background"}))]
|
:className "frame-background"}))]
|
||||||
|
@ -34,7 +50,6 @@
|
||||||
(when (or has-background? has-stroke?)
|
(when (or has-background? has-stroke?)
|
||||||
[:> :rect props])
|
[:> :rect props])
|
||||||
(for [item childs]
|
(for [item childs]
|
||||||
[:& shape-wrapper {:frame shape
|
[:& shape-wrapper {:shape item
|
||||||
:shape item
|
|
||||||
:key (:id item)}])])))
|
:key (:id item)}])])))
|
||||||
|
|
||||||
|
|
|
@ -17,8 +17,7 @@
|
||||||
(mf/fnc group-shape
|
(mf/fnc group-shape
|
||||||
{::mf/wrap-props false}
|
{::mf/wrap-props false}
|
||||||
[props]
|
[props]
|
||||||
(let [frame (unchecked-get props "frame")
|
(let [shape (unchecked-get props "shape")
|
||||||
shape (unchecked-get props "shape")
|
|
||||||
childs (unchecked-get props "childs")
|
childs (unchecked-get props "childs")
|
||||||
render-id (mf/use-ctx muc/render-ctx)
|
render-id (mf/use-ctx muc/render-ctx)
|
||||||
masked-group? (:masked-group? shape)
|
masked-group? (:masked-group? shape)
|
||||||
|
@ -46,11 +45,10 @@
|
||||||
[:> clip-wrapper clip-props
|
[:> clip-wrapper clip-props
|
||||||
[:> mask-wrapper mask-props
|
[:> mask-wrapper mask-props
|
||||||
(when masked-group?
|
(when masked-group?
|
||||||
[:> render-mask #js {:frame frame :mask mask}])
|
[:> render-mask #js {:mask mask}])
|
||||||
|
|
||||||
(for [item childs]
|
(for [item childs]
|
||||||
[:& shape-wrapper {:frame frame
|
[:& shape-wrapper {:shape item
|
||||||
:shape item
|
|
||||||
:key (:id item)}])]]))))
|
:key (:id item)}])]]))))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -34,13 +34,9 @@
|
||||||
(mf/fnc mask-shape
|
(mf/fnc mask-shape
|
||||||
{::mf/wrap-props false}
|
{::mf/wrap-props false}
|
||||||
[props]
|
[props]
|
||||||
(let [frame (unchecked-get props "frame")
|
(let [mask (unchecked-get props "mask")
|
||||||
mask (unchecked-get props "mask")
|
|
||||||
render-id (mf/use-ctx muc/render-ctx)
|
render-id (mf/use-ctx muc/render-ctx)
|
||||||
|
mask' (gsh/transform-shape mask)]
|
||||||
mask' (-> mask
|
|
||||||
(gsh/transform-shape)
|
|
||||||
(gsh/translate-to-frame frame))]
|
|
||||||
[:defs
|
[:defs
|
||||||
[:filter {:id (filter-id render-id mask)}
|
[:filter {:id (filter-id render-id mask)}
|
||||||
[:feFlood {:flood-color "white"
|
[:feFlood {:flood-color "white"
|
||||||
|
@ -52,13 +48,13 @@
|
||||||
;; Clip path is necessary so the elements inside the mask won't affect
|
;; Clip path is necessary so the elements inside the mask won't affect
|
||||||
;; the events outside. Clip hides the elements but mask doesn't (like display vs visibility)
|
;; the events outside. Clip hides the elements but mask doesn't (like display vs visibility)
|
||||||
;; we cannot use clips instead of mask because clips can only be simple shapes
|
;; we cannot use clips instead of mask because clips can only be simple shapes
|
||||||
[:clipPath {:id (clip-id render-id mask)}
|
[:clipPath {:class "mask-clip-path"
|
||||||
|
:id (clip-id render-id mask)}
|
||||||
[:polyline {:points (->> (:points mask')
|
[:polyline {:points (->> (:points mask')
|
||||||
(map #(str (:x %) "," (:y %)))
|
(map #(str (:x %) "," (:y %)))
|
||||||
(str/join " "))}]]
|
(str/join " "))}]]
|
||||||
[:mask {:id (mask-id render-id mask)}
|
[:mask {:class "mask-shape"
|
||||||
|
:id (mask-id render-id mask)}
|
||||||
[:g {:filter (filter-url render-id mask)}
|
[:g {:filter (filter-url render-id mask)}
|
||||||
[:& shape-wrapper {:frame frame
|
[:& shape-wrapper {:shape (dissoc mask :shadow :blur)}]]]])))
|
||||||
:shape (-> mask
|
|
||||||
(dissoc :shadow :blur))}]]]])))
|
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
[app.main.ui.shapes.export :as ed]
|
[app.main.ui.shapes.export :as ed]
|
||||||
[app.main.ui.shapes.fill-image :as fim]
|
[app.main.ui.shapes.fill-image :as fim]
|
||||||
[app.main.ui.shapes.filters :as filters]
|
[app.main.ui.shapes.filters :as filters]
|
||||||
|
[app.main.ui.shapes.frame :as frame]
|
||||||
[app.main.ui.shapes.gradients :as grad]
|
[app.main.ui.shapes.gradients :as grad]
|
||||||
[app.main.ui.shapes.svg-defs :as defs]
|
[app.main.ui.shapes.svg-defs :as defs]
|
||||||
[app.util.object :as obj]
|
[app.util.object :as obj]
|
||||||
|
@ -26,6 +27,8 @@
|
||||||
(let [shape (obj/get props "shape")
|
(let [shape (obj/get props "shape")
|
||||||
children (obj/get props "children")
|
children (obj/get props "children")
|
||||||
pointer-events (obj/get props "pointer-events")
|
pointer-events (obj/get props "pointer-events")
|
||||||
|
|
||||||
|
type (:type shape)
|
||||||
render-id (mf/use-memo #(str (uuid/next)))
|
render-id (mf/use-memo #(str (uuid/next)))
|
||||||
filter-id (str "filter_" render-id)
|
filter-id (str "filter_" render-id)
|
||||||
styles (-> (obj/new)
|
styles (-> (obj/new)
|
||||||
|
@ -34,10 +37,6 @@
|
||||||
(cond-> (and (:blend-mode shape) (not= (:blend-mode shape) :normal))
|
(cond-> (and (:blend-mode shape) (not= (:blend-mode shape) :normal))
|
||||||
(obj/set! "mixBlendMode" (d/name (:blend-mode shape)))))
|
(obj/set! "mixBlendMode" (d/name (:blend-mode shape)))))
|
||||||
|
|
||||||
{:keys [x y width height type]} shape
|
|
||||||
frame? (= :frame type)
|
|
||||||
group? (= :group type)
|
|
||||||
|
|
||||||
include-metadata? (mf/use-ctx ed/include-metadata-ctx)
|
include-metadata? (mf/use-ctx ed/include-metadata-ctx)
|
||||||
|
|
||||||
wrapper-props
|
wrapper-props
|
||||||
|
@ -50,26 +49,14 @@
|
||||||
|
|
||||||
wrapper-props
|
wrapper-props
|
||||||
(cond-> wrapper-props
|
(cond-> wrapper-props
|
||||||
frame?
|
(= :frame type)
|
||||||
(-> (obj/set! "x" x)
|
(obj/set! "clipPath" (frame/frame-clip-url shape render-id))
|
||||||
(obj/set! "y" y)
|
|
||||||
(obj/set! "width" width)
|
|
||||||
(obj/set! "height" height)
|
|
||||||
(obj/set! "xmlns" "http://www.w3.org/2000/svg")
|
|
||||||
(obj/set! "xmlnsXlink" "http://www.w3.org/1999/xlink")
|
|
||||||
(cond->
|
|
||||||
include-metadata?
|
|
||||||
(obj/set! "xmlns:penpot" "https://penpot.app/xmlns"))))
|
|
||||||
|
|
||||||
wrapper-props
|
(= :group type)
|
||||||
(cond-> wrapper-props
|
(attrs/add-style-attrs shape))]
|
||||||
group?
|
|
||||||
(attrs/add-style-attrs shape))
|
|
||||||
|
|
||||||
wrapper-tag (if frame? "svg" "g")]
|
|
||||||
|
|
||||||
[:& (mf/provider muc/render-ctx) {:value render-id}
|
[:& (mf/provider muc/render-ctx) {:value render-id}
|
||||||
[:> wrapper-tag wrapper-props
|
[:> :g wrapper-props
|
||||||
(when include-metadata?
|
(when include-metadata?
|
||||||
[:& ed/export-data {:shape shape}])
|
[:& ed/export-data {:shape shape}])
|
||||||
|
|
||||||
|
@ -79,5 +66,6 @@
|
||||||
[:& grad/gradient {:shape shape :attr :fill-color-gradient}]
|
[:& grad/gradient {:shape shape :attr :fill-color-gradient}]
|
||||||
[:& grad/gradient {:shape shape :attr :stroke-color-gradient}]
|
[:& grad/gradient {:shape shape :attr :stroke-color-gradient}]
|
||||||
[:& fim/fill-image-pattern {:shape shape :render-id render-id}]
|
[:& fim/fill-image-pattern {:shape shape :render-id render-id}]
|
||||||
[:& cs/stroke-defs {:shape shape :render-id render-id}]]
|
[:& cs/stroke-defs {:shape shape :render-id render-id}]
|
||||||
|
[:& frame/frame-clip-def {:shape shape :render-id render-id}]]
|
||||||
children]]))
|
children]]))
|
||||||
|
|
|
@ -88,8 +88,7 @@
|
||||||
{::mf/wrap-props false}
|
{::mf/wrap-props false}
|
||||||
[props]
|
[props]
|
||||||
|
|
||||||
(let [frame (unchecked-get props "frame")
|
(let [shape (unchecked-get props "shape")
|
||||||
shape (unchecked-get props "shape")
|
|
||||||
childs (unchecked-get props "childs")
|
childs (unchecked-get props "childs")
|
||||||
|
|
||||||
{:keys [content]} shape
|
{:keys [content]} shape
|
||||||
|
@ -103,12 +102,12 @@
|
||||||
svg-root?
|
svg-root?
|
||||||
[:& svg-root {:shape shape}
|
[:& svg-root {:shape shape}
|
||||||
(for [item childs]
|
(for [item childs]
|
||||||
[:& shape-wrapper {:frame frame :shape item :key (:id item)}])]
|
[:& shape-wrapper {:shape item :key (:id item)}])]
|
||||||
|
|
||||||
svg-tag?
|
svg-tag?
|
||||||
[:& svg-element {:shape shape}
|
[:& svg-element {:shape shape}
|
||||||
(for [item childs]
|
(for [item childs]
|
||||||
[:& shape-wrapper {:frame frame :shape item :key (:id item)}])]
|
[:& shape-wrapper {:shape item :key (:id item)}])]
|
||||||
|
|
||||||
svg-leaf?
|
svg-leaf?
|
||||||
content
|
content
|
||||||
|
|
|
@ -57,8 +57,7 @@
|
||||||
(let [{:keys [hover selected zoom]} local
|
(let [{:keys [hover selected zoom]} local
|
||||||
hover-shape (-> (or (first (resolve-shapes objects [hover])) frame)
|
hover-shape (-> (or (first (resolve-shapes objects [hover])) frame)
|
||||||
(gsh/translate-to-frame frame))
|
(gsh/translate-to-frame frame))
|
||||||
selected-shapes (->> (resolve-shapes objects selected)
|
selected-shapes (->> (resolve-shapes objects selected))
|
||||||
(map #(gsh/translate-to-frame % frame)))
|
|
||||||
|
|
||||||
selrect (gsh/selection-rect selected-shapes)
|
selrect (gsh/selection-rect selected-shapes)
|
||||||
bounds (frame->bounds frame)]
|
bounds (frame->bounds frame)]
|
||||||
|
|
|
@ -250,16 +250,16 @@
|
||||||
(or multiple? (and single? (or is-group? is-bool?))))
|
(or multiple? (and single? (or is-group? is-bool?))))
|
||||||
[:& menu-entry {:title (tr "workspace.shape.menu.path")}
|
[:& menu-entry {:title (tr "workspace.shape.menu.path")}
|
||||||
[:& menu-entry {:title (tr "workspace.shape.menu.union")
|
[:& menu-entry {:title (tr "workspace.shape.menu.union")
|
||||||
:shortcut (sc/get-tooltip :boolean-union)
|
:shortcut (sc/get-tooltip :bool-union)
|
||||||
:on-click (set-bool :union)}]
|
:on-click (set-bool :union)}]
|
||||||
[:& menu-entry {:title (tr "workspace.shape.menu.difference")
|
[:& menu-entry {:title (tr "workspace.shape.menu.difference")
|
||||||
:shortcut (sc/get-tooltip :boolean-difference)
|
:shortcut (sc/get-tooltip :bool-difference)
|
||||||
:on-click (set-bool :difference)}]
|
:on-click (set-bool :difference)}]
|
||||||
[:& menu-entry {:title (tr "workspace.shape.menu.intersection")
|
[:& menu-entry {:title (tr "workspace.shape.menu.intersection")
|
||||||
:shortcut (sc/get-tooltip :boolean-intersection)
|
:shortcut (sc/get-tooltip :bool-intersection)
|
||||||
:on-click (set-bool :intersection)}]
|
:on-click (set-bool :intersection)}]
|
||||||
[:& menu-entry {:title (tr "workspace.shape.menu.exclude")
|
[:& menu-entry {:title (tr "workspace.shape.menu.exclude")
|
||||||
:shortcut (sc/get-tooltip :boolean-exclude)
|
:shortcut (sc/get-tooltip :bool-exclude)
|
||||||
:on-click (set-bool :exclude)}]
|
:on-click (set-bool :exclude)}]
|
||||||
|
|
||||||
(when (and single? is-bool? (not disable-flatten?))
|
(when (and single? is-bool? (not disable-flatten?))
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
others are defined using a generic wrapper implemented in
|
others are defined using a generic wrapper implemented in
|
||||||
common."
|
common."
|
||||||
(:require
|
(:require
|
||||||
[app.common.geom.shapes :as geom]
|
|
||||||
[app.common.pages :as cp]
|
[app.common.pages :as cp]
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
[app.main.refs :as refs]
|
[app.main.refs :as refs]
|
||||||
|
@ -77,20 +76,16 @@
|
||||||
:key (:id item)}]))]))
|
:key (:id item)}]))]))
|
||||||
|
|
||||||
(mf/defc shape-wrapper
|
(mf/defc shape-wrapper
|
||||||
{::mf/wrap [#(mf/memo' % (mf/check-props ["shape" "frame"]))]
|
{::mf/wrap [#(mf/memo' % (mf/check-props ["shape"]))]
|
||||||
::mf/wrap-props false}
|
::mf/wrap-props false}
|
||||||
[props]
|
[props]
|
||||||
(let [shape (obj/get props "shape")
|
(let [shape (obj/get props "shape")
|
||||||
frame (obj/get props "frame")
|
opts #js {:shape shape}
|
||||||
shape (-> (geom/transform-shape shape {:round-coords? false})
|
|
||||||
(geom/translate-to-frame frame))
|
|
||||||
opts #js {:shape shape
|
|
||||||
:frame frame}
|
|
||||||
|
|
||||||
svg-element? (and (= (:type shape) :svg-raw)
|
svg-element? (and (= (:type shape) :svg-raw)
|
||||||
(not= :svg (get-in shape [:content :tag])))]
|
(not= :svg (get-in shape [:content :tag])))]
|
||||||
|
|
||||||
(when (and shape (not (:hidden shape)))
|
(when (and (some? shape) (not (:hidden shape)))
|
||||||
[:*
|
[:*
|
||||||
(if-not svg-element?
|
(if-not svg-element?
|
||||||
(case (:type shape)
|
(case (:type shape)
|
||||||
|
@ -104,7 +99,7 @@
|
||||||
:bool [:> bool-wrapper opts]
|
:bool [:> bool-wrapper opts]
|
||||||
|
|
||||||
;; Only used when drawing a new frame.
|
;; Only used when drawing a new frame.
|
||||||
:frame [:> frame-wrapper {:shape shape}]
|
:frame [:> frame-wrapper opts]
|
||||||
|
|
||||||
nil)
|
nil)
|
||||||
|
|
||||||
|
@ -112,7 +107,7 @@
|
||||||
[:> svg-raw-wrapper opts])
|
[:> svg-raw-wrapper opts])
|
||||||
|
|
||||||
(when (debug? :bounding-boxes)
|
(when (debug? :bounding-boxes)
|
||||||
[:& bounding-box {:shape shape :frame frame}])])))
|
[:> bounding-box opts])])))
|
||||||
|
|
||||||
(def group-wrapper (group/group-wrapper-factory shape-wrapper))
|
(def group-wrapper (group/group-wrapper-factory shape-wrapper))
|
||||||
(def svg-raw-wrapper (svg-raw/svg-raw-wrapper-factory shape-wrapper))
|
(def svg-raw-wrapper (svg-raw/svg-raw-wrapper-factory shape-wrapper))
|
||||||
|
|
|
@ -27,21 +27,26 @@
|
||||||
[shape-wrapper]
|
[shape-wrapper]
|
||||||
(let [shape-component (bool/bool-shape shape-wrapper)]
|
(let [shape-component (bool/bool-shape shape-wrapper)]
|
||||||
(mf/fnc bool-wrapper
|
(mf/fnc bool-wrapper
|
||||||
{::mf/wrap [#(mf/memo' % (mf/check-props ["shape" "frame"]))]
|
{::mf/wrap [#(mf/memo' % (mf/check-props ["shape"]))]
|
||||||
::mf/wrap-props false}
|
::mf/wrap-props false}
|
||||||
[props]
|
[props]
|
||||||
(let [shape (unchecked-get props "shape")
|
(let [shape (unchecked-get props "shape")
|
||||||
frame (unchecked-get props "frame")
|
child-sel-ref (mf/use-memo
|
||||||
|
(mf/deps (:id shape))
|
||||||
|
#(refs/is-child-selected? (:id shape)))
|
||||||
|
|
||||||
childs-ref (mf/use-memo
|
childs-ref (mf/use-memo
|
||||||
(mf/deps (:id shape))
|
(mf/deps (:id shape))
|
||||||
#(refs/select-children (:id shape)))
|
#(refs/select-bool-children (:id shape)))
|
||||||
|
|
||||||
childs (mf/deref childs-ref)]
|
child-sel? (mf/deref child-sel-ref)
|
||||||
|
childs (mf/deref childs-ref)
|
||||||
|
|
||||||
|
shape (cond-> shape
|
||||||
|
child-sel?
|
||||||
|
(dissoc :bool-content))]
|
||||||
|
|
||||||
[:> shape-container {:shape shape}
|
[:> shape-container {:shape shape}
|
||||||
[:& shape-component
|
[:& shape-component {:shape shape
|
||||||
{:frame frame
|
:childs childs}]]))))
|
||||||
:shape shape
|
|
||||||
:childs childs}]]))))
|
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
(ns app.main.ui.workspace.shapes.frame
|
(ns app.main.ui.workspace.shapes.frame
|
||||||
(:require
|
(:require
|
||||||
[app.common.geom.shapes :as gsh]
|
[app.common.data :as d]
|
||||||
[app.common.pages :as cp]
|
[app.common.pages :as cp]
|
||||||
[app.main.ui.hooks :as hooks]
|
[app.main.ui.hooks :as hooks]
|
||||||
[app.main.ui.shapes.frame :as frame]
|
[app.main.ui.shapes.frame :as frame]
|
||||||
|
@ -101,8 +101,7 @@
|
||||||
objects (unchecked-get props "objects")
|
objects (unchecked-get props "objects")
|
||||||
thumbnail? (unchecked-get props "thumbnail?")
|
thumbnail? (unchecked-get props "thumbnail?")
|
||||||
|
|
||||||
shape (gsh/transform-shape shape)
|
children (-> (mapv (d/getf objects) (:shapes shape))
|
||||||
children (-> (mapv #(get objects %) (:shapes shape))
|
|
||||||
(hooks/use-equal-memo))
|
(hooks/use-equal-memo))
|
||||||
|
|
||||||
all-children (-> (cp/get-children-objects (:id shape) objects)
|
all-children (-> (cp/get-children-objects (:id shape) objects)
|
||||||
|
|
|
@ -27,18 +27,15 @@
|
||||||
[shape-wrapper]
|
[shape-wrapper]
|
||||||
(let [group-shape (group/group-shape shape-wrapper)]
|
(let [group-shape (group/group-shape shape-wrapper)]
|
||||||
(mf/fnc group-wrapper
|
(mf/fnc group-wrapper
|
||||||
{::mf/wrap [#(mf/memo' % (mf/check-props ["shape" "frame"]))]
|
{::mf/wrap [#(mf/memo' % (mf/check-props ["shape"]))]
|
||||||
::mf/wrap-props false}
|
::mf/wrap-props false}
|
||||||
[props]
|
[props]
|
||||||
(let [shape (unchecked-get props "shape")
|
(let [shape (unchecked-get props "shape")
|
||||||
frame (unchecked-get props "frame")
|
childs-ref (mf/use-memo (mf/deps shape) #(refs/objects-by-id (:shapes shape)))
|
||||||
|
|
||||||
childs-ref (mf/use-memo (mf/deps shape) #(refs/objects-by-id (:shapes shape) {:with-modifiers? true}))
|
|
||||||
childs (mf/deref childs-ref)]
|
childs (mf/deref childs-ref)]
|
||||||
|
|
||||||
[:> shape-container {:shape shape}
|
[:> shape-container {:shape shape}
|
||||||
[:& group-shape
|
[:& group-shape
|
||||||
{:frame frame
|
{:shape shape
|
||||||
:shape shape
|
|
||||||
:childs childs}]]))))
|
:childs childs}]]))))
|
||||||
|
|
||||||
|
|
|
@ -15,12 +15,10 @@
|
||||||
[shape-wrapper]
|
[shape-wrapper]
|
||||||
(let [svg-raw-shape (svg-raw/svg-raw-shape shape-wrapper)]
|
(let [svg-raw-shape (svg-raw/svg-raw-shape shape-wrapper)]
|
||||||
(mf/fnc svg-raw-wrapper
|
(mf/fnc svg-raw-wrapper
|
||||||
{::mf/wrap [#(mf/memo' % (mf/check-props ["shape" "frame"]))]
|
{::mf/wrap [#(mf/memo' % (mf/check-props ["shape"]))]
|
||||||
::mf/wrap-props false}
|
::mf/wrap-props false}
|
||||||
[props]
|
[props]
|
||||||
(let [shape (unchecked-get props "shape")
|
(let [shape (unchecked-get props "shape")
|
||||||
frame (unchecked-get props "frame")
|
|
||||||
|
|
||||||
childs-ref (mf/use-memo (mf/deps shape) #(refs/objects-by-id (:shapes shape)))
|
childs-ref (mf/use-memo (mf/deps shape) #(refs/objects-by-id (:shapes shape)))
|
||||||
childs (mf/deref childs-ref)]
|
childs (mf/deref childs-ref)]
|
||||||
|
|
||||||
|
@ -28,11 +26,9 @@
|
||||||
(if (or (= (get-in shape [:content :tag]) :svg)
|
(if (or (= (get-in shape [:content :tag]) :svg)
|
||||||
(and (contains? shape :svg-attrs) (map? (:content shape))))
|
(and (contains? shape :svg-attrs) (map? (:content shape))))
|
||||||
[:> shape-container {:shape shape}
|
[:> shape-container {:shape shape}
|
||||||
[:& svg-raw-shape {:frame frame
|
[:& svg-raw-shape {:shape shape
|
||||||
:shape shape
|
|
||||||
:childs childs}]]
|
:childs childs}]]
|
||||||
|
|
||||||
[:& svg-raw-shape {:frame frame
|
[:& svg-raw-shape {:shape shape
|
||||||
:shape shape
|
|
||||||
:childs childs}])))))
|
:childs childs}])))))
|
||||||
|
|
||||||
|
|
|
@ -42,10 +42,10 @@
|
||||||
i/mask
|
i/mask
|
||||||
i/folder))
|
i/folder))
|
||||||
:bool (case (:bool-type shape)
|
:bool (case (:bool-type shape)
|
||||||
:difference i/boolean-difference
|
:difference i/bool-difference
|
||||||
:exclude i/boolean-exclude
|
:exclude i/bool-exclude
|
||||||
:intersection i/boolean-intersection
|
:intersection i/bool-intersection
|
||||||
#_:default i/boolean-union)
|
#_:default i/bool-union)
|
||||||
:svg-raw i/file-svg
|
:svg-raw i/file-svg
|
||||||
nil))
|
nil))
|
||||||
|
|
||||||
|
@ -308,6 +308,7 @@
|
||||||
:bool-type]))
|
:bool-type]))
|
||||||
|
|
||||||
(defn- strip-objects
|
(defn- strip-objects
|
||||||
|
"Remove unnecesary data from objects map"
|
||||||
[objects]
|
[objects]
|
||||||
(persistent!
|
(persistent!
|
||||||
(->> objects
|
(->> objects
|
||||||
|
@ -320,8 +321,11 @@
|
||||||
{::mf/wrap-props false
|
{::mf/wrap-props false
|
||||||
::mf/wrap [mf/memo #(mf/throttle % 200)]}
|
::mf/wrap [mf/memo #(mf/throttle % 200)]}
|
||||||
[props]
|
[props]
|
||||||
(let [objects (obj/get props "objects")
|
(let [objects (-> (obj/get props "objects")
|
||||||
objects (strip-objects objects)]
|
(hooks/use-equal-memo))
|
||||||
|
objects (mf/use-memo
|
||||||
|
(mf/deps objects)
|
||||||
|
#(strip-objects objects))]
|
||||||
[:& layers-tree {:objects objects}]))
|
[:& layers-tree {:objects objects}]))
|
||||||
|
|
||||||
;; --- Layers Toolbox
|
;; --- Layers Toolbox
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
[app.main.ui.components.tab-container :refer [tab-container tab-element]]
|
[app.main.ui.components.tab-container :refer [tab-container tab-element]]
|
||||||
[app.main.ui.context :as ctx]
|
[app.main.ui.context :as ctx]
|
||||||
[app.main.ui.workspace.sidebar.options.menus.align :refer [align-options]]
|
[app.main.ui.workspace.sidebar.options.menus.align :refer [align-options]]
|
||||||
[app.main.ui.workspace.sidebar.options.menus.booleans :refer [booleans-options]]
|
[app.main.ui.workspace.sidebar.options.menus.bool :refer [bool-options]]
|
||||||
[app.main.ui.workspace.sidebar.options.menus.exports :refer [exports-menu]]
|
[app.main.ui.workspace.sidebar.options.menus.exports :refer [exports-menu]]
|
||||||
[app.main.ui.workspace.sidebar.options.menus.interactions :refer [interactions-menu]]
|
[app.main.ui.workspace.sidebar.options.menus.interactions :refer [interactions-menu]]
|
||||||
[app.main.ui.workspace.sidebar.options.page :as page]
|
[app.main.ui.workspace.sidebar.options.page :as page]
|
||||||
|
@ -63,7 +63,7 @@
|
||||||
:title (tr "workspace.options.design")}
|
:title (tr "workspace.options.design")}
|
||||||
[:div.element-options
|
[:div.element-options
|
||||||
[:& align-options]
|
[:& align-options]
|
||||||
[:& booleans-options]
|
[:& bool-options]
|
||||||
(case (count selected)
|
(case (count selected)
|
||||||
0 [:& page/options]
|
0 [:& page/options]
|
||||||
1 [:& shape-options {:shape (first shapes)
|
1 [:& shape-options {:shape (first shapes)
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
;;
|
;;
|
||||||
;; Copyright (c) UXBOX Labs SL
|
;; Copyright (c) UXBOX Labs SL
|
||||||
|
|
||||||
(ns app.main.ui.workspace.sidebar.options.menus.booleans
|
(ns app.main.ui.workspace.sidebar.options.menus.bool
|
||||||
(:require
|
(:require
|
||||||
[app.main.data.workspace :as dw]
|
[app.main.data.workspace :as dw]
|
||||||
[app.main.data.workspace.shortcuts :as sc]
|
[app.main.data.workspace.shortcuts :as sc]
|
||||||
|
@ -15,7 +15,7 @@
|
||||||
[app.util.i18n :as i18n :refer [tr]]
|
[app.util.i18n :as i18n :refer [tr]]
|
||||||
[rumext.alpha :as mf]))
|
[rumext.alpha :as mf]))
|
||||||
|
|
||||||
(mf/defc booleans-options
|
(mf/defc bool-options
|
||||||
[]
|
[]
|
||||||
(let [selected (mf/deref refs/selected-objects)
|
(let [selected (mf/deref refs/selected-objects)
|
||||||
selected-with-children (mf/deref refs/selected-shapes-with-children)
|
selected-with-children (mf/deref refs/selected-shapes-with-children)
|
||||||
|
@ -52,37 +52,37 @@
|
||||||
[:div.align-options
|
[:div.align-options
|
||||||
[:div.align-group
|
[:div.align-group
|
||||||
[:div.align-button.tooltip.tooltip-bottom
|
[:div.align-button.tooltip.tooltip-bottom
|
||||||
{:alt (str (tr "workspace.shape.menu.union") " (" (sc/get-tooltip :boolean-union) ")")
|
{:alt (str (tr "workspace.shape.menu.union") " (" (sc/get-tooltip :bool-union) ")")
|
||||||
:class (dom/classnames :disabled disabled-bool-btns
|
:class (dom/classnames :disabled disabled-bool-btns
|
||||||
:selected (= head-bool-type :union))
|
:selected (= head-bool-type :union))
|
||||||
:on-click (set-bool :union)}
|
:on-click (set-bool :union)}
|
||||||
i/boolean-union]
|
i/bool-union]
|
||||||
|
|
||||||
[:div.align-button.tooltip.tooltip-bottom
|
[:div.align-button.tooltip.tooltip-bottom
|
||||||
{:alt (str (tr "workspace.shape.menu.difference") " (" (sc/get-tooltip :boolean-difference) ")")
|
{:alt (str (tr "workspace.shape.menu.difference") " (" (sc/get-tooltip :bool-difference) ")")
|
||||||
:class (dom/classnames :disabled disabled-bool-btns
|
:class (dom/classnames :disabled disabled-bool-btns
|
||||||
:selected (= head-bool-type :difference))
|
:selected (= head-bool-type :difference))
|
||||||
:on-click (set-bool :difference)}
|
:on-click (set-bool :difference)}
|
||||||
i/boolean-difference]
|
i/bool-difference]
|
||||||
|
|
||||||
[:div.align-button.tooltip.tooltip-bottom
|
[:div.align-button.tooltip.tooltip-bottom
|
||||||
{:alt (str (tr "workspace.shape.menu.intersection") " (" (sc/get-tooltip :boolean-intersection) ")")
|
{:alt (str (tr "workspace.shape.menu.intersection") " (" (sc/get-tooltip :bool-intersection) ")")
|
||||||
:class (dom/classnames :disabled disabled-bool-btns
|
:class (dom/classnames :disabled disabled-bool-btns
|
||||||
:selected (= head-bool-type :intersection))
|
:selected (= head-bool-type :intersection))
|
||||||
:on-click (set-bool :intersection)}
|
:on-click (set-bool :intersection)}
|
||||||
i/boolean-intersection]
|
i/bool-intersection]
|
||||||
|
|
||||||
[:div.align-button.tooltip.tooltip-bottom
|
[:div.align-button.tooltip.tooltip-bottom
|
||||||
{:alt (str (tr "workspace.shape.menu.exclude") " (" (sc/get-tooltip :boolean-exclude) ")")
|
{:alt (str (tr "workspace.shape.menu.exclude") " (" (sc/get-tooltip :bool-exclude) ")")
|
||||||
:class (dom/classnames :disabled disabled-bool-btns
|
:class (dom/classnames :disabled disabled-bool-btns
|
||||||
:selected (= head-bool-type :exclude))
|
:selected (= head-bool-type :exclude))
|
||||||
:on-click (set-bool :exclude)}
|
:on-click (set-bool :exclude)}
|
||||||
i/boolean-exclude]]
|
i/bool-exclude]]
|
||||||
|
|
||||||
[:div.align-group
|
[:div.align-group
|
||||||
[:div.align-button.tooltip.tooltip-bottom
|
[:div.align-button.tooltip.tooltip-bottom
|
||||||
{:alt (tr "workspace.shape.menu.flatten")
|
{:alt (tr "workspace.shape.menu.flatten")
|
||||||
:class (dom/classnames :disabled disabled-flatten)
|
:class (dom/classnames :disabled disabled-flatten)
|
||||||
:on-click (st/emitf (dw/convert-selected-to-path))}
|
:on-click (st/emitf (dw/convert-selected-to-path))}
|
||||||
i/boolean-flatten]]]))
|
i/bool-flatten]]]))
|
||||||
|
|
|
@ -43,7 +43,6 @@
|
||||||
;; that the new parameter is sent
|
;; that the new parameter is sent
|
||||||
{:keys [edit-path
|
{:keys [edit-path
|
||||||
edition
|
edition
|
||||||
modifiers
|
|
||||||
options-mode
|
options-mode
|
||||||
panning
|
panning
|
||||||
picking-color?
|
picking-color?
|
||||||
|
@ -61,11 +60,11 @@
|
||||||
;; DEREFS
|
;; DEREFS
|
||||||
drawing (mf/deref refs/workspace-drawing)
|
drawing (mf/deref refs/workspace-drawing)
|
||||||
options (mf/deref refs/workspace-page-options)
|
options (mf/deref refs/workspace-page-options)
|
||||||
objects (mf/deref refs/workspace-page-objects)
|
base-objects (mf/deref refs/workspace-page-objects)
|
||||||
object-modifiers (mf/deref refs/workspace-modifiers)
|
modifiers (mf/deref refs/workspace-modifiers)
|
||||||
objects (mf/use-memo
|
objects-modified (mf/use-memo
|
||||||
(mf/deps objects object-modifiers)
|
(mf/deps base-objects modifiers)
|
||||||
#(gsh/merge-modifiers objects object-modifiers))
|
#(gsh/merge-modifiers base-objects modifiers))
|
||||||
background (get options :background clr/canvas)
|
background (get options :background clr/canvas)
|
||||||
|
|
||||||
;; STATE
|
;; STATE
|
||||||
|
@ -81,7 +80,6 @@
|
||||||
|
|
||||||
;; REFS
|
;; REFS
|
||||||
viewport-ref (mf/use-ref nil)
|
viewport-ref (mf/use-ref nil)
|
||||||
render-ref (mf/use-ref nil)
|
|
||||||
|
|
||||||
;; VARS
|
;; VARS
|
||||||
disable-paste (mf/use-var false)
|
disable-paste (mf/use-var false)
|
||||||
|
@ -94,25 +92,21 @@
|
||||||
drawing-tool (:tool drawing)
|
drawing-tool (:tool drawing)
|
||||||
drawing-obj (:object drawing)
|
drawing-obj (:object drawing)
|
||||||
|
|
||||||
selected-shapes (into []
|
selected-shapes (into [] (keep (d/getf objects-modified)) selected)
|
||||||
(comp (map #(get objects %))
|
|
||||||
(filter some?))
|
|
||||||
selected)
|
|
||||||
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
|
||||||
selected-frame (when (= (count selected-frames) 1) (get objects (first selected-frames)))
|
selected-frame (when (= (count selected-frames) 1) (get base-objects (first selected-frames)))
|
||||||
|
|
||||||
|
|
||||||
create-comment? (= :comments drawing-tool)
|
create-comment? (= :comments drawing-tool)
|
||||||
drawing-path? (or (and edition (= :draw (get-in edit-path [edition :edit-mode])))
|
drawing-path? (or (and edition (= :draw (get-in edit-path [edition :edit-mode])))
|
||||||
(and (some? drawing-obj) (= :path (:type drawing-obj))))
|
(and (some? drawing-obj) (= :path (:type drawing-obj))))
|
||||||
node-editing? (and edition (not= :text (get-in objects [edition :type])))
|
node-editing? (and edition (not= :text (get-in base-objects [edition :type])))
|
||||||
text-editing? (and edition (= :text (get-in objects [edition :type])))
|
text-editing? (and edition (= :text (get-in base-objects [edition :type])))
|
||||||
|
|
||||||
on-click (actions/on-click hover selected edition drawing-path? drawing-tool)
|
on-click (actions/on-click hover selected edition drawing-path? drawing-tool)
|
||||||
on-context-menu (actions/on-context-menu hover)
|
on-context-menu (actions/on-context-menu hover)
|
||||||
on-double-click (actions/on-double-click hover hover-ids drawing-path? objects edition)
|
on-double-click (actions/on-double-click hover hover-ids drawing-path? base-objects edition)
|
||||||
on-drag-enter (actions/on-drag-enter)
|
on-drag-enter (actions/on-drag-enter)
|
||||||
on-drag-over (actions/on-drag-over)
|
on-drag-over (actions/on-drag-over)
|
||||||
on-drop (actions/on-drop file viewport-ref zoom)
|
on-drop (actions/on-drop file viewport-ref zoom)
|
||||||
|
@ -155,15 +149,15 @@
|
||||||
(hooks/setup-cursor cursor alt? panning drawing-tool drawing-path? node-editing?)
|
(hooks/setup-cursor cursor alt? panning drawing-tool drawing-path? node-editing?)
|
||||||
(hooks/setup-resize layout viewport-ref)
|
(hooks/setup-resize layout viewport-ref)
|
||||||
(hooks/setup-keyboard alt? ctrl? space?)
|
(hooks/setup-keyboard alt? ctrl? space?)
|
||||||
(hooks/setup-hover-shapes page-id move-stream objects transform selected ctrl? hover hover-ids @hover-disabled? zoom)
|
(hooks/setup-hover-shapes page-id move-stream base-objects transform selected ctrl? hover hover-ids @hover-disabled? zoom)
|
||||||
(hooks/setup-viewport-modifiers modifiers selected objects render-ref)
|
(hooks/setup-viewport-modifiers modifiers base-objects)
|
||||||
(hooks/setup-shortcuts node-editing? drawing-path?)
|
(hooks/setup-shortcuts node-editing? drawing-path?)
|
||||||
(hooks/setup-active-frames objects vbox hover active-frames)
|
(hooks/setup-active-frames base-objects vbox hover active-frames)
|
||||||
|
|
||||||
[:div.viewport
|
[:div.viewport
|
||||||
[:div.viewport-overlays
|
[:div.viewport-overlays
|
||||||
|
|
||||||
[:& wtr/frame-renderer {:objects objects
|
[:& wtr/frame-renderer {:objects base-objects
|
||||||
:background background}]
|
:background background}]
|
||||||
|
|
||||||
(when show-comments?
|
(when show-comments?
|
||||||
|
@ -184,7 +178,6 @@
|
||||||
[:& widgets/viewport-actions]]
|
[:& widgets/viewport-actions]]
|
||||||
[:svg.render-shapes
|
[:svg.render-shapes
|
||||||
{:id "render"
|
{:id "render"
|
||||||
:ref render-ref
|
|
||||||
:xmlns "http://www.w3.org/2000/svg"
|
:xmlns "http://www.w3.org/2000/svg"
|
||||||
:xmlnsXlink "http://www.w3.org/1999/xlink"
|
:xmlnsXlink "http://www.w3.org/1999/xlink"
|
||||||
:xmlns:penpot "https://penpot.app/xmlns"
|
:xmlns:penpot "https://penpot.app/xmlns"
|
||||||
|
@ -202,7 +195,7 @@
|
||||||
[:& (mf/provider embed/context) {:value true}
|
[:& (mf/provider embed/context) {:value true}
|
||||||
;; Render root shape
|
;; Render root shape
|
||||||
[:& shapes/root-shape {:key page-id
|
[:& shapes/root-shape {:key page-id
|
||||||
:objects objects
|
:objects base-objects
|
||||||
:active-frames @active-frames}]]]]
|
:active-frames @active-frames}]]]]
|
||||||
|
|
||||||
[:svg.viewport-controls
|
[:svg.viewport-controls
|
||||||
|
@ -234,7 +227,7 @@
|
||||||
[:g {:style {:pointer-events (if disable-events? "none" "auto")}}
|
[:g {:style {:pointer-events (if disable-events? "none" "auto")}}
|
||||||
(when show-outlines?
|
(when show-outlines?
|
||||||
[:& outline/shape-outlines
|
[:& outline/shape-outlines
|
||||||
{:objects objects
|
{:objects base-objects
|
||||||
:selected selected
|
:selected selected
|
||||||
:hover (when (not= :frame (:type @hover))
|
:hover (when (not= :frame (:type @hover))
|
||||||
#{(or @frame-hover (:id @hover))})
|
#{(or @frame-hover (:id @hover))})
|
||||||
|
@ -259,10 +252,10 @@
|
||||||
:zoom zoom}])
|
:zoom zoom}])
|
||||||
|
|
||||||
(when text-editing?
|
(when text-editing?
|
||||||
[:& editor/text-shape-edit {:shape (get objects edition)}])
|
[:& editor/text-shape-edit {:shape (get base-objects edition)}])
|
||||||
|
|
||||||
[:& widgets/frame-titles
|
[:& widgets/frame-titles
|
||||||
{:objects objects
|
{:objects objects-modified
|
||||||
:selected selected
|
:selected selected
|
||||||
:zoom zoom
|
:zoom zoom
|
||||||
:modifiers modifiers
|
:modifiers modifiers
|
||||||
|
@ -273,7 +266,7 @@
|
||||||
(when show-prototypes?
|
(when show-prototypes?
|
||||||
[:& widgets/frame-flows
|
[:& widgets/frame-flows
|
||||||
{:flows (:flows options)
|
{:flows (:flows options)
|
||||||
:objects objects
|
:objects base-objects
|
||||||
:selected selected
|
:selected selected
|
||||||
:zoom zoom
|
:zoom zoom
|
||||||
:modifiers modifiers
|
:modifiers modifiers
|
||||||
|
@ -310,7 +303,7 @@
|
||||||
:zoom zoom
|
:zoom zoom
|
||||||
:page-id page-id
|
:page-id page-id
|
||||||
:selected selected
|
:selected selected
|
||||||
:objects objects
|
:objects base-objects
|
||||||
:modifiers modifiers}])
|
:modifiers modifiers}])
|
||||||
|
|
||||||
(when show-snap-distance?
|
(when show-snap-distance?
|
||||||
|
@ -335,6 +328,9 @@
|
||||||
(when show-prototypes?
|
(when show-prototypes?
|
||||||
[:& interactions/interactions
|
[:& interactions/interactions
|
||||||
{:selected selected
|
{:selected selected
|
||||||
|
:zoom zoom
|
||||||
|
:objects objects-modified
|
||||||
|
:current-transform transform
|
||||||
:hover-disabled? hover-disabled?}])
|
:hover-disabled? hover-disabled?}])
|
||||||
|
|
||||||
(when show-selrect?
|
(when show-selrect?
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
|
|
||||||
[:g.draw-area
|
[:g.draw-area
|
||||||
[:g {:style {:pointer-events "none"}}
|
[:g {:style {:pointer-events "none"}}
|
||||||
[:& shapes/shape-wrapper {:shape shape}]]
|
[:& shapes/shape-wrapper {:shape (gsh/transform-shape shape)}]]
|
||||||
|
|
||||||
(case tool
|
(case tool
|
||||||
:path [:& path-editor {:shape shape :zoom zoom}]
|
:path [:& path-editor {:shape shape :zoom zoom}]
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
(ns app.main.ui.workspace.viewport.hooks
|
(ns app.main.ui.workspace.viewport.hooks
|
||||||
(:require
|
(:require
|
||||||
|
[app.common.data :as d]
|
||||||
[app.common.geom.shapes :as gsh]
|
[app.common.geom.shapes :as gsh]
|
||||||
[app.common.pages :as cp]
|
[app.common.pages :as cp]
|
||||||
[app.main.data.shortcuts :as dsc]
|
[app.main.data.shortcuts :as dsc]
|
||||||
|
@ -169,22 +170,31 @@
|
||||||
(reset! hover hover-shape)
|
(reset! hover hover-shape)
|
||||||
(reset! hover-ids ids))))))
|
(reset! hover-ids ids))))))
|
||||||
|
|
||||||
(defn setup-viewport-modifiers [modifiers selected objects render-ref]
|
(defn setup-viewport-modifiers
|
||||||
(let [roots (mf/use-memo
|
[modifiers objects]
|
||||||
(mf/deps objects selected)
|
(let [transforms
|
||||||
(fn []
|
(mf/use-memo
|
||||||
(let [roots-ids (cp/clean-loops objects selected)]
|
(mf/deps modifiers)
|
||||||
(->> roots-ids (mapv #(get objects %))))))]
|
(fn []
|
||||||
|
(d/mapm (fn [id {modifiers :modifiers}]
|
||||||
|
(let [center (gsh/center-shape (get objects id))]
|
||||||
|
(gsh/modifiers->transform center modifiers)))
|
||||||
|
modifiers)))
|
||||||
|
|
||||||
|
shapes
|
||||||
|
(mf/use-memo
|
||||||
|
(mf/deps transforms)
|
||||||
|
(fn []
|
||||||
|
(->> (keys transforms)
|
||||||
|
(mapv (d/getf objects)))))]
|
||||||
|
|
||||||
;; Layout effect is important so the code is executed before the modifiers
|
;; Layout effect is important so the code is executed before the modifiers
|
||||||
;; are applied to the shape
|
;; are applied to the shape
|
||||||
(mf/use-layout-effect
|
(mf/use-layout-effect
|
||||||
(mf/deps modifiers roots)
|
(mf/deps transforms)
|
||||||
|
(fn []
|
||||||
#(when-let [render-node (mf/ref-val render-ref)]
|
(utils/update-transform shapes transforms modifiers)
|
||||||
(if modifiers
|
#(utils/remove-transform shapes)))))
|
||||||
(utils/update-transform render-node roots modifiers)
|
|
||||||
(utils/remove-transform render-node roots))))))
|
|
||||||
|
|
||||||
(defn inside-vbox [vbox objects frame-id]
|
(defn inside-vbox [vbox objects frame-id]
|
||||||
(let [frame (get objects frame-id)]
|
(let [frame (get objects frame-id)]
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
"Visually show shape interactions in workspace"
|
"Visually show shape interactions in workspace"
|
||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
|
[app.common.geom.shapes :as gsh]
|
||||||
[app.common.pages :as cp]
|
[app.common.pages :as cp]
|
||||||
[app.common.types.interactions :as cti]
|
[app.common.types.interactions :as cti]
|
||||||
[app.main.data.workspace :as dw]
|
[app.main.data.workspace :as dw]
|
||||||
|
@ -235,18 +236,23 @@
|
||||||
:fill "var(--color-primary)"}]]))))
|
:fill "var(--color-primary)"}]]))))
|
||||||
|
|
||||||
(mf/defc interactions
|
(mf/defc interactions
|
||||||
[{:keys [selected hover-disabled?] :as props}]
|
[{:keys [current-transform objects zoom selected hover-disabled?] :as props}]
|
||||||
(let [local (mf/deref refs/workspace-local)
|
(let [active-shapes (into []
|
||||||
zoom (mf/deref refs/selected-zoom)
|
(comp (filter #(seq (:interactions %)))
|
||||||
current-transform (:transform local)
|
(map gsh/transform-shape))
|
||||||
objects (mf/deref refs/workspace-page-objects)
|
(vals objects))
|
||||||
active-shapes (filter #(seq (:interactions %)) (vals objects))
|
|
||||||
selected-shapes (map #(get objects %) selected)
|
selected-shapes (into []
|
||||||
editing-interaction-index (:editing-interaction-index local)
|
(comp (map (d/getf objects))
|
||||||
draw-interaction-to (:draw-interaction-to local)
|
(map gsh/transform-shape))
|
||||||
draw-interaction-to-frame (:draw-interaction-to-frame local)
|
selected)
|
||||||
move-overlay-to (:move-overlay-to local)
|
|
||||||
move-overlay-index (:move-overlay-index local)
|
{:keys [editing-interaction-index
|
||||||
|
draw-interaction-to
|
||||||
|
draw-interaction-to-frame
|
||||||
|
move-overlay-to
|
||||||
|
move-overlay-index]} (mf/deref refs/interactions-data)
|
||||||
|
|
||||||
first-selected (first selected-shapes)
|
first-selected (first selected-shapes)
|
||||||
|
|
||||||
calc-level (fn [index interactions]
|
calc-level (fn [index interactions]
|
||||||
|
|
|
@ -6,13 +6,14 @@
|
||||||
|
|
||||||
(ns app.main.ui.workspace.viewport.pixel-overlay
|
(ns app.main.ui.workspace.viewport.pixel-overlay
|
||||||
(:require
|
(:require
|
||||||
|
[app.common.data :as d]
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
[app.main.data.modal :as modal]
|
[app.main.data.modal :as modal]
|
||||||
[app.main.data.workspace.colors :as dwc]
|
[app.main.data.workspace.colors :as dwc]
|
||||||
[app.main.refs :as refs]
|
[app.main.refs :as refs]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
[app.main.ui.cursors :as cur]
|
[app.main.ui.cursors :as cur]
|
||||||
[app.main.ui.workspace.shapes :refer [shape-wrapper frame-wrapper]]
|
[app.main.ui.workspace.shapes :as shapes]
|
||||||
[app.util.dom :as dom]
|
[app.util.dom :as dom]
|
||||||
[app.util.keyboard :as kbd]
|
[app.util.keyboard :as kbd]
|
||||||
[app.util.object :as obj]
|
[app.util.object :as obj]
|
||||||
|
@ -36,16 +37,16 @@
|
||||||
(let [data (mf/deref refs/workspace-page)
|
(let [data (mf/deref refs/workspace-page)
|
||||||
objects (:objects data)
|
objects (:objects data)
|
||||||
root (get objects uuid/zero)
|
root (get objects uuid/zero)
|
||||||
shapes (->> (:shapes root) (map #(get objects %)))]
|
shapes (->> (:shapes root)
|
||||||
[:*
|
(map (d/getf objects)))]
|
||||||
[:g.shapes
|
[:g.shapes
|
||||||
(for [item shapes]
|
(for [item shapes]
|
||||||
(if (= (:type item) :frame)
|
(if (= (:type item) :frame)
|
||||||
[:& frame-wrapper {:shape item
|
[:& shapes/frame-wrapper {:shape item
|
||||||
:key (:id item)
|
:key (:id item)
|
||||||
:objects objects}]
|
:objects objects}]
|
||||||
[:& shape-wrapper {:shape item
|
[:& shapes/shape-wrapper {:shape item
|
||||||
:key (:id item)}]))]]))
|
:key (:id item)}]))]))
|
||||||
|
|
||||||
(mf/defc pixel-overlay
|
(mf/defc pixel-overlay
|
||||||
{::mf/wrap-props false}
|
{::mf/wrap-props false}
|
||||||
|
|
|
@ -320,7 +320,9 @@
|
||||||
(let [shape-id (:id shape)
|
(let [shape-id (:id shape)
|
||||||
shape (geom/transform-shape shape {:round-coords? false})
|
shape (geom/transform-shape shape {:round-coords? false})
|
||||||
|
|
||||||
shape' (if (debug? :simple-selection) (geom/setup {:type :rect} (geom/selection-rect [shape])) shape)
|
shape' (if (debug? :simple-selection)
|
||||||
|
(geom/setup {:type :rect} (geom/selection-rect [shape]))
|
||||||
|
shape)
|
||||||
|
|
||||||
on-resize
|
on-resize
|
||||||
(fn [current-position _initial-position event]
|
(fn [current-position _initial-position event]
|
||||||
|
|
|
@ -58,7 +58,7 @@
|
||||||
(->> shapes (first)))
|
(->> shapes (first)))
|
||||||
|
|
||||||
shape (if modifiers
|
shape (if modifiers
|
||||||
(-> shape (assoc :modifiers modifiers) gsh/transform-shape)
|
(-> shape (merge (get modifiers (:id shape))) gsh/transform-shape)
|
||||||
shape)
|
shape)
|
||||||
|
|
||||||
frame-id (snap/snap-frame-id shapes)]
|
frame-id (snap/snap-frame-id shapes)]
|
||||||
|
|
|
@ -32,8 +32,16 @@
|
||||||
loading-node (when frame-node
|
loading-node (when frame-node
|
||||||
(dom/query frame-node "[data-loading=\"true\"]"))]
|
(dom/query frame-node "[data-loading=\"true\"]"))]
|
||||||
(if (and (some? frame-node) (not (some? loading-node)))
|
(if (and (some? frame-node) (not (some? loading-node)))
|
||||||
(let [xml (-> (js/XMLSerializer.)
|
(let [frame-html (-> (js/XMLSerializer.)
|
||||||
(.serializeToString frame-node)
|
(.serializeToString frame-node))
|
||||||
|
|
||||||
|
;; We need to wrap the group node into a SVG with a viewbox that matches the selrect of the frame
|
||||||
|
svg-node (.createElementNS js/document "http://www.w3.org/2000/svg" "svg")
|
||||||
|
_ (.setAttribute svg-node "version" "1.1")
|
||||||
|
_ (.setAttribute svg-node "viewBox" (str (:x shape) " " (:y shape) " " (:width shape) " " (:height shape)))
|
||||||
|
_ (unchecked-set svg-node "innerHTML" frame-html)
|
||||||
|
xml (-> (js/XMLSerializer.)
|
||||||
|
(.serializeToString svg-node)
|
||||||
js/encodeURIComponent
|
js/encodeURIComponent
|
||||||
js/unescape
|
js/unescape
|
||||||
js/btoa)
|
js/btoa)
|
||||||
|
|
|
@ -7,35 +7,89 @@
|
||||||
(ns app.main.ui.workspace.viewport.utils
|
(ns app.main.ui.workspace.viewport.utils
|
||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
|
[app.common.geom.matrix :as gmt]
|
||||||
[app.common.geom.point :as gpt]
|
[app.common.geom.point :as gpt]
|
||||||
[app.main.ui.cursors :as cur]
|
[app.main.ui.cursors :as cur]
|
||||||
[app.util.dom :as dom]
|
[app.util.dom :as dom]
|
||||||
[cuerdas.core :as str]))
|
[cuerdas.core :as str]))
|
||||||
|
|
||||||
;; TODO: looks like first argument is not necessary.
|
(defn- text-corrected-transform
|
||||||
(defn update-transform [_node shapes modifiers]
|
"If we apply a scale directly to the texts it will show deformed so we need to create this
|
||||||
(doseq [{:keys [id type]} shapes]
|
correction matrix to \"undo\" the resize but keep the other transformations."
|
||||||
(let [shape-node (dom/get-element (str "shape-" id))
|
[{:keys [points transform transform-inverse]} current-transform modifiers]
|
||||||
|
|
||||||
;; When the shape is a frame we maybe need to move its thumbnail
|
(let [corner-pt (first points)
|
||||||
thumb-node (dom/get-element (str "thumbnail-" id))]
|
transform (or transform (gmt/matrix))
|
||||||
(when-let [node (cond
|
transform-inverse (or transform-inverse (gmt/matrix))
|
||||||
(and (some? shape-node) (= :frame type))
|
|
||||||
(.-parentNode shape-node)
|
|
||||||
|
|
||||||
(and (some? thumb-node) (= :frame type))
|
current-transform
|
||||||
(.-parentNode thumb-node)
|
(if (some? (:resize-vector modifiers))
|
||||||
|
(gmt/multiply
|
||||||
|
current-transform
|
||||||
|
transform
|
||||||
|
(gmt/scale-matrix (gpt/inverse (:resize-vector modifiers)) (gpt/transform corner-pt transform-inverse))
|
||||||
|
transform-inverse)
|
||||||
|
current-transform)
|
||||||
|
|
||||||
:else
|
current-transform
|
||||||
shape-node)]
|
(if (some? (:resize-vector-2 modifiers))
|
||||||
(dom/set-attribute node "transform" (str (:displacement modifiers)))))))
|
(gmt/multiply
|
||||||
|
current-transform
|
||||||
|
transform
|
||||||
|
(gmt/scale-matrix (gpt/inverse (:resize-vector-2 modifiers)) (gpt/transform corner-pt transform-inverse))
|
||||||
|
transform-inverse)
|
||||||
|
current-transform)]
|
||||||
|
current-transform))
|
||||||
|
|
||||||
;; TODO: looks like first argument is not necessary.
|
(defn get-nodes
|
||||||
(defn remove-transform [_node shapes]
|
"Retrieve the DOM nodes to apply the matrix transformation"
|
||||||
(doseq [{:keys [id type]} shapes]
|
[{:keys [id type masked-group?]}]
|
||||||
(when-let [node (dom/get-element (str "shape-" id))]
|
(let [shape-node (dom/get-element (str "shape-" id))
|
||||||
(let [node (if (= :frame type) (.-parentNode node) node)]
|
|
||||||
(dom/remove-attribute node "transform")))))
|
frame? (= :frame type)
|
||||||
|
group? (= :group type)
|
||||||
|
mask? (and group? masked-group?)
|
||||||
|
|
||||||
|
;; When the shape is a frame we maybe need to move its thumbnail
|
||||||
|
thumb-node (when frame? (dom/get-element (str "thumbnail-" id)))]
|
||||||
|
(cond
|
||||||
|
(some? thumb-node)
|
||||||
|
[(.-parentNode thumb-node)]
|
||||||
|
|
||||||
|
(and (some? shape-node) frame?)
|
||||||
|
[(dom/query shape-node ".frame-background")
|
||||||
|
(dom/query shape-node ".frame-clip")]
|
||||||
|
|
||||||
|
;; For groups we don't want to transform the whole group but only
|
||||||
|
;; its filters/masks
|
||||||
|
(and (some? shape-node) mask?)
|
||||||
|
[(dom/query shape-node ".mask-clip-path")
|
||||||
|
(dom/query shape-node ".mask-shape")]
|
||||||
|
|
||||||
|
group?
|
||||||
|
[]
|
||||||
|
|
||||||
|
:else
|
||||||
|
[shape-node])))
|
||||||
|
|
||||||
|
(defn update-transform [shapes transforms modifiers]
|
||||||
|
(doseq [{id :id :as shape} shapes]
|
||||||
|
(when-let [nodes (get-nodes shape)]
|
||||||
|
(let [transform (get transforms id)
|
||||||
|
modifiers (get-in modifiers [id :modifiers])
|
||||||
|
transform (case type
|
||||||
|
:text (text-corrected-transform shape transform modifiers)
|
||||||
|
transform)]
|
||||||
|
(doseq [node nodes]
|
||||||
|
(when (and (some? transform) (some? node))
|
||||||
|
(dom/set-attribute node "transform" (str transform))))))))
|
||||||
|
|
||||||
|
(defn remove-transform [shapes]
|
||||||
|
(doseq [shape shapes]
|
||||||
|
(when-let [nodes (get-nodes shape)]
|
||||||
|
(doseq [node nodes]
|
||||||
|
(when (some? node)
|
||||||
|
(dom/remove-attribute node "transform"))))))
|
||||||
|
|
||||||
(defn format-viewbox [vbox]
|
(defn format-viewbox [vbox]
|
||||||
(str/join " " [(+ (:x vbox 0) (:left-offset vbox 0))
|
(str/join " " [(+ (:x vbox 0) (:left-offset vbox 0))
|
||||||
|
|
|
@ -13,18 +13,22 @@
|
||||||
[error]
|
[error]
|
||||||
(js/console.error "Error on worker" error))
|
(js/console.error "Error on worker" error))
|
||||||
|
|
||||||
(defonce instance
|
(defonce instance (atom nil))
|
||||||
(when (not= *target* "nodejs")
|
|
||||||
(uw/init cfg/worker-uri on-error)))
|
(defn init!
|
||||||
|
[]
|
||||||
|
(reset!
|
||||||
|
instance
|
||||||
|
(uw/init cfg/worker-uri on-error)))
|
||||||
|
|
||||||
(defn ask!
|
(defn ask!
|
||||||
[message]
|
[message]
|
||||||
(uw/ask! instance message))
|
(when @instance (uw/ask! @instance message)))
|
||||||
|
|
||||||
(defn ask-buffered!
|
(defn ask-buffered!
|
||||||
[message]
|
[message]
|
||||||
(uw/ask-buffered! instance message))
|
(when @instance (uw/ask-buffered! @instance message)))
|
||||||
|
|
||||||
(defn ask-many!
|
(defn ask-many!
|
||||||
[message]
|
[message]
|
||||||
(uw/ask-many! instance message))
|
(when @instance (uw/ask-many! @instance message)))
|
||||||
|
|
|
@ -162,16 +162,30 @@
|
||||||
(->> (rx/take 1 observable)
|
(->> (rx/take 1 observable)
|
||||||
(rx/subs resolve reject)))))
|
(rx/subs resolve reject)))))
|
||||||
|
|
||||||
(defn fetch-data-uri [uri]
|
(defn fetch-data-uri
|
||||||
(c/with-cache {:key uri :max-age (dt/duration {:hours 4})}
|
([uri]
|
||||||
(->> (send! {:method :get
|
(fetch-data-uri uri false))
|
||||||
:uri uri
|
|
||||||
:response-type :blob
|
([uri throw-err?]
|
||||||
:omit-default-headers true})
|
(c/with-cache {:key uri :max-age (dt/duration {:hours 4})}
|
||||||
(rx/filter #(= 200 (:status %)))
|
(let [request-stream
|
||||||
(rx/map :body)
|
(send! {:method :get
|
||||||
(rx/mapcat wapi/read-file-as-data-url)
|
:uri uri
|
||||||
(rx/map #(hash-map uri %)))))
|
:response-type :blob
|
||||||
|
:omit-default-headers true})
|
||||||
|
|
||||||
|
request-stream
|
||||||
|
(if throw-err?
|
||||||
|
(rx/tap #(when-not (and (>= (:status %) 200) (< (:status %) 300))
|
||||||
|
;; HTTP ERRROR
|
||||||
|
(throw (js/Error. "Error fetching data uri" #js {:cause (clj->js %)})))
|
||||||
|
request-stream)
|
||||||
|
(rx/filter #(= 200 (:status %))
|
||||||
|
request-stream))]
|
||||||
|
(->> request-stream
|
||||||
|
(rx/map :body)
|
||||||
|
(rx/mapcat wapi/read-file-as-data-url)
|
||||||
|
(rx/map #(hash-map uri %)))))))
|
||||||
|
|
||||||
(defn fetch-text [url]
|
(defn fetch-text [url]
|
||||||
(c/with-cache {:key url :max-age (dt/duration {:hours 4})}
|
(c/with-cache {:key url :max-age (dt/duration {:hours 4})}
|
||||||
|
|
|
@ -105,3 +105,29 @@
|
||||||
:onRender on-render}
|
:onRender on-render}
|
||||||
children]
|
children]
|
||||||
children)))
|
children)))
|
||||||
|
|
||||||
|
(defn benchmark
|
||||||
|
[& {:keys [f warmup iterations name]
|
||||||
|
:or {iterations 10000}}]
|
||||||
|
(let [end-mark (str name ":end")]
|
||||||
|
(println "=> benchmarking:" name)
|
||||||
|
(println "--> warming up:" iterations)
|
||||||
|
(loop [i iterations]
|
||||||
|
(when (pos? i)
|
||||||
|
(f)
|
||||||
|
(recur (dec i))))
|
||||||
|
(println "--> benchmarking:" iterations)
|
||||||
|
(js/performance.mark name)
|
||||||
|
(loop [i iterations]
|
||||||
|
(when (pos? i)
|
||||||
|
(f)
|
||||||
|
(recur (dec i))))
|
||||||
|
(js/performance.measure end-mark name)
|
||||||
|
(let [[result] (js/performance.getEntriesByName end-mark)
|
||||||
|
duration (mth/precision (.-duration ^js result) 4)
|
||||||
|
avg (mth/precision (/ duration iterations) 4)]
|
||||||
|
(println "--> TOTAL:" (str duration "ms") "AVG:" (str avg "ms"))
|
||||||
|
(js/performance.clearMarks name)
|
||||||
|
(js/performance.clearMeasures end-mark)
|
||||||
|
#js {:duration duration
|
||||||
|
:avg avg})))
|
||||||
|
|
|
@ -19,6 +19,8 @@
|
||||||
[beicon.core :as rx]
|
[beicon.core :as rx]
|
||||||
[cuerdas.core :as str]))
|
[cuerdas.core :as str]))
|
||||||
|
|
||||||
|
(def ^:const current-version 2)
|
||||||
|
|
||||||
(defn create-manifest
|
(defn create-manifest
|
||||||
"Creates a manifest entry for the given files"
|
"Creates a manifest entry for the given files"
|
||||||
[team-id file-id export-type files]
|
[team-id file-id export-type files]
|
||||||
|
@ -41,6 +43,7 @@
|
||||||
:shared is-shared
|
:shared is-shared
|
||||||
:pages pages
|
:pages pages
|
||||||
:pagesIndex index
|
:pagesIndex index
|
||||||
|
:version current-version
|
||||||
:libraries (->> (:libraries file) (into #{}) (mapv str))
|
:libraries (->> (:libraries file) (into #{}) (mapv str))
|
||||||
:exportType (d/name export-type)
|
:exportType (d/name export-type)
|
||||||
:hasComponents (d/not-empty? (get-in file [:data :components]))
|
:hasComponents (d/not-empty? (get-in file [:data :components]))
|
||||||
|
|
|
@ -9,6 +9,8 @@
|
||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.file-builder :as fb]
|
[app.common.file-builder :as fb]
|
||||||
|
[app.common.geom.point :as gpt]
|
||||||
|
[app.common.geom.shapes.path :as gpa]
|
||||||
[app.common.logging :as log]
|
[app.common.logging :as log]
|
||||||
[app.common.pages :as cp]
|
[app.common.pages :as cp]
|
||||||
[app.common.text :as ct]
|
[app.common.text :as ct]
|
||||||
|
@ -229,6 +231,21 @@
|
||||||
(cond-> (= type :text)
|
(cond-> (= type :text)
|
||||||
(d/update-when :content resolve-text-content context)))))
|
(d/update-when :content resolve-text-content context)))))
|
||||||
|
|
||||||
|
(defn- translate-frame
|
||||||
|
[data type file]
|
||||||
|
(let [frame-id (:current-frame-id file)
|
||||||
|
frame (when (and (some? frame-id) (not= frame-id uuid/zero))
|
||||||
|
(fb/lookup-shape file frame-id))]
|
||||||
|
|
||||||
|
(if (some? frame)
|
||||||
|
(-> data
|
||||||
|
(d/update-when :x + (:x frame))
|
||||||
|
(d/update-when :y + (:y frame))
|
||||||
|
(cond-> (= :path type)
|
||||||
|
(update :content gpa/move-content (gpt/point (:x frame) (:y frame)))))
|
||||||
|
|
||||||
|
data)))
|
||||||
|
|
||||||
(defn process-import-node
|
(defn process-import-node
|
||||||
[context file node]
|
[context file node]
|
||||||
|
|
||||||
|
@ -250,7 +267,9 @@
|
||||||
data (-> (cip/parse-data type node)
|
data (-> (cip/parse-data type node)
|
||||||
(resolve-data-ids type context)
|
(resolve-data-ids type context)
|
||||||
(cond-> (some? old-id)
|
(cond-> (some? old-id)
|
||||||
(assoc :id (resolve old-id))))
|
(assoc :id (resolve old-id)))
|
||||||
|
(cond-> (< (:version context 1) 2)
|
||||||
|
(translate-frame type file)))
|
||||||
|
|
||||||
file (case type
|
file (case type
|
||||||
:frame (fb/add-artboard file data)
|
:frame (fb/add-artboard file data)
|
||||||
|
@ -463,6 +482,7 @@
|
||||||
(rx/flat-map
|
(rx/flat-map
|
||||||
(fn [context]
|
(fn [context]
|
||||||
(->> (create-file context)
|
(->> (create-file context)
|
||||||
|
(rx/tap #(.log js/console "create-file" (clj->js %)))
|
||||||
(rx/map #(vector % (first (get data (:file-id context)))))))))
|
(rx/map #(vector % (first (get data (:file-id context)))))))))
|
||||||
|
|
||||||
(->> (rx/from files)
|
(->> (rx/from files)
|
||||||
|
|
|
@ -5,10 +5,14 @@
|
||||||
;; Copyright (c) UXBOX Labs SL
|
;; Copyright (c) UXBOX Labs SL
|
||||||
|
|
||||||
(ns debug
|
(ns debug
|
||||||
|
#_(:import [goog.math AffineTransform])
|
||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
|
#_[app.common.geom.matrix :as gmt]
|
||||||
[app.common.math :as mth]
|
[app.common.math :as mth]
|
||||||
[app.common.pages :as cp]
|
[app.common.pages :as cp]
|
||||||
|
#_[app.common.perf :as perf]
|
||||||
|
[app.common.uuid :as uuid]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
[app.util.object :as obj]
|
[app.util.object :as obj]
|
||||||
[app.util.timers :as timers]
|
[app.util.timers :as timers]
|
||||||
|
@ -17,7 +21,32 @@
|
||||||
[cuerdas.core :as str]
|
[cuerdas.core :as str]
|
||||||
[potok.core :as ptk]))
|
[potok.core :as ptk]))
|
||||||
|
|
||||||
(def debug-options #{:bounding-boxes :group :events :rotation-handler :resize-handler :selection-center :export :import #_:simple-selection})
|
(def debug-options
|
||||||
|
#{;; Displays the bounding box for the shapes
|
||||||
|
:bounding-boxes
|
||||||
|
|
||||||
|
;; Displays an overlay over the groups
|
||||||
|
:group
|
||||||
|
|
||||||
|
;; Displays in the console log the events through the application
|
||||||
|
:events
|
||||||
|
|
||||||
|
;; Display the boxes that represent the rotation handlers
|
||||||
|
:rotation-handler
|
||||||
|
|
||||||
|
;; Display the boxes that represent the resize handlers
|
||||||
|
:resize-handler
|
||||||
|
|
||||||
|
;; Displays the center of a selection
|
||||||
|
:selection-center
|
||||||
|
|
||||||
|
;; When active the single selection will not take into account previous transformations
|
||||||
|
;; this is useful to debug transforms
|
||||||
|
:simple-selection
|
||||||
|
|
||||||
|
;; When active the thumbnails will be displayed with a sepia filter
|
||||||
|
:thumbnails
|
||||||
|
})
|
||||||
|
|
||||||
;; These events are excluded when we activate the :events flag
|
;; These events are excluded when we activate the :events flag
|
||||||
(def debug-exclude-events
|
(def debug-exclude-events
|
||||||
|
@ -106,27 +135,40 @@
|
||||||
(do-thing)))
|
(do-thing)))
|
||||||
|
|
||||||
(defn ^:export dump-state []
|
(defn ^:export dump-state []
|
||||||
(logjs "state" @st/state))
|
(logjs "state" @st/state)
|
||||||
|
nil)
|
||||||
|
|
||||||
(defn ^:export dump-buffer []
|
(defn ^:export dump-buffer []
|
||||||
(logjs "state" @st/last-events))
|
(logjs "state" @st/last-events)
|
||||||
|
nil)
|
||||||
|
|
||||||
(defn ^:export get-state [str-path]
|
(defn ^:export get-state [str-path]
|
||||||
(let [path (->> (str/split str-path " ")
|
(let [path (->> (str/split str-path " ")
|
||||||
(map d/read-string))]
|
(map d/read-string))]
|
||||||
(clj->js (get-in @st/state path))))
|
(clj->js (get-in @st/state path)))
|
||||||
|
nil)
|
||||||
|
|
||||||
(defn ^:export dump-objects []
|
(defn ^:export dump-objects []
|
||||||
(let [page-id (get @st/state :current-page-id)]
|
(let [page-id (get @st/state :current-page-id)
|
||||||
(logjs "state" (get-in @st/state [:workspace-data :pages-index page-id :objects]))))
|
objects (get-in @st/state [:workspace-data :pages-index page-id :objects])]
|
||||||
|
(logjs "objects" objects)
|
||||||
|
nil))
|
||||||
|
|
||||||
(defn ^:export dump-object [name]
|
(defn ^:export dump-object [name]
|
||||||
(let [page-id (get @st/state :current-page-id)
|
(let [page-id (get @st/state :current-page-id)
|
||||||
objects (get-in @st/state [:workspace-data :pages-index page-id :objects])
|
objects (get-in @st/state [:workspace-data :pages-index page-id :objects])
|
||||||
target (or (d/seek (fn [[_ shape]] (= name (:name shape))) objects)
|
result (or (d/seek (fn [[_ shape]] (= name (:name shape))) objects)
|
||||||
(get objects (uuid name)))]
|
(get objects (uuid/uuid name)))]
|
||||||
(->> target
|
(logjs name result)
|
||||||
(logjs "state"))))
|
nil))
|
||||||
|
|
||||||
|
(defn ^:export dump-selected []
|
||||||
|
(let [page-id (get @st/state :current-page-id)
|
||||||
|
objects (get-in @st/state [:workspace-data :pages-index page-id :objects])
|
||||||
|
selected (get-in @st/state [:workspace-local :selected])
|
||||||
|
result (->> selected (map (d/getf objects)))]
|
||||||
|
(logjs "selected" result)
|
||||||
|
nil))
|
||||||
|
|
||||||
(defn ^:export dump-tree
|
(defn ^:export dump-tree
|
||||||
([] (dump-tree false false))
|
([] (dump-tree false false))
|
||||||
|
@ -209,3 +251,41 @@
|
||||||
(not (debug-exclude-events (ptk/type s))))))
|
(not (debug-exclude-events (ptk/type s))))))
|
||||||
(rx/subs #(println "[stream]: " (ptk/repr-event %))))))
|
(rx/subs #(println "[stream]: " (ptk/repr-event %))))))
|
||||||
|
|
||||||
|
#_(defn ^:export bench-matrix
|
||||||
|
[]
|
||||||
|
(let [iterations 1000000
|
||||||
|
|
||||||
|
good (gmt/multiply (gmt/matrix 1 2 3 4 5 6)
|
||||||
|
(gmt/matrix 1 2 3 4 5 6))
|
||||||
|
|
||||||
|
k1 (perf/start)
|
||||||
|
_ (dotimes [_ iterations]
|
||||||
|
(when-not (= good (gmt/-old-multiply (gmt/matrix 1 2 3 4 5 6)
|
||||||
|
(gmt/matrix 1 2 3 4 5 6)))
|
||||||
|
(throw "ERROR")))
|
||||||
|
m1 (perf/measure k1)
|
||||||
|
|
||||||
|
k2 (perf/start)
|
||||||
|
_ (dotimes [_ iterations]
|
||||||
|
(when-not (= good (gmt/multiply (gmt/matrix 1 2 3 4 5 6)
|
||||||
|
(gmt/matrix 1 2 3 4 5 6)))
|
||||||
|
(throw "ERROR")))
|
||||||
|
m2 (perf/measure k2)
|
||||||
|
|
||||||
|
k3 (perf/start)
|
||||||
|
_ (dotimes [_ iterations]
|
||||||
|
(let [res (.concatenate (AffineTransform. 1 2 3 4 5 6)
|
||||||
|
(AffineTransform. 1 2 3 4 5 6))
|
||||||
|
res (gmt/matrix (.-m00_ res) (.-m10_ res) (.-m01_ res) (.-m11_ res) (.-m02_ res) (.-m12_ res))]
|
||||||
|
|
||||||
|
(when-not (= good res)
|
||||||
|
(throw "ERROR"))))
|
||||||
|
m3 (perf/measure k3)
|
||||||
|
]
|
||||||
|
|
||||||
|
(println "Clojure matrix. Total: " m1 " (" (/ m1 iterations) ")")
|
||||||
|
(println "Clojure matrix (NEW). Total: " m2 " (" (/ m2 iterations) ")")
|
||||||
|
(println "Affine transform (with new). Total: " m3 " (" (/ m3 iterations) ")")))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue