diff --git a/common/src/app/common/data.cljc b/common/src/app/common/data.cljc index 2fd7e27c38..eb74bd3a4b 100644 --- a/common/src/app/common/data.cljc +++ b/common/src/app/common/data.cljc @@ -406,15 +406,24 @@ [v default] (if (some? v) v default)) +(defn num? + "Checks if a value `val` is a number but not an Infinite or NaN" + ([val] + (and (number? val) + (mth/finite? val) + (not (mth/nan? val)))) + + ([val & vals] + (and (num? val) + (->> vals (every? num?))))) + (defn check-num "Function that checks if a number is nil or nan. Will return 0 when not valid and the number otherwise." ([v] (check-num v 0)) ([v default] - (if (or (not v) - (not (mth/finite? v)) - (mth/nan? v)) default v))) + (if (num? v) v default))) (defn any-key? [element & rest] (some #(contains? element %) rest)) diff --git a/common/src/app/common/file_builder.cljc b/common/src/app/common/file_builder.cljc index d58abedd79..eb73bde26c 100644 --- a/common/src/app/common/file_builder.cljc +++ b/common/src/app/common/file_builder.cljc @@ -21,6 +21,11 @@ (def conjv (fnil conj [])) (def conjs (fnil conj #{})) +(defn- raise + [err-str] + #?(:clj (throw (Exception. err-str)) + :cljs (throw (js/Error. err-str)))) + (defn- commit-change ([file change] (commit-change file change nil)) @@ -75,10 +80,12 @@ (commit-change file change {:add-container? true :fail-on-spec? fail-on-spec?}))) -(defn setup-rect-selrect [obj] - (let [rect (select-keys obj [:x :y :width :height]) +(defn setup-rect-selrect [{:keys [x y width height transform] :as obj}] + (when-not (d/num? x y width height) + (raise "Coords not valid for object")) + + (let [rect (gsh/make-rect x y width height) center (gsh/center-rect rect) - transform (:transform obj (gmt/matrix)) selrect (gsh/rect->selrect rect) points (-> (gsh/rect->points rect) @@ -89,17 +96,13 @@ (assoc :points points)))) (defn- setup-path-selrect - [obj] - (let [content (:content obj) - center (:center obj) + [{:keys [content center transform transform-inverse] :as obj}] - transform-inverse - (->> (:transform-inverse obj (gmt/matrix)) - (gmt/transform-in center)) + (when (or (empty? content) (nil? center)) + (raise "Path not valid")) - transform - (->> (:transform obj (gmt/matrix)) - (gmt/transform-in center)) + (let [transform (gmt/transform-in center transform) + transform-inverse (gmt/transform-in center transform-inverse) content' (gsh/transform-content content transform-inverse) selrect (gsh/content->selrect content') @@ -310,21 +313,30 @@ children (->> bool :shapes (mapv #(lookup-shape file %))) file - (let [objects (lookup-objects file) - bool' (gsh/update-bool-selrect bool children objects)] + (cond + (empty? children) (commit-change file - {:type :mod-obj - :id bool-id - :operations - [{:type :set :attr :selrect :val (:selrect bool')} - {:type :set :attr :points :val (:points bool')} - {:type :set :attr :x :val (-> bool' :selrect :x)} - {:type :set :attr :y :val (-> bool' :selrect :y)} - {:type :set :attr :width :val (-> bool' :selrect :width)} - {:type :set :attr :height :val (-> bool' :selrect :height)}]} + {:type :del-obj + :id bool-id} + {:add-container? true}) - {:add-container? true}))] + :else + (let [objects (lookup-objects file) + bool' (gsh/update-bool-selrect bool children objects)] + (commit-change + file + {:type :mod-obj + :id bool-id + :operations + [{:type :set :attr :selrect :val (:selrect bool')} + {:type :set :attr :points :val (:points bool')} + {:type :set :attr :x :val (-> bool' :selrect :x)} + {:type :set :attr :y :val (-> bool' :selrect :y)} + {:type :set :attr :width :val (-> bool' :selrect :width)} + {:type :set :attr :height :val (-> bool' :selrect :height)}]} + + {:add-container? true})))] (-> file (update :parent-stack pop)))) diff --git a/common/src/app/common/geom/matrix.cljc b/common/src/app/common/geom/matrix.cljc index ed9d8bf0f8..f95726a160 100644 --- a/common/src/app/common/geom/matrix.cljc +++ b/common/src/app/common/geom/matrix.cljc @@ -108,9 +108,12 @@ (= v base)) (defn translate-matrix - [{x :x y :y :as pt}] - (assert (gpt/point? pt)) - (Matrix. 1 0 0 1 x y)) + ([{x :x y :y :as pt}] + (assert (gpt/point? pt)) + (Matrix. 1 0 0 1 x y)) + + ([x y] + (translate-matrix (gpt/point x y)))) (defn scale-matrix ([pt center] @@ -184,7 +187,7 @@ (defmethod pp/simple-dispatch Matrix [obj] (pr obj)) (defn transform-in [pt mtx] - (if (some? pt) + (if (and (some? pt) (some? mtx)) (-> (matrix) (translate pt) (multiply mtx) diff --git a/common/src/app/common/geom/point.cljc b/common/src/app/common/geom/point.cljc index c2b81b2482..f79743f7a8 100644 --- a/common/src/app/common/geom/point.cljc +++ b/common/src/app/common/geom/point.cljc @@ -132,9 +132,8 @@ (assert (point? other)) (let [dx (- x ox) dy (- y oy)] - (-> (mth/sqrt (+ (mth/pow dx 2) - (mth/pow dy 2))) - (mth/precision 6)))) + (mth/sqrt (+ (mth/pow dx 2) + (mth/pow dy 2))))) (defn length [{x :x y :y :as p}] @@ -168,8 +167,7 @@ (* y oy)) (* length-p length-other)) a (mth/acos (if (< a -1) -1 (if (> a 1) 1 a))) - d (-> (mth/degrees a) - (mth/precision 6))] + d (mth/degrees a)] (if (mth/nan? d) 0 d))))) (defn angle-sign [v1 v2] @@ -194,14 +192,23 @@ (if (>= y 0) 2 3))) (defn round - "Change the precision of the point coordinates." - ([point] (round point 0)) + "Round the coordinates of the point to a precision" + ([point] + (round point 0)) + ([{:keys [x y] :as p} decimals] (assert (point? p)) (assert (number? decimals)) (Point. (mth/precision x decimals) (mth/precision y decimals)))) +(defn half-round + "Round the coordinates to the closest half-point" + [{:keys [x y] :as p}] + (assert (point? p)) + (Point. (mth/half-round x) + (mth/half-round y))) + (defn transform "Transform a point applying a matrix transformation." [{:keys [x y] :as p} {:keys [a b c d e f]}] diff --git a/common/src/app/common/geom/shapes.cljc b/common/src/app/common/geom/shapes.cljc index 412e0b44fb..75fe17862a 100644 --- a/common/src/app/common/geom/shapes.cljc +++ b/common/src/app/common/geom/shapes.cljc @@ -17,39 +17,6 @@ [app.common.geom.shapes.transforms :as gtr] [app.common.math :as mth])) -;; --- Setup (Initialize) -;; FIXME: Is this the correct place for these functions? - -(defn- setup-rect - "A specialized function for setup rect-like shapes." - [shape {:keys [x y width height]}] - (let [rect {:x x :y y :width width :height height} - points (gpr/rect->points rect) - selrect (gpr/points->selrect points)] - (assoc shape - :x x - :y y - :width width - :height height - :points points - :selrect selrect))) - -(defn- setup-image - [{:keys [metadata] :as shape} props] - (-> (setup-rect shape props) - (assoc - :proportion (/ (:width metadata) - (:height metadata)) - :proportion-lock true))) - -(defn setup - "A function that initializes the first coordinates for - the shape. Used mainly for draw operations." - [shape props] - (case (:type shape) - :image (setup-image shape props) - (setup-rect shape props))) - ;; --- Outer Rect (defn selection-rect @@ -106,12 +73,12 @@ :width (- x2 x1) :height (- y2 y1) :type :rect})) - {frame-x1 :x1 frame-x2 :x2 frame-y1 :y1 frame-y2 :y2} bounds + {bound-x1 :x1 bound-x2 :x2 bound-y1 :y1 bound-y2 :y2} bounds {sr-x1 :x1 sr-x2 :x2 sr-y1 :y1 sr-y2 :y2} selrect] - {:left (make-selrect frame-x1 sr-y1 (- sr-x1 2) sr-y2) - :top (make-selrect sr-x1 frame-y1 sr-x2 (- sr-y1 2)) - :right (make-selrect (+ sr-x2 2) sr-y1 frame-x2 sr-y2) - :bottom (make-selrect sr-x1 (+ sr-y2 2) sr-x2 frame-y2)})) + {:left (make-selrect bound-x1 sr-y1 sr-x1 sr-y2) + :top (make-selrect sr-x1 bound-y1 sr-x2 sr-y1) + :right (make-selrect sr-x2 sr-y1 bound-x2 sr-y2) + :bottom (make-selrect sr-x1 sr-y2 sr-x2 bound-y2)})) (defn distance-selrect [selrect other] (let [{:keys [x1 y1]} other @@ -121,13 +88,6 @@ (defn distance-shapes [shape other] (distance-selrect (:selrect shape) (:selrect other))) -(defn setup-selrect [shape] - (let [selrect (gpr/rect->selrect shape) - points (gpr/rect->points shape)] - (-> shape - (assoc :selrect selrect - :points points)))) - (defn shape-stroke-margin [shape stroke-width] (if (= (:type shape) :path) @@ -141,15 +101,17 @@ (dm/export gco/center-selrect) (dm/export gco/center-rect) (dm/export gco/center-points) -(dm/export gco/make-centered-rect) (dm/export gco/transform-points) +(dm/export gpr/make-rect) (dm/export gpr/rect->selrect) (dm/export gpr/rect->points) (dm/export gpr/points->selrect) (dm/export gpr/points->rect) (dm/export gpr/center->rect) +(dm/export gpr/center->selrect) (dm/export gpr/join-rects) +(dm/export gpr/join-selrects) (dm/export gpr/contains-selrect?) (dm/export gtr/move) @@ -166,6 +128,7 @@ (dm/export gtr/merge-modifiers) (dm/export gtr/transform-shape) (dm/export gtr/transform-selrect) +(dm/export gtr/transform-bounds) (dm/export gtr/modifiers->transform) (dm/export gtr/empty-modifiers?) diff --git a/common/src/app/common/geom/shapes/bool.cljc b/common/src/app/common/geom/shapes/bool.cljc index 4d5bdb4010..a71fc52c9c 100644 --- a/common/src/app/common/geom/shapes/bool.cljc +++ b/common/src/app/common/geom/shapes/bool.cljc @@ -8,7 +8,6 @@ (:require [app.common.data :as d] [app.common.geom.shapes.path :as gsp] - [app.common.geom.shapes.rect :as gpr] [app.common.geom.shapes.transforms :as gtr] [app.common.path.bool :as pb] [app.common.path.shapes-to-path :as stp])) @@ -30,15 +29,13 @@ "Calculates the selrect+points for the boolean shape" [shape children objects] - (let [content (calc-bool-content shape objects) - [points selrect] - (if (empty? content) - (let [selrect (gtr/selection-rect children) - points (gpr/rect->points selrect)] - [points selrect]) - (gsp/content->points+selrect shape content))] - (-> shape - (assoc :selrect selrect) - (assoc :points points) - (assoc :bool-content content)))) + (let [bool-content (calc-bool-content shape objects) + shape (assoc shape :bool-content bool-content) + [points selrect] (gsp/content->points+selrect shape bool-content)] + + (if (and (some? selrect) (d/not-empty? points)) + (-> shape + (assoc :selrect selrect) + (assoc :points points)) + (gtr/update-group-selrect shape children)))) diff --git a/common/src/app/common/geom/shapes/common.cljc b/common/src/app/common/geom/shapes/common.cljc index 0cd9d4704a..4c85437ec3 100644 --- a/common/src/app/common/geom/shapes/common.cljc +++ b/common/src/app/common/geom/shapes/common.cljc @@ -6,30 +6,24 @@ (ns app.common.geom.shapes.common (:require + [app.common.data :as d] [app.common.geom.matrix :as gmt] - [app.common.geom.point :as gpt] - [app.common.math :as mth])) + [app.common.geom.point :as gpt])) (defn center-rect [{:keys [x y width height]}] - (when (and (mth/finite? x) - (mth/finite? y) - (mth/finite? width) - (mth/finite? height)) + (when (d/num? x y width height) (gpt/point (+ x (/ width 2.0)) (+ y (/ height 2.0))))) (defn center-selrect - "Calculate the center of the shape." + "Calculate the center of the selrect." [selrect] (center-rect selrect)) -(def map-x-xf (comp (map :x) (remove nil?))) -(def map-y-xf (comp (map :y) (remove nil?))) - (defn center-points [points] - (let [ptx (into [] map-x-xf points) - pty (into [] map-y-xf points) + (let [ptx (into [] (keep :x) points) + pty (into [] (keep :y) points) minx (reduce min ##Inf ptx) miny (reduce min ##Inf pty) maxx (reduce max ##-Inf ptx) @@ -42,35 +36,12 @@ [shape] (center-rect (:selrect shape))) -(defn make-centered-rect - "Creates a rect given a center and a width and height" - [center width height] - {:x (- (:x center) (/ width 2.0)) - :y (- (:y center) (/ height 2.0)) - :width width - :height height}) - -(defn make-centered-selrect - "Creates a rect given a center and a width and height" - [center width height] - (let [x1 (- (:x center) (/ width 2.0)) - y1 (- (:y center) (/ height 2.0)) - x2 (+ x1 width) - y2 (+ y1 height)] - {:x x1 - :y y1 - :x1 x1 - :x2 x2 - :y1 y1 - :y2 y2 - :width width - :height height})) - (defn transform-points ([points matrix] (transform-points points nil matrix)) + ([points center matrix] - (if (some? matrix) + (if (and (d/not-empty? points) (gmt/matrix? matrix)) (let [prev (if center (gmt/translate-matrix center) (gmt/matrix)) post (if center (gmt/translate-matrix (gpt/negate center)) (gmt/matrix)) diff --git a/common/src/app/common/geom/shapes/path.cljc b/common/src/app/common/geom/shapes/path.cljc index 38d2e56807..b1922095a9 100644 --- a/common/src/app/common/geom/shapes/path.cljc +++ b/common/src/app/common/geom/shapes/path.cljc @@ -333,11 +333,8 @@ (command->point command :c2)]] (->> (curve-extremities curve) (mapv #(curve-values curve %))))) - []) - selrect (gpr/points->selrect points)] - (-> selrect - (update :width #(if (mth/almost-zero? %) 1 %)) - (update :height #(if (mth/almost-zero? %) 1 %)))))) + [])] + (gpr/points->selrect points)))) (defn content->selrect [content] (let [calc-extremities @@ -362,13 +359,8 @@ extremities (mapcat calc-extremities content - (concat [nil] content)) - - selrect (gpr/points->selrect extremities)] - - (-> selrect - (update :width #(if (mth/almost-zero? %) 1 %)) - (update :height #(if (mth/almost-zero? %) 1 %))))) + (concat [nil] content))] + (gpr/points->selrect extremities))) (defn move-content [content move-vec] (let [dx (:x move-vec) @@ -376,40 +368,49 @@ set-tr (fn [params px py] - (-> params - (update px + dx) - (update py + dy))) + (cond-> params + (d/num? dx) + (update px + dx) + + (d/num? dy) + (update py + dy))) transform-params - (fn [{:keys [x c1x c2x] :as params}] + (fn [{:keys [x y c1x c1y c2x c2y] :as params}] (cond-> params - (some? x) (set-tr :x :y) - (some? c1x) (set-tr :c1x :c1y) - (some? c2x) (set-tr :c2x :c2y)))] + (d/num? x y) (set-tr :x :y) + (d/num? c1x c1y) (set-tr :c1x :c1y) + (d/num? c2x c2y) (set-tr :c2x :c2y))) - (into [] - (map #(update % :params transform-params)) - content))) + update-command + (fn [command] + (update command :params transform-params))] + + (->> content + (into [] (map update-command))))) (defn transform-content [content transform] - (let [set-tr (fn [params px py] - (let [tr-point (-> (gpt/point (get params px) (get params py)) - (gpt/transform transform))] - (assoc params - px (:x tr-point) - py (:y tr-point)))) + (if (some? transform) + (let [set-tr + (fn [params px py] + (let [tr-point (-> (gpt/point (get params px) (get params py)) + (gpt/transform transform))] + (assoc params + px (:x tr-point) + py (:y tr-point)))) - transform-params - (fn [{:keys [x c1x c2x] :as params}] - (cond-> params - (some? x) (set-tr :x :y) - (some? c1x) (set-tr :c1x :c1y) - (some? c2x) (set-tr :c2x :c2y)))] + transform-params + (fn [{:keys [x c1x c2x] :as params}] + (cond-> params + (some? x) (set-tr :x :y) + (some? c1x) (set-tr :c1x :c1y) + (some? c2x) (set-tr :c2x :c2y)))] - (into [] - (map #(update % :params transform-params)) - content))) + (into [] + (map #(update % :params transform-params)) + content)) + content)) (defn segments->content ([segments] @@ -980,7 +981,6 @@ (gpr/points->selrect))] [points selrect])) - (defn open-path? [shape] diff --git a/common/src/app/common/geom/shapes/rect.cljc b/common/src/app/common/geom/shapes/rect.cljc index 740c91677b..0d36ad4a71 100644 --- a/common/src/app/common/geom/shapes/rect.cljc +++ b/common/src/app/common/geom/shapes/rect.cljc @@ -6,81 +6,101 @@ (ns app.common.geom.shapes.rect (:require + [app.common.data :as d] [app.common.geom.point :as gpt] - [app.common.geom.shapes.common :as gco] [app.common.math :as mth])) +(defn make-rect + [x y width height] + (when (d/num? x y width height) + (let [width (max width 0.01) + height (max height 0.01)] + {:x x + :y y + :width width + :height height}))) + +(defn make-selrect + [x y width height] + (when (d/num? x y width height) + (let [width (max width 0.01) + height (max height 0.01)] + {:x x + :y y + :x1 x + :y1 y + :x2 (+ x width) + :y2 (+ y height) + :width width + :height height}))) + (defn rect->points [{:keys [x y width height]}] - ;; (assert (number? x)) - ;; (assert (number? y)) - ;; (assert (and (number? width) (> width 0))) - ;; (assert (and (number? height) (> height 0))) - [(gpt/point x y) - (gpt/point (+ x width) y) - (gpt/point (+ x width) (+ y height)) - (gpt/point x (+ y height))]) + (when (d/num? x y) + (let [width (max width 0.01) + height (max height 0.01)] + [(gpt/point x y) + (gpt/point (+ x width) y) + (gpt/point (+ x width) (+ y height)) + (gpt/point x (+ y height))]))) (defn rect->lines [{:keys [x y width height]}] - [[(gpt/point x y) (gpt/point (+ x width) y)] - [(gpt/point (+ x width) y) (gpt/point (+ x width) (+ y height))] - [(gpt/point (+ x width) (+ y height)) (gpt/point x (+ y height))] - [(gpt/point x (+ y height)) (gpt/point x y)]]) + (when (d/num? x y) + (let [width (max width 0.01) + height (max height 0.01)] + [[(gpt/point x y) (gpt/point (+ x width) y)] + [(gpt/point (+ x width) y) (gpt/point (+ x width) (+ y height))] + [(gpt/point (+ x width) (+ y height)) (gpt/point x (+ y height))] + [(gpt/point x (+ y height)) (gpt/point x y)]]))) (defn points->rect [points] - (let [minx (transduce gco/map-x-xf min ##Inf points) - miny (transduce gco/map-y-xf min ##Inf points) - maxx (transduce gco/map-x-xf max ##-Inf points) - maxy (transduce gco/map-y-xf max ##-Inf points)] - {:x minx - :y miny - :width (- maxx minx) - :height (- maxy miny)})) + (when (d/not-empty? points) + (let [minx (transduce (keep :x) min ##Inf points) + miny (transduce (keep :y) min ##Inf points) + maxx (transduce (keep :x) max ##-Inf points) + maxy (transduce (keep :y) max ##-Inf points)] + (when (d/num? minx miny maxx maxy) + (make-rect minx miny (- maxx minx) (- maxy miny)))))) (defn points->selrect [points] - (let [{:keys [x y width height] :as rect} (points->rect points)] - (assoc rect - :x1 x - :x2 (+ x width) - :y1 y - :y2 (+ y height)))) + (when-let [rect (points->rect points)] + (let [{:keys [x y width height]} rect] + (make-selrect x y width height)))) (defn rect->selrect [rect] (-> rect rect->points points->selrect)) (defn join-rects [rects] - (let [minx (transduce (comp (map :x) (remove nil?)) min ##Inf rects) - miny (transduce (comp (map :y) (remove nil?)) min ##Inf rects) - maxx (transduce (comp (map #(+ (:x %) (:width %))) (remove nil?)) max ##-Inf rects) - maxy (transduce (comp (map #(+ (:y %) (:height %))) (remove nil?)) max ##-Inf rects)] - {:x minx - :y miny - :width (- maxx minx) - :height (- maxy miny)})) + (when (d/not-empty? rects) + (let [minx (transduce (keep :x) min ##Inf rects) + miny (transduce (keep :y) min ##Inf rects) + maxx (transduce (keep #(when (and (:x %) (:width %)) (+ (:x %) (:width %)))) max ##-Inf rects) + maxy (transduce (keep #(when (and (:y %) (:height %))(+ (:y %) (:height %)))) max ##-Inf rects)] + (when (d/num? minx miny maxx maxy) + (make-rect minx miny (- maxx minx) (- maxy miny)))))) (defn join-selrects [selrects] - (let [minx (transduce (comp (map :x1) (remove nil?)) min ##Inf selrects) - miny (transduce (comp (map :y1) (remove nil?)) min ##Inf selrects) - maxx (transduce (comp (map :x2) (remove nil?)) max ##-Inf selrects) - maxy (transduce (comp (map :y2) (remove nil?)) max ##-Inf selrects)] - {:x minx - :y miny - :x1 minx - :y1 miny - :x2 maxx - :y2 maxy - :width (- maxx minx) - :height (- maxy miny)})) + (when (d/not-empty? selrects) + (let [minx (transduce (keep :x1) min ##Inf selrects) + miny (transduce (keep :y1) min ##Inf selrects) + maxx (transduce (keep :x2) max ##-Inf selrects) + maxy (transduce (keep :y2) max ##-Inf selrects)] + (when (d/num? minx miny maxx maxy) + (make-selrect minx miny (- maxx minx) (- maxy miny)))))) -(defn center->rect [center width height] - (assert (gpt/point center)) - (assert (and (number? width) (> width 0))) - (assert (and (number? height) (> height 0))) +(defn center->rect [{:keys [x y]} width height] + (when (d/num? x y width height) + (make-rect (- x (/ width 2)) + (- y (/ height 2)) + width + height))) - {:x (- (:x center) (/ width 2)) - :y (- (:y center) (/ height 2)) - :width width - :height height}) +(defn center->selrect [{:keys [x y]} width height] + (when (d/num? x y width height) + (make-selrect (- x (/ width 2)) + (- y (/ height 2)) + width + height))) (defn s= [a b] @@ -130,10 +150,3 @@ (>= (:y1 sr2) (:y1 sr1)) (<= (:y2 sr2) (:y2 sr1)))) -(defn round-selrect - [selrect] - (-> selrect - (update :x mth/round) - (update :y mth/round) - (update :width mth/round) - (update :height mth/round))) diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc index 0cd7a31aff..3f561b6029 100644 --- a/common/src/app/common/geom/shapes/transforms.cljc +++ b/common/src/app/common/geom/shapes/transforms.cljc @@ -21,38 +21,38 @@ ;; --- Relative Movement -(defn- move-selrect [selrect pt] - (when (and (some? selrect) (some? pt)) - (let [dx (.-x pt) - dy (.-y pt) - {:keys [x y x1 y1 x2 y2 width height]} selrect] - {:x (if (some? x) (+ dx x) x) - :y (if (some? y) (+ dy y) y) - :x1 (if (some? x1) (+ dx x1) x1) - :y1 (if (some? y1) (+ dy y1) y1) - :x2 (if (some? x2) (+ dx x2) x2) - :y2 (if (some? y2) (+ dy y2) y2) - :width width - :height height}))) +(defn- move-selrect [{:keys [x y x1 y1 x2 y2 width height] :as selrect} {dx :x dy :y :as pt}] + (if (and (some? selrect) (some? pt) (d/num? dx dy)) + {:x (if (d/num? x) (+ dx x) x) + :y (if (d/num? y) (+ dy y) y) + :x1 (if (d/num? x1) (+ dx x1) x1) + :y1 (if (d/num? y1) (+ dy y1) y1) + :x2 (if (d/num? x2) (+ dx x2) x2) + :y2 (if (d/num? y2) (+ dy y2) y2) + :width width + :height height} + selrect)) (defn- move-points [points move-vec] - (->> points - (mapv #(gpt/add % move-vec)))) + (cond->> points + (d/num? (:x move-vec) (:y move-vec)) + (mapv #(gpt/add % move-vec)))) (defn move-position-data [position-data dx dy] - (->> position-data - (mapv #(-> % - (update :x + dx) - (update :y + dy))))) + (cond->> position-data + (d/num? dx dy) + (mapv #(-> % + (update :x + dx) + (update :y + dy))))) (defn move "Move the shape relatively to its current position applying the provided delta." [{:keys [type] :as shape} {dx :x dy :y}] - (let [dx (d/check-num dx) - dy (d/check-num dy) + (let [dx (d/check-num dx 0) + dy (d/check-num dy 0) move-vec (gpt/point dx dy)] (-> shape @@ -138,9 +138,12 @@ (defn transform-matrix "Returns a transformation matrix without changing the shape properties. The result should be used in a `transform` attribute in svg" - ([shape] (transform-matrix shape nil)) - ([shape params] (transform-matrix shape params (or (gco/center-shape shape) - (gpt/point 0 0)))) + ([shape] + (transform-matrix shape nil)) + + ([shape params] + (transform-matrix shape params (or (gco/center-shape shape) (gpt/point 0 0)))) + ([{:keys [flip-x flip-y] :as shape} {:keys [no-flip]} shape-center] (-> (gmt/matrix) (gmt/translate shape-center) @@ -168,12 +171,13 @@ (defn transform-point-center "Transform a point around the shape center" [point center matrix] - (when point + (if (and (some? point) (some? matrix) (some? center)) (gpt/transform point (gmt/multiply (gmt/translate-matrix center) matrix - (gmt/translate-matrix (gpt/negate center)))))) + (gmt/translate-matrix (gpt/negate center)))) + point)) (defn transform-rect "Transform a rectangles and changes its attributes" @@ -249,9 +253,9 @@ ;; This rectangle is the new data for the current rectangle. We want to change our rectangle ;; to have this width, height, x, y - new-width (max 1 (:width points-temp-dim)) - new-height (max 1 (:height points-temp-dim)) - selrect (gco/make-centered-selrect center new-width new-height) + new-width (max 0.01 (:width points-temp-dim)) + new-height (max 0.01 (:height points-temp-dim)) + selrect (gpr/center->selrect center new-width new-height) rect-points (gpr/rect->points selrect) [matrix matrix-inverse] (calculate-adjust-matrix points-temp rect-points flip-x flip-y)] @@ -263,7 +267,7 @@ (defn- apply-transform "Given a new set of points transformed, set up the rectangle so it keeps its properties. We adjust de x,y,width,height and create a custom transform" - [shape transform-mtx round-coords?] + [shape transform-mtx] (let [points' (:points shape) points (gco/transform-points points' transform-mtx) @@ -276,10 +280,6 @@ [(gpr/points->selrect points) nil nil] (adjust-rotated-transform shape points)) - selrect (cond-> selrect - round-coords? gpr/round-selrect) - - ;; Redondear los points? base-rotation (or (:rotation shape) 0) modif-rotation (or (get-in shape [:modifiers :rotation]) 0) rotation (mod (+ base-rotation modif-rotation) 360)] @@ -296,8 +296,10 @@ (assoc :transform-inverse transform-inverse))) (cond-> (not transform) (dissoc :transform :transform-inverse)) - (assoc :selrect selrect) - (assoc :points points) + (cond-> (some? selrect) + (assoc :selrect selrect)) + (cond-> (d/not-empty? points) + (assoc :points points)) (assoc :rotation rotation)))) (defn- update-group-viewbox @@ -345,7 +347,7 @@ ;; need to remove the flip flags (assoc :flip-x false) (assoc :flip-y false) - (apply-transform (gmt/matrix) true)))) + (apply-transform (gmt/matrix))))) (defn update-mask-selrect [masked-group children] @@ -409,13 +411,14 @@ width (:width new-size) height (:height new-size) - shape-transform (:transform shape (gmt/matrix)) - shape-transform-inv (:transform-inverse shape (gmt/matrix)) + shape-transform (:transform shape) + shape-transform-inv (:transform-inverse shape) shape-center (gco/center-shape shape) {sr-width :width sr-height :height} (:selrect shape) - origin (-> (gpt/point (:selrect shape)) - (transform-point-center shape-center shape-transform)) + origin (cond-> (gpt/point (:selrect shape)) + (some? shape-transform) + (transform-point-center shape-center shape-transform)) scalev (gpt/divide (gpt/point width height) (gpt/point sr-width sr-height))] @@ -464,24 +467,28 @@ (normalize-scale (:y resize-v2)))) - resize-transform (:resize-transform modifiers (gmt/matrix)) - resize-transform-inverse (:resize-transform-inverse modifiers (gmt/matrix)) + resize-transform (:resize-transform modifiers) + resize-transform-inverse (:resize-transform-inverse modifiers) rt-modif (:rotation modifiers)] (cond-> (gmt/matrix) (some? resize-1) (-> (gmt/translate origin-1) - (gmt/multiply resize-transform) + (cond-> (some? resize-transform) + (gmt/multiply resize-transform)) (gmt/scale resize-1) - (gmt/multiply resize-transform-inverse) + (cond-> (some? resize-transform-inverse) + (gmt/multiply resize-transform-inverse)) (gmt/translate (gpt/negate origin-1))) (some? resize-2) (-> (gmt/translate origin-2) - (gmt/multiply resize-transform) + (cond-> (some? resize-transform) + (gmt/multiply resize-transform)) (gmt/scale resize-2) - (gmt/multiply resize-transform-inverse) + (cond-> (some? resize-transform-inverse) + (gmt/multiply resize-transform-inverse)) (gmt/translate (gpt/negate origin-2))) (some? displacement) @@ -525,7 +532,6 @@ (d/parse-double) (* (get-in modifiers [:resize-vector :x] 1)) (* (get-in modifiers [:resize-vector-2 :x] 1)) - (mth/precision 2) (str))] (attrs/merge attrs {:font-size font-size})))] (update shape :content #(txt/transform-nodes @@ -535,64 +541,54 @@ shape)) (defn apply-modifiers - [shape modifiers round-coords?] + [shape modifiers] (let [center (gco/center-shape shape) transform (modifiers->transform center modifiers)] - (apply-transform shape transform round-coords?))) + (apply-transform shape transform))) (defn transform-shape - ([shape] - (transform-shape shape nil)) + [shape] + (let [modifiers (:modifiers shape)] + (cond + (nil? modifiers) + shape - ([shape {:keys [round-coords?] :or {round-coords? true}}] - (let [modifiers (:modifiers shape)] - (cond - (nil? modifiers) - shape + (empty-modifiers? modifiers) + (dissoc shape :modifiers) - (empty-modifiers? modifiers) - (dissoc shape :modifiers) + :else + (let [shape (apply-displacement shape) + modifiers (:modifiers shape)] + (cond-> shape + (not (empty-modifiers? modifiers)) + (-> (set-flip modifiers) + (apply-modifiers modifiers) + (apply-text-resize modifiers)) - :else - (let [shape (apply-displacement shape) - modifiers (:modifiers shape)] - (cond-> shape - (not (empty-modifiers? modifiers)) - (-> (set-flip modifiers) - (apply-modifiers modifiers round-coords?) - (apply-text-resize modifiers)) + :always + (dissoc :modifiers)))))) - :always - (dissoc :modifiers))))))) - -(defn transform-selrect - [selrect {:keys [displacement resize-transform-inverse resize-vector resize-origin resize-vector-2 resize-origin-2]}] +(defn transform-bounds + [points center {:keys [displacement resize-transform-inverse resize-vector resize-origin resize-vector-2 resize-origin-2]}] ;; FIXME: Improve Performance (let [resize-transform-inverse (or resize-transform-inverse (gmt/matrix)) displacement (when (some? displacement) - (gmt/multiply resize-transform-inverse displacement) - #_(-> (gpt/point 0 0) - (gpt/transform displacement) - (gpt/transform resize-transform-inverse) - (gmt/translate-matrix))) + (gmt/multiply resize-transform-inverse displacement)) resize-origin (when (some? resize-origin) - (transform-point-center resize-origin (gco/center-selrect selrect) resize-transform-inverse)) + (transform-point-center resize-origin center resize-transform-inverse)) resize-origin-2 (when (some? resize-origin-2) - (transform-point-center resize-origin-2 (gco/center-selrect selrect) resize-transform-inverse))] + (transform-point-center resize-origin-2 center resize-transform-inverse))] (if (and (nil? displacement) (nil? resize-origin) (nil? resize-origin-2)) - selrect - - (cond-> selrect - :always - (gpr/rect->points) + points + (cond-> points (some? displacement) (gco/transform-points displacement) @@ -600,11 +596,15 @@ (gco/transform-points resize-origin (gmt/scale-matrix resize-vector)) (some? resize-origin-2) - (gco/transform-points resize-origin-2 (gmt/scale-matrix resize-vector-2)) - - :always - (gpr/points->selrect))))) + (gco/transform-points resize-origin-2 (gmt/scale-matrix resize-vector-2)))))) +(defn transform-selrect + [selrect modifiers] + (let [center (gco/center-selrect selrect)] + (-> selrect + (gpr/rect->points) + (transform-bounds center modifiers) + (gpr/points->selrect)))) (defn selection-rect "Returns a rect that contains all the shapes and is aware of the diff --git a/common/src/app/common/math.cljc b/common/src/app/common/math.cljc index 6b61b3d731..f5bb087745 100644 --- a/common/src/app/common/math.cljc +++ b/common/src/app/common/math.cljc @@ -106,6 +106,11 @@ #?(:cljs (js/Math.round v) :clj (Math/round (float v)))) +(defn half-round + "Returns a value rounded to the next point or half point" + [v] + (/ (round (* v 2)) 2)) + (defn ceil "Returns the smallest integer greater than or equal to a given number." @@ -115,7 +120,7 @@ (defn precision [v n] - (when (and (number? v) (number? n)) + (when (and (number? v) (integer? n)) (let [d (pow 10 n)] (/ (round (* v d)) d)))) @@ -165,3 +170,7 @@ [v0 v1 t] (+ (* (- 1 t) v0) (* t v1))) + +(defn max-abs + [a b] + (max (abs a) (abs b))) diff --git a/common/src/app/common/pages.cljc b/common/src/app/common/pages.cljc index 496c7ccb7c..0e6a91a942 100644 --- a/common/src/app/common/pages.cljc +++ b/common/src/app/common/pages.cljc @@ -42,4 +42,5 @@ (dm/export init/make-minimal-shape) (dm/export init/make-minimal-group) (dm/export init/empty-file-data) - +(dm/export init/setup-shape) +(dm/export init/setup-rect-selrect) diff --git a/common/src/app/common/pages/common.cljc b/common/src/app/common/pages/common.cljc index 1988f2137d..1a586beb56 100644 --- a/common/src/app/common/pages/common.cljc +++ b/common/src/app/common/pages/common.cljc @@ -104,6 +104,7 @@ :group #{:proportion-lock :width :height :x :y + :rotation :selrect :constraints-h diff --git a/common/src/app/common/pages/init.cljc b/common/src/app/common/pages/init.cljc index 10bfba88b8..97e4997113 100644 --- a/common/src/app/common/pages/init.cljc +++ b/common/src/app/common/pages/init.cljc @@ -9,6 +9,7 @@ [app.common.colors :as clr] [app.common.data :as d] [app.common.exceptions :as ex] + [app.common.geom.shapes :as gsh] [app.common.pages.common :refer [file-version default-color]] [app.common.uuid :as uuid])) @@ -91,8 +92,8 @@ (def empty-selrect {:x 0 :y 0 :x1 0 :y1 0 - :x2 1 :y2 1 - :width 1 :height 1}) + :x2 0.01 :y2 0.01 + :width 0.01 :height 0.01}) (defn make-minimal-shape [type] @@ -111,16 +112,16 @@ (not= :path (:type shape)) (assoc :x 0 :y 0 - :width 1 - :height 1 + :width 0.01 + :height 0.01 :selrect {:x 0 :y 0 :x1 0 :y1 0 - :x2 1 - :y2 1 - :width 1 - :height 1})))) + :x2 0.01 + :y2 0.01 + :width 0.01 + :height 0.01})))) (defn make-minimal-group [frame-id selection-rect group-name] @@ -146,3 +147,40 @@ (assoc :id file-id) (update :pages conj page-id) (update :pages-index assoc page-id pd))))) + +(defn setup-rect-selrect + "Initializes the selrect and points for a shape" + [shape] + (let [selrect (gsh/rect->selrect shape) + points (gsh/rect->points shape)] + (-> shape + (assoc :selrect selrect + :points points)))) + +(defn- setup-rect + "A specialized function for setup rect-like shapes." + [shape {:keys [x y width height]}] + (-> shape + (assoc :x x :y y :width width :height height) + (setup-rect-selrect))) + +(defn- setup-image + [{:keys [metadata] :as shape} props] + (-> (setup-rect shape props) + (assoc + :proportion (/ (:width metadata) + (:height metadata)) + :proportion-lock true))) + +(defn setup-shape + "A function that initializes the first coordinates for + the shape. Used mainly for draw operations." + ([props] + (setup-shape {:type :rect} props)) + + ([shape props] + (case (:type shape) + :image (setup-image shape props) + (setup-rect shape props)))) + + diff --git a/common/src/app/common/pages/migrations.cljc b/common/src/app/common/pages/migrations.cljc index 4a3ebc5811..844475a08e 100644 --- a/common/src/app/common/pages/migrations.cljc +++ b/common/src/app/common/pages/migrations.cljc @@ -84,7 +84,7 @@ (fix-empty-points [shape] (let [shape (cond-> shape - (empty? (:selrect shape)) (gsh/setup-selrect))] + (empty? (:selrect shape)) (cp/setup-rect-selrect))] (cond-> shape (empty? (:points shape)) (assoc :points (gsh/rect->points (:selrect shape)))))) diff --git a/common/test/app/common/geom_shapes_test.cljc b/common/test/app/common/geom_shapes_test.cljc index a7b190e00e..079ecafd62 100644 --- a/common/test/app/common/geom_shapes_test.cljc +++ b/common/test/app/common/geom_shapes_test.cljc @@ -9,7 +9,7 @@ [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] - [app.common.math :refer [close?]] + [app.common.math :as mth :refer [close?]] [app.common.pages :refer [make-minimal-shape]] [clojure.test :as t])) @@ -52,7 +52,7 @@ (t/testing "Shape without modifiers should stay the same" (t/are [type] (let [shape-before (create-test-shape type) - shape-after (gsh/transform-shape shape-before {:round-coords? false})] + shape-after (gsh/transform-shape shape-before)] (= shape-before shape-after)) :rect :path)) @@ -61,7 +61,7 @@ (t/are [type] (let [modifiers {:displacement (gmt/translate-matrix (gpt/point 10 -10))}] (let [shape-before (create-test-shape type {:modifiers modifiers}) - shape-after (gsh/transform-shape shape-before {:round-coords? false})] + shape-after (gsh/transform-shape shape-before)] (t/is (not= shape-before shape-after)) (t/is (close? (get-in shape-before [:selrect :x]) @@ -82,7 +82,7 @@ (t/are [type] (let [modifiers {:displacement (gmt/matrix)} shape-before (create-test-shape type {:modifiers modifiers}) - shape-after (gsh/transform-shape shape-before {:round-coords? false})] + shape-after (gsh/transform-shape shape-before)] (t/are [prop] (t/is (close? (get-in shape-before [:selrect prop]) (get-in shape-after [:selrect prop]))) @@ -95,7 +95,7 @@ :resize-vector (gpt/point 2 2) :resize-transform (gmt/matrix)} shape-before (create-test-shape type {:modifiers modifiers}) - shape-after (gsh/transform-shape shape-before {:round-coords? false})] + shape-after (gsh/transform-shape shape-before)] (t/is (not= shape-before shape-after)) (t/is (close? (get-in shape-before [:selrect :x]) @@ -117,7 +117,7 @@ :resize-vector (gpt/point 1 1) :resize-transform (gmt/matrix)} shape-before (create-test-shape type {:modifiers modifiers}) - shape-after (gsh/transform-shape shape-before {:round-coords? false})] + shape-after (gsh/transform-shape shape-before)] (t/are [prop] (t/is (close? (get-in shape-before [:selrect prop]) (get-in shape-after [:selrect prop]))) @@ -130,7 +130,7 @@ :resize-vector (gpt/point 0 0) :resize-transform (gmt/matrix)} shape-before (create-test-shape type {:modifiers modifiers}) - shape-after (gsh/transform-shape shape-before {:round-coords? false})] + shape-after (gsh/transform-shape shape-before)] (t/is (> (get-in shape-before [:selrect :width]) (get-in shape-after [:selrect :width]))) (t/is (> (get-in shape-after [:selrect :width]) 0)) @@ -144,7 +144,7 @@ (t/are [type] (let [modifiers {:rotation 30} shape-before (create-test-shape type {:modifiers modifiers}) - shape-after (gsh/transform-shape shape-before {:round-coords? false})] + shape-after (gsh/transform-shape shape-before)] (t/is (not= shape-before shape-after)) @@ -168,7 +168,7 @@ (t/are [type] (let [modifiers {:rotation 0} shape-before (create-test-shape type {:modifiers modifiers}) - shape-after (gsh/transform-shape shape-before {:round-coords? false})] + shape-after (gsh/transform-shape shape-before)] (t/are [prop] (t/is (close? (get-in shape-before [:selrect prop]) (get-in shape-after [:selrect prop]))) @@ -180,7 +180,7 @@ (let [modifiers {:displacement (gmt/matrix)} shape-before (-> (create-test-shape type {:modifiers modifiers}) (assoc :selrect selrect)) - shape-after (gsh/transform-shape shape-before {:round-coords? false})] + shape-after (gsh/transform-shape shape-before)] (= (:selrect shape-before) (:selrect shape-after))) diff --git a/common/test/app/common/geom_test.cljc b/common/test/app/common/geom_test.cljc index 0828a76167..6807ed282f 100644 --- a/common/test/app/common/geom_test.cljc +++ b/common/test/app/common/geom_test.cljc @@ -7,6 +7,7 @@ (ns app.common.geom-test (:require [clojure.test :as t] + [app.common.math :as mth] [app.common.geom.point :as gpt] [app.common.geom.matrix :as gmt])) @@ -67,7 +68,7 @@ p2 (gpt/point 10 10) angle (gpt/angle-with-other p1 p2)] (t/is (number? angle)) - (t/is (= angle 45.0))))) + (t/is (mth/close? angle 45.0))))) (t/deftest matrix-constructors-test (let [m (gmt/matrix)] diff --git a/frontend/resources/styles/main/partials/workspace.scss b/frontend/resources/styles/main/partials/workspace.scss index 7cead65b32..48b943526d 100644 --- a/frontend/resources/styles/main/partials/workspace.scss +++ b/frontend/resources/styles/main/partials/workspace.scss @@ -241,12 +241,13 @@ $height-palette-max: 80px; position: relative; .viewport-overlays { + cursor: initial; + height: 100%; + overflow: hidden; + pointer-events: none; position: absolute; width: 100%; - height: 100%; z-index: 10; - pointer-events: none; - cursor: initial; .pixel-overlay { height: 100%; diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 5dbdcc2e17..0300d3bf07 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -395,8 +395,8 @@ srect (gsh/selection-rect shapes) local (assoc local :vport size :zoom 1)] (cond - (or (not (mth/finite? (:width srect))) - (not (mth/finite? (:height srect)))) + (or (not (d/num? (:width srect))) + (not (d/num? (:height srect)))) (assoc local :vbox (assoc size :x 0 :y 0)) (or (> (:width srect) width) @@ -1574,7 +1574,7 @@ page-id (:current-page-id state) frame-id (-> (wsh/lookup-page-objects state page-id) (cph/frame-id-by-position @ms/mouse-position)) - shape (gsh/setup-selrect + shape (cp/setup-rect-selrect {:id id :type :text :name "Text" @@ -1667,7 +1667,7 @@ shape (-> (cp/make-minimal-shape :frame) (merge {:x (:x srect) :y (:y srect) :width (:width srect) :height (:height srect)}) (assoc :frame-id frame-id) - (gsh/setup-selrect))] + (cp/setup-rect-selrect))] (rx/of (dwu/start-undo-transaction) (dwc/add-shape shape) diff --git a/frontend/src/app/main/data/workspace/comments.cljs b/frontend/src/app/main/data/workspace/comments.cljs index c3a7f572ef..b7f9f20308 100644 --- a/frontend/src/app/main/data/workspace/comments.cljs +++ b/frontend/src/app/main/data/workspace/comments.cljs @@ -6,7 +6,6 @@ (ns app.main.data.workspace.comments (:require - [app.common.math :as mth] [app.common.spec :as us] [app.main.data.comments :as dcm] [app.main.data.workspace :as dw] @@ -78,8 +77,8 @@ (fn [{:keys [vbox zoom] :as local}] (let [pw (/ 160 zoom) ph (/ 160 zoom) - nw (mth/round (- (/ (:width vbox) 2) pw)) - nh (mth/round (- (/ (:height vbox) 2) ph)) + nw (- (/ (:width vbox) 2) pw) + nh (- (/ (:height vbox) 2) ph) nx (- (:x position) nw) ny (- (:y position) nh)] (update local :vbox assoc :x nx :y ny))))))) diff --git a/frontend/src/app/main/data/workspace/common.cljs b/frontend/src/app/main/data/workspace/common.cljs index d610d4d842..55fc8adf7f 100644 --- a/frontend/src/app/main/data/workspace/common.cljs +++ b/frontend/src/app/main/data/workspace/common.cljs @@ -480,7 +480,7 @@ (merge data) (merge {:x x :y y}) (assoc :frame-id frame-id) - (gsh/setup-selrect))] + (cp/setup-rect-selrect))] (rx/of (add-shape shape)))))) (defn image-uploaded diff --git a/frontend/src/app/main/data/workspace/drawing/box.cljs b/frontend/src/app/main/data/workspace/drawing/box.cljs index f8c9a75925..2218845f60 100644 --- a/frontend/src/app/main/data/workspace/drawing/box.cljs +++ b/frontend/src/app/main/data/workspace/drawing/box.cljs @@ -9,6 +9,7 @@ [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] [app.common.math :as mth] + [app.common.pages :as cp] [app.common.pages.helpers :as cph] [app.common.uuid :as uuid] [app.main.data.workspace.drawing.common :as common] @@ -27,8 +28,8 @@ shapev (gpt/point width height) deltav (gpt/to-vec initial point) scalev (-> (gpt/divide (gpt/add shapev deltav) shapev) - (update :x truncate-zero 1) - (update :y truncate-zero 1)) + (update :x truncate-zero 0.01) + (update :y truncate-zero 0.01)) scalev (if lock? (let [v (max (:x scalev) (:y scalev))] (gpt/point v v)) @@ -45,9 +46,7 @@ (defn move-drawing [{:keys [x y]}] (fn [state] - (let [x (mth/precision x 0) - y (mth/precision y 0)] - (update-in state [:workspace-drawing :object] gsh/absolute-move (gpt/point x y))))) + (update-in state [:workspace-drawing :object] gsh/absolute-move (gpt/point x y)))) (defn handle-drawing-box [] (ptk/reify ::handle-drawing-box @@ -55,11 +54,14 @@ (watch [_ state stream] (let [stoper? #(or (ms/mouse-up? %) (= % :interrupt)) stoper (rx/filter stoper? stream) - initial @ms/mouse-position + layout (get state :workspace-layout) + snap-pixel? (contains? layout :snap-pixel-grid) + + initial (cond-> @ms/mouse-position + snap-pixel? gpt/round) page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) - layout (get state :workspace-layout) focus (:workspace-focus-selected state) zoom (get-in state [:workspace-local :zoom] 1) @@ -72,7 +74,10 @@ shape (-> state (get-in [:workspace-drawing :object]) - (gsh/setup {:x (:x initial) :y (:y initial) :width 1 :height 1}) + (cp/setup-shape {:x (:x initial) + :y (:y initial) + :width 0.01 + :height 0.01}) (assoc :frame-id fid) (assoc :initialized? true) (assoc :click-draw? true))] @@ -85,7 +90,7 @@ (rx/map move-drawing)) (->> ms/mouse-position - (rx/filter #(> (gpt/distance % initial) 2)) + (rx/filter #(> (gpt/distance % initial) (/ 2 zoom))) (rx/with-latest vector ms/mouse-position-shift) (rx/switch-map (fn [[point :as current]] @@ -93,7 +98,7 @@ (rx/map #(conj current %))))) (rx/map (fn [[_ shift? point]] - #(update-drawing % point shift?))) + #(update-drawing % (cond-> point snap-pixel? gpt/round) shift?))) (rx/take-until stoper)) (rx/of common/handle-finish-drawing)))))) diff --git a/frontend/src/app/main/data/workspace/drawing/common.cljs b/frontend/src/app/main/data/workspace/drawing/common.cljs index 624bbf3084..047f1c0ff6 100644 --- a/frontend/src/app/main/data/workspace/drawing/common.cljs +++ b/frontend/src/app/main/data/workspace/drawing/common.cljs @@ -6,8 +6,10 @@ (ns app.main.data.workspace.drawing.common (:require - [app.common.geom.point :as gpt] + [app.common.geom.matrix :as gmt] [app.common.geom.shapes :as gsh] + [app.common.math :as mth] + [app.common.pages :as cp] [app.main.data.workspace.common :as dwc] [app.main.data.workspace.undo :as dwu] [app.main.worker :as uw] @@ -29,27 +31,30 @@ (rx/concat (when (:initialized? shape) (let [page-id (:current-page-id state) - shape-click-width (case (:type shape) - :text 3 - 20) - shape-click-height (case (:type shape) - :text 16 - 20) - shape (if (:click-draw? shape) - (-> shape - (assoc-in [:modifiers :resize-vector] - (gpt/point shape-click-width shape-click-height)) - (assoc-in [:modifiers :resize-origin] - (gpt/point (:x shape) (:y shape)))) - shape) - shape (cond-> shape - (= (:type shape) :text) (assoc :grow-type - (if (:click-draw? shape) :auto-width :fixed))) + click-draw? (:click-draw? shape) + text? (= :text (:type shape)) - shape (-> shape - (gsh/transform-shape) - (dissoc :initialized? :click-draw?))] + min-side (min 100 + (mth/floor (get-in state [:workspace-local :vbox :width])) + (mth/floor (get-in state [:workspace-local :vbox :height]))) + + shape + (cond-> shape + (and click-draw? (not text?)) + (-> (assoc :width min-side :height min-side) + (assoc-in [:modifiers :displacement] + (gmt/translate-matrix (- (/ min-side 2)) (- (/ min-side 2))))) + + (and click-draw? text?) + (assoc :height 17 :width 4 :grow-type :auto-width) + + click-draw? + (cp/setup-rect-selrect) + + :always + (-> (gsh/transform-shape) + (dissoc :initialized? :click-draw?)))] ;; Add & select the created shape to the workspace (rx/concat (if (= :text (:type shape)) diff --git a/frontend/src/app/main/data/workspace/groups.cljs b/frontend/src/app/main/data/workspace/groups.cljs index 422299e628..f09384d06b 100644 --- a/frontend/src/app/main/data/workspace/groups.cljs +++ b/frontend/src/app/main/data/workspace/groups.cljs @@ -76,7 +76,7 @@ selrect (gsh/selection-rect shapes) group (-> (cp/make-minimal-group frame-id selrect gname) - (gsh/setup selrect) + (cp/setup-shape selrect) (assoc :shapes (mapv :id shapes) :parent-id parent-id :frame-id frame-id diff --git a/frontend/src/app/main/data/workspace/layout.cljs b/frontend/src/app/main/data/workspace/layout.cljs index 9693a10304..32e641c766 100644 --- a/frontend/src/app/main/data/workspace/layout.cljs +++ b/frontend/src/app/main/data/workspace/layout.cljs @@ -27,7 +27,9 @@ :scale-text :dynamic-alignment :display-artboard-names - :snap-guides}) + :snap-guides + :show-pixel-grid + :snap-pixel-grid}) (def presets {:assets @@ -53,7 +55,9 @@ :snap-grid :dynamic-alignment :display-artboard-names - :snap-guides}) + :snap-guides + :show-pixel-grid + :snap-pixel-grid}) (def default-global {:options-mode :design}) diff --git a/frontend/src/app/main/data/workspace/path/selection.cljs b/frontend/src/app/main/data/workspace/path/selection.cljs index 3fed3f3651..df6366a6d7 100644 --- a/frontend/src/app/main/data/workspace/path/selection.cljs +++ b/frontend/src/app/main/data/workspace/path/selection.cljs @@ -103,20 +103,21 @@ (defn handle-area-selection [shift?] - (letfn [(valid-rect? [{width :width height :height}] - (or (> width 10) (> height 10)))] + (letfn [(valid-rect? [zoom {width :width height :height}] + (or (> width (/ 10 zoom)) (> height (/ 10 zoom))))] (ptk/reify ::handle-area-selection ptk/WatchEvent - (watch [_ _ stream] - (let [stop? (fn [event] (or (dwc/interrupt? event) (ms/mouse-up? event))) + (watch [_ state stream] + (let [zoom (get-in state [:workspace-local :zoom] 1) + stop? (fn [event] (or (dwc/interrupt? event) (ms/mouse-up? event))) stoper (->> stream (rx/filter stop?)) from-p @ms/mouse-position] (rx/concat (->> ms/mouse-position (rx/take-until stoper) (rx/map #(gsh/points->rect [from-p %])) - (rx/filter valid-rect?) + (rx/filter (partial valid-rect? zoom)) (rx/map update-area-selection)) (rx/of (select-node-area shift?) diff --git a/frontend/src/app/main/data/workspace/path/streams.cljs b/frontend/src/app/main/data/workspace/path/streams.cljs index 8a8f8a59b3..94c8789c66 100644 --- a/frontend/src/app/main/data/workspace/path/streams.cljs +++ b/frontend/src/app/main/data/workspace/path/streams.cljs @@ -8,7 +8,6 @@ (:require [app.common.geom.point :as gpt] [app.common.geom.shapes.path :as upg] - [app.common.math :as mth] [app.main.data.workspace.path.state :as state] [app.main.snap :as snap] [app.main.store :as st] @@ -18,6 +17,7 @@ [potok.core :as ptk])) (defonce drag-threshold 5) +(def zoom-half-pixel-precision 8) (defn dragging? [start zoom] (fn [current] @@ -26,19 +26,37 @@ (defn finish-edition? [event] (= (ptk/type event) :app.main.data.workspace.common/clear-edition-mode)) +(defn to-pixel-snap [position] + (let [zoom (get-in @st/state [:workspace-local :zoom] 1) + layout (get @st/state :workspace-layout) + snap-pixel? (contains? layout :snap-pixel-grid)] + + (cond + (or (not snap-pixel?) (not (gpt/point? position))) + position + + (>= zoom zoom-half-pixel-precision) + (gpt/half-round position) + + :else + (gpt/round position)))) + (defn drag-stream ([to-stream] (drag-stream to-stream (rx/empty))) ([to-stream not-drag-stream] - (let [start @ms/mouse-position - zoom (get-in @st/state [:workspace-local :zoom] 1) - mouse-up (->> st/stream (rx/filter #(or (finish-edition? %) - (ms/mouse-up? %)))) + (let [zoom (get-in @st/state [:workspace-local :zoom] 1) + + start (-> @ms/mouse-position to-pixel-snap) + mouse-up (->> st/stream + (rx/filter #(or (finish-edition? %) + (ms/mouse-up? %)))) position-stream (->> ms/mouse-position (rx/take-until mouse-up) + (rx/map to-pixel-snap) (rx/filter (dragging? start zoom)) (rx/take 1))] @@ -53,10 +71,6 @@ (->> position-stream (rx/merge-map (fn [] to-stream))))))) -(defn to-dec [num] - (let [k 50] - (* (mth/floor (/ num k)) k))) - (defn move-points-stream [snap-toggled start-point selected-points points] @@ -73,6 +87,7 @@ (gpt/add position snap)) position))] (->> ms/mouse-position + (rx/map to-pixel-snap) (rx/map check-path-snap)))) (defn get-angle [node handler opposite] @@ -116,6 +131,7 @@ (merge position (gpt/add position snap))))) position))] (->> ms/mouse-position + (rx/map to-pixel-snap) (rx/with-latest merge (->> ms/mouse-position-shift (rx/map #(hash-map :shift? %)))) (rx/with-latest merge (->> ms/mouse-position-alt (rx/map #(hash-map :alt? %)))) (rx/map check-path-snap)))) @@ -136,6 +152,7 @@ (rx/map snap/create-ranges))] (->> ms/mouse-position + (rx/map to-pixel-snap) (rx/with-latest vector ranges-stream) (rx/map (fn [[position ranges]] (if snap-toggled diff --git a/frontend/src/app/main/data/workspace/shortcuts.cljs b/frontend/src/app/main/data/workspace/shortcuts.cljs index 242e2fcd86..b1b71a820d 100644 --- a/frontend/src/app/main/data/workspace/shortcuts.cljs +++ b/frontend/src/app/main/data/workspace/shortcuts.cljs @@ -33,17 +33,17 @@ ;; Shortcuts format https://github.com/ccampbell/mousetrap (def base-shortcuts - {:toggle-layers {:tooltip (ds/alt "L") - :command (ds/a-mod "l") - :fn #(st/emit! (dw/go-to-layout :layers))} + {:toggle-layers {:tooltip (ds/alt "L") + :command (ds/a-mod "l") + :fn #(st/emit! (dw/go-to-layout :layers))} - :toggle-assets {:tooltip (ds/alt "I") - :command (ds/a-mod "i") - :fn #(st/emit! (dw/go-to-layout :assets))} + :toggle-assets {:tooltip (ds/alt "I") + :command (ds/a-mod "i") + :fn #(st/emit! (dw/go-to-layout :assets))} - :toggle-history {:tooltip (ds/alt "H") - :command (ds/a-mod "h") - :fn #(st/emit! (dw/go-to-layout :document-history))} + :toggle-history {:tooltip (ds/alt "H") + :command (ds/a-mod "h") + :fn #(st/emit! (dw/go-to-layout :document-history))} :toggle-colorpalette {:tooltip (ds/alt "P") :command (ds/a-mod "p") @@ -57,250 +57,250 @@ (st/emit! (dw/remove-layout-flag :colorpalette) (toggle-layout-flag :textpalette)))} - :toggle-rules {:tooltip (ds/meta-shift "R") - :command (ds/c-mod "shift+r") - :fn #(st/emit! (toggle-layout-flag :rules))} + :toggle-rules {:tooltip (ds/meta-shift "R") + :command (ds/c-mod "shift+r") + :fn #(st/emit! (toggle-layout-flag :rules))} - :select-all {:tooltip (ds/meta "A") - :command (ds/c-mod "a") - :fn #(st/emit! (dw/select-all))} + :select-all {:tooltip (ds/meta "A") + :command (ds/c-mod "a") + :fn #(st/emit! (dw/select-all))} - :toggle-grid {:tooltip (ds/meta "'") - :command (ds/c-mod "'") - :fn #(st/emit! (toggle-layout-flag :display-grid))} + :toggle-grid {:tooltip (ds/meta "'") + :command (ds/c-mod "'") + :fn #(st/emit! (toggle-layout-flag :display-grid))} - :toggle-snap-grid {:tooltip (ds/meta-shift "'") - :command (ds/c-mod "shift+'") - :fn #(st/emit! (toggle-layout-flag :snap-grid))} + :toggle-snap-grid {:tooltip (ds/meta-shift "'") + :command (ds/c-mod "shift+'") + :fn #(st/emit! (toggle-layout-flag :snap-grid))} - :toggle-snap-guide {:tooltip (ds/meta-shift "G") - :command (ds/c-mod "shift+G") - :fn #(st/emit! (toggle-layout-flag :snap-guides))} + :toggle-snap-guide {:tooltip (ds/meta-shift "G") + :command (ds/c-mod "shift+G") + :fn #(st/emit! (toggle-layout-flag :snap-guides))} - :toggle-alignment {:tooltip (ds/meta "\\") - :command (ds/c-mod "\\") - :fn #(st/emit! (toggle-layout-flag :dynamic-alignment))} + :toggle-alignment {:tooltip (ds/meta "\\") + :command (ds/c-mod "\\") + :fn #(st/emit! (toggle-layout-flag :dynamic-alignment))} - :toggle-scale-text {:tooltip "K" - :command "k" - :fn #(st/emit! (toggle-layout-flag :scale-text))} + :toggle-scale-text {:tooltip "K" + :command "k" + :fn #(st/emit! (toggle-layout-flag :scale-text))} - :increase-zoom {:tooltip "+" - :command ["+" "="] - :fn #(st/emit! (dw/increase-zoom nil))} + :increase-zoom {:tooltip "+" + :command ["+" "="] + :fn #(st/emit! (dw/increase-zoom nil))} - :decrease-zoom {:tooltip "-" - :command ["-" "_"] - :fn #(st/emit! (dw/decrease-zoom nil))} + :decrease-zoom {:tooltip "-" + :command ["-" "_"] + :fn #(st/emit! (dw/decrease-zoom nil))} - :group {:tooltip (ds/meta "G") - :command (ds/c-mod "g") - :fn #(st/emit! dw/group-selected)} + :group {:tooltip (ds/meta "G") + :command (ds/c-mod "g") + :fn #(st/emit! dw/group-selected)} - :ungroup {:tooltip (ds/shift "G") - :command "shift+g" - :fn #(st/emit! dw/ungroup-selected)} + :ungroup {:tooltip (ds/shift "G") + :command "shift+g" + :fn #(st/emit! dw/ungroup-selected)} - :mask {:tooltip (ds/meta "M") - :command (ds/c-mod "m") - :fn #(st/emit! dw/mask-group)} + :mask {:tooltip (ds/meta "M") + :command (ds/c-mod "m") + :fn #(st/emit! dw/mask-group)} - :unmask {:tooltip (ds/meta-shift "M") - :command (ds/c-mod "shift+m") - :fn #(st/emit! dw/unmask-group)} + :unmask {:tooltip (ds/meta-shift "M") + :command (ds/c-mod "shift+m") + :fn #(st/emit! dw/unmask-group)} - :create-component {:tooltip (ds/meta "K") - :command (ds/c-mod "k") - :fn #(st/emit! (dwl/add-component))} + :create-component {:tooltip (ds/meta "K") + :command (ds/c-mod "k") + :fn #(st/emit! (dwl/add-component))} - :detach-component {:tooltip (ds/meta-shift "K") - :command (ds/c-mod "shift+k") - :fn #(st/emit! dwl/detach-selected-components)} + :detach-component {:tooltip (ds/meta-shift "K") + :command (ds/c-mod "shift+k") + :fn #(st/emit! dwl/detach-selected-components)} - :flip-vertical {:tooltip (ds/shift "V") - :command "shift+v" - :fn #(st/emit! (dw/flip-vertical-selected))} + :flip-vertical {:tooltip (ds/shift "V") + :command "shift+v" + :fn #(st/emit! (dw/flip-vertical-selected))} - :flip-horizontal {:tooltip (ds/shift "H") - :command "shift+h" - :fn #(st/emit! (dw/flip-horizontal-selected))} + :flip-horizontal {:tooltip (ds/shift "H") + :command "shift+h" + :fn #(st/emit! (dw/flip-horizontal-selected))} - :reset-zoom {:tooltip (ds/shift "0") - :command "shift+0" - :fn #(st/emit! dw/reset-zoom)} + :reset-zoom {:tooltip (ds/shift "0") + :command "shift+0" + :fn #(st/emit! dw/reset-zoom)} - :fit-all {:tooltip (ds/shift "1") - :command "shift+1" - :fn #(st/emit! dw/zoom-to-fit-all)} + :fit-all {:tooltip (ds/shift "1") + :command "shift+1" + :fn #(st/emit! dw/zoom-to-fit-all)} - :zoom-selected {:tooltip (ds/shift "2") - :command "shift+2" - :fn #(st/emit! dw/zoom-to-selected-shape)} + :zoom-selected {:tooltip (ds/shift "2") + :command "shift+2" + :fn #(st/emit! dw/zoom-to-selected-shape)} - :duplicate {:tooltip (ds/meta "D") - :command (ds/c-mod "d") - :fn #(st/emit! (dw/duplicate-selected true))} + :duplicate {:tooltip (ds/meta "D") + :command (ds/c-mod "d") + :fn #(st/emit! (dw/duplicate-selected true))} - :undo {:tooltip (ds/meta "Z") - :command (ds/c-mod "z") - :fn #(st/emit! dwc/undo)} + :undo {:tooltip (ds/meta "Z") + :command (ds/c-mod "z") + :fn #(st/emit! dwc/undo)} - :redo {:tooltip (ds/meta "Y") - :command [(ds/c-mod "shift+z") (ds/c-mod "y")] - :fn #(st/emit! dwc/redo)} + :redo {:tooltip (ds/meta "Y") + :command [(ds/c-mod "shift+z") (ds/c-mod "y")] + :fn #(st/emit! dwc/redo)} - :clear-undo {:tooltip (ds/meta "Q") - :command (ds/c-mod "q") - :fn #(st/emit! dwu/reinitialize-undo)} + :clear-undo {:tooltip (ds/meta "Q") + :command (ds/c-mod "q") + :fn #(st/emit! dwu/reinitialize-undo)} - :draw-frame {:tooltip "A" - :command "a" - :fn #(st/emit! (dwd/select-for-drawing :frame))} + :draw-frame {:tooltip "A" + :command "a" + :fn #(st/emit! (dwd/select-for-drawing :frame))} - :move {:tooltip "V" - :command "v" - :fn #(st/emit! :interrupt)} + :move {:tooltip "V" + :command "v" + :fn #(st/emit! :interrupt)} - :draw-rect {:tooltip "R" - :command "r" - :fn #(st/emit! (dwd/select-for-drawing :rect))} + :draw-rect {:tooltip "R" + :command "r" + :fn #(st/emit! (dwd/select-for-drawing :rect))} - :draw-ellipse {:tooltip "E" - :command "e" - :fn #(st/emit! (dwd/select-for-drawing :circle))} + :draw-ellipse {:tooltip "E" + :command "e" + :fn #(st/emit! (dwd/select-for-drawing :circle))} - :draw-text {:tooltip "T" - :command "t" - :fn #(st/emit! dwtxt/start-edit-if-selected - (dwd/select-for-drawing :text))} + :draw-text {:tooltip "T" + :command "t" + :fn #(st/emit! dwtxt/start-edit-if-selected + (dwd/select-for-drawing :text))} - :draw-path {:tooltip "P" - :command "p" - :fn #(st/emit! (dwd/select-for-drawing :path))} + :draw-path {:tooltip "P" + :command "p" + :fn #(st/emit! (dwd/select-for-drawing :path))} - :draw-curve {:tooltip (ds/shift "C") - :command "shift+c" - :fn #(st/emit! (dwd/select-for-drawing :curve))} + :draw-curve {:tooltip (ds/shift "C") + :command "shift+c" + :fn #(st/emit! (dwd/select-for-drawing :curve))} - :add-comment {:tooltip "C" - :command "c" - :fn #(st/emit! (dwd/select-for-drawing :comments))} + :add-comment {:tooltip "C" + :command "c" + :fn #(st/emit! (dwd/select-for-drawing :comments))} - :insert-image {:tooltip (ds/shift "K") - :command "shift+k" - :fn #(-> "image-upload" dom/get-element dom/click)} + :insert-image {:tooltip (ds/shift "K") + :command "shift+k" + :fn #(-> "image-upload" dom/get-element dom/click)} - :copy {:tooltip (ds/meta "C") - :command (ds/c-mod "c") - :fn #(st/emit! (dw/copy-selected))} + :copy {:tooltip (ds/meta "C") + :command (ds/c-mod "c") + :fn #(st/emit! (dw/copy-selected))} - :cut {:tooltip (ds/meta "X") - :command (ds/c-mod "x") - :fn #(st/emit! (dw/copy-selected) - (dw/delete-selected))} + :cut {:tooltip (ds/meta "X") + :command (ds/c-mod "x") + :fn #(st/emit! (dw/copy-selected) + (dw/delete-selected))} - :paste {:tooltip (ds/meta "V") - :disabled true - :command (ds/c-mod "v") - :fn (constantly nil)} + :paste {:tooltip (ds/meta "V") + :disabled true + :command (ds/c-mod "v") + :fn (constantly nil)} - :delete {:tooltip (ds/supr) - :command ["del" "backspace"] - :fn #(st/emit! (dw/delete-selected))} + :delete {:tooltip (ds/supr) + :command ["del" "backspace"] + :fn #(st/emit! (dw/delete-selected))} - :bring-forward {:tooltip (ds/meta ds/up-arrow) - :command (ds/c-mod "up") - :fn #(st/emit! (dw/vertical-order-selected :up))} + :bring-forward {:tooltip (ds/meta ds/up-arrow) + :command (ds/c-mod "up") + :fn #(st/emit! (dw/vertical-order-selected :up))} - :bring-backward {:tooltip (ds/meta ds/down-arrow) - :command (ds/c-mod "down") - :fn #(st/emit! (dw/vertical-order-selected :down))} + :bring-backward {:tooltip (ds/meta ds/down-arrow) + :command (ds/c-mod "down") + :fn #(st/emit! (dw/vertical-order-selected :down))} - :bring-front {:tooltip (ds/meta-shift ds/up-arrow) - :command (ds/c-mod "shift+up") - :fn #(st/emit! (dw/vertical-order-selected :top))} + :bring-front {:tooltip (ds/meta-shift ds/up-arrow) + :command (ds/c-mod "shift+up") + :fn #(st/emit! (dw/vertical-order-selected :top))} - :bring-back {:tooltip (ds/meta-shift ds/down-arrow) - :command (ds/c-mod "shift+down") - :fn #(st/emit! (dw/vertical-order-selected :bottom))} + :bring-back {:tooltip (ds/meta-shift ds/down-arrow) + :command (ds/c-mod "shift+down") + :fn #(st/emit! (dw/vertical-order-selected :bottom))} - :move-fast-up {:tooltip (ds/shift ds/up-arrow) - :command "shift+up" - :fn #(st/emit! (dwt/move-selected :up true))} + :move-fast-up {:tooltip (ds/shift ds/up-arrow) + :command "shift+up" + :fn #(st/emit! (dwt/move-selected :up true))} - :move-fast-down {:tooltip (ds/shift ds/down-arrow) - :command "shift+down" - :fn #(st/emit! (dwt/move-selected :down true))} + :move-fast-down {:tooltip (ds/shift ds/down-arrow) + :command "shift+down" + :fn #(st/emit! (dwt/move-selected :down true))} - :move-fast-right {:tooltip (ds/shift ds/right-arrow) - :command "shift+right" - :fn #(st/emit! (dwt/move-selected :right true))} + :move-fast-right {:tooltip (ds/shift ds/right-arrow) + :command "shift+right" + :fn #(st/emit! (dwt/move-selected :right true))} - :move-fast-left {:tooltip (ds/shift ds/left-arrow) - :command "shift+left" - :fn #(st/emit! (dwt/move-selected :left true))} + :move-fast-left {:tooltip (ds/shift ds/left-arrow) + :command "shift+left" + :fn #(st/emit! (dwt/move-selected :left true))} - :move-unit-up {:tooltip ds/up-arrow - :command "up" - :fn #(st/emit! (dwt/move-selected :up false))} + :move-unit-up {:tooltip ds/up-arrow + :command "up" + :fn #(st/emit! (dwt/move-selected :up false))} - :move-unit-down {:tooltip ds/down-arrow - :command "down" - :fn #(st/emit! (dwt/move-selected :down false))} + :move-unit-down {:tooltip ds/down-arrow + :command "down" + :fn #(st/emit! (dwt/move-selected :down false))} - :move-unit-left {:tooltip ds/right-arrow - :command "right" - :fn #(st/emit! (dwt/move-selected :right false))} + :move-unit-left {:tooltip ds/right-arrow + :command "right" + :fn #(st/emit! (dwt/move-selected :right false))} - :move-unit-right {:tooltip ds/left-arrow - :command "left" - :fn #(st/emit! (dwt/move-selected :left false))} + :move-unit-right {:tooltip ds/left-arrow + :command "left" + :fn #(st/emit! (dwt/move-selected :left false))} - :open-color-picker {:tooltip "I" - :command "i" - :fn #(st/emit! (mdc/picker-for-selected-shape))} + :open-color-picker {:tooltip "I" + :command "i" + :fn #(st/emit! (mdc/picker-for-selected-shape))} - :open-viewer {:tooltip "G V" - :command "g v" - :fn #(st/emit! (dw/go-to-viewer))} + :open-viewer {:tooltip "G V" + :command "g v" + :fn #(st/emit! (dw/go-to-viewer))} - :open-handoff {:tooltip "G H" - :command "g h" - :fn #(st/emit! (dw/go-to-viewer {:section :handoff}))} + :open-handoff {:tooltip "G H" + :command "g h" + :fn #(st/emit! (dw/go-to-viewer {:section :handoff}))} - :open-comments {:tooltip "G C" - :command "g c" - :fn #(st/emit! (dw/go-to-viewer {:section :comments}))} + :open-comments {:tooltip "G C" + :command "g c" + :fn #(st/emit! (dw/go-to-viewer {:section :comments}))} - :open-dashboard {:tooltip "G D" - :command "g d" - :fn #(st/emit! (dw/go-to-dashboard))} + :open-dashboard {:tooltip "G D" + :command "g d" + :fn #(st/emit! (dw/go-to-dashboard))} - :escape {:tooltip (ds/esc) - :command "escape" - :fn #(st/emit! :interrupt (dw/deselect-all true))} + :escape {:tooltip (ds/esc) + :command "escape" + :fn #(st/emit! :interrupt (dw/deselect-all true))} - :start-editing {:tooltip (ds/enter) - :command "enter" - :fn #(st/emit! (dw/start-editing-selected))} + :start-editing {:tooltip (ds/enter) + :command "enter" + :fn #(st/emit! (dw/start-editing-selected))} - :start-measure {:tooltip (ds/alt "") - :command ["alt" "."] - :type "keydown" - :fn #(st/emit! (dw/toggle-distances-display true))} + :start-measure {:tooltip (ds/alt "") + :command ["alt" "."] + :type "keydown" + :fn #(st/emit! (dw/toggle-distances-display true))} - :stop-measure {:tooltip (ds/alt "") - :command ["alt" "."] - :type "keyup" - :fn #(st/emit! (dw/toggle-distances-display false))} + :stop-measure {:tooltip (ds/alt "") + :command ["alt" "."] + :type "keyup" + :fn #(st/emit! (dw/toggle-distances-display false))} - :bool-union {:tooltip (ds/meta (ds/alt "U")) - :command (ds/c-mod "alt+u") - :fn #(st/emit! (dw/create-bool :union))} + :bool-union {:tooltip (ds/meta (ds/alt "U")) + :command (ds/c-mod "alt+u") + :fn #(st/emit! (dw/create-bool :union))} - :bool-difference {:tooltip (ds/meta (ds/alt "D")) - :command (ds/c-mod "alt+d") - :fn #(st/emit! (dw/create-bool :difference))} + :bool-difference {:tooltip (ds/meta (ds/alt "D")) + :command (ds/c-mod "alt+d") + :fn #(st/emit! (dw/create-bool :difference))} :bool-intersection {:tooltip (ds/meta (ds/alt "I")) :command (ds/c-mod "alt+i") @@ -329,7 +329,7 @@ :align-vcenter {:tooltip (ds/alt "V") :command "alt+v" :fn #(st/emit! (dw/align-objects :vcenter))} - + :align-bottom {:tooltip (ds/alt "S") :command "alt+s" :fn #(st/emit! (dw/align-objects :vbottom))} @@ -354,9 +354,9 @@ :command (ds/c-mod "alt+l") :fn #(st/emit! (dw/toggle-proportion-lock))} - :create-artboard-from-selection {:tooltip (ds/meta (ds/alt "G")) - :command (ds/c-mod "alt+g") - :fn #(st/emit! (dw/create-artboard-from-selection))} + :artboard-selection {:tooltip (ds/meta (ds/alt "G")) + :command (ds/c-mod "alt+g") + :fn #(st/emit! (dw/create-artboard-from-selection))} :hide-ui {:tooltip "\\" :command "\\" @@ -368,8 +368,16 @@ :thumbnail-set {:tooltip (ds/shift "T") :command "shift+t" - :fn #(st/emit! (dw/toggle-file-thumbnail-selected))}}) + :fn #(st/emit! (dw/toggle-file-thumbnail-selected))} + :show-pixel-grid {:tooltip (ds/shift ",") + :command "shift+," + :fn #(st/emit! (toggle-layout-flag :show-pixel-grid))} + + :snap-pixel-grid {:command "," + :tooltip "," + :fn #(st/emit! (toggle-layout-flag :snap-pixel-grid))} + }) (def opacity-shortcuts (into {} (->> diff --git a/frontend/src/app/main/data/workspace/svg_upload.cljs b/frontend/src/app/main/data/workspace/svg_upload.cljs index 0495075eb1..538dfaddbb 100644 --- a/frontend/src/app/main/data/workspace/svg_upload.cljs +++ b/frontend/src/app/main/data/workspace/svg_upload.cljs @@ -11,7 +11,7 @@ [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] - [app.common.math :as mth] + [app.common.pages :as cp] [app.common.pages.changes-builder :as pcb] [app.common.pages.helpers :as cph] [app.common.spec :refer [max-safe-int min-safe-int]] @@ -33,9 +33,7 @@ (defonce default-image {:x 0 :y 0 :width 1 :height 1 :rx 0 :ry 0}) (defn- assert-valid-num [attr num] - (when (or (nil? num) - (mth/nan? num) - (not (mth/finite? num)) + (when (or (not (d/num? num)) (>= num max-safe-int ) (<= num min-safe-int)) (ex/raise (str (d/name attr) " attribute invalid: " num))) @@ -168,7 +166,7 @@ (assoc :svg-attrs attrs) (assoc :svg-viewbox (-> (select-keys svg-data [:width :height]) (assoc :x offset-x :y offset-y))) - (gsh/setup-selrect)))) + (cp/setup-rect-selrect)))) (defn create-svg-root [frame-id svg-data] (let [{:keys [name x y width height offset-x offset-y]} svg-data] @@ -180,7 +178,7 @@ :height height :x (+ x offset-x) :y (+ y offset-y)} - (gsh/setup-selrect) + (cp/setup-rect-selrect) (assoc :svg-attrs (-> (:attrs svg-data) (dissoc :viewBox :xmlns) (d/without-keys usvg/inheritable-props)))))) @@ -200,7 +198,7 @@ (assoc :svg-attrs (d/without-keys attrs usvg/inheritable-props)) (assoc :svg-viewbox (-> (select-keys svg-data [:width :height]) (assoc :x offset-x :y offset-y))) - (gsh/setup-selrect)))) + (cp/setup-rect-selrect)))) (defn create-path-shape [name frame-id svg-data {:keys [attrs] :as data}] (when (and (contains? attrs :d) (seq (:d attrs))) @@ -230,14 +228,9 @@ (let [points (-> (gsh/rect->points rect-data) (gsh/transform-points transform)) - center (gsh/center-points points) - - rect-shape (-> (gsh/make-centered-rect center (:width rect-data) (:height rect-data)) - (update :width max 1) - (update :height max 1)) - - selrect (gsh/rect->selrect rect-shape) - + center (gsh/center-points points) + rect-shape (gsh/center->rect center (:width rect-data) (:height rect-data)) + selrect (gsh/rect->selrect rect-shape) rect-points (gsh/rect->points rect-shape) [shape-transform shape-transform-inv rotation] diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs index 58087a5599..7a1ccbcb0e 100644 --- a/frontend/src/app/main/data/workspace/texts.cljs +++ b/frontend/src/app/main/data/workspace/texts.cljs @@ -289,14 +289,6 @@ ;; --- RESIZE UTILS -(defn update-overflow-text [id value] - (ptk/reify ::update-overflow-text - ptk/UpdateEvent - (update [_ state] - (let [page-id (:current-page-id state)] - (update-in state [:workspace-data :pages-index page-id :objects id] assoc :overflow-text value))))) - - (def start-edit-if-selected (ptk/reify ::start-edit-if-selected ptk/UpdateEvent @@ -325,22 +317,13 @@ update-fn (fn [shape] (let [[new-width new-height] (get changes-map (:id shape)) - {:keys [selrect grow-type overflow-text]} (gsh/transform-shape shape) + {:keys [selrect grow-type]} (gsh/transform-shape shape) {shape-width :width shape-height :height} selrect modifier-width (gsh/resize-modifiers shape :width new-width) modifier-height (gsh/resize-modifiers shape :height new-height)] (cond-> shape - (and overflow-text (not= :fixed grow-type)) - (assoc :overflow-text false) - - (and (= :fixed grow-type) (not overflow-text) (> new-height shape-height)) - (assoc :overflow-text true) - - (and (= :fixed grow-type) overflow-text (<= new-height shape-height)) - (assoc :overflow-text false) - (and (not-changed? shape-width new-width) (= grow-type :auto-width)) (-> (assoc :modifiers modifier-width) (gsh/transform-shape)) diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index 71562c0a0c..f7b7d88ba1 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -114,23 +114,32 @@ (declare get-ignore-tree) (defn- set-modifiers - ([ids] (set-modifiers ids nil false)) - ([ids modifiers] (set-modifiers ids modifiers false)) + ([ids] + (set-modifiers ids nil false)) + + ([ids modifiers] + (set-modifiers ids modifiers false)) + ([ids modifiers ignore-constraints] + (set-modifiers ids modifiers ignore-constraints false)) + + ([ids modifiers ignore-constraints ignore-snap-pixel] (us/verify (s/coll-of uuid?) ids) (ptk/reify ::set-modifiers ptk/UpdateEvent (update [_ state] - (let [modifiers (or modifiers (get-in state [:workspace-local :modifiers] {})) - page-id (:current-page-id state) - objects (wsh/lookup-page-objects state page-id) - ids (into #{} (remove #(get-in objects [% :blocked] false)) ids) + (let [modifiers (or modifiers (get-in state [:workspace-local :modifiers] {})) + page-id (:current-page-id state) + objects (wsh/lookup-page-objects state page-id) + ids (into #{} (remove #(get-in objects [% :blocked] false)) ids) + layout (get state :workspace-layout) + snap-pixel? (and (not ignore-snap-pixel) (contains? layout :snap-pixel-grid)) setup-modifiers (fn [state id] (let [shape (get objects id)] (update state :workspace-modifiers - #(set-modifiers-recursive % objects shape modifiers ignore-constraints))))] + #(set-modifiers-recursive % objects shape modifiers ignore-constraints snap-pixel?))))] (reduce setup-modifiers state ids)))))) @@ -181,9 +190,12 @@ :ignore-tree ignore-tree ;; Attributes that can change in the transform. This way we don't have to check ;; all the attributes - :attrs [:selrect :points - :x :y - :width :height + :attrs [:selrect + :points + :x + :y + :width + :height :content :transform :transform-inverse @@ -232,9 +244,97 @@ [root transformed-root ignore-geometry?])) +(defn set-pixel-precision + "Adjust modifiers so they adjust to the pixel grid" + [modifiers shape] + + (if (or (some? (:resize-transform modifiers)) + (some? (:resize-transform-2 modifiers))) + ;; If we're working with a rotation we don't handle pixel precision because + ;; the transformation won't have the precision anyway + modifiers + + (let [center (gsh/center-shape shape) + base-bounds (-> (:points shape) (gsh/points->rect)) + + raw-bounds + (-> (gsh/transform-bounds (:points shape) center modifiers) + (gsh/points->rect)) + + flip-x? (neg? (get-in modifiers [:resize-vector :x])) + flip-y? (neg? (get-in modifiers [:resize-vector :y])) + + path? (= :path (:type shape)) + vertical-line? (and path? (<= (:width raw-bounds) 0.01)) + horizontal-line? (and path? (<= (:height raw-bounds) 0.01)) + + target-width (if vertical-line? + (:width raw-bounds) + (max 1 (mth/round (:width raw-bounds)))) + + target-height (if horizontal-line? + (:height raw-bounds) + (max 1 (mth/round (:height raw-bounds)))) + + target-p (cond-> (gpt/round (gpt/point raw-bounds)) + flip-x? + (update :x + target-width) + + flip-y? + (update :y + target-height)) + + ratio-width (/ target-width (:width raw-bounds)) + ratio-height (/ target-height (:height raw-bounds)) + + modifiers + (-> modifiers + (d/without-nils) + (d/update-in-when + [:resize-vector :x] #(* % ratio-width)) + + ;; If the resize-vector-2 modifier arrives means the resize-vector + ;; will only resize on the x axis + (cond-> (nil? (:resize-vector-2 modifiers)) + (d/update-in-when + [:resize-vector :y] #(* % ratio-height))) + + (d/update-in-when + [:resize-vector-2 :y] #(* % ratio-height))) + + origin (get modifiers :resize-origin) + origin-2 (get modifiers :resize-origin-2) + + resize-v (get modifiers :resize-vector) + resize-v-2 (get modifiers :resize-vector-2) + displacement (get modifiers :displacement) + + target-p-inv + (-> target-p + (gpt/transform + (cond-> (gmt/matrix) + (some? displacement) + (gmt/multiply (gmt/inverse displacement)) + + (and (some? resize-v) (some? origin)) + (gmt/scale (gpt/inverse resize-v) origin) + + (and (some? resize-v-2) (some? origin-2)) + (gmt/scale (gpt/inverse resize-v-2) origin-2)))) + + delta-v (gpt/subtract target-p-inv (gpt/point base-bounds)) + + modifiers + (-> modifiers + (d/update-when :displacement #(gmt/multiply (gmt/translate-matrix delta-v) %)) + (cond-> (nil? (:displacement modifiers)) + (assoc :displacement (gmt/translate-matrix delta-v))))] + modifiers))) + (defn- set-modifiers-recursive - [modif-tree objects shape modifiers ignore-constraints] + [modif-tree objects shape modifiers ignore-constraints snap-pixel?] + (let [children (map (d/getf objects) (:shapes shape)) + modifiers (cond-> modifiers snap-pixel? (set-pixel-precision shape)) transformed-rect (gsh/transform-selrect (:selrect shape) modifiers) set-child @@ -242,7 +342,7 @@ (let [child-modifiers (gsh/calc-child-modifiers shape child modifiers ignore-constraints transformed-rect)] (cond-> modif-tree (not (gsh/empty-modifiers? child-modifiers)) - (set-modifiers-recursive objects child child-modifiers ignore-constraints)))) + (set-modifiers-recursive objects child child-modifiers ignore-constraints snap-pixel?)))) modif-tree (-> modif-tree @@ -280,7 +380,6 @@ (dissoc :workspace-modifiers) (update :workspace-local dissoc :current-move-selected))))) - ;; -- Resize -------------------------------------------------------- (defn start-resize @@ -291,8 +390,8 @@ {:keys [rotation]} shape shape-center (gsh/center-shape shape) - shape-transform (:transform shape (gmt/matrix)) - shape-transform-inverse (:transform-inverse shape (gmt/matrix)) + shape-transform (:transform shape) + shape-transform-inverse (:transform-inverse shape) rotation (or rotation 0) @@ -399,7 +498,8 @@ (finish-transform)))))))) (defn update-dimensions - "Change size of shapes, from the sideber options form." + "Change size of shapes, from the sideber options form. + Will ignore pixel snap used in the options side panel" [ids attr value] (us/verify (s/coll-of ::us/uuid) ids) (us/verify #{:width :height} attr) @@ -408,15 +508,15 @@ ptk/UpdateEvent (update [_ state] (let [page-id (:current-page-id state) - objects (get-in state [:workspace-data :pages-index page-id :objects])] + objects (get-in state [:workspace-data :pages-index page-id :objects]) - (reduce (fn [state id] - (let [shape (get objects id) - modifiers (gsh/resize-modifiers shape attr value)] - (update state :workspace-modifiers - #(set-modifiers-recursive % objects shape modifiers false)))) - state - ids))) + update-modifiers + (fn [state id] + (let [shape (get objects id) + modifiers (gsh/resize-modifiers shape attr value)] + (update state :workspace-modifiers + #(set-modifiers-recursive % objects shape modifiers false false))))] + (reduce update-modifiers state ids))) ptk/WatchEvent (watch [_ _ _] @@ -439,26 +539,29 @@ group (gsh/selection-rect shapes) group-center (gsh/center-selrect group) initial-angle (gpt/angle @ms/mouse-position group-center) - calculate-angle (fn [pos ctrl? shift?] - (let [angle (- (gpt/angle pos group-center) initial-angle) - angle (if (neg? angle) (+ 360 angle) angle) - angle (if (= angle 360) - 0 - angle) - angle (if ctrl? - (* (mth/floor (/ angle 45)) 45) - angle) - angle (if shift? - (* (mth/floor (/ angle 15)) 15) - angle)] - angle))] + + calculate-angle + (fn [pos ctrl? shift?] + (let [angle (- (gpt/angle pos group-center) initial-angle) + angle (if (neg? angle) (+ 360 angle) angle) + angle (if (= angle 360) + 0 + angle) + angle (if ctrl? + (* (mth/floor (/ angle 45)) 45) + angle) + angle (if shift? + (* (mth/floor (/ angle 15)) 15) + angle)] + angle))] (rx/concat (->> ms/mouse-position (rx/with-latest vector ms/mouse-position-ctrl) (rx/with-latest vector ms/mouse-position-shift) - (rx/map (fn [[[pos ctrl?] shift?]] - (let [delta-angle (calculate-angle pos ctrl? shift?)] - (set-rotation-modifiers delta-angle shapes group-center)))) + (rx/map + (fn [[[pos ctrl?] shift?]] + (let [delta-angle (calculate-angle pos ctrl? shift?)] + (set-rotation-modifiers delta-angle shapes group-center)))) (rx/take-until stoper)) (rx/of (apply-modifiers (map :id shapes)) (finish-transform))))))) @@ -495,12 +598,13 @@ (watch [_ state stream] (let [initial (deref ms/mouse-position) selected (wsh/lookup-selected state {:omit-blocked? true}) - stopper (rx/filter ms/mouse-up? stream)] + stopper (rx/filter ms/mouse-up? stream) + zoom (get-in state [:workspace-local :zoom] 1)] (when-not (empty? selected) (->> ms/mouse-position (rx/map #(gpt/to-vec initial %)) (rx/map #(gpt/length %)) - (rx/filter #(> % 1)) + (rx/filter #(> % (/ 10 zoom))) (rx/take 1) (rx/with-latest vector ms/mouse-position-alt) (rx/mapcat @@ -574,7 +678,9 @@ (rx/of (finish-transform)) (rx/concat (->> position + ;; We ask for the snap position but we continue even if the result is not available (rx/with-latest vector snap-delta) + ;; We try to use the previous snap so we don't have to wait for the result of the new (rx/map snap/correct-snap-point) (rx/map #(hash-map :displacement (gmt/translate-matrix %))) (rx/map (partial set-modifiers ids)) @@ -654,8 +760,10 @@ cpos (gpt/point (:x bbox) (:y bbox)) pos (gpt/point (or (:x position) (:x bbox)) (or (:y position) (:y bbox))) - displ (gmt/translate-matrix (gpt/subtract pos cpos))] - (rx/of (set-modifiers [id] {:displacement displ}) + delta (gpt/subtract pos cpos) + displ (gmt/translate-matrix delta)] + + (rx/of (set-modifiers [id] {:displacement displ} false true) (apply-modifiers [id])))))) (defn- calculate-frame-for-move diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 99e338e563..1528dd1cc9 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -141,6 +141,9 @@ (def workspace-layout (l/derived :workspace-layout st/state)) +(def snap-pixel? + (l/derived #(contains? % :snap-pixel-grid) workspace-layout)) + (def workspace-file "A ref to a striped vision of file (without data)." (l/derived (fn [state] diff --git a/frontend/src/app/main/render.cljs b/frontend/src/app/main/render.cljs index e1ac07f885..7970bb1736 100644 --- a/frontend/src/app/main/render.cljs +++ b/frontend/src/app/main/render.cljs @@ -59,15 +59,14 @@ (defn- calculate-dimensions [{:keys [objects] :as data} vport] (let [shapes (cph/get-immediate-children objects) - to-finite (fn [val fallback] (if (not (mth/finite? val)) fallback val)) rect (cond->> (gsh/selection-rect shapes) (some? vport) (gal/adjust-to-viewport vport))] (-> rect - (update :x to-finite 0) - (update :y to-finite 0) - (update :width to-finite 100000) - (update :height to-finite 100000)))) + (update :x mth/finite 0) + (update :y mth/finite 0) + (update :width mth/finite 100000) + (update :height mth/finite 100000)))) (declare shape-wrapper-factory) diff --git a/frontend/src/app/main/snap.cljs b/frontend/src/app/main/snap.cljs index a78524c7ed..5bf8416860 100644 --- a/frontend/src/app/main/snap.cljs +++ b/frontend/src/app/main/snap.cljs @@ -22,7 +22,7 @@ (def ^:const snap-accuracy 10) (def ^:const snap-path-accuracy 10) -(def ^:const snap-distance-accuracy 10) +(def ^:const snap-distance-accuracy 20) (defn- remove-from-snap-points [remove-snap?] @@ -55,10 +55,6 @@ (and (d/not-empty? focus) (not (cp/is-in-focus? objects focus id))))))) -(defn- flatten-to-points - [query-result] - (mapcat (fn [[_ data]] (map :pt data)) query-result)) - (defn- calculate-distance [query-result point coord] (->> query-result (map (fn [[value _]] [(mth/abs (- value (coord point))) [(coord point) value]])))) @@ -82,16 +78,15 @@ ;; Otherwise the root frame is the common :else zero))) -(defn get-snap-points [page-id frame-id remove-snap? point coord] +(defn get-snap-points [page-id frame-id remove-snap? zoom point coord] (let [value (get point coord)] (->> (uw/ask! {:cmd :snaps/range-query :page-id page-id :frame-id frame-id :axis coord - :ranges [[(- value 0.5) (+ value 0.5)]]}) + :ranges [[(- value (/ 0.5 zoom)) (+ value (/ 0.5 zoom))]]}) (rx/take 1) - (rx/map (remove-from-snap-points remove-snap?)) - (rx/map flatten-to-points)))) + (rx/map (remove-from-snap-points remove-snap?))))) (defn- search-snap [page-id frame-id points coord remove-snap? zoom] @@ -195,7 +190,7 @@ snap-list (d/concat-vec lt-snap gt-snap between-snap) min-snap (reduce best-snap ##Inf snap-list)] - (if (mth/finite? min-snap) [0 min-snap] nil))) + (if (d/num? min-snap) [0 min-snap] nil))) (defn search-snap-distance [selrect coord shapes-lt shapes-gt zoom] (->> (rx/combine-latest shapes-lt shapes-gt) @@ -238,25 +233,35 @@ (rx/map #(or % (gpt/point 0 0))) (rx/map #(gpt/add point %))))) +(defn combine-snaps-points + ([] nil) + ([p1] p1) + ([p1 p2] + (cond + (nil? p2) p1 + (nil? p1) p2 + + :else + (gpt/point (mth/max-abs (:x p1) (:x p2)) + (mth/max-abs (:y p1) (:y p2)))))) + (defn closest-snap-move [page-id shapes objects layout zoom focus movev] (let [frame-id (snap-frame-id shapes) filter-shapes (into #{} (map :id shapes)) remove-snap? (make-remove-snap layout filter-shapes objects focus) - shape (if (> (count shapes) 1) - (->> shapes (map gsh/transform-shape) gsh/selection-rect (gsh/setup {:type :rect})) - (->> shapes (first))) + snap-points + (->> shapes + (gsh/selection-rect) + (sp/selrect-snap-points) + ;; Move the points in the translation vector + (map #(gpt/add % movev)))] - shapes-points (->> shape - (sp/shape-snap-points) - ;; Move the points in the translation vector - (map #(gpt/add % movev)))] - - (->> (rx/merge (closest-snap page-id frame-id shapes-points remove-snap? zoom) + (->> (rx/merge (closest-snap page-id frame-id snap-points remove-snap? zoom) (when (contains? layout :dynamic-alignment) (closest-distance-snap page-id shapes objects zoom movev))) - (rx/reduce gpt/min) + (rx/reduce combine-snaps-points) (rx/map #(or % (gpt/point 0 0)))))) @@ -359,9 +364,14 @@ 0) dy (if (not= 0 (:y snap-delta)) (- (+ (:y snap-pos) (:y snap-delta)) (:y position)) - 0)] + 0) + + ;; If the deltas (dx,dy) are bigger than the snap-accuracy means the stored snap + ;; is not valid, so we change to 0 + dx (if (> (mth/abs dx) snap-accuracy) 0 dx) + dy (if (> (mth/abs dy) snap-accuracy) 0 dy)] + (-> position + (update :x + dx) + (update :y + dy))) - (cond-> position - (<= (mth/abs dx) snap-accuracy) (update :x + dx) - (<= (mth/abs dy) snap-accuracy) (update :y + dy))) position)) diff --git a/frontend/src/app/main/ui/components/editable_select.cljs b/frontend/src/app/main/ui/components/editable_select.cljs index 42013291f9..9d4101bead 100644 --- a/frontend/src/app/main/ui/components/editable_select.cljs +++ b/frontend/src/app/main/ui/components/editable_select.cljs @@ -7,7 +7,7 @@ (ns app.main.ui.components.editable-select (:require [app.common.data :as d] - [app.common.math :as math] + [app.common.math :as mth] [app.common.uuid :as uuid] [app.main.ui.components.dropdown :refer [dropdown]] [app.main.ui.icons :as i] @@ -28,10 +28,6 @@ min-val (get params :min) max-val (get params :max) - num? (fn [val] (and (number? val) - (not (math/nan? val)) - (math/finite? val))) - emit-blur? (mf/use-ref nil) font-size-wrapper-ref (mf/use-ref) @@ -58,8 +54,7 @@ handle-change-input (fn [event] (let [value (-> event dom/get-target dom/get-value) - value (-> (or (d/parse-double value) value) - (math/precision 2))] + value (or (d/parse-double value) value)] (set-value value))) on-node-load @@ -89,8 +84,7 @@ (when (or up? down?) (dom/prevent-default event) (let [value (-> event dom/get-target dom/get-value) - value (-> (or (d/parse-double value) value) - (math/precision 2)) + value (or (d/parse-double value) value) increment (cond (kbd/shift? event) @@ -102,12 +96,11 @@ :else (if up? 1 -1)) - new-value (-> (+ value increment) - (math/precision 2)) + new-value (+ value increment) new-value (cond - (and (num? min-val) (< new-value min-val)) min-val - (and (num? max-val) (> new-value max-val)) max-val + (and (d/num? min-val) (< new-value min-val)) min-val + (and (d/num? max-val) (> new-value max-val)) max-val :else new-value)] (set-value new-value))))))) @@ -135,7 +128,7 @@ (let [wrapper-node (mf/ref-val font-size-wrapper-ref) node (dom/get-element-by-class "checked-element is-selected" wrapper-node) nodes (dom/get-elements-by-class "checked-element-value" wrapper-node) - closest (fn [a b] (first (sort-by #(math/abs (- % b)) a))) + closest (fn [a b] (first (sort-by #(mth/abs (- % b)) a))) closest-value (str (closest options value))] (when (:is-open? @state) (if (some? node) diff --git a/frontend/src/app/main/ui/components/numeric_input.cljs b/frontend/src/app/main/ui/components/numeric_input.cljs index c2e81f8371..fc8cabb21f 100644 --- a/frontend/src/app/main/ui/components/numeric_input.cljs +++ b/frontend/src/app/main/ui/components/numeric_input.cljs @@ -7,23 +7,17 @@ (ns app.main.ui.components.numeric-input (:require [app.common.data :as d] - [app.common.math :as math] [app.common.spec :as us] + [app.main.ui.formats :as fmt] [app.util.dom :as dom] [app.util.globals :as globals] [app.util.keyboard :as kbd] [app.util.object :as obj] [app.util.simple-math :as sm] - [app.util.strings :as ust] [goog.events :as events] [rumext.alpha :as mf]) (:import goog.events.EventType)) -(defn num? [val] - (and (number? val) - (not (math/nan? val)) - (math/finite? val))) - (mf/defc numeric-input {::mf/wrap-props false ::mf/forward-ref true} @@ -36,8 +30,8 @@ on-change (obj/get props "onChange") on-blur (obj/get props "onBlur") title (obj/get props "title") - default-val (obj/get props "default" 0) - precision (obj/get props "precision") + default-val (obj/get props "default") + nillable (obj/get props "nillable") ;; We need a ref pointing to the input dom element, but the user ;; of this component may provide one (that is forwarded here). @@ -78,34 +72,34 @@ parse-value (mf/use-callback - (mf/deps ref min-val max-val value) + (mf/deps ref min-val max-val value nillable default-val) (fn [] (let [input-node (mf/ref-val ref) new-value (-> (dom/get-value input-node) (sm/expr-eval value))] - (when (num? new-value) + (cond + (d/num? new-value) (-> new-value - (cond-> (number? precision) - (math/precision precision)) - (cond-> (nil? precision) - (math/round)) (cljs.core/max us/min-safe-int) (cljs.core/min us/max-safe-int) (cond-> - (num? min-val) + (d/num? min-val) (cljs.core/max min-val) - (num? max-val) - (cljs.core/min max-val))))))) + (d/num? max-val) + (cljs.core/min max-val))) + + nillable + default-val + + :else value)))) update-input (mf/use-callback (mf/deps ref) (fn [new-value] (let [input-node (mf/ref-val ref)] - (dom/set-value! input-node (if (some? precision) - (ust/format-precision new-value precision) - (str new-value)))))) + (dom/set-value! input-node (fmt/format-number new-value))))) apply-value (mf/use-callback @@ -122,24 +116,30 @@ (fn [event up? down?] (let [current-value (parse-value)] (when current-value - (let [increment (if (kbd/shift? event) + (let [increment (cond + (kbd/shift? event) (if up? (* step-val 10) (* step-val -10)) + + (kbd/alt? event) + (if up? (* step-val 0.1) (* step-val -0.1)) + + :else (if up? step-val (- step-val))) new-value (+ current-value increment) new-value (cond - (and wrap-value? (num? max-val) (num? min-val) + (and wrap-value? (d/num? max-val min-val) (> new-value max-val) up?) (-> new-value (- max-val) (+ min-val) (- step-val)) - (and wrap-value? (num? min-val) (num? max-val) + (and wrap-value? (d/num? max-val min-val) (< new-value min-val) down?) (-> new-value (- min-val) (+ max-val) (+ step-val)) - (and (num? min-val) (< new-value min-val)) + (and (d/num? min-val) (< new-value min-val)) min-val - (and (num? max-val) (> new-value max-val)) + (and (d/num? max-val) (> new-value max-val)) max-val :else new-value)] @@ -167,14 +167,19 @@ (mf/use-callback (mf/deps set-delta) (fn [event] - (set-delta event (< (.-deltaY event) 0) (> (.-deltaY event) 0)))) + (let [input-node (mf/ref-val ref)] + (when (dom/active? input-node) + (let [event (.getBrowserEvent ^js event)] + (dom/prevent-default event) + (dom/stop-propagation event) + (set-delta event (< (.-deltaY event) 0) (> (.-deltaY event) 0))))))) handle-blur (mf/use-callback (mf/deps parse-value apply-value update-input on-blur) (fn [_] (let [new-value (or (parse-value) default-val)] - (if new-value + (if (or nillable new-value) (apply-value new-value) (update-input new-value))) (when on-blur (on-blur)))) @@ -189,21 +194,20 @@ (dom/blur! current))))))) props (-> props - (obj/without ["value" "onChange"]) + (obj/without ["value" "onChange" "nillable"]) (obj/set! "className" "input-text") (obj/set! "type" "text") (obj/set! "ref" ref) - (obj/set! "defaultValue" value-str) + (obj/set! "defaultValue" (fmt/format-number value)) (obj/set! "title" title) - (obj/set! "onWheel" handle-mouse-wheel) (obj/set! "onKeyDown" handle-key-down) (obj/set! "onBlur" handle-blur))] (mf/use-effect - (mf/deps value-str) + (mf/deps value) (fn [] (when-let [input-node (mf/ref-val ref)] - (dom/set-value! input-node value-str)))) + (dom/set-value! input-node (fmt/format-number value))))) (mf/use-effect (mf/deps handle-blur) @@ -216,6 +220,13 @@ (let [handle-blur (:fn (mf/ref-val handle-blur-ref))] (handle-blur))))) + (mf/use-layout-effect + (mf/deps handle-mouse-wheel) + (fn [] + (let [keys [(events/listen (mf/ref-val ref) EventType.WHEEL handle-mouse-wheel #js {:pasive false})]] + #(doseq [key keys] + (events/unlistenByKey key))))) + (mf/use-layout-effect (fn [] (let [keys [(events/listen globals/window EventType.POINTERDOWN on-click) @@ -225,4 +236,3 @@ (events/unlistenByKey key))))) [:> :input props])) - diff --git a/frontend/src/app/main/ui/formats.cljs b/frontend/src/app/main/ui/formats.cljs new file mode 100644 index 0000000000..8cc39d56bc --- /dev/null +++ b/frontend/src/app/main/ui/formats.cljs @@ -0,0 +1,43 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) UXBOX Labs SL + +(ns app.main.ui.formats + (:require + [app.common.data :as d] + [app.common.data.macros :as dm] + [app.common.math :as mth])) + +(defn format-percent + ([value] + (format-percent value nil)) + + ([value {:keys [precision] :or {precision 2}}] + (when (d/num? value) + (let [percent-val (mth/precision (* value 100) precision)] + (dm/str percent-val "%"))))) + +(defn format-number + ([value] + (format-number value nil)) + ([value {:keys [precision] :or {precision 2}}] + (when (d/num? value) + (let [value (mth/precision value precision)] + (dm/str value))))) + +(defn format-pixels + ([value] + (format-pixels value nil)) + + ([value {:keys [precision] :or {precision 2}}] + (when (d/num? value) + (let [value (mth/precision value precision)] + (dm/str value "px"))))) + +(defn format-int + [value] + (when (d/num? value) + (let [value (mth/precision value 0)] + (dm/str value)))) diff --git a/frontend/src/app/main/ui/measurements.cljs b/frontend/src/app/main/ui/measurements.cljs index 08a4cd8d48..2f3181c0ef 100644 --- a/frontend/src/app/main/ui/measurements.cljs +++ b/frontend/src/app/main/ui/measurements.cljs @@ -12,6 +12,7 @@ [app.common.geom.shapes :as gsh] [app.common.math :as mth] [app.common.uuid :as uuid] + [app.main.ui.formats :as fmt] [rumext.alpha :as mf])) ;; ------------------------------------------------ @@ -97,7 +98,7 @@ (mf/defc size-display [{:keys [selrect zoom]}] (let [{:keys [x y width height]} selrect - size-label (dm/str (mth/round width) " x " (mth/round height)) + size-label (dm/str (fmt/format-number width) " x " (fmt/format-number height)) rect-height (/ size-display-height zoom) rect-width (/ (if (<= (count size-label) 9) @@ -164,7 +165,7 @@ :height distance-pill-height :style {:fill distance-text-color :font-size font-size}} - distance]])) + (fmt/format-pixels distance)]])) (mf/defc selection-rect [{:keys [selrect zoom]}] (let [{:keys [x y width height]} selrect @@ -214,7 +215,7 @@ {:x center-x :y center-y :zoom zoom - :distance (dm/str (mth/round distance) "px") + :distance distance :bounds bounds}]]))))) (mf/defc selection-guides [{:keys [bounds selrect zoom]}] diff --git a/frontend/src/app/main/ui/shapes/text/styles.cljs b/frontend/src/app/main/ui/shapes/text/styles.cljs index 232aa2acd2..db1f75013a 100644 --- a/frontend/src/app/main/ui/shapes/text/styles.cljs +++ b/frontend/src/app/main/ui/shapes/text/styles.cljs @@ -71,8 +71,8 @@ font-variant-id (:font-variant-id data) font-size (:font-size data) - fill-color (:fill-color data) - fill-opacity (:fill-opacity data) + fill-color (or (-> data :fills first :fill-color) (:fill-color data)) + fill-opacity (or (-> data :fills first :fill-opacity) (:fill-opacity data)) [r g b a] (uc/hex->rgba fill-color fill-opacity) text-color (when (and (some? fill-color) (some? fill-opacity)) diff --git a/frontend/src/app/main/ui/shapes/text/svg_text.cljs b/frontend/src/app/main/ui/shapes/text/svg_text.cljs index 02ecf4f212..75648bbfeb 100644 --- a/frontend/src/app/main/ui/shapes/text/svg_text.cljs +++ b/frontend/src/app/main/ui/shapes/text/svg_text.cljs @@ -8,6 +8,7 @@ (:require [app.common.data :as d] [app.common.geom.shapes :as gsh] + [app.common.math :as mth] [app.main.ui.context :as muc] [app.main.ui.shapes.attrs :as attrs] [app.main.ui.shapes.custom-stroke :refer [shape-custom-strokes]] @@ -24,6 +25,7 @@ (let [render-id (mf/use-ctx muc/render-ctx) {:keys [x y width height position-data] :as shape} (obj/get props "shape") + transform (str (gsh/transform-matrix shape)) ;; These position attributes are not really necesary but they are convenient for for the export @@ -48,8 +50,8 @@ [:> :g group-props (for [[index data] (d/enumerate position-data)] - (let [props (-> #js {:x (:x data) - :y (:y data) + (let [props (-> #js {:x (mth/round (:x data)) + :y (mth/round (:y data)) :dominantBaseline "ideographic" :style (-> #js {:fontFamily (:font-family data) :fontSize (:font-size data) @@ -61,7 +63,6 @@ :whiteSpace "pre"} (obj/set! "fill" (str "url(#fill-" index "-" render-id ")")))}) shape (assoc shape :fills (:fills data))] + [:& shape-custom-strokes {:shape shape} [:> :text props (:text data)]]))]])) - - diff --git a/frontend/src/app/main/ui/viewer/handoff/attributes/common.cljs b/frontend/src/app/main/ui/viewer/handoff/attributes/common.cljs index c7fa443edc..624ae2ab41 100644 --- a/frontend/src/app/main/ui/viewer/handoff/attributes/common.cljs +++ b/frontend/src/app/main/ui/viewer/handoff/attributes/common.cljs @@ -6,7 +6,6 @@ (ns app.main.ui.viewer.handoff.attributes.common (:require - [app.common.math :as mth] [app.main.store :as st] [app.main.ui.components.color-bullet :refer [color-bullet color-name]] [app.main.ui.components.copy-button :refer [copy-button]] @@ -50,9 +49,9 @@ (if (:gradient color) [:& color-name {:color color}] (case format - :rgba (let [[r g b a] (->> (uc/hex->rgba (:color color) (:opacity color)) (map #(mth/precision % 2)))] + :rgba (let [[r g b a] (uc/hex->rgba (:color color) (:opacity color))] [:div (str/fmt "%s, %s, %s, %s" r g b a)]) - :hsla (let [[h s l a] (->> (uc/hex->hsla (:color color) (:opacity color)) (map #(mth/precision % 2)))] + :hsla (let [[h s l a] (uc/hex->hsla (:color color) (:opacity color))] [:div (str/fmt "%s, %s, %s, %s" h s l a)]) [:* [:& color-name {:color color}] diff --git a/frontend/src/app/main/ui/viewer/handoff/attributes/layout.cljs b/frontend/src/app/main/ui/viewer/handoff/attributes/layout.cljs index b2da8eba36..ccbd9abc9e 100644 --- a/frontend/src/app/main/ui/viewer/handoff/attributes/layout.cljs +++ b/frontend/src/app/main/ui/viewer/handoff/attributes/layout.cljs @@ -6,7 +6,6 @@ (ns app.main.ui.viewer.handoff.attributes.layout (:require - [app.common.math :as mth] [app.common.spec.radius :as ctr] [app.main.ui.components.copy-button :refer [copy-button]] [app.util.code-gen :as cg] @@ -39,46 +38,46 @@ [:* [:div.attributes-unit-row [:div.attributes-label (t locale "handoff.attributes.layout.width")] - [:div.attributes-value (mth/precision width 2) "px"] + [:div.attributes-value width "px"] [:& copy-button {:data (copy-data selrect :width)}]] [:div.attributes-unit-row [:div.attributes-label (t locale "handoff.attributes.layout.height")] - [:div.attributes-value (mth/precision height 2) "px"] + [:div.attributes-value height "px"] [:& copy-button {:data (copy-data selrect :height)}]] (when (not= (:x shape) 0) [:div.attributes-unit-row [:div.attributes-label (t locale "handoff.attributes.layout.left")] - [:div.attributes-value (mth/precision x 2) "px"] + [:div.attributes-value x "px"] [:& copy-button {:data (copy-data selrect :x)}]]) (when (not= (:y shape) 0) [:div.attributes-unit-row [:div.attributes-label (t locale "handoff.attributes.layout.top")] - [:div.attributes-value (mth/precision y 2) "px"] + [:div.attributes-value y "px"] [:& copy-button {:data (copy-data selrect :y)}]]) (when (ctr/radius-1? shape) [:div.attributes-unit-row [:div.attributes-label (t locale "handoff.attributes.layout.radius")] - [:div.attributes-value (mth/precision (:rx shape 0) 2) "px"] + [:div.attributes-value (:rx shape 0) "px"] [:& copy-button {:data (copy-data shape :rx)}]]) (when (ctr/radius-4? shape) [:div.attributes-unit-row [:div.attributes-label (t locale "handoff.attributes.layout.radius")] [:div.attributes-value - (mth/precision (:r1 shape) 2) ", " - (mth/precision (:r2 shape) 2) ", " - (mth/precision (:r3 shape) 2) ", " - (mth/precision (:r4 shape) 2) "px"] + (:r1 shape) ", " + (:r2 shape) ", " + (:r3 shape) ", " + (:r4 shape) "px"] [:& copy-button {:data (copy-data shape :r1)}]]) (when (not= (:rotation shape 0) 0) [:div.attributes-unit-row [:div.attributes-label (t locale "handoff.attributes.layout.rotation")] - [:div.attributes-value (mth/precision (:rotation shape) 2) "deg"] + [:div.attributes-value (:rotation shape) "deg"] [:& copy-button {:data (copy-data shape :rotation)}]])])) diff --git a/frontend/src/app/main/ui/viewer/handoff/attributes/stroke.cljs b/frontend/src/app/main/ui/viewer/handoff/attributes/stroke.cljs index d04f8832cb..1564d73c51 100644 --- a/frontend/src/app/main/ui/viewer/handoff/attributes/stroke.cljs +++ b/frontend/src/app/main/ui/viewer/handoff/attributes/stroke.cljs @@ -7,7 +7,6 @@ (ns app.main.ui.viewer.handoff.attributes.stroke (:require [app.common.data :as d] - [app.common.math :as mth] [app.main.ui.components.copy-button :refer [copy-button]] [app.main.ui.viewer.handoff.attributes.common :refer [color-row]] [app.util.code-gen :as cg] @@ -67,7 +66,7 @@ stroke-alignment (or stroke-alignment :center)] [:div.attributes-stroke-row [:div.attributes-label (t locale "handoff.attributes.stroke.width")] - [:div.attributes-value (mth/precision (:stroke-width shape) 2) "px"] + [:div.attributes-value (:stroke-width shape) "px"] [:div.attributes-value (->> stroke-style d/name (str "handoff.attributes.stroke.style.") (t locale))] [:div.attributes-label (->> stroke-alignment d/name (str "handoff.attributes.stroke.alignment.") (t locale))] [:& copy-button {:data (copy-stroke-data shape)}]])])) diff --git a/frontend/src/app/main/ui/viewer/handoff/attributes/svg.cljs b/frontend/src/app/main/ui/viewer/handoff/attributes/svg.cljs index 8e126e0149..ea0f6c4e50 100644 --- a/frontend/src/app/main/ui/viewer/handoff/attributes/svg.cljs +++ b/frontend/src/app/main/ui/viewer/handoff/attributes/svg.cljs @@ -6,16 +6,12 @@ (ns app.main.ui.viewer.handoff.attributes.svg (:require - #_[app.common.math :as mth] - #_[app.main.ui.icons :as i] - #_[app.util.code-gen :as cg] [app.common.data :as d] [app.main.ui.components.copy-button :refer [copy-button]] [app.util.i18n :refer [tr]] [cuerdas.core :as str] [rumext.alpha :as mf])) - (defn map->css [attr] (->> attr (map (fn [[attr-key attr-value]] (str (d/name attr-key) ":" attr-value))) diff --git a/frontend/src/app/main/ui/viewer/header.cljs b/frontend/src/app/main/ui/viewer/header.cljs index 4427c7767a..b32adfbc15 100644 --- a/frontend/src/app/main/ui/viewer/header.cljs +++ b/frontend/src/app/main/ui/viewer/header.cljs @@ -6,13 +6,13 @@ (ns app.main.ui.viewer.header (:require - [app.common.math :as mth] [app.main.data.modal :as modal] [app.main.data.viewer :as dv] [app.main.data.viewer.shortcuts :as sc] [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.components.dropdown :refer [dropdown]] + [app.main.ui.formats :as fmt] [app.main.ui.icons :as i] [app.main.ui.viewer.comments :refer [comments-menu]] [app.main.ui.viewer.interactions :refer [flows-menu interactions-menu]] @@ -32,7 +32,7 @@ :as props}] (let [show-dropdown? (mf/use-state false)] [:div.zoom-widget {:on-click #(reset! show-dropdown? true)} - [:span.label {} (str (mth/round (* 100 zoom)) "%")] + [:span.label (fmt/format-percent zoom)] [:span.icon i/arrow-down] [:& dropdown {:show @show-dropdown? :on-close #(reset! show-dropdown? false)} @@ -43,7 +43,7 @@ (dom/stop-propagation event) (dom/prevent-default event) (on-decrease))} "-"] - [:p.zoom-size {} (str (mth/round (* 100 zoom)) "%")] + [:p.zoom-size (fmt/format-percent zoom)] [:button {:on-click (fn [event] (dom/stop-propagation event) (dom/prevent-default event) diff --git a/frontend/src/app/main/ui/workspace/colorpalette.cljs b/frontend/src/app/main/ui/workspace/colorpalette.cljs index ceec91be7c..64eb5bc8fa 100644 --- a/frontend/src/app/main/ui/workspace/colorpalette.cljs +++ b/frontend/src/app/main/ui/workspace/colorpalette.cljs @@ -6,7 +6,6 @@ (ns app.main.ui.workspace.colorpalette (:require - [app.common.math :as mth] [app.main.data.workspace.colors :as mdc] [app.main.refs :as refs] [app.main.store :as st] @@ -57,7 +56,7 @@ (let [state (mf/use-state {:show-menu false}) width (:width @state 0) - visible (mth/round (/ width 66)) + visible (/ width 66) offset (:offset @state 0) max-offset (- (count current-colors) @@ -75,7 +74,7 @@ (swap! state update :offset (fn [offset] (if (pos? offset) - (max (- offset (mth/round (/ visible 2))) 0) + (max (- offset (/ visible 2)) 0) offset))))) on-right-arrow-click @@ -85,7 +84,7 @@ (swap! state update :offset (fn [offset] (if (< offset max-offset) - (min max-offset (+ offset (mth/round (/ visible 2)))) + (min max-offset (+ offset (/ visible 2))) offset))))) on-scroll diff --git a/frontend/src/app/main/ui/workspace/colorpicker/color_inputs.cljs b/frontend/src/app/main/ui/workspace/colorpicker/color_inputs.cljs index 00a0c78511..b1645d3f64 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker/color_inputs.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker/color_inputs.cljs @@ -6,7 +6,7 @@ (ns app.main.ui.workspace.colorpicker.color-inputs (:require - [app.common.math :as math] + [app.common.math :as mth] [app.util.color :as uc] [app.util.dom :as dom] [rumext.alpha :as mf])) @@ -52,7 +52,7 @@ on-change-property (fn [property max-value] (fn [e] - (let [val (-> e dom/get-target-val (math/clamp 0 max-value)) + (let [val (-> e dom/get-target-val (mth/clamp 0 max-value)) val (if (#{:s} property) (/ val 100) val)] (when (not (nil? val)) (if (#{:r :g :b} property) @@ -72,7 +72,7 @@ on-change-opacity (fn [e] - (when-let [new-alpha (-> e dom/get-target-val (math/clamp 0 100) (/ 100))] + (when-let [new-alpha (-> e dom/get-target-val (mth/clamp 0 100) (/ 100))] (on-change {:alpha new-alpha})))] @@ -86,9 +86,9 @@ (when (and property-val property-ref) (when-let [node (mf/ref-val property-ref)] (case ref-key - (:s :alpha) (dom/set-value! node (math/round (* property-val 100))) + (:s :alpha) (dom/set-value! node (* property-val 100)) :hex (dom/set-value! node property-val) - (dom/set-value! node (math/round property-val))))))))) + (dom/set-value! node property-val)))))))) [:div.color-values {:class (when disable-opacity "disable-opacity")} @@ -156,7 +156,7 @@ :min 0 :step 1 :max 100 - :default-value (if (= alpha :multiple) "" (math/precision alpha 2)) + :default-value (if (= alpha :multiple) "" alpha) :on-change on-change-opacity}]) [:label.hex-label {:for "hex-value"} "HEX"] diff --git a/frontend/src/app/main/ui/workspace/colorpicker/harmony.cljs b/frontend/src/app/main/ui/workspace/colorpicker/harmony.cljs index 69247a2d09..b73e85843a 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker/harmony.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker/harmony.cljs @@ -7,7 +7,7 @@ (ns app.main.ui.workspace.colorpicker.harmony (:require [app.common.geom.point :as gpt] - [app.common.math :as math] + [app.common.math :as mth] [app.main.ui.workspace.colorpicker.slider-selector :refer [slider-selector]] [app.util.color :as uc] [app.util.dom :as dom] @@ -28,9 +28,9 @@ (.clearRect ctx 0 0 width height) (doseq [degrees (range 0 360 step)] - (let [degrees-rad (math/radians degrees) - x (* radius (math/cos (- degrees-rad))) - y (* radius (math/sin (- degrees-rad)))] + (let [degrees-rad (mth/radians degrees) + x (* radius (mth/cos (- degrees-rad))) + y (* radius (mth/sin (- degrees-rad)))] (obj/set! ctx "strokeStyle" (str/format "hsl(%s, 100%, 50%)" degrees)) (.beginPath ctx) (.moveTo ctx cx cy) @@ -43,15 +43,15 @@ (obj/set! ctx "fillStyle" grd) (.beginPath ctx) - (.arc ctx cx cy radius 0 (* 2 math/PI) true) + (.arc ctx cx cy radius 0 (* 2 mth/PI) true) (.closePath ctx) (.fill ctx)))) (defn color->point [canvas-side hue saturation] - (let [hue-rad (math/radians (- hue)) - comp-x (* saturation (math/cos hue-rad)) - comp-y (* saturation (math/sin hue-rad)) + (let [hue-rad (mth/radians (- hue)) + comp-x (* saturation (mth/cos hue-rad)) + comp-y (* saturation (mth/sin hue-rad)) x (+ (/ canvas-side 2) (* comp-x (/ canvas-side 2))) y (+ (/ canvas-side 2) (* comp-y (/ canvas-side 2)))] (gpt/point x y))) @@ -68,15 +68,15 @@ calculate-pos (fn [ev] (let [{:keys [left right top bottom]} (-> ev dom/get-target dom/get-bounding-rect) {:keys [x y]} (-> ev dom/get-client-position) - px (math/clamp (/ (- x left) (- right left)) 0 1) - py (math/clamp (/ (- y top) (- bottom top)) 0 1) + px (mth/clamp (/ (- x left) (- right left)) 0 1) + py (mth/clamp (/ (- y top) (- bottom top)) 0 1) px (- (* 2 px) 1) py (- (* 2 py) 1) - angle (math/degrees (math/atan2 px py)) - new-hue (math/precision (mod (- angle 90 ) 360) 2) - new-saturation (math/clamp (math/distance [px py] [0 0]) 0 1) + angle (mth/degrees (mth/atan2 px py)) + new-hue (mod (- angle 90 ) 360) + new-saturation (mth/clamp (mth/distance [px py] [0 0]) 0 1) hex (uc/hsv->hex [new-hue new-saturation value]) [r g b] (uc/hex->rgb hex)] (on-change {:hex hex diff --git a/frontend/src/app/main/ui/workspace/colorpicker/ramp.cljs b/frontend/src/app/main/ui/workspace/colorpicker/ramp.cljs index e9bd1d91d5..09a8cfa419 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker/ramp.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker/ramp.cljs @@ -6,7 +6,7 @@ (ns app.main.ui.workspace.colorpicker.ramp (:require - [app.common.math :as math] + [app.common.math :as mth] [app.main.ui.components.color-bullet :refer [color-bullet]] [app.main.ui.workspace.colorpicker.slider-selector :refer [slider-selector]] [app.util.color :as uc] @@ -19,8 +19,8 @@ (fn [ev] (let [{:keys [left right top bottom]} (-> ev dom/get-target dom/get-bounding-rect) {:keys [x y]} (-> ev dom/get-client-position) - px (math/clamp (/ (- x left) (- right left)) 0 1) - py (* 255 (- 1 (math/clamp (/ (- y top) (- bottom top)) 0 1)))] + px (mth/clamp (/ (- x left) (- right left)) 0 1) + py (* 255 (- 1 (mth/clamp (/ (- y top) (- bottom top)) 0 1)))] (on-change px py))) handle-start-drag diff --git a/frontend/src/app/main/ui/workspace/colorpicker/slider_selector.cljs b/frontend/src/app/main/ui/workspace/colorpicker/slider_selector.cljs index 5e72584c4c..9c47681d26 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker/slider_selector.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker/slider_selector.cljs @@ -6,7 +6,7 @@ (ns app.main.ui.workspace.colorpicker.slider-selector (:require - [app.common.math :as math] + [app.common.math :as mth] [app.util.dom :as dom] [app.util.object :as obj] [rumext.alpha :as mf])) @@ -41,13 +41,13 @@ (let [{:keys [left right top bottom]} (-> ev dom/get-target dom/get-bounding-rect) {:keys [x y]} (-> ev dom/get-client-position) unit-value (if vertical? - (math/clamp (/ (- bottom y) (- bottom top)) 0 1) - (math/clamp (/ (- x left) (- right left)) 0 1)) + (mth/clamp (/ (- bottom y) (- bottom top)) 0 1) + (mth/clamp (/ (- x left) (- right left)) 0 1)) unit-value (if reverse? - (math/abs (- unit-value 1.0)) + (mth/abs (- unit-value 1.0)) unit-value) value (+ min-value (* unit-value (- max-value min-value)))] - (on-change (math/precision value 2)))))] + (on-change value))))] [:div.slider-selector {:class (str (if vertical? "vertical " "") class) @@ -60,7 +60,7 @@ (- max-value min-value)) 100) value-percent (if reverse? - (math/abs (- value-percent 100)) + (mth/abs (- value-percent 100)) value-percent) value-percent-str (str value-percent "%") diff --git a/frontend/src/app/main/ui/workspace/context_menu.cljs b/frontend/src/app/main/ui/workspace/context_menu.cljs index 6f4a88ddc7..c1a4e00a0c 100644 --- a/frontend/src/app/main/ui/workspace/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/context_menu.cljs @@ -225,7 +225,7 @@ (when (not has-frame?) [:* [:& menu-entry {:title (tr "workspace.shape.menu.create-artboard-from-selection") - :shortcut (sc/get-tooltip :create-artboard-from-selection) + :shortcut (sc/get-tooltip :artboard-selection) :on-click do-create-artboard-from-selection}] [:& menu-separator]])])) diff --git a/frontend/src/app/main/ui/workspace/header.cljs b/frontend/src/app/main/ui/workspace/header.cljs index 81b2361407..b7097bd395 100644 --- a/frontend/src/app/main/ui/workspace/header.cljs +++ b/frontend/src/app/main/ui/workspace/header.cljs @@ -7,7 +7,6 @@ (ns app.main.ui.workspace.header (:require [app.common.data :as d] - [app.common.math :as mth] [app.config :as cf] [app.main.data.events :as ev] [app.main.data.messages :as dm] @@ -18,6 +17,7 @@ [app.main.repo :as rp] [app.main.store :as st] [app.main.ui.components.dropdown :refer [dropdown]] + [app.main.ui.formats :as fmt] [app.main.ui.hooks.resize :as r] [app.main.ui.icons :as i] [app.main.ui.workspace.presence :refer [active-sessions]] @@ -71,7 +71,7 @@ :as props}] (let [show-dropdown? (mf/use-state false)] [:div.zoom-widget {:on-click #(reset! show-dropdown? true)} - [:span.label {} (str (mth/round (* 100 zoom)) "%")] + [:span.label (fmt/format-percent zoom {:precision 0})] [:span.icon i/arrow-down] [:& dropdown {:show @show-dropdown? :on-close #(reset! show-dropdown? false)} @@ -82,7 +82,7 @@ (dom/stop-propagation event) (dom/prevent-default event) (on-decrease))} "-"] - [:p.zoom-size {} (str (mth/round (* 100 zoom)) "%")] + [:p.zoom-size {} (fmt/format-percent zoom {:precision 0})] [:button {:on-click (fn [event] (dom/stop-propagation event) (dom/prevent-default event) @@ -346,6 +346,13 @@ (tr "workspace.header.menu.hide-artboard-names") (tr "workspace.header.menu.show-artboard-names"))]] + [:li {:on-click #(st/emit! (toggle-flag :show-pixel-grid))} + [:span + (if (contains? layout :show-pixel-grid) + (tr "workspace.header.menu.hide-pixel-grid") + (tr "workspace.header.menu.show-pixel-grid"))] + [:span.shortcut (sc/get-tooltip :show-pixel-grid)]] + [:li {:on-click #(st/emit! (-> (toggle-flag :hide-ui) (vary-meta assoc ::ev/origin "workspace-menu")))} [:span @@ -376,6 +383,13 @@ (tr "workspace.header.menu.enable-dynamic-alignment"))] [:span.shortcut (sc/get-tooltip :toggle-alignment)]] + [:li {:on-click #(st/emit! (toggle-flag :snap-pixel-grid))} + [:span + (if (contains? layout :snap-pixel-grid) + (tr "workspace.header.menu.disable-snap-pixel-grid") + (tr "workspace.header.menu.enable-snap-pixel-grid"))] + [:span.shortcut (sc/get-tooltip :snap-pixel-grid)]] + [:li {:on-click #(st/emit! (modal/show {:type :nudge-option}))} [:span (tr "modals.nudge-title")]]]]])) diff --git a/frontend/src/app/main/ui/workspace/left_toolbar.cljs b/frontend/src/app/main/ui/workspace/left_toolbar.cljs index 584d8ce6c4..11a0b2ae22 100644 --- a/frontend/src/app/main/ui/workspace/left_toolbar.cljs +++ b/frontend/src/app/main/ui/workspace/left_toolbar.cljs @@ -7,7 +7,6 @@ (ns app.main.ui.workspace.left-toolbar (:require [app.common.geom.point :as gpt] - [app.common.math :as mth] [app.common.media :as cm] [app.main.data.events :as ev] [app.main.data.workspace :as dw] @@ -40,8 +39,8 @@ ;; We don't want to add a ref because that redraws the component ;; for everychange. Better direct access on the callback. (let [vbox (deref refs/vbox) - x (mth/round (+ (:x vbox) (/ (:width vbox) 2))) - y (mth/round (+ (:y vbox) (/ (:height vbox) 2))) + x (+ (:x vbox) (/ (:width vbox) 2)) + y (+ (:y vbox) (/ (:height vbox) 2)) params {:file-id (:id file) :blobs (seq blobs) :position (gpt/point x y)}] diff --git a/frontend/src/app/main/ui/workspace/nudge.cljs b/frontend/src/app/main/ui/workspace/nudge.cljs index 8574be2460..0bab8bed15 100644 --- a/frontend/src/app/main/ui/workspace/nudge.cljs +++ b/frontend/src/app/main/ui/workspace/nudge.cljs @@ -49,12 +49,12 @@ [:div.input-wrapper [:span [:p.nudge-subtitle (tr "modals.small-nudge")] - [:> numeric-input {:min 1 + [:> numeric-input {:min 0.01 :value (:small nudge) :on-change update-small}]]] [:div.input-wrapper [:span [:p.nudge-subtitle (tr "modals.big-nudge")] - [:> numeric-input {:min 1 + [:> numeric-input {:min 0.01 :value (:big nudge) :on-change update-big}]]]]]])) diff --git a/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs b/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs index 2ccc1583da..f89dd55f7f 100644 --- a/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs @@ -89,6 +89,7 @@ :style {:cursor (cond (= edit-mode :draw) cur/pen-node (= edit-mode :move) cur/pointer-node) + :stroke-width 0 :fill "none"}}]])) (mf/defc path-handler [{:keys [index prefix point handler zoom selected? hover? edit-mode snap-angle?]}] @@ -147,7 +148,8 @@ :on-mouse-enter on-enter :on-mouse-leave on-leave :style {:cursor (when (= edit-mode :move) cur/pointer-move) - :fill "none"}}]]))) + :fill "none" + :stroke-width 0}}]]))) (mf/defc path-preview [{:keys [zoom command from]}] [:g.preview {:style {:pointer-events "none"}} diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs index 0fa376e891..0f99378352 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs @@ -6,7 +6,6 @@ (ns app.main.ui.workspace.sidebar.options.menus.frame-grid (:require - [app.common.math :as mth] [app.main.data.workspace.grid :as dw] [app.main.refs :as refs] [app.main.store :as st] @@ -71,8 +70,7 @@ (let [{:keys [margin gutter item-length]} (:params grid) frame-length (if (= :column (:type grid)) frame-width frame-height) item-length (if (nil? size) - (-> (gg/calculate-default-item-length frame-length margin gutter) - (mth/precision 2)) + (gg/calculate-default-item-length frame-length margin gutter) item-length)] (-> grid (update :params assoc :size size :item-length item-length) @@ -140,7 +138,7 @@ (if (= type :square) [:div.input-element.pixels {:title (tr "workspace.options.size")} - [:> numeric-input {:min 1 + [:> numeric-input {:min 0.01 :value (or (:size params) "") :no-validate true :on-change (handle-change :params :size)}]] @@ -162,7 +160,7 @@ (when (= :square type) [:& input-row {:label (tr "workspace.options.grid.params.size") :class "pixels" - :min 1 + :min 0.01 :value (:size params) :on-change (handle-change :params :size)}]) @@ -207,7 +205,7 @@ [:> numeric-input {:placeholder "Auto" :value (or (:item-length params) "") - :default nil + :nillable true :on-change handle-change-item-length}]]) (when (#{:row :column} type) @@ -216,11 +214,15 @@ :class "pixels" :value (:gutter params) :min 0 + :nillable true + :default 0 :placeholder "0" :on-change (handle-change :params :gutter)}] [:& input-row {:label (tr "workspace.options.grid.params.margin") :class "pixels" :min 0 + :nillable true + :default 0 :placeholder "0" :value (:margin params) :on-change (handle-change :params :margin)}]]) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layer.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layer.cljs index cbcbaf7d83..a1a1959917 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layer.cljs @@ -7,7 +7,6 @@ (ns app.main.ui.workspace.sidebar.options.menus.layer (:require [app.common.data :as d] - [app.common.math :as mth] [app.main.data.workspace.changes :as dch] [app.main.store :as st] [app.main.ui.components.numeric-input :refer [numeric-input]] @@ -23,8 +22,7 @@ "" (str (-> opacity (d/coalesce 1) - (* 100) - (mth/round))))) + (* 100))))) (defn select-all [event] (dom/select-text! (dom/get-target event))) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs index 5b7a597477..d592077543 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs @@ -8,7 +8,6 @@ (:require [app.common.data :as d] [app.common.geom.shapes :as gsh] - [app.common.math :as math] [app.common.spec.radius :as ctr] [app.main.data.workspace :as udw] [app.main.data.workspace.changes :as dch] @@ -42,14 +41,6 @@ :svg-raw #{:size :position :rotation} :text #{:size :position :rotation}}) -(defn- attr->string [attr values] - (let [value (attr values)] - (if (= value :multiple) - "" - (str (-> value - (d/coalesce 0) - (math/precision 2)))))) - (declare +size-presets+) ;; -- User/drawing coords @@ -204,8 +195,8 @@ (fn [] (when (and (= radius-mode :radius-1) (= @radius-multi? false)) - ;; when going back from radius-multi to normal radius-1, - ;; restore focus to the newly created numeric-input + ;; when going back from radius-multi to normal radius-1, + ;; restore focus to the newly created numeric-input (let [radius-input (mf/ref-val radius-input-ref)] (dom/focus! radius-input))))) @@ -239,20 +230,20 @@ [:div.row-flex [:span.element-set-subtitle (tr "workspace.options.size")] [:div.input-element.width {:title (tr "workspace.options.width")} - [:> numeric-input {:min 1 + [:> numeric-input {:min 0.01 :no-validate true :placeholder "--" :on-click select-all :on-change on-width-change - :value (attr->string :width values)}]] + :value (:width values)}]] [:div.input-element.height {:title (tr "workspace.options.height")} - [:> numeric-input {:min 1 + [:> numeric-input {:min 0.01 :no-validate true :placeholder "--" :on-click select-all :on-change on-height-change - :value (attr->string :height values)}]] + :value (:height values)}]] [:div.lock-size {:class (dom/classnames :selected (true? proportion-lock) @@ -271,15 +262,13 @@ :placeholder "--" :on-click select-all :on-change on-pos-x-change - :value (attr->string :x values) - :precision 2}]] + :value (:x values)}]] [:div.input-element.Yaxis {:title (tr "workspace.options.y")} [:> numeric-input {:no-validate true :placeholder "--" :on-click select-all :on-change on-pos-y-change - :value (attr->string :y values) - :precision 2}]]]) + :value (:y values)}]]]) ;; ROTATION (when (options :rotation) @@ -290,19 +279,12 @@ {:no-validate true :min 0 :max 359 + :default 0 :data-wrap true :placeholder "--" :on-click select-all :on-change on-rotation-change - :value (attr->string :rotation values)}]] - #_[:input.slidebar - {:type "range" - :min "0" - :max "359" - :step "10" - :no-validate true - :on-change on-rotation-change - :value (attr->string :rotation values)}]]) + :value (:rotation values)}]]]) ;; RADIUS (when (options :radius) @@ -330,13 +312,14 @@ :min 0 :on-click select-all :on-change on-radius-1-change - :value (attr->string :rx values)}]] + :value (:rx values)}]] @radius-multi? [:div.input-element.mini {:title (tr "workspace.options.radius")} [:input.input-text {:type "number" :placeholder "--" + :min 0 :on-click select-all :on-change on-radius-multi-change :value ""}]] @@ -349,7 +332,7 @@ :min 0 :on-click select-all :on-change on-radius-r1-change - :value (attr->string :r1 values)}]] + :value (:r1 values)}]] [:div.input-element.mini {:title (tr "workspace.options.radius")} [:> numeric-input @@ -357,7 +340,7 @@ :min 0 :on-click select-all :on-change on-radius-r2-change - :value (attr->string :r2 values)}]] + :value (:r2 values)}]] [:div.input-element.mini {:title (tr "workspace.options.radius")} [:> numeric-input @@ -365,7 +348,7 @@ :min 0 :on-click select-all :on-change on-radius-r3-change - :value (attr->string :r3 values)}]] + :value (:r3 values)}]] [:div.input-element.mini {:title (tr "workspace.options.radius")} [:> numeric-input @@ -373,7 +356,7 @@ :min 0 :on-click select-all :on-change on-radius-r4-change - :value (attr->string :r4 values)}]]])])]]])) + :value (:r4 values)}]]])])]]])) (def +size-presets+ [{:name "APPLE"} diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs index 2f2d08f9be..0cb9906db4 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs @@ -386,7 +386,6 @@ {:min -200 :max 200 :step 0.1 - :precision 2 :value (attr->string line-height) :placeholder (tr "settings.multiple") :on-change #(handle-change % :line-height) @@ -400,7 +399,6 @@ {:min -200 :max 200 :step 0.1 - :precision 2 :value (attr->string letter-spacing) :placeholder (tr "settings.multiple") :on-change #(handle-change % :letter-spacing) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs index 8628265755..41ce9d1d15 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs @@ -7,7 +7,6 @@ (ns app.main.ui.workspace.sidebar.options.rows.color-row (:require [app.common.data :as d] - [app.common.math :as math] [app.common.pages :as cp] [app.main.data.modal :as modal] [app.main.refs :as refs] @@ -54,8 +53,7 @@ "" (str (-> opacity (d/coalesce 1) - (* 100) - (math/round))))) + (* 100))))) (defn remove-multiple [v] (if (= v :multiple) nil v)) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/rows/input_row.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/rows/input_row.cljs index 5adc576429..489fca6c70 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/rows/input_row.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/rows/input_row.cljs @@ -12,7 +12,7 @@ [app.util.object :as obj] [rumext.alpha :as mf])) -(mf/defc input-row [{:keys [label options value class min max on-change type placeholder]}] +(mf/defc input-row [{:keys [label options value class min max on-change type placeholder default nillable]}] [:div.row-flex.input-row [:span.element-set-subtitle label] [:div.input-element {:class class} @@ -43,6 +43,8 @@ {:placeholder placeholder :min min :max max + :default default + :nillable nillable :on-change on-change :value (or value "")}])]]) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/rows/stroke_row.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/rows/stroke_row.cljs index 6f462ded7e..c27501057b 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/rows/stroke_row.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/rows/stroke_row.cljs @@ -97,7 +97,6 @@ [:> numeric-input {:min 0 :value (-> (:stroke-width stroke) width->string) - :precision 2 :placeholder (tr "settings.multiple") :on-change (on-stroke-width-change index) :on-click select-all diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index dcc160f25d..c43b88c149 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -149,7 +149,8 @@ show-gradient-handlers? (= (count selected) 1) show-grids? (contains? layout :display-grid) show-outlines? (and (nil? transform) (not edition) (not drawing-obj) (not (#{:comments :path :curve} drawing-tool))) - show-pixel-grid? (>= zoom 8) + show-pixel-grid? (and (contains? layout :show-pixel-grid) + (>= zoom 8)) show-presence? page-id show-prototypes? (= options-mode :prototype) show-selection-handlers? (seq selected) @@ -345,6 +346,7 @@ :zoom zoom :transform transform :selected selected + :selected-shapes selected-shapes :page-id page-id}]) (when show-cursor-tooltip? diff --git a/frontend/src/app/main/ui/workspace/viewport/frame_grid.cljs b/frontend/src/app/main/ui/workspace/viewport/frame_grid.cljs index b3169b12c4..013c1cee50 100644 --- a/frontend/src/app/main/ui/workspace/viewport/frame_grid.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/frame_grid.cljs @@ -6,6 +6,7 @@ (ns app.main.ui.workspace.viewport.frame-grid (:require + [app.common.data :as d] [app.common.geom.shapes :as gsh] [app.common.math :as mth] [app.common.uuid :as uuid] @@ -42,27 +43,71 @@ :fill (str "url(#" grid-id ")")}]])) (mf/defc layout-grid - [{:keys [key frame grid]}] + [{:keys [key frame grid zoom]}] (let [{color-value :color color-opacity :opacity} (-> grid :params :color) ;; Support for old color format color-value (or color-value (:value (get-in grid [:params :color :value]))) - gutter (-> grid :params :gutter) - gutter? (and (not (nil? gutter)) (not= gutter 0)) + gutter (gg/grid-gutter frame grid) + gutter? (and (not (nil? gutter)) (not (mth/almost-zero? gutter)))] - style (if gutter? - #js {:fill color-value - :opacity color-opacity} - #js {:stroke color-value - :strokeOpacity color-opacity - :fill "none"})] [:g.grid - (for [{:keys [x y width height] :as area} (gg/grid-areas frame grid)] - [:rect {:key (str key "-" x "-" y) - :x (mth/round x) - :y (mth/round y) - :width (- (mth/round (+ x width)) (mth/round x)) - :height (- (mth/round (+ y height)) (mth/round y)) - :style style}])])) + (for [[idx {:keys [x y width height] :as area}] (d/enumerate (gg/grid-areas frame grid))] + (cond + gutter? + [:rect {:key (str key "-" x "-" y) + :x x + :y y + :width (- (+ x width) x) + :height (- (+ y height) y) + :style {:fill color-value + :stroke-width 0 + :opacity color-opacity}}] + + (and (not gutter?) (= :column (:type grid))) + [:* + (when (= idx 0) + [:line {:key (str key "-" x "-" y "-start") + :x1 x + :y1 y + :x2 x + :y2 (+ y height) + :style {:stroke color-value + :stroke-width (/ 1 zoom) + :strokeOpacity color-opacity + :fill "none"}}]) + + [:line {:key (str key "-" x "-" y "-end") + :x1 (+ x width) + :y1 y + :x2 (+ x width) + :y2 (+ y height) + :style {:stroke color-value + :stroke-width (/ 1 zoom) + :strokeOpacity color-opacity + :fill "none"}}]] + + (and (not gutter?) (= :row (:type grid))) + [:* + (when (= idx 0) + [:line {:key (str key "-" x "-" y "-start") + :x1 x + :y1 y + :x2 (+ x width) + :y2 y + :style {:stroke color-value + :stroke-width (/ 1 zoom) + :strokeOpacity color-opacity + :fill "none"}}]) + + [:line {:key (str key "-" x "-" y "-end") + :x1 x + :y1 (+ y height) + :x2 (+ x width) + :y2 (+ y height) + :style {:stroke color-value + :stroke-width (/ 1 zoom) + :strokeOpacity color-opacity + :fill "none"}}]]))])) (mf/defc grid-display-frame [{:keys [frame zoom]}] diff --git a/frontend/src/app/main/ui/workspace/viewport/gradients.cljs b/frontend/src/app/main/ui/workspace/viewport/gradients.cljs index 2fdcc2372d..08b524f0ba 100644 --- a/frontend/src/app/main/ui/workspace/viewport/gradients.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/gradients.cljs @@ -7,6 +7,7 @@ (ns app.main.ui.workspace.viewport.gradients "Gradients handlers and renders" (:require + [app.common.data :as d] [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] @@ -281,9 +282,7 @@ (fn [point] (let [point (gpt/transform point transform-inverse) start-x (/ (- (:x point) x) width) - start-y (/ (- (:y point) y) height) - start-x (mth/precision start-x 2) - start-y (mth/precision start-y 2)] + start-y (/ (- (:y point) y) height)] (change! {:start-x start-x :start-y start-y})))) on-change-finish @@ -292,9 +291,7 @@ (fn [point] (let [point (gpt/transform point transform-inverse) end-x (/ (- (:x point) x) width) - end-y (/ (- (:y point) y) height) - end-x (mth/precision end-x 2) - end-y (mth/precision end-y 2)] + end-y (/ (- (:y point) y) height)] (change! {:end-x end-x :end-y end-y})))) on-change-width @@ -304,7 +301,7 @@ (let [scale-factor-y (/ gradient-length (/ height 2)) norm-dist (/ (gpt/distance point from-p) (* (/ width 2) scale-factor-y))] - (when (and norm-dist (mth/finite? norm-dist)) + (when (and norm-dist (d/num? norm-dist)) (change! {:width norm-dist})))))] (when (and gradient diff --git a/frontend/src/app/main/ui/workspace/viewport/guides.cljs b/frontend/src/app/main/ui/workspace/viewport/guides.cljs index 36e040e096..c12d318504 100644 --- a/frontend/src/app/main/ui/workspace/viewport/guides.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/guides.cljs @@ -16,6 +16,7 @@ [app.main.store :as st] [app.main.streams :as ms] [app.main.ui.cursors :as cur] + [app.main.ui.formats :as fmt] [app.main.ui.workspace.viewport.rules :as rules] [app.util.dom :as dom] [rumext.alpha :as mf])) @@ -49,6 +50,8 @@ frame-ref (mf/use-memo (mf/deps frame-id) #(refs/object-by-id frame-id)) frame (mf/deref frame-ref) + snap-pixel? (mf/deref refs/snap-pixel?) + on-pointer-enter (mf/use-callback (fn [] @@ -89,7 +92,7 @@ on-mouse-move (mf/use-callback - (mf/deps position zoom) + (mf/deps position zoom snap-pixel?) (fn [event] (when-let [_ (mf/ref-val dragging-ref)] @@ -101,8 +104,10 @@ (+ position delta) (+ start-pos delta)) - ;; TODO: Change when pixel-grid flag exists - new-position (mth/round new-position) + new-position (if snap-pixel? + (mth/round new-position) + new-position) + new-frame-id (:id (get-hover-frame))] (swap! state assoc :new-position new-position @@ -366,7 +371,7 @@ :style {:font-size (/ rules/font-size zoom) :font-family rules/font-family :fill colors/black}} - (str (mth/round pos))]]))]))) + (fmt/format-number pos)]]))]))) (mf/defc new-guide-area [{:keys [vbox zoom axis get-hover-frame disabled-guides?]}] diff --git a/frontend/src/app/main/ui/workspace/viewport/interactions.cljs b/frontend/src/app/main/ui/workspace/viewport/interactions.cljs index 6021158557..20a51b826c 100644 --- a/frontend/src/app/main/ui/workspace/viewport/interactions.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/interactions.cljs @@ -146,12 +146,12 @@ :else (connect-to-point orig-shape - {:x (+ (:x2 (:selrect orig-shape)) 100) - :y (+ (- (:y1 (:selrect orig-shape)) 50) + {:x (+ (:x2 (:selrect orig-shape)) (/ 100 zoom)) + :y (+ (- (:y1 (:selrect orig-shape)) (/ 50 zoom)) (/ (* level 32) zoom))})) - orig-dx (if (= orig-pos :right) 100 -100) - dest-dx (if (= dest-pos :right) 100 -100) + orig-dx (/ (if (= orig-pos :right) 100 -100) zoom) + dest-dx (/ (if (= dest-pos :right) 100 -100) zoom) path ["M" orig-x orig-y "C" (+ orig-x orig-dx) orig-y (+ dest-x dest-dx) dest-y dest-x dest-y] pdata (str/join " " path) @@ -182,7 +182,8 @@ :d pdata}] (when dest-shape - [:& outline {:shape dest-shape + [:& outline {:zoom zoom + :shape dest-shape :color "var(--color-primary)"}]) [:& interaction-marker {:index index diff --git a/frontend/src/app/main/ui/workspace/viewport/rules.cljs b/frontend/src/app/main/ui/workspace/viewport/rules.cljs index 57a27c1063..e6440786e0 100644 --- a/frontend/src/app/main/ui/workspace/viewport/rules.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/rules.cljs @@ -10,6 +10,7 @@ [app.common.data :as d] [app.common.geom.shapes :as gsh] [app.common.math :as mth] + [app.main.ui.formats :as fmt] [app.main.ui.hooks :as hooks] [app.util.object :as obj] [rumext.alpha :as mf])) @@ -131,9 +132,9 @@ (let [{:keys [start end]} (get-rule-params vbox axis) - minv (max (mth/round start) -100000) + minv (max start -100000) minv (* (mth/ceil (/ minv step)) step) - maxv (min (mth/round end) 100000) + maxv (min end 100000) maxv (* (mth/floor (/ maxv step)) step)] (for [step-val (range minv (inc maxv) step)] @@ -149,7 +150,7 @@ :style {:font-size (/ font-size zoom) :font-family font-family :fill colors/gray-30}} - (str (mth/round step-val))] + (fmt/format-number step-val)] [:line {:key (str "line-" (d/name axis) "-" step-val) :x1 line-x1 @@ -184,7 +185,7 @@ :style {:font-size (/ font-size zoom) :font-family font-family :fill selection-area-color}} - (str (mth/round (:x1 selection-rect)))] + (fmt/format-number (:x1 selection-rect))] [:rect {:x (:x2 selection-rect) :y (:y vbox) @@ -200,7 +201,7 @@ :style {:font-size (/ font-size zoom) :font-family font-family :fill selection-area-color}} - (str (mth/round (:x2 selection-rect)))]] + (fmt/format-number (:x2 selection-rect))]] (let [center-x (+ (:x vbox) (/ rule-area-half-size zoom)) center-y (- (+ (:y selection-rect) (/ (:height selection-rect) 2)) (/ rule-area-half-size zoom))] @@ -234,7 +235,7 @@ :style {:font-size (/ font-size zoom) :font-family font-family :fill selection-area-color}} - (str (mth/round (:y2 selection-rect)))] + (fmt/format-number (:y2 selection-rect))] [:text {:x (+ center-x (/ (:height selection-rect) 2) ) :y center-y @@ -243,7 +244,7 @@ :style {:font-size (/ font-size zoom) :font-family font-family :fill selection-area-color}} - (str (mth/round (:y1 selection-rect)))]])]) + (fmt/format-number (:y1 selection-rect))]])]) (mf/defc rules {::mf/wrap-props false diff --git a/frontend/src/app/main/ui/workspace/viewport/scroll_bars.cljs b/frontend/src/app/main/ui/workspace/viewport/scroll_bars.cljs index 3de69d6b24..a003b039e4 100644 --- a/frontend/src/app/main/ui/workspace/viewport/scroll_bars.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/scroll_bars.cljs @@ -7,7 +7,6 @@ (ns app.main.ui.workspace.viewport.scroll-bars (:require [app.common.geom.shapes :as gsh] - [app.common.geom.shapes.rect :as gpr] [app.common.pages.helpers :as cph] [app.main.data.workspace :as dw] [app.main.store :as st] @@ -131,7 +130,7 @@ (let [viewport (mf/ref-val viewport-ref) start-pt (mf/ref-val start-ref) current-pt (dom/get-client-position event) - current-pt-viewport (utils/translate-point-to-viewport-raw viewport zoom current-pt) + current-pt-viewport (utils/translate-point-to-viewport viewport zoom current-pt) y-delta (/ (* (mf/ref-val height-factor-ref) (- (:y current-pt) (:y start-pt))) zoom) x-delta (/ (* (mf/ref-val width-factor-ref) (- (:x current-pt) (:x start-pt))) zoom) new-v-scrollbar-y (-> current-pt-viewport @@ -156,8 +155,9 @@ (fn [event axis] (let [viewport (mf/ref-val viewport-ref) start-pt (dom/get-client-position event) - new-v-scrollbar-y (-> (utils/translate-point-to-viewport-raw viewport zoom start-pt) :y) - new-h-scrollbar-x (-> (utils/translate-point-to-viewport-raw viewport zoom start-pt) :x) + viewport-point (utils/translate-point-to-viewport viewport zoom start-pt) + new-h-scrollbar-x (:x viewport-point) + new-v-scrollbar-y (:y viewport-point) v-scrollbar-y-padding (- v-scrollbar-y new-v-scrollbar-y) h-scrollbar-x-padding (- h-scrollbar-x new-h-scrollbar-x) vbox-rect {:x vbox-x @@ -168,7 +168,7 @@ :y2 (+ vbox-y (:height vbox)) :width (:width vbox) :height (:height vbox)} - containing-rect (gpr/join-selrects [base-objects-rect vbox-rect]) + containing-rect (gsh/join-selrects [base-objects-rect vbox-rect]) height-factor (/ (:height containing-rect) vbox-height) width-factor (/ (:width containing-rect) vbox-width)] (mf/set-ref-val! start-ref start-pt) @@ -206,7 +206,7 @@ :x v-scrollbar-x :y v-scrollbar-y :style {:stroke "white" - :stroke-width 0.15}}]]) + :stroke-width (/ 0.15 zoom)}}]]) (when show-h-scroll? [:g.h-scroll [:rect {:on-mouse-move #(on-mouse-move % :x) @@ -220,4 +220,4 @@ :x h-scrollbar-x :y h-scrollbar-y :style {:stroke "white" - :stroke-width 0.15}}]])])) + :stroke-width (/ 0.15 zoom)}}]])])) diff --git a/frontend/src/app/main/ui/workspace/viewport/selection.cljs b/frontend/src/app/main/ui/workspace/viewport/selection.cljs index 99c85734d9..dcac1a492f 100644 --- a/frontend/src/app/main/ui/workspace/viewport/selection.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/selection.cljs @@ -7,9 +7,11 @@ (ns app.main.ui.workspace.viewport.selection "Selection handlers component." (:require + [app.common.data.macros :as dm] [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] - [app.common.geom.shapes :as geom] + [app.common.geom.shapes :as gsh] + [app.common.pages :as cp] [app.main.data.workspace :as dw] [app.main.refs :as refs] [app.main.store :as st] @@ -17,7 +19,6 @@ [app.main.ui.workspace.shapes.path.editor :refer [path-editor]] [app.util.dom :as dom] [app.util.object :as obj] - [cuerdas.core :as str] [debug :refer [debug?]] [rumext.alpha :as mf] [rumext.util :refer [map->obj]])) @@ -33,7 +34,8 @@ (def min-selrect-side 10) (def small-selrect-side 30) -(mf/defc selection-rect [{:keys [transform rect zoom color on-move-selected on-context-menu]}] +(mf/defc selection-rect + [{:keys [transform rect zoom color on-move-selected on-context-menu]}] (when rect (let [{:keys [x y width height]} rect] [:rect.main.viewport-selrect @@ -41,7 +43,7 @@ :y y :width width :height height - :transform transform + :transform (str transform) :on-mouse-down on-move-selected :on-context-menu on-context-menu :style {:stroke color @@ -49,75 +51,106 @@ :fill "none"}}]))) (defn- handlers-for-selection [{:keys [x y width height]} {:keys [type]} zoom] - (let [zoom-width (* width zoom) - zoom-height (* height zoom) + (let [threshold-small (/ 25 zoom) + threshold-tiny (/ 10 zoom) - align (when (or (<= zoom-width small-selrect-side) - (<= zoom-height small-selrect-side)) - :outside) - show-resize-point? (or (not= type :path) - (and - (> zoom-width min-selrect-side) - (> zoom-height min-selrect-side))) - min-side-top? (or (not= type :path) (> zoom-height min-selrect-side)) - min-side-side? (or (not= type :path) (> zoom-width min-selrect-side))] + small-width? (<= width threshold-small) + tiny-width? (<= width threshold-tiny) + + small-height? (<= height threshold-small) + tiny-height? (<= height threshold-tiny) + + vertical-line? (and (= type :path) tiny-width?) + horizontal-line? (and (= type :path) tiny-height?) + + align (if (or small-width? small-height?) + :outside + :inside)] (->> [ ;; TOP-LEFT {:type :rotation :position :top-left :props {:cx x :cy y}} - (when show-resize-point? - {:type :resize-point - :position :top-left - :props {:cx x :cy y :align align}}) - {:type :rotation :position :top-right :props {:cx (+ x width) :cy y}} - (when show-resize-point? - {:type :resize-point - :position :top-right - :props {:cx (+ x width) :cy y :align align}}) - {:type :rotation :position :bottom-right :props {:cx (+ x width) :cy (+ y height)}} - (when show-resize-point? - {:type :resize-point - :position :bottom-right - :props {:cx (+ x width) :cy (+ y height) :align align}}) - {:type :rotation :position :bottom-left :props {:cx x :cy (+ y height)}} - (when show-resize-point? + (when-not horizontal-line? + (let [x (if small-width? (+ x (/ (- width threshold-small) 2)) x) + length (if small-width? threshold-small width)] + {:type :resize-side + :position :top + :props {:x x + :y y + :length length + :angle 0 + :align align + :show-handler? tiny-width?}})) + + (when-not horizontal-line? + (let [x (if small-width? (+ x (/ (+ width threshold-small) 2)) (+ x width)) + length (if small-width? threshold-small width)] + {:type :resize-side + :position :bottom + :props {:x x + :y (+ y height) + :length length + :angle 180 + :align align + :show-handler? tiny-width?}})) + + (when-not vertical-line? + (let [y (if small-height? (+ y (/ (- height threshold-small) 2)) y) + length (if small-height? threshold-small height)] + {:type :resize-side + :position :right + :props {:x (+ x width) + :y y + :length length + :angle 90 + :align align + :show-handler? tiny-height?}})) + + (when-not vertical-line? + (let [y (if small-height? (+ y (/ (+ height threshold-small) 2)) (+ y height)) + length (if small-height? threshold-small height)] + {:type :resize-side + :position :left + :props {:x x + :y y + :length length + :angle 270 + :align align + :show-handler? tiny-height?}})) + + (when (and (not tiny-width?) (not tiny-height?)) + {:type :resize-point + :position :top-left + :props {:cx x :cy y :align align}}) + + (when (and (not tiny-width?) (not tiny-height?)) + {:type :resize-point + :position :top-right + :props {:cx (+ x width) :cy y :align align}}) + + (when (and (not tiny-width?) (not tiny-height?)) + {:type :resize-point + :position :bottom-right + :props {:cx (+ x width) :cy (+ y height) :align align}}) + + (when (and (not tiny-width?) (not tiny-height?)) {:type :resize-point :position :bottom-left - :props {:cx x :cy (+ y height) :align align}}) - - (when min-side-top? - {:type :resize-side - :position :top - :props {:x x :y y :length width :angle 0 :align align}}) - - (when min-side-side? - {:type :resize-side - :position :right - :props {:x (+ x width) :y y :length height :angle 90 :align align}}) - - (when min-side-top? - {:type :resize-side - :position :bottom - :props {:x (+ x width) :y (+ y height) :length width :angle 180 :align align}}) - - (when min-side-side? - {:type :resize-side - :position :left - :props {:x x :y (+ y height) :length height :angle 270 :align align}})] + :props {:cx x :cy (+ y height) :align align}})] (filterv (comp not nil?))))) @@ -135,12 +168,13 @@ :y y :width size :height size - :fill (if (debug? :rotation-handler) "blue" "none") - :transform transform + :fill (if (debug? :handlers) "blue" "none") + :stroke-width 0 + :transform (str transform) :on-mouse-down on-rotate}])) (mf/defc resize-point-handler - [{:keys [cx cy zoom position on-resize transform rotation color overflow-text align]}] + [{:keys [cx cy zoom position on-resize transform rotation color align]}] (let [cursor (if (#{:top-left :bottom-right} position) (cur/resize-nesw rotation) (cur/resize-nwse rotation)) {cx' :x cy' :y} (gpt/transform (gpt/point cx cy) transform)] @@ -151,7 +185,7 @@ :strokeWidth "1px" :vectorEffect "non-scaling-stroke"} :fill "var(--color-white)" - :stroke (if (and (= position :bottom-right) overflow-text) "red" color) + :stroke color :cx cx' :cy cy'}] @@ -166,8 +200,9 @@ :y cy' :width resize-point-circle-radius :height resize-point-circle-radius - :transform (when rotation (str/fmt "rotate(%s, %s, %s)" rotation cx' cy')) - :style {:fill (if (debug? :resize-handler) "red" "none") + :transform (when rotation (dm/fmt "rotate(%, %, %)" rotation cx' cy')) + :style {:fill (if (debug? :handlers) "red" "none") + :stroke-width 0 :cursor cursor} :on-mouse-down #(on-resize {:x cx' :y cy'} %)}]) @@ -175,36 +210,43 @@ :r (/ resize-point-circle-radius zoom) :cx cx' :cy cy' - :style {:fill (if (debug? :resize-handler) "red" "none") + :style {:fill (if (debug? :handlers) "red" "none") + :stroke-width 0 :cursor cursor}}])])) (mf/defc resize-side-handler "The side handler is always rendered horizontally and then rotated" - [{:keys [x y length align angle zoom position rotation transform on-resize]}] + [{:keys [x y length align angle zoom position rotation transform on-resize color show-handler?]}] (let [res-point (if (#{:top :bottom} position) {:y y} {:x x}) - target-length (max 0 (- length (/ (* resize-point-rect-size 2) zoom))) - width (if (< target-length 6) length target-length) height (/ resize-side-height zoom) - - offset-x (/ (- length width) 2) offset-y (if (= align :outside) (- height) (- (/ height 2))) - - target-x (+ x offset-x) - target-y (+ y offset-y)] - [:rect {:x target-x - :y target-y - :width width - :height height - :transform (gmt/multiply transform - (gmt/rotate-matrix angle (gpt/point x y))) - :on-mouse-down #(on-resize res-point %) - :style {:fill (if (debug? :resize-handler) "yellow" "none") - :cursor (if (#{:left :right} position) - (cur/resize-ew rotation) - (cur/resize-ns rotation)) }}])) + target-y (+ y offset-y) + transform-str (str (gmt/multiply transform (gmt/rotate-matrix angle (gpt/point x y))))] + [:g.resize-handler + (when show-handler? + [:circle {:r (/ resize-point-radius zoom) + :style {:fillOpacity 1 + :stroke color + :strokeWidth "1px" + :fill "var(--color-white)" + :vectorEffect "non-scaling-stroke"} + :cx (+ x (/ length 2)) + :cy y + :transform transform-str}]) + [:rect {:x x + :y target-y + :width length + :height height + :transform transform-str + :on-mouse-down #(on-resize res-point %) + :style {:fill (if (debug? :handlers) "yellow" "none") + :stroke-width 0 + :cursor (if (#{:left :right} position) + (cur/resize-ew rotation) + (cur/resize-ns rotation)) }}]])) (defn minimum-selrect [{:keys [x y width height] :as selrect}] (let [final-width (max width min-selrect-side) @@ -229,13 +271,13 @@ current-transform (mf/deref refs/current-transform) selrect (:selrect shape) - transform (geom/transform-matrix shape {:no-flip true})] + transform (gsh/transform-matrix shape {:no-flip true})] (when (not (#{:move :rotate} current-transform)) [:g.controls {:pointer-events (if disable-handlers "none" "visible")} ;; Selection rect [:& selection-rect {:rect selrect - :transform transform + :transform (str transform) :zoom zoom :color color :on-move-selected on-move-selected @@ -244,7 +286,7 @@ (mf/defc controls-handlers {::mf/wrap-props false} [props] - (let [{:keys [overflow-text] :as shape} (obj/get props "shape") + (let [shape (obj/get props "shape") zoom (obj/get props "zoom") color (obj/get props "color") on-resize (obj/get props "on-resize") @@ -253,7 +295,7 @@ current-transform (mf/deref refs/current-transform) selrect (:selrect shape) - transform (geom/transform-matrix shape {:no-flip true}) + transform (gsh/transform-matrix shape {:no-flip true}) rotation (-> (gpt/point 1 0) (gpt/transform (:transform shape)) @@ -264,15 +306,14 @@ [:g.controls {:pointer-events (if disable-handlers "none" "visible")} ;; Handlers (for [{:keys [type position props]} (handlers-for-selection selrect shape zoom)] - (let [common-props {:key (str (name type) "-" (name position)) + (let [common-props {:key (dm/str (name type) "-" (name position)) :zoom zoom :position position :on-rotate on-rotate :on-resize (partial on-resize position) :transform transform :rotation rotation - :color color - :overflow-text overflow-text} + :color color} props (map->obj (merge common-props props))] (case type :rotation (when (not= :frame (:type shape)) [:> rotation-handler props]) @@ -286,13 +327,13 @@ (let [{:keys [x y width height]} shape] [:g.controls [:rect.main {:x x :y y - :transform (geom/transform-matrix shape) + :transform (str (gsh/transform-matrix shape)) :width width :height height :pointer-events "visible" :style {:stroke color :stroke-width (/ 0.5 zoom) - :stroke-opacity "1" + :stroke-opacity 1 :fill "none"}}]])) (mf/defc multiple-handlers @@ -300,10 +341,9 @@ (let [shape (mf/use-memo (mf/deps shapes) #(->> shapes - (map geom/transform-shape) - (geom/selection-rect) - (geom/setup {:type :rect}))) - + (map gsh/transform-shape) + (gsh/selection-rect) + (cp/setup-shape))) on-resize (fn [current-position _initial-position event] (when (dom/left-mouse? event) @@ -329,9 +369,9 @@ (let [shape (mf/use-memo (mf/deps shapes) #(->> shapes - (map geom/transform-shape) - (geom/selection-rect) - (geom/setup {:type :rect})))] + (map gsh/transform-shape) + (gsh/selection-rect) + (cp/setup-shape)))] [:& controls-selection {:shape shape @@ -344,7 +384,7 @@ (mf/defc single-handlers [{:keys [shape zoom color disable-handlers] :as props}] (let [shape-id (:id shape) - shape (geom/transform-shape shape {:round-coords? false}) + shape (gsh/transform-shape shape) on-resize (fn [current-position _initial-position event] @@ -368,8 +408,7 @@ (mf/defc single-selection [{:keys [shape zoom color disable-handlers on-move-selected on-context-menu] :as props}] - (let [shape (geom/transform-shape shape {:round-coords? false})] - + (let [shape (gsh/transform-shape shape)] [:& controls-selection {:shape shape :zoom zoom diff --git a/frontend/src/app/main/ui/workspace/viewport/snap_distances.cljs b/frontend/src/app/main/ui/workspace/viewport/snap_distances.cljs index ca450b2a81..6fbd5b842f 100644 --- a/frontend/src/app/main/ui/workspace/viewport/snap_distances.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/snap_distances.cljs @@ -11,6 +11,7 @@ [app.common.math :as mth] [app.common.pages.helpers :as cph] [app.main.refs :as refs] + [app.main.ui.formats :as fmt] [app.main.worker :as uw] [beicon.core :as rx] [clojure.set :as set] @@ -44,6 +45,7 @@ (def pill-text-font-size 12) (def pill-text-height 20) (def pill-text-border-radius 4) +(def pill-text-padding 4) (mf/defc shape-distance-segment "Displays a segment between two selrects with the distance between them" @@ -54,12 +56,13 @@ (get sr2 (if (= :x coord) :x1 :y1))) distance (- to-c from-c) - distance-str (-> distance (mth/precision 0) str) + distance-str (fmt/format-number distance) half-point (half-point coord sr1 sr2) width (-> distance-str count (* (/ pill-text-width-letter zoom)) - (+ (/ pill-text-width-margin zoom)))] + (+ (/ pill-text-width-margin zoom)) + (+ (* (/ pill-text-width-margin zoom) 2)))] [:g.distance-segment (let [point [(+ from-c (/ distance 2)) @@ -81,7 +84,7 @@ :font-size (/ pill-text-font-size zoom) :fill "var(--color-white)" :text-anchor "middle"} - (mth/precision distance 0)]]) + (fmt/format-number distance)]]) (let [p1 [(+ from-c (/ segment-gap zoom)) (+ half-point (/ segment-gap-side zoom))] p2 [(+ from-c (/ segment-gap zoom)) (- half-point (/ segment-gap-side zoom))] @@ -110,7 +113,7 @@ sr2 (:selrect sh2) c1 (if (= coord :x) :x1 :y1) c2 (if (= coord :x) :x2 :y2) - dist (mth/precision (- (c1 sr2) (c2 sr1)) 0)] + dist (- (c1 sr2) (c2 sr1))] [dist [sh1 sh2]])) (defn overlap? [coord sh1 sh2] @@ -134,8 +137,7 @@ (-> (if (<= (coord sr) (coord selrect)) (gsh/distance-selrect sr selrect) (gsh/distance-selrect selrect sr)) - coord - (mth/precision 0)))) + coord))) get-shapes-match (fn [pred? shapes] @@ -149,9 +151,9 @@ check-in-set (fn [value number-set] (->> number-set - (some #(<= (mth/abs (- value %)) 1)))) + (some #(<= (mth/abs (- value %)) 0.01)))) - ;; Left/Top shapes and right/bottom shapes (depends on `coord` parameter + ;; Left/Top shapes and right/bottom shapes (depends on `coord` parameter) ;; Gets the distance to the current selection distances-xf (comp (map distance-to-selrect) (filter pos?)) @@ -195,6 +197,7 @@ (map #(vector selrect (:selrect %)))) segments-to-display (d/concat-set other-shapes-segments selection-segments)] + segments-to-display)) (mf/defc shape-distance @@ -217,8 +220,9 @@ container-selrec (or (:selrect frame) (gsh/rect->selrect @refs/vbox)) areas (gsh/selrect->areas container-selrec selrect) + query-side (fn [side] - (let [rect (gsh/pad-selrec (areas side))] + (let [rect (get areas side)] (if (and (> (:width rect) 0) (> (:height rect) 0)) (->> (uw/ask! {:cmd :selection/query :page-id page-id @@ -264,15 +268,10 @@ (let [page-id (unchecked-get props "page-id") zoom (unchecked-get props "zoom") selected (unchecked-get props "selected") - selected-shapes (mf/deref (refs/objects-by-id selected)) + selected-shapes (unchecked-get props "selected-shapes") frame-id (-> selected-shapes first :frame-id) frame (mf/deref (refs/object-by-id frame-id)) - local (mf/deref refs/workspace-local) - - update-shape (fn [shape] (-> shape - (update :modifiers merge (:modifiers local)) - gsh/transform-shape)) - selrect (->> selected-shapes (map update-shape) gsh/selection-rect)] + selrect (gsh/selection-rect selected-shapes)] [:g.distance [:& shape-distance {:selrect selrect diff --git a/frontend/src/app/main/ui/workspace/viewport/snap_points.cljs b/frontend/src/app/main/ui/workspace/viewport/snap_points.cljs index d5dd3b0a53..959c1aac19 100644 --- a/frontend/src/app/main/ui/workspace/viewport/snap_points.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/snap_points.cljs @@ -8,7 +8,6 @@ (:require [app.common.data :as d] [app.common.geom.shapes :as gsh] - [app.common.math :as mth] [app.common.pages.helpers :as cph] [app.common.spec :as us] [app.main.snap :as snap] @@ -28,8 +27,6 @@ (mf/defc snap-point [{:keys [point zoom]}] (let [{:keys [x y]} point - x (mth/round x) - y (mth/round y) cross-width (/ 3 zoom)] [:g [:line {:x1 (- x cross-width) @@ -45,32 +42,36 @@ (mf/defc snap-line [{:keys [snap point zoom]}] - [:line {:x1 (mth/round (:x snap)) - :y1 (mth/round (:y snap)) - :x2 (mth/round (:x point)) - :y2 (mth/round (:y point)) + [:line {:x1 (:x snap) + :y1 (:y snap) + :x2 (:x point) + :y2 (:y point) :style {:stroke line-color :stroke-width (str (/ line-width zoom))} :opacity line-opacity}]) (defn get-snap - [coord {:keys [shapes page-id remove-snap? modifiers]}] - (let [shape (if (> (count shapes) 1) - (->> shapes (map gsh/transform-shape) gsh/selection-rect (gsh/setup {:type :rect})) - (->> shapes (first))) - - shape (if modifiers - (-> shape (merge (get modifiers (:id shape))) gsh/transform-shape) - shape) + [coord {:keys [shapes page-id remove-snap? zoom modifiers]}] + (let [shapes-sr + (->> shapes + ;; Merge modifiers into shapes + (map #(merge % (get modifiers (:id %)))) + ;; Create the bounding rectangle for the shapes + (gsh/selection-rect)) frame-id (snap/snap-frame-id shapes)] - (->> (rx/of shape) - (rx/flat-map (fn [shape] - (->> (sp/shape-snap-points shape) - (map #(vector frame-id %))))) - (rx/flat-map (fn [[frame-id point]] - (->> (snap/get-snap-points page-id frame-id remove-snap? point coord) - (rx/map #(vector point % coord))))) + (->> (rx/of shapes-sr) + (rx/flat-map + (fn [selrect] + (->> (sp/selrect-snap-points selrect) + (map #(vector frame-id %))))) + + (rx/flat-map + (fn [[frame-id point]] + (->> (snap/get-snap-points page-id frame-id remove-snap? zoom point coord) + (rx/map #(mapcat second %)) + (rx/map #(map :pt %)) + (rx/map #(vector point % coord))))) (rx/reduce conj [])))) (defn- flip @@ -119,12 +120,19 @@ (mf/use-effect (fn [] (let [sub (->> subject - (rx/switch-map #(rx/combine-latest (get-snap :x %) - (get-snap :y %))) - (rx/map (fn [result] - (apply d/concat-vec (seq result)))) - (rx/subs #(let [rs (filter (fn [[_ snaps _]] (> (count snaps) 0)) %)] - (reset! state rs))))] + (rx/switch-map + (fn [props] + (->> (get-snap :y props) + (rx/combine-latest (get-snap :x props))))) + + (rx/map + (fn [result] + (apply d/concat-vec (seq result)))) + + (rx/subs + (fn [data] + (let [rs (filter (fn [[_ snaps _]] (> (count snaps) 0)) data)] + (reset! state rs)))))] ;; On unmount callback #(rx/dispose! sub)))) @@ -151,22 +159,29 @@ (mf/defc snap-points {::mf/wrap [mf/memo]} - [{:keys [layout zoom objects selected page-id drawing transform modifiers focus] :as props}] + [{:keys [layout zoom objects selected page-id drawing modifiers focus] :as props}] (us/assert set? selected) (let [shapes (into [] (keep (d/getf objects)) selected) filter-shapes (into selected (mapcat #(cph/get-children-ids objects %)) selected) - remove-snap? + remove-snap-base? (mf/with-memo [layout filter-shapes objects focus] (snap/make-remove-snap layout filter-shapes objects focus)) - shapes (if drawing [drawing] shapes)] - (when (or drawing transform) - [:& snap-feedback {:shapes shapes - :page-id page-id - :remove-snap? remove-snap? - :zoom zoom - :modifiers modifiers}]))) + remove-snap? + (mf/use-callback + (mf/deps remove-snap-base?) + (fn [{:keys [type grid] :as snap}] + (or (remove-snap-base? snap) + (and (= type :layout) (= grid :square)) + (= type :guide)))) + + shapes (if drawing [drawing] shapes)] + [:& snap-feedback {:shapes shapes + :page-id page-id + :remove-snap? remove-snap? + :zoom zoom + :modifiers modifiers}])) diff --git a/frontend/src/app/main/ui/workspace/viewport/thumbnail_renderer.cljs b/frontend/src/app/main/ui/workspace/viewport/thumbnail_renderer.cljs index 6e0ff85fec..634c54c898 100644 --- a/frontend/src/app/main/ui/workspace/viewport/thumbnail_renderer.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/thumbnail_renderer.cljs @@ -6,6 +6,7 @@ (ns app.main.ui.workspace.viewport.thumbnail-renderer (:require + [app.common.math :as mth] [app.main.data.workspace.persistence :as dwp] [app.main.store :as st] [app.util.dom :as dom] @@ -21,6 +22,10 @@ (let [thumbnail-img (mf/use-ref nil) thumbnail-canvas (mf/use-ref nil) + {:keys [width height]} shape + fixed-width (mth/clamp width 250 2000) + fixed-height (/ (* height fixed-width) width) + on-dom-rendered (mf/use-callback (mf/deps (:id shape)) @@ -71,23 +76,23 @@ _ (.rect canvas-context 0 0 canvas-width canvas-height) _ (set! (.-fillStyle canvas-context) background) _ (.fill canvas-context) - _ (.drawImage canvas-context img-node 0 0) + _ (.drawImage canvas-context img-node 0 0 canvas-width canvas-height) - data (.toDataURL canvas-node "image/jpeg" 0.8)] + data (.toDataURL canvas-node "image/jpg" 1)] (on-thumbnail-data data))))] [:div.frame-renderer {:ref on-dom-rendered :style {:display "none"}} [:img.thumbnail-img {:ref thumbnail-img - :width (:width shape) - :height (:height shape) + :width width + :height height :on-load on-image-load}] [:canvas.thumbnail-canvas {:ref thumbnail-canvas - :width (:width shape) - :height (:height shape)}]])) + :width fixed-width + :height fixed-height}]])) (mf/defc frame-renderer "Component in charge of creating thumbnails and storing them" diff --git a/frontend/src/app/main/ui/workspace/viewport/utils.cljs b/frontend/src/app/main/ui/workspace/viewport/utils.cljs index 6e7db9411d..4caedeeb10 100644 --- a/frontend/src/app/main/ui/workspace/viewport/utils.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/utils.cljs @@ -163,7 +163,7 @@ (:width vbox 0) (:height vbox 0)])) -(defn translate-point-to-viewport-raw [viewport zoom pt] +(defn translate-point-to-viewport [viewport zoom pt] (let [vbox (.. ^js viewport -viewBox -baseVal) brect (dom/get-bounding-rect viewport) brect (gpt/point (d/parse-integer (:left brect)) @@ -174,10 +174,6 @@ (gpt/divide zoom) (gpt/add box)))) -(defn translate-point-to-viewport [viewport zoom pt] - (-> (translate-point-to-viewport-raw viewport zoom pt) - (gpt/round 0))) - (defn get-cursor [cursor] (case cursor :hand cur/hand diff --git a/frontend/src/app/main/ui/workspace/viewport/widgets.cljs b/frontend/src/app/main/ui/workspace/viewport/widgets.cljs index a9661c26f0..a4361ad65d 100644 --- a/frontend/src/app/main/ui/workspace/viewport/widgets.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/widgets.cljs @@ -32,8 +32,8 @@ :pattern-units "userSpaceOnUse"} [:path {:d "M 1 0 L 0 0 0 1" :style {:fill "none" - :stroke "var(--color-info)" - :stroke-opacity "0.2" + :stroke "var(--color-gray-20)" + :stroke-opacity "1" :stroke-width (str (/ 1 zoom))}}]]] [:rect {:x (:x vbox) :y (:y vbox) diff --git a/frontend/src/app/util/code_gen.cljs b/frontend/src/app/util/code_gen.cljs index 19978d5a89..3c91623784 100644 --- a/frontend/src/app/util/code_gen.cljs +++ b/frontend/src/app/util/code_gen.cljs @@ -7,7 +7,6 @@ (ns app.util.code-gen (:require [app.common.data :as d] - [app.common.math :as mth] [app.common.text :as txt] [app.util.color :as uc] [cuerdas.core :as str])) @@ -109,7 +108,7 @@ (every? #(or (nil? %) (= % 0)) value) (or (nil? value) (= value 0)))) - default-format (fn [value] (str (mth/precision value 2) "px")) + default-format (fn [value] (str value "px")) format-property (fn [prop] (let [css-prop (or (prop to-prop) (name prop)) format-fn (or (prop format) default-format) diff --git a/frontend/src/app/util/data.cljs b/frontend/src/app/util/data.cljs index 7447359fb0..0c0d500afd 100644 --- a/frontend/src/app/util/data.cljs +++ b/frontend/src/app/util/data.cljs @@ -167,4 +167,3 @@ (let [st (str/trim (str/lower search-term)) nm (str/trim (str/lower name))] (str/includes? nm st)))) - diff --git a/frontend/src/app/util/geom/grid.cljs b/frontend/src/app/util/geom/grid.cljs index 60cb715e9c..81e00aaaaa 100644 --- a/frontend/src/app/util/geom/grid.cljs +++ b/frontend/src/app/util/geom/grid.cljs @@ -6,6 +6,7 @@ (ns app.util.geom.grid (:require + [app.common.data :as d] [app.common.geom.point :as gpt] [app.common.math :as mth])) @@ -39,13 +40,13 @@ gutter (if (= :stretch type) (let [gutter (/ (- width (* width' size) (* margin 2)) (dec size))] - (if (mth/finite? gutter) gutter 0)) + (if (d/num? gutter) gutter 0)) gutter) next-v (fn [cur-val] (+ offset v (* (+ width' gutter) cur-val)))] - [size width' next-v])) + [size width' next-v gutter])) (defn- calculate-column-grid [{:keys [width height x y] :as frame} params] @@ -69,6 +70,20 @@ [(* col-size row-size) size size next-x next-y])) +(defn grid-gutter + [{:keys [x y width height]} {:keys [type params] :as grid}] + + (case type + :column + (let [[_ _ _ gutter] (calculate-generic-grid x width params)] + gutter) + + :row + (let [[_ _ _ gutter] (calculate-generic-grid y height params)] + gutter) + + nil)) + (defn grid-areas "Given a frame and the grid parameters returns the areas defined on the grid" [frame grid] @@ -92,28 +107,25 @@ (defn grid-snap-points "Returns the snap points for a given grid" - ([shape coord] - (mapcat #(grid-snap-points shape % coord) (:grids shape))) + [shape {:keys [type params] :as grid} coord] + (when (:display grid) + (case type + :square + (let [{:keys [x y width height]} shape + size (-> params :size)] + (when (> size 0) + (if (= coord :x) + (mapcat #(vector (gpt/point (+ x %) y) + (gpt/point (+ x %) (+ y height))) (range size width size)) + (mapcat #(vector (gpt/point x (+ y %)) + (gpt/point (+ x width) (+ y %))) (range size height size))))) - ([shape {:keys [type params] :as grid} coord] - (when (:display grid) - (case type - :square - (let [{:keys [x y width height]} shape - size (-> params :size)] - (when (> size 0) - (if (= coord :x) - (mapcat #(vector (gpt/point (+ x %) y) - (gpt/point (+ x %) (+ y height))) (range size width size)) - (mapcat #(vector (gpt/point x (+ y %)) - (gpt/point (+ x width) (+ y %))) (range size height size))))) + :column + (when (= coord :x) + (->> (grid-areas shape grid) + (mapcat grid-area-points))) - :column - (when (= coord :x) - (->> (grid-areas shape grid) - (mapcat grid-area-points))) - - :row - (when (= coord :y) - (->> (grid-areas shape grid) - (mapcat grid-area-points))))))) + :row + (when (= coord :y) + (->> (grid-areas shape grid) + (mapcat grid-area-points)))))) diff --git a/frontend/src/app/util/geom/snap_points.cljs b/frontend/src/app/util/geom/snap_points.cljs index 8fccf5bf36..17df6445a1 100644 --- a/frontend/src/app/util/geom/snap_points.cljs +++ b/frontend/src/app/util/geom/snap_points.cljs @@ -9,25 +9,28 @@ [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh])) -(defn- selrect-snap-points [{:keys [x y width height]}] +(defn selrect-snap-points [{:keys [x y width height] :as selrect}] #{(gpt/point x y) (gpt/point (+ x width) y) (gpt/point (+ x width) (+ y height)) - (gpt/point x (+ y height))}) + (gpt/point x (+ y height)) + (gsh/center-selrect selrect)}) -(defn- frame-snap-points [{:keys [x y width height] :as selrect}] - (into (selrect-snap-points selrect) - #{(gpt/point (+ x (/ width 2)) y) - (gpt/point (+ x width) (+ y (/ height 2))) - (gpt/point (+ x (/ width 2)) (+ y height)) - (gpt/point x (+ y (/ height 2)))})) +(defn frame-snap-points [{:keys [x y width height blocked hidden] :as selrect}] + (when (and (not blocked) (not hidden)) + (into (selrect-snap-points selrect) + #{(gpt/point (+ x (/ width 2)) y) + (gpt/point (+ x width) (+ y (/ height 2))) + (gpt/point (+ x (/ width 2)) (+ y height)) + (gpt/point x (+ y (/ height 2)))}))) (defn shape-snap-points - [shape] - (let [shape (gsh/transform-shape shape)] - (case (:type shape) - :frame (-> shape :selrect frame-snap-points) - (into #{(gsh/center-shape shape)} (:points shape))))) + [{:keys [hidden blocked] :as shape}] + (when (and (not blocked) (not hidden)) + (let [shape (gsh/transform-shape shape)] + (case (:type shape) + :frame (-> shape :selrect frame-snap-points) + (into #{(gsh/center-shape shape)} (:points shape)))))) (defn guide-snap-points [guide] diff --git a/frontend/src/app/util/path/tools.cljs b/frontend/src/app/util/path/tools.cljs index 97a8a0ddd2..fffd3816b7 100644 --- a/frontend/src/app/util/path/tools.cljs +++ b/frontend/src/app/util/path/tools.cljs @@ -9,7 +9,6 @@ [app.common.data :as d] [app.common.geom.point :as gpt] [app.common.geom.shapes.path :as upg] - [app.common.math :as mth] [app.common.path.commands :as upc] [clojure.set :as set])) @@ -402,9 +401,7 @@ (rest segments)))))) (defn calculate-merge-points [group-segments points] - (let [index-merge-point (fn [group] (vector group (-> (gpt/center-points group) - (update :x mth/round) - (update :y mth/round)))) + (let [index-merge-point (fn [group] (vector group (gpt/center-points group))) index-group (fn [point] (vector point (d/seek #(contains? % point) group-segments))) group->merge-point (into {} (map index-merge-point) group-segments) diff --git a/frontend/src/app/util/perf.cljs b/frontend/src/app/util/perf.cljs index 87a0273be0..d894f477e1 100644 --- a/frontend/src/app/util/perf.cljs +++ b/frontend/src/app/util/perf.cljs @@ -8,7 +8,7 @@ "Performance profiling for react components." (:require-macros [app.util.perf]) (:require - [app.common.math :as math] + [app.common.math :as mth] [rumext.alpha :as mf] [goog.functions :as f] ["react" :as react] diff --git a/frontend/src/app/util/snap_data.cljs b/frontend/src/app/util/snap_data.cljs index 5725ea826d..597e6ad4a1 100644 --- a/frontend/src/app/util/snap_data.cljs +++ b/frontend/src/app/util/snap_data.cljs @@ -53,6 +53,19 @@ (assoc-in [frame-id :x] (rt/make-tree)) (assoc-in [frame-id :y] (rt/make-tree))))) +(defn get-grids-snap-points + [frame coord] + (let [grid->snap (fn [[grid-type position]] + {:type :layout + :id (:id frame) + :grid grid-type + :pt position})] + (->> (:grids frame) + (mapcat (fn [grid] + (->> (gg/grid-snap-points frame grid coord) + (mapv #(vector (:type grid) %))))) + (mapv grid->snap)))) + (defn- add-frame [page-data frame] (let [frame-id (:id frame) @@ -61,17 +74,8 @@ (mapv #(array-map :type :shape :id frame-id :pt %))) - - grid-x-data (->> (gg/grid-snap-points frame :x) - (mapv #(array-map :type :layout - :id frame-id - :pt %))) - - grid-y-data (->> (gg/grid-snap-points frame :y) - (mapv #(array-map :type :layout - :id frame-id - :pt %)))] - + grid-x-data (get-grids-snap-points frame :x) + grid-y-data (get-grids-snap-points frame :y)] (-> page-data ;; Update root frame information (assoc-in [uuid/zero :objects-data frame-id] frame-data) @@ -107,6 +111,7 @@ (mapv #(array-map :type :guide :id (:id guide) + :axis (:axis guide) :frame-id (:frame-id guide) :pt %)))] (if-let [frame-id (:frame-id guide)] diff --git a/frontend/src/debug.cljs b/frontend/src/debug.cljs index 8e469a0d45..881971a9f3 100644 --- a/frontend/src/debug.cljs +++ b/frontend/src/debug.cljs @@ -7,7 +7,6 @@ (ns debug (:require [app.common.data :as d] - [app.common.math :as mth] [app.common.pages.helpers :as cph] [app.common.transit :as t] [app.common.uuid :as uuid] @@ -32,11 +31,8 @@ ;; Displays in the console log the events through the application :events - ;; Display the boxes that represent the rotation handlers - :rotation-handler - - ;; Display the boxes that represent the resize handlers - :resize-handler + ;; Display the boxes that represent the rotation and resize handlers + :handlers ;; Displays the center of a selection :selection-center @@ -130,7 +126,7 @@ ts (/ 1000 (* (- cur @last))) val (+ @avg (* (- ts @avg) 0.1))] - (obj/set! node "innerText" (mth/precision val 0)) + (obj/set! node "innerText" val) (vreset! last cur) (vreset! avg val) (do-thing)))))] diff --git a/frontend/test/app/test_helpers/pages.cljs b/frontend/test/app/test_helpers/pages.cljs index 6f2cb3990c..13aa606bce 100644 --- a/frontend/test/app/test_helpers/pages.cljs +++ b/frontend/test/app/test_helpers/pages.cljs @@ -70,7 +70,7 @@ (let [page (current-page state) frame (cph/get-frame (:objects page)) shape (-> (cp/make-minimal-shape type) - (gsh/setup {:x 0 :y 0 :width 1 :height 1}) + (cp/setup-shape {:x 0 :y 0 :width 1 :height 1}) (merge props))] (swap! idmap assoc label (:id shape)) (update state :workspace-data diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 6eacdf3a18..180e7def95 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -2230,6 +2230,18 @@ msgstr "Show rules" msgid "workspace.header.menu.show-textpalette" msgstr "Show fonts palette" +msgid "workspace.header.menu.hide-pixel-grid" +msgstr "Hide pixel grid" + +msgid "workspace.header.menu.show-pixel-grid" +msgstr "Show pixel grid" + +msgid "workspace.header.menu.disable-snap-pixel-grid" +msgstr "Disable snap to pixel" + +msgid "workspace.header.menu.enable-snap-pixel-grid" +msgstr "Enable snap to pixel" + #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.reset-zoom" msgstr "Reset" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 9638816428..d0794d6e55 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -2246,6 +2246,18 @@ msgstr "Mostrar reglas" msgid "workspace.header.menu.show-textpalette" msgstr "Mostrar paleta de textos" +msgid "workspace.header.menu.hide-pixel-grid" +msgstr "Ocultar rejilla de pixeles" + +msgid "workspace.header.menu.show-pixel-grid" +msgstr "Mostrar rejilla de pixeles" + +msgid "workspace.header.menu.disable-snap-pixel-grid" +msgstr "Desactivar ajuste al pixel" + +msgid "workspace.header.menu.enable-snap-pixel-grid" +msgstr "Activar ajuste al pixel" + #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.reset-zoom" msgstr "Restablecer"