mirror of
https://github.com/penpot/penpot.git
synced 2025-07-18 20:07:13 +02:00
🎉 Add stroke caps to path ends
This commit is contained in:
parent
ac6c07b771
commit
be9073f0b7
11 changed files with 292 additions and 8 deletions
|
@ -31,7 +31,7 @@
|
|||
local-ref (mf/use-ref)
|
||||
ref (or external-ref local-ref)
|
||||
|
||||
value (d/parse-integer value-str)
|
||||
value (d/parse-integer value-str 0)
|
||||
|
||||
min-val (when (string? min-val-str)
|
||||
(d/parse-integer min-val-str))
|
||||
|
|
|
@ -124,6 +124,7 @@
|
|||
(def sort-descending (icon-xref :sort-descending))
|
||||
(def strikethrough (icon-xref :strikethrough))
|
||||
(def stroke (icon-xref :stroke))
|
||||
(def switch (icon-xref :switch))
|
||||
(def text (icon-xref :text))
|
||||
(def text-align-center (icon-xref :text-align-center))
|
||||
(def text-align-justify (icon-xref :text-align-justify))
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
(ns app.main.ui.shapes.attrs
|
||||
(:require
|
||||
[app.common.pages.spec :as spec]
|
||||
[app.main.ui.context :as muc]
|
||||
[app.util.object :as obj]
|
||||
[app.util.svg :as usvg]
|
||||
|
@ -117,7 +118,29 @@
|
|||
(assoc :strokeOpacity (:stroke-opacity shape nil))
|
||||
|
||||
(not= stroke-style :svg)
|
||||
(assoc :strokeDasharray (stroke-type->dasharray stroke-style)))]
|
||||
(assoc :strokeDasharray (stroke-type->dasharray stroke-style))
|
||||
|
||||
;; For simple line caps we use svg stroke-line-cap attribute. This
|
||||
;; only works if all caps are the same and we are not using the tricks
|
||||
;; for inner or outer strokes.
|
||||
(and (spec/stroke-caps-line (:stroke-cap-start shape))
|
||||
(= (:stroke-cap-start shape) (:stroke-cap-end shape))
|
||||
(= (:stroke-alignment shape) :center))
|
||||
(assoc :strokeLinecap (:stroke-cap-start shape))
|
||||
|
||||
;; For other cap types we use markers.
|
||||
(and (or (spec/stroke-caps-marker (:stroke-cap-start shape))
|
||||
(and (spec/stroke-caps-line (:stroke-cap-start shape))
|
||||
(not= (:stroke-cap-start shape) (:stroke-cap-end shape))))
|
||||
(= (:stroke-alignment shape) :center))
|
||||
(assoc :markerStart (str "url(#marker-" render-id "-" (name (:stroke-cap-start shape))))
|
||||
|
||||
(and (or (spec/stroke-caps-marker (:stroke-cap-end shape))
|
||||
(and (spec/stroke-caps-line (:stroke-cap-end shape))
|
||||
(not= (:stroke-cap-start shape) (:stroke-cap-end shape))))
|
||||
(= (:stroke-alignment shape) :center))
|
||||
(assoc :markerEnd (str "url(#marker-" render-id "-" (name (:stroke-cap-end shape)))))]
|
||||
|
||||
(obj/merge! attrs (clj->js stroke-attrs)))
|
||||
attrs)))
|
||||
|
||||
|
|
|
@ -42,6 +42,107 @@
|
|||
[:use {:xlinkHref (str "#" shape-id)
|
||||
:style #js {:fill "black"}}]]))
|
||||
|
||||
(mf/defc cap-markers
|
||||
[{:keys [shape render-id]}]
|
||||
(let [marker-id-prefix (str "marker-" render-id)
|
||||
cap-start (:stroke-cap-start shape)
|
||||
cap-end (:stroke-cap-end shape)
|
||||
|
||||
stroke-color (if (:stroke-color-gradient shape)
|
||||
(str/format "url(#%s)" (str "stroke-color-gradient_" render-id))
|
||||
(:stroke-color shape))
|
||||
|
||||
stroke-opacity (when-not (:stroke-color-gradient shape)
|
||||
(:stroke-opacity shape))]
|
||||
[:*
|
||||
(when (or (= cap-start :line-arrow) (= cap-end :line-arrow))
|
||||
[:marker {:id (str marker-id-prefix "-line-arrow")
|
||||
:viewBox "0 0 3 6"
|
||||
:refX "2"
|
||||
:refY "3"
|
||||
:markerWidth "3"
|
||||
:markerHeight "6"
|
||||
:orient "auto-start-reverse"
|
||||
:fill stroke-color
|
||||
:fillOpacity stroke-opacity}
|
||||
[:path {:d "M 0.5 0.5 L 3 3 L 0.5 5.5 L 0 5 L 2 3 L 0 1 z"}]])
|
||||
|
||||
(when (or (= cap-start :triangle-arrow) (= cap-end :triangle-arrow))
|
||||
[:marker {:id (str marker-id-prefix "-triangle-arrow")
|
||||
:viewBox "0 0 3 6"
|
||||
:refX "2"
|
||||
:refY "3"
|
||||
:markerWidth "3"
|
||||
:markerHeight "6"
|
||||
:orient "auto-start-reverse"
|
||||
:fill stroke-color
|
||||
:fillOpacity stroke-opacity}
|
||||
[:path {:d "M 0 0 L 3 3 L 0 6 z"}]])
|
||||
|
||||
(when (or (= cap-start :square-marker) (= cap-end :square-marker))
|
||||
[:marker {:id (str marker-id-prefix "-square-marker")
|
||||
:viewBox "0 0 6 6"
|
||||
:refX "5"
|
||||
:refY "3"
|
||||
:markerWidth "6"
|
||||
:markerHeight "6"
|
||||
:orient "auto-start-reverse"
|
||||
:fill stroke-color
|
||||
:fillOpacity stroke-opacity}
|
||||
[:rect {:x 0 :y 0 :width 6 :height 6}]])
|
||||
|
||||
(when (or (= cap-start :circle-marker) (= cap-end :circle-marker))
|
||||
[:marker {:id (str marker-id-prefix "-circle-marker")
|
||||
:viewBox "0 0 6 6"
|
||||
:refX "5"
|
||||
:refY "3"
|
||||
:markerWidth "6"
|
||||
:markerHeight "6"
|
||||
:orient "auto-start-reverse"
|
||||
:fill stroke-color
|
||||
:fillOpacity stroke-opacity}
|
||||
[:circle {:cx "3" :cy "3" :r "3"}]])
|
||||
|
||||
(when (or (= cap-start :diamond-marker) (= cap-end :diamond-marker))
|
||||
[:marker {:id (str marker-id-prefix "-diamond-marker")
|
||||
:viewBox "0 0 6 6"
|
||||
:refX "5"
|
||||
:refY "3"
|
||||
:markerWidth "6"
|
||||
:markerHeight "6"
|
||||
:orient "auto-start-reverse"
|
||||
:fill stroke-color
|
||||
:fillOpacity stroke-opacity}
|
||||
[:path {:d "M 3 0 L 6 3 L 3 6 L 0 3 z"}]])
|
||||
|
||||
;; If the user wants line caps but different in each end,
|
||||
;; simulate it with markers.
|
||||
(when (and (or (= cap-start :round) (= cap-end :round))
|
||||
(not= cap-start cap-end))
|
||||
[:marker {:id (str marker-id-prefix "-round")
|
||||
:viewBox "0 0 6 6"
|
||||
:refX "3"
|
||||
:refY "3"
|
||||
:markerWidth "6"
|
||||
:markerHeight "6"
|
||||
:orient "auto-start-reverse"
|
||||
:fill stroke-color
|
||||
:fillOpacity stroke-opacity}
|
||||
[:path {:d "M 3 2.5 A 0.5 0.5 0 0 1 3 3.5 "}]])
|
||||
|
||||
(when (and (or (= cap-start :square) (= cap-end :square))
|
||||
(not= cap-start cap-end))
|
||||
[:marker {:id (str marker-id-prefix "-square")
|
||||
:viewBox "0 0 6 6"
|
||||
:refX "3"
|
||||
:refY "3"
|
||||
:markerWidth "6"
|
||||
:markerHeight "6"
|
||||
:orient "auto-start-reverse"
|
||||
:fill stroke-color
|
||||
:fillOpacity stroke-opacity}
|
||||
[:rect {:x 3 :y 2.5 :width 0.5 :height 1}]])]))
|
||||
|
||||
(mf/defc stroke-defs
|
||||
[{:keys [shape render-id]}]
|
||||
(cond
|
||||
|
@ -53,7 +154,13 @@
|
|||
(and (= :outer (:stroke-alignment shape :center))
|
||||
(> (:stroke-width shape 0) 0))
|
||||
[:& outer-stroke-mask {:shape shape
|
||||
:render-id render-id}]))
|
||||
:render-id render-id}]
|
||||
|
||||
(and (or (some? (:stroke-cap-start shape))
|
||||
(some? (:stroke-cap-end shape)))
|
||||
(= (:stroke-alignment shape) :center))
|
||||
[:& cap-markers {:shape shape
|
||||
:render-id render-id}]))
|
||||
|
||||
;; Outer alingmnent: display the shape in two layers. One
|
||||
;; without stroke (only fill), and another one only with stroke
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.math :as math]
|
||||
[app.common.pages.spec :as spec]
|
||||
[app.main.data.workspace.changes :as dch]
|
||||
[app.main.data.workspace.colors :as dc]
|
||||
[app.main.data.workspace.undo :as dwu]
|
||||
|
@ -27,7 +28,9 @@
|
|||
:stroke-color-ref-id
|
||||
:stroke-color-ref-file
|
||||
:stroke-opacity
|
||||
:stroke-color-gradient])
|
||||
:stroke-color-gradient
|
||||
:stroke-cap-start
|
||||
:stroke-cap-end])
|
||||
|
||||
(defn- width->string [width]
|
||||
(if (= width :multiple)
|
||||
|
@ -42,8 +45,8 @@
|
|||
(pr-str value)))
|
||||
|
||||
(mf/defc stroke-menu
|
||||
{::mf/wrap [#(mf/memo' % (mf/check-props ["ids" "values" "type"]))]}
|
||||
[{:keys [ids type values] :as props}]
|
||||
{::mf/wrap [#(mf/memo' % (mf/check-props ["ids" "values" "type" "show-caps"]))]}
|
||||
[{:keys [ids type values show-caps] :as props}]
|
||||
(let [label (case type
|
||||
:multiple (tr "workspace.options.selection-stroke")
|
||||
:group (tr "workspace.options.group-stroke")
|
||||
|
@ -51,6 +54,8 @@
|
|||
|
||||
show-options (not= (:stroke-style values :none) :none)
|
||||
|
||||
show-caps (and show-caps (= (:stroke-alignment values) :center))
|
||||
|
||||
current-stroke-color {:color (:stroke-color values)
|
||||
:opacity (:stroke-opacity values)
|
||||
:id (:stroke-color-ref-id values)
|
||||
|
@ -94,6 +99,38 @@
|
|||
(when-not (str/empty? value)
|
||||
(st/emit! (dch/update-shapes ids #(assoc % :stroke-width value))))))
|
||||
|
||||
update-cap-attr
|
||||
(fn [& kvs]
|
||||
#(if (spec/has-caps? %)
|
||||
(apply (partial assoc %) kvs)
|
||||
%))
|
||||
|
||||
on-stroke-cap-start-change
|
||||
(fn [event]
|
||||
(let [value (-> (dom/get-target event)
|
||||
(dom/get-value)
|
||||
(d/read-string))]
|
||||
(when-not (str/empty? value)
|
||||
(st/emit! (dch/update-shapes ids (update-cap-attr :stroke-cap-start value))))))
|
||||
|
||||
on-stroke-cap-end-change
|
||||
(fn [event]
|
||||
(let [value (-> (dom/get-target event)
|
||||
(dom/get-value)
|
||||
(d/read-string))]
|
||||
(when-not (str/empty? value)
|
||||
(st/emit! (dch/update-shapes ids (update-cap-attr :stroke-cap-end value))))))
|
||||
|
||||
on-stroke-cap-switch
|
||||
(fn [_]
|
||||
(let [stroke-cap-start (:stroke-cap-start values)
|
||||
stroke-cap-end (:stroke-cap-end values)]
|
||||
(when (and (not= stroke-cap-start :multiple)
|
||||
(not= stroke-cap-end :multiple))
|
||||
(st/emit! (dch/update-shapes ids (update-cap-attr
|
||||
:stroke-cap-start stroke-cap-end
|
||||
:stroke-cap-end stroke-cap-start))))))
|
||||
|
||||
on-add-stroke
|
||||
(fn [_]
|
||||
(st/emit! (dch/update-shapes ids #(assoc %
|
||||
|
@ -157,7 +194,39 @@
|
|||
[:option {:value ":solid"} (tr "workspace.options.stroke.solid")]
|
||||
[:option {:value ":dotted"} (tr "workspace.options.stroke.dotted")]
|
||||
[:option {:value ":dashed"} (tr "workspace.options.stroke.dashed")]
|
||||
[:option {:value ":mixed"} (tr "workspace.options.stroke.mixed")]]]]]
|
||||
[:option {:value ":mixed"} (tr "workspace.options.stroke.mixed")]]]
|
||||
|
||||
;; Stroke Caps
|
||||
(when show-caps
|
||||
[:div.row-flex
|
||||
[:select#style.input-select {:value (enum->string (:stroke-cap-start values))
|
||||
:on-change on-stroke-cap-start-change}
|
||||
(when (= (:stroke-cap-start values) :multiple)
|
||||
[:option {:value ""} "--"])
|
||||
[:option {:value ""} (tr "workspace.options.stroke-cap.none")]
|
||||
[:option {:value ":line-arrow"} (tr "workspace.options.stroke-cap.line-arrow")]
|
||||
[:option {:value ":triangle-arrow"} (tr "workspace.options.stroke-cap.triangle-arrow")]
|
||||
[:option {:value ":square-marker"} (tr "workspace.options.stroke-cap.square-marker")]
|
||||
[:option {:value ":circle-marker"} (tr "workspace.options.stroke-cap.circle-marker")]
|
||||
[:option {:value ":diamond-marker"} (tr "workspace.options.stroke-cap.diamond-marker")]
|
||||
[:option {:value ":round"} (tr "workspace.options.stroke-cap.round")]
|
||||
[:option {:value ":square"} (tr "workspace.options.stroke-cap.square")]]
|
||||
|
||||
[:div.element-set-actions-button {:on-click on-stroke-cap-switch}
|
||||
i/switch]
|
||||
|
||||
[:select#style.input-select {:value (enum->string (:stroke-cap-end values))
|
||||
:on-change on-stroke-cap-end-change}
|
||||
(when (= (:stroke-cap-end values) :multiple)
|
||||
[:option {:value ""} "--"])
|
||||
[:option {:value ""} (tr "workspace.options.stroke-cap.none")]
|
||||
[:option {:value ":line-arrow"} (tr "workspace.options.stroke-cap.line-arrow")]
|
||||
[:option {:value ":triangle-arrow"} (tr "workspace.options.stroke-cap.triangle-arrow")]
|
||||
[:option {:value ":square-marker"} (tr "workspace.options.stroke-cap.square-marker")]
|
||||
[:option {:value ":circle-marker"} (tr "workspace.options.stroke-cap.circle-marker")]
|
||||
[:option {:value ":diamond-marker"} (tr "workspace.options.stroke-cap.diamond-marker")]
|
||||
[:option {:value ":round"} (tr "workspace.options.stroke-cap.round")]
|
||||
[:option {:value ":square"} (tr "workspace.options.stroke-cap.square")]]])]]
|
||||
|
||||
;; NO STROKE
|
||||
[:div.element-set
|
||||
|
|
|
@ -215,7 +215,7 @@
|
|||
[:& blur-menu {:type type :ids blur-ids :values blur-values}])
|
||||
|
||||
(when-not (empty? stroke-ids)
|
||||
[:& stroke-menu {:type type :ids stroke-ids :values stroke-values}])
|
||||
[:& stroke-menu {:type type :ids stroke-ids :show-caps true :values stroke-values}])
|
||||
|
||||
(when-not (empty? text-ids)
|
||||
[:& ot/text-menu {:type type :ids text-ids :values text-values}])]))
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
:values (select-keys shape fill-attrs)}]
|
||||
[:& stroke-menu {:ids ids
|
||||
:type type
|
||||
:show-caps true
|
||||
:values stroke-values}]
|
||||
[:& shadow-menu {:ids ids
|
||||
:values (select-keys shape [:shadow])}]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue