mirror of
https://github.com/penpot/penpot.git
synced 2025-02-09 16:48:16 -05:00
✨ Add border radius to artboards
This commit is contained in:
parent
78d7fe3e10
commit
d4af28c52b
7 changed files with 242 additions and 154 deletions
|
@ -5,6 +5,7 @@
|
|||
### :boom: Breaking changes
|
||||
### :sparkles: New features
|
||||
|
||||
- Add border radius to our artboars [Taiga #2056](https://tree.taiga.io/project/penpot/us/2056)
|
||||
- Persist color palette and color picker across refresh [Taiga #1660](https://tree.taiga.io/project/penpot/issue/1660)
|
||||
- Ability to add multiple strokes to a shape [Taiga #2778](https://tree.taiga.io/project/penpot/us/2778)
|
||||
- Scroll to selected size in font size selector [Taiga #2825](https://tree.taiga.io/project/penpot/us/2825)
|
||||
|
|
|
@ -72,6 +72,7 @@
|
|||
{:frame #{:proportion-lock
|
||||
:width :height
|
||||
:x :y
|
||||
:rx :ry :r1 :r2 :r3 :r4
|
||||
:selrect
|
||||
|
||||
:opacity
|
||||
|
|
|
@ -73,7 +73,14 @@
|
|||
:name "Artboard-1"
|
||||
:fills [{:fill-color clr/white
|
||||
:fill-opacity 1}]
|
||||
:strokes []}
|
||||
:strokes []
|
||||
:stroke-style :none
|
||||
:stroke-alignment :center
|
||||
:stroke-width 0
|
||||
:stroke-color clr/black
|
||||
:stroke-opacity 0
|
||||
:rx 0
|
||||
:ry 0}
|
||||
|
||||
{:type :text
|
||||
:name "Text-1"
|
||||
|
|
|
@ -27,9 +27,18 @@
|
|||
[{:keys [shape render-id]}]
|
||||
(when (= :frame (:type shape))
|
||||
(let [{:keys [x y width height]} shape
|
||||
padding (filters/calculate-padding shape)]
|
||||
padding (filters/calculate-padding shape)
|
||||
props (-> (attrs/extract-style-attrs shape)
|
||||
(obj/merge!
|
||||
#js {:x (- x padding)
|
||||
:y (- y padding)
|
||||
:width (+ width (* 2 padding))
|
||||
:height (+ height (* 2 padding))}))
|
||||
path? (some? (.-d props))]
|
||||
[:clipPath {:id (frame-clip-id shape render-id) :class "frame-clip"}
|
||||
[:rect {:x (- x padding) :y (- y padding) :width (+ width (* 2 padding)) :height (+ height (* 2 padding))}]])))
|
||||
(if path?
|
||||
[:> :path props]
|
||||
[:> :rect props])])))
|
||||
|
||||
(mf/defc frame-thumbnail
|
||||
{::mf/wrap-props false}
|
||||
|
@ -49,25 +58,28 @@
|
|||
(defn frame-shape
|
||||
[shape-wrapper]
|
||||
(mf/fnc frame-shape
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [childs (unchecked-get props "childs")
|
||||
shape (unchecked-get props "shape")
|
||||
{:keys [x y width height]} shape
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [childs (unchecked-get props "childs")
|
||||
shape (unchecked-get props "shape")
|
||||
{:keys [x y width height]} shape
|
||||
|
||||
props (-> (attrs/extract-style-attrs shape)
|
||||
(obj/merge!
|
||||
#js {:x x
|
||||
:y y
|
||||
:width width
|
||||
:height height
|
||||
:className "frame-background"}))]
|
||||
props (-> (attrs/extract-style-attrs shape)
|
||||
(obj/merge!
|
||||
#js {:x x
|
||||
:y y
|
||||
:width width
|
||||
:height height
|
||||
:className "frame-background"}))
|
||||
path? (some? (.-d props))]
|
||||
|
||||
[:*
|
||||
[:& shape-custom-strokes {:shape shape}
|
||||
(if path?
|
||||
[:> :path props]
|
||||
[:> :rect props])
|
||||
|
||||
[:*
|
||||
[:& shape-custom-strokes {:shape shape}
|
||||
[:> :rect props]]
|
||||
|
||||
(for [item childs]
|
||||
[:& shape-wrapper {:shape item
|
||||
:key (dm/str (:id item))}])])))
|
||||
(for [item childs]
|
||||
[:& shape-wrapper {:shape item
|
||||
:key (dm/str (:id item))}])]])))
|
||||
|
||||
|
|
|
@ -9,13 +9,12 @@
|
|||
[app.common.data :as d]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.math :as math]
|
||||
[app.common.spec.radius :as ctr]
|
||||
[app.main.data.workspace :as udw]
|
||||
[app.main.data.workspace.changes :as dch]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.numeric-input :refer [numeric-input]]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.main.ui.workspace.sidebar.options.menus.radius :refer [border-radius]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[rumext.alpha :as mf]))
|
||||
|
@ -42,8 +41,6 @@
|
|||
[{:keys [options ids ids-with-children values] :as props}]
|
||||
(let [options (or options #{:size :position :rotation :radius})
|
||||
|
||||
ids-with-children (or ids-with-children ids)
|
||||
|
||||
old-shapes (deref (refs/objects-by-id ids))
|
||||
frames (map #(deref (refs/object-by-id (:frame-id %))) old-shapes)
|
||||
|
||||
|
@ -63,11 +60,6 @@
|
|||
|
||||
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)
|
||||
|
@ -100,64 +92,13 @@
|
|||
(fn [value]
|
||||
(st/emit! (udw/increase-rotation ids value))))
|
||||
|
||||
on-switch-to-radius-1
|
||||
(mf/use-callback
|
||||
(mf/deps ids)
|
||||
(fn [_value]
|
||||
(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]
|
||||
(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]
|
||||
(st/emit! (dch/update-shapes ids-with-children #(ctr/set-radius-1 % value)))))
|
||||
|
||||
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]
|
||||
(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)
|
||||
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))]
|
||||
|
||||
(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,72 +174,4 @@
|
|||
:value (attr->string :rotation values)}]])
|
||||
|
||||
;; RADIUS
|
||||
(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]]
|
||||
|
||||
(cond
|
||||
(= radius-mode :radius-1)
|
||||
[:div.input-element.mini {:title (tr "workspace.options.radius")}
|
||||
[:> 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 {:title (tr "workspace.options.radius")}
|
||||
[: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 {:title (tr "workspace.options.radius")}
|
||||
[:> numeric-input
|
||||
{:placeholder "--"
|
||||
:min 0
|
||||
:on-click select-all
|
||||
:on-change on-radius-r1-change
|
||||
:value (attr->string :r1 values)}]]
|
||||
|
||||
[:div.input-element.mini {:title (tr "workspace.options.radius")}
|
||||
[:> numeric-input
|
||||
{:placeholder "--"
|
||||
:min 0
|
||||
:on-click select-all
|
||||
:on-change on-radius-r2-change
|
||||
:value (attr->string :r2 values)}]]
|
||||
|
||||
[:div.input-element.mini {:title (tr "workspace.options.radius")}
|
||||
[:> numeric-input
|
||||
{:placeholder "--"
|
||||
:min 0
|
||||
:on-click select-all
|
||||
:on-change on-radius-r3-change
|
||||
:value (attr->string :r3 values)}]]
|
||||
|
||||
[:div.input-element.mini {:title (tr "workspace.options.radius")}
|
||||
[:> numeric-input
|
||||
{:placeholder "--"
|
||||
:min 0
|
||||
:on-click select-all
|
||||
:on-change on-radius-r4-change
|
||||
:value (attr->string :r4 values)}]]])])]]]))
|
||||
[:& border-radius {:options options :ids-with-children ids-with-children :values values :ids ids}]]]]))
|
||||
|
|
|
@ -0,0 +1,190 @@
|
|||
;; 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
|
||||
|
||||
;; 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.main.ui.workspace.sidebar.options.menus.radius
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.math :as math]
|
||||
[app.common.spec.radius :as ctr]
|
||||
[app.main.data.workspace.changes :as dch]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.numeric-input :refer [numeric-input]]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
|
||||
(defn- attr->string [attr values]
|
||||
(let [value (attr values)]
|
||||
(if (= value :multiple)
|
||||
""
|
||||
(str (-> value
|
||||
(d/coalesce 0)
|
||||
(math/precision 2))))))
|
||||
|
||||
(mf/defc border-radius
|
||||
[{:keys [options ids ids-with-children values] :as props}]
|
||||
(let [options (or options #{:size :position :rotation :radius})
|
||||
|
||||
ids-with-children (or ids-with-children ids)
|
||||
|
||||
old-shapes (deref (refs/objects-by-id ids))
|
||||
frames (map #(deref (refs/object-by-id (:frame-id %))) old-shapes)
|
||||
|
||||
shapes (as-> old-shapes $
|
||||
(map gsh/transform-shape $)
|
||||
(map gsh/translate-to-frame $ frames))
|
||||
|
||||
values (let [{:keys [x y]} (-> shapes first :points gsh/points->selrect)]
|
||||
(cond-> values
|
||||
(not= (:x values) :multiple) (assoc :x x)
|
||||
(not= (:y values) :multiple) (assoc :y y)))
|
||||
|
||||
values (let [{:keys [width height]} (-> shapes first :selrect)]
|
||||
(cond-> values
|
||||
(not= (:width values) :multiple) (assoc :width width)
|
||||
(not= (:height values) :multiple) (assoc :height height)))
|
||||
|
||||
|
||||
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-switch-to-radius-1
|
||||
(mf/use-callback
|
||||
(mf/deps ids)
|
||||
(fn [_value]
|
||||
(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]
|
||||
(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]
|
||||
(st/emit! (dch/update-shapes ids-with-children #(ctr/set-radius-1 % value)))))
|
||||
|
||||
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]
|
||||
(st/emit! (dch/update-shapes ids-with-children #(ctr/set-radius-4 % attr value)))))
|
||||
|
||||
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))]
|
||||
|
||||
(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)))))
|
||||
|
||||
[:*
|
||||
;; RADIUS
|
||||
(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]]
|
||||
|
||||
(cond
|
||||
(= radius-mode :radius-1)
|
||||
[:div.input-element.mini {:title (tr "workspace.options.radius")}
|
||||
[:> 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 {:title (tr "workspace.options.radius")}
|
||||
[: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 {:title (tr "workspace.options.radius")}
|
||||
[:> numeric-input
|
||||
{:placeholder "--"
|
||||
:min 0
|
||||
:on-click select-all
|
||||
:on-change on-radius-r1-change
|
||||
:value (attr->string :r1 values)}]]
|
||||
|
||||
[:div.input-element.mini {:title (tr "workspace.options.radius")}
|
||||
[:> numeric-input
|
||||
{:placeholder "--"
|
||||
:min 0
|
||||
:on-click select-all
|
||||
:on-change on-radius-r2-change
|
||||
:value (attr->string :r2 values)}]]
|
||||
|
||||
[:div.input-element.mini {:title (tr "workspace.options.radius")}
|
||||
[:> numeric-input
|
||||
{:placeholder "--"
|
||||
:min 0
|
||||
:on-click select-all
|
||||
:on-change on-radius-r3-change
|
||||
:value (attr->string :r3 values)}]]
|
||||
|
||||
[:div.input-element.mini {:title (tr "workspace.options.radius")}
|
||||
[:> numeric-input
|
||||
{:placeholder "--"
|
||||
:min 0
|
||||
:on-click select-all
|
||||
:on-change on-radius-r4-change
|
||||
:value (attr->string :r4 values)}]]])])]))
|
|
@ -17,6 +17,7 @@
|
|||
[app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs-shape fill-menu]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.frame-grid :refer [frame-grid]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.radius :refer [border-radius]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.shadow :refer [shadow-menu]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.stroke :refer [stroke-attrs stroke-menu]]
|
||||
[app.util.dom :as dom]
|
||||
|
@ -25,10 +26,12 @@
|
|||
|
||||
(declare +size-presets+)
|
||||
|
||||
|
||||
(mf/defc measures-menu
|
||||
[{:keys [shape] :as props}]
|
||||
|
||||
(let [show-presets-dropdown? (mf/use-state false)
|
||||
id (:id shape)
|
||||
|
||||
on-preset-selected
|
||||
(fn [width height]
|
||||
|
@ -63,9 +66,7 @@
|
|||
select-all #(-> % (dom/get-target) (.select))]
|
||||
|
||||
[:div.element-set
|
||||
|
||||
[:div.element-set-content
|
||||
|
||||
[:div.row-flex
|
||||
[:div.presets.custom-select.flex-grow {:on-click #(reset! show-presets-dropdown? true)}
|
||||
[:span (tr "workspace.options.size-presets")]
|
||||
|
@ -125,7 +126,10 @@
|
|||
:on-change on-pos-y-change
|
||||
:value (-> (:y shape)
|
||||
(math/precision 2)
|
||||
(d/coalesce-str "0"))}]]]]]))
|
||||
(d/coalesce-str "0"))}]]]
|
||||
|
||||
;; RADIUS
|
||||
[:& border-radius {:ids [id] :values shape}]]]))
|
||||
|
||||
(def +size-presets+
|
||||
[{:name "APPLE"}
|
||||
|
|
Loading…
Add table
Reference in a new issue