diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3477000c..0f5dd5fb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,6 +19,15 @@ we use the following conventions - el - element - attr, attrs - attribute, attributes +We also use the following namespace aliases + +- v - views +- e - events +- h - handlers +- s - subs + +If the namespace belongs to a different module, we use `module.v`. + ## App structure Main structure @@ -39,7 +48,6 @@ module\ ├── db.cljs <--- schema, validation ├── views.cljs <--- reagent views ├── events.cljs <--- event handlers -├── effects.cljs <--- effectful handlers ├── subs.cljs <--- subscription handlers ├── handlers.cljs <--- helper functions for db transformations ├── styles.css <--- styles diff --git a/src/renderer/attribute/angle.cljs b/src/renderer/attribute/angle.cljs index 165f6142..8dbacdc4 100644 --- a/src/renderer/attribute/angle.cljs +++ b/src/renderer/attribute/angle.cljs @@ -3,16 +3,16 @@ (:require ["@radix-ui/react-popover" :as Popover] [renderer.attribute.hierarchy :as hierarchy] - [renderer.attribute.views :as views] + [renderer.attribute.views :as v] [renderer.components :as comp])) (defmethod hierarchy/form-element ::angle [k v disabled? initial] [:<> - [views/form-input {:key k - :value v - :disabled? disabled? - :placeholder initial}] + [v/form-input {:key k + :value v + :disabled? disabled? + :placeholder initial}] [:> Popover/Root {:modal true} [:> Popover/Trigger {:asChild true} [:button.button.ml-px.level-2.text-muted diff --git a/src/renderer/attribute/color.cljs b/src/renderer/attribute/color.cljs index 7f99a98b..4b4772ae 100644 --- a/src/renderer/attribute/color.cljs +++ b/src/renderer/attribute/color.cljs @@ -5,7 +5,7 @@ ["@re-path/react-color" :refer [ChromePicker]] [re-frame.core :as rf] [renderer.attribute.hierarchy :as hierarchy] - [renderer.attribute.views :as views])) + [renderer.attribute.views :as v])) (derive :stroke ::color) (derive :fill ::color) @@ -14,10 +14,10 @@ (defmethod hierarchy/form-element ::color [k v disabled? initial] [:<> - [views/form-input {:key k - :value v - :disabled? disabled? - :placeholder initial}] + [v/form-input {:key k + :value v + :disabled? disabled? + :placeholder initial}] [:> Popover/Root {:modal true} [:> Popover/Trigger {:asChild true} [:button.color-drip.ml-px.inline-block diff --git a/src/renderer/attribute/hierarchy.cljs b/src/renderer/attribute/hierarchy.cljs index cae33752..75ffd3bb 100644 --- a/src/renderer/attribute/hierarchy.cljs +++ b/src/renderer/attribute/hierarchy.cljs @@ -5,8 +5,8 @@ (defmulti form-element (fn [k _v _disabled? _initial] k)) (defmethod update-attr :default - [el attr f & args] - (update-in el [:attrs attr] f (first args))) + [el attr f & more] + (apply update-in el [:attrs attr] f more)) (defmethod description :d [] diff --git a/src/renderer/attribute/length.cljs b/src/renderer/attribute/length.cljs index 82192ab2..05da90b2 100644 --- a/src/renderer/attribute/length.cljs +++ b/src/renderer/attribute/length.cljs @@ -3,7 +3,7 @@ (:require [re-frame.core :as rf] [renderer.attribute.hierarchy :as hierarchy] - [renderer.attribute.views :as views] + [renderer.attribute.views :as v] [renderer.components :as comp] [renderer.utils.units :as units])) @@ -27,7 +27,7 @@ (defmethod hierarchy/form-element ::length [k v disabled? initial] [:div.flex.w-full - [views/form-input + [v/form-input {:key k :value v :disabled? disabled? @@ -47,5 +47,5 @@ [comp/icon "plus" {:class "small"}]]]]) (defmethod hierarchy/update-attr ::length - [element attribute f & args] - (update-in element [:attrs attribute] #(units/transform f (first args) %))) + [element attribute f & more] + (update-in element [:attrs attribute] #(apply units/transform % f more))) diff --git a/src/renderer/attribute/overflow.cljs b/src/renderer/attribute/overflow.cljs index ca0fbaaa..afedb09c 100644 --- a/src/renderer/attribute/overflow.cljs +++ b/src/renderer/attribute/overflow.cljs @@ -1,7 +1,7 @@ (ns renderer.attribute.overflow (:require [renderer.attribute.hierarchy :as hierarchy] - [renderer.attribute.views :as views])) + [renderer.attribute.views :as v])) (defmethod hierarchy/description :overflow [] @@ -11,7 +11,7 @@ (defmethod hierarchy/form-element :overflow [k v disabled? _initial] - [views/select-input + [v/select-input {:key k :value v :disabled? disabled? diff --git a/src/renderer/attribute/points.cljs b/src/renderer/attribute/points.cljs index 9cfddc0d..598c08a4 100644 --- a/src/renderer/attribute/points.cljs +++ b/src/renderer/attribute/points.cljs @@ -6,7 +6,7 @@ [re-frame.core :as rf] [renderer.attribute.hierarchy :as hierarchy] [renderer.attribute.utils :as utils] - [renderer.attribute.views :as views] + [renderer.attribute.views :as v] [renderer.components :as comp] [renderer.utils.vec :as vec])) @@ -26,11 +26,11 @@ [k v disabled?] (let [state-default? (= @(rf/subscribe [:state]) :default)] [:<> - [views/form-input {:key k - :value (if state-default? v "waiting") - :disabled? (or disabled? - (not v) - (not state-default?))}] + [v/form-input {:key k + :value (if state-default? v "waiting") + :disabled? (or disabled? + (not v) + (not state-default?))}] (when v [:> Popover/Root {:modal true} [:> Popover/Trigger {:asChild true} @@ -43,7 +43,7 @@ :className "popover-content" :align "end"} (when state-default? - (let [points (utils/points-to-vec v)] + (let [points (utils/points->vec v)] [:div.flex.flex-col.v-scroll.py-4.pr-2 {:style {:max-height "500px"}} (map-indexed (fn [index [x y]] diff --git a/src/renderer/attribute/range.cljs b/src/renderer/attribute/range.cljs index 0f9afdf9..181debd2 100644 --- a/src/renderer/attribute/range.cljs +++ b/src/renderer/attribute/range.cljs @@ -1,13 +1,13 @@ (ns renderer.attribute.range (:require [renderer.attribute.hierarchy :as hierarchy] - [renderer.attribute.views :as views])) + [renderer.attribute.views :as v])) (derive :opacity ::range) (defmethod hierarchy/form-element ::range [k v disabled? initial] - [views/range-input k v {:disabled disabled? - :min 0 - :max 1 - :step 0.01} initial]) + [v/range-input k v {:disabled disabled? + :min 0 + :max 1 + :step 0.01} initial]) diff --git a/src/renderer/attribute/stroke_linecap.cljs b/src/renderer/attribute/stroke_linecap.cljs index 9d896b41..a3db8aab 100644 --- a/src/renderer/attribute/stroke_linecap.cljs +++ b/src/renderer/attribute/stroke_linecap.cljs @@ -1,7 +1,7 @@ (ns renderer.attribute.stroke-linecap (:require [renderer.attribute.hierarchy :as hierarchy] - [renderer.attribute.views :as views])) + [renderer.attribute.views :as v])) (defmethod hierarchy/description :stroke-linecap [] @@ -10,20 +10,20 @@ (defmethod hierarchy/form-element :stroke-linecap [k v disabled? initial] - [views/select-input {:key k - :value v - :disabled? disabled? - :initial initial - :default-value "butt" - :items [{:key :butt - :value "butt" - :label "Butt" - :icon "linecap-butt"} - {:key :round - :value "round" - :label "Round" - :icon "linecap-round"} - {:key :square - :value "square" - :label "Square" - :icon "linecap-square"}]}]) + [v/select-input {:key k + :value v + :disabled? disabled? + :initial initial + :default-value "butt" + :items [{:key :butt + :value "butt" + :label "Butt" + :icon "linecap-butt"} + {:key :round + :value "round" + :label "Round" + :icon "linecap-round"} + {:key :square + :value "square" + :label "Square" + :icon "linecap-square"}]}]) diff --git a/src/renderer/attribute/stroke_linejoin.cljs b/src/renderer/attribute/stroke_linejoin.cljs index 74baaa73..40ef7318 100644 --- a/src/renderer/attribute/stroke_linejoin.cljs +++ b/src/renderer/attribute/stroke_linejoin.cljs @@ -1,7 +1,7 @@ (ns renderer.attribute.stroke-linejoin (:require [renderer.attribute.hierarchy :as hierarchy] - [renderer.attribute.views :as views])) + [renderer.attribute.views :as v])) (defmethod hierarchy/description :stroke-linejoin [] @@ -10,17 +10,17 @@ (defmethod hierarchy/form-element :stroke-linejoin [k v disabled? initial] - [views/select-input {:key k - :value v - :disabled? disabled? - :initial initial - :default-value "miter" - :items [{:key :bevel - :value "bevel" - :label "Bevel"} - {:key :miter - :value "miter" - :label "Miter"} - {:key :round - :value "round" - :label "Round"}]}]) + [v/select-input {:key k + :value v + :disabled? disabled? + :initial initial + :default-value "miter" + :items [{:key :bevel + :value "bevel" + :label "Bevel"} + {:key :miter + :value "miter" + :label "Miter"} + {:key :round + :value "round" + :label "Round"}]}]) diff --git a/src/renderer/attribute/utils.cljs b/src/renderer/attribute/utils.cljs index f705c9a7..7edbc0af 100644 --- a/src/renderer/attribute/utils.cljs +++ b/src/renderer/attribute/utils.cljs @@ -1,6 +1,7 @@ (ns renderer.attribute.utils (:require - [clojure.string :as str])) + [clojure.string :as str] + [renderer.utils.units :as units])) (def attrs-order [:d @@ -27,9 +28,17 @@ :id :class :tabindex :style]) -(defn points-to-vec +(defn points->vec [points] (vec (as-> points p (str/triml p) (str/split p #"\s+") (partition 2 p)))) ; OPTIMIZE + +(defn points->px + [points] + (vec (as-> points p + (str/triml p) + (str/split p #"\s+") + (map units/unit->px p) + (partition 2 p)))) diff --git a/src/renderer/attribute/views.cljs b/src/renderer/attribute/views.cljs index 3e38fa59..afb40eb5 100644 --- a/src/renderer/attribute/views.cljs +++ b/src/renderer/attribute/views.cljs @@ -8,7 +8,8 @@ [re-frame.core :as rf] [renderer.attribute.hierarchy :as hierarchy] [renderer.components :as comp] - [renderer.tools.base :as tools])) + [renderer.tools.base :as tools] + [renderer.utils.spec :as spec])) (defn browser-support [browser version-added] @@ -23,12 +24,7 @@ (defn caniusethis [{:keys [tag attr]}] - (let [data (if attr - (or (-> tools/svg-spec :elements tag attr :__compat) - (-> tools/svg-spec :attributes :presentation attr :__compat) - (-> tools/svg-spec :attributes :core attr :__compat) - (-> tools/svg-spec :attributes :style attr :__compat)) - (-> tools/svg-spec :elements tag :__compat))] + (let [data (if attr (spec/compat-data tag attr) (spec/compat-data tag))] (when (or (:support data) (isa? tag ::tools/animation)) [:div.flex.flex-col (when (some :version_added (vals (:support data))) diff --git a/src/renderer/cmdk/views.cljs b/src/renderer/cmdk/views.cljs index ef71d1cc..1ad6747c 100644 --- a/src/renderer/cmdk/views.cljs +++ b/src/renderer/cmdk/views.cljs @@ -12,8 +12,9 @@ (when-not (= type :separator) [:> Command/CommandItem {:key key - :on-select #(doall (rf/dispatch action) - (rf/dispatch [:cmdk/set false]))} + :on-select (fn [] + (rf/dispatch action) + (rf/dispatch [:cmdk/set false]))} (str group " / " label) [:div.right-slot [comp/shortcuts action]]])) diff --git a/src/renderer/components.cljs b/src/renderer/components.cljs index 0e885074..36c228c1 100644 --- a/src/renderer/components.cljs +++ b/src/renderer/components.cljs @@ -136,8 +136,8 @@ [:div.right-slot [shortcuts action]]])) -;; TODO: Add and group actions (def element-menu + ;; TODO: Add and group actions [{:label "Cut" :action [:element/cut]} {:label "Copy" diff --git a/src/renderer/db.cljs b/src/renderer/db.cljs index e8c9438d..23dcdf5a 100644 --- a/src/renderer/db.cljs +++ b/src/renderer/db.cljs @@ -9,7 +9,7 @@ (def app [:map [:tool keyword?] - [:mouse-pos [:tuple double? double?]] + [:pointer-pos [:tuple double? double?]] [:zoom-factor double?] [:state keyword?] [:grid? boolean?] @@ -28,7 +28,7 @@ (def default {:tool :select - :mouse-pos [0 0] + :pointer-pos [0 0] :zoom-sensitivity 0.3 :state :default :documents {} diff --git a/src/renderer/debug.cljs b/src/renderer/debug.cljs index 64dd0fc9..16be7709 100644 --- a/src/renderer/debug.cljs +++ b/src/renderer/debug.cljs @@ -21,16 +21,16 @@ (str (mapv units/->fixed @(rf/subscribe [:frame/viewbox])))] ["Mouse position" - (str @(rf/subscribe [:mouse-pos]))] + (str @(rf/subscribe [:pointer-pos]))] ["Adjusted mouse position" - (str (mapv units/->fixed @(rf/subscribe [:adjusted-mouse-pos])))] + (str (mapv units/->fixed @(rf/subscribe [:adjusted-pointer-pos])))] ["Mouse offset" - (str @(rf/subscribe [:mouse-offset]))] + (str @(rf/subscribe [:pointer-offset]))] ["Adjusted mouse offset" - (str (mapv units/->fixed @(rf/subscribe [:adjusted-mouse-offset])))] + (str (mapv units/->fixed @(rf/subscribe [:adjusted-pointer-offset])))] ["Mouse drag?" (str @(rf/subscribe [:drag?]))] diff --git a/src/renderer/document/core.cljs b/src/renderer/document/core.cljs index d3993c0a..632e14eb 100644 --- a/src/renderer/document/core.cljs +++ b/src/renderer/document/core.cljs @@ -1,5 +1,4 @@ (ns renderer.document.core (:require - [renderer.document.effects] [renderer.document.events] [renderer.document.subs])) diff --git a/src/renderer/document/db.cljs b/src/renderer/document/db.cljs index 009d6bf2..686dee9d 100644 --- a/src/renderer/document/db.cljs +++ b/src/renderer/document/db.cljs @@ -1,6 +1,6 @@ (ns renderer.document.db (:require - [renderer.element.db :as element-db])) + [renderer.element.db :as element.db])) (def document [:map @@ -13,7 +13,7 @@ [:rotate double?] [:filter string?] [:pan [:tuple double? double?]] - [:elements [:map-of :uuid element-db/element]]]) + [:elements [:map-of :uuid element.db/element]]]) (def default-document {:hovered-keys #{} diff --git a/src/renderer/document/effects.cljs b/src/renderer/document/effects.cljs deleted file mode 100644 index 8e6fdf04..00000000 --- a/src/renderer/document/effects.cljs +++ /dev/null @@ -1,26 +0,0 @@ -(ns renderer.document.effects - (:require - [de-dupe.core :as dd] - [re-frame.core :as rf])) - -(rf/reg-event-fx - :document/open - (fn [_ [_]] - {:send-to-main {:action "openDocument"}})) - -(rf/reg-event-fx - :document/save - (fn [{:keys [db]} [_]] - (let [document (get-in db [:documents (:active-document db)]) - duped (assoc document :history (dd/de-dupe (:history document)))] - {:send-to-main {:action "saveDocument" :data (pr-str duped)}}))) - -(rf/reg-event-fx - :document/save-as - (fn [{:keys [db]} [_]] - db)) - -(rf/reg-event-fx - :document/save-all - (fn [{:keys [db]} [_]] - db)) diff --git a/src/renderer/document/events.cljs b/src/renderer/document/events.cljs index 5af49866..f94c2e1e 100644 --- a/src/renderer/document/events.cljs +++ b/src/renderer/document/events.cljs @@ -1,12 +1,13 @@ (ns renderer.document.events (:require + [de-dupe.core :as dd] [re-frame.core :as rf] [re-frame.interceptor :refer [->interceptor get-effect get-coeffect assoc-coeffect assoc-effect]] [renderer.document.db :as db] - [renderer.document.handlers :as handlers] - [renderer.element.handlers :as element-handlers] + [renderer.document.handlers :as h] + [renderer.element.handlers :as element.h] [renderer.frame.handlers :as frame] - [renderer.history.handlers :as history] + [renderer.history.handlers :as history.h] [renderer.utils.uuid :as uuid] [renderer.utils.vec :as vec])) @@ -63,14 +64,14 @@ (fn [{active-document :active-document :as db} [_ fill]] (-> db (assoc-in [:documents active-document :fill] fill) - (element-handlers/set-attribute :fill fill)))) + (element.h/set-attribute :fill fill)))) (rf/reg-event-db :document/set-stroke (fn [{active-document :active-document :as db} [_ stroke]] (-> db (assoc-in [:documents active-document :stroke] stroke) - (element-handlers/set-attribute :stroke stroke)))) + (element.h/set-attribute :stroke stroke)))) (rf/reg-event-db :document/new @@ -86,17 +87,17 @@ (update :document-tabs #(vec/add % (inc active-index) key)) (assoc :active-document key) (frame/pan-to-element (:active-page db/default-document)) - (history/init "Create document"))))) + (history.h/init "Create document"))))) (rf/reg-event-db :document/close (fn [db [_ key]] - (handlers/close db key))) + (h/close db key))) (rf/reg-event-db :document/close-active (fn [db [_]] - (handlers/close db))) + (h/close db))) (rf/reg-event-db :document/close-others @@ -131,3 +132,25 @@ dragged-index (.indexOf document-tabs dragged-key) swapped-index (.indexOf document-tabs swapped-key)] (assoc db :document-tabs (vec/swap document-tabs dragged-index swapped-index))))) + +(rf/reg-event-fx + :document/open + (fn [_ [_]] + {:send-to-main {:action "openDocument"}})) + +(rf/reg-event-fx + :document/save + (fn [{:keys [db]} [_]] + (let [document (get-in db [:documents (:active-document db)]) + duped (assoc document :history (dd/de-dupe (:history document)))] + {:send-to-main {:action "saveDocument" :data (pr-str duped)}}))) + +(rf/reg-event-fx + :document/save-as + (fn [{:keys [db]} [_]] + db)) + +(rf/reg-event-fx + :document/save-all + (fn [{:keys [db]} [_]] + db)) diff --git a/src/renderer/document/views.cljs b/src/renderer/document/views.cljs index 48169f44..293a0322 100644 --- a/src/renderer/document/views.cljs +++ b/src/renderer/document/views.cljs @@ -4,7 +4,7 @@ [re-frame.core :as rf] [reagent.core :as ra] [renderer.components :as comp] - [renderer.history.views :as history])) + [renderer.history.views :as history.h])) (defn actions [] [:div.flex.toolbar @@ -54,7 +54,7 @@ :max-width "14px" :background "var(--level-0)" :font-size "1em"}} - (history/select-options @(rf/subscribe [:history/undos]))] + (history.h/select-options @(rf/subscribe [:history/undos]))] [comp/icon-button "redo" {:title "Undo" :on-click #(rf/dispatch [:history/redo]) @@ -67,7 +67,7 @@ :max-width "14px" :background "var(--level-0)" :font-size "1em"}} - (history/select-options @(rf/subscribe [:history/redos]))]]) + (history.h/select-options @(rf/subscribe [:history/redos]))]]) (defn close-button [key] diff --git a/src/renderer/effects.cljs b/src/renderer/effects.cljs index cdb53412..dbc92033 100644 --- a/src/renderer/effects.cljs +++ b/src/renderer/effects.cljs @@ -8,3 +8,13 @@ (fn [data] (when platform/electron? (js/window.api.send "toMain" (clj->js data))))) + +(rf/reg-fx + :clipboard-write + (fn [text-html] + (js/navigator.clipboard.write + (clj->js [(js/ClipboardItem. + #js {"text/html" (when text-html + (js/Blob. + [text-html] + #js {:type ["text/html"]}))})])))) diff --git a/src/renderer/element/core.cljs b/src/renderer/element/core.cljs index 9a8e437c..acb1200e 100644 --- a/src/renderer/element/core.cljs +++ b/src/renderer/element/core.cljs @@ -1,5 +1,4 @@ (ns renderer.element.core (:require - [renderer.element.effects] [renderer.element.events] [renderer.element.subs])) diff --git a/src/renderer/element/effects.cljs b/src/renderer/element/effects.cljs deleted file mode 100644 index a4e407c0..00000000 --- a/src/renderer/element/effects.cljs +++ /dev/null @@ -1,40 +0,0 @@ -(ns renderer.element.effects - (:require - [re-frame.core :as rf] - [renderer.element.handlers :as element-handlers] - [renderer.history.handlers :as history] - [renderer.tools.base :as tools])) - - -(defn elements->string - [elements] - (reduce #(str % (tools/render-to-string %2)) "" elements)) - -(rf/reg-fx - :clipboard-write - (fn [text-html] - (js/navigator.clipboard.write - (clj->js [(js/ClipboardItem. - #js {"text/html" (when text-html - (js/Blob. - [text-html] - #js {:type ["text/html"]}))})])))) - -(rf/reg-event-fx - :element/copy - (fn [{:keys [db]} [_]] - (let [selected-elements (element-handlers/selected db) - text-html (elements->string selected-elements)] - {:db (element-handlers/copy db) - :clipboard-write [text-html]}))) - -(rf/reg-event-fx - :element/cut - (fn [{:keys [db]} [_]] - (let [selected-elements (element-handlers/selected db) - text-html (elements->string selected-elements)] - {:db (-> db - element-handlers/copy - element-handlers/delete - (history/finalize "Cut selection")) - :clipboard-write [text-html]}))) diff --git a/src/renderer/element/events.cljs b/src/renderer/element/events.cljs index 10b7db37..056c2551 100644 --- a/src/renderer/element/events.cljs +++ b/src/renderer/element/events.cljs @@ -2,289 +2,293 @@ (:require [clojure.string :as str] [re-frame.core :as rf] - [renderer.element.handlers :as handlers] - [renderer.frame.handlers :as frame-handlers] - [renderer.history.handlers :as history-handlers] - [renderer.tools.base :as tools])) + [renderer.element.handlers :as h] + [renderer.frame.handlers :as frame.h] + [renderer.history.handlers :as history.h] + [renderer.tools.base :as tools] + [renderer.utils.bounds :as bounds])) (rf/reg-event-db :element/select (fn [db [_ el-k multi?]] (-> db - (handlers/select el-k multi?) - (history-handlers/finalize "Select element")))) + (h/select el-k multi?) + (history.h/finalize "Select element")))) (rf/reg-event-db :element/toggle-property (fn [db [_ key property]] (-> db - (handlers/toggle-property key property) - (history-handlers/finalize (str "Toggle " (name property)))))) + (h/toggle-property key property) + (history.h/finalize "Toggle " (name property))))) (rf/reg-event-db :element/preview-property (fn [db [_ el-k k v]] - (handlers/set-property db el-k k v))) + (h/set-property db el-k k v))) (rf/reg-event-db :element/set-property (fn [db [_ el-k k v]] (-> db - (handlers/set-property el-k k v) - (history-handlers/finalize (str "Set " (name k) " to " v))))) + (h/set-property el-k k v) + (history.h/finalize "Set " (name k) " to " v)))) (rf/reg-event-db :element/lock - (fn [db [_]] + (fn [db _] (-> db - (handlers/lock) - (history-handlers/finalize "Lock selection")))) + h/lock + (history.h/finalize "Lock selection")))) (rf/reg-event-db :element/unlock - (fn [db [_]] + (fn [db _] (-> db - (handlers/unlock) - (history-handlers/finalize "Unlock selection")))) + h/unlock + (history.h/finalize "Unlock selection")))) (rf/reg-event-db :element/set-attribute (fn [db [_ k v]] (-> db - (handlers/set-attribute k v) - (history-handlers/finalize (str "Set " (name k) " to " v))))) + (h/set-attribute k v) + (history.h/finalize "Set " (name k) " to " v)))) (rf/reg-event-db :element/inc-attribute (fn [db [_ k]] (-> db - (handlers/update-attribute k inc) - (history-handlers/finalize (str "Increase " (name k)))))) + (h/update-attribute k inc) + (history.h/finalize "Increase " (name k))))) (rf/reg-event-db :element/dec-attribute (fn [db [_ k]] (-> db - (handlers/update-attribute k dec) - (history-handlers/finalize (str "Decrease " (name k)))))) + (h/update-attribute k dec) + (history.h/finalize "Decrease " (name k))))) (rf/reg-event-db :element/preview-attribute (fn [db [_ k v]] - (handlers/set-attribute db k v))) + (h/set-attribute db k v))) (rf/reg-event-db :element/fill (fn [db [_ color]] (-> db - (handlers/set-attribute :fill color) - (history-handlers/finalize (str "Fill " color))))) + (h/set-attribute :fill color) + (history.h/finalize "Fill " color)))) (rf/reg-event-db :element/delete (fn [db _] (-> db - (handlers/delete) - (handlers/deselect-all) - (history-handlers/finalize "Delete selection")))) + h/delete + (history.h/finalize "Delete selection")))) (rf/reg-event-db :element/deselect-all (fn [db _] - (-> db - (handlers/deselect-all) - (history-handlers/finalize "Deselect all")))) + (-> db h/deselect (history.h/finalize "Deselect all")))) (rf/reg-event-db :element/select-all (fn [db _] - (-> db - (handlers/select-all) - (history-handlers/finalize "Select all")))) + (-> db h/select-all (history.h/finalize "Select all")))) (rf/reg-event-db :element/select-same-tags (fn [db _] (-> db - (handlers/select-same-tags) - (history-handlers/finalize "Select same tags")))) + h/select-same-tags + (history.h/finalize "Select same tags")))) (rf/reg-event-db :element/invert-selection (fn [db _] (-> db - (handlers/invert-selection) - (history-handlers/finalize "Invert selection")))) + h/invert-selection + (history.h/finalize "Invert selection")))) (rf/reg-event-db :element/raise (fn [db _] - (-> db - (handlers/update-selected-by handlers/raise) - (history-handlers/finalize "Raise selection")))) + (-> db + h/raise + (history.h/finalize "Raise selection")))) (rf/reg-event-db :element/lower (fn [db _] - (-> db - (handlers/update-selected-by handlers/lower) - (history-handlers/finalize "Lower selection")))) + (-> db + h/lower + (history.h/finalize "Lower selection")))) (rf/reg-event-db :element/raise-to-top (fn [db _] (-> db - (handlers/update-selected-by handlers/raise-to-top) - (history-handlers/finalize "Raise selection to top")))) + h/raise-to-top + (history.h/finalize "Raise selection to top")))) (rf/reg-event-db :element/lower-to-bottom (fn [db _] (-> db - (handlers/update-selected-by handlers/lower-to-bottom) - (history-handlers/finalize "Lower selection to bottom")))) + h/lower-to-bottom + (history.h/finalize "Lower selection to bottom")))) (rf/reg-event-db :element/align (fn [db [_ direction]] (-> db - (handlers/align direction) - (history-handlers/finalize (str "Align " (name direction)))))) + (h/align direction) + (history.h/finalize "Align " (name direction))))) (rf/reg-event-db :element/export (fn [db _] - (let [xml (tools/render-to-string (handlers/active-page db))] + (let [xml (tools/render-to-string (h/active-page db))] (js/window.api.send "toMain" #js {:action "export" :data xml})))) (rf/reg-event-db :element/paste (fn [db _] (-> db - (handlers/paste) - (history-handlers/finalize "Paste selection")))) + h/paste + (history.h/finalize "Paste selection")))) (rf/reg-event-db :element/paste-in-place (fn [db _] (-> db - (handlers/paste-in-place) - (history-handlers/finalize "Paste selection in place")))) + h/paste-in-place + (history.h/finalize "Paste selection in place")))) (rf/reg-event-db :element/paste-styles (fn [db _] (-> db - (handlers/paste-styles) - (history-handlers/finalize "Paste styles to selection")))) + h/paste-styles + (history.h/finalize "Paste styles to selection")))) (rf/reg-event-db :element/duplicate-in-place (fn [db [_]] (-> db - (handlers/duplicate-in-place) - (history-handlers/finalize "Duplicate selection")))) + h/duplicate-in-place + (history.h/finalize "Duplicate selection")))) (rf/reg-event-db :element/translate (fn [db [_ offset]] (-> db - (handlers/translate offset) - (history-handlers/finalize (str "Move selection " offset))))) + (h/translate offset) + (history.h/finalize "Move selection by " offset)))) + +(rf/reg-event-db + :element/scale + (fn [db [_ ratio]] + (let [bounds (h/selected-bounds db) + pivot-point (bounds/center bounds)] + (-> db + (h/scale ratio pivot-point) + (history.h/finalize "Scale selection by " ratio))))) (rf/reg-event-db :element/move-up (fn [db [_]] (-> db - (handlers/translate [0 -1]) - (history-handlers/finalize (str "Move selection up"))))) + (h/translate [0 -1]) + (history.h/finalize "Move selection up")))) (rf/reg-event-db :element/move-down (fn [db [_]] (-> db - (handlers/translate [0 1]) - (history-handlers/finalize (str "Move selection down"))))) + (h/translate [0 1]) + (history.h/finalize "Move selection down")))) (rf/reg-event-db :element/move-left (fn [db [_]] (-> db - (handlers/translate [-1 0]) - (history-handlers/finalize (str "Move selection left"))))) + (h/translate [-1 0]) + (history.h/finalize "Move selection left")))) (rf/reg-event-db :element/move-right (fn [db [_]] (-> db - (handlers/translate [1 0]) - (history-handlers/finalize (str "Move selection right"))))) + (h/translate [1 0]) + (history.h/finalize "Move selection right")))) (rf/reg-event-db :element/->path (fn [db _] (-> db - (handlers/->path) - (history-handlers/finalize "Convert selection to path")))) + h/->path + (history.h/finalize "Convert selection to path")))) (rf/reg-event-db :element/stroke->path (fn [db _] (-> db - (handlers/stroke->path) - (history-handlers/finalize "Convert selection's stroke to path")))) + h/stroke->path + (history.h/finalize "Convert selection's stroke to path")))) (rf/reg-event-db :element/bool-operation (fn [db [_ operation]] - (if (> (count (handlers/selected db)) 1) + (if (> (count (h/selected db)) 1) (-> db - (handlers/bool-operation operation) - (history-handlers/finalize (-> operation name str/capitalize))) db))) + (h/bool-operation operation) + (history.h/finalize (-> operation name str/capitalize))) db))) (rf/reg-event-db :element/create (fn [db [_ element]] (-> db - (handlers/create element) - (history-handlers/finalize (str "Create " (name (:tag element))))))) + (h/create element) + (history.h/finalize "Create " (name (:tag element)))))) (rf/reg-event-db :element/animate (fn [db [_ tag attrs]] (-> db - (handlers/animate tag attrs) - (history-handlers/finalize (name tag))))) + (h/animate tag attrs) + (history.h/finalize (name tag))))) (rf/reg-event-db :element/set-parent (fn [db [_ element-key parent-key]] (-> db - (handlers/set-parent element-key parent-key) - (history-handlers/finalize "Set parent of selection")))) + (h/set-parent element-key parent-key) + (history.h/finalize "Set parent of selection")))) (rf/reg-event-db :element/group (fn [db _] - (-> db - (handlers/group) - (history-handlers/finalize "Group selection")))) + (-> db + h/group + (history.h/finalize "Group selection")))) (rf/reg-event-db :element/ungroup (fn [db _] - (-> db - (handlers/ungroup) - (history-handlers/finalize "Ungroup selection")))) + (-> db + h/ungroup + (history.h/finalize "Ungroup selection")))) (rf/reg-event-db :element/add-page (fn [db _] - (let [[_ y1 x2 _] (tools/elements-bounds - (handlers/elements db) - (handlers/pages db)) - {:keys [width height fill]} (:attrs (handlers/active-page db)) - db (handlers/create db {:tag :page + (let [[_ y1 x2 _] (tools/elements-bounds (h/elements db) + (h/pages db)) + {:keys [width height fill]} (:attrs (h/active-page db)) + db (h/create db {:tag :page :name "Page" :attrs {:x (+ x2 100) :y y1 @@ -292,13 +296,36 @@ :height height :fill fill}})] (-> db - (frame-handlers/pan-to-element (:key (handlers/active-page db))) - (history-handlers/finalize "Add page"))))) + (frame.h/pan-to-element (:key (h/active-page db))) + (history.h/finalize "Add page"))))) #_:clj-kondo/ignore (rf/reg-event-db :element/manipulate-path (fn [db [_ action]] (-> db - (handlers/manipulate-path action) - (history-handlers/finalize (str/capitalize (str (name action) "path")))))) + (h/manipulate-path action) + (history.h/finalize (str/capitalize (name action)) "path")))) + +(defn elements->string + [elements] + (reduce #(str % (tools/render-to-string %2)) "" elements)) + +(rf/reg-event-fx + :element/copy + (fn [{:keys [db]} [_]] + (let [selected-elements (h/selected db) + text-html (elements->string selected-elements)] + {:db (h/copy db) + :clipboard-write [text-html]}))) + +(rf/reg-event-fx + :element/cut + (fn [{:keys [db]} [_]] + (let [selected-elements (h/selected db) + text-html (elements->string selected-elements)] + {:db (-> db + h/copy + h/delete + (history.h/finalize "Cut selection")) + :clipboard-write [text-html]}))) diff --git a/src/renderer/element/handlers.cljs b/src/renderer/element/handlers.cljs index a0748fec..8f006a3c 100644 --- a/src/renderer/element/handlers.cljs +++ b/src/renderer/element/handlers.cljs @@ -8,23 +8,31 @@ [renderer.utils.bounds :as bounds] [renderer.utils.map :as map] [renderer.utils.uuid :as uuid] - [renderer.utils.vec :as vec])) + [renderer.utils.vec :as vec] + [renderer.utils.spec :as spec])) -(defn path [db] - [:documents (:active-document db) :elements]) +(defn path + ([db] + [:documents (:active-document db) :elements]) + ([db el-k] + (conj (path db) el-k))) + +(defn update-el + [db key f & more] + (apply update-in db (conj (path db) key) f more)) (defn elements [db] (get-in db (path db))) -(defn get-element +(defn element [db k] - (get (elements db) k)) + (get-in db (path db k))) (defn active-page [db] (->> (get-in db [:documents (:active-document db) :active-page]) - (get-element db))) + (element db))) (defn selected [db] @@ -45,7 +53,7 @@ (and (first parents) (empty? (rest parents)) (= (count selected) - (count (:children (get-element db (first parents))))))) + (count (:children (element db (first parents))))))) (or (parent db (parent db (first selected))) (active-page db)) @@ -57,7 +65,7 @@ (active-page db)))) ([db el] (when-let [parent (:parent el)] - (get-element db parent)))) + (element db parent)))) (defn siblings ([db] @@ -79,7 +87,7 @@ parent-keys #{}] (if parent-key (recur - (:parent (get-element db parent-key)) + (:parent (element db parent-key)) (conj parent-keys parent-key)) parent-keys))) @@ -88,8 +96,8 @@ (update-in db [:documents (:active-document db)] dissoc :temp-element)) (defn set-temp - [db element] - (assoc-in db [:documents (:active-document db) :temp-element] element)) + [db el] + (assoc-in db [:documents (:active-document db) :temp-element] el)) (defn get-temp [db] @@ -116,8 +124,8 @@ (assoc-in db (path db) (reduce f (elements db) (selected db)))) (defn update-property - [db el-k k f] - (update-in db (conj (path db) el-k k) f)) + [db el-k k f & more] + (apply update-in db (conj (path db) el-k k) f more)) (defn toggle-property [db el-k k] @@ -135,62 +143,59 @@ ([db el-k k v] (let [attr-path (conj (path db) el-k :attrs k)] (cond-> db - (and (not (:locked? (get-element db el-k))) - (get-in db attr-path)) (assoc-in attr-path v))))) + (and (not (:locked? (element db el-k))) + (-> (element db el-k) tools/attributes k)) + (assoc-in attr-path v))))) (defn update-attribute - [db k f] - (update-selected-by db (fn [elements el] - (if (and (not (:locked? el)) - (-> el tools/attributes k)) - (update elements - (:key el) - #(hierarchy/update-attr % k f)) - elements)))) + ([db k f] + (reduce #(update-attribute %1 %2 k f) db (selected db))) + ([db el k f] + (cond-> db + (and (not (:locked? el)) (-> el tools/attributes k)) + (update-el (:key el) hierarchy/update-attr k f)))) (defn deselect - [db key] - (set-property db key :selected? false)) - -(defn deselect-all - [db] - (reduce deselect db (keys (elements db)))) + ([db] + (reduce deselect db (keys (elements db)))) + ([db key] + (set-property db key :selected? false))) (defn select ([db key] (set-property db key :selected? true)) ([db key multi?] - (if-let [el (get-element db key)] + (if-let [el (element db key)] (if-not multi? (-> db - deselect-all + deselect (select key) (set-active-page (:key (page db el)))) (toggle-property db key :selected?)) - (deselect-all db)))) + (deselect db)))) (defn select-all [db] - (reduce select (deselect-all db) (siblings db))) + (reduce select (deselect db) (siblings db))) (defn selected-tags [db] - (reduce (fn [tags element] (conj tags (:tag element))) #{} (selected db))) + (reduce #(conj %1 (:tag %2)) #{} (selected db))) (defn select-same-tags [db] (let [selected-tags (selected-tags db)] - (reduce (fn [db element] - (if (contains? selected-tags (:tag element)) - (select db (:key element)) - db)) (deselect-all db) (vals (elements db))))) + (reduce (fn [db {:keys [key tag]}] + (cond-> db + (contains? selected-tags tag) + (select key))) (deselect db) (vals (elements db))))) (defn invert-selection [db] (reduce (fn [db {:keys [key tag]}] - (if (contains? #{:page :canvas} tag) - db - (update-in db (conj (path db) key :selected?) not))) + (cond-> db + (not (contains? #{:page :canvas} tag)) + (update-attribute key :selected? not))) db (vals (elements db)))) @@ -207,8 +212,8 @@ (assoc-in db [:documents (:active-document db) :hovered-keys] #{})) (defn clear-ignored - [{active-document :active-document :as db}] - (assoc-in db [:documents active-document :ignored-keys] #{})) + [db] + (assoc-in db [:documents (:active-document db) :ignored-keys] #{})) (defmulti intersects-with-bounds? (fn [element _] (:tag element))) @@ -217,14 +222,14 @@ (defn lock ([db] (reduce lock db (selected-keys db))) - ([db k] - (set-property db k :locked? true))) + ([db el-k] + (set-property db el-k :locked? true))) (defn unlock ([db] (reduce unlock db (selected-keys db))) - ([db k] - (set-property db k :locked? false))) + ([db el-k] + (set-property db el-k :locked? false))) (defn copy [db] @@ -234,7 +239,8 @@ ([db] (reduce delete db (selected-keys db))) ([db k] - (let [el (get-element db k) + (let [el (element db k) + ;; OPTIMIZE: No need to recur to delete all children db (reduce delete db (:children el))] (cond-> db :always @@ -249,119 +255,105 @@ (update-in (path db) dissoc k))))) (defn update-index - [elements el f] - (let [siblings (siblings el) - index (.indexOf siblings (:key el))] - (assoc-in elements - [(:parent el) :children] - (vec/swap siblings index (f index))))) + [db el f] + (let [siblings (siblings db el) + index (.indexOf siblings (:key el)) + new-index (f index)] + (cond-> db + (<= 0 new-index (-> siblings count dec)) + (update-property (:parent el) :children vec/move index (f index))))) (defn raise - [elements el] - (update-index elements el inc)) + ([db] + (reduce raise db (selected db))) + ([db el] + (update-index db el inc))) (defn lower - [elements el] - (update-index elements el dec)) + ([db] + (reduce lower db (selected db))) + ([db el] + (update-index db el dec))) (defn lower-to-bottom - [elements el] - (update-index elements el (fn [_] 0))) + ([db] + (reduce lower-to-bottom db (selected db))) + ([db el] + (update-index db el (fn [_] 0)))) (defn raise-to-top - [elements el] - (update-index elements el #(-> el - siblings - count - dec))) + ([db] + (reduce raise-to-top db (selected db))) + ([db el] + (update-index db el #(-> (siblings db el) count dec)))) (defn translate ([db offset] (reduce #(translate %1 %2 offset) db (selected db))) ([db el offset] - (assoc-in db (conj (path db) (:key el)) (tools/translate el offset)))) + (cond-> db + (not (:locked? el)) + (update-el (:key el) tools/translate offset)))) (defn selected-bounds [db] (tools/elements-bounds (elements db) (selected db))) (defn scale - [db offset _lock-ratio? _in-place?] - (let [[x1 y1 x2 y2] (selected-bounds db) - outer-dimensions (bounds/->dimensions [x1 y1 x2 y2]) - handler (-> db :clicked-element :key)] - (update-selected-by - db - (fn [elements element] - (let [[inner-x1 inner-y1 inner-x2 inner-y2] (tools/bounds element (elements db)) - inner-dimensions (bounds/->dimensions [inner-x1 inner-y1 inner-x2 inner-y2]) - scale-multiplier (mat/div inner-dimensions outer-dimensions) - translate-multiplier (mat/div (case handler - :middle-right [(- inner-x1 x1) 0] - :middle-left [(- x2 inner-x2) 0] - :top-middle [0 (- y2 inner-y2)] - :bottom-middle [0 (- inner-y1 y1)] - :top-right [(- inner-x1 x1) (- y2 inner-y2)] - :top-left [(- x2 inner-x2) (- y2 inner-y2)] - :bottom-right [(- inner-x1 x1) (- inner-y1 y1)] - :bottom-left [(- x2 inner-x2) (- inner-y1 y1)]) outer-dimensions)] - (assoc elements (:key element) (-> element - (tools/scale (mat/mul offset scale-multiplier) handler) - (tools/translate (mat/mul offset translate-multiplier))))))))) + ([db ratio pivot-point] + (reduce #(scale %1 %2 ratio pivot-point) db (selected db))) + ([db el ratio pivot-point] + (cond-> db + (not (:locked? el)) + (update-el (:key el) tools/scale ratio pivot-point)))) (defn align ([db direction] - (update-selected-by - db - (fn [elements el] - (let [[x1 y1 x2 y2] (tools/bounds el (elements db)) - [width height] (bounds/->dimensions [x1 y1 x2 y2]) - parent ((:parent el) elements) - [parent-x1 parent-y1 parent-x2 parent-y2] (tools/bounds parent (elements db)) - [parent-width parent-height] (mat/sub [parent-x2 parent-y2] [parent-x1 parent-y1])] - (assoc elements - (:key el) - (tools/translate el - (case direction - :top [0 (- y1)] - :center-vertical [0 (- (/ parent-height 2) - (+ y1 (/ height 2)))] - :bottom [0 (- parent-height y2)] - :left [(- x1) 0] - :center-horizontal [(- (/ parent-width 2) - (+ x1 (/ width 2))) 0] - :right [(- parent-width x2) 0])))))))) + (reduce #(align %1 %2 direction) db (selected db))) + ([db el direction] + (let [bounds (tools/bounds el (elements db)) + center (bounds/center bounds) + parent-bounds (tools/bounds (parent db el) (elements db)) + parent-center (bounds/center parent-bounds) + [cx cy] (mat/sub parent-center center) + [x1 y1 x2 y2] (mat/sub parent-bounds bounds)] + (translate db el (case direction + :top [0 y1] + :center-vertical [0 cy] + :bottom [0 y2] + :left [x1 0] + :center-horizontal [cx 0] + :right [x2 0]))))) (defn ->path ([db] (reduce ->path db (selected db))) ([db el] - (if (not (:locked? el)) - (assoc-in db (conj (path db) (:key el)) (tools/->path el)) - db))) + (cond-> db + (not (:locked? el)) (update-el (:key el) tools/->path)))) (defn stroke->path ([db] (reduce stroke->path db (selected db))) ([db el] - (if (not (:locked? el)) - (assoc-in db (conj (path db) (:key el)) (tools/stroke->path el)) - db))) + (cond-> db + (not (:locked? el)) (update-el (:key el) tools/stroke->path)))) + +(def default-props + {:type :element + :visible? true + :selected? true + :children []}) (defn create-element - [db parent-key el] + [db el] (let [key (uuid/generate) - el (map/deep-merge el {:key key - :type :element - :visible? true - :selected? true - :parent parent-key - :children []})] + parent (if (page? el) :canvas (-> db active-page :key)) + el (map/deep-merge el default-props {:key key :parent parent})] (cond-> db :always (-> (assoc-in (conj (path db) key) el) - (update-in (conj (path db) parent-key :children) - #(vec (conj % key)))) + (update-property (:parent el) :children #(vec (conj % key)))) (not= (:tool db) :select) (tools/set-tool :select) @@ -369,38 +361,40 @@ (page? el) (set-active-page key)))) -;; TODO: Handle child elements (defn create ([db] (-> db (create (get-temp db)) clear-temp)) ([db elements] + ;; TODO: Handle child elements (let [active-page (active-page db) - page? (page? elements) - parent-key (if page? :canvas (:key active-page))] - (if elements - (cond-> (reduce (fn [db el] (create-element db parent-key el)) - (deselect-all db) - (if (seq? elements) elements [elements])) - (not page?) - (translate [(- (get-in active-page [:attrs :x])) (- (get-in active-page [:attrs :y]))])) - db)))) + elements (if (seq? elements) elements [elements])] + (cond-> (reduce create-element (deselect db) elements) + (not page?) + (translate [(- (get-in active-page [:attrs :x])) + (- (get-in active-page [:attrs :y]))]))))) + +(defn bool + [path-a path-b operation] + (case operation + :unite (.unite path-a path-b) + :intersect (.intersect path-a path-b) + :subtract (.subtract path-a path-b) + :exclude (.exclude path-a path-b) + :divide (.divide path-a path-b))) (defn bool-operation [db operation] - (let [selected-elements (selected db) ; TODO: sort elements by visibily index + ;; TODO: Sort elements by visibily index + (let [selected-elements (selected db) attrs (-> selected-elements first tools/->path :attrs) new-path (reduce (fn [path element] (let [path-a (Path. path) - path-b (-> element tools/->path :attrs :d Path.) - result-path (case operation - :unite (.unite path-a path-b) - :intersect (.intersect path-a path-b) - :subtract (.subtract path-a path-b) - :exclude (.exclude path-a path-b) - :divide (.divide path-a path-b))] - (.getAttribute (.exportSVG result-path) "d"))) + path-b (-> element tools/->path :attrs :d Path.)] + (-> (bool path-a path-b operation) + (.exportSVG) + (.getAttribute "d")))) (:d attrs) (rest selected-elements))] (-> db @@ -411,11 +405,9 @@ (defn paste-in-place ([db] - (reduce paste-in-place (deselect-all db) (:copied-elements db))) + (reduce paste-in-place (deselect db) (:copied-elements db))) ([db el] - (create-element db (if (page? (get-element db (:parent el))) - (-> db active-page :key) - (:parent el)) el))) + (create-element db (assoc el :parent (-> db active-page :key))))) (defn paste [db] @@ -423,15 +415,15 @@ bounds (selected-bounds db) [x1 y1] bounds [width height] (bounds/->dimensions bounds) - [x y] (:adjusted-mouse-pos db)] + [x y] (:adjusted-pointer-pos db)] (translate db [(- x (+ x1 (/ width 2))) (- y (+ y1 (/ height 2)))]))) (defn duplicate-in-place ([db] - (reduce duplicate-in-place (deselect-all db) (selected db))) + (reduce duplicate-in-place (deselect db) (selected db))) ([db el] - (create-element db (:parent el) el))) + (create-element db el))) (defn duplicate [db offset] @@ -441,9 +433,9 @@ (defn animate ([db tag attrs] - (reduce #(animate %1 %2 tag attrs) (deselect-all db) (selected db))) + (reduce #(animate %1 %2 tag attrs) (deselect db) (selected db))) ([db el tag attrs] - (create-element db (:key el) {:tag tag :attrs attrs}))) + (create-element db {:tag tag :attrs attrs :parent (:key el)}))) (defn paste-styles [{copied-elements :copied-elements :as db}] @@ -455,12 +447,7 @@ (fn [elements element] (let [key (:key element) ;; Copy all presentation attributes except transform. - ;; https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/Presentation - style-attrs (-> tools/svg-spec - :attributes - :presentation - (dissoc :transform) - keys)] + style-attrs (disj spec/presentation-attrs :transform)] (assoc-in elements [key] @@ -477,42 +464,38 @@ ([db parent-key] (reduce #(set-parent %1 %2 parent-key) db (selected-keys db))) ([db element-key parent-key] - (if (= element-key parent-key) - db - (-> db - (update-in (conj (path db) (:parent (get-element db element-key)) :children) - #(vec (remove #{element-key} %))) - (update-in (conj (path db) parent-key :children) conj element-key) - (assoc-in (conj (path db) element-key :parent) parent-key))))) + (cond-> db + (not= element-key parent-key) + (-> (update-property (:parent (element db element-key)) + :children + #(vec (remove #{element-key} %))) + (update-property parent-key :children conj element-key) + (set-property element-key :parent parent-key))))) (defn group [db] - (reduce (fn [db key] - (set-parent db key (first (selected-keys db)))) - (-> db - deselect-all - (create-element (:key (active-page db)) {:tag :g})) + (reduce (fn [db key] (set-parent db key (first (selected-keys db)))) + (-> (deselect db) + (create-element {:tag :g :parent (:key (active-page db))})) (selected-keys db))) (defn ungroup - [db] - (reduce (fn [db key] - (let [element (get-element db key)] - (if (= (:tag element) :g) - (delete (reduce (fn [db child-key] - (set-parent db child-key (:parent element))) - db - (:children element)) - key) - db))) - db - (selected-keys db))) + ([db] + (reduce ungroup db (selected db))) + ([db el] + (cond-> db + (and (not (:locked? el)) + (= (:tag el) :g)) + (as-> db db + ;; TODO: Apply group attrs to children. + (reduce #(set-parent %1 %2 (:parent el)) db (:children el)) + (delete db (:key el)))))) (defn manipulate-path - [db action] - (update-selected-by db (fn [elements element] - (if (= (:tag element) :path) - (assoc elements - (:key element) - (path/manipulate element action)) - elements)))) + ([db action] + (reduce #(manipulate-path %1 %2 action) db (selected db))) + ([db el action] + (cond-> db + (and (not (:locked? el)) + (= (:tag el) :path)) + (update-in (path db (:key el)) path/manipulate action)))) diff --git a/src/renderer/element/subs.cljs b/src/renderer/element/subs.cljs index ab40d942..d0e9387d 100644 --- a/src/renderer/element/subs.cljs +++ b/src/renderer/element/subs.cljs @@ -1,13 +1,13 @@ (ns renderer.element.subs (:require ["js-beautify" :as js-beautify] - #_[clojure.core.matrix :as mat] [clojure.set :as set] [goog.color :as color] [re-frame.core :as rf] - [renderer.attribute.utils :as attr-utils] + [renderer.attribute.utils :as attr.utils] [renderer.tools.base :as tools] - [renderer.utils.map :as map])) + [renderer.utils.map :as map] + [renderer.utils.bounds :as bounds])) #_(rf/reg-sub :element/element @@ -115,7 +115,7 @@ :attrs (.indexOf k))) attrs))] - (sort-by (fn [[k _]] (.indexOf attr-utils/attrs-order k)) attrs)))) + (sort-by (fn [[k _]] (.indexOf attr.utils/attrs-order k)) attrs)))) (rf/reg-sub :element/bounds @@ -158,20 +158,20 @@ #{} visible-elements))) -#_(rf/reg-sub - :snaping-points - :<- [:document/elements] - :<- [:element/visible] - (fn [elements visible-elements _] - (reduce (fn [points element] - (let [[x1 y1 x2 y2] (tools/adjusted-bounds element elements) - [width height] (mat/sub [x2 y2] [x1 y1])] - (concat points [[x1 y1] - [x1 y2] - [x1 (+ y1 (/ height 2))] - [x2 y1] - [(+ x1 (/ width 2)) y1] - [x2 y2] - [(+ x1 (/ width 2)) (+ y1 (/ height 2))]]))) - [] - visible-elements))) +(rf/reg-sub + :snapping-points + :<- [:document/elements] + :<- [:element/visible] + (fn [[elements visible-elements] _] + (reduce (fn [points element] + (let [[x1 y1 x2 y2] (tools/adjusted-bounds element elements) + [cx cy] (bounds/center [x1 y1 x2 y2])] + (conj points [x1 y1] + [x1 y2] + [x1 cy] + [x2 y1] + [x2 y2] + [x2 cy] + [cx y1] + [cx y2] + [cx cy]))) [] visible-elements))) diff --git a/src/renderer/events.cljs b/src/renderer/events.cljs index 7c4c0d60..2e570700 100644 --- a/src/renderer/events.cljs +++ b/src/renderer/events.cljs @@ -4,8 +4,8 @@ [malli.core :as ma] [re-frame.core :as rf] [renderer.db :as db] - [renderer.frame.handlers :as frame-handlers] - [renderer.handlers :as handlers] + [renderer.frame.handlers :as frame-h] + [renderer.handlers :as h] [renderer.tools.base :as tools] [renderer.utils.local-storage :as local-storage])) @@ -99,27 +99,27 @@ (update db :snap? not))) (defn significant-movement? - [mouse-pos mouse-offset] + [pointer-pos pointer-offset] (let [threshold 1] - (when (and (vector? mouse-pos) (vector? mouse-offset)) - (> (apply max (map abs (mat/sub mouse-pos mouse-offset))) + (when (and (vector? pointer-pos) (vector? pointer-offset)) + (> (apply max (map abs (mat/sub pointer-pos pointer-offset))) threshold)))) (rf/reg-event-db :pointer-event - (fn [{:keys [mouse-offset tool content-rect] :as db} [_ e]] - (let [{:keys [mouse-pos delta element]} e - mouse-pos (mapv js/parseInt mouse-pos) - adjusted-mouse-pos (frame-handlers/adjusted-mouse-pos db mouse-pos)] + (fn [{:keys [pointer-offset tool content-rect] :as db} [_ e]] + (let [{:keys [pointer-pos delta element]} e + pointer-pos (mapv js/parseInt pointer-pos) + adjusted-pointer-pos (frame-h/adjusted-pointer-pos db pointer-pos)] (case (:type e) :pointermove - (-> (if (and (significant-movement? mouse-pos mouse-offset) + (-> (if (and (significant-movement? pointer-pos pointer-offset) (not= (:buttons e) 2)) (cond-> db (not= tool :pan) - (frame-handlers/pan-out-of-canvas content-rect - mouse-pos - mouse-offset) + (frame-h/pan-out-of-canvas content-rect + pointer-pos + pointer-offset) (not (:drag? db)) (-> (tools/drag-start e element) @@ -128,8 +128,8 @@ :always (tools/drag e element)) (tools/mouse-move db e element)) - (assoc :mouse-pos mouse-pos - :adjusted-mouse-pos adjusted-mouse-pos)) + (assoc :pointer-pos pointer-pos + :adjusted-pointer-pos adjusted-pointer-pos)) :pointerdown (cond-> db @@ -139,8 +139,8 @@ :always (-> (tools/mouse-down e element) - (assoc :mouse-offset mouse-pos - :adjusted-mouse-offset adjusted-mouse-pos))) + (assoc :pointer-offset pointer-pos + :adjusted-pointer-offset adjusted-pointer-pos))) :pointerup (cond-> (if (:drag? db) @@ -151,7 +151,7 @@ (dissoc :primary-tool)) :always - (dissoc :mouse-offset :drag?)) + (dissoc :pointer-offset :drag?)) :dblclick (tools/double-click db e element) @@ -161,19 +161,19 @@ (let [delta-y (second delta) factor (Math/pow (+ 1 (/ (:zoom-sensitivity db) 100)) (- delta-y))] - (frame-handlers/zoom-in-mouse-position db factor)) - (frame-handlers/pan db delta)) + (frame-h/zoom-in-pointer-position db factor)) + (frame-h/pan db delta)) :drop (let [data-transfer (:data-transfer e) items (.-items data-transfer) files (.-files data-transfer)] (-> db - (assoc :mouse-pos mouse-pos - :adjusted-mouse-pos adjusted-mouse-pos) + (assoc :pointer-pos pointer-pos + :adjusted-pointer-pos adjusted-pointer-pos) (cond-> - items (handlers/drop-items items) - files (handlers/drop-files files)))) + items (h/drop-items items) + files (h/drop-files files)))) db)))) diff --git a/src/renderer/frame/events.cljs b/src/renderer/frame/events.cljs index 0a236448..c1d75d48 100644 --- a/src/renderer/frame/events.cljs +++ b/src/renderer/frame/events.cljs @@ -30,7 +30,7 @@ :pan-to-active-page (fn [{:keys [active-document content-rect] :as db} [_ zoom]] (let [active-page (get-in db [:documents active-document :active-page]) - {:keys [width height]} (:attrs (el/get-element db active-page)) + {:keys [width height]} (:attrs (el/element db active-page)) [width height] (map units/unit->px [width height]) width-ratio (/ (:width content-rect) width) height-ratio (/ (:height content-rect) height)] diff --git a/src/renderer/frame/handlers.cljs b/src/renderer/frame/handlers.cljs index a75eb1ff..e1ffcc1b 100644 --- a/src/renderer/frame/handlers.cljs +++ b/src/renderer/frame/handlers.cljs @@ -3,7 +3,7 @@ [clojure.core.matrix :as mat] [goog.math] [renderer.element.handlers :as el] - [renderer.element.utils :as el-utils] + [renderer.element.utils :as el.utils] [renderer.tools.base :as tools] [renderer.utils.bounds :as bounds])) @@ -28,23 +28,23 @@ (assoc-in [:documents active-document :zoom] updated-zoom) (assoc-in [:documents active-document :pan] updated-pan)))) -(defn adjust-mouse-pos - [zoom pan mouse-pos] - (-> mouse-pos +(defn adjust-pointer-pos + [zoom pan pointer-pos] + (-> pointer-pos (mat/div zoom) (mat/add pan))) -(defn adjusted-mouse-pos - [{:keys [active-document] :as db} mouse-pos] +(defn adjusted-pointer-pos + [{:keys [active-document] :as db} pointer-pos] (let [{:keys [zoom pan snap?]} (get-in db [:documents active-document]) - adjusted-mouse-pos (adjust-mouse-pos zoom pan mouse-pos)] + adjusted-pointer-pos (adjust-pointer-pos zoom pan pointer-pos)] (if snap? - (mapv Math/round (adjust-mouse-pos zoom pan mouse-pos)) - adjusted-mouse-pos))) + (mapv Math/round (adjust-pointer-pos zoom pan pointer-pos)) + adjusted-pointer-pos))) -(defn zoom-in-mouse-position - [{:keys [mouse-pos] :as db} factor] - (zoom-in-position db factor (adjusted-mouse-pos db mouse-pos))) +(defn zoom-in-pointer-position + [{:keys [pointer-pos] :as db} factor] + (zoom-in-position db factor (adjusted-pointer-pos db pointer-pos))) (defn zoom [{:keys [active-document content-rect] :as db} factor] @@ -69,9 +69,9 @@ (defn pan-to-element [db key] - (let [element (el/get-element db key) + (let [element (el/element db key) elements (el/elements db) - parrent-page-attrs (:attrs (el-utils/parent-page elements element)) + parrent-page-attrs (:attrs (el.utils/parent-page elements element)) db (pan-to-bounds db (tools/bounds element elements))] (if-not (el/page? element) (pan db [(:x parrent-page-attrs) diff --git a/src/renderer/frame/subs.cljs b/src/renderer/frame/subs.cljs index 5f9bb2f2..f8781562 100644 --- a/src/renderer/frame/subs.cljs +++ b/src/renderer/frame/subs.cljs @@ -2,7 +2,7 @@ (:require [clojure.core.matrix :as mat] [re-frame.core :as rf] - [renderer.frame.handlers :as handlers])) + [renderer.frame.handlers :as h])) (rf/reg-sub :frame/viewbox @@ -15,9 +15,9 @@ [x y width height]))) (rf/reg-sub - :frame/adjusted-mouse-pos + :frame/adjusted-pointer-pos :<- [:document/zoom] :<- [:document/pan] - :<- [:mouse-pos] - (fn [[zoom pan mouse-pos] _] - (handlers/adjust-mouse-pos zoom pan mouse-pos))) + :<- [:pointer-pos] + (fn [[zoom pan pointer-pos] _] + (h/adjust-pointer-pos zoom pan pointer-pos))) diff --git a/src/renderer/handlers.cljs b/src/renderer/handlers.cljs index 96c345ae..68c02f8e 100644 --- a/src/renderer/handlers.cljs +++ b/src/renderer/handlers.cljs @@ -1,7 +1,7 @@ (ns renderer.handlers (:require [re-frame.core :as rf] - [renderer.element.handlers :as element-handlers])) + [renderer.element.handlers :as element-h])) (defn set-state [db state] @@ -15,15 +15,15 @@ (defn drop-files "https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/files" - [{:keys [adjusted-mouse-pos] :as db} files] + [{:keys [adjusted-pointer-pos] :as db} files] (reduce (fn [db file] (case (.-type file) "image/png" - (element-handlers/create db {:type :element + (element-h/create db {:type :element :tag :image - :attrs {:x (first adjusted-mouse-pos) - :y (second adjusted-mouse-pos) + :attrs {:x (first adjusted-pointer-pos) + :y (second adjusted-pointer-pos) :href (.-path file)}}) db)) @@ -32,7 +32,7 @@ (defn drop-items "https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItem" - [{:keys [adjusted-mouse-pos active-document] :as db} items] + [{:keys [adjusted-pointer-pos active-document] :as db} items] (let [fill (get-in db [:documents active-document :fill])] (doall (for [item items] @@ -43,8 +43,8 @@ {:type :element :tag :text :content % - :attrs {:x (first adjusted-mouse-pos) - :y (second adjusted-mouse-pos) + :attrs {:x (first adjusted-pointer-pos) + :y (second adjusted-pointer-pos) :fill fill}}])) nil)))) db) diff --git a/src/renderer/history/core.cljs b/src/renderer/history/core.cljs index 7fa40879..0ebf8290 100644 --- a/src/renderer/history/core.cljs +++ b/src/renderer/history/core.cljs @@ -1,5 +1,4 @@ (ns renderer.history.core (:require [renderer.history.events] - [renderer.history.handlers] [renderer.history.subs])) diff --git a/src/renderer/history/events.cljs b/src/renderer/history/events.cljs index 1593949f..9945b04d 100644 --- a/src/renderer/history/events.cljs +++ b/src/renderer/history/events.cljs @@ -1,43 +1,43 @@ (ns renderer.history.events (:require [re-frame.core :as rf] - [renderer.element.handlers :as elements] - [renderer.history.handlers :as handlers] + [renderer.element.handlers :as element.h] + [renderer.history.handlers :as h] [renderer.tools.base :as tools])) (rf/reg-event-db :history/undo (fn [db _] - (handlers/undo db 1))) + (h/undo db 1))) (rf/reg-event-db :history/redo (fn [db _] - (handlers/redo db 1))) + (h/redo db 1))) (rf/reg-event-db :history/undo-by (fn [db [_ n]] - (handlers/undo db n))) + (h/undo db n))) (rf/reg-event-db :history/redo-by (fn [db [_ n]] - (handlers/redo db n))) + (h/redo db n))) (rf/reg-event-db :history/cancel (fn [db _] (cond-> db - (and (= (:tool db) :select) (not (:mouse-offset db))) - (-> elements/deselect-all - (handlers/finalize "Deselect all")) + (and (= (:tool db) :select) (not (:pointer-offset db))) + (-> element.h/deselect + (h/finalize "Deselect all")) - (not (:mouse-offset db)) + (not (:pointer-offset db)) (-> (tools/set-tool :select) (assoc :state :default)) - (:mouse-offset db) (dissoc :mouse-offset) + (:pointer-offset db) (dissoc :pointer-offset) - :always (-> elements/clear-temp - handlers/swap)))) + :always (-> element.h/clear-temp + h/swap)))) diff --git a/src/renderer/history/handlers.cljs b/src/renderer/history/handlers.cljs index cd13ee83..09084d74 100644 --- a/src/renderer/history/handlers.cljs +++ b/src/renderer/history/handlers.cljs @@ -1,7 +1,7 @@ (ns renderer.history.handlers (:require [clojure.zip :as zip] - [renderer.element.handlers :as elements])) + [renderer.element.handlers :as element.h])) (defn history-path [db] [:documents (:active-document db) :history]) @@ -18,7 +18,7 @@ (defn state [db explanation] - (with-meta (elements/elements db) {:explanation explanation + (with-meta (element.h/elements db) {:explanation explanation :date (.now js/Date) :index (step-count db)})) @@ -41,7 +41,7 @@ (defn swap [db] - (assoc-in db (elements/path db) (zip/node (history db)))) + (assoc-in db (element.h/path db) (zip/node (history db)))) (defn move [db f] @@ -83,15 +83,20 @@ [history] (accumulate history zip/next)) +(print ) + (defn finalize "Pushes changes to the zip-tree. Explicitly adding states, allows canceling actions before adding the result to the history. We also avoid the need of throttling in consecutive actions (move, color pick etc)" - [db explanation] - (let [state (state db explanation) + [db explanation & more] + (let [explanation (apply str explanation more) + state (state db explanation) history (history db)] - (assoc-in db (history-path db) (cond-> history - (redos? history) (zip/replace (conj (conj (zip/rights history) (zip/node history)) state)) - (zip/branch? history) zip/down - (not (redos? history)) (zip/insert-right state) - :always zip/rightmost)))) + (assoc-in db + (history-path db) + (cond-> history + (redos? history) (zip/replace (conj (conj (zip/rights history) (zip/node history)) state)) + (zip/branch? history) zip/down + (not (redos? history)) (zip/insert-right state) + :always zip/rightmost)))) diff --git a/src/renderer/history/subs.cljs b/src/renderer/history/subs.cljs index 733418cf..b9573567 100644 --- a/src/renderer/history/subs.cljs +++ b/src/renderer/history/subs.cljs @@ -1,28 +1,28 @@ (ns renderer.history.subs (:require [re-frame.core :as rf] - [renderer.history.handlers :as handlers])) + [renderer.history.handlers :as h])) (rf/reg-sub :history/undos? :<- [:document/history] - (fn [history _] (handlers/undos? history))) + (fn [history _] (h/undos? history))) (rf/reg-sub :history/redos? :<- [:document/history] - (fn [history _] (handlers/redos? history))) + (fn [history _] (h/redos? history))) (rf/reg-sub :history/undos :<- [:document/history] - (fn [history _] (drop-last (handlers/undos history)))) + (fn [history _] (drop-last (h/undos history)))) (rf/reg-sub :history/redos :<- [:document/history] - (fn [history _] (rest (handlers/redos history)))) + (fn [history _] (rest (h/redos history)))) #_(rf/reg-sub :history/step-count - (fn [db _] (handlers/step-count db))) + (fn [db _] (h/step-count db))) diff --git a/src/renderer/history/views.cljs b/src/renderer/history/views.cljs index 6700c9a8..561b34a2 100644 --- a/src/renderer/history/views.cljs +++ b/src/renderer/history/views.cljs @@ -14,8 +14,9 @@ (defn tree [] - #_[:div {:style {:flex "0 0 300px" - :overflow "auto"}} + #_[:div + {:style {:flex "0 0 300px" + :overflow "auto"}} (loop [step-count @(rf/subscribe [:history/step-count])] [:div.p-1 step-count diff --git a/src/renderer/menubar/core.cljs b/src/renderer/menubar/core.cljs index 7dcafccf..e2fa3e53 100644 --- a/src/renderer/menubar/core.cljs +++ b/src/renderer/menubar/core.cljs @@ -1,3 +1,3 @@ (ns renderer.menubar.core (:require - [renderer.menubar.effects])) + [renderer.menubar.events])) diff --git a/src/renderer/menubar/effects.cljs b/src/renderer/menubar/events.cljs similarity index 88% rename from src/renderer/menubar/effects.cljs rename to src/renderer/menubar/events.cljs index 171442e6..7db18bae 100644 --- a/src/renderer/menubar/effects.cljs +++ b/src/renderer/menubar/events.cljs @@ -1,4 +1,4 @@ -(ns renderer.menubar.effects +(ns renderer.menubar.events (:require [re-frame.core :as rf])) diff --git a/src/renderer/overlay.cljs b/src/renderer/overlay.cljs index 65fa4a0d..700ad04a 100644 --- a/src/renderer/overlay.cljs +++ b/src/renderer/overlay.cljs @@ -1,5 +1,5 @@ (ns renderer.overlay - "Render functions for canvas overlay objects" + "Render functions for canvas overlay objects (select helpers etc)." (:require [clojure.core.matrix :as mat] [goog.math :as math] @@ -22,9 +22,8 @@ (let [zoom @(rf/subscribe [:document/zoom])] [:circle {:cx x :cy y - :stroke stroke - :stroke-width (/ 1 zoom) - :fill "transparent" + :stroke-width 0 + :fill accent :r (/ 3 zoom)} children])) (defn circle-handler @@ -219,9 +218,9 @@ :stroke-dasharray stroke-dasharray})]])) (defn select-box - [adjusted-mouse-pos adjusted-mouse-offset zoom] - (let [[offset-x offset-y] adjusted-mouse-offset - [pos-x pos-y] adjusted-mouse-pos] + [adjusted-pointer-pos adjusted-pointer-offset zoom] + (let [[offset-x offset-y] adjusted-pointer-offset + [pos-x pos-y] adjusted-pointer-pos] {:tag :rect :attrs {:x (min pos-x offset-x) :y (min pos-y offset-y) :width (abs (- pos-x offset-x)) diff --git a/src/renderer/panel/events.cljs b/src/renderer/panel/events.cljs index 151c5397..7fa95036 100644 --- a/src/renderer/panel/events.cljs +++ b/src/renderer/panel/events.cljs @@ -21,32 +21,32 @@ (rf/reg-event-db :panel/clear-drag (fn [db [_]] - (update db :panel-state dissoc :drag :mouse-pos))) + (update db :panel-state dissoc :drag :pointer-pos))) (rf/reg-event-db :panel/on-drag local-storage/persist (fn [db [_ evt]] (when-let [key (-> db :panel-state :drag)] - (let [mouse-pos [(.-clientX evt) (.-clientY evt)] + (let [pointer-pos [(.-clientX evt) (.-clientY evt)] min-width 300 max-width 600 - previous-mouse-pos (-> db :panel-state :mouse-pos) + previous-pointer-pos (-> db :panel-state :pointer-pos) current-size (-> db :panel key :size) - offset (when previous-mouse-pos (mat/sub previous-mouse-pos mouse-pos)) + offset (when previous-pointer-pos (mat/sub previous-pointer-pos pointer-pos)) direction (-> db :panel-state :drag-direction) updated-size ((if (contains? #{:right :bottom} direction) + -) current-size ((if (contains? #{:left :right} direction) first second) offset))] (cond-> db (or (< min-width updated-size max-width) - (not (-> db :panel-state :mouse-pos))) - (assoc-in [:panel-state :mouse-pos] mouse-pos) + (not (-> db :panel-state :pointer-pos))) + (assoc-in [:panel-state :pointer-pos] pointer-pos) (and (not (-> db :panel key :visible?)) (> updated-size current-size)) (assoc-in [:panel key :visible?] true) - (and key previous-mouse-pos) + (and key previous-pointer-pos) (assoc-in [:panel key :size] (cond (< updated-size min-width) min-width (> updated-size max-width) max-width :else updated-size))))))) diff --git a/src/renderer/rulers/views.cljs b/src/renderer/rulers/views.cljs index 84382b65..2418d910 100644 --- a/src/renderer/rulers/views.cljs +++ b/src/renderer/rulers/views.cljs @@ -74,7 +74,7 @@ (defn mouse-pointer [orientation size] - (let [[x y] @(rf/subscribe [:mouse-pos]) + (let [[x y] @(rf/subscribe [:pointer-pos]) pointer-size (/ size 5) size-diff (- size pointer-size)] [:polygon {:points (str/join " " (if (= orientation :vertical) diff --git a/src/renderer/statusbar.cljs b/src/renderer/statusbar.cljs index 756df4e8..4a96eff0 100644 --- a/src/renderer/statusbar.cljs +++ b/src/renderer/statusbar.cljs @@ -5,12 +5,12 @@ [goog.string :as gstring] [re-frame.core :as rf] [re-frame.registrar] - [renderer.color.views :as color] + [renderer.color.views :as color-v] [renderer.components :as comp] [renderer.filters :as filters])) (defn coordinates [] - (let [[x y] @(rf/subscribe [:frame/adjusted-mouse-pos])] + (let [[x y] @(rf/subscribe [:frame/adjusted-pointer-pos])] [:div.flex.flex-col.ml-2.font-mono {:style {:min-width "80px"}} [:div.flex.justify-between @@ -67,8 +67,8 @@ _element-colors @(rf/subscribe [:element/colors]) filter @(rf/subscribe [:document/filter])] [:div.toolbar.footer - [color/picker] - [:div.grow [color/palette]] + [color-v/picker] + [:div.grow [color-v/palette]] #_(when element-colors (map (fn [color] [color-drip (color/hexToRgb color)]) element-colors)) [:> Select/Root {:value (name filter) :onValueChange #(rf/dispatch [:document/set-filter %])} diff --git a/src/renderer/subs.cljs b/src/renderer/subs.cljs index 41126b51..bed1a783 100644 --- a/src/renderer/subs.cljs +++ b/src/renderer/subs.cljs @@ -11,20 +11,24 @@ :-> :primary-tool) (rf/reg-sub - :mouse-pos - :-> :mouse-pos) + :pointer-pos + :-> :pointer-pos) (rf/reg-sub - :adjusted-mouse-pos - :-> :adjusted-mouse-pos) + :adjusted-pointer-pos + :-> :adjusted-pointer-pos) (rf/reg-sub - :adjusted-mouse-offset - :-> :adjusted-mouse-offset) + :pointer-offset + :-> :pointer-offset) (rf/reg-sub - :mouse-offset - :-> :mouse-offset) + :adjusted-pointer-offset + :-> :adjusted-pointer-offset) + +(rf/reg-sub + :pivot-point + :-> :pivot-point) (rf/reg-sub :drag? diff --git a/src/renderer/theme/core.cljs b/src/renderer/theme/core.cljs index 88b66c1e..7d110ddb 100644 --- a/src/renderer/theme/core.cljs +++ b/src/renderer/theme/core.cljs @@ -1,4 +1,4 @@ (ns renderer.theme.core (:require - [renderer.theme.effects] + [renderer.theme.events] [renderer.theme.subs])) diff --git a/src/renderer/theme/effects.cljs b/src/renderer/theme/events.cljs similarity index 91% rename from src/renderer/theme/effects.cljs rename to src/renderer/theme/events.cljs index ed279819..797bcc3f 100644 --- a/src/renderer/theme/effects.cljs +++ b/src/renderer/theme/events.cljs @@ -1,4 +1,4 @@ -(ns renderer.theme.effects +(ns renderer.theme.events (:require [platform] [re-frame.core :as rf] @@ -27,7 +27,7 @@ :theme/cycle-mode (fn [{:keys [db]} [_]] (let [mode (case (-> db :theme :mode) - ;; TODO: system mode + ;; TODO: Support system mode. :dark :light :light :dark)] {:dispatch [:theme/set-mode mode]}))) diff --git a/src/renderer/tools/arc.cljs b/src/renderer/tools/arc.cljs index 7bbae748..07aae88e 100644 --- a/src/renderer/tools/arc.cljs +++ b/src/renderer/tools/arc.cljs @@ -34,10 +34,10 @@ :stroke-dasharray]}) (defmethod tools/drag :arc - [{:keys [adjusted-mouse-offset active-document adjusted-mouse-pos] :as db}] + [{:keys [adjusted-pointer-offset active-document adjusted-pointer-pos] :as db}] (let [{:keys [stroke fill]} (get-in db [:documents active-document]) - [offset-x offset-y] adjusted-mouse-offset - [pos-x pos-y] adjusted-mouse-pos + [offset-x offset-y] adjusted-pointer-offset + [pos-x pos-y] adjusted-pointer-pos attrs {:cx offset-x :cy offset-y ::start-deg 90 diff --git a/src/renderer/tools/base.cljs b/src/renderer/tools/base.cljs index f683b6b2..a0f1ff6e 100644 --- a/src/renderer/tools/base.cljs +++ b/src/renderer/tools/base.cljs @@ -1,13 +1,13 @@ (ns renderer.tools.base (:require - ["@mdn/browser-compat-data" :as bcd] ["paper" :refer [Path]] ["paperjs-offset" :refer [PaperOffset]] [goog.string :as gstring] [reagent.dom.server :as server] [renderer.element.utils :as el-utils] [renderer.utils.bounds :as bounds] - [renderer.utils.map :as map])) + [renderer.utils.map :as map] + [renderer.utils.spec :as spec])) (derive ::transform ::tool) (derive ::draw ::tool) @@ -46,8 +46,8 @@ (defmulti render-edit #(:tag %)) (defmulti bounds #(:tag %)) -(defmulti translate #(when-not (:locked? %) (:tag %))) -(defmulti scale #(when-not (:locked? %) (:tag %))) +(defmulti translate #(:tag %)) +(defmulti scale #(:tag %)) (defmulti edit #(when-not (:locked? %) (:tag %))) (defmulti mouse-down #(:tool %)) @@ -114,12 +114,8 @@ (defn elements-bounds [elements bound-elements] - (reduce - #(bounds/union % (adjusted-bounds %2 elements)) - (adjusted-bounds (first bound-elements) elements) (rest bound-elements))) - -(def svg-spec - (js->clj (.-svg bcd) :keywordize-keys true)) + (when (seq bound-elements) + (apply bounds/union (map #(adjusted-bounds % elements) bound-elements)))) (defn attrs-map [attrs] @@ -138,11 +134,11 @@ (when tag (merge (when (or (isa? tag ::shape) (= tag :svg)) (merge - (attrs-map (tag (:elements svg-spec))) - (attrs-map (-> svg-spec :attributes :core)) - (attrs-map (-> svg-spec :attributes :style)))) + (attrs-map (tag (:elements spec/svg))) + (attrs-map (-> spec/svg :attributes :core)) + (attrs-map (-> spec/svg :attributes :style)))) (when (contains? #{:animateMotion :animateTransform} tag) - (attrs-map (:animate (:elements svg-spec)))) + (attrs-map (:animate (:elements spec/svg)))) (zipmap (:attrs (properties tag)) (repeat "")))) attrs)) diff --git a/src/renderer/tools/blob.cljs b/src/renderer/tools/blob.cljs index 95cfc098..0f059043 100644 --- a/src/renderer/tools/blob.cljs +++ b/src/renderer/tools/blob.cljs @@ -91,15 +91,12 @@ :opacity]}) (defmethod tools/drag-start ::blob - [{:keys [adjusted-mouse-offset + [{:keys [adjusted-pointer-offset active-document - adjusted-mouse-pos] :as db}] + adjusted-pointer-pos] :as db}] (let [{:keys [stroke fill]} (get-in db [:documents active-document]) - [offset-x offset-y] adjusted-mouse-offset - radius (Math/sqrt (apply + (mat/pow - (mat/sub adjusted-mouse-pos - adjusted-mouse-offset) - 2))) + [offset-x offset-y] adjusted-pointer-offset + radius (mat/distance adjusted-pointer-pos adjusted-pointer-offset) attrs {::x (- offset-x radius) ::y (- offset-y radius) ::seed (goog.math/randomInt 1000000) @@ -111,12 +108,9 @@ (elements/set-temp db {:type :element :tag ::blob :attrs attrs}))) (defmethod tools/drag ::blob - [{:keys [adjusted-mouse-offset adjusted-mouse-pos] :as db}] - (let [[offset-x offset-y] adjusted-mouse-offset - radius (Math/sqrt (apply + (mat/pow - (mat/sub adjusted-mouse-pos - adjusted-mouse-offset) - 2))) + [{:keys [adjusted-pointer-offset adjusted-pointer-pos] :as db}] + (let [[offset-x offset-y] adjusted-pointer-offset + radius (mat/distance adjusted-pointer-pos adjusted-pointer-offset) temp (-> (elements/get-temp db) (assoc-in [:attrs ::x] (- offset-x radius)) (assoc-in [:attrs ::y] (- offset-y radius)) diff --git a/src/renderer/tools/box.cljs b/src/renderer/tools/box.cljs index 6ad06c58..c8d0ac55 100644 --- a/src/renderer/tools/box.cljs +++ b/src/renderer/tools/box.cljs @@ -1,6 +1,6 @@ (ns renderer.tools.box "This serves as an abstraction for box elements that share the - :x :y :whidth :height attributes." + :x :y :width :height attributes." (:require [clojure.core.matrix :as mat] [clojure.string :as str] @@ -18,63 +18,42 @@ (hierarchy/update-attr :y + y))) (defmethod tools/scale ::tools/box - [element [x y] handler] - (cond-> element - (contains? #{:bottom-right - :top-right - :middle-right} handler) - (hierarchy/update-attr :width + x) - - (contains? #{:bottom-left - :top-left - :middle-left} handler) - (-> (hierarchy/update-attr :x + x) - (hierarchy/update-attr :width - x)) - - (contains? #{:bottom-middle - :bottom-right - :bottom-left} handler) - (hierarchy/update-attr :height + y) - - (contains? #{:top-middle - :top-left - :top-right} handler) - (-> (hierarchy/update-attr :y + y) - (hierarchy/update-attr :height - y)))) + [el ratio pivot-point] + (let [[x y] ratio + bounds (tools/bounds el) + [x1 y1 _ _] bounds + pivot-diff (mat/sub pivot-point [x1 y1]) + translate-diff (mat/sub pivot-diff (mat/mul pivot-diff ratio))] + (-> el + (hierarchy/update-attr :width * x) + (hierarchy/update-attr :height * y) + (hierarchy/update-attr :stroke-width * y) + (tools/translate translate-diff)))) (defmethod tools/edit ::tools/element [el [x y] handler] (case handler - :position - (-> el - (hierarchy/update-attr :width #(max 0 (- % x))) - (hierarchy/update-attr :height #(max 0 (- % y))) - (tools/translate [x y])) - - :size - (-> el - (hierarchy/update-attr :width #(max 0 (+ % x))) - (hierarchy/update-attr :height #(max 0 (+ % y)))) - - el)) + :position (-> el + (hierarchy/update-attr :width #(max 0 (- % x))) + (hierarchy/update-attr :height #(max 0 (- % y))) + (tools/translate [x y])) + :size (-> el + (hierarchy/update-attr :width #(max 0 (+ % x))) + (hierarchy/update-attr :height #(max 0 (+ % y)))))) (defmethod tools/render-edit ::tools/box [{:keys [attrs key] :as el}] (let [{:keys [x y width height]} attrs [x y width height] (mapv units/unit->px [x y width height]) active-page @(rf/subscribe [:element/active-page]) - page-pos (mapv units/unit->px - [(-> active-page :attrs :x) - (-> active-page :attrs :y)]) - [x y] (if-not (= (:tag el) :page) - (mat/add page-pos [x y]) - [x y])] + page-pos (mapv units/unit->px [(-> active-page :attrs :x) + (-> active-page :attrs :y)]) + [x y] (cond-> [x y] (not= (:tag el) :page) (mat/add page-pos))] [:g {:key :edit-handlers} (map (fn [handler] - [overlay/square-handler (merge handler - {:type :handler - :tag :edit - :element key})]) + [overlay/square-handler (merge handler {:type :handler + :tag :edit + :element key})]) [{:x x :y y :key :position} {:x (+ x width) :y (+ y height) :key :size}])])) @@ -89,7 +68,6 @@ (if (str/blank? stroke) 0 stroke-width-px))] (mapv units/unit->px [x y (+ x width) (+ y height)]))) - (defmethod tools/area ::tools/box [{{:keys [width height]} :attrs}] (apply * (map units/unit->px [width height]))) diff --git a/src/renderer/tools/brush.cljs b/src/renderer/tools/brush.cljs index 638f2dec..90863632 100644 --- a/src/renderer/tools/brush.cljs +++ b/src/renderer/tools/brush.cljs @@ -80,16 +80,16 @@ (defmethod tools/drag :brush [{:keys [active-document - adjusted-mouse-pos] :as db} {:keys [pressure]}] + adjusted-pointer-pos] :as db} {:keys [pressure]}] (let [stroke (get-in db [:documents active-document :stroke])] (if (get-in db [:documents active-document :temp-element :attrs ::points]) (update-in db [:documents active-document :temp-element :attrs ::points] conj - (conj adjusted-mouse-pos pressure)) + (conj adjusted-pointer-pos pressure)) (elements/set-temp db {:type :element :tag :brush - :attrs {::points [(conj adjusted-mouse-pos pressure)] + :attrs {::points [(conj adjusted-pointer-pos pressure)] ::stroke stroke ::size 16 ::thinning 0.5 diff --git a/src/renderer/tools/button.cljs b/src/renderer/tools/button.cljs index 799ccf82..f5afae1e 100644 --- a/src/renderer/tools/button.cljs +++ b/src/renderer/tools/button.cljs @@ -30,9 +30,9 @@ :class]}) (defmethod tools/drag :button - [{:keys [adjusted-mouse-pos tool adjusted-mouse-offset fill stroke]}] - (let [[offset-x offset-y] adjusted-mouse-offset - [pos-x pos-y] adjusted-mouse-pos + [{:keys [adjusted-pointer-pos tool adjusted-pointer-offset fill stroke]}] + (let [[offset-x offset-y] adjusted-pointer-offset + [pos-x pos-y] adjusted-pointer-pos attrs {:x (min pos-x offset-x) :y (min pos-y offset-y) :width (abs (- pos-x offset-x)) diff --git a/src/renderer/tools/canvas.cljs b/src/renderer/tools/canvas.cljs index 9600d2bf..b56b319d 100644 --- a/src/renderer/tools/canvas.cljs +++ b/src/renderer/tools/canvas.cljs @@ -32,8 +32,11 @@ tool @(rf/subscribe [:tool]) primary-tool @(rf/subscribe [:primary-tool]) rotate @(rf/subscribe [:document/rotate]) + snapping-points @(rf/subscribe [:snapping-points]) + debug-info? @(rf/subscribe [:debug-info?]) grid? @(rf/subscribe [:grid?]) mouse-handler #(mouse/event-handler % element) + pivot-point @(rf/subscribe [:pivot-point]) select? (or (= tool :select) (= primary-tool :select))] [:svg {:on-pointer-up mouse-handler @@ -62,6 +65,11 @@ [:filter {:id id :key id} [tag attrs]]) filters/accessibility)] + (when debug-info? + (into [:g] (map (fn [snapping-point] + [overlay/point-of-interest snapping-point]) + snapping-points))) + (when select? (map (fn [el] ^{:key (str (:key el) "-bounds")} @@ -75,7 +83,8 @@ (when (not-empty (filter (comp not zero?) bounds)) [:<> [overlay/size bounds] - [overlay/bounding-handlers bounds]])]) + [overlay/bounding-handlers bounds] + (when pivot-point [overlay/point-of-interest pivot-point])])]) (when (or (= tool :edit) (= primary-tool :edit)) diff --git a/src/renderer/tools/circle.cljs b/src/renderer/tools/circle.cljs index 84189d80..14f44890 100644 --- a/src/renderer/tools/circle.cljs +++ b/src/renderer/tools/circle.cljs @@ -8,7 +8,9 @@ [renderer.element.handlers :as elements] [renderer.overlay :as overlay] [renderer.tools.base :as tools] - [renderer.utils.units :as units])) + [renderer.utils.units :as units] + [renderer.utils.bounds :as bounds] + [renderer.utils.mouse :as mouse])) (derive :circle ::tools/shape) @@ -24,13 +26,10 @@ :stroke-dasharray]}) (defmethod tools/drag :circle - [{:keys [adjusted-mouse-offset active-document adjusted-mouse-pos] :as db}] + [{:keys [adjusted-pointer-offset active-document adjusted-pointer-pos] :as db}] (let [{:keys [stroke fill]} (get-in db [:documents active-document]) - [offset-x offset-y] adjusted-mouse-offset - radius (Math/sqrt (apply + (mat/pow - (mat/sub adjusted-mouse-pos - adjusted-mouse-offset) - 2))) + [offset-x offset-y] adjusted-pointer-offset + radius (mat/distance adjusted-pointer-pos adjusted-pointer-offset) attrs {:cx offset-x :cy offset-y :fill fill @@ -44,23 +43,13 @@ (hierarchy/update-attr :cy + y))) (defmethod tools/scale :circle - [el [x y] handler] - (cond-> el - (contains? #{:middle-right} handler) - (-> (hierarchy/update-attr :cx + (/ x 2)) - (hierarchy/update-attr :r + (/ x 2))) - - (contains? #{:middle-left} handler) - (-> (hierarchy/update-attr :cx + (/ x 2)) - (hierarchy/update-attr :r - (/ x 2))) - - (contains? #{:bottom-middle} handler) - (-> (hierarchy/update-attr :cy + (/ y 2)) - (hierarchy/update-attr :r + (/ y 2))) - - (contains? #{:top-middle} handler) - (-> (hierarchy/update-attr :cy + (/ y 2)) - (hierarchy/update-attr :r - (/ y 2))))) + [element ratio pivot-point] + (let [center (bounds/center (tools/bounds element)) + pivot-diff (mat/sub pivot-point center) + translate-diff (mat/sub pivot-diff (mat/mul pivot-diff ratio))] + (-> element + (hierarchy/update-attr :r * ratio) + (tools/translate translate-diff)))) (defmethod tools/bounds :circle [{{:keys [cx cy r stroke-width stroke]} :attrs}] diff --git a/src/renderer/tools/core.cljs b/src/renderer/tools/core.cljs index 7ba30838..5624d2e9 100644 --- a/src/renderer/tools/core.cljs +++ b/src/renderer/tools/core.cljs @@ -1,5 +1,6 @@ (ns renderer.tools.core (:require + [renderer.tools.select] [renderer.tools.animate-motion] [renderer.tools.animate-transform] [renderer.tools.animate] @@ -27,7 +28,6 @@ [renderer.tools.polyline] [renderer.tools.polyshape] [renderer.tools.rect] - [renderer.tools.select] [renderer.tools.shape] [renderer.tools.svg] [renderer.tools.text] diff --git a/src/renderer/tools/edit.cljs b/src/renderer/tools/edit.cljs index e04ed4e7..6d8e7390 100644 --- a/src/renderer/tools/edit.cljs +++ b/src/renderer/tools/edit.cljs @@ -38,18 +38,18 @@ (handlers/set-state db :edit)) (defmethod tools/drag :edit - [{:keys [adjusted-mouse-offset adjusted-mouse-pos clicked-element] :as db} e] - (let [mouse-offset (mat/sub adjusted-mouse-pos adjusted-mouse-offset) + [{:keys [adjusted-pointer-offset adjusted-pointer-pos clicked-element] :as db} e] + (let [pointer-offset (mat/sub adjusted-pointer-pos adjusted-pointer-offset) db (history/swap db) element-key (:element clicked-element) - mouse-offset (if (contains? (:modifiers e) :ctrl) - (mouse/lock-direction mouse-offset) - mouse-offset)] + pointer-offset (if (contains? (:modifiers e) :ctrl) + (mouse/lock-direction pointer-offset) + pointer-offset)] (if element-key (assoc-in db (conj (elements/path db) element-key) - (tools/edit (elements/get-element db element-key) - mouse-offset + (tools/edit (elements/element db element-key) + pointer-offset (:key clicked-element))) db))) diff --git a/src/renderer/tools/ellipse.cljs b/src/renderer/tools/ellipse.cljs index 907c8177..ec7c6251 100644 --- a/src/renderer/tools/ellipse.cljs +++ b/src/renderer/tools/ellipse.cljs @@ -8,7 +8,8 @@ [renderer.element.handlers :as elements] [renderer.overlay :as overlay] [renderer.tools.base :as tools] - [renderer.utils.units :as units])) + [renderer.utils.units :as units] + [renderer.utils.bounds :as bounds])) (derive :ellipse ::tools/shape) @@ -25,10 +26,10 @@ :stroke-dasharray]}) (defmethod tools/drag :ellipse - [{:keys [adjusted-mouse-offset active-document adjusted-mouse-pos] :as db} e] + [{:keys [adjusted-pointer-offset active-document adjusted-pointer-pos] :as db} e] (let [{:keys [stroke fill]} (get-in db [:documents active-document]) - [offset-x offset-y] adjusted-mouse-offset - [pos-x pos-y] adjusted-mouse-pos + [offset-x offset-y] adjusted-pointer-offset + [pos-x pos-y] adjusted-pointer-pos lock-ratio? (contains? (:modifiers e) :ctrl) rx (abs (- pos-x offset-x)) ry (abs (- pos-y offset-y)) @@ -48,24 +49,15 @@ (hierarchy/update-attr :cy + y))) (defmethod tools/scale :ellipse - [el [x y] handler] - (let [[x y] (mat/div [x y] 2)] - (cond-> el - (contains? #{:bottom-right :top-right :middle-right} handler) - (-> (hierarchy/update-attr :rx + x) - (hierarchy/update-attr :cx + x)) - - (contains? #{:bottom-left :top-left :middle-left} handler) - (-> (hierarchy/update-attr :rx - x) - (hierarchy/update-attr :cx + x)) - - (contains? #{:bottom-middle :bottom-right :bottom-left} handler) - (-> (hierarchy/update-attr :cy + y) - (hierarchy/update-attr :ry + y)) - - (contains? #{:top-middle :top-left :top-right} handler) - (-> (hierarchy/update-attr :ry - y) - (hierarchy/update-attr :cy + y))))) + [element ratio pivot-point] + (let [[x y] ratio + center (bounds/center (tools/bounds element)) + pivot-diff (mat/sub pivot-point center) + translate-diff (mat/sub pivot-diff (mat/mul pivot-diff ratio))] + (-> element + (hierarchy/update-attr :rx * x) + (hierarchy/update-attr :ry * y) + (tools/translate translate-diff)))) (defmethod tools/bounds :ellipse [{{:keys [cx cy rx ry stroke-width stroke]} :attrs}] diff --git a/src/renderer/tools/group.cljs b/src/renderer/tools/group.cljs index 3189a1d5..834d8a89 100644 --- a/src/renderer/tools/group.cljs +++ b/src/renderer/tools/group.cljs @@ -11,7 +11,8 @@ (defmethod tools/properties :g [] {:description "The SVG element is a container used to group other - SVG elements."}) + SVG elements." + :attrs []}) (defmethod tools/translate :g [el [_x _y]] diff --git a/src/renderer/tools/image.cljs b/src/renderer/tools/image.cljs index 49871bb7..51841878 100644 --- a/src/renderer/tools/image.cljs +++ b/src/renderer/tools/image.cljs @@ -15,9 +15,9 @@ :attrs [:href]}) (defmethod tools/drag :image - [{:keys [adjusted-mouse-offset adjusted-mouse-pos] :as db}] - (let [[offset-x offset-y] adjusted-mouse-offset - [pos-x pos-y] adjusted-mouse-pos + [{:keys [adjusted-pointer-offset adjusted-pointer-pos] :as db}] + (let [[offset-x offset-y] adjusted-pointer-offset + [pos-x pos-y] adjusted-pointer-pos attrs {:x (min pos-x offset-x) :y (min pos-y offset-y) :width (abs (- pos-x offset-x)) diff --git a/src/renderer/tools/line.cljs b/src/renderer/tools/line.cljs index 16aa91ef..023a3835 100644 --- a/src/renderer/tools/line.cljs +++ b/src/renderer/tools/line.cljs @@ -24,10 +24,10 @@ :opacity]}) (defn create-line - [{:keys [adjusted-mouse-offset adjusted-mouse-pos active-document] :as db}] + [{:keys [adjusted-pointer-offset adjusted-pointer-pos active-document] :as db}] (let [stroke (get-in db [:documents active-document :stroke]) - [offset-x offset-y] adjusted-mouse-offset - [pos-x pos-y] adjusted-mouse-pos + [offset-x offset-y] adjusted-pointer-offset + [pos-x pos-y] adjusted-pointer-pos attrs {:x1 offset-x :y1 offset-y :x2 pos-x @@ -36,10 +36,10 @@ (elements/set-temp db {:type :element :tag :line :attrs attrs}))) (defn update-line-end - [{:keys [adjusted-mouse-pos] :as db}] + [{:keys [adjusted-pointer-pos] :as db}] (let [temp (-> (elements/get-temp db) - (assoc-in [:attrs :x2] (first adjusted-mouse-pos)) - (assoc-in [:attrs :y2] (second adjusted-mouse-pos)))] + (assoc-in [:attrs :x2] (first adjusted-pointer-pos)) + (assoc-in [:attrs :y2] (second adjusted-pointer-pos)))] (elements/set-temp db temp))) (defmethod tools/mouse-move :line diff --git a/src/renderer/tools/map.cljs b/src/renderer/tools/map.cljs index 2523db65..fd51b813 100644 --- a/src/renderer/tools/map.cljs +++ b/src/renderer/tools/map.cljs @@ -20,9 +20,9 @@ :height]}) (defmethod tools/drag :map - [{:keys [adjusted-mouse-pos tool adjusted-mouse-offset]}] - (let [[offset-x offset-y] adjusted-mouse-offset - [pos-x pos-y] adjusted-mouse-pos + [{:keys [adjusted-pointer-pos tool adjusted-pointer-offset]}] + (let [[offset-x offset-y] adjusted-pointer-offset + [pos-x pos-y] adjusted-pointer-pos attrs {:x (min pos-x offset-x) :y (min pos-y offset-y) :width (abs (- pos-x offset-x)) diff --git a/src/renderer/tools/measure.cljs b/src/renderer/tools/measure.cljs index fa3348f6..286222a7 100644 --- a/src/renderer/tools/measure.cljs +++ b/src/renderer/tools/measure.cljs @@ -32,11 +32,11 @@ (defmethod tools/drag-end :measure [db] db) (defmethod tools/drag :measure - [{:keys [adjusted-mouse-offset adjusted-mouse-pos] :as db}] - (let [[offset-x offset-y] adjusted-mouse-offset - [pos-x pos-y] adjusted-mouse-pos - [adjacent opposite] (mat/sub adjusted-mouse-offset - adjusted-mouse-pos) + [{:keys [adjusted-pointer-offset adjusted-pointer-pos] :as db}] + (let [[offset-x offset-y] adjusted-pointer-offset + [pos-x pos-y] adjusted-pointer-pos + [adjacent opposite] (mat/sub adjusted-pointer-offset + adjusted-pointer-pos) hypotenuse (Math/hypot adjacent opposite) attrs {:x1 offset-x :y1 offset-y diff --git a/src/renderer/tools/page.cljs b/src/renderer/tools/page.cljs index 10328348..4047faca 100644 --- a/src/renderer/tools/page.cljs +++ b/src/renderer/tools/page.cljs @@ -20,9 +20,9 @@ :attrs [:overflow]}) (defmethod tools/drag :page - [{:keys [adjusted-mouse-pos adjusted-mouse-offset] :as db} e] - (let [[offset-x offset-y] adjusted-mouse-offset - [pos-x pos-y] adjusted-mouse-pos + [{:keys [adjusted-pointer-pos adjusted-pointer-offset] :as db} e] + (let [[offset-x offset-y] adjusted-pointer-offset + [pos-x pos-y] adjusted-pointer-pos lock-ratio? (contains? (:modifiers e) :ctrl) width (abs (- pos-x offset-x)) height (abs (- pos-y offset-y)) diff --git a/src/renderer/tools/pan.cljs b/src/renderer/tools/pan.cljs index 02679806..eaee84b9 100644 --- a/src/renderer/tools/pan.cljs +++ b/src/renderer/tools/pan.cljs @@ -24,7 +24,7 @@ (defmethod tools/drag :pan [db e _] - (frame/pan db (mat/sub (:mouse-pos db) (:mouse-pos e)))) + (frame/pan db (mat/sub (:pointer-pos db) (:pointer-pos e)))) (defmethod tools/drag-end :pan [db] diff --git a/src/renderer/tools/pen.cljs b/src/renderer/tools/pen.cljs index 8fcfa5ae..2634a272 100644 --- a/src/renderer/tools/pen.cljs +++ b/src/renderer/tools/pen.cljs @@ -24,15 +24,15 @@ (handlers/set-state db :create)) (defmethod tools/drag :pen - [{:keys [active-document adjusted-mouse-pos] :as db}] + [{:keys [active-document adjusted-pointer-pos] :as db}] (let [stroke (get-in db [:documents active-document :stroke])] (if (get-in db [:documents active-document :temp-element :attrs :points]) (update-in db [:documents active-document :temp-element :attrs :points] - #(str % " " (str/join " " adjusted-mouse-pos))) + #(str % " " (str/join " " adjusted-pointer-pos))) (elements/set-temp db {:type :element :tag :polyline - :attrs {:points (str/join " " adjusted-mouse-pos) + :attrs {:points (str/join " " adjusted-pointer-pos) :stroke stroke :fill "transparent"}})))) diff --git a/src/renderer/tools/polyshape.cljs b/src/renderer/tools/polyshape.cljs index 5a72d4d3..c31ef3a3 100644 --- a/src/renderer/tools/polyshape.cljs +++ b/src/renderer/tools/polyshape.cljs @@ -40,37 +40,37 @@ #(str % " " (str/join " " point)))) (defmethod tools/mouse-up ::tools/polyshape - [{:keys [adjusted-mouse-pos] :as db}] + [{:keys [adjusted-pointer-pos] :as db}] (if (elements/get-temp db) - (add-point db adjusted-mouse-pos) + (add-point db adjusted-pointer-pos) (-> db (handlers/set-state :create) - (create-polyline adjusted-mouse-pos)))) + (create-polyline adjusted-pointer-pos)))) (defmethod tools/drag-end ::tools/polyshape - [{:keys [adjusted-mouse-pos] :as db}] + [{:keys [adjusted-pointer-pos] :as db}] (if (elements/get-temp db) - (add-point db adjusted-mouse-pos) + (add-point db adjusted-pointer-pos) (-> db (handlers/set-state :create) - (create-polyline adjusted-mouse-pos)))) + (create-polyline adjusted-pointer-pos)))) (defmethod tools/mouse-move ::tools/polyshape - [{:keys [active-document adjusted-mouse-pos] :as db}] + [{:keys [active-document adjusted-pointer-pos] :as db}] (if-let [points (get-in db [:documents active-document :temp-element :attrs :points])] - (let [point-vector (attr-utils/points-to-vec points)] + (let [point-vector (attr-utils/points->vec points)] (assoc-in db [:documents active-document :temp-element :attrs :points] (str/join " " (concat (apply concat (if (second point-vector) (drop-last point-vector) point-vector)) - adjusted-mouse-pos)))) db)) + adjusted-pointer-pos)))) db)) (defmethod tools/double-click ::tools/polyshape [{:keys [active-document] :as db}] (-> db (update-in [:documents active-document :temp-element :attrs :points] - #(str/join " " (apply concat (drop-last 2 (attr-utils/points-to-vec %))))) + #(str/join " " (apply concat (drop-last 2 (attr-utils/points->vec %))))) (elements/create) (history/finalize (str "Create " (:tool db))))) @@ -79,11 +79,11 @@ (update-in element [:attrs :points] #(->> % - attr-utils/points-to-vec + attr-utils/points->vec (reduce (fn [points point] (conj points - (units/transform + x (first point)) - (units/transform + y (second point)))) []) + (units/transform (first point) + x) + (units/transform (second point) + y))) []) (str/join " ")))) @@ -107,7 +107,7 @@ :type :handler :tag :edit :element key}])) - (attr-utils/points-to-vec points))])) + (attr-utils/points->vec points))])) (defmethod tools/edit ::tools/polyshape [element [x y] handler] @@ -115,18 +115,18 @@ (update-in element [:attrs :points] #(str/join " " - (-> (attr-utils/points-to-vec %1) + (-> (attr-utils/points->vec %1) (update (int handler) (fn [point] (list - (units/transform + x (first point)) - (units/transform + y (second point))))) + (units/transform (first point) + x) + (units/transform (second point) + y)))) flatten))) element)) (defmethod tools/bounds ::tools/polyshape [{{:keys [points]} :attrs}] - (let [points-v (attr-utils/points-to-vec points) + (let [points-v (attr-utils/points->vec points) x1 (apply min (map #(units/unit->px (first %)) points-v)) y1 (apply min (map #(units/unit->px (second %)) points-v)) x2 (apply max (map #(units/unit->px (first %)) points-v)) @@ -148,19 +148,16 @@ (defmethod tools/area ::tools/polyshape [{{:keys [points]} :attrs}] - (let [points-v (attr-utils/points-to-vec points) - points-v (mapv (fn [point] (mapv units/unit->px point)) points-v)] + (let [points-v (attr-utils/points->px points)] (calc-polygon-area points-v))) (defmethod tools/centroid ::tools/polyshape [{{:keys [points]} :attrs}] - (let [points-v (attr-utils/points-to-vec points) - points-v (mapv (fn [point] (mapv units/unit->px point)) points-v)] + (let [points-v (attr-utils/points->px points)] (-> (reduce mat/add [0 0] points-v) (mat/div (count points-v))))) (defmethod tools/poi ::tools/polyshape [{{:keys [points]} :attrs}] - (let [points-v (attr-utils/points-to-vec points) - points-v (mapv (fn [point] (mapv units/unit->px point)) points-v)] + (let [points-v (attr-utils/points->px points)] (take 2 (polylabel (clj->js [points-v]))))) diff --git a/src/renderer/tools/rect.cljs b/src/renderer/tools/rect.cljs index d1ff3c3e..f4c970e3 100644 --- a/src/renderer/tools/rect.cljs +++ b/src/renderer/tools/rect.cljs @@ -21,10 +21,10 @@ :stroke-linejoin]}) (defmethod tools/drag :rect - [{:keys [adjusted-mouse-offset active-document adjusted-mouse-pos] :as db} e] + [{:keys [adjusted-pointer-offset active-document adjusted-pointer-pos] :as db} e] (let [{:keys [stroke fill]} (get-in db [:documents active-document]) - [offset-x offset-y] adjusted-mouse-offset - [pos-x pos-y] adjusted-mouse-pos + [offset-x offset-y] adjusted-pointer-offset + [pos-x pos-y] adjusted-pointer-pos lock-ratio? (contains? (:modifiers e) :ctrl) width (abs (- pos-x offset-x)) height (abs (- pos-y offset-y)) diff --git a/src/renderer/tools/select.cljs b/src/renderer/tools/select.cljs index c6795c6f..ee551d10 100644 --- a/src/renderer/tools/select.cljs +++ b/src/renderer/tools/select.cljs @@ -60,8 +60,8 @@ "Hold " [:strong "Ctrl"] " to lock proportions, and " - [:strong "Alt"] - " to center the pivot point."]]) + [:strong "Shift"] + " to scale in position."]]) (defn reduce-by-area [{:keys [active-document] :as db} intersecting? f] @@ -112,11 +112,11 @@ (elements/clear-ignored))) (defn select-rect - [{:keys [adjusted-mouse-offset - adjusted-mouse-pos + [{:keys [adjusted-pointer-offset + adjusted-pointer-pos active-document] :as db} intersecting?] (let [zoom (get-in db [:documents active-document :zoom])] - (cond-> (overlay/select-box adjusted-mouse-pos adjusted-mouse-offset zoom) + (cond-> (overlay/select-box adjusted-pointer-pos adjusted-pointer-offset zoom) (not intersecting?) (assoc-in [:attrs :fill] "transparent")))) (defmethod tools/drag-start :select @@ -135,11 +135,36 @@ (history/finalize "Select element")) db) :move))) +(defn offset-scale + [db [x y] lock-ratio? in-place?] + (let [handler (-> db :clicked-element :key) + bounds (elements/selected-bounds db) + dimensions (bounds/->dimensions bounds) + [x1 y1 x2 y2] bounds + [cx cy] (bounds/center bounds) + [offset pivot-point] (case handler + :middle-right [[x 0] [x1 cy]] + :middle-left [[(- x) 0] [x2 cy]] + :top-middle [[0 (- y)] [cx y2]] + :bottom-middle [[0 y] [cx y1]] + :top-right [[x (- y)] [x1 y2]] + :top-left [[(- x) (- y)] [x2 y2]] + :bottom-right [[x y] [x1 y1]] + :bottom-left [[(- x) y] [x2 y1]]) + offset (cond-> offset in-place? (mat/mul 2)) + pivot-point (if in-place? [cx cy] pivot-point) + scale-ratio (mat/div (mat/add dimensions offset) dimensions) + scale-ratio (cond-> scale-ratio lock-ratio? mouse/lock-ratio) + scale-ratio (mapv #(max 0 %) scale-ratio)] + (-> db + (assoc :pivot-point pivot-point) + (elements/scale scale-ratio pivot-point)))) + (defmethod tools/drag :select [{:keys [state - adjusted-mouse-offset - adjusted-mouse-pos] :as db} e] - (let [offset (mat/sub adjusted-mouse-pos adjusted-mouse-offset) + adjusted-pointer-offset + adjusted-pointer-pos] :as db} e] + (let [offset (mat/sub adjusted-pointer-pos adjusted-pointer-offset) offset (if (and (contains? (:modifiers e) :ctrl) (not= state :scale)) (mouse/lock-direction offset) @@ -164,28 +189,26 @@ (handlers/set-state db :move)) :scale - (elements/scale (history/swap db) - offset - (contains? (:modifiers e) :ctrl) - (contains? (:modifiers e) :shift)) + (offset-scale (history/swap db) + offset + (contains? (:modifiers e) :ctrl) + (contains? (:modifiers e) :shift)) :default db) (handlers/set-message (message offset state))))) (defmethod tools/drag-end :select - [{:keys [state adjusted-mouse-offset] :as db} e] + [{:keys [state adjusted-pointer-offset] :as db} e] (-> (case state - :select (-> (if-not (mouse/multiselect? e) - (elements/deselect-all db) - db) + :select (-> (cond-> db (not (mouse/multiselect? e)) elements/deselect) (reduce-by-area (contains? (:modifiers e) :alt) - #(elements/select %1 (:key %2) )) + #(elements/select %1 (:key %2))) (elements/clear-temp) (history/finalize "Modify selection")) - :move (history/finalize db (str "Move selection by " adjusted-mouse-offset)) + :move (history/finalize db (str "Move selection by " adjusted-pointer-offset)) :scale (history/finalize db "Scale selection") :clone (history/finalize db "Clone selection") :default db) (handlers/set-state :default) - (dissoc :clicked-element) + (dissoc :clicked-element :pivot-point) (handlers/set-message (message nil :default)))) diff --git a/src/renderer/tools/svg.cljs b/src/renderer/tools/svg.cljs index fa7c38dd..c051b937 100644 --- a/src/renderer/tools/svg.cljs +++ b/src/renderer/tools/svg.cljs @@ -20,9 +20,9 @@ :overflow]}) (defmethod tools/drag :svg - [{:keys [adjusted-mouse-offset adjusted-mouse-pos] :as db}] - (let [[offset-x offset-y] adjusted-mouse-offset - [pos-x pos-y] adjusted-mouse-pos + [{:keys [adjusted-pointer-offset adjusted-pointer-pos] :as db}] + (let [[offset-x offset-y] adjusted-pointer-offset + [pos-x pos-y] adjusted-pointer-pos attrs {:x (min pos-x offset-x) :y (min pos-y offset-y) :width (abs (- pos-x offset-x)) diff --git a/src/renderer/tools/text.cljs b/src/renderer/tools/text.cljs index 94b844f5..0d812eee 100644 --- a/src/renderer/tools/text.cljs +++ b/src/renderer/tools/text.cljs @@ -36,9 +36,9 @@ [:div "Click to enter your text."]]))) (defmethod tools/mouse-up :text - [{:keys [adjusted-mouse-offset active-document] :as db}] + [{:keys [adjusted-pointer-offset active-document] :as db}] (let [fill (get-in db [:documents active-document :fill]) - [offset-x offset-y] adjusted-mouse-offset + [offset-x offset-y] adjusted-pointer-offset attrs {:x offset-x :y offset-y :fill fill}] @@ -119,7 +119,7 @@ (.textToPath js/window.api (.-path (first (.findFonts js/window.api - ;; TODO: Getting the computed styles might safer + ;; TODO: Getting the computed styles might safer. #js {:family (:font-family attrs) :weight (js/parseInt (:font-weight attrs)) :italic (= (:font-style attrs) diff --git a/src/renderer/tools/zoom.cljs b/src/renderer/tools/zoom.cljs index acadb67c..b8b9914c 100644 --- a/src/renderer/tools/zoom.cljs +++ b/src/renderer/tools/zoom.cljs @@ -38,20 +38,20 @@ (assoc db :cursor "default")) (defmethod tools/drag :zoom - [{:keys [adjusted-mouse-offset adjusted-mouse-pos active-document] :as db}] + [{:keys [adjusted-pointer-offset adjusted-pointer-pos active-document] :as db}] (elements/set-temp db (overlay/select-box - adjusted-mouse-pos - adjusted-mouse-offset + adjusted-pointer-pos + adjusted-pointer-offset (get-in db [:documents active-document :zoom])))) (defmethod tools/drag-end :zoom [{:keys [active-document content-rect - adjusted-mouse-offset - adjusted-mouse-pos + adjusted-pointer-offset + adjusted-pointer-pos zoom-factor] :as db} e] - (let [[offset-x offset-y] adjusted-mouse-offset - [pos-x pos-y] adjusted-mouse-pos + (let [[offset-x offset-y] adjusted-pointer-offset + [pos-x pos-y] adjusted-pointer-pos width (abs (- pos-x offset-x)) height (abs (- pos-y offset-y)) width-ratio (/ (:width content-rect) width) @@ -71,4 +71,4 @@ (let [factor (if (contains? (:modifiers e) :shift) (:zoom-factor db) (/ 1 (:zoom-factor db)))] - (frame/zoom-in-mouse-position db factor))) + (frame/zoom-in-pointer-position db factor))) diff --git a/src/renderer/utils/bounds.cljs b/src/renderer/utils/bounds.cljs index e13efd55..9a072487 100644 --- a/src/renderer/utils/bounds.cljs +++ b/src/renderer/utils/bounds.cljs @@ -3,9 +3,12 @@ [clojure.core.matrix :as mat])) (defn union - "Calculates the wrapping bounds of bounds." - [[ax1 ay1 ax2 ay2] [bx1 by1 bx2 by2]] - [(min ax1 bx1) (min ay1 by1) (max ax2 bx2) (max ay2 by2)]) + "Calculates the union of bounds." + [& bounds] + (concat (apply map min (map #(take 2 %) bounds)) + (apply map max (map #(drop 2 %) bounds)))) + +(apply union [[-100 -100 100 100]] ) (defn ->dimensions "Converts bounds to a [width heigh] vector." diff --git a/src/renderer/utils/mouse.cljs b/src/renderer/utils/mouse.cljs index 84231b1c..f12063c3 100644 --- a/src/renderer/utils/mouse.cljs +++ b/src/renderer/utils/mouse.cljs @@ -13,6 +13,11 @@ [x 0] [0 y])) +(defn lock-ratio + [[x y]] + (let [ratio (if (< (abs x) (abs y)) x y)] + [ratio ratio])) + (defn event-handler "Gathers pointer event props. https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent @@ -32,7 +37,7 @@ (rf/dispatch-sync [:pointer-event {:element el :target (.-target e) :type (keyword (.-type e)) - :mouse-pos [(.-pageX e) (.-pageY e)] + :pointer-pos [(.-pageX e) (.-pageY e)] :pressure (.-pressure e) :pointer-type (.-pointerType e) :primary? (.-isPrimary e) diff --git a/src/renderer/utils/spec.cljs b/src/renderer/utils/spec.cljs new file mode 100644 index 00000000..8eb17edc --- /dev/null +++ b/src/renderer/utils/spec.cljs @@ -0,0 +1,25 @@ +(ns renderer.utils.spec + "Use BCD to get compatibility data for properties and more. + https://github.com/mdn/browser-compat-data" + (:require ["@mdn/browser-compat-data" :as bcd])) + +(def svg + (js->clj (.-svg bcd) :keywordize-keys true)) + +(def presentation-attrs + "https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/Presentation" + (-> svg + :attributes + :presentation + keys + set)) + +(defn compat-data + "Returns conmpatibility data for tags or attributes." + ([tag] + (-> svg :elements tag :__compat)) + ([tag attr] + (or (-> svg :elements tag attr :__compat) + (-> svg :attributes :presentation attr :__compat) + (-> svg :attributes :core attr :__compat) + (-> svg :attributes :style attr :__compat)))) diff --git a/src/renderer/utils/units.cljs b/src/renderer/utils/units.cljs index eacc2255..06144c7f 100644 --- a/src/renderer/utils/units.cljs +++ b/src/renderer/utils/units.cljs @@ -4,18 +4,19 @@ (def ppi 96) -(def units {:px 1 - :ch 8 - :ex 7.15625 - :em 16 - :rem 16 - :in ppi - :cm (/ ppi 2.54) - :mm (/ ppi 25.4) - :pt (/ ppi 72) - :pc (/ ppi 6) - ;; TODO: Find an agnostix way to handle percentages - :% 1}) +(def units + ;; TODO: Find an agnostix way to handle percentages. + {:px 1 + :ch 8 + :ex 7.15625 + :em 16 + :rem 16 + :in ppi + :cm (/ ppi 2.54) + :mm (/ ppi 25.4) + :pt (/ ppi 72) + :pc (/ ppi 6) + :% 1}) (defn unit->key "Converts the string unit to a lower-cased keyword." @@ -64,11 +65,9 @@ (defn transform "Converts a value to pixels, applies a transformation and converts the result back to the original unit." - [transformation-f transformation-v value] + [value f & more] (let [[number unit] (parse-unit value)] - (-> number - (->px unit) - (transformation-f transformation-v) + (-> (apply f (->px number unit) more) (js/parseFloat) (.toFixed 2) (->unit unit) diff --git a/src/renderer/utils/vec.cljs b/src/renderer/utils/vec.cljs index 134d8951..d4dd2ba8 100644 --- a/src/renderer/utils/vec.cljs +++ b/src/renderer/utils/vec.cljs @@ -15,13 +15,13 @@ [el] (subvec coll index)))) -#_(defn move - "Moves element by index." - [coll index-1 index-2] - (let [el (nth coll index-1)] - (if (= index-1 index-2) - coll - (into [] (add (remove-by-index coll index-1) index-2 el))))) +(defn move + "Moves element by index." + [coll index-1 index-2] + (let [el (nth coll index-1)] + (if (= index-1 index-2) + coll + (into [] (add (remove-by-index coll index-1) index-2 el))))) (defn swap "Swaps the position of two elements by index." diff --git a/src/renderer/window/core.cljs b/src/renderer/window/core.cljs index 3f276594..24575963 100644 --- a/src/renderer/window/core.cljs +++ b/src/renderer/window/core.cljs @@ -1,5 +1,4 @@ (ns renderer.window.core (:require - [renderer.window.effects] [renderer.window.events] [renderer.window.subs])) diff --git a/src/renderer/window/effects.cljs b/src/renderer/window/effects.cljs deleted file mode 100644 index 3c874d4a..00000000 --- a/src/renderer/window/effects.cljs +++ /dev/null @@ -1,51 +0,0 @@ -(ns renderer.window.effects - (:require - [platform] - [re-frame.core :as rf])) - -(rf/reg-fx - ::close - (fn [_] - (.close js/window))) - -(rf/reg-fx - ::toggle-fullscreen - (fn [_] - (let [element js/document.documentElement] - (if (.-fullscreenElement element) - (.exitFullscreen element) - (.requestFullscreen element))))) - -(rf/reg-fx - ::open-remote-url - (fn [url] - (.open js/window url))) - -(rf/reg-event-fx - :window/close - (fn [_ _] - {::close nil})) - -(rf/reg-event-fx - :window/toggle-maximized - (fn [_ _] - {:send-to-main {:action "windowToggleMaximized"}})) - -(rf/reg-event-fx - :window/toggle-fullscreen - (fn [_ _] - (if platform/electron? - {:send-to-main {:action "windowToggleFullscreen"}} - {::toggle-fullscreen nil}))) - -(rf/reg-event-fx - :window/minimize - (fn [_ _] - {:send-to-main {:action "windowMinimize"}})) - -(rf/reg-event-fx - :window/open-remote-url - (fn [_ [_ url]] - (if platform/electron? - {:send-to-main {:action "openRemoteUrl" :data url}} - {::open-remote-url url}))) diff --git a/src/renderer/window/events.cljs b/src/renderer/window/events.cljs index f8e4276b..87c75d57 100644 --- a/src/renderer/window/events.cljs +++ b/src/renderer/window/events.cljs @@ -1,5 +1,6 @@ (ns renderer.window.events (:require + [platform] [re-frame.core :as rf])) (rf/reg-event-db @@ -25,3 +26,50 @@ (rf/path :window) (fn [db [_]] (update db :header? not))) + +(rf/reg-fx + ::close + (fn [_] + (.close js/window))) + +(rf/reg-fx + ::toggle-fullscreen + (fn [_] + (let [element js/document.documentElement] + (if (.-fullscreenElement element) + (.exitFullscreen element) + (.requestFullscreen element))))) + +(rf/reg-fx + ::open-remote-url + (fn [url] + (.open js/window url))) + +(rf/reg-event-fx + :window/close + (fn [_ _] + {::close nil})) + +(rf/reg-event-fx + :window/toggle-maximized + (fn [_ _] + {:send-to-main {:action "windowToggleMaximized"}})) + +(rf/reg-event-fx + :window/toggle-fullscreen + (fn [_ _] + (if platform/electron? + {:send-to-main {:action "windowToggleFullscreen"}} + {::toggle-fullscreen nil}))) + +(rf/reg-event-fx + :window/minimize + (fn [_ _] + {:send-to-main {:action "windowMinimize"}})) + +(rf/reg-event-fx + :window/open-remote-url + (fn [_ [_ url]] + (if platform/electron? + {:send-to-main {:action "openRemoteUrl" :data url}} + {::open-remote-url url}))) diff --git a/src/user.cljs b/src/user.cljs index ef5a09a3..70d62c2b 100644 --- a/src/user.cljs +++ b/src/user.cljs @@ -14,6 +14,13 @@ ([x y] (move [x y]))) +(defn ^:export scale + "Scales the selected elements." + ([ratio] + (rf/dispatch [:element/scale (if (number? ratio) [ratio ratio] ratio)])) + ([x y] + (rf/dispatch [:element/scale [x y]]))) + (defn ^:export fill "Fills the selected elements." [color]