diff --git a/CHANGES.md b/CHANGES.md index 591d6eb1c..10a25a3f1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,9 @@ ### :boom: Breaking changes ### :sparkles: New features + +- Enhance corner radius behavior [Taiga #2190](https://tree.taiga.io/project/penpot/issue/2190). + ### :bug: Bugs fixed - Fix problem with exporting before the document is saved [Taiga #2189](https://tree.taiga.io/project/penpot/issue/2189) diff --git a/common/src/app/common/pages/spec.cljc b/common/src/app/common/pages/spec.cljc index 286148439..8ce65e409 100644 --- a/common/src/app/common/pages/spec.cljc +++ b/common/src/app/common/pages/spec.cljc @@ -11,6 +11,7 @@ [app.common.spec :as us] [app.common.types.interactions :as cti] [app.common.types.page-options :as cto] + [app.common.types.radius :as ctr] [app.common.uuid :as uuid] [clojure.set :as set] [clojure.spec.alpha :as s])) @@ -191,12 +192,6 @@ (s/def :internal.shape/page-id uuid?) (s/def :internal.shape/proportion ::us/safe-number) (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-gradient (s/nilable ::gradient)) (s/def :internal.shape/stroke-color-ref-file (s/nilable uuid?)) @@ -285,12 +280,12 @@ :internal.shape/constraints-h :internal.shape/constraints-v :internal.shape/fixed-scroll - :internal.shape/rx - :internal.shape/ry - :internal.shape/r1 - :internal.shape/r2 - :internal.shape/r3 - :internal.shape/r4 + ::ctr/rx + ::ctr/ry + ::ctr/r1 + ::ctr/r2 + ::ctr/r3 + ::ctr/r4 :internal.shape/x :internal.shape/y :internal.shape/exports diff --git a/common/src/app/common/types/radius.cljc b/common/src/app/common/types/radius.cljc new file mode 100644 index 000000000..308de6c2e --- /dev/null +++ b/common/src/app/common/types/radius.cljc @@ -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))) + diff --git a/frontend/src/app/main/ui/shapes/attrs.cljs b/frontend/src/app/main/ui/shapes/attrs.cljs index e9ca5a966..9cf576ded 100644 --- a/frontend/src/app/main/ui/shapes/attrs.cljs +++ b/frontend/src/app/main/ui/shapes/attrs.cljs @@ -7,6 +7,7 @@ (ns app.main.ui.shapes.attrs (:require [app.common.pages.spec :as spec] + [app.common.types.radius :as ctr] [app.main.ui.context :as muc] [app.util.object :as obj] [app.util.svg :as usvg] @@ -53,7 +54,13 @@ (min r-bottom-left r-left-bottom)])) (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) top (- (:width shape) r1 r2) right (- (:height shape) r2 r3) @@ -69,10 +76,7 @@ "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))) + attrs)) (defn add-fill [attrs shape render-id] (let [fill-attrs (cond diff --git a/frontend/src/app/main/ui/viewer/handoff/attributes/layout.cljs b/frontend/src/app/main/ui/viewer/handoff/attributes/layout.cljs index ea7ca7b52..bc8c48589 100644 --- a/frontend/src/app/main/ui/viewer/handoff/attributes/layout.cljs +++ b/frontend/src/app/main/ui/viewer/handoff/attributes/layout.cljs @@ -7,6 +7,7 @@ (ns app.main.ui.viewer.handoff.attributes.layout (:require [app.common.math :as mth] + [app.common.types.radius :as ctr] [app.main.ui.components.copy-button :refer [copy-button]] [app.util.code-gen :as cg] [app.util.i18n :refer [t]] @@ -58,20 +59,17 @@ [:div.attributes-value (mth/precision y 2) "px"] [:& 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-label (t locale "handoff.attributes.layout.radius")] [: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))) + (when (ctr/radius-4? shape) [:div.attributes-unit-row [: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 (:r3 shape) 2) ", " (mth/precision (:r4 shape) 2) "px"] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs index 9305404f5..d2505dafb 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs @@ -9,6 +9,7 @@ [app.common.data :as d] [app.common.geom.shapes :as gsh] [app.common.math :as math] + [app.common.types.radius :as ctr] [app.main.data.workspace :as udw] [app.main.data.workspace.changes :as dch] [app.main.refs :as refs] @@ -61,6 +62,11 @@ 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 (mf/use-callback (mf/deps ids) @@ -97,57 +103,38 @@ (mf/use-callback (mf/deps ids) (fn [_value] - (let [radius-update - (fn [shape] - (cond-> shape - (:r1 shape) - (-> (assoc :rx 0 :ry 0) - (dissoc :r1 :r2 :r3 :r4))))] - (st/emit! (dch/update-shapes ids-with-children radius-update))))) + (if all-equal? + (st/emit! (dch/update-shapes ids-with-children ctr/switch-to-radius-1)) + (reset! radius-multi? true)))) 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! (dch/update-shapes ids-with-children radius-update))))) + (st/emit! (dch/update-shapes ids-with-children ctr/switch-to-radius-4)) + (reset! radius-multi? false))) 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)) + (st/emit! (dch/update-shapes ids-with-children #(ctr/set-radius-1 % value))))) - (or (:rx shape) (:r1 shape)) - (assoc :rx value :ry value)))] - - (st/emit! (dch/update-shapes ids-with-children radius-update))))) + on-radius-multi-change + (mf/use-callback + (mf/deps ids) + (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 (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! (dch/update-shapes ids-with-children radius-update))))) + (st/emit! (dch/update-shapes ids-with-children #(ctr/set-radius-4 % attr value))))) on-width-change #(on-size-change % :width) on-height-change #(on-size-change % :height) @@ -160,6 +147,16 @@ 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-content @@ -233,60 +230,70 @@ :value (attr->string :rotation values)}]]) ;; RADIUS - (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 (dom/classnames - :selected - (and radius-1? (not radius-4?))) - :alt (tr "workspace.options.radius.all-corners") - :on-click on-switch-to-radius-1} - i/radius-1] - [:div.radius-icon.tooltip.tooltip-bottom - {:class (dom/classnames - :selected - (and radius-4? (not radius-1?))) - :alt (tr "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)}]] + (when (and (options :radius) (some? radius-mode)) + [:div.row-flex + [:div.radius-options + [:div.radius-icon.tooltip.tooltip-bottom + {:class (dom/classnames + :selected (or (= radius-mode :radius-1) @radius-multi?)) + :alt (tr "workspace.options.radius.all-corners") + :on-click on-switch-to-radius-1} + i/radius-1] + [:div.radius-icon.tooltip.tooltip-bottom + {:class (dom/classnames + :selected (and (= radius-mode :radius-4) (not @radius-multi?))) + :alt (tr "workspace.options.radius.single-corners") + :on-click on-switch-to-radius-4} + i/radius-4]] - [:* - [: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)}]]])]))]]])) + (cond + + (= radius-mode :radius-1) + [:div.input-element.mini + [:> numeric-input + {:placeholder "--" + :ref radius-input-ref + :min 0 + :on-click select-all + :on-change on-radius-1-change + :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 + [:> 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)}]]])])]]])) diff --git a/frontend/translations/es.po b/frontend/translations/es.po index a7a3afe5f..f2561d82e 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -771,7 +771,7 @@ msgstr "Izquierda" #: src/app/main/ui/handoff/attributes/layout.cljs, src/app/main/ui/handoff/attributes/layout.cljs msgid "handoff.attributes.layout.radius" -msgstr "Derecha" +msgstr "Radio" #: src/app/main/ui/handoff/attributes/layout.cljs msgid "handoff.attributes.layout.rotation" @@ -3196,4 +3196,4 @@ msgid "workspace.updates.update" msgstr "Actualizar" msgid "workspace.viewport.click-to-close-path" -msgstr "Pulsar para cerrar la ruta" \ No newline at end of file +msgstr "Pulsar para cerrar la ruta"