🎉 Add stroke caps to path ends

This commit is contained in:
Andrés Moya 2021-08-16 12:56:44 +02:00 committed by Andrey Antukh
parent ac6c07b771
commit be9073f0b7
11 changed files with 292 additions and 8 deletions

View file

@ -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))

View file

@ -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))

View file

@ -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)))

View file

@ -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

View file

@ -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

View file

@ -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}])]))

View file

@ -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])}]