diff --git a/.clj-kondo/config.edn b/.clj-kondo/config.edn index b8fa2ab1..7563b3c5 100644 --- a/.clj-kondo/config.edn +++ b/.clj-kondo/config.edn @@ -19,7 +19,7 @@ :unused-alias {:level :warning} :redundant-call {:level :warning} :redundant-str-call {:level :warning} - :line-length {:max-line-length 100 + :line-length {:max-line-length 90 :exclude-urls true} :clojure-lsp/unused-public-var {:exclude-regex #{"pages.*"}} :consistent-alias {:level :warning diff --git a/src/electron/main.cljs b/src/electron/main.cljs index 511a0db8..5bf9f9d3 100644 --- a/src/electron/main.cljs +++ b/src/electron/main.cljs @@ -162,7 +162,9 @@ :frame false})) (.once ^js @loading-window "show" init-main-window!) (.loadURL ^js @loading-window (resource-path "/public/loading.html")) - (.once ^js (.-webContents @loading-window) "did-finish-load" #(.show ^js @loading-window))) + (.once ^js (.-webContents @loading-window) + "did-finish-load" + #(.show ^js @loading-window))) (defn ^:export init! [] (.initialize log) diff --git a/src/renderer/app/db.cljs b/src/renderer/app/db.cljs index a8d407dc..89524639 100644 --- a/src/renderer/app/db.cljs +++ b/src/renderer/app/db.cljs @@ -36,7 +36,7 @@ (def App [:map {:closed true} [:tool {:default :transform} Tool] - [:primary-tool {:optional true} Tool] + [:cached-tool {:optional true} Tool] [:pointer-pos {:default [0 0]} Vec2] [:pointer-offset {:optional true} Vec2] [:adjusted-pointer-pos {:default [0 0]} Vec2] @@ -49,6 +49,7 @@ [:event-timestamp {:optional true} number?] [:double-click-delta {:default 250} [:and number? pos?]] [:state {:default :idle} State] + [:cached-state {:optional true} State] [:grid {:default false :persist true} boolean?] [:ruler {:default {} :persist true} Ruler] [:snap {:default {} :persist true} Snap] diff --git a/src/renderer/app/effects.cljs b/src/renderer/app/effects.cljs index 729998b4..74317e29 100644 --- a/src/renderer/app/effects.cljs +++ b/src/renderer/app/effects.cljs @@ -45,7 +45,9 @@ (fn [{:keys [on-success on-error formatter]}] (when-not (undefined? js/window.queryLocalFonts) (-> (.queryLocalFonts js/window) - (.then #(when on-success (rf/dispatch (conj on-success (cond-> % formatter formatter))))) + (.then #(when on-success (rf/dispatch (conj on-success + (cond-> % + formatter formatter))))) (.catch #(when on-error (rf/dispatch (conj on-error %)))))))) (rf/reg-fx diff --git a/src/renderer/app/views.cljs b/src/renderer/app/views.cljs index dc5fcd6a..1722e9f0 100644 --- a/src/renderer/app/views.cljs +++ b/src/renderer/app/views.cljs @@ -44,25 +44,39 @@ (interpose ", " (map (fn [[k v]] ^{:key k} [:span (str (name k) ": " (if (number? v) - (.toFixed v 2) + (.toFixed v 3) (coll->str v)))]) m))) (defn debug-rows [] - [["Dom rect" (map->str @(rf/subscribe [::app.subs/dom-rect]))] - ["Viewbox" (coll->str @(rf/subscribe [::frame.subs/viewbox]))] - ["Pointer position" (coll->str @(rf/subscribe [::app.subs/pointer-pos]))] - ["Adjusted pointer position" (coll->str @(rf/subscribe [::app.subs/adjusted-pointer-pos]))] - ["Pointer offset" (coll->str @(rf/subscribe [::app.subs/pointer-offset]))] - ["Adjusted pointer offset" (coll->str @(rf/subscribe [::app.subs/adjusted-pointer-offset]))] - ["Pointer drag?" (str @(rf/subscribe [::tool.subs/drag?]))] - ["Pan" (coll->str @(rf/subscribe [::document.subs/pan]))] - ["Active tool" @(rf/subscribe [::tool.subs/active])] - ["Primary tool" @(rf/subscribe [::tool.subs/primary])] - ["State" @(rf/subscribe [::tool.subs/state])] - ["Clicked element" (:id @(rf/subscribe [::app.subs/clicked-element]))] - ["Ignored elements" @(rf/subscribe [::document.subs/ignored-ids])] - ["Snap" (map->str @(rf/subscribe [::snap.subs/nearest-neighbor]))]]) + (let [dom-rect (rf/subscribe [::app.subs/dom-rect]) + viewbox (rf/subscribe [::frame.subs/viewbox]) + pointer-pos (rf/subscribe [::app.subs/pointer-pos]) + adjusted-pointer-pos (rf/subscribe [::app.subs/adjusted-pointer-pos]) + pointer-offset (rf/subscribe [::app.subs/pointer-offset]) + adjusted-pointer-offset (rf/subscribe [::app.subs/adjusted-pointer-offset]) + drag? (rf/subscribe [::tool.subs/drag?]) + pan (rf/subscribe [::document.subs/pan]) + active-tool (rf/subscribe [::tool.subs/active]) + cached-tool (rf/subscribe [::tool.subs/cached]) + tool-state (rf/subscribe [::tool.subs/state]) + clicked-element (rf/subscribe [::app.subs/clicked-element]) + ignored-ids (rf/subscribe [::document.subs/ignored-ids]) + nearest-neighbor (rf/subscribe [::snap.subs/nearest-neighbor])] + [["Dom rect" (map->str @dom-rect)] + ["Viewbox" (coll->str @viewbox)] + ["Pointer position" (coll->str @pointer-pos)] + ["Adjusted pointer position" (coll->str @adjusted-pointer-pos)] + ["Pointer offset" (coll->str @pointer-offset)] + ["Adjusted pointer offset" (coll->str @adjusted-pointer-offset)] + ["Pointer drag?" (str @drag?)] + ["Pan" (coll->str @pan)] + ["Active tool" @active-tool] + ["Cached tool" @cached-tool] + ["State" @tool-state] + ["Clicked element" (:id @clicked-element)] + ["Ignored elements" @ignored-ids] + ["Snap" (map->str @nearest-neighbor)]])) (defn debug-info [] @@ -112,8 +126,9 @@ (when (and help-bar (seq help-message)) [:div.flex.absolute.justify-center.w-full.p-4.pointer-events-none [:div.bg-primary.rounded-full.overflow-hidden.shadow-lg - [:div.overlay.text-color.text-xs.gap-1.flex.flex-wrap.truncate.py-2.px-4.justify-center - {:aria-live "polite"} + [:div.overlay.text-color.text-xs.gap-1.flex.flex-wrap.truncate.py-2 + {:class "px-4 justify-center" + :aria-live "polite"} help-message]]])]]])) (defn center-top-group diff --git a/src/renderer/attribute/impl/core.cljs b/src/renderer/attribute/impl/core.cljs index 0339427c..aff93bff 100644 --- a/src/renderer/attribute/impl/core.cljs +++ b/src/renderer/attribute/impl/core.cljs @@ -194,8 +194,8 @@ (defmethod attribute.hierarchy/description [:default :values] [] - "The values attribute has different meanings, depending upon the context where it's used, - either it defines a sequence of values used over the course of an animation, + "The values attribute has different meanings, depending upon the context where it's + used, either it defines a sequence of values used over the course of an animation, or it's a list of numbers for a color matrix, which is interpreted differently depending on the type of color change to be performed.") diff --git a/src/renderer/attribute/impl/d.cljs b/src/renderer/attribute/impl/d.cljs index 466a179e..b1d16827 100644 --- a/src/renderer/attribute/impl/d.cljs +++ b/src/renderer/attribute/impl/d.cljs @@ -140,7 +140,8 @@ [:> Popover/Root {:modal true} [:> Popover/Trigger {:title "Edit path" - :class "form-control-button"} + :class "form-control-button" + :disabled disabled} [views/icon "pencil"]] [:> Popover/Portal [:> Popover/Content {:sideOffset 5 diff --git a/src/renderer/attribute/impl/font_weight.cljs b/src/renderer/attribute/impl/font_weight.cljs index 8bb68896..89c3f202 100644 --- a/src/renderer/attribute/impl/font_weight.cljs +++ b/src/renderer/attribute/impl/font_weight.cljs @@ -12,6 +12,11 @@ "The font-weight attribute refers to the boldness or lightness of the glyphs used to render the text, relative to other fonts in the same font family.") +(defn label + [weight] + (let [weight-name (first (get utils.attribute/weight-name-mapping weight))] + (str weight " - " weight-name))) + (defmethod attribute.hierarchy/form-element [:default :font-weight] [_ k v attrs] (let [available-weights @(rf/subscribe [::element.subs/font-weights]) @@ -22,7 +27,5 @@ (merge attrs {:default-value "400" :items (mapv #(do {:key % - :label (str % " - " (-> % - utils.attribute/weight-name-mapping - first)) + :label (label %) :value %}) weights)})])) diff --git a/src/renderer/attribute/impl/href.cljs b/src/renderer/attribute/impl/href.cljs index 5638d449..9bb17e29 100644 --- a/src/renderer/attribute/impl/href.cljs +++ b/src/renderer/attribute/impl/href.cljs @@ -45,7 +45,6 @@ ::success (fn [{:keys [db]} [_ file]] {:db (tool.handlers/activate db :transform) - ::effects/file-read-as [file - :data-url - {"load" {:on-fire [::element.events/set-attr :href]} - "error" {:on-fire [::notification.events/show-exception]}}]})) + ::effects/file-read-as + [file :data-url {"load" {:on-fire [::element.events/set-attr :href]} + "error" {:on-fire [::notification.events/show-exception]}}]})) diff --git a/src/renderer/attribute/impl/points.cljs b/src/renderer/attribute/impl/points.cljs index 37a3a5e8..eb518e57 100644 --- a/src/renderer/attribute/impl/points.cljs +++ b/src/renderer/attribute/impl/points.cljs @@ -38,11 +38,12 @@ [views/icon "times"]]]) (defn points-popover - [points] + [points disabled] [:> Popover/Root {:modal true} [:> Popover/Trigger {:title "Edit points" - :class "form-control-button"} + :class "form-control-button" + :disabled disabled} [views/icon "pencil"]] [:> Popover/Portal [:> Popover/Content @@ -64,4 +65,4 @@ [:div.flex.gap-px.w-full [attribute.views/form-input k (if state-idle v "waiting") {:disabled (or disabled (not v) (not state-idle))}] - (when v [points-popover (utils.utils.attribute/points->vec v)])])) + (when v [points-popover (utils.utils.attribute/points->vec v) disabled])])) diff --git a/src/renderer/attribute/views.cljs b/src/renderer/attribute/views.cljs index 21859674..dc1faa64 100644 --- a/src/renderer/attribute/views.cljs +++ b/src/renderer/attribute/views.cljs @@ -13,6 +13,7 @@ [renderer.event.impl.keyboard :as event.impl.keyboard] [renderer.events :as-alias events] [renderer.tool.hierarchy :as tool.hierarchy] + [renderer.tool.subs :as-alias tool.subs] [renderer.utils.attribute :as utils.attribute] [renderer.views :as views])) @@ -95,8 +96,8 @@ % v on-change-handler! k v)})] (when-not (or (empty? (str v)) disabled) - [:button.button.bg-primary.text-muted.absolute.h-full.right-0.clear-input-button.invisible.p-1 - {:class "hover:bg-transparent" + [:button.button.bg-primary.text-muted.absolute.h-full.right-0.p-1.invisible + {:class "clear-input-button hover:bg-transparent" :on-pointer-down #(rf/dispatch [::element.events/remove-attr k])} [views/icon "times"]])]) @@ -242,7 +243,12 @@ (let [selected-elements @(rf/subscribe [::element.subs/selected]) selected-tags @(rf/subscribe [::element.subs/selected-tags]) selected-attrs @(rf/subscribe [::element.subs/selected-attrs]) - locked? @(rf/subscribe [::element.subs/selected-locked?]) + selected-locked? @(rf/subscribe [::element.subs/selected-locked?]) + tool-state @(rf/subscribe [::tool.subs/state]) + tool-cached-state @(rf/subscribe [::tool.subs/cached-state]) + locked? (or selected-locked? + (not= tool-state :idle) + (and tool-cached-state (not= tool-cached-state :idle))) tag (first selected-tags) multitag? (next selected-tags)] (when-first [el selected-elements] diff --git a/src/renderer/dialog/views.cljs b/src/renderer/dialog/views.cljs index e83d3149..daf1b60b 100644 --- a/src/renderer/dialog/views.cljs +++ b/src/renderer/dialog/views.cljs @@ -47,14 +47,16 @@ " will be lost if you close the document without saving."] [:div.flex.gap-2.flex-wrap [:button.button.px-2.bg-primary.rounded.flex-1 - {:on-click #(rf/dispatch [::dialog.events/close [::document.events/close id false]])} + {:on-click #(rf/dispatch [::dialog.events/close + [::document.events/close id false]])} "Don't save"] [:button.button.px-2.bg-primary.rounded.flex-1 {:on-click #(rf/dispatch [::dialog.events/close])} "Cancel"] [:button.button.px-2.rounded.flex-1.accent {:auto-focus true - :on-click #(rf/dispatch [::dialog.events/close [::document.events/save-and-close id]])} + :on-click #(rf/dispatch [::dialog.events/close + [::document.events/save-and-close id]])} "Save"]]]) (defn cmdk-item diff --git a/src/renderer/document/db.cljs b/src/renderer/document/db.cljs index 5158d249..c8b84726 100644 --- a/src/renderer/document/db.cljs +++ b/src/renderer/document/db.cljs @@ -23,7 +23,6 @@ [:zoom {:default 1} ZoomFactor] [:rotate {:default 0} number?] [:history {:optional true} History] - [:temp-element {:optional true} Element] [:pan {:default [0 0]} Vec2] [:elements {:default {} :persist true} [:map-of uuid? Element]] [:focused {:optional true} boolean?] diff --git a/src/renderer/document/subs.cljs b/src/renderer/document/subs.cljs index e3433a18..3c5197ee 100644 --- a/src/renderer/document/subs.cljs +++ b/src/renderer/document/subs.cljs @@ -103,11 +103,6 @@ :<- [::active] :-> :elements) -(rf/reg-sub - ::temp-element - :<- [::active] - :-> :temp-element) - (rf/reg-sub ::filter :<- [::active] diff --git a/src/renderer/effects.cljs b/src/renderer/effects.cljs index f757baa4..13c00c1c 100644 --- a/src/renderer/effects.cljs +++ b/src/renderer/effects.cljs @@ -150,7 +150,8 @@ (.addEventListener target channel #(rf/dispatch (conj listener - (cond-> % formatter formatter)))))) + (cond-> % + formatter formatter)))))) (rf/reg-fx ::ipc-send @@ -163,7 +164,8 @@ (fn [{:keys [channel data formatter on-success on-error]}] (when js/window.api (-> (js/window.api.invoke channel (clj->js data)) - (.then #(when on-success (rf/dispatch (conj on-success (cond-> % formatter formatter))))) + (.then #(when on-success (rf/dispatch (conj on-success (cond-> % + formatter formatter))))) (.catch #(when on-error (rf/dispatch (conj on-error %)))))))) (rf/reg-fx diff --git a/src/renderer/element/impl/box.cljs b/src/renderer/element/impl/box.cljs index 695cf034..9c587ae7 100644 --- a/src/renderer/element/impl/box.cljs +++ b/src/renderer/element/impl/box.cljs @@ -23,15 +23,17 @@ (defmethod element.hierarchy/edit ::element.hierarchy/box [el [x y] handle] - (case handle - :position - (-> (utils.element/update-attrs-with el (comp (partial max 0) -) [[:width x] [:height y]]) - (element.hierarchy/translate [x y])) + (let [clamp (partial max 0)] + (case handle + :position + (-> el + (utils.element/update-attrs-with (comp clamp -) [[:width x] [:height y]]) + (element.hierarchy/translate [x y])) - :size - (utils.element/update-attrs-with el (comp (partial max 0) +) [[:width x] [:height y]]) + :size + (utils.element/update-attrs-with el (comp clamp +) [[:width x] [:height y]]) - el)) + el))) (defmethod element.hierarchy/render-edit ::element.hierarchy/box [el] diff --git a/src/renderer/element/impl/container/canvas.cljs b/src/renderer/element/impl/container/canvas.cljs index a94fa947..2bb93acc 100644 --- a/src/renderer/element/impl/container/canvas.cljs +++ b/src/renderer/element/impl/container/canvas.cljs @@ -30,18 +30,18 @@ (let [child-elements @(rf/subscribe [::element.subs/filter-visible (:children el)]) viewbox-attr @(rf/subscribe [::frame.subs/viewbox-attr]) {:keys [width height]} @(rf/subscribe [::app.subs/dom-rect]) - temp-element @(rf/subscribe [::document.subs/temp-element]) read-only? @(rf/subscribe [::document.subs/read-only?]) cursor @(rf/subscribe [::tool.subs/cursor]) active-tool @(rf/subscribe [::tool.subs/active]) - primary-tool @(rf/subscribe [::tool.subs/primary]) + cached-tool @(rf/subscribe [::tool.subs/cached]) rotate @(rf/subscribe [::document.subs/rotate]) grid @(rf/subscribe [::app.subs/grid]) pointer-handler #(event.pointer/handler! % el) snap? @(rf/subscribe [::snap.subs/active?]) nearest-neighbor @(rf/subscribe [::snap.subs/nearest-neighbor]) snapped-el-id (-> nearest-neighbor meta :id) - snapped-el (when snapped-el-id @(rf/subscribe [::element.subs/entity snapped-el-id]))] + snapped-el (when snapped-el-id + @(rf/subscribe [::element.subs/entity snapped-el-id]))] [:svg#canvas {:on-pointer-up pointer-handler :on-pointer-down pointer-handler :on-pointer-move pointer-handler @@ -66,8 +66,6 @@ (when grid [ruler.views/grid]) - (when-not read-only? [element.hierarchy/render temp-element]) - (when snap? [:<> (when snapped-el @@ -75,7 +73,7 @@ (when nearest-neighbor [snap.views/canvas-label nearest-neighbor])]) - (when-not read-only? [tool.hierarchy/render (or primary-tool active-tool)])])) + (when-not read-only? [tool.hierarchy/render (or cached-tool active-tool)])])) (defmethod element.hierarchy/render-to-string :canvas [el] diff --git a/src/renderer/element/impl/custom/brush.cljs b/src/renderer/element/impl/custom/brush.cljs index 7736b252..db25e301 100644 --- a/src/renderer/element/impl/custom/brush.cljs +++ b/src/renderer/element/impl/custom/brush.cljs @@ -165,7 +165,8 @@ #(->> (into [] partition-to-px (utils.attribute/str->seq %)) (reduce (fn [points point] (let [rel-point (matrix/sub bbox-min (take 2 point)) - rel-offset (utils.element/scale-offset ratio rel-point) + rel-offset (utils.element/scale-offset ratio + rel-point) offset (matrix/add offset rel-offset)] (translate offset points point))) []) (string/join " "))))) diff --git a/src/renderer/element/impl/custom/core.cljs b/src/renderer/element/impl/custom/core.cljs index 1259b9e7..11a55450 100644 --- a/src/renderer/element/impl/custom/core.cljs +++ b/src/renderer/element/impl/custom/core.cljs @@ -1,5 +1,4 @@ (ns renderer.element.impl.custom.core (:require [renderer.element.impl.custom.blob] - [renderer.element.impl.custom.brush] - [renderer.element.impl.custom.measure])) + [renderer.element.impl.custom.brush])) diff --git a/src/renderer/element/impl/custom/measure.cljs b/src/renderer/element/impl/custom/measure.cljs deleted file mode 100644 index aa701988..00000000 --- a/src/renderer/element/impl/custom/measure.cljs +++ /dev/null @@ -1,37 +0,0 @@ -(ns renderer.element.impl.custom.measure - (:require - [re-frame.core :as rf] - [renderer.document.subs :as-alias document.subs] - [renderer.element.hierarchy :as element.hierarchy] - [renderer.utils.length :as utils.length] - [renderer.utils.math :as utils.math] - [renderer.utils.svg :as utils.svg])) - -(derive :measure ::element.hierarchy/element) - -(defmethod element.hierarchy/render :measure - [el] - (let [{:keys [attrs id]} el - {:keys [x1 x2 y1 y2 hypotenuse]} attrs - [x1 y1 x2 y2] (map utils.length/unit->px [x1 y1 x2 y2]) - angle (utils.math/angle [x1 y1] [x2 y2]) - zoom @(rf/subscribe [::document.subs/zoom]) - straight? (< angle 180) - straight-angle (if straight? angle (- angle 360))] - [:g {:key id} - [utils.svg/cross [x1 y1]] - [utils.svg/cross [x2 y2]] - - [utils.svg/arc [x1 y1] 20 (if straight? 0 angle) (abs straight-angle)] - - [utils.svg/line [x1 y1] [x2 y2] false] - [utils.svg/line [x1 y1] [(+ x1 (/ 30 zoom)) y1]] - - [utils.svg/label - (str (.toFixed straight-angle 2) "°") - [(+ x1 (/ 40 zoom)) y1] - "start"] - - [utils.svg/label - (-> hypotenuse js/parseFloat (.toFixed 2) str) - [(/ (+ x1 x2) 2) (/ (+ y1 y2) 2)]]])) diff --git a/src/renderer/element/impl/shape/circle.cljs b/src/renderer/element/impl/shape/circle.cljs index c2958bd5..46917336 100644 --- a/src/renderer/element/impl/shape/circle.cljs +++ b/src/renderer/element/impl/shape/circle.cljs @@ -77,7 +77,7 @@ r (/ (first (utils.bounds/->dimensions bbox)) 2)] [:g [utils.svg/line [cx cy] [(+ cx r) cy]] - [utils.svg/label (str (.toFixed r 2)) [(+ cx (/ r 2)) cy]] + [utils.svg/label (str (.toFixed r 3)) [(+ cx (/ r 2)) cy]] [utils.svg/times [cx cy]] [tool.views/square-handle {:x (+ cx r) :y cy diff --git a/src/renderer/element/impl/shape/ellipse.cljs b/src/renderer/element/impl/shape/ellipse.cljs index 1b3586f4..9c272b30 100644 --- a/src/renderer/element/impl/shape/ellipse.cljs +++ b/src/renderer/element/impl/shape/ellipse.cljs @@ -84,9 +84,9 @@ [:g ::edit-handles [utils.svg/times [cx cy]] [utils.svg/line [cx cy] [(+ cx rx) cy]] - [utils.svg/label (str (.toFixed rx 2)) [(+ cx (/ rx 2)) cy]] + [utils.svg/label (str (.toFixed rx 3)) [(+ cx (/ rx 2)) cy]] [utils.svg/line [cx cy] [cx (- cy ry)]] - [utils.svg/label (str (.toFixed ry 2)) [cx (- cy (/ ry 2))]] + [utils.svg/label (str (.toFixed ry 3)) [cx (- cy (/ ry 2))]] (map (fn [handle] ^{:key (:id handle)} [tool.views/square-handle diff --git a/src/renderer/element/impl/shape/line.cljs b/src/renderer/element/impl/shape/line.cljs index f2686b26..64c73026 100644 --- a/src/renderer/element/impl/shape/line.cljs +++ b/src/renderer/element/impl/shape/line.cljs @@ -72,7 +72,7 @@ [:title {:key (str id "-title")} (name id)]] (when is-active [utils.svg/label - (string/join " " [(.toFixed x 2) (.toFixed y 2)]) + (string/join " " [(.toFixed x 3) (.toFixed y 3)]) [(- x margin) (+ y margin)] "end"])])) [{:x x1 diff --git a/src/renderer/element/impl/shape/path.cljs b/src/renderer/element/impl/shape/path.cljs index 5e487cab..45f03b54 100644 --- a/src/renderer/element/impl/shape/path.cljs +++ b/src/renderer/element/impl/shape/path.cljs @@ -22,6 +22,7 @@ :fill :stroke :stroke-linejoin + :stroke-linecap :opacity]}) (defmethod element.hierarchy/translate :path @@ -62,13 +63,13 @@ (map-indexed (fn [i segment] (case (-> segment first string/lower-case) "m" - (let [[x y] (mapv utils.length/unit->px [(second segment) (last segment)]) - [x y] (matrix/add offset [x y])] + (let [point (mapv utils.length/unit->px (rest segment)) + [x y] (matrix/add offset point)] (square-handle i [x y])) "l" - (let [[x y] (mapv utils.length/unit->px [(second segment) (last segment)]) - [x y] (matrix/add offset [x y])] + (let [point (mapv utils.length/unit->px (rest segment)) + [x y] (matrix/add offset point)] (square-handle i [x y])) nil)) diff --git a/src/renderer/element/impl/shape/polyshape.cljs b/src/renderer/element/impl/shape/polyshape.cljs index f2816169..89541efe 100644 --- a/src/renderer/element/impl/shape/polyshape.cljs +++ b/src/renderer/element/impl/shape/polyshape.cljs @@ -66,7 +66,8 @@ [:g (map-indexed (fn [index point] (let [id (keyword (str index)) is-active (and (= (:id clicked-element) id) - (= (:element clicked-element) (:id el))) + (= (:element clicked-element) + (:id el))) offset (utils.element/offset el) [x y] (->> point (mapv utils.length/unit->px) @@ -82,18 +83,19 @@ :element (:id el)}] (when is-active [utils.svg/label - (string/join " " [(.toFixed x 2) (.toFixed y 2)]) + (string/join " " [(.toFixed x 3) (.toFixed y 3)]) [(- x margin) (+ y margin)] "end"])])) (utils.attribute/points->vec (-> el :attrs :points)))])) (defmethod element.hierarchy/edit ::element.hierarchy/polyshape [el [x y] handle] - (let [index (js/parseInt (name handle))] + (let [index (js/parseInt (name handle)) + transform-point (fn [[px py]] + (list (utils.length/transform px + x) + (utils.length/transform py + y)))] (update-in el [:attrs :points] #(-> (utils.attribute/points->vec %) - (update index (fn [[px py]] - (list (utils.length/transform px + x) - (utils.length/transform py + y)))) + (update index transform-point) (flatten) (->> (string/join " ") (string/trim)))))) diff --git a/src/renderer/element/impl/text.cljs b/src/renderer/element/impl/text.cljs index c2145d6f..ea94d525 100644 --- a/src/renderer/element/impl/text.cljs +++ b/src/renderer/element/impl/text.cljs @@ -139,8 +139,8 @@ (.then (fn [blob] (-> (.arrayBuffer blob) (.then (fn [buffer] - (let [opentype-font (opentype/parse buffer) - path (.getPath opentype-font content x y font-size)] + (let [font (opentype/parse buffer) + path (.getPath font content x y font-size)] (.toPathData path))))))))) (defn includes-prop? @@ -152,9 +152,9 @@ [weight fonts] (let [weight-num (js/parseInt weight) weight-names (get utils.attribute/weight-name-mapping weight) - matched-weight (->> fonts - (filter (fn [font] - (some #(includes-prop? % (.-style font)) weight-names))))] + includes-weight? (fn [font] + (some #(includes-prop? % (.-style font)) weight-names)) + matched-weight (filter includes-weight? fonts)] (if (or (seq matched-weight) (< weight-num 100)) matched-weight (recur (str (- weight-num 100)) fonts)))) @@ -171,6 +171,10 @@ (first matched-family) (first fonts)))) +(defn default-font-path + [font-style font-weight] + (str "./css/files/noto-sans-latin-" font-weight "-" font-style ".woff")) + (defmethod element.hierarchy/path :text [el] (let [{:keys [attrs content]} el @@ -180,8 +184,11 @@ (if font-family (-> (js/window.queryLocalFonts) (.then (fn [fonts] - (when-let [font (match-font fonts font-family font-style font-weight)] + (when-let [font (match-font fonts + font-family + font-style + font-weight)] (font-file->path-data font content x y font-size))))) - (-> (js/fetch (str "./css/files/noto-sans-latin-" font-weight "-" font-style ".woff")) + (-> (js/fetch (default-font-path font-style font-weight)) (.then (fn [response] (font-file->path-data response content x y font-size))))))) diff --git a/src/renderer/event/handlers.cljs b/src/renderer/event/handlers.cljs index 09cfb54d..ad573800 100644 --- a/src/renderer/event/handlers.cljs +++ b/src/renderer/event/handlers.cljs @@ -22,8 +22,8 @@ (m/=> pointer [:-> App PointerEvent App]) (defn pointer [db e] - (let [{:keys [pointer-offset tool dom-rect drag primary-tool - drag-threshold nearest-neighbor]} db + (let [{:keys [pointer-offset tool state cached-tool cached-state + dom-rect drag drag-threshold nearest-neighbor]} db {:keys [button pointer-pos timestamp pointer-id]} e adjusted-pointer-pos (frame.handlers/adjusted-pointer-pos db pointer-pos) db (snap.handlers/update-nearest-neighbors db)] @@ -36,9 +36,10 @@ (tool.handlers/pan-out-of-canvas dom-rect pointer-pos pointer-offset) (not drag) - (-> (tool.hierarchy/on-drag-start e) - (tool.handlers/add-fx [::event.effects/set-pointer-capture pointer-id]) - (assoc :drag true)) + (-> (assoc :drag true) + (tool.hierarchy/on-drag-start e) + (tool.handlers/add-fx [::event.effects/set-pointer-capture + pointer-id])) :always (tool.hierarchy/on-drag e)) @@ -50,7 +51,8 @@ "pointerdown" (cond-> db (= button :middle) - (-> (assoc :primary-tool tool) + (-> (assoc :cached-tool tool + :cached-state state) (tool.handlers/activate :pan)) (not= button :right) @@ -65,7 +67,8 @@ "pointerup" (cond-> (if drag (-> (tool.hierarchy/on-drag-end db e) - (tool.handlers/add-fx [::event.effects/release-pointer-capture pointer-id])) + (tool.handlers/add-fx [::event.effects/release-pointer-capture + pointer-id])) (if (= button :right) db (if (< 0 (- timestamp (:event-timestamp db)) (:double-click-delta db)) @@ -73,9 +76,10 @@ (tool.hierarchy/on-double-click e)) (-> (assoc db :event-timestamp timestamp) (tool.hierarchy/on-pointer-up e))))) - (and primary-tool (= button :middle)) - (-> (tool.handlers/activate primary-tool) - (dissoc :primary-tool)) + (and cached-tool (= button :middle)) + (-> (tool.handlers/activate cached-tool) + (tool.handlers/set-state cached-state) + (dissoc :cached-tool :cached-state)) :always (dissoc :pointer-offset :drag :nearest-neighbor)) @@ -91,7 +95,7 @@ (and (= (:code e) "Space") (not= (:tool db) :pan) (= (:state db) :idle)) - (-> (assoc :primary-tool (:tool db)) + (-> (assoc :cached-tool (:tool db)) (tool.handlers/activate :pan)) :always @@ -100,9 +104,9 @@ "keyup" (cond-> db (and (= (:code e) "Space") - (:primary-tool db)) - (-> (tool.handlers/activate (:primary-tool db)) - (dissoc :primary-tool)) + (:cached-tool db)) + (-> (tool.handlers/activate (:cached-tool db)) + (dissoc :cached-tool)) :always (tool.hierarchy/on-key-up e)) diff --git a/src/renderer/frame/handlers.cljs b/src/renderer/frame/handlers.cljs index 851b62e1..6f8ee9e2 100644 --- a/src/renderer/frame/handlers.cljs +++ b/src/renderer/frame/handlers.cljs @@ -38,13 +38,12 @@ (m/=> recenter-to-dom-rect [:-> App DomRect App]) (defn recenter-to-dom-rect [db updated-dom-rect] - (let [offset (-> (merge-with - (:dom-rect db) updated-dom-rect) - (select-keys [:width :height]))] + (let [delta (merge-with - (:dom-rect db) updated-dom-rect) + offset (matrix/div [(:width delta) (:height delta)] 2)] (if-not (-> db :window :focused) db (->> (:document-tabs db) - (reduce (fn [db id] - (pan-by db (matrix/div [(:width offset) (:height offset)] 2) id)) db))))) + (reduce (fn [db id] (pan-by db offset id)) db))))) (m/=> zoom-at-position [:-> App number? Vec2 App]) (defn zoom-at-position diff --git a/src/renderer/frame/views.cljs b/src/renderer/frame/views.cljs index 3d0eb5b6..c0dc9a7e 100644 --- a/src/renderer/frame/views.cljs +++ b/src/renderer/frame/views.cljs @@ -23,7 +23,8 @@ (let [frame-window (.-window (useFrame))] (reagent/create-class {:component-did-mount - #(.addEventListener frame-window "wheel" event.impl.wheel/handler! #js {:passive false}) + #(.addEventListener frame-window "wheel" event.impl.wheel/handler! + #js {:passive false}) :component-will-unmount #(.removeEventListener frame-window "wheel" event.impl.wheel/handler!) diff --git a/src/renderer/notification/views.cljs b/src/renderer/notification/views.cljs index 02278c36..2d5fdd46 100644 --- a/src/renderer/notification/views.cljs +++ b/src/renderer/notification/views.cljs @@ -42,8 +42,9 @@ [:div.fixed.flex.flex-col.m-4.right-0.bottom-0.gap-2.items-end (map-indexed (fn [index notification] - [:div.relative.flex.bg-secondary.w-80.p-4.mb-2.rounded.shadow-md.border.border-default - {:key index} + [:div.relative.flex.bg-secondary.w-80.p-4.mb-2.rounded.shadow-md + {:key index + :class "border border-default"} (:content notification) [views/icon-button "times" diff --git a/src/renderer/reepl/replumb.cljs b/src/renderer/reepl/replumb.cljs index d02981c7..179b3172 100644 --- a/src/renderer/reepl/replumb.cljs +++ b/src/renderer/reepl/replumb.cljs @@ -238,8 +238,10 @@ cljs.js/*load-fn* [mode text] (let [parts (vec (.split text ".")) completion (or (last parts) "") - prefix #(str (when (= mode :cljs) "js/") (string/join "." (conj (vec (butlast parts)) %))) - possibles (js-attrs (reduce aget js/window (butlast parts)))] + possibles (js-attrs (reduce aget js/window (butlast parts))) + prefix #(->> (conj (vec (butlast parts)) %) + (string/join ".") + (str (when (= mode :cljs) "js/")))] (->> possibles (filter #(not= -1 (.indexOf % completion))) (sort (partial compare-completion text)) diff --git a/src/renderer/reepl/views.cljs b/src/renderer/reepl/views.cljs index e900fe30..a0d145d8 100644 --- a/src/renderer/reepl/views.cljs +++ b/src/renderer/reepl/views.cljs @@ -100,7 +100,8 @@ {:ref ref} (into [:div.p-1] - (map (fn [i] [:div.font-mono.p-1.flex.text-xs.min-h-4 (item i opts)]) items))]])}))) + (map (fn [i] + [:div.font-mono.p-1.flex.text-xs.min-h-4 (item i opts)]) items))]])}))) (defn repl-items-panel [items show-value-opts set-text] @@ -232,17 +233,17 @@ (defn root [] - [repl - :execute #(replumb/run-repl (if (= @(rf/subscribe [::app.subs/repl-mode]) :cljs) - %1 - (str "(js/eval \"" %1 "\")")) - {:verbose @(rf/subscribe [::app.subs/debug-info])} %2) - :complete-word (fn [text] (replumb/process-apropos @(rf/subscribe [::app.subs/repl-mode]) text)) - :get-docs replumb/process-doc - :state state - :show-value-opts - {:showers [show-devtools/show-devtools - (partial show-function/show-fn-with-docs maybe-fn-docs)]} - :js-cm-opts {:mode (if (= @(rf/subscribe [::app.subs/repl-mode]) :cljs) "clojure" "javascript") - :keyMap "default" - :showCursorWhenSelecting true}]) + (let [repl-mode (rf/subscribe [::app.subs/repl-mode]) + debug-info (rf/subscribe [::app.subs/debug-info])] + [repl + :execute #(replumb/run-repl (if (= @repl-mode :cljs) %1 (str "(js/eval \"" %1 "\")")) + {:verbose @debug-info} %2) + :complete-word (fn [text] (replumb/process-apropos @repl-mode text)) + :get-docs replumb/process-doc + :state state + :show-value-opts + {:showers [show-devtools/show-devtools + (partial show-function/show-fn-with-docs maybe-fn-docs)]} + :js-cm-opts {:mode (if (= @repl-mode :cljs) "clojure" "javascript") + :keyMap "default" + :showCursorWhenSelecting true}])) diff --git a/src/renderer/tool/db.cljs b/src/renderer/tool/db.cljs index 357336dd..852e3902 100644 --- a/src/renderer/tool/db.cljs +++ b/src/renderer/tool/db.cljs @@ -9,7 +9,7 @@ [:fn {:error/fn (fn [{:keys [value]} _] (str value " is not a supported tool"))} tool?]) -(def State [:enum :idle :translate :clone :scale :select :create :edit :pan :type]) +(def State [:enum :idle :translate :clone :scale :select :create :edit :type]) (def Cursor [:enum diff --git a/src/renderer/tool/handlers.cljs b/src/renderer/tool/handlers.cljs index 78883ebe..f3e572a7 100644 --- a/src/renderer/tool/handlers.cljs +++ b/src/renderer/tool/handlers.cljs @@ -3,15 +3,12 @@ [clojure.core.matrix :as matrix] [malli.core :as m] [renderer.app.db :refer [App]] - [renderer.element.db :refer [Element]] - [renderer.element.handlers :as element.handlers] [renderer.frame.db :refer [DomRect]] [renderer.frame.handlers :as frame.handlers] [renderer.history.handlers :as history.handlers] [renderer.snap.handlers :as snap.handlers] [renderer.tool.db :refer [Tool State Cursor]] [renderer.tool.hierarchy :as tool.hierarchy] - [renderer.utils.element :as utils.element] [renderer.utils.math :refer [Vec2]])) (m/=> add-fx [:-> App vector? App]) @@ -32,44 +29,26 @@ (m/=> activate [:-> App Tool App]) (defn activate [db tool] - (-> db - (tool.hierarchy/on-deactivate) - (assoc :tool tool) - (set-state :idle) - (set-cursor "default") - (dissoc :drag :pointer-offset :clicked-element) - (snap.handlers/rebuild-tree) - (tool.hierarchy/on-activate))) - -(m/=> pointer-delta [:-> App Vec2]) -(defn pointer-delta - [db] - (matrix/sub (:adjusted-pointer-pos db) (:adjusted-pointer-offset db))) - -(m/=> dissoc-temp [:-> App App]) -(defn dissoc-temp - [db] (cond-> db - (:active-document db) - (update-in [:documents (:active-document db)] dissoc :temp-element))) + :always + (tool.hierarchy/on-deactivate) -(m/=> set-temp [:-> App map? App]) -(defn set-temp - [db el] - (->> (utils.element/normalize-attrs el) - (assoc-in db [:documents (:active-document db) :temp-element]))) + (and (not= (:cached-state db) :create) + (not= (:state db) :type)) + (history.handlers/reset-state) -(m/=> temp [:-> App [:maybe Element]]) -(defn temp - [db] - (get-in db [:documents (:active-document db) :temp-element])) + :always + (-> (assoc :tool tool) + (set-state :idle) + (set-cursor "default") + (dissoc :drag :pointer-offset :clicked-element) + (snap.handlers/rebuild-tree) + (tool.hierarchy/on-activate)))) -(m/=> create-temp-element [:-> App App]) -(defn create-temp-element +(m/=> pointer-delta [:-> App Vec2]) +(defn pointer-delta [db] - (->> (temp db) - (element.handlers/add db) - (dissoc-temp))) + (matrix/sub (:adjusted-pointer-pos db) (:adjusted-pointer-offset db))) (m/=> axis-pan-offset [:-> number? number? number? number?]) (defn axis-pan-offset @@ -106,7 +85,6 @@ (cond-> db :always (-> (activate (:tool db)) - (dissoc-temp) (history.handlers/reset-state)) (= (:state db) :idle) diff --git a/src/renderer/tool/impl/base/pan.cljs b/src/renderer/tool/impl/base/pan.cljs index c564c243..9a07c442 100644 --- a/src/renderer/tool/impl/base/pan.cljs +++ b/src/renderer/tool/impl/base/pan.cljs @@ -21,19 +21,13 @@ [] "Click and drag to pan.") -(defmethod tool.hierarchy/help [:pan :pan] - [] - "Drag to pan.") - (defmethod tool.hierarchy/on-pointer-up :pan [db _e] - (-> (tool.handlers/set-cursor db "grab") - (tool.handlers/set-state :idle))) + (tool.handlers/set-cursor db "grab")) (defmethod tool.hierarchy/on-pointer-down :pan [db _e] - (-> (tool.handlers/set-cursor db "grabbing") - (tool.handlers/set-state :pan))) + (tool.handlers/set-cursor db "grabbing")) (defmethod tool.hierarchy/on-drag :pan [db e] @@ -42,6 +36,5 @@ (defmethod tool.hierarchy/on-drag-end :pan [db _e] (-> (tool.handlers/set-cursor db "grab") - (tool.handlers/set-state :idle) (snap.handlers/update-viewport-tree) (tool.handlers/add-fx [::app.effects/persist]))) diff --git a/src/renderer/tool/impl/base/transform.cljs b/src/renderer/tool/impl/base/transform.cljs index 8885fdf3..9bc286da 100644 --- a/src/renderer/tool/impl/base/transform.cljs +++ b/src/renderer/tool/impl/base/transform.cljs @@ -3,6 +3,7 @@ [clojure.core.matrix :as matrix] [malli.core :as m] [re-frame.core :as rf] + [reagent.core :as reagent] [renderer.app.db :refer [App]] [renderer.document.subs :as-alias document.subs] [renderer.element.db :refer [Element]] @@ -33,6 +34,15 @@ (derive :transform ::tool.hierarchy/tool) +(derive :zoom ::tool.hierarchy/tool) + +(defonce bbox-element (reagent/atom nil)) + +(rf/reg-fx + ::update + (fn [value] + (reset! bbox-element value))) + (defmethod tool.hierarchy/properties :transform [] {:icon "pointer"}) @@ -66,8 +76,8 @@ (m/=> hovered? [:-> App Element boolean? boolean?]) (defn hovered? - [db el intersecting?] - (let [selection-bbox (element.hierarchy/bbox (tool.handlers/temp db))] + [el intersecting?] + (let [selection-bbox (element.hierarchy/bbox @bbox-element)] (if-let [el-bbox (:bbox el)] (if intersecting? (utils.bounds/intersect? el-bbox selection-bbox) @@ -82,7 +92,7 @@ (filter :visible) (reduce (fn [db el] (cond-> db - (hovered? db el intersecting?) + (hovered? el intersecting?) (f (:id el)))) db))) (defmethod tool.hierarchy/on-pointer-move :transform @@ -269,7 +279,8 @@ offset)] (reduce (fn [db id] (let [container (element.handlers/parent-container db id) - hovered-svg (element.handlers/hovered-svg db)] + hovered-svg (element.handlers/hovered-svg db) + start-point (fn [el] (into [] (take 2) (:bbox el)))] (cond-> (element.handlers/translate db id offset) (and (seq (element.handlers/selected db)) (empty? (rest (element.handlers/selected db))) @@ -282,10 +293,10 @@ ;; FIXME: Handle nested containers. (:bbox container) - (element.handlers/translate id (vec (take 2 (:bbox container)))) + (element.handlers/translate id (start-point container)) (:bbox hovered-svg) - (element.handlers/translate id (matrix/mul (take 2 (:bbox hovered-svg)) + (element.handlers/translate id (matrix/mul (start-point hovered-svg) -1)))))) db (element.handlers/top-ancestor-ids db)))) @@ -324,7 +335,7 @@ (case (:state db) :select (-> (element.handlers/clear-hovered db) - (tool.handlers/set-temp (select-rect db (:alt-key e))) + (tool.handlers/add-fx [::update (select-rect db (:alt-key e))]) (reduce-by-area (:alt-key e) element.handlers/hover)) :translate @@ -361,7 +372,7 @@ (-> (case (:state db) :select (-> (cond-> db (not (:shift-key e)) element.handlers/deselect-all) (reduce-by-area (:alt-key e) element.handlers/select) - (tool.handlers/dissoc-temp) + (tool.handlers/add-fx [::update nil]) (history.handlers/finalize "Modify selection")) :translate (history.handlers/finalize db "Move selection") :scale (history.handlers/finalize db "Scale selection") @@ -392,7 +403,8 @@ (defmethod tool.hierarchy/snapping-elements :transform [db] (let [non-selected-ids (element.handlers/non-selected-ids db) - non-selected (select-keys (element.handlers/entities db) (vec non-selected-ids))] + els (element.handlers/entities db) + non-selected (select-keys els (vec non-selected-ids))] (filter :visible (vals non-selected)))) (m/=> size-label [:-> BBox any?]) @@ -403,7 +415,7 @@ x (+ min-x (/ (- max-x min-x) 2)) y (+ y2 (/ (+ (/ theme.db/handle-size 2) 15) zoom)) [w h] (utils.bounds/->dimensions bbox) - text (str (.toFixed w 2) " x " (.toFixed h 2))] + text (str (.toFixed w 3) " x " (.toFixed h 3))] [utils.svg/label text [x y]])) (m/=> area-label [:-> number? BBox any?]) @@ -414,7 +426,7 @@ [min-x min-y max-x] bbox x (+ min-x (/ (- max-x min-x) 2)) y (+ min-y (/ (- -15 (/ theme.db/handle-size 2)) zoom)) - text (str (.toFixed area 2) " px²")] + text (str (.toFixed area 3) " px²")] [utils.svg/label text [x y]]))) (defmethod tool.hierarchy/render :transform @@ -446,4 +458,6 @@ (when (= state :scale) [size-label bbox])]) (when pivot-point - [utils.svg/times pivot-point])])) + [utils.svg/times pivot-point]) + + [element.hierarchy/render @bbox-element]])) diff --git a/src/renderer/tool/impl/base/zoom.cljs b/src/renderer/tool/impl/base/zoom.cljs index f2393a73..265fca0a 100644 --- a/src/renderer/tool/impl/base/zoom.cljs +++ b/src/renderer/tool/impl/base/zoom.cljs @@ -1,6 +1,9 @@ (ns renderer.tool.impl.base.zoom (:require + [re-frame.core :as rf] + [reagent.core :as reagent] [renderer.app.effects :as-alias app.effects] + [renderer.element.hierarchy :as element.hierarchy] [renderer.frame.handlers :as frame.handlers] [renderer.snap.handlers :as snap.handlers] [renderer.tool.handlers :as tool.handlers] @@ -9,6 +12,13 @@ (derive :zoom ::tool.hierarchy/tool) +(defonce element (reagent/atom nil)) + +(rf/reg-fx + ::update + (fn [value] + (reset! element value))) + (defmethod tool.hierarchy/properties :zoom [] {:icon "magnifier"}) @@ -23,6 +33,10 @@ [db] (tool.handlers/set-cursor db "zoom-in")) +(defmethod tool.hierarchy/on-deactivate :zoom + [db] + (tool.handlers/add-fx db [::update nil])) + (defmethod tool.hierarchy/on-key-down :zoom [db e] (cond-> db @@ -37,7 +51,7 @@ (defmethod tool.hierarchy/on-drag :zoom [db _e] - (tool.handlers/set-temp db (utils.svg/select-box db))) + (tool.handlers/add-fx db [::update (utils.svg/select-box db)])) (defmethod tool.hierarchy/on-drag-end :zoom [db e] @@ -52,7 +66,8 @@ zoom (min width-ratio height-ratio) factor (if (:shift-key e) (:zoom-sensitivity db) (/ zoom current-zoom)) cursor (if (:shift-key e) "zoom-out" "zoom-in")] - (-> (tool.handlers/dissoc-temp db) + (-> db + (tool.handlers/add-fx [::update nil]) (tool.handlers/set-cursor cursor) (frame.handlers/zoom-in-place factor) (frame.handlers/pan-to-bbox [x y offset-x offset-y]) @@ -65,3 +80,7 @@ (-> (frame.handlers/zoom-at-pointer db factor) (snap.handlers/update-viewport-tree) (tool.handlers/add-fx [::app.effects/persist])))) + +(defmethod tool.hierarchy/render :zoom + [] + [element.hierarchy/render @element]) diff --git a/src/renderer/tool/impl/draw/brush.cljs b/src/renderer/tool/impl/draw/brush.cljs index e4de7e18..2dd64a84 100644 --- a/src/renderer/tool/impl/draw/brush.cljs +++ b/src/renderer/tool/impl/draw/brush.cljs @@ -1,8 +1,13 @@ (ns renderer.tool.impl.draw.brush "https://github.com/steveruizok/perfect-freehand" (:require + [clojure.core.matrix :as matrix] [clojure.string :as string] + [re-frame.core :as rf] + [reagent.core :as reagent] [renderer.document.handlers :as document.handlers] + [renderer.element.handlers :as element.handlers] + [renderer.element.hierarchy :as element.hierarchy] [renderer.history.handlers :as history.handlers] [renderer.tool.handlers :as tool.handlers] [renderer.tool.hierarchy :as tool.hierarchy])) @@ -13,38 +18,56 @@ [] {:icon "brush"}) +(defonce brush-element (reagent/atom nil)) + +(rf/reg-fx + ::update-brush + (fn [value] + (reset! brush-element value))) + (defmethod tool.hierarchy/on-pointer-move :brush [db e] (let [[x y] (:adjusted-pointer-pos db) pressure (:pressure e) pressure (if (zero? pressure) 1 pressure) - r (* (/ 16 2) pressure)] - (tool.handlers/set-temp db {:type :element - :tag :circle - :attrs {:cx x - :cy y - :r r - :fill (document.handlers/attr db :stroke)}}))) + r (* (/ 16 2) pressure) + stroke (document.handlers/attr db :stroke)] + (tool.handlers/add-fx db [::update-brush {:type :element + :tag :circle + :attrs {:cx x + :cy y + :r r + :fill stroke}}]))) + +(defmethod tool.hierarchy/on-drag-start :brush + [db e] + (let [point (string/join " " (conj (:adjusted-pointer-pos db) (:pressure e))) + stroke (document.handlers/attr db :stroke)] + (-> db + (tool.handlers/set-state :create) + (element.handlers/add {:type :element + :tag :brush + :attrs {:points point + :stroke stroke + :size 16 + :thinning 0.5 + :smoothing 0.5 + :streamline 0.5}})))) (defmethod tool.hierarchy/on-drag :brush [db e] - (let [active-document (:active-document db) - point (string/join " " (conj (:adjusted-pointer-pos db) (:pressure e))) - points-path [:documents active-document :temp-element :attrs :points]] - (if (get-in db points-path) - (update-in db points-path #(str % " " point)) - (tool.handlers/set-temp db {:type :element - :tag :brush - :attrs {:points point - :stroke (document.handlers/attr db :stroke) - :size 16 - :thinning 0.5 - :smoothing 0.5 - :streamline 0.5}})))) + (let [{:keys [id parent]} (first (element.handlers/selected db)) + [min-x min-y] (element.hierarchy/bbox (element.handlers/entity db parent)) + point (matrix/sub (:adjusted-pointer-pos db) [min-x min-y]) + point (string/join " " (conj point (:pressure e)))] + (element.handlers/update-attr db id :points str " " point))) (defmethod tool.hierarchy/on-drag-end :brush [db _e] (-> db - (tool.handlers/create-temp-element) - (tool.handlers/activate :transform) - (history.handlers/finalize "Draw line"))) + (history.handlers/finalize "Draw brush") + (tool.handlers/activate :transform))) + +(defmethod tool.hierarchy/render :brush + [] + [element.hierarchy/render @brush-element]) diff --git a/src/renderer/tool/impl/draw/pen.cljs b/src/renderer/tool/impl/draw/pen.cljs index 5971f53d..3f652999 100644 --- a/src/renderer/tool/impl/draw/pen.cljs +++ b/src/renderer/tool/impl/draw/pen.cljs @@ -1,7 +1,10 @@ (ns renderer.tool.impl.draw.pen (:require + [clojure.core.matrix :as matrix] [clojure.string :as string] [renderer.document.handlers :as document.handlers] + [renderer.element.handlers :as element.handlers] + [renderer.element.hierarchy :as element.hierarchy] [renderer.history.handlers :as history.handlers] [renderer.tool.handlers :as tool.handlers] [renderer.tool.hierarchy :as tool.hierarchy] @@ -14,26 +17,34 @@ [] {:icon "pencil"}) +(defmethod tool.hierarchy/on-drag-start :pen + [db _e] + (let [stroke (document.handlers/attr db :stroke) + point-1 (string/join " " (:adjusted-pointer-offset db)) + point-2 (string/join " " (:adjusted-pointer-pos db))] + (-> db + (tool.handlers/set-state :create) + (element.handlers/add {:type :element + :tag :polyline + :attrs {:points (str point-1 " " point-2) + :stroke stroke + :fill "transparent"}})))) + (defmethod tool.hierarchy/on-drag :pen [db _e] - (let [{:keys [active-document adjusted-pointer-pos]} db - points-path [:documents active-document :temp-element :attrs :points]] - (if (get-in db points-path) - (update-in db points-path #(str % " " (string/join " " adjusted-pointer-pos))) - (tool.handlers/set-temp db {:type :element - :tag :polyline - :attrs {:points (string/join " " adjusted-pointer-pos) - :stroke (document.handlers/attr db :stroke) - :fill "transparent"}})))) + (let [{:keys [id parent]} (first (element.handlers/selected db)) + [min-x min-y] (element.hierarchy/bbox (element.handlers/entity db parent)) + point (matrix/sub (:adjusted-pointer-pos db) [min-x min-y]) + point (string/join " " point)] + (element.handlers/update-attr db id :points str " " point))) (defmethod tool.hierarchy/on-drag-end :pen [db _e] - (let [path (-> (tool.handlers/temp db) + (let [path (-> (first (element.handlers/selected db)) (utils.element/->path) (update-in [:attrs :d] utils.path/manipulate :smooth) (update-in [:attrs :d] utils.path/manipulate :simplify))] (-> db - (tool.handlers/set-temp path) - (tool.handlers/create-temp-element) - (tool.handlers/activate :transform) - (history.handlers/finalize "Draw line")))) + (element.handlers/swap path) + (history.handlers/finalize "Draw line") + (tool.handlers/activate :transform)))) diff --git a/src/renderer/tool/impl/element/circle.cljs b/src/renderer/tool/impl/element/circle.cljs index 675f0f02..86f1c683 100644 --- a/src/renderer/tool/impl/element/circle.cljs +++ b/src/renderer/tool/impl/element/circle.cljs @@ -3,6 +3,8 @@ (:require [clojure.core.matrix :as matrix] [renderer.document.handlers :as document.handlers] + [renderer.element.handlers :as element.handlers] + [renderer.history.handlers :as history.handlers] [renderer.tool.handlers :as tool.handlers] [renderer.tool.hierarchy :as tool.hierarchy])) @@ -12,21 +14,39 @@ [] {:icon "circle-tool"}) -(defmethod tool.hierarchy/on-drag :circle +(defmethod tool.hierarchy/on-drag-start :circle [db _e] (let [offset (or (:nearest-neighbor-offset db) (:adjusted-pointer-offset db)) position (or (:point (:nearest-neighbor db)) (:adjusted-pointer-pos db)) - [x y] offset radius (matrix/distance position offset) - attrs {:cx x - :cy y - :fill (document.handlers/attr db :fill) - :stroke (document.handlers/attr db :stroke) - :r radius}] - (tool.handlers/set-temp db {:type :element :tag :circle :attrs attrs}))) + [cx cy] offset + fill (document.handlers/attr db :fill) + stroke (document.handlers/attr db :stroke)] + (-> (tool.handlers/set-state db :create) + (element.handlers/add {:type :element + :tag :circle + :attrs {:cx cx + :cy cy + :fill fill + :stroke stroke + :r radius}})))) + +(defmethod tool.hierarchy/on-drag :circle + [db _e] + (let [offset (or (:nearest-neighbor-offset db) (:adjusted-pointer-offset db)) + position (or (:point (:nearest-neighbor db)) (:adjusted-pointer-pos db)) + radius (.toFixed (matrix/distance position offset) 3) + id (:id (first (element.handlers/selected db)))] + (element.handlers/update-el db id #(assoc-in % [:attrs :r] (str radius))))) + +(defmethod tool.hierarchy/on-drag-end :circle + [db _e] + (-> db + (history.handlers/finalize "Create circle") + (tool.handlers/activate :transform))) (defmethod tool.hierarchy/snapping-points :circle [db] [(with-meta (:adjusted-pointer-pos db) - {:label (str (name (:tool db)) " " (if (tool.handlers/temp db) "radius" "center"))})]) + {:label (str "Circle " (if (= (:state db) :create) "radius" "center"))})]) diff --git a/src/renderer/tool/impl/element/core.cljs b/src/renderer/tool/impl/element/core.cljs index a6ff9e08..930e68f0 100644 --- a/src/renderer/tool/impl/element/core.cljs +++ b/src/renderer/tool/impl/element/core.cljs @@ -1,7 +1,6 @@ (ns renderer.tool.impl.element.core (:require [renderer.element.handlers :as element.handlers] - [renderer.history.handlers :as history.handlers] [renderer.tool.handlers :as tool.handlers] [renderer.tool.hierarchy :as tool.hierarchy] [renderer.tool.impl.element.circle] @@ -25,17 +24,6 @@ [db] (tool.handlers/set-cursor db "crosshair")) -(defmethod tool.hierarchy/on-drag-start ::tool.hierarchy/element - [db _e] - (tool.handlers/set-state db :create)) - -(defmethod tool.hierarchy/on-drag-end ::tool.hierarchy/element - [db _e] - (-> db - (tool.handlers/create-temp-element) - (tool.handlers/activate :transform) - (history.handlers/finalize (str "Create " (name (:tag (tool.handlers/temp db))))))) - (defmethod tool.hierarchy/snapping-points ::tool.hierarchy/element [db] [(with-meta diff --git a/src/renderer/tool/impl/element/ellipse.cljs b/src/renderer/tool/impl/element/ellipse.cljs index 109b2bdd..f226e701 100644 --- a/src/renderer/tool/impl/element/ellipse.cljs +++ b/src/renderer/tool/impl/element/ellipse.cljs @@ -2,6 +2,8 @@ "https://www.w3.org/TR/SVG/shapes.html#EllipseElement" (:require [renderer.document.handlers :as document.handlers] + [renderer.element.handlers :as element.handlers] + [renderer.history.handlers :as history.handlers] [renderer.tool.handlers :as tool.handlers] [renderer.tool.hierarchy :as tool.hierarchy])) @@ -9,25 +11,48 @@ (defmethod tool.hierarchy/properties :ellipse [] - {:icon "ellipse-tool"}) + {:icon "ellipse-tool" + :label "Ellipse"}) (defmethod tool.hierarchy/help [:ellipse :create] [] [:div "Hold " [:span.shortcut-key "Ctrl"] " to lock proportions."]) +(defn attributes + [db lock-ratio] + (let [[offset-x offset-y] (or (:nearest-neighbor-offset db) + (:adjusted-pointer-offset db)) + [x y] (or (:point (:nearest-neighbor db)) (:adjusted-pointer-pos db)) + rx (.toFixed (abs (- x offset-x)) 3) + ry (.toFixed (abs (- y offset-y)) 3)] + {:rx (cond-> rx lock-ratio (min ry)) + :ry (cond-> ry lock-ratio (min rx))})) + +(defmethod tool.hierarchy/on-drag-start :ellipse + [db e] + (let [[x y] (or (:nearest-neighbor-offset db) + (:adjusted-pointer-offset db)) + fill (document.handlers/attr db :fill) + stroke (document.handlers/attr db :stroke)] + (-> db + (tool.handlers/set-state :create) + (element.handlers/add {:type :element + :tag :ellipse + :attrs (merge (attributes db (:ctrl-key e)) + {:cx x + :cy y + :fill fill + :stroke stroke})})))) + (defmethod tool.hierarchy/on-drag :ellipse [db e] - (let [[offset-x offset-y] (or (:nearest-neighbor-offset db) (:adjusted-pointer-offset db)) - [x y] (or (:point (:nearest-neighbor db)) (:adjusted-pointer-pos db)) - lock-ratio (:ctrl-key e) - rx (abs (- x offset-x)) - ry (abs (- y offset-y)) - attrs {:cx offset-x - :cy offset-y - :fill (document.handlers/attr db :fill) - :stroke (document.handlers/attr db :stroke) - :rx (if lock-ratio (min rx ry) rx) - :ry (if lock-ratio (min rx ry) ry)}] - (tool.handlers/set-temp db {:type :element - :tag :ellipse - :attrs attrs}))) + (let [attrs (attributes db (:ctrl-key e)) + assoc-attr (fn [el [k v]] (assoc-in el [:attrs k] (str v))) + id (:id (first (element.handlers/selected db)))] + (element.handlers/update-el db id #(reduce assoc-attr % attrs)))) + +(defmethod tool.hierarchy/on-drag-end :ellipse + [db _e] + (-> db + (history.handlers/finalize "Create ellipse") + (tool.handlers/activate :transform))) diff --git a/src/renderer/tool/impl/element/line.cljs b/src/renderer/tool/impl/element/line.cljs index 2056f637..1a17a35d 100644 --- a/src/renderer/tool/impl/element/line.cljs +++ b/src/renderer/tool/impl/element/line.cljs @@ -1,7 +1,10 @@ (ns renderer.tool.impl.element.line "https://www.w3.org/TR/SVG/shapes.html#LineElement" (:require + [clojure.core.matrix :as matrix] [renderer.document.handlers :as document.handlers] + [renderer.element.handlers :as element.handlers] + [renderer.element.hierarchy :as element.hierarchy] [renderer.history.handlers :as history.handlers] [renderer.tool.handlers :as tool.handlers] [renderer.tool.hierarchy :as tool.hierarchy])) @@ -12,53 +15,36 @@ [] {:icon "line-tool"}) -(defn create-line - [db] - (let [[offset-x offset-y] (or (:nearest-neighbor-offset db) (:adjusted-pointer-offset db)) - [x y] (or (:point (:nearest-neighbor db)) (:adjusted-pointer-pos db)) - attrs {:x1 offset-x - :y1 offset-y - :x2 x - :y2 y - :stroke (document.handlers/attr db :stroke)}] - (tool.handlers/set-temp db {:type :element - :tag :line - :attrs attrs}))) - -(defn update-line-end - [db] - (let [[x y] (or (:point (:nearest-neighbor db)) (:adjusted-pointer-pos db)) - temp (-> (tool.handlers/temp db) - (assoc-in [:attrs :x2] x) - (assoc-in [:attrs :y2] y))] - (tool.handlers/set-temp db temp))) - -(defmethod tool.hierarchy/on-pointer-move :line +(defmethod tool.hierarchy/on-drag-start :line [db _e] - (cond-> db - (tool.handlers/temp db) - (update-line-end))) - -(defmethod tool.hierarchy/on-pointer-up :line - [db _e] - (cond - (tool.handlers/temp db) - (-> (tool.handlers/create-temp-element db) - (tool.handlers/activate :transform) - (history.handlers/finalize "Create line")) - - (:pointer-offset db) - (-> (tool.handlers/set-state db :create) - (create-line)) - - :else db)) - -(defmethod tool.hierarchy/on-pointer-down :line - [db _e] - (cond-> db - (tool.handlers/temp db) - (history.handlers/finalize "Create line"))) + (let [[offset-x offset-y] (or (:nearest-neighbor-offset db) + (:adjusted-pointer-offset db)) + [x y] (or (:point (:nearest-neighbor db)) (:adjusted-pointer-pos db)) + stroke (document.handlers/attr db :stroke)] + (-> db + (tool.handlers/set-state :create) + (element.handlers/add {:type :element + :tag :line + :attrs {:x1 offset-x + :y1 offset-y + :x2 x + :y2 y + :stroke stroke}})))) (defmethod tool.hierarchy/on-drag :line [db _e] - (create-line db)) + (let [position (or (:point (:nearest-neighbor db)) (:adjusted-pointer-pos db)) + {:keys [id parent]} (first (element.handlers/selected db)) + [min-x min-y] (element.hierarchy/bbox (element.handlers/entity db parent)) + [x y] (matrix/sub position [min-x min-y]) + x (.toFixed x 3) + y (.toFixed y 3)] + (element.handlers/update-el db id #(-> % + (assoc-in [:attrs :x2] (str x)) + (assoc-in [:attrs :y2] (str y)))))) + +(defmethod tool.hierarchy/on-drag-end :line + [db _e] + (-> db + (history.handlers/finalize "Create line") + (tool.handlers/activate :transform))) diff --git a/src/renderer/tool/impl/element/polyshape.cljs b/src/renderer/tool/impl/element/polyshape.cljs index 8d6b8cb7..0469fc62 100644 --- a/src/renderer/tool/impl/element/polyshape.cljs +++ b/src/renderer/tool/impl/element/polyshape.cljs @@ -2,8 +2,11 @@ "This serves as an abstraction for polygons and polylines that have similar attributes and hehavior" (:require + [clojure.core.matrix :as matrix] [clojure.string :as string] [renderer.document.handlers :as document.handlers] + [renderer.element.handlers :as element.handlers] + [renderer.element.hierarchy :as element.hierarchy] [renderer.history.handlers :as history.handlers] [renderer.tool.handlers :as tool.handlers] [renderer.tool.hierarchy :as tool.hierarchy] @@ -11,10 +14,6 @@ (derive ::tool.hierarchy/polyshape ::tool.hierarchy/element) -(defn points-path - [db] - [:documents (:active-document db) :temp-element :attrs :points]) - (defmethod tool.hierarchy/help [::tool.hierarchy/polyshape :idle] [] [:<> @@ -23,54 +22,62 @@ (defn create-polyline [db points] - (tool.handlers/set-temp db {:type :element - :tag (:tool db) - :attrs {:points (string/join " " points) - :stroke (document.handlers/attr db :stroke) - :fill (document.handlers/attr db :fill)}})) + (let [stroke (document.handlers/attr db :stroke) + fill (document.handlers/attr db :fill)] + (-> db + (tool.handlers/set-state :create) + (element.handlers/add {:type :element + :tag (:tool db) + :attrs {:points (string/join " " points) + :stroke stroke + :fill fill}})))) (defn add-point [db point] - (update-in db (points-path db) #(str % " " (string/join " " point)))) + (let [id (:id (first (element.handlers/selected db)))] + (element.handlers/update-attr db id :points str " " point))) (defn drop-last-point [db] - (let [points (get-in db (points-path db)) - point-vector (utils.attribute/points->vec points)] - (assoc-in db - (points-path db) - (->> point-vector drop-last flatten (string/join " "))))) + (let [id (:id (first (element.handlers/selected db)))] + (element.handlers/update-attr db id :points #(->> % + utils.attribute/points->vec + drop-last + flatten + (string/join " "))))) (defmethod tool.hierarchy/on-pointer-up ::tool.hierarchy/polyshape [db _e] (let [point (or (:point (:nearest-neighbor db)) (:adjusted-pointer-pos db))] - (if (tool.handlers/temp db) + (if (= (:state db) :create) (add-point db point) - (-> (tool.handlers/set-state db :create) - (create-polyline point))))) + (create-polyline db point)))) (defmethod tool.hierarchy/on-drag-end ::tool.hierarchy/polyshape [db _e] - (if (tool.handlers/temp db) + (if (= (:state db) :create) (add-point db (:adjusted-pointer-pos db)) - (-> (tool.handlers/set-state db :create) - (create-polyline (:adjusted-pointer-pos db))))) + (create-polyline db (:adjusted-pointer-pos db)))) (defmethod tool.hierarchy/on-pointer-move ::tool.hierarchy/polyshape [db _e] - (let [point (or (:point (:nearest-neighbor db)) (:adjusted-pointer-pos db))] - (if-let [points (get-in db (points-path db))] - (let [point-vector (utils.attribute/points->vec points)] - (assoc-in db - (points-path db) - (string/join " " (concat (apply concat (if (second point-vector) - (drop-last point-vector) - point-vector)) - point)))) db))) + (let [point (or (:point (:nearest-neighbor db)) (:adjusted-pointer-pos db)) + {:keys [id parent]} (first (element.handlers/selected db)) + [min-x min-y] (element.hierarchy/bbox (element.handlers/entity db parent)) + point (matrix/sub point [min-x min-y])] + (if (= (:state db) :create) + (element.handlers/update-attr + db id :points + #(let [point-vector (utils.attribute/points->vec %)] + (string/join " " (concat (apply concat (if (second point-vector) + (drop-last point-vector) + point-vector)) + point)))) + + db))) (defmethod tool.hierarchy/on-double-click ::tool.hierarchy/polyshape [db _e] (-> (drop-last-point db) - (tool.handlers/create-temp-element) - (tool.handlers/activate :transform) - (history.handlers/finalize (str "Create " (name (:tool db)))))) + (history.handlers/finalize (str "Create " (name (:tool db)))) + (tool.handlers/activate :transform))) diff --git a/src/renderer/tool/impl/element/rect.cljs b/src/renderer/tool/impl/element/rect.cljs index 467cb70a..05b171ad 100644 --- a/src/renderer/tool/impl/element/rect.cljs +++ b/src/renderer/tool/impl/element/rect.cljs @@ -2,6 +2,9 @@ "https://www.w3.org/TR/SVG/shapes.html#RectElement" (:require [renderer.document.handlers :as document.handlers] + [renderer.element.handlers :as element.handlers] + [renderer.element.hierarchy :as element.hierarchy] + [renderer.history.handlers :as history.handlers] [renderer.tool.handlers :as tool.handlers] [renderer.tool.hierarchy :as tool.hierarchy])) @@ -16,17 +19,42 @@ [] [:div "Hold " [:span.shortcut-key "Ctrl"] " to lock proportions."]) +(defn attributes + [db lock-ratio] + (let [[offset-x offset-y] (or (:nearest-neighbor-offset db) + (:adjusted-pointer-offset db)) + [x y] (or (:point (:nearest-neighbor db)) (:adjusted-pointer-pos db)) + width (.toFixed (abs (- x offset-x)) 3) + height (.toFixed (abs (- y offset-y)) 3)] + {:x (.toFixed (min x offset-x) 3) + :y (.toFixed (min y offset-y) 3) + :width (cond-> width lock-ratio (min height)) + :height (cond-> height lock-ratio (min width))})) + +(defmethod tool.hierarchy/on-drag-start :rect + [db e] + (let [fill (document.handlers/attr db :fill) + stroke (document.handlers/attr db :stroke)] + (-> db + (tool.handlers/set-state :create) + (element.handlers/add {:type :element + :tag :rect + :attrs (merge (attributes db (:ctrl-key e)) + {:fill fill + :stroke stroke})})))) + (defmethod tool.hierarchy/on-drag :rect [db e] - (let [[offset-x offset-y] (or (:nearest-neighbor-offset db) (:adjusted-pointer-offset db)) - [x y] (or (:point (:nearest-neighbor db)) (:adjusted-pointer-pos db)) - width (abs (- x offset-x)) - height (abs (- y offset-y))] - (tool.handlers/set-temp db {:type :element - :tag :rect - :attrs {:x (min x offset-x) - :y (min y offset-y) - :width (if (:ctrl-key e) (min width height) width) - :height (if (:ctrl-key e) (min width height) height) - :fill (document.handlers/attr db :fill) - :stroke (document.handlers/attr db :stroke)}}))) + (let [attrs (attributes db (:ctrl-key e)) + assoc-attr (fn [el [k v]] (assoc-in el [:attrs k] (str v))) + {:keys [id parent]} (first (element.handlers/selected db)) + [min-x min-y] (element.hierarchy/bbox (element.handlers/entity db parent))] + (-> db + (element.handlers/update-el id #(reduce assoc-attr % attrs)) + (element.handlers/translate [(- min-x) (- min-y)])))) + +(defmethod tool.hierarchy/on-drag-end :rect + [db _e] + (-> db + (history.handlers/finalize "Create rectangle") + (tool.handlers/activate :transform))) diff --git a/src/renderer/tool/impl/element/svg.cljs b/src/renderer/tool/impl/element/svg.cljs index eba325ac..0e68385b 100644 --- a/src/renderer/tool/impl/element/svg.cljs +++ b/src/renderer/tool/impl/element/svg.cljs @@ -1,6 +1,8 @@ (ns renderer.tool.impl.element.svg "https://www.w3.org/TR/SVG/struct.html#SVGElement" (:require + [renderer.element.handlers :as element.handlers] + [renderer.history.handlers :as history.handlers] [renderer.tool.handlers :as tool.handlers] [renderer.tool.hierarchy :as tool.hierarchy])) @@ -14,17 +16,35 @@ [] [:div "Hold " [:span.shortcut-key "Ctrl"] " to lock proportions."]) +(defn attributes + [db lock-ratio] + (let [[offset-x offset-y] (or (:nearest-neighbor-offset db) + (:adjusted-pointer-offset db)) + [x y] (or (:point (:nearest-neighbor db)) (:adjusted-pointer-pos db)) + width (.toFixed (abs (- x offset-x)) 3) + height (.toFixed (abs (- y offset-y)) 3)] + {:x (.toFixed (min x offset-x) 3) + :y (.toFixed (min y offset-y) 3) + :width (cond-> width lock-ratio (min height)) + :height (cond-> height lock-ratio (min width))})) + +(defmethod tool.hierarchy/on-drag-start :svg + [db e] + (-> db + (tool.handlers/set-state :create) + (element.handlers/add {:tag :svg + :type :element + :attrs (attributes db (:ctrl-key e))}))) + (defmethod tool.hierarchy/on-drag :svg [db e] - (let [[offset-x offset-y] (or (:nearest-neighbor-offset db) (:adjusted-pointer-offset db)) - [x y] (or (:point (:nearest-neighbor db)) (:adjusted-pointer-pos db)) - lock-ratio (:ctrl-key e) - width (abs (- x offset-x)) - height (abs (- y offset-y)) - attrs {:x (min x offset-x) - :y (min y offset-y) - :width (if lock-ratio (min width height) width) - :height (if lock-ratio (min width height) height)}] - (tool.handlers/set-temp db {:tag :svg - :type :element - :attrs attrs}))) + (let [id (:id (first (element.handlers/selected db))) + attrs (attributes db (:ctrl-key e)) + assoc-attr (fn [el [k v]] (assoc-in el [:attrs k] (str v)))] + (element.handlers/update-el db id #(reduce assoc-attr % attrs)))) + +(defmethod tool.hierarchy/on-drag-end :svg + [db _e] + (-> db + (history.handlers/finalize "Create SVG") + (tool.handlers/activate :transform))) diff --git a/src/renderer/tool/impl/element/text.cljs b/src/renderer/tool/impl/element/text.cljs index 4a40758e..7dc9422e 100644 --- a/src/renderer/tool/impl/element/text.cljs +++ b/src/renderer/tool/impl/element/text.cljs @@ -20,15 +20,16 @@ (defmethod tool.hierarchy/on-pointer-up :text [db _e] - (let [[offset-x offset-y] (or (:nearest-neighbor-offset db) (:adjusted-pointer-offset db)) + (let [[offset-x offset-y] (or (:nearest-neighbor-offset db) + (:adjusted-pointer-offset db)) el {:type :element :tag :text :attrs {:x offset-x :y offset-y}}] (-> (element.handlers/deselect-all db) (element.handlers/add el) - (tool.handlers/activate :edit) - (tool.handlers/set-state :type)))) + (tool.handlers/set-state :type) + (tool.handlers/activate :edit)))) (defmethod tool.hierarchy/on-drag-end :text [db e] diff --git a/src/renderer/tool/impl/extension/blob.cljs b/src/renderer/tool/impl/extension/blob.cljs index 51ea0965..5ae2ec08 100644 --- a/src/renderer/tool/impl/extension/blob.cljs +++ b/src/renderer/tool/impl/extension/blob.cljs @@ -3,6 +3,9 @@ (:require [clojure.core.matrix :as matrix] [renderer.document.handlers :as document.handlers] + [renderer.element.handlers :as element.handlers] + [renderer.element.hierarchy :as element.hierarchy] + [renderer.history.handlers :as history.handlers] [renderer.tool.handlers :as tool.handlers] [renderer.tool.hierarchy :as tool.hierarchy])) @@ -14,30 +17,48 @@ (defn pointer-delta [db] - (matrix/distance (or (:point (:nearest-neighbor db)) (:adjusted-pointer-pos db)) - (or (:nearest-neighbor-offset db) (:adjusted-pointer-offset db)))) + (matrix/distance (or (:point (:nearest-neighbor db)) + (:adjusted-pointer-pos db)) + (or (:nearest-neighbor-offset db) + (:adjusted-pointer-offset db)))) + +(defn attributes + [db] + (let [[offset-x offset-y] (or (:nearest-neighbor-offset db) + (:adjusted-pointer-offset db)) + radius (pointer-delta db)] + {:x (- offset-x radius) + :y (- offset-y radius) + :size (* radius 2)})) (defmethod tool.hierarchy/on-drag-start :blob [db _e] - (let [[offset-x offset-y] (or (:nearest-neighbor-offset db) (:adjusted-pointer-offset db)) - radius (pointer-delta db)] - (tool.handlers/set-temp db {:type :element - :tag :blob - :attrs {:x (- offset-x radius) - :y (- offset-y radius) - :seed (rand-int 1000000) - :extraPoints 8 - :randomness 4 - :size (* radius 2) - :fill (document.handlers/attr db :fill) - :stroke (document.handlers/attr db :stroke)}}))) + (let [fill (document.handlers/attr db :fill) + stroke (document.handlers/attr db :stroke) + seed (rand-int 1000000)] + (-> (tool.handlers/set-state db :create) + (element.handlers/add {:type :element + :tag :blob + :attrs (merge (attributes db) + {:seed seed + :extraPoints 8 + :randomness 4 + :fill fill + :stroke stroke})})))) (defmethod tool.hierarchy/on-drag :blob [db _e] - (let [[offset-x offset-y] (or (:nearest-neighbor-offset db) (:adjusted-pointer-offset db)) - radius (pointer-delta db) - temp (-> (tool.handlers/temp db) - (assoc-in [:attrs :x] (- offset-x radius)) - (assoc-in [:attrs :y] (- offset-y radius)) - (assoc-in [:attrs :size] (* radius 2)))] - (tool.handlers/set-temp db temp))) + (let [attrs (attributes db) + assoc-attr (fn [el [k v]] (assoc-in el [:attrs k] (str v))) + {:keys [id parent]} (first (element.handlers/selected db)) + el (element.handlers/entity db parent) + [min-x min-y] (element.hierarchy/bbox el)] + (-> db + (element.handlers/update-el id #(reduce assoc-attr % attrs)) + (element.handlers/translate [(- min-x) (- min-y)])))) + +(defmethod tool.hierarchy/on-drag-end :blob + [db _e] + (-> db + (history.handlers/finalize "Create blob") + (tool.handlers/activate :transform))) diff --git a/src/renderer/tool/impl/misc/dropper.cljs b/src/renderer/tool/impl/misc/dropper.cljs index 9a7a79a2..41855ffc 100644 --- a/src/renderer/tool/impl/misc/dropper.cljs +++ b/src/renderer/tool/impl/misc/dropper.cljs @@ -25,7 +25,8 @@ (if (.-EyeDropper js/window) (tool.handlers/add-fx db [::effects/eye-dropper {:on-success [::success] :on-error [::error]}]) - (-> (tool.handlers/activate db :transform) + (-> db + (tool.handlers/activate :transform) (notification.handlers/add (notification.views/unavailable-feature "EyeDropper" @@ -35,13 +36,15 @@ ::success (fn [db [_ ^js color]] (let [srgb-color (.-sRGBHex color)] - (-> (document.handlers/assoc-attr db :fill srgb-color) + (-> db + (document.handlers/assoc-attr :fill srgb-color) (element.handlers/assoc-attr :fill srgb-color) - (tool.handlers/activate :transform) - (history.handlers/finalize "Pick color"))))) + (history.handlers/finalize "Pick color") + (tool.handlers/activate :transform))))) (rf/reg-event-db ::error (fn [db [_ error]] - (-> (tool.handlers/activate db :transform) + (-> db + (tool.handlers/activate :transform) (notification.handlers/add (notification.views/exception error))))) diff --git a/src/renderer/tool/impl/misc/measure.cljs b/src/renderer/tool/impl/misc/measure.cljs index 2821b6f6..7e7a54db 100644 --- a/src/renderer/tool/impl/misc/measure.cljs +++ b/src/renderer/tool/impl/misc/measure.cljs @@ -1,12 +1,25 @@ (ns renderer.tool.impl.misc.measure (:require [clojure.core.matrix :as matrix] + [re-frame.core :as rf] + [reagent.core :as reagent] + [renderer.document.subs :as-alias document.subs] [renderer.element.handlers :as element.handlers] [renderer.tool.handlers :as tool.handlers] - [renderer.tool.hierarchy :as tool.hierarchy])) + [renderer.tool.hierarchy :as tool.hierarchy] + [renderer.utils.length :as utils.length] + [renderer.utils.math :as utils.math] + [renderer.utils.svg :as utils.svg])) (derive :measure ::tool.hierarchy/tool) +(defonce attrs (reagent/atom nil)) + +(rf/reg-fx + ::update + (fn [value] + (reset! attrs value))) + (defmethod tool.hierarchy/properties :measure [] {:icon "ruler-triangle"}) @@ -21,28 +34,54 @@ (defmethod tool.hierarchy/on-deactivate :measure [db] - (tool.handlers/dissoc-temp db)) + (tool.handlers/add-fx db [::update nil])) (defmethod tool.hierarchy/on-drag :measure [db _e] - (let [[offset-x offset-y] (or (:nearest-neighbor-offset db) (:adjusted-pointer-offset db)) + (let [[offset-x offset-y] (or (:nearest-neighbor-offset db) + (:adjusted-pointer-offset db)) [x y] (or (:point (:nearest-neighbor db)) (:adjusted-pointer-pos db)) [adjacent opposite] (matrix/sub [offset-x offset-y] [x y]) hypotenuse (Math/hypot adjacent opposite)] - (tool.handlers/set-temp db {:type :element - :tag :measure - :attrs {:x1 offset-x + (tool.handlers/add-fx db [::update {:x1 offset-x :y1 offset-y :x2 x :y2 y :hypotenuse hypotenuse - :stroke "gray"}}))) + :stroke "gray"}]))) + +(defmethod tool.hierarchy/render :measure + [] + (when @attrs + (let [{:keys [x1 x2 y1 y2 hypotenuse]} @attrs + [x1 y1 x2 y2] (map utils.length/unit->px [x1 y1 x2 y2]) + angle (utils.math/angle [x1 y1] [x2 y2]) + zoom @(rf/subscribe [::document.subs/zoom]) + straight? (< angle 180) + straight-angle (if straight? angle (- angle 360))] + [:g + [utils.svg/cross [x1 y1]] + [utils.svg/cross [x2 y2]] + + [utils.svg/arc [x1 y1] 20 (if straight? 0 angle) (abs straight-angle)] + + [utils.svg/line [x1 y1] [x2 y2] false] + [utils.svg/line [x1 y1] [(+ x1 (/ 30 zoom)) y1]] + + [utils.svg/label + (str (.toFixed straight-angle 3) "°") + [(+ x1 (/ 40 zoom)) y1] + "start"] + + [utils.svg/label + (-> hypotenuse js/parseFloat (.toFixed 3) str) + [(/ (+ x1 x2) 2) (/ (+ y1 y2) 2)]]]))) (defmethod tool.hierarchy/snapping-points :measure [db] [(with-meta (:adjusted-pointer-pos db) - {:label (str "measure " (if (tool.handlers/temp db) "end" "start"))})]) + {:label (str "measure " (if @attrs "end" "start"))})]) (defmethod tool.hierarchy/snapping-elements :measure [db] diff --git a/src/renderer/tool/subs.cljs b/src/renderer/tool/subs.cljs index cd19b8b1..7377485c 100644 --- a/src/renderer/tool/subs.cljs +++ b/src/renderer/tool/subs.cljs @@ -8,8 +8,8 @@ :-> :tool) (rf/reg-sub - ::primary - :-> :primary-tool) + ::cached + :-> :cached-tool) (rf/reg-sub ::pivot-point @@ -27,6 +27,10 @@ ::state :-> :state) +(rf/reg-sub + ::cached-state + :-> :cached-state) + (rf/reg-sub ::help :<- [::active] diff --git a/src/renderer/toolbar/status.cljs b/src/renderer/toolbar/status.cljs index f5bcc526..fda97a00 100644 --- a/src/renderer/toolbar/status.cljs +++ b/src/renderer/toolbar/status.cljs @@ -21,11 +21,11 @@ (let [[x y] @(rf/subscribe [::app.subs/adjusted-pointer-pos])] [:div.flex-col.font-mono.leading-tight.hidden {:class "xl:flex" - :style {:min-width "90px"}} + :style {:min-width "100px"}} [:div.flex.justify-between - [:span.mr-1 "X:"] [:span (.toFixed x 2)]] + [:span.mr-1 "X:"] [:span (.toFixed x 3)]] [:div.flex.justify-between - [:span.mr-1 "Y:"] [:span (.toFixed y 2)]]])) + [:span.mr-1 "Y:"] [:span (.toFixed y 3)]]])) (def zoom-options [{:label "Set to 50%" diff --git a/src/renderer/toolbar/tools.cljs b/src/renderer/toolbar/tools.cljs index b3b90e41..fb304976 100644 --- a/src/renderer/toolbar/tools.cljs +++ b/src/renderer/toolbar/tools.cljs @@ -11,9 +11,9 @@ (defn button [tool] (let [active-tool @(rf/subscribe [::tool.subs/active]) - primary-tool @(rf/subscribe [::tool.subs/primary]) + cached-tool @(rf/subscribe [::tool.subs/cached]) active (= active-tool tool) - primary (= primary-tool tool) + primary (= cached-tool tool) properties (tool.hierarchy/properties tool) label (or (:label properties) (string/capitalize (name tool)))] (when (:icon properties) diff --git a/src/renderer/tree/views.cljs b/src/renderer/tree/views.cljs index 2e766689..ec818f0f 100644 --- a/src/renderer/tree/views.cljs +++ b/src/renderer/tree/views.cljs @@ -18,11 +18,12 @@ [renderer.utils.element :as utils.element] [renderer.views :as views])) -(defn toggle-item-prop-button +(defn item-prop-toggle [id state k active-icon inactive-icon active-title inactive-title] [views/icon-button (if state active-icon inactive-icon) - {:class ["hover:bg-transparent text-inherit hover:text-inherit focus:outline-hidden small" + {:class ["hover:bg-transparent text-inherit hover:text-inherit + focus:outline-hidden small" (when-not state "invisible")] :title (if state active-title inactive-title) :on-double-click #(.stopPropagation %) @@ -47,7 +48,8 @@ :default-value label :placeholder tag-label :auto-focus true - :on-key-down #(event.impl.keyboard/input-key-down-handler! % label set-item-label! id) + :on-key-down #(event.impl.keyboard/input-key-down-handler! % label + set-item-label! id) :on-blur (fn [e] (reset! edit-mode? false) (set-item-label! e id))}] @@ -103,7 +105,8 @@ [views/icon-button (if collapsed "chevron-right" "chevron-down") {:title (if collapsed "expand" "collapse") - :class "hover:bg-transparent text-inherit hover:text-inherit focus:outline-hidden small" + :class "hover:bg-transparent text-inherit hover:text-inherit + focus:outline-hidden small" :on-double-click #(.stopPropagation %) :on-click #(do (.stopPropagation %) (rf/dispatch (if collapsed @@ -115,8 +118,8 @@ (let [{:keys [id selected children locked visible]} el collapse-button-width 21 padding (* collapse-button-width (cond-> depth (seq children) dec))] - [:div.list-item-button.button.flex.pr-1.items-center.text-start.outline-default.hover:overlay - {:class ["[&.hovered]:overlay hover:[&_button]:visible" + [:div.list-item-button.button.flex.pr-1.items-center.text-start.outline-default + {:class ["hover:overlay [&.hovered]:overlay hover:[&_button]:visible" (when selected "accent") (when hovered "hovered")] :tab-index 0 @@ -150,8 +153,8 @@ (when-let [icon (:icon (utils.element/properties el))] [views/icon icon {:class (when-not visible "opacity-60")}]) [item-label el]] - [toggle-item-prop-button id locked :locked "lock" "unlock" "unlock" "lock"] - [toggle-item-prop-button id (not visible) :visible "eye-closed" "eye" "show" "hide"]]])) + [item-prop-toggle id locked :locked "lock" "unlock" "unlock" "lock"] + [item-prop-toggle id (not visible) :visible "eye-closed" "eye" "show" "hide"]]])) (defn item [el depth elements] (let [{:keys [selected children id]} el diff --git a/src/renderer/utils/element.cljs b/src/renderer/utils/element.cljs index afa42819..0132ea51 100644 --- a/src/renderer/utils/element.cljs +++ b/src/renderer/utils/element.cljs @@ -95,14 +95,15 @@ ([el] (->path el (element.hierarchy/path el))) ([el d] - (cond - (string? d) - (-> (assoc el :tag :path) - (update :attrs #(utils.map/merge-common-with str % (utils.attribute/defaults-memo :path))) - (assoc-in [:attrs :d] d)) + (let [default-attrs (utils.attribute/defaults-memo :path)] + (cond + (string? d) + (-> (assoc el :tag :path) + (update :attrs #(utils.map/merge-common-with str % default-attrs)) + (assoc-in [:attrs :d] d)) - (instance? js/Promise d) - (.then d (fn [d] (->path el d)))))) + (instance? js/Promise d) + (.then d (fn [d] (->path el d))))))) (m/=> stroke->path [:-> Element Element]) (defn stroke->path @@ -114,10 +115,11 @@ (/ el-offset 2) #js {:cap (or (:stroke-linecap attrs) "butt") :join (or (:stroke-linejoin attrs) "miter")}) - new-d (.getAttribute (.exportSVG stroke-path) "d")] + new-d (.getAttribute (.exportSVG stroke-path) "d") + default-attrs (utils.attribute/defaults-memo :path)] (-> (assoc el :tag :path) (update :attrs dissoc :stroke :stroke-width) - (update :attrs #(utils.map/merge-common-with str % (utils.attribute/defaults-memo :path))) + (update :attrs #(utils.map/merge-common-with str % default-attrs)) (assoc-in [:attrs :d] new-d) (assoc-in [:attrs :fill] (:stroke attrs))))) diff --git a/src/renderer/utils/length.cljs b/src/renderer/utils/length.cljs index e425501c..6b81297b 100644 --- a/src/renderer/utils/length.cljs +++ b/src/renderer/utils/length.cljs @@ -60,7 +60,7 @@ ([v f & more] (let [[n unit] (utils.unit/parse v)] (-> (apply f (->px n unit) more) - (.toFixed 2) + (.toFixed 3) (js/parseFloat) (->unit unit) (str (when (valid-unit? unit) unit)))))) diff --git a/src/renderer/window/views.cljs b/src/renderer/window/views.cljs index 116e421c..5da150df 100644 --- a/src/renderer/window/views.cljs +++ b/src/renderer/window/views.cljs @@ -50,8 +50,8 @@ [:div.flex.relative.bg-secondary {:class (when (and mac? (not fullscreen?)) "ml-16")} [menubar.views/root]] - [:div.absolute.hidden.justify-center.drag.grow.h-full.items-center.pointer-events-none - {:class "md:flex left-1/2 -translate-x-1/2" + [:div.absolute.hidden.justify-center.drag.grow.h-full.items-center + {:class "pointer-events-none md:flex left-1/2 -translate-x-1/2" :style {:z-index -1}} @(rf/subscribe [::document.subs/title-bar])] [:div.flex.h-full.flex-1.drag] diff --git a/src/renderer/worker/effects.cljs b/src/renderer/worker/effects.cljs index cff8d640..06d65e67 100644 --- a/src/renderer/worker/effects.cljs +++ b/src/renderer/worker/effects.cljs @@ -14,6 +14,8 @@ #(let [response-data (js->clj (.. % -data) :keywordize-keys true)] (rf/dispatch [::worker.events/message id on-success response-data]))) - (.addEventListener worker "error" #(rf/dispatch [::worker.events/message id on-error %])) + (.addEventListener worker + "error" + #(rf/dispatch [::worker.events/message id on-error %])) (.postMessage worker (clj->js data))))) diff --git a/test/document_test.cljs b/test/document_test.cljs index b38706ed..bfe63697 100644 --- a/test/document_test.cljs +++ b/test/document_test.cljs @@ -12,147 +12,150 @@ (rf.test/run-test-sync (rf/dispatch [::app.events/initialize-db]) - (testing "defaults" - (is (not @(rf/subscribe [::document.subs/entities?]))) - (is (not @(rf/subscribe [::document.subs/active])))) - - (testing "initialization" - (rf/dispatch [::document.events/init]) - (is @(rf/subscribe [::document.subs/entities?])) - (is (document.db/valid? @(rf/subscribe [::document.subs/active]))) - (is (= "• Untitled-1 - Repath Studio" @(rf/subscribe [::document.subs/title-bar])))) - - (testing "close" - (rf/dispatch [::document.events/close @(rf/subscribe [::document.subs/active-id]) false]) - (is (not @(rf/subscribe [::document.subs/active])))) - - (testing "close active" - (rf/dispatch [::document.events/new]) - (rf/dispatch [::document.events/saved @(rf/subscribe [::document.subs/active])]) - (rf/dispatch [::document.events/close-active]) - (is (not @(rf/subscribe [::document.subs/active])))) - - (testing "close saved" - (rf/dispatch [::document.events/new]) - (rf/dispatch [::document.events/new]) - (rf/dispatch [::document.events/saved @(rf/subscribe [::document.subs/active])]) - (rf/dispatch [::document.events/close-saved]) - (is (= (count @(rf/subscribe [::document.subs/entities])) 1))) - - (testing "close all" - (rf/dispatch [::document.events/saved @(rf/subscribe [::document.subs/active])]) - (rf/dispatch [::document.events/close-all]) - (is (not @(rf/subscribe [::document.subs/active])))) - - (testing "create" - (rf/dispatch [::document.events/new]) - (is (= "• Untitled-1 - Repath Studio" @(rf/subscribe [::document.subs/title-bar]))) - - (rf/dispatch [::document.events/new-from-template [800 600]]) - (is (= "• Untitled-2 - Repath Studio" @(rf/subscribe [::document.subs/title-bar]))) - (is (= "800" (->> @(rf/subscribe [::document.subs/elements]) - (vals) - (filter #(= (:tag %) :svg)) - (first) - :attrs - :width)))) - - (testing "colors" - (let [fill (rf/subscribe [::document.subs/fill]) - stroke (rf/subscribe [::document.subs/stroke])] - (testing "default color values" - (is (= @fill "white")) - (is (= @stroke "black"))) - - (testing "swap colors" - (rf/dispatch [::document.events/swap-colors]) - (is (= @fill "black")) - (is (= @stroke "white"))) - - (testing "set fill" - (rf/dispatch [::document.events/set-attr :fill "red"]) - (is (= @fill "red"))) - - (testing "set stroke" - (rf/dispatch [::document.events/set-attr :stroke "yellow"]) - (is (= @stroke "yellow"))))) - - (testing "filters" - (let [active-filter (rf/subscribe [::document.subs/filter])] + (let [document-entities? (rf/subscribe [::document.subs/entities?]) + active-document (rf/subscribe [::document.subs/active]) + saved? (rf/subscribe [::document.subs/active-saved?]) + title-bar (rf/subscribe [::document.subs/title-bar]) + active-id (rf/subscribe [::document.subs/active-id])] + (testing "defaults" + (is (not @document-entities?)) + (is (not @active-document))) + + (testing "initialization" + (rf/dispatch [::document.events/init]) + (is @document-entities?) + (is (document.db/valid? @active-document)) + (is (= "• Untitled-1 - Repath Studio" @title-bar))) + + (testing "close" + (rf/dispatch [::document.events/close @active-id false]) + (is (not @active-document))) + + (testing "close active" + (rf/dispatch [::document.events/new]) + (rf/dispatch [::document.events/saved @active-document]) + (rf/dispatch [::document.events/close-active]) + (is (not @active-document))) + + (testing "close saved" + (rf/dispatch [::document.events/new]) + (rf/dispatch [::document.events/new]) + (rf/dispatch [::document.events/saved @active-document]) + (rf/dispatch [::document.events/close-saved]) + (is (= (count @(rf/subscribe [::document.subs/entities])) 1))) + + (testing "close all" + (rf/dispatch [::document.events/saved @active-document]) + (rf/dispatch [::document.events/close-all]) + (is (not @active-document))) + + (testing "create" + (rf/dispatch [::document.events/new]) + (is (= "• Untitled-1 - Repath Studio" @title-bar)) + + (rf/dispatch [::document.events/new-from-template [800 600]]) + (is (= "• Untitled-2 - Repath Studio" @title-bar)) + (is (= "800" (->> @(rf/subscribe [::document.subs/elements]) + (vals) + (filter #(= (:tag %) :svg)) + (first) + :attrs + :width)))) + + (testing "colors" + (let [fill (rf/subscribe [::document.subs/fill]) + stroke (rf/subscribe [::document.subs/stroke])] + (testing "default color values" + (is (= @fill "white")) + (is (= @stroke "black"))) + + (testing "swap colors" + (rf/dispatch [::document.events/swap-colors]) + (is (= @fill "black")) + (is (= @stroke "white"))) + + (testing "set fill" + (rf/dispatch [::document.events/set-attr :fill "red"]) + (is (= @fill "red"))) + + (testing "set stroke" + (rf/dispatch [::document.events/set-attr :stroke "yellow"]) + (is (= @stroke "yellow"))))) + + (testing "filters" + (let [active-filter (rf/subscribe [::document.subs/filter])] + (testing "default state" + (is (not @active-filter))) + + (testing "enable filter" + (rf/dispatch [::document.events/toggle-filter :blur]) + (is (= @active-filter :blur))) + + (testing "change active filter" + (rf/dispatch [::document.events/toggle-filter :deuteranopia]) + (is (= @active-filter :deuteranopia))) + + (testing "disable filter" + (rf/dispatch [::document.events/toggle-filter :deuteranopia]) + (is (not @active-filter))))) + + (testing "collapse/expand elements" + (let [collapsed-ids (rf/subscribe [::document.subs/collapsed-ids]) + id (random-uuid)] + (testing "default state" + (is (empty? @collapsed-ids))) + + (testing "collapse" + (rf/dispatch [::document.events/collapse-el id]) + (is (= #{id} @collapsed-ids))) + + (testing "expand" + (rf/dispatch [::document.events/expand-el id]) + (is (empty? @collapsed-ids))))) + + (testing "hover elements" + (let [hovered-ids (rf/subscribe [::document.subs/hovered-ids]) + id (random-uuid)] + (testing "default state" + (is (empty? @hovered-ids))) + + (testing "hover" + (rf/dispatch [::document.events/set-hovered-id id]) + (is (= #{id} @hovered-ids))) + + (testing "clear hovered" + (rf/dispatch [::document.events/clear-hovered]) + (is (empty? @hovered-ids))))) + + (testing "save" (testing "default state" - (is (not @active-filter))) - - (testing "enable filter" - (rf/dispatch [::document.events/toggle-filter :blur]) - (is (= @active-filter :blur))) - - (testing "change active filter" - (rf/dispatch [::document.events/toggle-filter :deuteranopia]) - (is (= @active-filter :deuteranopia))) - - (testing "disable filter" - (rf/dispatch [::document.events/toggle-filter :deuteranopia]) - (is (not @active-filter))))) - - (testing "collapse/expand elements" - (let [collapsed-ids (rf/subscribe [::document.subs/collapsed-ids]) - id (random-uuid)] - (testing "default state" - (is (empty? @collapsed-ids))) - - (testing "collapse" - (rf/dispatch [::document.events/collapse-el id]) - (is (= #{id} @collapsed-ids))) - - (testing "expand" - (rf/dispatch [::document.events/expand-el id]) - (is (empty? @collapsed-ids))))) - - (testing "hover elements" - (let [hovered-ids (rf/subscribe [::document.subs/hovered-ids]) - id (random-uuid)] - (testing "default state" - (is (empty? @hovered-ids))) - - (testing "hover" - (rf/dispatch [::document.events/set-hovered-id id]) - (is (= #{id} @hovered-ids))) - - (testing "clear hovered" - (rf/dispatch [::document.events/clear-hovered]) - (is (empty? @hovered-ids))))) - - (testing "save" - (let [saved (rf/subscribe [::document.subs/active-saved?]) - active-document (rf/subscribe [::document.subs/active]) - id (:id @active-document)] - (testing "default state" - (is (not @saved))) + (is (not @saved?))) (testing "save" (rf/dispatch [::document.events/saved @active-document]) - (is @saved) - (is @(rf/subscribe [::document.subs/saved? id]))))) - - (testing "load" - (rf/dispatch [::document.events/load {:version "100000.0.0" ; Skips migrations. - :path "foo/bar/document.rps" - :title "document.rps" - :elements {}}]) - - (is @(rf/subscribe [::document.subs/active-saved?])) - (is (= "foo/bar/document.rps - Repath Studio" @(rf/subscribe [::document.subs/title-bar])))) - - (testing "load multiple" - (rf/dispatch [::document.events/load-multiple [{:version "100000.0.0" - :path "foo/bar/document-1.rps" - :title "document-1.rps" - :elements {}} - {:version "100000.0.0" - :path "foo/bar/document-2.rps" - :title "document-2.rps" - :elements {}}]]) - - (is (= (:title @(rf/subscribe [::document.subs/active])) "document-2.rps")) - (is (= (take 2 @(rf/subscribe [::document.subs/recent])) ["foo/bar/document-2.rps" - "foo/bar/document-1.rps"]))))) + (is @saved?) + (is @(rf/subscribe [::document.subs/saved? (:id @active-document)])))) + + (testing "load" + (rf/dispatch [::document.events/load {:version "100000.0.0" ; Skips migrations. + :path "foo/bar/document.rps" + :title "document.rps" + :elements {}}]) + + (is @(rf/subscribe [::document.subs/active-saved?])) + (is (= "foo/bar/document.rps - Repath Studio" @title-bar))) + + (testing "load multiple" + (let [recent-documents (rf/subscribe [::document.subs/recent])] + (rf/dispatch [::document.events/load-multiple [{:version "100000.0.0" + :path "foo/bar/document-1.rps" + :title "document-1.rps" + :elements {}} + {:version "100000.0.0" + :path "foo/bar/document-2.rps" + :title "document-2.rps" + :elements {}}]]) + + (is (= (:title @active-document) "document-2.rps")) + (is (= (take 2 @recent-documents) ["foo/bar/document-2.rps" + "foo/bar/document-1.rps"])))))))