Adds support for drop shadow

This commit is contained in:
alonso.torres 2020-09-24 16:55:50 +02:00
parent 23b53faac7
commit 64c0884eb9
15 changed files with 396 additions and 35 deletions

View file

@ -48,6 +48,11 @@ $br-big: 8px;
}
}
.row-grid-2 {
display: grid;
grid-template-columns: repeat(2, 1fr);
}
.flex-grow {
flex-grow: 1;
}

View file

@ -364,7 +364,8 @@ ul.slider-dots {
position: relative;
width: 75px;
&::after {
&::after,
.after {
color: $color-gray-20;
font-size: $fs12;
height: 20px;
@ -374,6 +375,11 @@ ul.slider-dots {
width: 20px;
}
.after {
width: auto;
right: 6px;
}
// Input amounts
&.pixels {

View file

@ -468,8 +468,8 @@
}
.color-th {
background-color: $color-gray-10;
border: 1px solid $color-gray-10;
background-color: $color-gray-30;
border: 1px solid $color-gray-30;
border-radius: $br-small;
cursor: pointer;
display: flex;
@ -765,6 +765,7 @@
top: 0;
width: 100%;
opacity: 0.4;
z-index: 10;
}
.element-set-content .advanced-options {
@ -775,6 +776,7 @@
position: relative;
top: 2px;
width: calc(100% + 16px);
z-index: 20;
}
.btn-options {
@ -815,7 +817,8 @@
}
}
.exports-options {
.exports-options,
.shadow-options{
.element-set-options-group {
justify-content: space-between;
.delete-icon {
@ -841,3 +844,35 @@
margin-top: 10px;
}
}
.shadow-options .color-row-wrap {
margin-left: 6px;
margin-top: 0.5rem;
}
.element-set-actions-button {
display: flex;
min-width: 30px;
min-height: 30px;
justify-content: center;
align-items: center;
cursor: pointer;
svg {
width: 16px;
height: 16px;
fill: $color-gray-20;
}
&:hover svg {
fill: $color-primary;
}
}
.element-set-actions {
display: flex;
visibility: hidden;
}
.element-set-options-group:hover .element-set-actions {
visibility: visible;
}

View file

@ -11,6 +11,7 @@
"The main logic for SVG export functionality."
(:require
[rumext.alpha :as mf]
[cuerdas.core :as str]
[app.common.uuid :as uuid]
[app.common.pages :as cp]
[app.common.pages-helpers :as cph]
@ -18,6 +19,7 @@
[app.common.geom.shapes :as geom]
[app.common.geom.point :as gpt]
[app.common.geom.matrix :as gmt]
[app.main.ui.shapes.filters :as filters]
[app.main.ui.shapes.frame :as frame]
[app.main.ui.shapes.circle :as circle]
[app.main.ui.shapes.icon :as icon]
@ -77,18 +79,21 @@
frame-wrapper (mf/use-memo (mf/deps objects) #(frame-wrapper-factory objects))]
(when (and shape (not (:hidden shape)))
(let [shape (geom/transform-shape frame shape)
opts #js {:shape shape}]
(case (:type shape)
:curve [:> path/path-shape opts]
:text [:> text/text-shape opts]
:icon [:> icon/icon-shape opts]
:rect [:> rect/rect-shape opts]
:path [:> path/path-shape opts]
:image [:> image/image-shape opts]
:circle [:> circle/circle-shape opts]
:frame [:> frame-wrapper {:shape shape}]
:group [:> group-wrapper {:shape shape :frame frame}]
nil))))))
opts #js {:shape shape}
filter-id (filters/get-filter-id)]
[:g {:filter (filters/filter-str filter-id shape)}
[:& filters/filters {:filter-id filter-id :shape shape}]
(case (:type shape)
:curve [:> path/path-shape opts]
:text [:> text/text-shape opts]
:icon [:> icon/icon-shape opts]
:rect [:> rect/rect-shape opts]
:path [:> path/path-shape opts]
:image [:> image/image-shape opts]
:circle [:> circle/circle-shape opts]
:frame [:> frame-wrapper {:shape shape}]
:group [:> group-wrapper {:shape shape :frame frame}]
nil)])))))
(mf/defc page-svg
{::mf/wrap [mf/memo]}

View file

@ -21,7 +21,8 @@
(defn extract-style-attrs
[shape]
(let [stroke-style (:stroke-style shape :none)
attrs #js {:fill (or (:fill-color shape) "transparent")
attrs #js {;:filter (when (not= :frame (:type shape)) (str "url(#filter_" (:id shape) ")"))
:fill (or (:fill-color shape) "transparent")
:fillOpacity (:fill-opacity shape nil)
:rx (:rx shape nil)
:ry (:ry shape nil)}]

View file

@ -7,6 +7,7 @@
(ns app.main.ui.shapes.custom-stroke
(:require
[rumext.alpha :as mf]
[app.common.uuid :as uuid]
[app.common.geom.shapes :as geom]
[app.util.object :as obj]))
@ -21,7 +22,8 @@
(let [shape (unchecked-get props "shape")
base-props (unchecked-get props "base-props")
elem-name (unchecked-get props "elem-name")
{:keys [id x y width height]} (geom/shape->rect-shape shape)
{:keys [x y width height]} (geom/shape->rect-shape shape)
id (uuid/next)
stroke-style (:stroke-style shape :none)
stroke-position (:stroke-alignment shape :center)]
(cond

View file

@ -0,0 +1,77 @@
;; 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/.
;;
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns app.main.ui.shapes.filters
(:require
[rumext.alpha :as mf]
[cuerdas.core :as str]
[app.util.color :as color]
[app.common.math :as mth]
[app.common.uuid :as uuid]))
(defn get-filter-id []
(str "filter_" (uuid/next)))
(defn filter-str
[filter-id shape]
(when (seq (:shadow shape))
(str/fmt "url(#$0)" [filter-id])))
(mf/defc drop-shadow-filter
[{:keys [filter-id filter shape]}]
(let [{:keys [x y width height]} (:selrect shape)
{:keys [fid color opacity offset-x offset-y blur spread]} filter
filter-x (min x (+ x offset-x (- spread) (- blur) -5))
filter-y (min y (+ y offset-y (- spread) (- blur) -5))
filter-width (+ width (mth/abs offset-x) (* spread 2) (* blur 2) 10)
filter-height (+ height (mth/abs offset-x) (* spread 2) (* blur 2) 10)
[r g b a] (color/hex->rgba color opacity)
[r g b] [(/ r 255) (/ g 255) (/ b 255)]
color-matrix (str/fmt "0 0 0 0 $0 0 0 0 0 $1 0 0 0 0 $2 0 0 0 $3 0" [r g b a])]
[:filter {:id filter-id
:x filter-x :y filter-y
:width filter-width :height filter-height
:filterUnits "userSpaceOnUse"
:color-interpolation-filters "sRGB"}
[:feFlood {:flood-opacity 0 :result "BackgroundImageFix"}]
[:feColorMatrix {:in "SourceAlpha" :type "matrix"
:values "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"}]
(when (> spread 0)
[:feMorphology {:radius spread
:operator "dilate"
:in "SourceAlpha"
:result "effect1_dropShadow"}])
[:feOffset {:dx offset-x :dy offset-y}]
[:feGaussianBlur {:stdDeviation (/ blur 2)}]
[:feColorMatrix {:type "matrix" :values color-matrix}]
[:feBlend {:mode "normal"
:in2 "BackgroundImageFix"
:result "effect1_dropShadow"}]
[:feBlend {:mode "normal"
:in "SourceGraphic"
:in2 "effect1_dropShadow"
:result "shape"}]]))
(mf/defc filters
[{:keys [filter-id shape]}]
[:defs
(for [{:keys [id type hidden] :as filter} (:shadow shape)]
(when (not hidden)
[:& drop-shadow-filter {:key id
:filter-id filter-id
:filter filter
:shape shape}]))])

View file

@ -11,12 +11,15 @@
"The main container for a frame in viewer mode"
(:require
[rumext.alpha :as mf]
[cuerdas.core :as str]
[app.common.uuid :as uuid]
[app.common.data :as d]
[app.common.pages :as cp]
[app.common.pages-helpers :as cph]
[app.main.data.viewer :as dv]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.shapes.filters :as filters]
[app.main.ui.shapes.circle :as circle]
[app.main.ui.shapes.frame :as frame]
[app.main.ui.shapes.group :as group]
@ -53,10 +56,14 @@
on-mouse-down (mf/use-callback
(mf/deps shape)
#(on-mouse-down % shape))]
#(on-mouse-down % shape))
filter-id (filters/get-filter-id)]
[:g.shape {:on-mouse-down on-mouse-down
:cursor (when (:interactions shape) "pointer")}
:cursor (when (:interactions shape) "pointer")
:filter (filters/filter-str filter-id shape)}
[:& filters/filters {:filter-id filter-id :shape shape}]
[:& component {:shape shape
:frame frame
:childs childs

View file

@ -14,6 +14,7 @@
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.keyboard :as kbd]
[app.main.ui.shapes.filters :as filters]
[app.util.dom :as dom]
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
@ -69,9 +70,12 @@
#(on-mouse-down % shape))
on-context-menu (mf/use-callback
(mf/deps shape)
#(on-context-menu % shape))]
#(on-context-menu % shape))
filter-id (filters/get-filter-id)]
[:g.shape {:on-mouse-down on-mouse-down
:on-context-menu on-context-menu}
:on-context-menu on-context-menu
:filter (filters/filter-str filter-id shape)}
[:& filters/filters {:filter-id filter-id :shape shape}]
[:& component {:shape shape}]])))

View file

@ -0,0 +1,24 @@
;; 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/.
;;
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns app.main.ui.workspace.sidebar.options.common
(:require
[rumext.alpha :as mf]
[app.util.dom :as dom]))
(mf/defc advanced-options [{:keys [visible? on-close children]}]
(let [handle-click (fn [event] (when on-close
(do (dom/stop-propagation event)
(on-close))))]
(when visible?
[:*
[:div.focus-overlay {:on-click handle-click}]
[:div.advanced-options {}
children]])))

View file

@ -20,6 +20,7 @@
[app.main.data.workspace.grid :as dw]
[app.util.geom.grid :as gg]
[app.main.ui.icons :as i]
[app.main.ui.workspace.sidebar.options.common :refer [advanced-options]]
[app.main.ui.workspace.sidebar.options.rows.color-row :refer [color-row]]
[app.main.ui.workspace.sidebar.options.rows.input-row :refer [input-row]]
[app.main.ui.components.select :refer [select]]
@ -30,16 +31,6 @@
(def workspace-saved-grids
(l/derived :saved-grids refs/workspace-page-options))
(mf/defc advanced-options [{:keys [visible? on-close children]}]
(when visible?
[:*
[:div.focus-overlay {:on-click #(when on-close
(do
(dom/stop-propagation %)
(on-close)))}]
[:div.advanced-options {}
children]]))
(defn- get-size-options [locale]
[{:value :auto :label (t locale "workspace.options.grid.auto")}
:separator
@ -254,3 +245,4 @@
:on-remove (handle-remove-grid index)
:on-save-grid handle-save-grid}])])]))

View file

@ -12,7 +12,8 @@
[rumext.alpha :as mf]
[app.main.ui.workspace.sidebar.options.measures :refer [measure-attrs measures-menu]]
[app.main.ui.workspace.sidebar.options.fill :refer [fill-attrs fill-menu]]
[app.main.ui.workspace.sidebar.options.stroke :refer [stroke-attrs stroke-menu]]))
[app.main.ui.workspace.sidebar.options.stroke :refer [stroke-attrs stroke-menu]]
[app.main.ui.workspace.sidebar.options.shadow :refer [shadow-menu]]))
(mf/defc options
{::mf/wrap [mf/memo]}
@ -31,5 +32,8 @@
:values fill-values}]
[:& stroke-menu {:ids ids
:type type
:values stroke-values}]]))
:values stroke-values}]
[:& shadow-menu {:ids ids
:type type
:values (select-keys shape [:shadow])}]]))

View file

@ -0,0 +1,195 @@
;; 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/.
;;
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns app.main.ui.workspace.sidebar.options.shadow
(:require
[rumext.alpha :as mf]
[app.common.data :as d]
[app.common.uuid :as uuid]
[app.main.data.workspace.common :as dwc]
[app.main.store :as st]
[app.main.ui.icons :as i]
[app.main.ui.workspace.sidebar.options.common :refer [advanced-options]]
[app.main.ui.workspace.sidebar.options.rows.input-row :refer [input-row]]
[app.main.ui.workspace.sidebar.options.rows.color-row :refer [color-row]]
[app.util.dom :as dom]))
(defn create-shadow []
(let [id (uuid/next)]
{:id id
:style :drop-shadow
:color "#000000"
:opacity 0.2
:offset-x 4
:offset-y 4
:blur 4
:spread 0
:hidden false}))
(defn valid-number? [value]
(or (number? value) (not (js/isNaN (js/parseInt value)))))
(mf/defc shadow-entry
[{:keys [ids index value]}]
(let [open-shadow (mf/use-state false)
basic-offset-x-ref (mf/use-ref nil)
basic-offset-y-ref (mf/use-ref nil)
basic-blur-ref (mf/use-ref nil)
adv-offset-x-ref (mf/use-ref nil)
adv-offset-y-ref (mf/use-ref nil)
adv-blur-ref (mf/use-ref nil)
adv-spread-ref (mf/use-ref nil)
remove-shadow-by-id
(fn [values id] (->> values (filterv (fn [s] (not= (:id s) id)))))
on-remove-shadow
(fn [id]
(fn []
(st/emit! (dwc/update-shapes ids #(update % :shadow remove-shadow-by-id id) ))))
select-text
(fn [ref] (fn [event] (dom/select-text! (mf/ref-val ref))))
update-attr
(fn update-attr
([index attr valid?]
(update-attr index attr valid? nil))
([index attr valid? update-ref]
(fn [event]
(let [value (dom/get-value (dom/get-target event))]
(when (or (not valid?) (valid? value))
(do
(when update-ref
(dom/set-value! (mf/ref-val update-ref) value))
(st/emit! (dwc/update-shapes ids #(assoc-in % [:shadow index attr] (js/parseInt value 10))))))))))
update-color
(fn [index]
(fn [color opacity]
(st/emit! (dwc/update-shapes
ids
#(-> %
(assoc-in [:shadow index :color] color)
(assoc-in [:shadow index :opacity] opacity))))))
toggle-visibility
(fn [index]
(fn []
(st/emit! (dwc/update-shapes ids #(update-in % [:shadow index :hidden] not)))))]
[:*
[:div.element-set-options-group
[:div.element-set-actions-button
{:on-click #(reset! open-shadow true)}
i/actions]
[:input.input-text {:type "number"
:ref basic-offset-x-ref
:on-change (update-attr index :offset-x valid-number?)
:on-click (select-text basic-offset-x-ref)
:default-value (:offset-x value)}]
[:input.input-text {:type "number"
:ref basic-offset-y-ref
:on-change (update-attr index :offset-y valid-number?)
:on-click (select-text basic-offset-y-ref)
:default-value (:offset-y value)}]
[:input.input-text {:type "number"
:ref basic-blur-ref
:on-click (select-text basic-blur-ref)
:on-change (update-attr index :blur valid-number?)
:min 0
:default-value (:blur value)}]
[:div.element-set-actions
[:div.element-set-actions-button {:on-click (toggle-visibility index)}
(if (:hidden value) i/eye-closed i/eye)]
[:div.element-set-actions-button {:on-click (on-remove-shadow (:id value))}
i/minus]]]
[:& advanced-options {:visible? @open-shadow
:on-close #(reset! open-shadow false)}
[:div.row-grid-2
[:select.input-select
[:option {:value ":drop-shadow"} "Drop shadow"]
#_[:option {:value ":inner-shadow"} "Inner shadow"]]]
[:div.row-grid-2
[:div.input-element
[:input.input-text {:type "number"
:ref adv-offset-x-ref
:no-validate true
:placeholder "--"
:on-click (select-text adv-offset-x-ref)
:on-change (update-attr index :offset-x valid-number? basic-offset-x-ref)
:default-value (:offset-x value)}]
[:span.after "X"]]
[:div.input-element
[:input.input-text {:type "number"
:ref adv-offset-y-ref
:no-validate true
:placeholder "--"
:on-click (select-text adv-offset-y-ref)
:on-change (update-attr index :offset-y valid-number? basic-offset-y-ref)
:default-value (:offset-y value)}]
[:span.after "Y"]]]
[:div.row-grid-2
[:div.input-element
[:input.input-text {:type "number"
:ref adv-blur-ref
:no-validate true
:placeholder "--"
:on-click (select-text adv-blur-ref)
:on-change (update-attr index :blur valid-number? basic-blur-ref)
:min 0
:default-value (:blur value)}]
[:span.after "Blur"]]
[:div.input-element
[:input.input-text {:type "number"
:ref adv-spread-ref
:no-validate true
:placeholder "--"
:on-click (select-text adv-spread-ref)
:on-change (update-attr index :spread valid-number?)
:min 0
:default-value (:spread value)}]
[:span.after "Spread"]]]
[:div.color-row-wrap
[:& color-row {:color {:value (:color value) :opacity (:opacity value)}
:on-change (update-color index)
:on-open #(st/emit! dwc/start-undo-transaction)
:on-close #(st/emit! dwc/commit-undo-transaction)}]]]]))
(mf/defc shadow-menu
[{:keys [ids type values] :as props}]
(.log js/console "values" (clj->js values))
(let [on-add-shadow
(fn []
(st/emit! (dwc/update-shapes ids #(update % :shadow (fnil conj []) (create-shadow)) )))]
[:div.element-set.shadow-options
[:div.element-set-title
[:span "Shadow"]
[:div.add-page {:on-click on-add-shadow} i/close]]
(when (seq (:shadow values))
[:div.element-set-content
(for [[index {:keys [id] :as value}] (d/enumerate (:shadow values []))]
[:& shadow-entry {:key (str "shadow-" id)
:ids ids
:value value
:index index}])])]))

View file

@ -70,7 +70,7 @@
:group (t locale "workspace.options.group-stroke")
(t locale "workspace.options.stroke"))
show-options (not= (or (:stroke-style values) :none) :none)
show-options (not= (:stroke-style values :none) :none)
current-stroke-color {:value (:stroke-color values)
:opacity (:stroke-opacity values)

View file

@ -103,6 +103,10 @@
[node]
(set! (.-value node) ""))
(defn set-value!
[node value]
(set! (.-value node) value))
(defn select-text!
[node]
(.select node))