From 4b22615f9729f950d14839bb09b4c9b25643ca96 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 8 Dec 2020 15:33:53 +0100 Subject: [PATCH] :bug: Fixed issues with masks when coloring --- .../styles/main/partials/sidebar-layers.scss | 54 +++-- frontend/src/app/main/data/workspace.cljs | 148 ++----------- .../src/app/main/data/workspace/groups.cljs | 199 ++++++++++++++++++ .../app/main/data/workspace/libraries.cljs | 6 +- .../app/main/data/workspace/selection.cljs | 85 -------- frontend/src/app/main/ui/shapes/group.cljs | 59 +++--- frontend/src/app/main/ui/shapes/mask.cljs | 35 +++ 7 files changed, 304 insertions(+), 282 deletions(-) create mode 100644 frontend/src/app/main/data/workspace/groups.cljs create mode 100644 frontend/src/app/main/ui/shapes/mask.cljs diff --git a/frontend/resources/styles/main/partials/sidebar-layers.scss b/frontend/resources/styles/main/partials/sidebar-layers.scss index a0df8eaf2..d31e5c7b7 100644 --- a/frontend/resources/styles/main/partials/sidebar-layers.scss +++ b/frontend/resources/styles/main/partials/sidebar-layers.scss @@ -144,38 +144,36 @@ } } -.element-list li.masked { - .element-children { - li:first-child { - position: relative; +.element-list li.masked > .element-children > li { + &:first-child { + position: relative; - &::before { - content: " "; - border-right: 1px solid $color-gray-40; - border-top: 1px solid $color-gray-40; - position: absolute; - width: 6px; - height: 6px; - transform: rotate(-45deg); - top: -1px; - left: -4px; - } + &::before { + content: " "; + border-right: 1px solid $color-gray-40; + border-top: 1px solid $color-gray-40; + position: absolute; + width: 6px; + height: 6px; + transform: rotate(-45deg); + top: -1px; + left: -4px; } + } - li:last-child { - border-left: none; - position: relative; + &:last-child { + border-left: none; + position: relative; - &::after { - content: " "; - border-left: 1px solid $color-gray-40; - border-bottom: 1px solid $color-gray-40; - height: 1rem; - width: 0.3rem; - position: absolute; - top: 0; - left: 0; - } + &::after { + content: " "; + border-left: 1px solid $color-gray-40; + border-bottom: 1px solid $color-gray-40; + height: 1rem; + width: 0.3rem; + position: absolute; + top: 0; + left: 0; } } } diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 481a6eff4..126ed925b 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -32,6 +32,7 @@ [app.main.data.workspace.selection :as dws] [app.main.data.workspace.texts :as dwtxt] [app.main.data.workspace.transforms :as dwt] + [app.main.data.workspace.groups :as dwg] [app.main.data.workspace.drawing :as dwd] [app.main.data.workspace.drawing.path :as dwdp] [app.main.repo :as rp] @@ -1011,11 +1012,12 @@ (ptk/reify ::set-shape-proportion-lock ptk/WatchEvent (watch [_ state stream] - (rx/of (dwc/update-shapes [id] (fn [shape] - (if-not lock - (assoc shape :proportion-lock false) - (-> (assoc shape :proportion-lock true) - (gpr/assign-proportions))))))))) + (letfn [(assign-proportions [shape] + (if-not lock + (assoc shape :proportion-lock false) + (-> (assoc shape :proportion-lock true) + (gpr/assign-proportions))))] + (rx/of (dwc/update-shapes [id] assign-proportions)))))) ;; --- Update Shape Position @@ -1371,135 +1373,6 @@ (with-meta params {:on-success image-uploaded}))))))) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; GROUPS -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(def group-selected - (ptk/reify ::group-selected - ptk/WatchEvent - (watch [_ state stream] - (let [page-id (:current-page-id state) - objects (dwc/lookup-page-objects state page-id) - selected (get-in state [:workspace-local :selected]) - shapes (dws/shapes-for-grouping objects selected)] - (when-not (empty? shapes) - (let [[group rchanges uchanges] (dws/prepare-create-group page-id shapes "Group-" false)] - (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}) - (dwc/select-shapes (d/ordered-set (:id group)))))))))) - -(def ungroup-selected - (ptk/reify ::ungroup-selected - ptk/WatchEvent - (watch [_ state stream] - (let [page-id (:current-page-id state) - objects (dwc/lookup-page-objects state page-id) - selected (get-in state [:workspace-local :selected]) - group-id (first selected) - group (get objects group-id)] - (when (and (= 1 (count selected)) - (= (:type group) :group)) - (let [[rchanges uchanges] - (dws/prepare-remove-group page-id group objects)] - (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})))))))) - -(def mask-group - (ptk/reify ::mask-group - ptk/WatchEvent - (watch [_ state stream] - (let [page-id (:current-page-id state) - objects (dwc/lookup-page-objects state page-id) - selected (get-in state [:workspace-local :selected]) - shapes (dws/shapes-for-grouping objects selected)] - (when-not (empty? shapes) - (let [;; If the selected shape is a group, we can use it. If not, - ;; create a new group and set it as masked. - [group rchanges uchanges] - (if (and (= (count shapes) 1) - (= (:type (first shapes)) :group)) - [(first shapes) [] []] - (dws/prepare-create-group page-id shapes "Group-" true)) - - rchanges (d/concat rchanges - [{:type :mod-obj - :page-id page-id - :id (:id group) - :operations [{:type :set - :attr :masked-group? - :val true}]} - {:type :reg-objects - :page-id page-id - :shapes [(:id group)]}]) - - uchanges (conj uchanges - {:type :mod-obj - :page-id page-id - :id (:id group) - :operations [{:type :set - :attr :masked-group? - :val nil}]}) - - ;; If the mask has the default color, change it automatically - ;; to white, to have an opaque mask by default (user may change - ;; it later to have different degrees of transparency). - mask (first shapes) - rchanges (if (not= (:fill-color mask) cp/default-color) - rchanges - (conj rchanges - {:type :mod-obj - :page-id page-id - :id (:id mask) - :operations [{:type :set - :attr :fill-color - :val "#ffffff"}]})) - - uchanges (if (not= (:fill-color mask) cp/default-color) - uchanges - (conj uchanges - {:type :mod-obj - :page-id page-id - :id (:id mask) - :operations [{:type :set - :attr :fill-color - :val (:fill-color mask)}]}))] - - (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}) - (dwc/select-shapes (d/ordered-set (:id group)))))))))) - -(def unmask-group - (ptk/reify ::unmask-group - ptk/WatchEvent - (watch [_ state stream] - (let [page-id (:current-page-id state) - objects (dwc/lookup-page-objects state page-id) - selected (get-in state [:workspace-local :selected])] - (when (= (count selected) 1) - (let [group (get objects (first selected)) - - rchanges [{:type :mod-obj - :page-id page-id - :id (:id group) - :operations [{:type :set - :attr :masked-group? - :val nil}]} - {:type :reg-objects - :page-id page-id - :shapes [(:id group)]}] - - uchanges [{:type :mod-obj - :page-id page-id - :id (:id group) - :operations [{:type :set - :attr :masked-group? - :val (:masked-group? group)}]} - {:type :reg-objects - :page-id page-id - :shapes [(:id group)]}]] - - (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}) - (dwc/select-shapes (d/ordered-set (:id group)))))))))) - - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Interactions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -1631,6 +1504,13 @@ (d/export dwc/start-edition-mode) (d/export dwdp/start-path-edit) +;; Groups + +(d/export dwg/mask-group) +(d/export dwg/unmask-group) +(d/export dwg/group-selected) +(d/export dwg/ungroup-selected) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Shortcuts ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/frontend/src/app/main/data/workspace/groups.cljs b/frontend/src/app/main/data/workspace/groups.cljs new file mode 100644 index 000000000..41cf994a6 --- /dev/null +++ b/frontend/src/app/main/data/workspace/groups.cljs @@ -0,0 +1,199 @@ +(ns app.main.data.workspace.groups + (:require + [app.common.data :as d] + [app.common.geom.shapes :as gsh] + [app.common.pages :as cp] + [app.common.pages-helpers :as cph] + [app.main.data.workspace.common :as dwc] + [app.main.data.workspace.selection :as dws] + [beicon.core :as rx] + [potok.core :as ptk])) + +(defn shapes-for-grouping + [objects selected] + (->> selected + (map #(get objects %)) + (filter #(not= :frame (:type %))) + (map #(assoc % ::index (cph/position-on-parent (:id %) objects))) + (sort-by ::index))) + +(defn- make-group + [shapes prefix keep-name] + (let [selrect (gsh/selection-rect shapes) + frame-id (-> shapes first :frame-id) + parent-id (-> shapes first :parent-id) + group-name (if (and keep-name + (= (count shapes) 1) + (= (:type (first shapes)) :group)) + (:name (first shapes)) + (name (gensym prefix)))] + (-> (cp/make-minimal-group frame-id selrect group-name) + (gsh/setup selrect) + (assoc :shapes (mapv :id shapes))))) + +(defn prepare-create-group + [page-id shapes prefix keep-name] + (let [group (make-group shapes prefix keep-name) + rchanges [{:type :add-obj + :id (:id group) + :page-id page-id + :frame-id (:frame-id (first shapes)) + :parent-id (:parent-id (first shapes)) + :obj group + :index (::index (first shapes))} + {:type :mov-objects + :page-id page-id + :parent-id (:id group) + :shapes (mapv :id shapes)}] + + uchanges (conj + (mapv (fn [obj] {:type :mov-objects + :page-id page-id + :parent-id (:parent-id obj) + :index (::index obj) + :shapes [(:id obj)]}) + shapes) + {:type :del-obj + :id (:id group) + :page-id page-id})] + [group rchanges uchanges])) + +(defn prepare-remove-group + [page-id group objects] + (let [shapes (:shapes group) + parent-id (cph/get-parent (:id group) objects) + parent (get objects parent-id) + index-in-parent (->> (:shapes parent) + (map-indexed vector) + (filter #(#{(:id group)} (second %))) + (ffirst)) + rchanges [{:type :mov-objects + :page-id page-id + :parent-id parent-id + :shapes shapes + :index index-in-parent} + {:type :del-obj + :page-id page-id + :id (:id group)}] + uchanges [{:type :add-obj + :page-id page-id + :id (:id group) + :frame-id (:frame-id group) + :obj (assoc group :shapes [])} + {:type :mov-objects + :page-id page-id + :parent-id (:id group) + :shapes shapes} + {:type :mov-objects + :page-id page-id + :parent-id parent-id + :shapes [(:id group)] + :index index-in-parent}]] + [rchanges uchanges])) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; GROUPS +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(def group-selected + (ptk/reify ::group-selected + ptk/WatchEvent + (watch [_ state stream] + (let [page-id (:current-page-id state) + objects (dwc/lookup-page-objects state page-id) + selected (get-in state [:workspace-local :selected]) + shapes (shapes-for-grouping objects selected)] + (when-not (empty? shapes) + (let [[group rchanges uchanges] (prepare-create-group page-id shapes "Group-" false)] + (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}) + (dwc/select-shapes (d/ordered-set (:id group)))))))))) + +(def ungroup-selected + (ptk/reify ::ungroup-selected + ptk/WatchEvent + (watch [_ state stream] + (let [page-id (:current-page-id state) + objects (dwc/lookup-page-objects state page-id) + selected (get-in state [:workspace-local :selected]) + group-id (first selected) + group (get objects group-id)] + (when (and (= 1 (count selected)) + (= (:type group) :group)) + (let [[rchanges uchanges] + (prepare-remove-group page-id group objects)] + (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})))))))) + +(def mask-group + (ptk/reify ::mask-group + ptk/WatchEvent + (watch [_ state stream] + (let [page-id (:current-page-id state) + objects (dwc/lookup-page-objects state page-id) + selected (get-in state [:workspace-local :selected]) + shapes (shapes-for-grouping objects selected)] + (when-not (empty? shapes) + (let [;; If the selected shape is a group, we can use it. If not, + ;; create a new group and set it as masked. + [group rchanges uchanges] + (if (and (= (count shapes) 1) + (= (:type (first shapes)) :group)) + [(first shapes) [] []] + (prepare-create-group page-id shapes "Group-" true)) + + rchanges (d/concat rchanges + [{:type :mod-obj + :page-id page-id + :id (:id group) + :operations [{:type :set + :attr :masked-group? + :val true}]} + {:type :reg-objects + :page-id page-id + :shapes [(:id group)]}]) + + uchanges (conj uchanges + {:type :mod-obj + :page-id page-id + :id (:id group) + :operations [{:type :set + :attr :masked-group? + :val nil}]})] + + (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}) + (dwc/select-shapes (d/ordered-set (:id group)))))))))) + +(def unmask-group + (ptk/reify ::unmask-group + ptk/WatchEvent + (watch [_ state stream] + (let [page-id (:current-page-id state) + objects (dwc/lookup-page-objects state page-id) + selected (get-in state [:workspace-local :selected])] + (when (= (count selected) 1) + (let [group (get objects (first selected)) + + rchanges [{:type :mod-obj + :page-id page-id + :id (:id group) + :operations [{:type :set + :attr :masked-group? + :val nil}]} + {:type :reg-objects + :page-id page-id + :shapes [(:id group)]}] + + uchanges [{:type :mod-obj + :page-id page-id + :id (:id group) + :operations [{:type :set + :attr :masked-group? + :val (:masked-group? group)}]} + {:type :reg-objects + :page-id page-id + :shapes [(:id group)]}]] + + (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}) + (dwc/select-shapes (d/ordered-set (:id group)))))))))) + + diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 3eb8f60db..381b8f5e3 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -17,7 +17,7 @@ [app.common.geom.shapes :as geom] [app.main.data.messages :as dm] [app.main.data.workspace.common :as dwc] - [app.main.data.workspace.selection :as dws] + [app.main.data.workspace.groups :as dwg] [app.main.data.workspace.libraries-helpers :as dwlh] [app.common.pages :as cp] [app.main.repo :as rp] @@ -182,7 +182,7 @@ (let [page-id (:current-page-id state) objects (dwc/lookup-page-objects state page-id) selected (get-in state [:workspace-local :selected]) - shapes (dws/shapes-for-grouping objects selected)] + shapes (dwg/shapes-for-grouping objects selected)] (when-not (empty? shapes) (let [;; If the selected shape is a group, we can use it. If not, ;; we need to create a group before creating the component. @@ -190,7 +190,7 @@ (if (and (= (count shapes) 1) (= (:type (first shapes)) :group)) [(first shapes) [] []] - (dws/prepare-create-group page-id shapes "Component-" true)) + (dwg/prepare-create-group page-id shapes "Component-" true)) [new-shape new-shapes updated-shapes] (dwlh/make-component-shape group objects) diff --git a/frontend/src/app/main/data/workspace/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs index d01e9c8b4..fe9ea3d6f 100644 --- a/frontend/src/app/main/data/workspace/selection.cljs +++ b/frontend/src/app/main/data/workspace/selection.cljs @@ -197,91 +197,6 @@ (rx/of (deselect-all) (select-shape (:id selected)))))))) -;; --- Group shapes - -(defn shapes-for-grouping - [objects selected] - (->> selected - (map #(get objects %)) - (filter #(not= :frame (:type %))) - (map #(assoc % ::index (cph/position-on-parent (:id %) objects))) - (sort-by ::index))) - -(defn- make-group - [shapes prefix keep-name] - (let [selrect (geom/selection-rect shapes) - frame-id (-> shapes first :frame-id) - parent-id (-> shapes first :parent-id) - group-name (if (and keep-name - (= (count shapes) 1) - (= (:type (first shapes)) :group)) - (:name (first shapes)) - (name (gensym prefix)))] - (-> (cp/make-minimal-group frame-id selrect group-name) - (geom/setup selrect) - (assoc :shapes (mapv :id shapes))))) - -(defn prepare-create-group - [page-id shapes prefix keep-name] - (let [group (make-group shapes prefix keep-name) - rchanges [{:type :add-obj - :id (:id group) - :page-id page-id - :frame-id (:frame-id (first shapes)) - :parent-id (:parent-id (first shapes)) - :obj group - :index (::index (first shapes))} - {:type :mov-objects - :page-id page-id - :parent-id (:id group) - :shapes (mapv :id shapes)}] - - uchanges (conj - (mapv (fn [obj] {:type :mov-objects - :page-id page-id - :parent-id (:parent-id obj) - :index (::index obj) - :shapes [(:id obj)]}) - shapes) - {:type :del-obj - :id (:id group) - :page-id page-id})] - [group rchanges uchanges])) - -(defn prepare-remove-group - [page-id group objects] - (let [shapes (:shapes group) - parent-id (cph/get-parent (:id group) objects) - parent (get objects parent-id) - index-in-parent (->> (:shapes parent) - (map-indexed vector) - (filter #(#{(:id group)} (second %))) - (ffirst)) - rchanges [{:type :mov-objects - :page-id page-id - :parent-id parent-id - :shapes shapes - :index index-in-parent} - {:type :del-obj - :page-id page-id - :id (:id group)}] - uchanges [{:type :add-obj - :page-id page-id - :id (:id group) - :frame-id (:frame-id group) - :obj (assoc group :shapes [])} - {:type :mov-objects - :page-id page-id - :parent-id (:id group) - :shapes shapes} - {:type :mov-objects - :page-id page-id - :parent-id parent-id - :shapes [(:id group)] - :index index-in-parent}]] - [rchanges uchanges])) - - ;; --- Duplicate Shapes (declare prepare-duplicate-change) (declare prepare-duplicate-frame-change) diff --git a/frontend/src/app/main/ui/shapes/group.cljs b/frontend/src/app/main/ui/shapes/group.cljs index f9b039b6c..5e00ea9e3 100644 --- a/frontend/src/app/main/ui/shapes/group.cljs +++ b/frontend/src/app/main/ui/shapes/group.cljs @@ -10,42 +10,37 @@ (ns app.main.ui.shapes.group (:require [rumext.alpha :as mf] - [cuerdas.core :as str] - [app.main.ui.shapes.attrs :as attrs] - [app.common.geom.shapes :as geom])) + [app.main.ui.shapes.mask :refer [mask-str mask-factory]])) (defn group-shape [shape-wrapper] - (mf/fnc group-shape - {::mf/wrap-props false} - [props] - (let [frame (unchecked-get props "frame") - shape (unchecked-get props "shape") - childs (unchecked-get props "childs") - expand-mask (unchecked-get props "expand-mask") - pointer-events (unchecked-get props "pointer-events") - mask (if (and (:masked-group? shape) (not expand-mask)) - (first childs) - nil) - childs (if (and (:masked-group? shape) (not expand-mask)) - (rest childs) - childs) - {:keys [id x y width height]} shape - transform (geom/transform-matrix shape)] - [:g.group {:pointer-events pointer-events - :mask (when (and mask (not expand-mask)) - (str/fmt "url(#%s)" (:id mask)))} - (when mask - [:defs - [:mask {:id (:id mask) - :width width - :height height} + (let [render-mask (mask-factory shape-wrapper)] + (mf/fnc group-shape + {::mf/wrap-props false} + [props] + (let [frame (unchecked-get props "frame") + shape (unchecked-get props "shape") + childs (unchecked-get props "childs") + expand-mask (unchecked-get props "expand-mask") + pointer-events (unchecked-get props "pointer-events") + + {:keys [id x y width height]} shape + + show-mask? (and (:masked-group? shape) (not expand-mask)) + mask (when show-mask? (first childs)) + childs (if show-mask? (rest childs) childs)] + + [:g.group + {:pointer-events pointer-events + :mask (when (and mask (not expand-mask)) (mask-str mask))} + + (when mask + [:> render-mask #js {:frame frame :mask mask}]) + + (for [item childs] [:& shape-wrapper {:frame frame - :shape mask}]]]) - (for [item childs] - [:& shape-wrapper {:frame frame - :shape item - :key (:id item)}])]))) + :shape item + :key (:id item)}])])))) diff --git a/frontend/src/app/main/ui/shapes/mask.cljs b/frontend/src/app/main/ui/shapes/mask.cljs new file mode 100644 index 000000000..be9684793 --- /dev/null +++ b/frontend/src/app/main/ui/shapes/mask.cljs @@ -0,0 +1,35 @@ +;; 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.mask + (:require + [rumext.alpha :as mf] + [cuerdas.core :as str])) + +(defn mask-str [mask] + (str/fmt "url(#%s)" (str (:id mask) "-mask"))) + +(defn mask-factory + [shape-wrapper] + (mf/fnc mask-shape + {::mf/wrap-props false} + [props] + (let [frame (unchecked-get props "frame") + mask (unchecked-get props "mask")] + [:defs + [:filter {:id (str (:id mask) "-filter")} + [:feFlood {:flood-color "white"}] + [:feComposite {:in "BackgroundImage" + :in2 "SourceGraphic" + :operator "in" + :result "comp"}]] + [:mask {:id (str (:id mask) "-mask")} + [:g {:filter (str/fmt "url(#%s)" (str (:id mask) "-filter"))} + [:& shape-wrapper {:frame frame :shape mask}]]]]))) +