Enhance border radius options form

This commit is contained in:
Andrés Moya 2021-10-27 15:20:30 +02:00 committed by Andrey Antukh
parent 99d173789e
commit 30cd499014
7 changed files with 214 additions and 117 deletions

View file

@ -5,6 +5,9 @@
### :boom: Breaking changes ### :boom: Breaking changes
### :sparkles: New features ### :sparkles: New features
- Enhance corner radius behavior [Taiga #2190](https://tree.taiga.io/project/penpot/issue/2190).
### :bug: Bugs fixed ### :bug: Bugs fixed
- Fix problem with exporting before the document is saved [Taiga #2189](https://tree.taiga.io/project/penpot/issue/2189) - Fix problem with exporting before the document is saved [Taiga #2189](https://tree.taiga.io/project/penpot/issue/2189)

View file

@ -11,6 +11,7 @@
[app.common.spec :as us] [app.common.spec :as us]
[app.common.types.interactions :as cti] [app.common.types.interactions :as cti]
[app.common.types.page-options :as cto] [app.common.types.page-options :as cto]
[app.common.types.radius :as ctr]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[clojure.set :as set] [clojure.set :as set]
[clojure.spec.alpha :as s])) [clojure.spec.alpha :as s]))
@ -191,12 +192,6 @@
(s/def :internal.shape/page-id uuid?) (s/def :internal.shape/page-id uuid?)
(s/def :internal.shape/proportion ::us/safe-number) (s/def :internal.shape/proportion ::us/safe-number)
(s/def :internal.shape/proportion-lock boolean?) (s/def :internal.shape/proportion-lock boolean?)
(s/def :internal.shape/rx ::us/safe-number)
(s/def :internal.shape/ry ::us/safe-number)
(s/def :internal.shape/r1 ::us/safe-number)
(s/def :internal.shape/r2 ::us/safe-number)
(s/def :internal.shape/r3 ::us/safe-number)
(s/def :internal.shape/r4 ::us/safe-number)
(s/def :internal.shape/stroke-color string?) (s/def :internal.shape/stroke-color string?)
(s/def :internal.shape/stroke-color-gradient (s/nilable ::gradient)) (s/def :internal.shape/stroke-color-gradient (s/nilable ::gradient))
(s/def :internal.shape/stroke-color-ref-file (s/nilable uuid?)) (s/def :internal.shape/stroke-color-ref-file (s/nilable uuid?))
@ -285,12 +280,12 @@
:internal.shape/constraints-h :internal.shape/constraints-h
:internal.shape/constraints-v :internal.shape/constraints-v
:internal.shape/fixed-scroll :internal.shape/fixed-scroll
:internal.shape/rx ::ctr/rx
:internal.shape/ry ::ctr/ry
:internal.shape/r1 ::ctr/r1
:internal.shape/r2 ::ctr/r2
:internal.shape/r3 ::ctr/r3
:internal.shape/r4 ::ctr/r4
:internal.shape/x :internal.shape/x
:internal.shape/y :internal.shape/y
:internal.shape/exports :internal.shape/exports

View file

@ -0,0 +1,90 @@
;; 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.types.radius
(:require
[app.common.spec :as us]
[clojure.spec.alpha :as s]))
(s/def ::rx ::us/safe-number)
(s/def ::ry ::us/safe-number)
(s/def ::r1 ::us/safe-number)
(s/def ::r2 ::us/safe-number)
(s/def ::r3 ::us/safe-number)
(s/def ::r4 ::us/safe-number)
;; Rectangle shapes may define the radius of the corners in two modes:
;; - radius-1 all corners have the same radius (although we store two
;; values :rx and :ry because svg uses it this way).
;; - radius-4 each corner (top-left, top-right, bottom-right, bottom-left)
;; has an independent value. SVG does not allow this directly, so we
;; emulate it with paths.
;; A shape never will have both :rx and :r1 simultaneously
;; All operations take into account that the shape may not be a rectangle, and so
;; it hasn't :rx nor :r1. In this case operations must leave shape untouched.
(defn radius-mode
[shape]
(cond (:rx shape) :radius-1
(:r1 shape) :radius-4
:else nil))
(defn radius-1?
[shape]
(and (:rx shape) (not= (:rx shape) 0)))
(defn radius-4?
[shape]
(and (:r1 shape)
(or (not= (:r1 shape) 0)
(not= (:r2 shape) 0)
(not= (:r3 shape) 0)
(not= (:r4 shape) 0))))
(defn all-equal?
[shape]
(= (:r1 shape) (:r2 shape) (:r3 shape) (:r4 shape)))
(defn switch-to-radius-1
[shape]
(let [r (if (all-equal? shape) (:r1 shape) 0)]
(cond-> shape
(:r1 shape)
(-> (assoc :rx r :ry r)
(dissoc :r1 :r2 :r3 :r4)))))
(defn switch-to-radius-4
[shape]
(cond-> shape
(:rx shape)
(-> (assoc :r1 (:rx shape)
:r2 (:rx shape)
:r3 (:rx shape)
:r4 (:rx shape))
(dissoc :rx :ry))))
(defn set-radius-1
[shape value]
(cond-> shape
(:r1 shape)
(-> (dissoc :r1 :r2 :r3 :r4)
(assoc :rx 0 :ry 0))
(:rx shape)
(assoc :rx value :ry value)))
(defn set-radius-4
[shape attr value]
(cond-> shape
(:rx shape)
(-> (dissoc :rx :rx)
(assoc :r1 0 :r2 0 :r3 0 :r4 0))
(attr shape)
(assoc attr value)))

View file

@ -7,6 +7,7 @@
(ns app.main.ui.shapes.attrs (ns app.main.ui.shapes.attrs
(:require (:require
[app.common.pages.spec :as spec] [app.common.pages.spec :as spec]
[app.common.types.radius :as ctr]
[app.main.ui.context :as muc] [app.main.ui.context :as muc]
[app.util.object :as obj] [app.util.object :as obj]
[app.util.svg :as usvg] [app.util.svg :as usvg]
@ -53,7 +54,13 @@
(min r-bottom-left r-left-bottom)])) (min r-bottom-left r-left-bottom)]))
(defn add-border-radius [attrs shape] (defn add-border-radius [attrs shape]
(if (or (:r1 shape) (:r2 shape) (:r3 shape) (:r4 shape)) (case (ctr/radius-mode shape)
:radius-1
(obj/merge! attrs #js {:rx (:rx shape)
:ry (:ry shape)})
:radius-4
(let [[r1 r2 r3 r4] (truncate-radius shape) (let [[r1 r2 r3 r4] (truncate-radius shape)
top (- (:width shape) r1 r2) top (- (:width shape) r1 r2)
right (- (:height shape) r2 r3) right (- (:height shape) r2 r3)
@ -69,10 +76,7 @@
"v" (- left) " " "v" (- left) " "
"a" r1 "," r1 " 0 0 1 " r1 "," (- r1) " " "a" r1 "," r1 " 0 0 1 " r1 "," (- r1) " "
"z")})) "z")}))
(if (or (:rx shape) (:ry shape)) attrs))
(obj/merge! attrs #js {:rx (:rx shape)
:ry (:ry shape)})
attrs)))
(defn add-fill [attrs shape render-id] (defn add-fill [attrs shape render-id]
(let [fill-attrs (cond (let [fill-attrs (cond

View file

@ -7,6 +7,7 @@
(ns app.main.ui.viewer.handoff.attributes.layout (ns app.main.ui.viewer.handoff.attributes.layout
(:require (:require
[app.common.math :as mth] [app.common.math :as mth]
[app.common.types.radius :as ctr]
[app.main.ui.components.copy-button :refer [copy-button]] [app.main.ui.components.copy-button :refer [copy-button]]
[app.util.code-gen :as cg] [app.util.code-gen :as cg]
[app.util.i18n :refer [t]] [app.util.i18n :refer [t]]
@ -58,20 +59,17 @@
[:div.attributes-value (mth/precision y 2) "px"] [:div.attributes-value (mth/precision y 2) "px"]
[:& copy-button {:data (copy-data selrect :y)}]]) [:& copy-button {:data (copy-data selrect :y)}]])
(when (and (:rx shape) (not= (:rx shape) 0)) (when (ctr/radius-1? shape)
[:div.attributes-unit-row [:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.layout.radius")] [:div.attributes-label (t locale "handoff.attributes.layout.radius")]
[:div.attributes-value (mth/precision (:rx shape) 2) "px"] [:div.attributes-value (mth/precision (:rx shape) 2) "px"]
[:& copy-button {:data (copy-data shape :rx)}]]) [:& copy-button {:data (copy-data shape :rx)}]])
(when (and (:r1 shape) (when (ctr/radius-4? shape)
(or (not= (:r1 shape) 0)
(not= (:r2 shape) 0)
(not= (:r3 shape) 0)
(not= (:r4 shape) 0)))
[:div.attributes-unit-row [:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.layout.radius")] [:div.attributes-label (t locale "handoff.attributes.layout.radius")]
[:div.attributes-value (mth/precision (:r1 shape) 2) ", " [:div.attributes-value
(mth/precision (:r1 shape) 2) ", "
(mth/precision (:r2 shape) 2) ", " (mth/precision (:r2 shape) 2) ", "
(mth/precision (:r3 shape) 2) ", " (mth/precision (:r3 shape) 2) ", "
(mth/precision (:r4 shape) 2) "px"] (mth/precision (:r4 shape) 2) "px"]

View file

@ -9,6 +9,7 @@
[app.common.data :as d] [app.common.data :as d]
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.math :as math] [app.common.math :as math]
[app.common.types.radius :as ctr]
[app.main.data.workspace :as udw] [app.main.data.workspace :as udw]
[app.main.data.workspace.changes :as dch] [app.main.data.workspace.changes :as dch]
[app.main.refs :as refs] [app.main.refs :as refs]
@ -61,6 +62,11 @@
proportion-lock (:proportion-lock values) proportion-lock (:proportion-lock values)
radius-mode (ctr/radius-mode values)
all-equal? (ctr/all-equal? values)
radius-multi? (mf/use-state nil)
radius-input-ref (mf/use-ref nil)
on-size-change on-size-change
(mf/use-callback (mf/use-callback
(mf/deps ids) (mf/deps ids)
@ -97,57 +103,38 @@
(mf/use-callback (mf/use-callback
(mf/deps ids) (mf/deps ids)
(fn [_value] (fn [_value]
(let [radius-update (if all-equal?
(fn [shape] (st/emit! (dch/update-shapes ids-with-children ctr/switch-to-radius-1))
(cond-> shape (reset! radius-multi? true))))
(:r1 shape)
(-> (assoc :rx 0 :ry 0)
(dissoc :r1 :r2 :r3 :r4))))]
(st/emit! (dch/update-shapes ids-with-children radius-update)))))
on-switch-to-radius-4 on-switch-to-radius-4
(mf/use-callback (mf/use-callback
(mf/deps ids) (mf/deps ids)
(fn [_value] (fn [_value]
(let [radius-update (st/emit! (dch/update-shapes ids-with-children ctr/switch-to-radius-4))
(fn [shape] (reset! radius-multi? false)))
(cond-> shape
(:rx shape)
(-> (assoc :r1 0 :r2 0 :r3 0 :r4 0)
(dissoc :rx :ry))))]
(st/emit! (dch/update-shapes ids-with-children radius-update)))))
on-radius-1-change on-radius-1-change
(mf/use-callback (mf/use-callback
(mf/deps ids) (mf/deps ids)
(fn [value] (fn [value]
(let [radius-update (st/emit! (dch/update-shapes ids-with-children #(ctr/set-radius-1 % value)))))
(fn [shape]
(cond-> shape
(:r1 shape)
(-> (dissoc :r1 :r2 :r3 :r4)
(assoc :rx 0 :ry 0))
(or (:rx shape) (:r1 shape)) on-radius-multi-change
(assoc :rx value :ry value)))] (mf/use-callback
(mf/deps ids)
(st/emit! (dch/update-shapes ids-with-children radius-update))))) (fn [event]
(let [value (-> event dom/get-target dom/get-value d/parse-integer)]
(when (some? value)
(st/emit! (dch/update-shapes ids-with-children ctr/switch-to-radius-1)
(dch/update-shapes ids-with-children #(ctr/set-radius-1 % value)))
(reset! radius-multi? false)))))
on-radius-4-change on-radius-4-change
(mf/use-callback (mf/use-callback
(mf/deps ids) (mf/deps ids)
(fn [value attr] (fn [value attr]
(let [radius-update (st/emit! (dch/update-shapes ids-with-children #(ctr/set-radius-4 % attr value)))))
(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! (dch/update-shapes ids-with-children radius-update)))))
on-width-change #(on-size-change % :width) on-width-change #(on-size-change % :width)
on-height-change #(on-size-change % :height) on-height-change #(on-size-change % :height)
@ -160,6 +147,16 @@
select-all #(-> % (dom/get-target) (.select))] select-all #(-> % (dom/get-target) (.select))]
(mf/use-layout-effect
(mf/deps radius-mode @radius-multi?)
(fn []
(when (and (= radius-mode :radius-1)
(= @radius-multi? false))
;; when going back from radius-multi to normal radius-1,
;; restore focus to the newly created numeric-input
(let [radius-input (mf/ref-val radius-input-ref)]
(dom/focus! radius-input)))))
[:* [:*
[:div.element-set [:div.element-set
[:div.element-set-content [:div.element-set-content
@ -233,34 +230,44 @@
:value (attr->string :rotation values)}]]) :value (attr->string :rotation values)}]])
;; RADIUS ;; RADIUS
(let [radius-1? (some? (:rx values)) (when (and (options :radius) (some? radius-mode))
radius-4? (some? (:r1 values))]
(when (and (options :radius) (or radius-1? radius-4?))
[:div.row-flex [:div.row-flex
[:div.radius-options [:div.radius-options
[:div.radius-icon.tooltip.tooltip-bottom [:div.radius-icon.tooltip.tooltip-bottom
{:class (dom/classnames {:class (dom/classnames
:selected :selected (or (= radius-mode :radius-1) @radius-multi?))
(and radius-1? (not radius-4?)))
:alt (tr "workspace.options.radius.all-corners") :alt (tr "workspace.options.radius.all-corners")
:on-click on-switch-to-radius-1} :on-click on-switch-to-radius-1}
i/radius-1] i/radius-1]
[:div.radius-icon.tooltip.tooltip-bottom [:div.radius-icon.tooltip.tooltip-bottom
{:class (dom/classnames {:class (dom/classnames
:selected :selected (and (= radius-mode :radius-4) (not @radius-multi?)))
(and radius-4? (not radius-1?)))
:alt (tr "workspace.options.radius.single-corners") :alt (tr "workspace.options.radius.single-corners")
:on-click on-switch-to-radius-4} :on-click on-switch-to-radius-4}
i/radius-4]] i/radius-4]]
(if radius-1?
(cond
(= radius-mode :radius-1)
[:div.input-element.mini [:div.input-element.mini
[:> numeric-input [:> numeric-input
{:placeholder "--" {:placeholder "--"
:ref radius-input-ref
:min 0 :min 0
:on-click select-all :on-click select-all
:on-change on-radius-1-change :on-change on-radius-1-change
:value (attr->string :rx values)}]] :value (attr->string :rx values)}]]
@radius-multi?
[:div.input-element.mini
[:input.input-text
{:type "number"
:placeholder "--"
:on-click select-all
:on-change on-radius-multi-change
:value ""}]]
(= radius-mode :radius-4)
[:* [:*
[:div.input-element.mini [:div.input-element.mini
[:> numeric-input [:> numeric-input
@ -289,4 +296,4 @@
:min 0 :min 0
:on-click select-all :on-click select-all
:on-change on-radius-r4-change :on-change on-radius-r4-change
:value (attr->string :r4 values)}]]])]))]]])) :value (attr->string :r4 values)}]]])])]]]))

View file

@ -771,7 +771,7 @@ msgstr "Izquierda"
#: src/app/main/ui/handoff/attributes/layout.cljs, src/app/main/ui/handoff/attributes/layout.cljs #: src/app/main/ui/handoff/attributes/layout.cljs, src/app/main/ui/handoff/attributes/layout.cljs
msgid "handoff.attributes.layout.radius" msgid "handoff.attributes.layout.radius"
msgstr "Derecha" msgstr "Radio"
#: src/app/main/ui/handoff/attributes/layout.cljs #: src/app/main/ui/handoff/attributes/layout.cljs
msgid "handoff.attributes.layout.rotation" msgid "handoff.attributes.layout.rotation"