🎉 Allow a different radius for each rect corner

This commit is contained in:
Andrés Moya 2021-02-18 16:54:45 +01:00 committed by Andrey Antukh
parent d56b758490
commit c38117d116
15 changed files with 287 additions and 33 deletions

View file

@ -17,13 +17,17 @@
[app.util.code-gen :as cg]
[app.main.ui.components.copy-button :refer [copy-button]]))
(def properties [:width :height :x :y :radius :rx])
(def properties [:width :height :x :y :radius :rx :r1])
(def params
{:to-prop {:x "left"
:y "top"
:rotation "transform"
:rx "border-radius"}
:format {:rotation #(str/fmt "rotate(%sdeg)" %)}})
:rx "border-radius"
:r1 "border-radius"}
:format {:rotation #(str/fmt "rotate(%sdeg)" %)
:r1 #(apply str/fmt "%spx, %spx, %spx, %spx" %)}
:multi {:r1 [:r1 :r2 :r3 :r4]}})
(defn copy-data
([shape]
@ -62,6 +66,19 @@
[:div.attributes-value (mth/precision (:rx shape) 2) "px"]
[:& copy-button {:data (copy-data shape :rx)}]])
(when (and (:r1 shape)
(or (not= (:r1 shape) 0)
(not= (:r2 shape) 0)
(not= (:r3 shape) 0)
(not= (:r4 shape) 0)))
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.layout.radius")]
[:div.attributes-value (mth/precision (:r1 shape) 2) ", "
(mth/precision (:r2 shape) 2) ", "
(mth/precision (:r3 shape) 2) ", "
(mth/precision (:r4 shape) 2) "px"]
[:& copy-button {:data (copy-data shape :r1)}]])
(when (not= (:rotation shape 0) 0)
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.layout.rotation")]

View file

@ -87,6 +87,8 @@
(def play (icon-xref :play))
(def plus (icon-xref :plus))
(def radius (icon-xref :radius))
(def radius-1 (icon-xref :radius-1))
(def radius-4 (icon-xref :radius-4))
(def recent (icon-xref :recent))
(def redo (icon-xref :redo))
(def rotate (icon-xref :rotate))

View file

@ -22,11 +22,56 @@
:dashed "10,10"
nil))
(defn- truncate-side
[shape ra-attr rb-attr dimension-attr]
(let [ra (ra-attr shape)
rb (rb-attr shape)
dimension (dimension-attr shape)]
(if (<= (+ ra rb) dimension)
[ra rb]
[(/ (* ra dimension) (+ ra rb))
(/ (* rb dimension) (+ ra rb))])))
(defn- truncate-radius
[shape]
(let [[r-top-left r-top-right]
(truncate-side shape :r1 :r2 :width)
[r-right-top r-right-bottom]
(truncate-side shape :r2 :r3 :height)
[r-bottom-right r-bottom-left]
(truncate-side shape :r3 :r4 :width)
[r-left-bottom r-left-top]
(truncate-side shape :r4 :r1 :height)]
[(min r-top-left r-left-top)
(min r-top-right r-right-top)
(min r-right-bottom r-bottom-right)
(min r-bottom-left r-left-bottom)]))
(defn add-border-radius [attrs shape]
(if (or (:rx shape) (:ry shape))
(obj/merge! attrs #js {:rx (:rx shape)
:ry (:ry shape)})
attrs))
(if (or (:r1 shape) (:r2 shape) (:r3 shape) (:r4 shape))
(let [[r1 r2 r3 r4] (truncate-radius shape)
top (- (:width shape) r1 r2)
right (- (:height shape) r2 r3)
bottom (- (:width shape) r3 r4)
left (- (:height shape) r4 r1)]
(obj/merge! attrs #js {:d (str "M" (+ (:x shape) r1) "," (:y shape) " "
"h" top " "
"a" r2 "," r2 " 0 0 1 " r2 "," r2 " "
"v" right " "
"a" r3 "," r3 " 0 0 1 " (- r3) "," r3 " "
"h" (- bottom) " "
"a" r4 "," r4 " 0 0 1 " (- r4) "," (- r4) " "
"v" (- left) " "
"a" r1 "," r1 " 0 0 1 " r1 "," (- r1) " "
"z")}))
(if (or (:rx shape) (:ry shape))
(obj/merge! attrs #js {:rx (:rx shape)
:ry (:ry shape)})
attrs)))
(defn add-fill [attrs shape render-id]
(let [fill-color-gradient-id (str "fill-color-gradient_" render-id)]

View file

@ -37,4 +37,7 @@
[:& shape-custom-stroke {:shape shape
:base-props props
:elem-name "rect"}]))
:elem-name
(if (.-d props)
"path"
"rect")}]))

View file

@ -24,7 +24,13 @@
[app.common.math :as math]
[app.util.i18n :refer [t] :as i18n]))
(def measure-attrs [:proportion-lock :width :height :x :y :rotation :rx :ry :selrect])
(def measure-attrs [:proportion-lock
:width :height
:x :y
:rotation
:rx :ry
:r1 :r2 :r3 :r4
:selrect])
(defn- attr->string [attr values]
(let [value (attr values)]
@ -93,20 +99,70 @@
(fn [value]
(st/emit! (udw/increase-rotation ids value))))
on-radius-change
on-switch-to-radius-1
(mf/use-callback
(mf/deps ids)
(fn [value]
(let [radius-update
(fn [shape]
(cond-> shape
(:rx shape) (assoc :rx value :ry value)))]
(:r1 shape)
(-> (assoc :rx 0 :ry 0)
(dissoc :r1 :r2 :r3 :r4))))]
(st/emit! (dwc/update-shapes ids-with-children radius-update)))))
on-switch-to-radius-4
(mf/use-callback
(mf/deps ids)
(fn [value]
(let [radius-update
(fn [shape]
(cond-> shape
(:rx shape)
(-> (assoc :r1 0 :r2 0 :r3 0 :r4 0)
(dissoc :rx :ry))))]
(st/emit! (dwc/update-shapes ids-with-children radius-update)))))
on-radius-1-change
(mf/use-callback
(mf/deps ids)
(fn [value]
(let [radius-update
(fn [shape]
(cond-> shape
(:r1 shape)
(-> (dissoc :r1 :r2 :r3 :r4)
(assoc :rx 0 :ry 0))
(or (:rx shape) (:r1 shape))
(assoc :rx value :ry value)))]
(st/emit! (dwc/update-shapes ids-with-children radius-update)))))
on-radius-4-change
(mf/use-callback
(mf/deps ids)
(fn [value attr]
(let [radius-update
(fn [shape]
(cond-> shape
(:rx shape)
(-> (dissoc :rx :rx)
(assoc :r1 0 :r2 0 :r3 0 :r4 0))
(attr shape)
(assoc attr value)))]
(st/emit! (dwc/update-shapes ids-with-children radius-update)))))
on-width-change #(on-size-change % :width)
on-height-change #(on-size-change % :height)
on-pos-x-change #(on-position-change % :x)
on-pos-y-change #(on-position-change % :y)
on-radius-r1-change #(on-radius-4-change % :r1)
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))]
[:div.element-set
@ -181,14 +237,61 @@
:value (attr->string :rotation values)}]])
;; RADIUS
(when (and (options :radius) (not (nil? (:rx values))))
[:div.row-flex
[:span.element-set-subtitle (t locale "workspace.options.radius")]
[:div.input-element.pixels
[:> numeric-input
{:placeholder "--"
:min 0
:on-click select-all
:on-change on-radius-change
:value (attr->string :rx values)}]]
[:div.input-element]])]]))
(let [radius-1? (some? (:rx values))
radius-4? (some? (:r1 values))]
(when (and (options :radius) (or radius-1? radius-4?))
[:div.row-flex
[:div.radius-options
[:div.radius-icon.tooltip.tooltip-bottom
{:class (classnames
:selected
(and radius-1? (not radius-4?)))
:alt (t locale "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")
:on-click on-switch-to-radius-4}
i/radius-4]]
(if radius-1?
[:div.input-element.mini
[:> numeric-input
{:placeholder "--"
:min 0
:on-click select-all
:on-change on-radius-1-change
:value (attr->string :rx values)}]]
[:*
[:div.input-element.mini
[:> numeric-input
{:placeholder "--"
:min 0
:on-click select-all
:on-change on-radius-r1-change
:value (attr->string :r1 values)}]]
[:div.input-element.mini
[:> numeric-input
{:placeholder "--"
:min 0
:on-click select-all
:on-change on-radius-r2-change
:value (attr->string :r2 values)}]]
[:div.input-element.mini
[:> numeric-input
{:placeholder "--"
:min 0
:on-click select-all
:on-change on-radius-r3-change
:value (attr->string :r3 values)}]]
[:div.input-element.mini
[:> numeric-input
{:placeholder "--"
:min 0
:on-click select-all
:on-change on-radius-r4-change
:value (attr->string :r4 values)}]]])
]))]]))

View file

@ -39,9 +39,15 @@
(str/format "%spx %s %s" width style (uc/color->background color)))))
(def styles-data
{:layout {:props [:width :height :x :y :radius :rx]
:to-prop {:x "left" :y "top" :rotation "transform" :rx "border-radius"}
:format {:rotation #(str/fmt "rotate(%sdeg)" %)}}
{:layout {:props [:width :height :x :y :radius :rx :r1]
:to-prop {:x "left"
:y "top"
:rotation "transform"
:rx "border-radius"
:r1 "border-radius"}
:format {:rotation #(str/fmt "rotate(%sdeg)" %)
:r1 #(apply str/fmt "%spx, %spx, %spx, %spx" %)}
:multi {:r1 [:r1 :r2 :r3 :r4]}}
:fill {:props [:fill-color :fill-color-gradient]
:to-prop {:fill-color "background" :fill-color-gradient "background"}
:format {:fill-color format-fill-color :fill-color-gradient format-fill-color}}
@ -74,13 +80,14 @@
:text-transform name
:fill-color format-fill-color}})
(defn generate-css-props
([values properties]
(generate-css-props values properties nil))
([values properties params]
(let [{:keys [to-prop format tab-size] :or {to-prop {} tab-size 0}} params
(let [{:keys [to-prop format tab-size multi]
:or {to-prop {} tab-size 0 multi {}}} params
;; We allow the :format and :to-prop to be a map for different properties
;; or just a value for a single property. This code transform a single
;; property to a uniform one
@ -94,19 +101,28 @@
(into {} (map #(vector % to-prop) properties))
to-prop)
get-value (fn [prop]
(if-let [props (prop multi)]
(map #(get values %) props)
(get values prop)))
null? (fn [value]
(if (coll? value)
(every? #(or (nil? %) (= % 0)) value)
(or (nil? value) (= value 0))))
default-format (fn [value] (str (mth/precision value 2) "px"))
format-property (fn [prop]
(let [css-prop (or (prop to-prop) (name prop))
format-fn (or (prop format) default-format)
css-val (format-fn (prop values) values)]
css-val (format-fn (get-value prop) values)]
(when css-val
(str
(str/repeat " " tab-size)
(str/fmt "%s: %s;" css-prop css-val)))))]
(->> properties
(remove #(let [value (get values %)]
(or (nil? value) (= value 0))))
(remove #(null? (get-value %)))
(map format-property)
(filter (comp not nil?))
(str/join "\n")))))
@ -114,9 +130,11 @@
(defn shape->properties [shape]
(let [props (->> styles-data vals (mapcat :props))
to-prop (->> styles-data vals (map :to-prop) (reduce merge))
format (->> styles-data vals (map :format) (reduce merge))]
format (->> styles-data vals (map :format) (reduce merge))
multi (->> styles-data vals (map :multi) (reduce merge))]
(generate-css-props shape props {:to-prop to-prop
:format format
:multi multi
:tab-size 2})))
(defn text->properties [shape]
(let [text-shape-style (select-keys styles-data [:layout :shadow :blur])
@ -149,7 +167,7 @@
properties (if (= :text (:type shape))
(text->properties shape)
(shape->properties shape))
selector (str/css-selector name)
selector (if (str/starts-with? selector "-") (subs selector 1) selector)]
(str/join "\n" [(str/fmt "/* %s */" name)