mirror of
https://github.com/penpot/penpot.git
synced 2025-07-19 07:37:20 +02:00
🎉 Add resize constraints to shapes
This commit is contained in:
parent
55b0f6e950
commit
092a973f9a
9 changed files with 602 additions and 43 deletions
|
@ -151,8 +151,7 @@
|
|||
:resize-origin origin
|
||||
:resize-transform shape-transform
|
||||
:resize-scale-text scale-text
|
||||
:resize-transform-inverse shape-transform-inverse}
|
||||
false))))
|
||||
:resize-transform-inverse shape-transform-inverse}))))
|
||||
|
||||
;; Unifies the instantaneous proportion lock modifier
|
||||
;; activated by Shift key and the shapes own proportion
|
||||
|
@ -426,10 +425,34 @@
|
|||
|
||||
;; -- Apply modifiers
|
||||
|
||||
(defn- set-modifiers-recursive
|
||||
[modif-tree objects shape modifiers]
|
||||
(let [children (->> (get shape :shapes [])
|
||||
(map #(get objects %)))
|
||||
|
||||
transformed-shape (when (seq children) ; <- don't calculate it if not needed
|
||||
(gsh/transform-shape
|
||||
(assoc shape :modifiers (select-keys modifiers
|
||||
[:resize-origin
|
||||
:resize-vector]))))
|
||||
|
||||
set-child (fn [modif-tree child]
|
||||
(let [child-modifiers (gsh/calc-child-modifiers shape
|
||||
transformed-shape
|
||||
child
|
||||
modifiers)]
|
||||
(set-modifiers-recursive modif-tree
|
||||
objects
|
||||
child
|
||||
child-modifiers)))]
|
||||
|
||||
(reduce set-child
|
||||
(update-in modif-tree [(:id shape) :modifiers] #(merge % modifiers))
|
||||
children)))
|
||||
|
||||
(defn set-modifiers
|
||||
([ids] (set-modifiers ids nil true))
|
||||
([ids modifiers] (set-modifiers ids modifiers true))
|
||||
([ids modifiers recurse-frames?]
|
||||
([ids] (set-modifiers ids nil))
|
||||
([ids modifiers]
|
||||
(us/verify (s/coll-of uuid?) ids)
|
||||
(ptk/reify ::set-modifiers
|
||||
ptk/UpdateEvent
|
||||
|
@ -438,25 +461,16 @@
|
|||
page-id (:current-page-id state)
|
||||
objects (wsh/lookup-page-objects state page-id)
|
||||
|
||||
ids (->> ids (into #{} (remove #(get-in objects [% :blocked] false))))
|
||||
|
||||
not-frame-id?
|
||||
(fn [shape-id]
|
||||
(let [shape (get objects shape-id)]
|
||||
(or recurse-frames? (not (= :frame (:type shape))))))
|
||||
|
||||
;; For each shape updates the modifiers given as arguments
|
||||
update-shape
|
||||
(fn [objects shape-id]
|
||||
(update-in objects [shape-id :modifiers] #(merge % modifiers)))
|
||||
|
||||
;; ID's + Children but remove frame children if the flag is set to false
|
||||
ids-with-children (concat ids (mapcat #(cp/get-children % objects)
|
||||
(filter not-frame-id? ids)))]
|
||||
|
||||
(update state :workspace-modifiers
|
||||
#(reduce update-shape % ids-with-children)))))))
|
||||
ids (->> ids (into #{} (remove #(get-in objects [% :blocked] false))))]
|
||||
|
||||
(reduce (fn [state id]
|
||||
(update state :workspace-modifiers
|
||||
#(set-modifiers-recursive %
|
||||
objects
|
||||
(get objects id)
|
||||
modifiers)))
|
||||
state
|
||||
ids))))))
|
||||
|
||||
;; Set-rotation is custom because applies different modifiers to each
|
||||
;; shape adjusting their position.
|
||||
|
@ -567,10 +581,7 @@
|
|||
(fn [objects shape-id]
|
||||
(let [shape (get objects shape-id)
|
||||
modifier (gsh/resize-modifiers shape attr value)]
|
||||
(-> objects
|
||||
(assoc-in [shape-id :modifiers] modifier)
|
||||
(cond-> (not (= :frame (:type shape)))
|
||||
(update-children (cp/get-children shape-id objects) modifier)))))]
|
||||
(set-modifiers-recursive objects objects shape modifier)))]
|
||||
|
||||
(d/update-in-when
|
||||
state
|
||||
|
@ -597,8 +608,7 @@
|
|||
(rx/of (set-modifiers selected
|
||||
{:resize-vector (gpt/point -1.0 1.0)
|
||||
:resize-origin origin
|
||||
:displacement (gmt/translate-matrix (gpt/point (- (:width selrect)) 0))}
|
||||
false)
|
||||
:displacement (gmt/translate-matrix (gpt/point (- (:width selrect)) 0))})
|
||||
(apply-modifiers selected))))))
|
||||
|
||||
(defn flip-vertical-selected []
|
||||
|
@ -614,8 +624,7 @@
|
|||
(rx/of (set-modifiers selected
|
||||
{:resize-vector (gpt/point 1.0 -1.0)
|
||||
:resize-origin origin
|
||||
:displacement (gmt/translate-matrix (gpt/point 0 (- (:height selrect))))}
|
||||
false)
|
||||
:displacement (gmt/translate-matrix (gpt/point 0 (- (:height selrect))))})
|
||||
(apply-modifiers selected))))))
|
||||
|
||||
(defn start-local-displacement [point]
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
(ns app.main.ui.workspace.sidebar.options.menus.measures
|
||||
(:require
|
||||
[cuerdas.core :as str]
|
||||
[rumext.alpha :as mf]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.main.store :as st]
|
||||
|
@ -15,12 +16,14 @@
|
|||
[app.util.data :refer [classnames]]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.pages.spec :as spec]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.workspace :as udw]
|
||||
[app.main.data.workspace.common :as dwc]
|
||||
[app.main.data.workspace.changes :as dch]
|
||||
[app.main.ui.components.numeric-input :refer [numeric-input]]
|
||||
[app.common.math :as math]
|
||||
[app.util.i18n :refer [t] :as i18n]))
|
||||
[app.util.i18n :refer [tr] :as i18n]))
|
||||
|
||||
(def measure-attrs [:proportion-lock
|
||||
:width :height
|
||||
|
@ -28,7 +31,12 @@
|
|||
:rotation
|
||||
:rx :ry
|
||||
:r1 :r2 :r3 :r4
|
||||
:selrect])
|
||||
:selrect
|
||||
:constraints-h
|
||||
:constraints-v
|
||||
:fixed-scroll
|
||||
:parent-id
|
||||
:frame-id])
|
||||
|
||||
(defn- attr->string [attr values]
|
||||
(let [value (attr values)]
|
||||
|
@ -42,7 +50,6 @@
|
|||
(mf/defc measures-menu
|
||||
[{:keys [options ids ids-with-children values] :as props}]
|
||||
(let [options (or options #{:size :position :rotation :radius})
|
||||
locale (i18n/use-locale)
|
||||
|
||||
ids-with-children (or ids-with-children ids)
|
||||
|
||||
|
@ -65,6 +72,13 @@
|
|||
|
||||
proportion-lock (:proportion-lock values)
|
||||
|
||||
in-frame? (not= (:parent-id values) uuid/zero)
|
||||
first-level? (and in-frame?
|
||||
(= (:parent-id values) (:frame-id values)))
|
||||
|
||||
constraints-h (get values :constraints-h (spec/default-constraints-h values))
|
||||
constraints-v (get values :constraints-v (spec/default-constraints-v values))
|
||||
|
||||
on-size-change
|
||||
(mf/use-callback
|
||||
(mf/deps ids)
|
||||
|
@ -161,15 +175,75 @@
|
|||
on-radius-r2-change #(on-radius-4-change % :r2)
|
||||
on-radius-r3-change #(on-radius-4-change % :r3)
|
||||
on-radius-r4-change #(on-radius-4-change % :r4)
|
||||
select-all #(-> % (dom/get-target) (.select))]
|
||||
|
||||
select-all #(-> % (dom/get-target) (.select))
|
||||
|
||||
on-constraint-button-clicked
|
||||
(mf/use-callback
|
||||
(mf/deps [ids values])
|
||||
(fn [button]
|
||||
(fn [event]
|
||||
(let [constraints-h (get values :constraints-h :scale)
|
||||
constraints-v (get values :constraints-v :scale)
|
||||
|
||||
[constraint new-value]
|
||||
(case button
|
||||
:top (case constraints-v
|
||||
:top [:constraints-v :scale]
|
||||
:topbottom [:constraints-v :bottom]
|
||||
:bottom [:constraints-v :topbottom]
|
||||
[:constraints-v :top])
|
||||
:bottom (case constraints-v
|
||||
:bottom [:constraints-v :scale]
|
||||
:topbottom [:constraints-v :top]
|
||||
:top [:constraints-v :topbottom]
|
||||
[:constraints-v :bottom])
|
||||
:left (case constraints-h
|
||||
:left [:constraints-h :scale]
|
||||
:leftright [:constraints-h :right]
|
||||
:right [:constraints-h :leftright]
|
||||
[:constraints-h :left])
|
||||
:right (case constraints-h
|
||||
:right [:constraints-h :scale]
|
||||
:leftright [:constraints-h :left]
|
||||
:left [:constraints-h :leftright]
|
||||
[:constraints-h :right])
|
||||
:centerv (case constraints-v
|
||||
:center [:constraints-v :scale]
|
||||
[:constraints-v :center])
|
||||
:centerh (case constraints-h
|
||||
:center [:constraints-h :scale]
|
||||
[:constraints-h :center]))]
|
||||
(st/emit! (dch/update-shapes
|
||||
ids
|
||||
#(assoc % constraint new-value)))))))
|
||||
|
||||
on-constraint-select-changed
|
||||
(mf/use-callback
|
||||
(mf/deps [ids values])
|
||||
(fn [constraint]
|
||||
(fn [event]
|
||||
(let [value (-> (dom/get-target-val event) (keyword))]
|
||||
(when-not (str/empty? value)
|
||||
(st/emit! (dch/update-shapes
|
||||
ids
|
||||
#(assoc % constraint value))))))))
|
||||
|
||||
on-fixed-scroll-clicked
|
||||
(mf/use-callback
|
||||
(mf/deps [ids values])
|
||||
(fn [event]
|
||||
(st/emit! (dch/update-shapes
|
||||
ids
|
||||
#(update % :fixed-scroll not)))))]
|
||||
[:*
|
||||
[:div.element-set
|
||||
[:div.element-set-content
|
||||
|
||||
;; WIDTH & HEIGHT
|
||||
(when (options :size)
|
||||
[:div.row-flex
|
||||
[:span.element-set-subtitle (t locale "workspace.options.size")]
|
||||
[:span.element-set-subtitle (tr "workspace.options.size")]
|
||||
[:div.input-element.width
|
||||
[:> numeric-input {:min 1
|
||||
:no-validate true
|
||||
|
@ -197,7 +271,7 @@
|
|||
;; POSITION
|
||||
(when (options :position)
|
||||
[:div.row-flex
|
||||
[:span.element-set-subtitle (t locale "workspace.options.position")]
|
||||
[:span.element-set-subtitle (tr "workspace.options.position")]
|
||||
[:div.input-element.Xaxis
|
||||
[:> numeric-input {:no-validate true
|
||||
:placeholder "--"
|
||||
|
@ -214,7 +288,7 @@
|
|||
;; ROTATION
|
||||
(when (options :rotation)
|
||||
[:div.row-flex
|
||||
[:span.element-set-subtitle (t locale "workspace.options.rotation")]
|
||||
[:span.element-set-subtitle (tr "workspace.options.rotation")]
|
||||
[:div.input-element.degrees
|
||||
[:> numeric-input
|
||||
{:no-validate true
|
||||
|
@ -244,14 +318,14 @@
|
|||
{:class (classnames
|
||||
:selected
|
||||
(and radius-1? (not radius-4?)))
|
||||
:alt (t locale "workspace.options.radius.all-corners")
|
||||
:alt (tr "workspace.options.radius.all-corners")
|
||||
:on-click on-switch-to-radius-1}
|
||||
i/radius-1]
|
||||
[:div.radius-icon.tooltip.tooltip-bottom
|
||||
{:class (classnames
|
||||
:selected
|
||||
(and radius-4? (not radius-1?)))
|
||||
:alt (t locale "workspace.options.radius.single-corners")
|
||||
:alt (tr "workspace.options.radius.single-corners")
|
||||
:on-click on-switch-to-radius-4}
|
||||
i/radius-4]]
|
||||
(if radius-1?
|
||||
|
@ -291,5 +365,68 @@
|
|||
:min 0
|
||||
:on-click select-all
|
||||
:on-change on-radius-r4-change
|
||||
:value (attr->string :r4 values)}]]])
|
||||
]))]]))
|
||||
:value (attr->string :r4 values)}]]])]))]]
|
||||
|
||||
;; CONSTRAINTS
|
||||
(when in-frame?
|
||||
[:div.element-set
|
||||
[:div.element-set-title
|
||||
[:span (tr "workspace.options.constraints")]]
|
||||
|
||||
[:div.element-set-content
|
||||
[:div.row-flex.align-top
|
||||
|
||||
[:div.constraints-widget
|
||||
[:div.constraints-box]
|
||||
[:div.constraint-button.top
|
||||
{:class (classnames :active (or (= constraints-v :top)
|
||||
(= constraints-v :topbottom)))
|
||||
:on-click (on-constraint-button-clicked :top)}]
|
||||
[:div.constraint-button.bottom
|
||||
{:class (classnames :active (or (= constraints-v :bottom)
|
||||
(= constraints-v :topbottom)))
|
||||
:on-click (on-constraint-button-clicked :bottom)}]
|
||||
[:div.constraint-button.left
|
||||
{:class (classnames :active (or (= constraints-h :left)
|
||||
(= constraints-h :leftright)))
|
||||
:on-click (on-constraint-button-clicked :left)}]
|
||||
[:div.constraint-button.right
|
||||
{:class (classnames :active (or (= constraints-h :right)
|
||||
(= constraints-h :leftright)))
|
||||
:on-click (on-constraint-button-clicked :right)}]
|
||||
[:div.constraint-button.centerv
|
||||
{:class (classnames :active (= constraints-v :center))
|
||||
:on-click (on-constraint-button-clicked :centerv)}]
|
||||
[:div.constraint-button.centerh
|
||||
{:class (classnames :active (= constraints-h :center))
|
||||
:on-click (on-constraint-button-clicked :centerh)}]]
|
||||
|
||||
[:div.constraints-form
|
||||
[:div.row-flex
|
||||
[:span.left-right i/full-screen]
|
||||
[:select.input-select {:on-change (on-constraint-select-changed :constraints-h)
|
||||
:value (d/name constraints-h "scale")}
|
||||
(when (= constraints-h :multiple)
|
||||
[:option {:value ""} (tr "settings.multiple")])
|
||||
[:option {:value "left"} (tr "workspace.options.constraints.left")]
|
||||
[:option {:value "right"} (tr "workspace.options.constraints.right")]
|
||||
[:option {:value "leftright"} (tr "workspace.options.constraints.leftright")]
|
||||
[:option {:value "center"} (tr "workspace.options.constraints.center")]
|
||||
[:option {:value "scale"} (tr "workspace.options.constraints.scale")]]]
|
||||
[:div.row-flex
|
||||
[:span.top-bottom i/full-screen]
|
||||
[:select.input-select {:on-change (on-constraint-select-changed :constraints-v)
|
||||
:value (d/name constraints-v "scale")}
|
||||
(when (= constraints-v :multiple)
|
||||
[:option {:value ""} (tr "settings.multiple")])
|
||||
[:option {:value "top"} (tr "workspace.options.constraints.top")]
|
||||
[:option {:value "bottom"} (tr "workspace.options.constraints.bottom")]
|
||||
[:option {:value "topbottom"} (tr "workspace.options.constraints.topbottom")]
|
||||
[:option {:value "center"} (tr "workspace.options.constraints.center")]
|
||||
[:option {:value "scale"} (tr "workspace.options.constraints.scale")]]]
|
||||
(when first-level?
|
||||
[:div.row-flex
|
||||
[:div.fix-when {:class (classnames :active (:fixed-scroll values))
|
||||
:on-click on-fixed-scroll-clicked}
|
||||
i/pin
|
||||
[:span (tr "workspace.options.constraints.fix-when-scrolling")]]])]]]])]))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue