mirror of
https://github.com/penpot/penpot.git
synced 2025-07-09 23:27:18 +02:00
✨ Rework multi edit of measures values
This commit is contained in:
parent
180c355340
commit
95a2da5ebc
8 changed files with 100 additions and 36 deletions
|
@ -5,7 +5,49 @@
|
||||||
;; Copyright (c) UXBOX Labs SL
|
;; Copyright (c) UXBOX Labs SL
|
||||||
|
|
||||||
(ns app.common.attrs
|
(ns app.common.attrs
|
||||||
(:refer-clojure :exclude [merge]))
|
(:require
|
||||||
|
[app.common.geom.shapes.transforms :as gst]
|
||||||
|
[app.common.math :as mth]))
|
||||||
|
|
||||||
|
(defn- get-attr
|
||||||
|
[obj attr]
|
||||||
|
(if (= (get obj attr) :multiple)
|
||||||
|
:multiple
|
||||||
|
(cond
|
||||||
|
;; For rotated or stretched shapes, the origin point we show in the menu
|
||||||
|
;; is not the (:x :y) shape attribute, but the top left coordinate of the
|
||||||
|
;; wrapping recangle (see measures.cljs). As the :points attribute cannot
|
||||||
|
;; be merged for several objects, we calculate the origin point in two fake
|
||||||
|
;; attributes to be used in the measures menu.
|
||||||
|
(#{:ox :oy} attr)
|
||||||
|
(if-let [value (get obj attr)]
|
||||||
|
value
|
||||||
|
(if-let [points (:points obj)]
|
||||||
|
(if (not= points :multiple)
|
||||||
|
(let [rect (gst/selection-rect [obj])]
|
||||||
|
(if (= attr :ox) (:x rect) (:y rect)))
|
||||||
|
:multiple)
|
||||||
|
(get obj attr ::unset)))
|
||||||
|
|
||||||
|
;; Not all shapes have width and height (e.g. paths), so we extract
|
||||||
|
;; them from the :selrect attribute.
|
||||||
|
(#{:width :height} attr)
|
||||||
|
(if-let [value (get obj attr)]
|
||||||
|
value
|
||||||
|
(if-let [selrect (:selrect obj)]
|
||||||
|
(if (not= selrect :multiple)
|
||||||
|
(get (:selrect obj) attr)
|
||||||
|
:multiple)
|
||||||
|
(get obj attr ::unset)))
|
||||||
|
|
||||||
|
:else
|
||||||
|
(get obj attr ::unset))))
|
||||||
|
|
||||||
|
(defn- default-equal
|
||||||
|
[val1 val2]
|
||||||
|
(if (and (number? val1) (number? val2))
|
||||||
|
(mth/close? val1 val2)
|
||||||
|
(= val1 val2)))
|
||||||
|
|
||||||
;; Extract some attributes of a list of shapes.
|
;; Extract some attributes of a list of shapes.
|
||||||
;; For each attribute, if the value is the same in all shapes,
|
;; For each attribute, if the value is the same in all shapes,
|
||||||
|
@ -36,13 +78,11 @@
|
||||||
;; :rx nil
|
;; :rx nil
|
||||||
;; :ry nil}
|
;; :ry nil}
|
||||||
;;
|
;;
|
||||||
|
|
||||||
(defn get-attrs-multi
|
(defn get-attrs-multi
|
||||||
([objs attrs]
|
([objs attrs]
|
||||||
(get-attrs-multi objs attrs = identity))
|
(get-attrs-multi objs attrs default-equal identity))
|
||||||
|
|
||||||
([objs attrs eqfn sel]
|
([objs attrs eqfn sel]
|
||||||
|
|
||||||
(loop [attr (first attrs)
|
(loop [attr (first attrs)
|
||||||
attrs (rest attrs)
|
attrs (rest attrs)
|
||||||
result (transient {})]
|
result (transient {})]
|
||||||
|
@ -50,34 +90,25 @@
|
||||||
(let [value
|
(let [value
|
||||||
(loop [curr (first objs)
|
(loop [curr (first objs)
|
||||||
objs (rest objs)
|
objs (rest objs)
|
||||||
value ::undefined]
|
value ::unset]
|
||||||
|
|
||||||
(if (and curr (not= value :multiple))
|
(if (and curr (not= value :multiple))
|
||||||
;;
|
(let [new-val (get-attr curr attr)
|
||||||
(let [new-val (get curr attr ::undefined)
|
|
||||||
value (cond
|
value (cond
|
||||||
(= new-val ::undefined) value
|
(= new-val ::unset) value
|
||||||
(= new-val :multiple) :multiple
|
(= new-val :multiple) :multiple
|
||||||
(= value ::undefined) (sel new-val)
|
(= value ::unset) (sel new-val)
|
||||||
(eqfn new-val value) value
|
(eqfn new-val value) value
|
||||||
:else :multiple)]
|
:else :multiple)]
|
||||||
(recur (first objs) (rest objs) value))
|
(recur (first objs) (rest objs) value))
|
||||||
;;
|
|
||||||
value))]
|
value))]
|
||||||
|
|
||||||
(recur (first attrs)
|
(recur (first attrs)
|
||||||
(rest attrs)
|
(rest attrs)
|
||||||
(cond-> result
|
(cond-> result
|
||||||
(not= value ::undefined)
|
(not= value ::unset)
|
||||||
(assoc! attr value))))
|
(assoc! attr value))))
|
||||||
|
|
||||||
(persistent! result)))))
|
(persistent! result)))))
|
||||||
|
|
||||||
(defn merge
|
|
||||||
"Attrs specific merge function."
|
|
||||||
[obj attrs]
|
|
||||||
(reduce-kv (fn [obj k v]
|
|
||||||
(if (nil? v)
|
|
||||||
(dissoc obj k)
|
|
||||||
(assoc obj k v)))
|
|
||||||
obj
|
|
||||||
attrs))
|
|
||||||
|
|
|
@ -336,6 +336,16 @@
|
||||||
[& maps]
|
[& maps]
|
||||||
(reduce conj (or (first maps) {}) (rest maps)))
|
(reduce conj (or (first maps) {}) (rest maps)))
|
||||||
|
|
||||||
|
(defn txt-merge
|
||||||
|
"Text attrs specific merge function."
|
||||||
|
[obj attrs]
|
||||||
|
(reduce-kv (fn [obj k v]
|
||||||
|
(if (nil? v)
|
||||||
|
(dissoc obj k)
|
||||||
|
(assoc obj k v)))
|
||||||
|
obj
|
||||||
|
attrs))
|
||||||
|
|
||||||
(defn distinct-xf
|
(defn distinct-xf
|
||||||
[f]
|
[f]
|
||||||
(fn [rf]
|
(fn [rf]
|
||||||
|
|
|
@ -28,4 +28,3 @@
|
||||||
[shape]
|
[shape]
|
||||||
(gpr/points->selrect (position-data-points shape)))
|
(gpr/points->selrect (position-data-points shape)))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
|
|
||||||
(ns app.common.geom.shapes.transforms
|
(ns app.common.geom.shapes.transforms
|
||||||
(:require
|
(:require
|
||||||
[app.common.attrs :as attrs]
|
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.geom.matrix :as gmt]
|
[app.common.geom.matrix :as gmt]
|
||||||
[app.common.geom.point :as gpt]
|
[app.common.geom.point :as gpt]
|
||||||
|
@ -533,7 +532,7 @@
|
||||||
(* (get-in modifiers [:resize-vector :x] 1))
|
(* (get-in modifiers [:resize-vector :x] 1))
|
||||||
(* (get-in modifiers [:resize-vector-2 :x] 1))
|
(* (get-in modifiers [:resize-vector-2 :x] 1))
|
||||||
(str))]
|
(str))]
|
||||||
(attrs/merge attrs {:font-size font-size})))]
|
(d/txt-merge attrs {:font-size font-size})))]
|
||||||
(update shape :content #(txt/transform-nodes
|
(update shape :content #(txt/transform-nodes
|
||||||
txt/is-text-node?
|
txt/is-text-node?
|
||||||
merge-attrs
|
merge-attrs
|
||||||
|
|
|
@ -79,6 +79,7 @@
|
||||||
:rx :ry
|
:rx :ry
|
||||||
:r1 :r2 :r3 :r4
|
:r1 :r2 :r3 :r4
|
||||||
:selrect
|
:selrect
|
||||||
|
:points
|
||||||
|
|
||||||
:opacity
|
:opacity
|
||||||
:blend-mode
|
:blend-mode
|
||||||
|
@ -112,6 +113,7 @@
|
||||||
:x :y
|
:x :y
|
||||||
:rotation
|
:rotation
|
||||||
:selrect
|
:selrect
|
||||||
|
:points
|
||||||
|
|
||||||
:constraints-h
|
:constraints-h
|
||||||
:constraints-v
|
:constraints-v
|
||||||
|
@ -137,6 +139,7 @@
|
||||||
:rx :ry
|
:rx :ry
|
||||||
:r1 :r2 :r3 :r4
|
:r1 :r2 :r3 :r4
|
||||||
:selrect
|
:selrect
|
||||||
|
:points
|
||||||
|
|
||||||
:constraints-h
|
:constraints-h
|
||||||
:constraints-v
|
:constraints-v
|
||||||
|
@ -179,6 +182,7 @@
|
||||||
:x :y
|
:x :y
|
||||||
:rotation
|
:rotation
|
||||||
:selrect
|
:selrect
|
||||||
|
:points
|
||||||
|
|
||||||
:constraints-h
|
:constraints-h
|
||||||
:constraints-v
|
:constraints-v
|
||||||
|
@ -221,6 +225,7 @@
|
||||||
:x :y
|
:x :y
|
||||||
:rotation
|
:rotation
|
||||||
:selrect
|
:selrect
|
||||||
|
:points
|
||||||
|
|
||||||
:constraints-h
|
:constraints-h
|
||||||
:constraints-v
|
:constraints-v
|
||||||
|
@ -263,6 +268,7 @@
|
||||||
:x :y
|
:x :y
|
||||||
:rotation
|
:rotation
|
||||||
:selrect
|
:selrect
|
||||||
|
:points
|
||||||
|
|
||||||
:constraints-h
|
:constraints-h
|
||||||
:constraints-v
|
:constraints-v
|
||||||
|
@ -330,6 +336,7 @@
|
||||||
:rx :ry
|
:rx :ry
|
||||||
:r1 :r2 :r3 :r4
|
:r1 :r2 :r3 :r4
|
||||||
:selrect
|
:selrect
|
||||||
|
:points
|
||||||
|
|
||||||
:constraints-h
|
:constraints-h
|
||||||
:constraints-v
|
:constraints-v
|
||||||
|
@ -355,6 +362,7 @@
|
||||||
:rx :ry
|
:rx :ry
|
||||||
:r1 :r2 :r3 :r4
|
:r1 :r2 :r3 :r4
|
||||||
:selrect
|
:selrect
|
||||||
|
:points
|
||||||
|
|
||||||
:constraints-h
|
:constraints-h
|
||||||
:constraints-v
|
:constraints-v
|
||||||
|
@ -399,6 +407,7 @@
|
||||||
:rx :ry
|
:rx :ry
|
||||||
:r1 :r2 :r3 :r4
|
:r1 :r2 :r3 :r4
|
||||||
:selrect
|
:selrect
|
||||||
|
:points
|
||||||
|
|
||||||
:constraints-h
|
:constraints-h
|
||||||
:constraints-v
|
:constraints-v
|
||||||
|
|
|
@ -187,8 +187,8 @@
|
||||||
update-fn
|
update-fn
|
||||||
(fn [shape]
|
(fn [shape]
|
||||||
(if (some? (:content shape))
|
(if (some? (:content shape))
|
||||||
(update-text-content shape txt/is-root-node? attrs/merge attrs)
|
(update-text-content shape txt/is-root-node? d/txt-merge attrs)
|
||||||
(assoc shape :content (attrs/merge {:type "root"} attrs))))
|
(assoc shape :content (d/txt-merge {:type "root"} attrs))))
|
||||||
|
|
||||||
shape-ids (cond (cph/text-shape? shape) [id]
|
shape-ids (cond (cph/text-shape? shape) [id]
|
||||||
(cph/group-shape? shape) (cph/get-children-ids objects id))]
|
(cph/group-shape? shape) (cph/get-children-ids objects id))]
|
||||||
|
@ -240,7 +240,7 @@
|
||||||
shape-ids (cond
|
shape-ids (cond
|
||||||
(cph/text-shape? shape) [id]
|
(cph/text-shape? shape) [id]
|
||||||
(cph/group-shape? shape) (cph/get-children-ids objects id))]
|
(cph/group-shape? shape) (cph/get-children-ids objects id))]
|
||||||
(rx/of (dch/update-shapes shape-ids #(update-text-content % update-node? attrs/merge attrs))))))))
|
(rx/of (dch/update-shapes shape-ids #(update-text-content % update-node? d/txt-merge attrs))))))))
|
||||||
|
|
||||||
(defn migrate-node
|
(defn migrate-node
|
||||||
[node]
|
[node]
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
|
|
||||||
(ns app.main.ui.workspace.shapes.text.viewport-texts
|
(ns app.main.ui.workspace.shapes.text.viewport-texts
|
||||||
(:require
|
(:require
|
||||||
[app.common.attrs :as attrs]
|
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.data.macros :as dm]
|
[app.common.data.macros :as dm]
|
||||||
[app.common.geom.shapes :as gsh]
|
[app.common.geom.shapes :as gsh]
|
||||||
|
@ -50,7 +49,7 @@
|
||||||
|
|
||||||
(cond-> shape
|
(cond-> shape
|
||||||
(and (some? shape) (some? editor-content))
|
(and (some? shape) (some? editor-content))
|
||||||
(assoc :content (attrs/merge content editor-content)))))
|
(assoc :content (d/txt-merge content editor-content)))))
|
||||||
|
|
||||||
(defn- update-text-shape
|
(defn- update-text-shape
|
||||||
[{:keys [grow-type id]} node]
|
[{:keys [grow-type id]} node]
|
||||||
|
|
|
@ -25,10 +25,12 @@
|
||||||
[:proportion-lock
|
[:proportion-lock
|
||||||
:width :height
|
:width :height
|
||||||
:x :y
|
:x :y
|
||||||
|
:ox :oy
|
||||||
:rotation
|
:rotation
|
||||||
:rx :ry
|
:rx :ry
|
||||||
:r1 :r2 :r3 :r4
|
:r1 :r2 :r3 :r4
|
||||||
:selrect])
|
:selrect
|
||||||
|
:points])
|
||||||
|
|
||||||
(def ^:private type->options
|
(def ^:private type->options
|
||||||
{:bool #{:size :position :rotation}
|
{:bool #{:size :position :rotation}
|
||||||
|
@ -58,20 +60,36 @@
|
||||||
[shape])
|
[shape])
|
||||||
frames (map #(deref (refs/object-by-id (:frame-id %))) old-shapes)
|
frames (map #(deref (refs/object-by-id (:frame-id %))) old-shapes)
|
||||||
|
|
||||||
|
;; To show interactively the measures while the user is manipulating
|
||||||
|
;; the shape with the mouse, generate a copy of the shapes applying
|
||||||
|
;; the transient tranformations.
|
||||||
shapes (as-> old-shapes $
|
shapes (as-> old-shapes $
|
||||||
(map gsh/transform-shape $)
|
(map gsh/transform-shape $)
|
||||||
(map gsh/translate-to-frame $ frames))
|
(map gsh/translate-to-frame $ frames))
|
||||||
|
|
||||||
values (let [{:keys [x y]} (-> shapes first :points gsh/points->selrect)]
|
;; For rotated or stretched shapes, the origin point we show in the menu
|
||||||
|
;; is not the (:x :y) shape attribute, but the top left coordinate of the
|
||||||
|
;; wrapping rectangle.
|
||||||
|
values (let [{:keys [x y]} (gsh/selection-rect [(first shapes)])]
|
||||||
(cond-> values
|
(cond-> values
|
||||||
(not= (:x values) :multiple) (assoc :x x)
|
(not= (:x values) :multiple) (assoc :x x)
|
||||||
(not= (:y values) :multiple) (assoc :y y)))
|
(not= (:y values) :multiple) (assoc :y y)
|
||||||
|
;; In case of multiple selection, the origin point has been already
|
||||||
|
;; calculated and given in the fake :ox and :oy attributes. See
|
||||||
|
;; common/src/app/common/attrs.cljc
|
||||||
|
(some? (:ox values)) (assoc :x (:ox values))
|
||||||
|
(some? (:oy values)) (assoc :y (:oy values))))
|
||||||
|
|
||||||
|
;; For :height and :width we take those in the :selrect attribute, because
|
||||||
|
;; not all shapes have an own :width and :height (e. g. paths). Here the
|
||||||
|
;; rotation is ignored (selrect always has the original size excluding
|
||||||
|
;; transforms).
|
||||||
values (let [{:keys [width height]} (-> shapes first :selrect)]
|
values (let [{:keys [width height]} (-> shapes first :selrect)]
|
||||||
(cond-> values
|
(cond-> values
|
||||||
(not= (:width values) :multiple) (assoc :width width)
|
(not= (:width values) :multiple) (assoc :width width)
|
||||||
(not= (:height values) :multiple) (assoc :height height)))
|
(not= (:height values) :multiple) (assoc :height height)))
|
||||||
|
|
||||||
|
;; The :rotation, however, does use the transforms.
|
||||||
values (let [{:keys [rotation] :or {rotation 0}} (-> shapes first)]
|
values (let [{:keys [rotation] :or {rotation 0}} (-> shapes first)]
|
||||||
(cond-> values
|
(cond-> values
|
||||||
(not= (:rotation values) :multiple) (assoc :rotation rotation)))
|
(not= (:rotation values) :multiple) (assoc :rotation rotation)))
|
||||||
|
@ -85,7 +103,6 @@
|
||||||
radius-multi? (mf/use-state nil)
|
radius-multi? (mf/use-state nil)
|
||||||
radius-input-ref (mf/use-ref nil)
|
radius-input-ref (mf/use-ref nil)
|
||||||
|
|
||||||
|
|
||||||
on-preset-selected
|
on-preset-selected
|
||||||
(fn [width height]
|
(fn [width height]
|
||||||
(st/emit! (udw/update-dimensions ids :width width)
|
(st/emit! (udw/update-dimensions ids :width width)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue