diff --git a/common/src/app/common/geom/shapes.cljc b/common/src/app/common/geom/shapes.cljc index 7ef49ea81..6a21f6c58 100644 --- a/common/src/app/common/geom/shapes.cljc +++ b/common/src/app/common/geom/shapes.cljc @@ -8,6 +8,7 @@ (:require [app.common.data :as d] [app.common.geom.point :as gpt] + [app.common.geom.shapes.bool :as gsb] [app.common.geom.shapes.common :as gco] [app.common.geom.shapes.intersect :as gin] [app.common.geom.shapes.path :as gsp] @@ -164,3 +165,6 @@ (d/export gin/has-point?) (d/export gin/has-point-rect?) (d/export gin/rect-contains-shape?) + +;; Bool +(d/export gsb/update-bool-selrect) diff --git a/common/src/app/common/geom/shapes/bool.cljc b/common/src/app/common/geom/shapes/bool.cljc new file mode 100644 index 000000000..35c91b2dc --- /dev/null +++ b/common/src/app/common/geom/shapes/bool.cljc @@ -0,0 +1,25 @@ +;; 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.common.geom.shapes.bool + (:require + [app.common.geom.shapes.path :as gsp] + [app.common.geom.shapes.rect :as gpr] + [app.common.path.bool :as pb] + [app.common.path.shapes-to-path :as stp])) + +(defn update-bool-selrect + [shape children objects] + + (let [selrect (->> children + (map #(stp/convert-to-path % objects)) + (mapv :content) + (pb/content-bool (:bool-type shape)) + (gsp/content->selrect)) + points (gpr/rect->points selrect)] + (-> shape + (assoc :selrect selrect) + (assoc :points points)))) diff --git a/common/src/app/common/geom/shapes/path.cljc b/common/src/app/common/geom/shapes/path.cljc index 6a7c4c6d8..b2d5643b3 100644 --- a/common/src/app/common/geom/shapes/path.cljc +++ b/common/src/app/common/geom/shapes/path.cljc @@ -10,10 +10,42 @@ [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.shapes.rect :as gpr] - [app.common.math :as mth])) + [app.common.math :as mth] + [app.common.path.commands :as upc])) (def ^:const curve-curve-precision 0.1) +(defn calculate-opposite-handler + "Given a point and its handler, gives the symetric handler" + [point handler] + (let [handler-vector (gpt/to-vec point handler)] + (gpt/add point (gpt/negate handler-vector)))) + +(defn opposite-handler + "Calculates the coordinates of the opposite handler" + [point handler] + (let [phv (gpt/to-vec point handler)] + (gpt/add point (gpt/negate phv)))) + +(defn opposite-handler-keep-distance + "Calculates the coordinates of the opposite handler but keeping the old distance" + [point handler old-opposite] + (let [old-distance (gpt/distance point old-opposite) + phv (gpt/to-vec point handler) + phv2 (gpt/multiply + (gpt/unit (gpt/negate phv)) + (gpt/point old-distance))] + (gpt/add point phv2))) + +(defn content->points + "Returns the points in the given content" + [content] + (->> content + (map #(when (-> % :params :x) + (gpt/point (-> % :params :x) (-> % :params :y)))) + (remove nil?) + (into []))) + (defn line-values [[from-p to-p] t] (let [move-v (-> (gpt/to-vec from-p to-p) @@ -670,3 +702,57 @@ (map second) (reduce +) (not= 0)))) + +(defn split-line-to + "Given a point and a line-to command will create a two new line-to commands + that will split the original line into two given a value between 0-1" + [from-p cmd t-val] + (let [to-p (upc/command->point cmd) + sp (gpt/lerp from-p to-p t-val)] + [(upc/make-line-to sp) cmd])) + +(defn split-curve-to + "Given the point and a curve-to command will split the curve into two new + curve-to commands given a value between 0-1" + [from-p cmd t-val] + (let [params (:params cmd) + end (gpt/point (:x params) (:y params)) + h1 (gpt/point (:c1x params) (:c1y params)) + h2 (gpt/point (:c2x params) (:c2y params)) + [[_ to1 h11 h21] + [_ to2 h12 h22]] (curve-split from-p end h1 h2 t-val)] + [(upc/make-curve-to to1 h11 h21) + (upc/make-curve-to to2 h12 h22)])) + +(defn split-line-to-ranges + "Splits a line into several lines given the points in `values` + for example (split-line-to-ranges p c [0 0.25 0.5 0.75 1] will split + the line into 4 lines" + [from-p cmd values] + (let [to-p (upc/command->point cmd)] + (->> (conj values 1) + (mapv (fn [val] + (-> (gpt/lerp from-p to-p val) + #_(gpt/round 2) + (upc/make-line-to))))))) + +(defn split-curve-to-ranges + "Splits a curve into several curves given the points in `values` + for example (split-curve-to-ranges p c [0 0.25 0.5 0.75 1] will split + the curve into 4 curves that draw the same curve" + [from-p cmd values] + (if (empty? values) + [cmd] + (let [to-p (upc/command->point cmd) + params (:params cmd) + h1 (gpt/point (:c1x params) (:c1y params)) + h2 (gpt/point (:c2x params) (:c2y params)) + + values-set (->> (conj values 1) (into (sorted-set)))] + (->> (d/with-prev values-set) + (mapv + (fn [[t1 t0]] + (let [t0 (if (nil? t0) 0 t0) + [_ to-p h1' h2'] (subcurve-range from-p to-p h1 h2 t0 t1)] + (upc/make-curve-to (-> to-p #_(gpt/round 2)) h1' h2')))))))) + diff --git a/common/src/app/common/pages.cljc b/common/src/app/common/pages.cljc index fdf02cfa3..640a6858e 100644 --- a/common/src/app/common/pages.cljc +++ b/common/src/app/common/pages.cljc @@ -40,6 +40,7 @@ (d/export helpers/get-children) (d/export helpers/get-children-objects) (d/export helpers/get-object-with-children) +(d/export helpers/select-children) (d/export helpers/is-shape-grouped) (d/export helpers/get-parent) (d/export helpers/get-parents) diff --git a/common/src/app/common/pages/changes.cljc b/common/src/app/common/pages/changes.cljc index 997edc170..a2211b238 100644 --- a/common/src/app/common/pages/changes.cljc +++ b/common/src/app/common/pages/changes.cljc @@ -9,6 +9,7 @@ [app.common.data :as d] [app.common.exceptions :as ex] [app.common.geom.shapes :as gsh] + [app.common.geom.shapes.bool :as gshb] [app.common.pages.common :refer [component-sync-attrs]] [app.common.pages.helpers :as cph] [app.common.pages.init :as init] @@ -156,7 +157,7 @@ (sequence (comp (mapcat #(cons % (cph/get-parents % objects))) (map #(get objects %)) - (filter #(= (:type %) :group)) + (filter #(contains? #{:group :bool} (:type %))) (map :id) (distinct)) shapes))) @@ -177,6 +178,9 @@ (empty? children) group + (= :bool (:type group)) + (gshb/update-bool-selrect group children objects) + (:masked-group? group) (set-mask-selrect group children) diff --git a/common/src/app/common/pages/helpers.cljc b/common/src/app/common/pages/helpers.cljc index 0d729275b..d170b45f1 100644 --- a/common/src/app/common/pages/helpers.cljc +++ b/common/src/app/common/pages/helpers.cljc @@ -138,6 +138,10 @@ [id objects] (mapv #(get objects %) (cons id (get-children id objects)))) +(defn select-children [id objects] + (->> (get-children id objects) + (select-keys objects))) + (defn is-shape-grouped "Checks if a shape is inside a group" [shape-id objects] diff --git a/frontend/src/app/util/path/bool.cljs b/common/src/app/common/path/bool.cljc similarity index 94% rename from frontend/src/app/util/path/bool.cljs rename to common/src/app/common/path/bool.cljc index 5e0eb3068..3d61ff98c 100644 --- a/frontend/src/app/util/path/bool.cljs +++ b/common/src/app/common/path/bool.cljc @@ -4,20 +4,19 @@ ;; ;; Copyright (c) UXBOX Labs SL -(ns app.util.path.bool +(ns app.common.path.bool (:require [app.common.data :as d] [app.common.geom.point :as gpt] [app.common.geom.shapes.path :as gsp] - [app.util.path.commands :as upc] - [app.util.path.geom :as upg] - [app.util.path.subpaths :as ups])) + [app.common.path.commands :as upc] + [app.common.path.subpaths :as ups])) (defn- split-command [cmd values] (case (:command cmd) - :line-to (upg/split-line-to-ranges (:prev cmd) cmd values) - :curve-to (upg/split-curve-to-ranges (:prev cmd) cmd values) + :line-to (gsp/split-line-to-ranges (:prev cmd) cmd values) + :curve-to (gsp/split-curve-to-ranges (:prev cmd) cmd values) [cmd])) (defn split [seg-1 seg-2] @@ -198,7 +197,7 @@ (gsp/command->point current) (conj result (dissoc current :prev))))))) -(defn content-bool +(defn content-bool-pair [bool-type content-a content-b] (let [content-a (add-previous content-a) @@ -218,3 +217,11 @@ (->> (fix-move-to bool-content) (ups/close-subpaths)))) + +(defn content-bool + [bool-type contents] + ;; We apply the boolean operation in to each pair and the result to the next + ;; element + (->> contents + (reduce (partial content-bool-pair bool-type)) + (into []))) diff --git a/frontend/src/app/util/path/commands.cljs b/common/src/app/common/path/commands.cljc similarity index 99% rename from frontend/src/app/util/path/commands.cljs rename to common/src/app/common/path/commands.cljc index fd1df9da9..80737db8c 100644 --- a/frontend/src/app/util/path/commands.cljs +++ b/common/src/app/common/path/commands.cljc @@ -4,7 +4,7 @@ ;; ;; Copyright (c) UXBOX Labs SL -(ns app.util.path.commands +(ns app.common.path.commands (:require [app.common.data :as d] [app.common.geom.point :as gpt])) diff --git a/frontend/src/app/util/path/shapes_to_path.cljs b/common/src/app/common/path/shapes_to_path.cljc similarity index 68% rename from frontend/src/app/util/path/shapes_to_path.cljs rename to common/src/app/common/path/shapes_to_path.cljc index 8d7c86cbd..ec8294fd0 100644 --- a/frontend/src/app/util/path/shapes_to_path.cljs +++ b/common/src/app/common/path/shapes_to_path.cljc @@ -4,22 +4,46 @@ ;; ;; Copyright (c) UXBOX Labs SL -(ns app.util.path.shapes-to-path +(ns app.common.path.shapes-to-path (:require [app.common.data :as d] [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] - [app.common.geom.shapes :as gsh] + [app.common.geom.shapes.common :as gsc] [app.common.geom.shapes.path :as gsp] - [app.util.path.commands :as pc])) + [app.common.path.commands :as pc])) -(def bezier-circle-c 0.551915024494) -(def dissoc-attrs [:x :y :width :height - :rx :ry :r1 :r2 :r3 :r4 - :medata]) -(def allowed-transform-types #{:rect - :circle - :image}) +(def ^:const bezier-circle-c 0.551915024494) + +(def ^:const dissoc-attrs + [:x :y :width :height + :rx :ry :r1 :r2 :r3 :r4 + :metadata :shapes]) + +(def ^:const allowed-transform-types + #{:rect + :circle + :image + :group}) + +(def ^:const style-properties + [:fill-color + :fill-opacity + :fill-color-gradient + :fill-color-ref-file + :fill-color-ref-id + :fill-image + :stroke-color + :stroke-color-ref-file + :stroke-color-ref-id + :stroke-opacity + :stroke-style + :stroke-width + :stroke-alignment + :stroke-cap-start + :stroke-cap-end + :shadow + :blur]) (defn make-corner-arc "Creates a curvle corner for border radius" @@ -86,8 +110,9 @@ (defn rect->path "Creates a bezier curve that approximates a rounded corner rectangle" - [x y width height r1 r2 r3 r4] - (let [p1 (gpt/point x (+ y r1)) + [x y width height r1 r2 r3 r4 rx] + (let [[r1 r2 r3 r4] (->> [r1 r2 r3 r4] (mapv #(or % rx 0))) + p1 (gpt/point x (+ y r1)) p2 (gpt/point (+ x r1) y) p3 (gpt/point (+ width x (- r2)) y) @@ -113,34 +138,51 @@ (conj (make-corner-arc p7 p8 :bottom-left r4))) (conj (pc/make-line-to p1))))) +(declare convert-to-path) + +(defn group-to-path + [group objects] + + (let [xform (comp (map #(get objects %)) + (map #(-> (convert-to-path % objects)))) + + child-as-paths (into [] xform (:shapes group)) + head (first child-as-paths) + head-data (select-keys head style-properties) + content (into [] (mapcat :content) child-as-paths)] + + (-> group + (assoc :type :path) + (assoc :content content) + (merge head-data) + (d/without-keys dissoc-attrs)))) + (defn convert-to-path "Transforms the given shape to a path" - [{:keys [type x y width height r1 r2 r3 r4 rx metadata] :as shape}] + [{:keys [type x y width height r1 r2 r3 r4 rx metadata] :as shape} objects] + (assert (map? objects)) + (cond + (= (:type shape) :group) + (group-to-path shape objects) - (if (contains? allowed-transform-types type) - (let [r1 (or r1 rx 0) - r2 (or r2 rx 0) - r3 (or r3 rx 0) - r4 (or r4 rx 0) - - new-content + (contains? allowed-transform-types type) + (let [new-content (case type - :circle - (circle->path x y width height) - (rect->path x y width height r1 r2 r3 r4)) + :circle (circle->path x y width height) + #_:else (rect->path x y width height r1 r2 r3 r4 rx)) ;; Apply the transforms that had the shape transform (:transform shape) new-content (cond-> new-content (some? transform) - (gsp/transform-content (gmt/transform-in (gsh/center-shape shape) transform)))] + (gsp/transform-content (gmt/transform-in (gsc/center-shape shape) transform)))] (-> shape - (d/without-keys dissoc-attrs) (assoc :type :path) (assoc :content new-content) - (cond-> (= :image type) (-> (assoc :fill-image metadata) - (dissoc :metadata))))) + (cond-> (= :image type) + (assoc :fill-image metadata)) + (d/without-keys dissoc-attrs))) + :else ;; Do nothing if the shape is not of a correct type shape)) - diff --git a/frontend/src/app/util/path/subpaths.cljs b/common/src/app/common/path/subpaths.cljc similarity index 98% rename from frontend/src/app/util/path/subpaths.cljs rename to common/src/app/common/path/subpaths.cljc index d4ddf10b3..3c4d90c68 100644 --- a/frontend/src/app/util/path/subpaths.cljs +++ b/common/src/app/common/path/subpaths.cljc @@ -4,11 +4,11 @@ ;; ;; Copyright (c) UXBOX Labs SL -(ns app.util.path.subpaths +(ns app.common.path.subpaths (:require [app.common.data :as d] [app.common.geom.point :as gpt] - [app.util.path.commands :as upc])) + [app.common.path.commands :as upc])) (defn pt= "Check if two points are close" diff --git a/frontend/src/app/main/data/workspace/booleans.cljs b/frontend/src/app/main/data/workspace/booleans.cljs index b2be46791..301fa9c2f 100644 --- a/frontend/src/app/main/data/workspace/booleans.cljs +++ b/frontend/src/app/main/data/workspace/booleans.cljs @@ -10,6 +10,7 @@ [app.common.geom.shapes :as gsh] [app.common.pages :as cp] [app.common.pages.changes-builder :as cb] + [app.common.path.shapes-to-path :as stp] [app.common.uuid :as uuid] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.common :as dwc] @@ -18,24 +19,6 @@ [cuerdas.core :as str] [potok.core :as ptk])) -(def ^:const style-properties - [:fill-color - :fill-opacity - :fill-color-gradient - :fill-color-ref-file - :fill-color-ref-id - :stroke-color - :stroke-color-ref-file - :stroke-color-ref-id - :stroke-opacity - :stroke-style - :stroke-width - :stroke-alignment - :stroke-cap-start - :stroke-cap-end - :shadow - :blur]) - (defn selected-shapes [state] (let [objects (wsh/lookup-page-objects state)] @@ -47,10 +30,10 @@ (sort-by ::index)))) (defn create-bool-data - [type name shapes] - (let [head (first shapes) - head-data (select-keys head style-properties) - selrect (gsh/selection-rect shapes)] + [type name shapes objects] + (let [shapes (mapv #(stp/convert-to-path % objects) shapes) + head (first shapes) + head-data (select-keys head stp/style-properties)] (-> {:id (uuid/next) :type :bool :bool-type type @@ -60,7 +43,7 @@ ::index (::index head) :shapes []} (merge head-data) - (gsh/setup selrect)))) + (gsh/update-bool-selrect shapes objects)))) (defn create-bool [bool-type] @@ -69,14 +52,14 @@ (watch [it state _] (let [page-id (:current-page-id state) - objects (wsh/lookup-page-objects state page-id) + objects (wsh/lookup-page-objects state) base-name (-> bool-type d/name str/capital (str "-1")) name (-> (dwc/retrieve-used-names objects) (dwc/generate-unique-name base-name)) shapes (selected-shapes state)] (when-not (empty? shapes) - (let [boolean-data (create-bool-data bool-type name shapes) + (let [boolean-data (create-bool-data bool-type name shapes objects) shape-id (:id boolean-data) changes (-> (cb/empty-changes it page-id) (cb/add-obj boolean-data) diff --git a/frontend/src/app/main/data/workspace/path/drawing.cljs b/frontend/src/app/main/data/workspace/path/drawing.cljs index 202266e79..8e7de7cae 100644 --- a/frontend/src/app/main/data/workspace/path/drawing.cljs +++ b/frontend/src/app/main/data/workspace/path/drawing.cljs @@ -7,7 +7,10 @@ (ns app.main.data.workspace.path.drawing (:require [app.common.geom.point :as gpt] + [app.common.geom.shapes.path :as upg] [app.common.pages :as cp] + [app.common.path.commands :as upc] + [app.common.path.shapes-to-path :as upsp] [app.common.spec :as us] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.common :as dwc] @@ -21,9 +24,6 @@ [app.main.data.workspace.path.undo :as undo] [app.main.data.workspace.state-helpers :as wsh] [app.main.streams :as ms] - [app.util.path.commands :as upc] - [app.util.path.geom :as upg] - [app.util.path.shapes-to-path :as upsp] [beicon.core :as rx] [potok.core :as ptk])) diff --git a/frontend/src/app/main/data/workspace/path/edition.cljs b/frontend/src/app/main/data/workspace/path/edition.cljs index 89331ebd2..9df8b6e9a 100644 --- a/frontend/src/app/main/data/workspace/path/edition.cljs +++ b/frontend/src/app/main/data/workspace/path/edition.cljs @@ -8,6 +8,10 @@ (:require [app.common.data :as d] [app.common.geom.point :as gpt] + [app.common.geom.shapes.path :as upg] + [app.common.path.commands :as upc] + [app.common.path.shapes-to-path :as upsp] + [app.common.path.subpaths :as ups] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.common :as dwc] [app.main.data.workspace.path.changes :as changes] @@ -19,10 +23,6 @@ [app.main.data.workspace.path.undo :as undo] [app.main.data.workspace.state-helpers :as wsh] [app.main.streams :as ms] - [app.util.path.commands :as upc] - [app.util.path.geom :as upg] - [app.util.path.shapes-to-path :as upsp] - [app.util.path.subpaths :as ups] [app.util.path.tools :as upt] [beicon.core :as rx] [potok.core :as ptk])) diff --git a/frontend/src/app/main/data/workspace/path/helpers.cljs b/frontend/src/app/main/data/workspace/path/helpers.cljs index 9b36e4099..a7d47d238 100644 --- a/frontend/src/app/main/data/workspace/path/helpers.cljs +++ b/frontend/src/app/main/data/workspace/path/helpers.cljs @@ -10,10 +10,10 @@ [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] [app.common.math :as mth] + [app.common.path.commands :as upc] + [app.common.path.subpaths :as ups] [app.main.data.workspace.path.common :as common] [app.main.streams :as ms] - [app.util.path.commands :as upc] - [app.util.path.subpaths :as ups] [potok.core :as ptk])) (defn end-path-event? [event] diff --git a/frontend/src/app/main/data/workspace/path/shapes_to_path.cljs b/frontend/src/app/main/data/workspace/path/shapes_to_path.cljs new file mode 100644 index 000000000..6f68123de --- /dev/null +++ b/frontend/src/app/main/data/workspace/path/shapes_to_path.cljs @@ -0,0 +1,21 @@ +;; 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.data.workspace.path.shapes-to-path + (:require + [app.common.path.shapes-to-path :as upsp] + [app.main.data.workspace.changes :as dch] + [app.main.data.workspace.state-helpers :as wsh] + [beicon.core :as rx] + [potok.core :as ptk])) + +(defn convert-selected-to-path [] + (ptk/reify ::convert-selected-to-path + ptk/WatchEvent + (watch [_ state _] + (let [objects (wsh/lookup-page-objects state) + selected (wsh/lookup-selected state)] + (rx/of (dch/update-shapes selected #(upsp/convert-to-path % objects))))))) diff --git a/frontend/src/app/main/data/workspace/path/state.cljs b/frontend/src/app/main/data/workspace/path/state.cljs index 229e46256..86ac1731e 100644 --- a/frontend/src/app/main/data/workspace/path/state.cljs +++ b/frontend/src/app/main/data/workspace/path/state.cljs @@ -7,7 +7,7 @@ (ns app.main.data.workspace.path.state (:require [app.common.data :as d] - [app.util.path.shapes-to-path :as upsp])) + [app.common.path.shapes-to-path :as upsp])) (defn get-path-id "Retrieves the currently editing path id" @@ -31,7 +31,8 @@ [state & ks] (let [path-loc (get-path-location state) shape (-> (get-in state path-loc) - (upsp/convert-to-path))] + ;; Empty map because we know the current shape will not have children + (upsp/convert-to-path {}))] (if (empty? ks) shape diff --git a/frontend/src/app/main/data/workspace/path/streams.cljs b/frontend/src/app/main/data/workspace/path/streams.cljs index b67607ae0..8a8f8a59b 100644 --- a/frontend/src/app/main/data/workspace/path/streams.cljs +++ b/frontend/src/app/main/data/workspace/path/streams.cljs @@ -7,12 +7,12 @@ (ns app.main.data.workspace.path.streams (:require [app.common.geom.point :as gpt] + [app.common.geom.shapes.path :as upg] [app.common.math :as mth] [app.main.data.workspace.path.state :as state] [app.main.snap :as snap] [app.main.store :as st] [app.main.streams :as ms] - [app.util.path.geom :as upg] [beicon.core :as rx] [okulary.core :as l] [potok.core :as ptk])) diff --git a/frontend/src/app/main/data/workspace/path/tools.cljs b/frontend/src/app/main/data/workspace/path/tools.cljs index 18f262743..fce88f9db 100644 --- a/frontend/src/app/main/data/workspace/path/tools.cljs +++ b/frontend/src/app/main/data/workspace/path/tools.cljs @@ -6,13 +6,13 @@ (ns app.main.data.workspace.path.tools (:require + [app.common.path.shapes-to-path :as upsp] + [app.common.path.subpaths :as ups] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.common :as dwc] [app.main.data.workspace.path.changes :as changes] [app.main.data.workspace.path.state :as st] [app.main.data.workspace.state-helpers :as wsh] - [app.util.path.shapes-to-path :as upsp] - [app.util.path.subpaths :as ups] [app.util.path.tools :as upt] [beicon.core :as rx] [potok.core :as ptk])) diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 9224abce2..15e90c422 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -243,16 +243,34 @@ ([ids {:keys [with-modifiers?] :or { with-modifiers? false }}] - (l/derived (fn [state] - (let [objects (wsh/lookup-page-objects state) - modifiers (:workspace-modifiers state) - objects (cond-> objects - with-modifiers? - (gsh/merge-modifiers modifiers)) - xform (comp (map #(get objects %)) - (remove nil?))] - (into [] xform ids))) - st/state =))) + (let [selector + (fn [state] + (let [objects (wsh/lookup-page-objects state) + modifiers (:workspace-modifiers state) + objects (cond-> objects + with-modifiers? + (gsh/merge-modifiers modifiers)) + xform (comp (map #(get objects %)) + (remove nil?))] + (into [] xform ids)))] + (l/derived selector st/state =)))) + +(defn select-children [id] + (let [selector + (fn [state] + (let [objects (wsh/lookup-page-objects state) + children (cp/select-children id objects) + modifiers (-> (:workspace-modifiers state)) + + {selected :selected disp-modifiers :modifiers} + (-> (:workspace-local state) + (select-keys [:modifiers :selected])) + + modifiers (merge modifiers + (into #{} (map #(vector % disp-modifiers)) selected))] + + (gsh/merge-modifiers children modifiers)))] + (l/derived selector st/state =))) (def selected-data (l/derived #(let [selected (wsh/lookup-selected %) diff --git a/frontend/src/app/main/ui/shapes/bool.cljs b/frontend/src/app/main/ui/shapes/bool.cljs index dd77780e9..7fdbfbcc3 100644 --- a/frontend/src/app/main/ui/shapes/bool.cljs +++ b/frontend/src/app/main/ui/shapes/bool.cljs @@ -6,11 +6,10 @@ (ns app.main.ui.shapes.bool (:require - [app.common.geom.shapes :as gsh] + [app.common.path.bool :as pb] + [app.common.path.shapes-to-path :as stp] [app.main.ui.hooks :refer [use-equal-memo]] [app.util.object :as obj] - [app.util.path.bool :as pb] - [app.util.path.shapes-to-path :as stp] [rumext.alpha :as mf])) (defn bool-shape @@ -20,32 +19,25 @@ [props] (let [frame (obj/get props "frame") shape (obj/get props "shape") - childs (obj/get props "childs")] + childs (obj/get props "childs") - (when (> (count childs) 1) - (let [shape-1 (stp/convert-to-path (nth childs 0)) - shape-2 (stp/convert-to-path (nth childs 1)) + childs (use-equal-memo childs) - content-1 (use-equal-memo (-> shape-1 gsh/transform-shape :content)) - content-2 (use-equal-memo (-> shape-2 gsh/transform-shape :content)) + bool-content + (mf/use-memo + (mf/deps childs) + (fn [] + (->> shape + :shapes + (map #(get childs %)) + (map #(stp/convert-to-path % childs)) + (mapv :content) + (pb/content-bool (:bool-type shape)))))] - content - (mf/use-memo - (mf/deps content-1 content-2) - #(pb/content-bool (:bool-type shape) content-1 content-2))] - - [:* - [:& shape-wrapper {:shape (-> shape - (assoc :type :path) - (assoc :content content)) - :frame frame}] - - #_[:g - (for [point (app.util.path.geom/content->points content)] - [:circle {:cx (:x point) - :cy (:y point) - :r 1 - :style {:fill "blue"}}])]]))))) + [:& shape-wrapper {:shape (-> shape + (assoc :type :path) + (assoc :content bool-content)) + :frame frame}]))) diff --git a/frontend/src/app/main/ui/workspace/context_menu.cljs b/frontend/src/app/main/ui/workspace/context_menu.cljs index 8e2b43b78..b65f9d062 100644 --- a/frontend/src/app/main/ui/workspace/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/context_menu.cljs @@ -10,6 +10,7 @@ [app.main.data.modal :as modal] [app.main.data.workspace :as dw] [app.main.data.workspace.libraries :as dwl] + [app.main.data.workspace.path.shapes-to-path :as dwpe] [app.main.data.workspace.shortcuts :as sc] [app.main.data.workspace.undo :as dwu] [app.main.refs :as refs] @@ -147,6 +148,7 @@ do-boolean-difference (st/emitf (dw/create-bool :difference)) do-boolean-intersection (st/emitf (dw/create-bool :intersection)) do-boolean-exclude (st/emitf (dw/create-bool :exclude)) + do-transform-to-path (st/emitf (dwpe/convert-selected-to-path)) ] [:* [:& menu-entry {:title (tr "workspace.shape.menu.copy") @@ -214,6 +216,9 @@ :shortcut (sc/get-tooltip :start-editing) :on-click do-start-editing}]) + [:& menu-entry {:title "Transform to path" + :on-click do-transform-to-path}] + [:& menu-entry {:title (tr "workspace.shape.menu.path")} [:& menu-entry {:title (tr "workspace.shape.menu.union") :shortcut (sc/get-tooltip :boolean-union) @@ -230,8 +235,7 @@ [:& menu-separator] ;; TODO - [:& menu-entry {:title "Flatten"}] - [:& menu-entry {:title "Transform to path"}]] + [:& menu-entry {:title "Flatten"}]] (if (:hidden shape) [:& menu-entry {:title (tr "workspace.shape.menu.show") diff --git a/frontend/src/app/main/ui/workspace/shapes/bool.cljs b/frontend/src/app/main/ui/workspace/shapes/bool.cljs index e53fc3b8f..2cc8f6e00 100644 --- a/frontend/src/app/main/ui/workspace/shapes/bool.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/bool.cljs @@ -33,17 +33,11 @@ (let [shape (unchecked-get props "shape") frame (unchecked-get props "frame") - childs-ref (mf/use-memo (mf/deps shape) #(refs/objects-by-id (:shapes shape) {:with-modifiers? true})) - {:keys [selected modifiers]} (mf/deref refs/local-displacement) + childs-ref (mf/use-memo + (mf/deps (:id shape)) + #(refs/select-children (:id shape))) - add-modifiers - (fn [{:keys [id] :as shape}] - (cond-> shape - (contains? selected id) - (update :modifiers merge modifiers))) - - childs (->> (mf/deref childs-ref) - (mapv add-modifiers))] + childs (mf/deref childs-ref)] [:> shape-container {:shape shape} [:& shape-component diff --git a/frontend/src/app/main/ui/workspace/shapes/path.cljs b/frontend/src/app/main/ui/workspace/shapes/path.cljs index bae6a5d99..a245d5bb2 100644 --- a/frontend/src/app/main/ui/workspace/shapes/path.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/path.cljs @@ -6,11 +6,11 @@ (ns app.main.ui.workspace.shapes.path (:require + [app.common.path.commands :as upc] [app.main.refs :as refs] [app.main.ui.shapes.path :as path] [app.main.ui.shapes.shape :refer [shape-container]] [app.main.ui.workspace.shapes.path.common :as pc] - [app.util.path.commands :as upc] [rumext.alpha :as mf])) (mf/defc path-wrapper diff --git a/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs b/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs index aa683f708..ed653f588 100644 --- a/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs @@ -8,7 +8,9 @@ (:require [app.common.data :as d] [app.common.geom.point :as gpt] - [app.common.geom.shapes.path :as gshp] + [app.common.geom.shapes.path :as gsp] + [app.common.path.commands :as upc] + [app.common.path.shapes-to-path :as ups] [app.main.data.workspace.path :as drp] [app.main.snap :as snap] [app.main.store :as st] @@ -18,10 +20,7 @@ [app.main.ui.workspace.shapes.path.common :as pc] [app.util.dom :as dom] [app.util.keyboard :as kbd] - [app.util.path.commands :as upc] [app.util.path.format :as upf] - [app.util.path.geom :as upg] - [app.util.path.shapes-to-path :as ups] [clojure.set :refer [map-invert]] [goog.events :as events] [rumext.alpha :as mf]) @@ -217,16 +216,16 @@ shape (cond-> shape (not= :path (:type shape)) - ups/convert-to-path + (ups/convert-to-path {}) :always hooks/use-equal-memo) base-content (:content shape) - base-points (mf/use-memo (mf/deps base-content) #(->> base-content upg/content->points)) + base-points (mf/use-memo (mf/deps base-content) #(->> base-content gsp/content->points)) content (upc/apply-content-modifiers base-content content-modifiers) - content-points (mf/use-memo (mf/deps content) #(->> content upg/content->points)) + content-points (mf/use-memo (mf/deps content) #(->> content gsp/content->points)) point->base (->> (map hash-map content-points base-points) (reduce merge)) base->point (map-invert point->base) @@ -269,7 +268,7 @@ ms/mouse-position (mf/deps shape zoom) (fn [position] - (when-let [point (gshp/path-closest-point shape position)] + (when-let [point (gsp/path-closest-point shape position)] (reset! hover-point (when (< (gpt/distance position point) (/ 10 zoom)) point))))) [:g.path-editor {:ref editor-ref} diff --git a/frontend/src/app/util/path/format.cljs b/frontend/src/app/util/path/format.cljs index 312746f90..2cdf6f900 100644 --- a/frontend/src/app/util/path/format.cljs +++ b/frontend/src/app/util/path/format.cljs @@ -6,8 +6,8 @@ (ns app.util.path.format (:require - [app.util.path.commands :as upc] - [app.util.path.subpaths :refer [pt=]] + [app.common.path.commands :as upc] + [app.common.path.subpaths :refer [pt=]] [cuerdas.core :as str])) (defn command->param-list [command] diff --git a/frontend/src/app/util/path/geom.cljs b/frontend/src/app/util/path/geom.cljs deleted file mode 100644 index afb8787a1..000000000 --- a/frontend/src/app/util/path/geom.cljs +++ /dev/null @@ -1,97 +0,0 @@ -;; 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.util.path.geom - (:require - [app.common.data :as d] - [app.common.geom.point :as gpt] - [app.common.geom.shapes.path :as gshp] - [app.util.path.commands :as upc])) - -(defn calculate-opposite-handler - "Given a point and its handler, gives the symetric handler" - [point handler] - (let [handler-vector (gpt/to-vec point handler)] - (gpt/add point (gpt/negate handler-vector)))) - -(defn split-line-to - "Given a point and a line-to command will create a two new line-to commands - that will split the original line into two given a value between 0-1" - [from-p cmd t-val] - (let [to-p (upc/command->point cmd) - sp (gpt/lerp from-p to-p t-val)] - [(upc/make-line-to sp) cmd])) - -(defn split-curve-to - "Given the point and a curve-to command will split the curve into two new - curve-to commands given a value between 0-1" - [from-p cmd t-val] - (let [params (:params cmd) - end (gpt/point (:x params) (:y params)) - h1 (gpt/point (:c1x params) (:c1y params)) - h2 (gpt/point (:c2x params) (:c2y params)) - [[_ to1 h11 h21] - [_ to2 h12 h22]] (gshp/curve-split from-p end h1 h2 t-val)] - [(upc/make-curve-to to1 h11 h21) - (upc/make-curve-to to2 h12 h22)])) - -(defn split-line-to-ranges - "Splits a line into several lines given the points in `values` - for example (split-line-to-ranges p c [0 0.25 0.5 0.75 1] will split - the line into 4 lines" - [from-p cmd values] - (let [to-p (upc/command->point cmd)] - (->> (conj values 1) - (mapv (fn [val] - (-> (gpt/lerp from-p to-p val) - #_(gpt/round 2) - (upc/make-line-to))))))) - -(defn split-curve-to-ranges - "Splits a curve into several curves given the points in `values` - for example (split-curve-to-ranges p c [0 0.25 0.5 0.75 1] will split - the curve into 4 curves that draw the same curve" - [from-p cmd values] - (if (empty? values) - [cmd] - (let [to-p (upc/command->point cmd) - params (:params cmd) - h1 (gpt/point (:c1x params) (:c1y params)) - h2 (gpt/point (:c2x params) (:c2y params)) - - values-set (->> (conj values 1) (into (sorted-set)))] - (->> (d/with-prev values-set) - (mapv - (fn [[t1 t0]] - (let [t0 (if (nil? t0) 0 t0) - [_ to-p h1' h2'] (gshp/subcurve-range from-p to-p h1 h2 t0 t1)] - (upc/make-curve-to (-> to-p #_(gpt/round 2)) h1' h2')))))))) - -(defn opposite-handler - "Calculates the coordinates of the opposite handler" - [point handler] - (let [phv (gpt/to-vec point handler)] - (gpt/add point (gpt/negate phv)))) - -(defn opposite-handler-keep-distance - "Calculates the coordinates of the opposite handler but keeping the old distance" - [point handler old-opposite] - (let [old-distance (gpt/distance point old-opposite) - phv (gpt/to-vec point handler) - phv2 (gpt/multiply - (gpt/unit (gpt/negate phv)) - (gpt/point old-distance))] - (gpt/add point phv2))) - -(defn content->points - "Returns the points in the given content" - [content] - (->> content - (map #(when (-> % :params :x) - (gpt/point (-> % :params :x) (-> % :params :y)))) - (remove nil?) - (into []))) - diff --git a/frontend/src/app/util/path/parser.cljs b/frontend/src/app/util/path/parser.cljs index 7b68caf64..9e6023600 100644 --- a/frontend/src/app/util/path/parser.cljs +++ b/frontend/src/app/util/path/parser.cljs @@ -8,9 +8,9 @@ (:require [app.common.data :as d] [app.common.geom.point :as gpt] + [app.common.geom.shapes.path :as upg] + [app.common.path.commands :as upc] [app.util.path.arc-to-curve :refer [a2c]] - [app.util.path.commands :as upc] - [app.util.path.geom :as upg] [app.util.svg :as usvg] [cuerdas.core :as str])) diff --git a/frontend/src/app/util/path/tools.cljs b/frontend/src/app/util/path/tools.cljs index 3a05c2e1d..9f97ab666 100644 --- a/frontend/src/app/util/path/tools.cljs +++ b/frontend/src/app/util/path/tools.cljs @@ -8,9 +8,9 @@ (:require [app.common.data :as d] [app.common.geom.point :as gpt] + [app.common.geom.shapes.path :as upg] [app.common.math :as mth] - [app.util.path.commands :as upc] - [app.util.path.geom :as upg] + [app.common.path.commands :as upc] [clojure.set :as set])) (defn remove-line-curves