Merge pull request #1690 from penpot/feat/pixel-precision

Pixel precision
This commit is contained in:
Andrés Moya 2022-03-18 10:49:01 +01:00 committed by GitHub
commit 4d2de63374
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
86 changed files with 1488 additions and 1152 deletions

View File

@ -406,15 +406,24 @@
[v default] [v default]
(if (some? v) 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 (defn check-num
"Function that checks if a number is nil or nan. Will return 0 when not "Function that checks if a number is nil or nan. Will return 0 when not
valid and the number otherwise." valid and the number otherwise."
([v] ([v]
(check-num v 0)) (check-num v 0))
([v default] ([v default]
(if (or (not v) (if (num? v) v default)))
(not (mth/finite? v))
(mth/nan? v)) default v)))
(defn any-key? [element & rest] (defn any-key? [element & rest]
(some #(contains? element %) rest)) (some #(contains? element %) rest))

View File

@ -21,6 +21,11 @@
(def conjv (fnil conj [])) (def conjv (fnil conj []))
(def conjs (fnil conj #{})) (def conjs (fnil conj #{}))
(defn- raise
[err-str]
#?(:clj (throw (Exception. err-str))
:cljs (throw (js/Error. err-str))))
(defn- commit-change (defn- commit-change
([file change] ([file change]
(commit-change file change nil)) (commit-change file change nil))
@ -75,10 +80,12 @@
(commit-change file change {:add-container? true :fail-on-spec? fail-on-spec?}))) (commit-change file change {:add-container? true :fail-on-spec? fail-on-spec?})))
(defn setup-rect-selrect [obj] (defn setup-rect-selrect [{:keys [x y width height transform] :as obj}]
(let [rect (select-keys obj [:x :y :width :height]) (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) center (gsh/center-rect rect)
transform (:transform obj (gmt/matrix))
selrect (gsh/rect->selrect rect) selrect (gsh/rect->selrect rect)
points (-> (gsh/rect->points rect) points (-> (gsh/rect->points rect)
@ -89,17 +96,13 @@
(assoc :points points)))) (assoc :points points))))
(defn- setup-path-selrect (defn- setup-path-selrect
[obj] [{:keys [content center transform transform-inverse] :as obj}]
(let [content (:content obj)
center (:center obj)
transform-inverse (when (or (empty? content) (nil? center))
(->> (:transform-inverse obj (gmt/matrix)) (raise "Path not valid"))
(gmt/transform-in center))
transform (let [transform (gmt/transform-in center transform)
(->> (:transform obj (gmt/matrix)) transform-inverse (gmt/transform-in center transform-inverse)
(gmt/transform-in center))
content' (gsh/transform-content content transform-inverse) content' (gsh/transform-content content transform-inverse)
selrect (gsh/content->selrect content') selrect (gsh/content->selrect content')
@ -310,21 +313,30 @@
children (->> bool :shapes (mapv #(lookup-shape file %))) children (->> bool :shapes (mapv #(lookup-shape file %)))
file file
(let [objects (lookup-objects file) (cond
bool' (gsh/update-bool-selrect bool children objects)] (empty? children)
(commit-change (commit-change
file file
{:type :mod-obj {:type :del-obj
:id bool-id :id bool-id}
:operations {:add-container? true})
[{: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}))] :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 (-> file
(update :parent-stack pop)))) (update :parent-stack pop))))

View File

@ -108,9 +108,12 @@
(= v base)) (= v base))
(defn translate-matrix (defn translate-matrix
[{x :x y :y :as pt}] ([{x :x y :y :as pt}]
(assert (gpt/point? pt)) (assert (gpt/point? pt))
(Matrix. 1 0 0 1 x y)) (Matrix. 1 0 0 1 x y))
([x y]
(translate-matrix (gpt/point x y))))
(defn scale-matrix (defn scale-matrix
([pt center] ([pt center]
@ -184,7 +187,7 @@
(defmethod pp/simple-dispatch Matrix [obj] (pr obj)) (defmethod pp/simple-dispatch Matrix [obj] (pr obj))
(defn transform-in [pt mtx] (defn transform-in [pt mtx]
(if (some? pt) (if (and (some? pt) (some? mtx))
(-> (matrix) (-> (matrix)
(translate pt) (translate pt)
(multiply mtx) (multiply mtx)

View File

@ -132,9 +132,8 @@
(assert (point? other)) (assert (point? other))
(let [dx (- x ox) (let [dx (- x ox)
dy (- y oy)] dy (- y oy)]
(-> (mth/sqrt (+ (mth/pow dx 2) (mth/sqrt (+ (mth/pow dx 2)
(mth/pow dy 2))) (mth/pow dy 2)))))
(mth/precision 6))))
(defn length (defn length
[{x :x y :y :as p}] [{x :x y :y :as p}]
@ -168,8 +167,7 @@
(* y oy)) (* y oy))
(* length-p length-other)) (* length-p length-other))
a (mth/acos (if (< a -1) -1 (if (> a 1) 1 a))) a (mth/acos (if (< a -1) -1 (if (> a 1) 1 a)))
d (-> (mth/degrees a) d (mth/degrees a)]
(mth/precision 6))]
(if (mth/nan? d) 0 d))))) (if (mth/nan? d) 0 d)))))
(defn angle-sign [v1 v2] (defn angle-sign [v1 v2]
@ -194,14 +192,23 @@
(if (>= y 0) 2 3))) (if (>= y 0) 2 3)))
(defn round (defn round
"Change the precision of the point coordinates." "Round the coordinates of the point to a precision"
([point] (round point 0)) ([point]
(round point 0))
([{:keys [x y] :as p} decimals] ([{:keys [x y] :as p} decimals]
(assert (point? p)) (assert (point? p))
(assert (number? decimals)) (assert (number? decimals))
(Point. (mth/precision x decimals) (Point. (mth/precision x decimals)
(mth/precision y 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 (defn transform
"Transform a point applying a matrix transformation." "Transform a point applying a matrix transformation."
[{:keys [x y] :as p} {:keys [a b c d e f]}] [{:keys [x y] :as p} {:keys [a b c d e f]}]

View File

@ -17,39 +17,6 @@
[app.common.geom.shapes.transforms :as gtr] [app.common.geom.shapes.transforms :as gtr]
[app.common.math :as mth])) [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 ;; --- Outer Rect
(defn selection-rect (defn selection-rect
@ -106,12 +73,12 @@
:width (- x2 x1) :width (- x2 x1)
:height (- y2 y1) :height (- y2 y1)
:type :rect})) :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] {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) {:left (make-selrect bound-x1 sr-y1 sr-x1 sr-y2)
:top (make-selrect sr-x1 frame-y1 sr-x2 (- sr-y1 2)) :top (make-selrect sr-x1 bound-y1 sr-x2 sr-y1)
:right (make-selrect (+ sr-x2 2) sr-y1 frame-x2 sr-y2) :right (make-selrect sr-x2 sr-y1 bound-x2 sr-y2)
:bottom (make-selrect sr-x1 (+ sr-y2 2) sr-x2 frame-y2)})) :bottom (make-selrect sr-x1 sr-y2 sr-x2 bound-y2)}))
(defn distance-selrect [selrect other] (defn distance-selrect [selrect other]
(let [{:keys [x1 y1]} other (let [{:keys [x1 y1]} other
@ -121,13 +88,6 @@
(defn distance-shapes [shape other] (defn distance-shapes [shape other]
(distance-selrect (:selrect shape) (:selrect 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 (defn shape-stroke-margin
[shape stroke-width] [shape stroke-width]
(if (= (:type shape) :path) (if (= (:type shape) :path)
@ -141,15 +101,17 @@
(dm/export gco/center-selrect) (dm/export gco/center-selrect)
(dm/export gco/center-rect) (dm/export gco/center-rect)
(dm/export gco/center-points) (dm/export gco/center-points)
(dm/export gco/make-centered-rect)
(dm/export gco/transform-points) (dm/export gco/transform-points)
(dm/export gpr/make-rect)
(dm/export gpr/rect->selrect) (dm/export gpr/rect->selrect)
(dm/export gpr/rect->points) (dm/export gpr/rect->points)
(dm/export gpr/points->selrect) (dm/export gpr/points->selrect)
(dm/export gpr/points->rect) (dm/export gpr/points->rect)
(dm/export gpr/center->rect) (dm/export gpr/center->rect)
(dm/export gpr/center->selrect)
(dm/export gpr/join-rects) (dm/export gpr/join-rects)
(dm/export gpr/join-selrects)
(dm/export gpr/contains-selrect?) (dm/export gpr/contains-selrect?)
(dm/export gtr/move) (dm/export gtr/move)
@ -166,6 +128,7 @@
(dm/export gtr/merge-modifiers) (dm/export gtr/merge-modifiers)
(dm/export gtr/transform-shape) (dm/export gtr/transform-shape)
(dm/export gtr/transform-selrect) (dm/export gtr/transform-selrect)
(dm/export gtr/transform-bounds)
(dm/export gtr/modifiers->transform) (dm/export gtr/modifiers->transform)
(dm/export gtr/empty-modifiers?) (dm/export gtr/empty-modifiers?)

View File

@ -8,7 +8,6 @@
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.geom.shapes.path :as gsp] [app.common.geom.shapes.path :as gsp]
[app.common.geom.shapes.rect :as gpr]
[app.common.geom.shapes.transforms :as gtr] [app.common.geom.shapes.transforms :as gtr]
[app.common.path.bool :as pb] [app.common.path.bool :as pb]
[app.common.path.shapes-to-path :as stp])) [app.common.path.shapes-to-path :as stp]))
@ -30,15 +29,13 @@
"Calculates the selrect+points for the boolean shape" "Calculates the selrect+points for the boolean shape"
[shape children objects] [shape children objects]
(let [content (calc-bool-content shape objects) (let [bool-content (calc-bool-content shape objects)
[points selrect] shape (assoc shape :bool-content bool-content)
(if (empty? content) [points selrect] (gsp/content->points+selrect shape bool-content)]
(let [selrect (gtr/selection-rect children)
points (gpr/rect->points selrect)] (if (and (some? selrect) (d/not-empty? points))
[points selrect]) (-> shape
(gsp/content->points+selrect shape content))] (assoc :selrect selrect)
(-> shape (assoc :points points))
(assoc :selrect selrect) (gtr/update-group-selrect shape children))))
(assoc :points points)
(assoc :bool-content content))))

View File

@ -6,30 +6,24 @@
(ns app.common.geom.shapes.common (ns app.common.geom.shapes.common
(:require (:require
[app.common.data :as d]
[app.common.geom.matrix :as gmt] [app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]))
[app.common.math :as mth]))
(defn center-rect (defn center-rect
[{:keys [x y width height]}] [{:keys [x y width height]}]
(when (and (mth/finite? x) (when (d/num? x y width height)
(mth/finite? y)
(mth/finite? width)
(mth/finite? height))
(gpt/point (+ x (/ width 2.0)) (gpt/point (+ x (/ width 2.0))
(+ y (/ height 2.0))))) (+ y (/ height 2.0)))))
(defn center-selrect (defn center-selrect
"Calculate the center of the shape." "Calculate the center of the selrect."
[selrect] [selrect]
(center-rect 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] (defn center-points [points]
(let [ptx (into [] map-x-xf points) (let [ptx (into [] (keep :x) points)
pty (into [] map-y-xf points) pty (into [] (keep :y) points)
minx (reduce min ##Inf ptx) minx (reduce min ##Inf ptx)
miny (reduce min ##Inf pty) miny (reduce min ##Inf pty)
maxx (reduce max ##-Inf ptx) maxx (reduce max ##-Inf ptx)
@ -42,35 +36,12 @@
[shape] [shape]
(center-rect (:selrect 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 (defn transform-points
([points matrix] ([points matrix]
(transform-points points nil matrix)) (transform-points points nil matrix))
([points center 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)) (let [prev (if center (gmt/translate-matrix center) (gmt/matrix))
post (if center (gmt/translate-matrix (gpt/negate center)) (gmt/matrix)) post (if center (gmt/translate-matrix (gpt/negate center)) (gmt/matrix))

View File

@ -333,11 +333,8 @@
(command->point command :c2)]] (command->point command :c2)]]
(->> (curve-extremities curve) (->> (curve-extremities curve)
(mapv #(curve-values curve %))))) (mapv #(curve-values curve %)))))
[]) [])]
selrect (gpr/points->selrect points)] (gpr/points->selrect points))))
(-> selrect
(update :width #(if (mth/almost-zero? %) 1 %))
(update :height #(if (mth/almost-zero? %) 1 %))))))
(defn content->selrect [content] (defn content->selrect [content]
(let [calc-extremities (let [calc-extremities
@ -362,13 +359,8 @@
extremities (mapcat calc-extremities extremities (mapcat calc-extremities
content content
(concat [nil] content)) (concat [nil] content))]
(gpr/points->selrect extremities)))
selrect (gpr/points->selrect extremities)]
(-> selrect
(update :width #(if (mth/almost-zero? %) 1 %))
(update :height #(if (mth/almost-zero? %) 1 %)))))
(defn move-content [content move-vec] (defn move-content [content move-vec]
(let [dx (:x move-vec) (let [dx (:x move-vec)
@ -376,40 +368,49 @@
set-tr set-tr
(fn [params px py] (fn [params px py]
(-> params (cond-> params
(update px + dx) (d/num? dx)
(update py + dy))) (update px + dx)
(d/num? dy)
(update py + dy)))
transform-params transform-params
(fn [{:keys [x c1x c2x] :as params}] (fn [{:keys [x y c1x c1y c2x c2y] :as params}]
(cond-> params (cond-> params
(some? x) (set-tr :x :y) (d/num? x y) (set-tr :x :y)
(some? c1x) (set-tr :c1x :c1y) (d/num? c1x c1y) (set-tr :c1x :c1y)
(some? c2x) (set-tr :c2x :c2y)))] (d/num? c2x c2y) (set-tr :c2x :c2y)))
(into [] update-command
(map #(update % :params transform-params)) (fn [command]
content))) (update command :params transform-params))]
(->> content
(into [] (map update-command)))))
(defn transform-content (defn transform-content
[content transform] [content transform]
(let [set-tr (fn [params px py] (if (some? transform)
(let [tr-point (-> (gpt/point (get params px) (get params py)) (let [set-tr
(gpt/transform transform))] (fn [params px py]
(assoc params (let [tr-point (-> (gpt/point (get params px) (get params py))
px (:x tr-point) (gpt/transform transform))]
py (:y tr-point)))) (assoc params
px (:x tr-point)
py (:y tr-point))))
transform-params transform-params
(fn [{:keys [x c1x c2x] :as params}] (fn [{:keys [x c1x c2x] :as params}]
(cond-> params (cond-> params
(some? x) (set-tr :x :y) (some? x) (set-tr :x :y)
(some? c1x) (set-tr :c1x :c1y) (some? c1x) (set-tr :c1x :c1y)
(some? c2x) (set-tr :c2x :c2y)))] (some? c2x) (set-tr :c2x :c2y)))]
(into [] (into []
(map #(update % :params transform-params)) (map #(update % :params transform-params))
content))) content))
content))
(defn segments->content (defn segments->content
([segments] ([segments]
@ -980,7 +981,6 @@
(gpr/points->selrect))] (gpr/points->selrect))]
[points selrect])) [points selrect]))
(defn open-path? (defn open-path?
[shape] [shape]

View File

@ -6,81 +6,101 @@
(ns app.common.geom.shapes.rect (ns app.common.geom.shapes.rect
(:require (:require
[app.common.data :as d]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.shapes.common :as gco]
[app.common.math :as mth])) [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]}] (defn rect->points [{:keys [x y width height]}]
;; (assert (number? x)) (when (d/num? x y)
;; (assert (number? y)) (let [width (max width 0.01)
;; (assert (and (number? width) (> width 0))) height (max height 0.01)]
;; (assert (and (number? height) (> height 0))) [(gpt/point x y)
[(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))])
(defn rect->lines [{:keys [x y width height]}] (defn rect->lines [{:keys [x y width height]}]
[[(gpt/point x y) (gpt/point (+ x width) y)] (when (d/num? x y)
[(gpt/point (+ x width) y) (gpt/point (+ x width) (+ y height))] (let [width (max width 0.01)
[(gpt/point (+ x width) (+ y height)) (gpt/point x (+ y height))] height (max height 0.01)]
[(gpt/point x (+ y height)) (gpt/point x y)]]) [[(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 (defn points->rect
[points] [points]
(let [minx (transduce gco/map-x-xf min ##Inf points) (when (d/not-empty? points)
miny (transduce gco/map-y-xf min ##Inf points) (let [minx (transduce (keep :x) min ##Inf points)
maxx (transduce gco/map-x-xf max ##-Inf points) miny (transduce (keep :y) min ##Inf points)
maxy (transduce gco/map-y-xf max ##-Inf points)] maxx (transduce (keep :x) max ##-Inf points)
{:x minx maxy (transduce (keep :y) max ##-Inf points)]
:y miny (when (d/num? minx miny maxx maxy)
:width (- maxx minx) (make-rect minx miny (- maxx minx) (- maxy miny))))))
:height (- maxy miny)}))
(defn points->selrect [points] (defn points->selrect [points]
(let [{:keys [x y width height] :as rect} (points->rect points)] (when-let [rect (points->rect points)]
(assoc rect (let [{:keys [x y width height]} rect]
:x1 x (make-selrect x y width height))))
:x2 (+ x width)
:y1 y
:y2 (+ y height))))
(defn rect->selrect [rect] (defn rect->selrect [rect]
(-> rect rect->points points->selrect)) (-> rect rect->points points->selrect))
(defn join-rects [rects] (defn join-rects [rects]
(let [minx (transduce (comp (map :x) (remove nil?)) min ##Inf rects) (when (d/not-empty? rects)
miny (transduce (comp (map :y) (remove nil?)) min ##Inf rects) (let [minx (transduce (keep :x) min ##Inf rects)
maxx (transduce (comp (map #(+ (:x %) (:width %))) (remove nil?)) max ##-Inf rects) miny (transduce (keep :y) min ##Inf rects)
maxy (transduce (comp (map #(+ (:y %) (:height %))) (remove nil?)) max ##-Inf rects)] maxx (transduce (keep #(when (and (:x %) (:width %)) (+ (:x %) (:width %)))) max ##-Inf rects)
{:x minx maxy (transduce (keep #(when (and (:y %) (:height %))(+ (:y %) (:height %)))) max ##-Inf rects)]
:y miny (when (d/num? minx miny maxx maxy)
:width (- maxx minx) (make-rect minx miny (- maxx minx) (- maxy miny))))))
:height (- maxy miny)}))
(defn join-selrects [selrects] (defn join-selrects [selrects]
(let [minx (transduce (comp (map :x1) (remove nil?)) min ##Inf selrects) (when (d/not-empty? selrects)
miny (transduce (comp (map :y1) (remove nil?)) min ##Inf selrects) (let [minx (transduce (keep :x1) min ##Inf selrects)
maxx (transduce (comp (map :x2) (remove nil?)) max ##-Inf selrects) miny (transduce (keep :y1) min ##Inf selrects)
maxy (transduce (comp (map :y2) (remove nil?)) max ##-Inf selrects)] maxx (transduce (keep :x2) max ##-Inf selrects)
{:x minx maxy (transduce (keep :y2) max ##-Inf selrects)]
:y miny (when (d/num? minx miny maxx maxy)
:x1 minx (make-selrect minx miny (- maxx minx) (- maxy miny))))))
:y1 miny
:x2 maxx
:y2 maxy
:width (- maxx minx)
:height (- maxy miny)}))
(defn center->rect [center width height] (defn center->rect [{:keys [x y]} width height]
(assert (gpt/point center)) (when (d/num? x y width height)
(assert (and (number? width) (> width 0))) (make-rect (- x (/ width 2))
(assert (and (number? height) (> height 0))) (- y (/ height 2))
width
height)))
{:x (- (:x center) (/ width 2)) (defn center->selrect [{:keys [x y]} width height]
:y (- (:y center) (/ height 2)) (when (d/num? x y width height)
:width width (make-selrect (- x (/ width 2))
:height height}) (- y (/ height 2))
width
height)))
(defn s= (defn s=
[a b] [a b]
@ -130,10 +150,3 @@
(>= (:y1 sr2) (:y1 sr1)) (>= (:y1 sr2) (:y1 sr1))
(<= (:y2 sr2) (:y2 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)))

View File

@ -21,38 +21,38 @@
;; --- Relative Movement ;; --- Relative Movement
(defn- move-selrect [selrect pt] (defn- move-selrect [{:keys [x y x1 y1 x2 y2 width height] :as selrect} {dx :x dy :y :as pt}]
(when (and (some? selrect) (some? pt)) (if (and (some? selrect) (some? pt) (d/num? dx dy))
(let [dx (.-x pt) {:x (if (d/num? x) (+ dx x) x)
dy (.-y pt) :y (if (d/num? y) (+ dy y) y)
{:keys [x y x1 y1 x2 y2 width height]} selrect] :x1 (if (d/num? x1) (+ dx x1) x1)
{:x (if (some? x) (+ dx x) x) :y1 (if (d/num? y1) (+ dy y1) y1)
:y (if (some? y) (+ dy y) y) :x2 (if (d/num? x2) (+ dx x2) x2)
:x1 (if (some? x1) (+ dx x1) x1) :y2 (if (d/num? y2) (+ dy y2) y2)
:y1 (if (some? y1) (+ dy y1) y1) :width width
:x2 (if (some? x2) (+ dx x2) x2) :height height}
:y2 (if (some? y2) (+ dy y2) y2) selrect))
:width width
:height height})))
(defn- move-points [points move-vec] (defn- move-points [points move-vec]
(->> points (cond->> points
(mapv #(gpt/add % move-vec)))) (d/num? (:x move-vec) (:y move-vec))
(mapv #(gpt/add % move-vec))))
(defn move-position-data (defn move-position-data
[position-data dx dy] [position-data dx dy]
(->> position-data (cond->> position-data
(mapv #(-> % (d/num? dx dy)
(update :x + dx) (mapv #(-> %
(update :y + dy))))) (update :x + dx)
(update :y + dy)))))
(defn move (defn move
"Move the shape relatively to its current "Move the shape relatively to its current
position applying the provided delta." position applying the provided delta."
[{:keys [type] :as shape} {dx :x dy :y}] [{:keys [type] :as shape} {dx :x dy :y}]
(let [dx (d/check-num dx) (let [dx (d/check-num dx 0)
dy (d/check-num dy) dy (d/check-num dy 0)
move-vec (gpt/point dx dy)] move-vec (gpt/point dx dy)]
(-> shape (-> shape
@ -138,9 +138,12 @@
(defn transform-matrix (defn transform-matrix
"Returns a transformation matrix without changing the shape properties. "Returns a transformation matrix without changing the shape properties.
The result should be used in a `transform` attribute in svg" The result should be used in a `transform` attribute in svg"
([shape] (transform-matrix shape nil)) ([shape]
([shape params] (transform-matrix shape params (or (gco/center-shape shape) (transform-matrix shape nil))
(gpt/point 0 0))))
([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] ([{:keys [flip-x flip-y] :as shape} {:keys [no-flip]} shape-center]
(-> (gmt/matrix) (-> (gmt/matrix)
(gmt/translate shape-center) (gmt/translate shape-center)
@ -168,12 +171,13 @@
(defn transform-point-center (defn transform-point-center
"Transform a point around the shape center" "Transform a point around the shape center"
[point center matrix] [point center matrix]
(when point (if (and (some? point) (some? matrix) (some? center))
(gpt/transform (gpt/transform
point point
(gmt/multiply (gmt/translate-matrix center) (gmt/multiply (gmt/translate-matrix center)
matrix matrix
(gmt/translate-matrix (gpt/negate center)))))) (gmt/translate-matrix (gpt/negate center))))
point))
(defn transform-rect (defn transform-rect
"Transform a rectangles and changes its attributes" "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 ;; This rectangle is the new data for the current rectangle. We want to change our rectangle
;; to have this width, height, x, y ;; to have this width, height, x, y
new-width (max 1 (:width points-temp-dim)) new-width (max 0.01 (:width points-temp-dim))
new-height (max 1 (:height points-temp-dim)) new-height (max 0.01 (:height points-temp-dim))
selrect (gco/make-centered-selrect center new-width new-height) selrect (gpr/center->selrect center new-width new-height)
rect-points (gpr/rect->points selrect) rect-points (gpr/rect->points selrect)
[matrix matrix-inverse] (calculate-adjust-matrix points-temp rect-points flip-x flip-y)] [matrix matrix-inverse] (calculate-adjust-matrix points-temp rect-points flip-x flip-y)]
@ -263,7 +267,7 @@
(defn- apply-transform (defn- apply-transform
"Given a new set of points transformed, set up the rectangle so it keeps "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" 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) (let [points' (:points shape)
points (gco/transform-points points' transform-mtx) points (gco/transform-points points' transform-mtx)
@ -276,10 +280,6 @@
[(gpr/points->selrect points) nil nil] [(gpr/points->selrect points) nil nil]
(adjust-rotated-transform shape points)) (adjust-rotated-transform shape points))
selrect (cond-> selrect
round-coords? gpr/round-selrect)
;; Redondear los points?
base-rotation (or (:rotation shape) 0) base-rotation (or (:rotation shape) 0)
modif-rotation (or (get-in shape [:modifiers :rotation]) 0) modif-rotation (or (get-in shape [:modifiers :rotation]) 0)
rotation (mod (+ base-rotation modif-rotation) 360)] rotation (mod (+ base-rotation modif-rotation) 360)]
@ -296,8 +296,10 @@
(assoc :transform-inverse transform-inverse))) (assoc :transform-inverse transform-inverse)))
(cond-> (not transform) (cond-> (not transform)
(dissoc :transform :transform-inverse)) (dissoc :transform :transform-inverse))
(assoc :selrect selrect) (cond-> (some? selrect)
(assoc :points points) (assoc :selrect selrect))
(cond-> (d/not-empty? points)
(assoc :points points))
(assoc :rotation rotation)))) (assoc :rotation rotation))))
(defn- update-group-viewbox (defn- update-group-viewbox
@ -345,7 +347,7 @@
;; need to remove the flip flags ;; need to remove the flip flags
(assoc :flip-x false) (assoc :flip-x false)
(assoc :flip-y false) (assoc :flip-y false)
(apply-transform (gmt/matrix) true)))) (apply-transform (gmt/matrix)))))
(defn update-mask-selrect (defn update-mask-selrect
[masked-group children] [masked-group children]
@ -409,13 +411,14 @@
width (:width new-size) width (:width new-size)
height (:height new-size) height (:height new-size)
shape-transform (:transform shape (gmt/matrix)) shape-transform (:transform shape)
shape-transform-inv (:transform-inverse shape (gmt/matrix)) shape-transform-inv (:transform-inverse shape)
shape-center (gco/center-shape shape) shape-center (gco/center-shape shape)
{sr-width :width sr-height :height} (:selrect shape) {sr-width :width sr-height :height} (:selrect shape)
origin (-> (gpt/point (:selrect shape)) origin (cond-> (gpt/point (:selrect shape))
(transform-point-center shape-center shape-transform)) (some? shape-transform)
(transform-point-center shape-center shape-transform))
scalev (gpt/divide (gpt/point width height) scalev (gpt/divide (gpt/point width height)
(gpt/point sr-width sr-height))] (gpt/point sr-width sr-height))]
@ -464,24 +467,28 @@
(normalize-scale (:y resize-v2)))) (normalize-scale (:y resize-v2))))
resize-transform (:resize-transform modifiers (gmt/matrix)) resize-transform (:resize-transform modifiers)
resize-transform-inverse (:resize-transform-inverse modifiers (gmt/matrix)) resize-transform-inverse (:resize-transform-inverse modifiers)
rt-modif (:rotation modifiers)] rt-modif (:rotation modifiers)]
(cond-> (gmt/matrix) (cond-> (gmt/matrix)
(some? resize-1) (some? resize-1)
(-> (gmt/translate origin-1) (-> (gmt/translate origin-1)
(gmt/multiply resize-transform) (cond-> (some? resize-transform)
(gmt/multiply resize-transform))
(gmt/scale resize-1) (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))) (gmt/translate (gpt/negate origin-1)))
(some? resize-2) (some? resize-2)
(-> (gmt/translate origin-2) (-> (gmt/translate origin-2)
(gmt/multiply resize-transform) (cond-> (some? resize-transform)
(gmt/multiply resize-transform))
(gmt/scale resize-2) (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))) (gmt/translate (gpt/negate origin-2)))
(some? displacement) (some? displacement)
@ -525,7 +532,6 @@
(d/parse-double) (d/parse-double)
(* (get-in modifiers [:resize-vector :x] 1)) (* (get-in modifiers [:resize-vector :x] 1))
(* (get-in modifiers [:resize-vector-2 :x] 1)) (* (get-in modifiers [:resize-vector-2 :x] 1))
(mth/precision 2)
(str))] (str))]
(attrs/merge attrs {:font-size font-size})))] (attrs/merge attrs {:font-size font-size})))]
(update shape :content #(txt/transform-nodes (update shape :content #(txt/transform-nodes
@ -535,64 +541,54 @@
shape)) shape))
(defn apply-modifiers (defn apply-modifiers
[shape modifiers round-coords?] [shape modifiers]
(let [center (gco/center-shape shape) (let [center (gco/center-shape shape)
transform (modifiers->transform center modifiers)] transform (modifiers->transform center modifiers)]
(apply-transform shape transform round-coords?))) (apply-transform shape transform)))
(defn transform-shape (defn transform-shape
([shape] [shape]
(transform-shape shape nil)) (let [modifiers (:modifiers shape)]
(cond
(nil? modifiers)
shape
([shape {:keys [round-coords?] :or {round-coords? true}}] (empty-modifiers? modifiers)
(let [modifiers (:modifiers shape)] (dissoc shape :modifiers)
(cond
(nil? modifiers)
shape
(empty-modifiers? modifiers) :else
(dissoc shape :modifiers) (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 :always
(let [shape (apply-displacement shape) (dissoc :modifiers))))))
modifiers (:modifiers shape)]
(cond-> shape
(not (empty-modifiers? modifiers))
(-> (set-flip modifiers)
(apply-modifiers modifiers round-coords?)
(apply-text-resize modifiers))
:always (defn transform-bounds
(dissoc :modifiers))))))) [points center {:keys [displacement resize-transform-inverse resize-vector resize-origin resize-vector-2 resize-origin-2]}]
(defn transform-selrect
[selrect {:keys [displacement resize-transform-inverse resize-vector resize-origin resize-vector-2 resize-origin-2]}]
;; FIXME: Improve Performance ;; FIXME: Improve Performance
(let [resize-transform-inverse (or resize-transform-inverse (gmt/matrix)) (let [resize-transform-inverse (or resize-transform-inverse (gmt/matrix))
displacement displacement
(when (some? displacement) (when (some? displacement)
(gmt/multiply resize-transform-inverse displacement) (gmt/multiply resize-transform-inverse displacement))
#_(-> (gpt/point 0 0)
(gpt/transform displacement)
(gpt/transform resize-transform-inverse)
(gmt/translate-matrix)))
resize-origin resize-origin
(when (some? 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 resize-origin-2
(when (some? 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)) (if (and (nil? displacement) (nil? resize-origin) (nil? resize-origin-2))
selrect points
(cond-> selrect
:always
(gpr/rect->points)
(cond-> points
(some? displacement) (some? displacement)
(gco/transform-points displacement) (gco/transform-points displacement)
@ -600,11 +596,15 @@
(gco/transform-points resize-origin (gmt/scale-matrix resize-vector)) (gco/transform-points resize-origin (gmt/scale-matrix resize-vector))
(some? resize-origin-2) (some? resize-origin-2)
(gco/transform-points resize-origin-2 (gmt/scale-matrix resize-vector-2)) (gco/transform-points resize-origin-2 (gmt/scale-matrix resize-vector-2))))))
:always
(gpr/points->selrect)))))
(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 (defn selection-rect
"Returns a rect that contains all the shapes and is aware of the "Returns a rect that contains all the shapes and is aware of the

View File

@ -106,6 +106,11 @@
#?(:cljs (js/Math.round v) #?(:cljs (js/Math.round v)
:clj (Math/round (float 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 (defn ceil
"Returns the smallest integer greater than "Returns the smallest integer greater than
or equal to a given number." or equal to a given number."
@ -115,7 +120,7 @@
(defn precision (defn precision
[v n] [v n]
(when (and (number? v) (number? n)) (when (and (number? v) (integer? n))
(let [d (pow 10 n)] (let [d (pow 10 n)]
(/ (round (* v d)) d)))) (/ (round (* v d)) d))))
@ -165,3 +170,7 @@
[v0 v1 t] [v0 v1 t]
(+ (* (- 1 t) v0) (+ (* (- 1 t) v0)
(* t v1))) (* t v1)))
(defn max-abs
[a b]
(max (abs a) (abs b)))

View File

@ -42,4 +42,5 @@
(dm/export init/make-minimal-shape) (dm/export init/make-minimal-shape)
(dm/export init/make-minimal-group) (dm/export init/make-minimal-group)
(dm/export init/empty-file-data) (dm/export init/empty-file-data)
(dm/export init/setup-shape)
(dm/export init/setup-rect-selrect)

View File

@ -104,6 +104,7 @@
:group #{:proportion-lock :group #{:proportion-lock
:width :height :width :height
:x :y :x :y
:rotation
:selrect :selrect
:constraints-h :constraints-h

View File

@ -9,6 +9,7 @@
[app.common.colors :as clr] [app.common.colors :as clr]
[app.common.data :as d] [app.common.data :as d]
[app.common.exceptions :as ex] [app.common.exceptions :as ex]
[app.common.geom.shapes :as gsh]
[app.common.pages.common :refer [file-version default-color]] [app.common.pages.common :refer [file-version default-color]]
[app.common.uuid :as uuid])) [app.common.uuid :as uuid]))
@ -91,8 +92,8 @@
(def empty-selrect (def empty-selrect
{:x 0 :y 0 {:x 0 :y 0
:x1 0 :y1 0 :x1 0 :y1 0
:x2 1 :y2 1 :x2 0.01 :y2 0.01
:width 1 :height 1}) :width 0.01 :height 0.01})
(defn make-minimal-shape (defn make-minimal-shape
[type] [type]
@ -111,16 +112,16 @@
(not= :path (:type shape)) (not= :path (:type shape))
(assoc :x 0 (assoc :x 0
:y 0 :y 0
:width 1 :width 0.01
:height 1 :height 0.01
:selrect {:x 0 :selrect {:x 0
:y 0 :y 0
:x1 0 :x1 0
:y1 0 :y1 0
:x2 1 :x2 0.01
:y2 1 :y2 0.01
:width 1 :width 0.01
:height 1})))) :height 0.01}))))
(defn make-minimal-group (defn make-minimal-group
[frame-id selection-rect group-name] [frame-id selection-rect group-name]
@ -146,3 +147,40 @@
(assoc :id file-id) (assoc :id file-id)
(update :pages conj page-id) (update :pages conj page-id)
(update :pages-index assoc page-id pd))))) (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))))

View File

@ -84,7 +84,7 @@
(fix-empty-points [shape] (fix-empty-points [shape]
(let [shape (cond-> shape (let [shape (cond-> shape
(empty? (:selrect shape)) (gsh/setup-selrect))] (empty? (:selrect shape)) (cp/setup-rect-selrect))]
(cond-> shape (cond-> shape
(empty? (:points shape)) (empty? (:points shape))
(assoc :points (gsh/rect->points (:selrect shape)))))) (assoc :points (gsh/rect->points (:selrect shape))))))

View File

@ -9,7 +9,7 @@
[app.common.geom.matrix :as gmt] [app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh] [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]] [app.common.pages :refer [make-minimal-shape]]
[clojure.test :as t])) [clojure.test :as t]))
@ -52,7 +52,7 @@
(t/testing "Shape without modifiers should stay the same" (t/testing "Shape without modifiers should stay the same"
(t/are [type] (t/are [type]
(let [shape-before (create-test-shape 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)) (= shape-before shape-after))
:rect :path)) :rect :path))
@ -61,7 +61,7 @@
(t/are [type] (t/are [type]
(let [modifiers {:displacement (gmt/translate-matrix (gpt/point 10 -10))}] (let [modifiers {:displacement (gmt/translate-matrix (gpt/point 10 -10))}]
(let [shape-before (create-test-shape type {:modifiers modifiers}) (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 (not= shape-before shape-after))
(t/is (close? (get-in shape-before [:selrect :x]) (t/is (close? (get-in shape-before [:selrect :x])
@ -82,7 +82,7 @@
(t/are [type] (t/are [type]
(let [modifiers {:displacement (gmt/matrix)} (let [modifiers {:displacement (gmt/matrix)}
shape-before (create-test-shape type {:modifiers modifiers}) 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/are [prop]
(t/is (close? (get-in shape-before [:selrect prop]) (t/is (close? (get-in shape-before [:selrect prop])
(get-in shape-after [:selrect prop]))) (get-in shape-after [:selrect prop])))
@ -95,7 +95,7 @@
:resize-vector (gpt/point 2 2) :resize-vector (gpt/point 2 2)
:resize-transform (gmt/matrix)} :resize-transform (gmt/matrix)}
shape-before (create-test-shape type {:modifiers modifiers}) 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 (not= shape-before shape-after))
(t/is (close? (get-in shape-before [:selrect :x]) (t/is (close? (get-in shape-before [:selrect :x])
@ -117,7 +117,7 @@
:resize-vector (gpt/point 1 1) :resize-vector (gpt/point 1 1)
:resize-transform (gmt/matrix)} :resize-transform (gmt/matrix)}
shape-before (create-test-shape type {:modifiers modifiers}) 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/are [prop]
(t/is (close? (get-in shape-before [:selrect prop]) (t/is (close? (get-in shape-before [:selrect prop])
(get-in shape-after [:selrect prop]))) (get-in shape-after [:selrect prop])))
@ -130,7 +130,7 @@
:resize-vector (gpt/point 0 0) :resize-vector (gpt/point 0 0)
:resize-transform (gmt/matrix)} :resize-transform (gmt/matrix)}
shape-before (create-test-shape type {:modifiers modifiers}) 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]) (t/is (> (get-in shape-before [:selrect :width])
(get-in shape-after [:selrect :width]))) (get-in shape-after [:selrect :width])))
(t/is (> (get-in shape-after [:selrect :width]) 0)) (t/is (> (get-in shape-after [:selrect :width]) 0))
@ -144,7 +144,7 @@
(t/are [type] (t/are [type]
(let [modifiers {:rotation 30} (let [modifiers {:rotation 30}
shape-before (create-test-shape type {:modifiers modifiers}) 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 (not= shape-before shape-after))
@ -168,7 +168,7 @@
(t/are [type] (t/are [type]
(let [modifiers {:rotation 0} (let [modifiers {:rotation 0}
shape-before (create-test-shape type {:modifiers modifiers}) 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/are [prop]
(t/is (close? (get-in shape-before [:selrect prop]) (t/is (close? (get-in shape-before [:selrect prop])
(get-in shape-after [:selrect prop]))) (get-in shape-after [:selrect prop])))
@ -180,7 +180,7 @@
(let [modifiers {:displacement (gmt/matrix)} (let [modifiers {:displacement (gmt/matrix)}
shape-before (-> (create-test-shape type {:modifiers modifiers}) shape-before (-> (create-test-shape type {:modifiers modifiers})
(assoc :selrect selrect)) (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-before)
(:selrect shape-after))) (:selrect shape-after)))

View File

@ -7,6 +7,7 @@
(ns app.common.geom-test (ns app.common.geom-test
(:require (:require
[clojure.test :as t] [clojure.test :as t]
[app.common.math :as mth]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.matrix :as gmt])) [app.common.geom.matrix :as gmt]))
@ -67,7 +68,7 @@
p2 (gpt/point 10 10) p2 (gpt/point 10 10)
angle (gpt/angle-with-other p1 p2)] angle (gpt/angle-with-other p1 p2)]
(t/is (number? angle)) (t/is (number? angle))
(t/is (= angle 45.0))))) (t/is (mth/close? angle 45.0)))))
(t/deftest matrix-constructors-test (t/deftest matrix-constructors-test
(let [m (gmt/matrix)] (let [m (gmt/matrix)]

View File

@ -241,12 +241,13 @@ $height-palette-max: 80px;
position: relative; position: relative;
.viewport-overlays { .viewport-overlays {
cursor: initial;
height: 100%;
overflow: hidden;
pointer-events: none;
position: absolute; position: absolute;
width: 100%; width: 100%;
height: 100%;
z-index: 10; z-index: 10;
pointer-events: none;
cursor: initial;
.pixel-overlay { .pixel-overlay {
height: 100%; height: 100%;

View File

@ -395,8 +395,8 @@
srect (gsh/selection-rect shapes) srect (gsh/selection-rect shapes)
local (assoc local :vport size :zoom 1)] local (assoc local :vport size :zoom 1)]
(cond (cond
(or (not (mth/finite? (:width srect))) (or (not (d/num? (:width srect)))
(not (mth/finite? (:height srect)))) (not (d/num? (:height srect))))
(assoc local :vbox (assoc size :x 0 :y 0)) (assoc local :vbox (assoc size :x 0 :y 0))
(or (> (:width srect) width) (or (> (:width srect) width)
@ -1574,7 +1574,7 @@
page-id (:current-page-id state) page-id (:current-page-id state)
frame-id (-> (wsh/lookup-page-objects state page-id) frame-id (-> (wsh/lookup-page-objects state page-id)
(cph/frame-id-by-position @ms/mouse-position)) (cph/frame-id-by-position @ms/mouse-position))
shape (gsh/setup-selrect shape (cp/setup-rect-selrect
{:id id {:id id
:type :text :type :text
:name "Text" :name "Text"
@ -1667,7 +1667,7 @@
shape (-> (cp/make-minimal-shape :frame) shape (-> (cp/make-minimal-shape :frame)
(merge {:x (:x srect) :y (:y srect) :width (:width srect) :height (:height srect)}) (merge {:x (:x srect) :y (:y srect) :width (:width srect) :height (:height srect)})
(assoc :frame-id frame-id) (assoc :frame-id frame-id)
(gsh/setup-selrect))] (cp/setup-rect-selrect))]
(rx/of (rx/of
(dwu/start-undo-transaction) (dwu/start-undo-transaction)
(dwc/add-shape shape) (dwc/add-shape shape)

View File

@ -6,7 +6,6 @@
(ns app.main.data.workspace.comments (ns app.main.data.workspace.comments
(:require (:require
[app.common.math :as mth]
[app.common.spec :as us] [app.common.spec :as us]
[app.main.data.comments :as dcm] [app.main.data.comments :as dcm]
[app.main.data.workspace :as dw] [app.main.data.workspace :as dw]
@ -78,8 +77,8 @@
(fn [{:keys [vbox zoom] :as local}] (fn [{:keys [vbox zoom] :as local}]
(let [pw (/ 160 zoom) (let [pw (/ 160 zoom)
ph (/ 160 zoom) ph (/ 160 zoom)
nw (mth/round (- (/ (:width vbox) 2) pw)) nw (- (/ (:width vbox) 2) pw)
nh (mth/round (- (/ (:height vbox) 2) ph)) nh (- (/ (:height vbox) 2) ph)
nx (- (:x position) nw) nx (- (:x position) nw)
ny (- (:y position) nh)] ny (- (:y position) nh)]
(update local :vbox assoc :x nx :y ny))))))) (update local :vbox assoc :x nx :y ny)))))))

View File

@ -480,7 +480,7 @@
(merge data) (merge data)
(merge {:x x :y y}) (merge {:x x :y y})
(assoc :frame-id frame-id) (assoc :frame-id frame-id)
(gsh/setup-selrect))] (cp/setup-rect-selrect))]
(rx/of (add-shape shape)))))) (rx/of (add-shape shape))))))
(defn image-uploaded (defn image-uploaded

View File

@ -9,6 +9,7 @@
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.math :as mth] [app.common.math :as mth]
[app.common.pages :as cp]
[app.common.pages.helpers :as cph] [app.common.pages.helpers :as cph]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.main.data.workspace.drawing.common :as common] [app.main.data.workspace.drawing.common :as common]
@ -27,8 +28,8 @@
shapev (gpt/point width height) shapev (gpt/point width height)
deltav (gpt/to-vec initial point) deltav (gpt/to-vec initial point)
scalev (-> (gpt/divide (gpt/add shapev deltav) shapev) scalev (-> (gpt/divide (gpt/add shapev deltav) shapev)
(update :x truncate-zero 1) (update :x truncate-zero 0.01)
(update :y truncate-zero 1)) (update :y truncate-zero 0.01))
scalev (if lock? scalev (if lock?
(let [v (max (:x scalev) (:y scalev))] (let [v (max (:x scalev) (:y scalev))]
(gpt/point v v)) (gpt/point v v))
@ -45,9 +46,7 @@
(defn move-drawing (defn move-drawing
[{:keys [x y]}] [{:keys [x y]}]
(fn [state] (fn [state]
(let [x (mth/precision x 0) (update-in state [:workspace-drawing :object] gsh/absolute-move (gpt/point x y))))
y (mth/precision y 0)]
(update-in state [:workspace-drawing :object] gsh/absolute-move (gpt/point x y)))))
(defn handle-drawing-box [] (defn handle-drawing-box []
(ptk/reify ::handle-drawing-box (ptk/reify ::handle-drawing-box
@ -55,11 +54,14 @@
(watch [_ state stream] (watch [_ state stream]
(let [stoper? #(or (ms/mouse-up? %) (= % :interrupt)) (let [stoper? #(or (ms/mouse-up? %) (= % :interrupt))
stoper (rx/filter stoper? stream) 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) page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id) objects (wsh/lookup-page-objects state page-id)
layout (get state :workspace-layout)
focus (:workspace-focus-selected state) focus (:workspace-focus-selected state)
zoom (get-in state [:workspace-local :zoom] 1) zoom (get-in state [:workspace-local :zoom] 1)
@ -72,7 +74,10 @@
shape (-> state shape (-> state
(get-in [:workspace-drawing :object]) (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 :frame-id fid)
(assoc :initialized? true) (assoc :initialized? true)
(assoc :click-draw? true))] (assoc :click-draw? true))]
@ -85,7 +90,7 @@
(rx/map move-drawing)) (rx/map move-drawing))
(->> ms/mouse-position (->> 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/with-latest vector ms/mouse-position-shift)
(rx/switch-map (rx/switch-map
(fn [[point :as current]] (fn [[point :as current]]
@ -93,7 +98,7 @@
(rx/map #(conj current %))))) (rx/map #(conj current %)))))
(rx/map (rx/map
(fn [[_ shift? point]] (fn [[_ shift? point]]
#(update-drawing % point shift?))) #(update-drawing % (cond-> point snap-pixel? gpt/round) shift?)))
(rx/take-until stoper)) (rx/take-until stoper))
(rx/of common/handle-finish-drawing)))))) (rx/of common/handle-finish-drawing))))))

View File

@ -6,8 +6,10 @@
(ns app.main.data.workspace.drawing.common (ns app.main.data.workspace.drawing.common
(:require (:require
[app.common.geom.point :as gpt] [app.common.geom.matrix :as gmt]
[app.common.geom.shapes :as gsh] [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.common :as dwc]
[app.main.data.workspace.undo :as dwu] [app.main.data.workspace.undo :as dwu]
[app.main.worker :as uw] [app.main.worker :as uw]
@ -29,27 +31,30 @@
(rx/concat (rx/concat
(when (:initialized? shape) (when (:initialized? shape)
(let [page-id (:current-page-id state) (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 click-draw? (:click-draw? shape)
(= (:type shape) :text) (assoc :grow-type text? (= :text (:type shape))
(if (:click-draw? shape) :auto-width :fixed)))
shape (-> shape min-side (min 100
(gsh/transform-shape) (mth/floor (get-in state [:workspace-local :vbox :width]))
(dissoc :initialized? :click-draw?))] (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 ;; Add & select the created shape to the workspace
(rx/concat (rx/concat
(if (= :text (:type shape)) (if (= :text (:type shape))

View File

@ -76,7 +76,7 @@
selrect (gsh/selection-rect shapes) selrect (gsh/selection-rect shapes)
group (-> (cp/make-minimal-group frame-id selrect gname) group (-> (cp/make-minimal-group frame-id selrect gname)
(gsh/setup selrect) (cp/setup-shape selrect)
(assoc :shapes (mapv :id shapes) (assoc :shapes (mapv :id shapes)
:parent-id parent-id :parent-id parent-id
:frame-id frame-id :frame-id frame-id

View File

@ -27,7 +27,9 @@
:scale-text :scale-text
:dynamic-alignment :dynamic-alignment
:display-artboard-names :display-artboard-names
:snap-guides}) :snap-guides
:show-pixel-grid
:snap-pixel-grid})
(def presets (def presets
{:assets {:assets
@ -53,7 +55,9 @@
:snap-grid :snap-grid
:dynamic-alignment :dynamic-alignment
:display-artboard-names :display-artboard-names
:snap-guides}) :snap-guides
:show-pixel-grid
:snap-pixel-grid})
(def default-global (def default-global
{:options-mode :design}) {:options-mode :design})

View File

@ -103,20 +103,21 @@
(defn handle-area-selection (defn handle-area-selection
[shift?] [shift?]
(letfn [(valid-rect? [{width :width height :height}] (letfn [(valid-rect? [zoom {width :width height :height}]
(or (> width 10) (> height 10)))] (or (> width (/ 10 zoom)) (> height (/ 10 zoom))))]
(ptk/reify ::handle-area-selection (ptk/reify ::handle-area-selection
ptk/WatchEvent ptk/WatchEvent
(watch [_ _ stream] (watch [_ state stream]
(let [stop? (fn [event] (or (dwc/interrupt? event) (ms/mouse-up? event))) (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?)) stoper (->> stream (rx/filter stop?))
from-p @ms/mouse-position] from-p @ms/mouse-position]
(rx/concat (rx/concat
(->> ms/mouse-position (->> ms/mouse-position
(rx/take-until stoper) (rx/take-until stoper)
(rx/map #(gsh/points->rect [from-p %])) (rx/map #(gsh/points->rect [from-p %]))
(rx/filter valid-rect?) (rx/filter (partial valid-rect? zoom))
(rx/map update-area-selection)) (rx/map update-area-selection))
(rx/of (select-node-area shift?) (rx/of (select-node-area shift?)

View File

@ -8,7 +8,6 @@
(:require (:require
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.shapes.path :as upg] [app.common.geom.shapes.path :as upg]
[app.common.math :as mth]
[app.main.data.workspace.path.state :as state] [app.main.data.workspace.path.state :as state]
[app.main.snap :as snap] [app.main.snap :as snap]
[app.main.store :as st] [app.main.store :as st]
@ -18,6 +17,7 @@
[potok.core :as ptk])) [potok.core :as ptk]))
(defonce drag-threshold 5) (defonce drag-threshold 5)
(def zoom-half-pixel-precision 8)
(defn dragging? [start zoom] (defn dragging? [start zoom]
(fn [current] (fn [current]
@ -26,19 +26,37 @@
(defn finish-edition? [event] (defn finish-edition? [event]
(= (ptk/type event) :app.main.data.workspace.common/clear-edition-mode)) (= (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 (defn drag-stream
([to-stream] ([to-stream]
(drag-stream to-stream (rx/empty))) (drag-stream to-stream (rx/empty)))
([to-stream not-drag-stream] ([to-stream not-drag-stream]
(let [start @ms/mouse-position (let [zoom (get-in @st/state [:workspace-local :zoom] 1)
zoom (get-in @st/state [:workspace-local :zoom] 1)
mouse-up (->> st/stream (rx/filter #(or (finish-edition? %) start (-> @ms/mouse-position to-pixel-snap)
(ms/mouse-up? %)))) mouse-up (->> st/stream
(rx/filter #(or (finish-edition? %)
(ms/mouse-up? %))))
position-stream position-stream
(->> ms/mouse-position (->> ms/mouse-position
(rx/take-until mouse-up) (rx/take-until mouse-up)
(rx/map to-pixel-snap)
(rx/filter (dragging? start zoom)) (rx/filter (dragging? start zoom))
(rx/take 1))] (rx/take 1))]
@ -53,10 +71,6 @@
(->> position-stream (->> position-stream
(rx/merge-map (fn [] to-stream))))))) (rx/merge-map (fn [] to-stream)))))))
(defn to-dec [num]
(let [k 50]
(* (mth/floor (/ num k)) k)))
(defn move-points-stream (defn move-points-stream
[snap-toggled start-point selected-points points] [snap-toggled start-point selected-points points]
@ -73,6 +87,7 @@
(gpt/add position snap)) (gpt/add position snap))
position))] position))]
(->> ms/mouse-position (->> ms/mouse-position
(rx/map to-pixel-snap)
(rx/map check-path-snap)))) (rx/map check-path-snap))))
(defn get-angle [node handler opposite] (defn get-angle [node handler opposite]
@ -116,6 +131,7 @@
(merge position (gpt/add position snap))))) (merge position (gpt/add position snap)))))
position))] position))]
(->> ms/mouse-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-shift (rx/map #(hash-map :shift? %))))
(rx/with-latest merge (->> ms/mouse-position-alt (rx/map #(hash-map :alt? %)))) (rx/with-latest merge (->> ms/mouse-position-alt (rx/map #(hash-map :alt? %))))
(rx/map check-path-snap)))) (rx/map check-path-snap))))
@ -136,6 +152,7 @@
(rx/map snap/create-ranges))] (rx/map snap/create-ranges))]
(->> ms/mouse-position (->> ms/mouse-position
(rx/map to-pixel-snap)
(rx/with-latest vector ranges-stream) (rx/with-latest vector ranges-stream)
(rx/map (fn [[position ranges]] (rx/map (fn [[position ranges]]
(if snap-toggled (if snap-toggled

View File

@ -33,17 +33,17 @@
;; Shortcuts format https://github.com/ccampbell/mousetrap ;; Shortcuts format https://github.com/ccampbell/mousetrap
(def base-shortcuts (def base-shortcuts
{:toggle-layers {:tooltip (ds/alt "L") {:toggle-layers {:tooltip (ds/alt "L")
:command (ds/a-mod "l") :command (ds/a-mod "l")
:fn #(st/emit! (dw/go-to-layout :layers))} :fn #(st/emit! (dw/go-to-layout :layers))}
:toggle-assets {:tooltip (ds/alt "I") :toggle-assets {:tooltip (ds/alt "I")
:command (ds/a-mod "i") :command (ds/a-mod "i")
:fn #(st/emit! (dw/go-to-layout :assets))} :fn #(st/emit! (dw/go-to-layout :assets))}
:toggle-history {:tooltip (ds/alt "H") :toggle-history {:tooltip (ds/alt "H")
:command (ds/a-mod "h") :command (ds/a-mod "h")
:fn #(st/emit! (dw/go-to-layout :document-history))} :fn #(st/emit! (dw/go-to-layout :document-history))}
:toggle-colorpalette {:tooltip (ds/alt "P") :toggle-colorpalette {:tooltip (ds/alt "P")
:command (ds/a-mod "p") :command (ds/a-mod "p")
@ -57,250 +57,250 @@
(st/emit! (dw/remove-layout-flag :colorpalette) (st/emit! (dw/remove-layout-flag :colorpalette)
(toggle-layout-flag :textpalette)))} (toggle-layout-flag :textpalette)))}
:toggle-rules {:tooltip (ds/meta-shift "R") :toggle-rules {:tooltip (ds/meta-shift "R")
:command (ds/c-mod "shift+r") :command (ds/c-mod "shift+r")
:fn #(st/emit! (toggle-layout-flag :rules))} :fn #(st/emit! (toggle-layout-flag :rules))}
:select-all {:tooltip (ds/meta "A") :select-all {:tooltip (ds/meta "A")
:command (ds/c-mod "a") :command (ds/c-mod "a")
:fn #(st/emit! (dw/select-all))} :fn #(st/emit! (dw/select-all))}
:toggle-grid {:tooltip (ds/meta "'") :toggle-grid {:tooltip (ds/meta "'")
:command (ds/c-mod "'") :command (ds/c-mod "'")
:fn #(st/emit! (toggle-layout-flag :display-grid))} :fn #(st/emit! (toggle-layout-flag :display-grid))}
:toggle-snap-grid {:tooltip (ds/meta-shift "'") :toggle-snap-grid {:tooltip (ds/meta-shift "'")
:command (ds/c-mod "shift+'") :command (ds/c-mod "shift+'")
:fn #(st/emit! (toggle-layout-flag :snap-grid))} :fn #(st/emit! (toggle-layout-flag :snap-grid))}
:toggle-snap-guide {:tooltip (ds/meta-shift "G") :toggle-snap-guide {:tooltip (ds/meta-shift "G")
:command (ds/c-mod "shift+G") :command (ds/c-mod "shift+G")
:fn #(st/emit! (toggle-layout-flag :snap-guides))} :fn #(st/emit! (toggle-layout-flag :snap-guides))}
:toggle-alignment {:tooltip (ds/meta "\\") :toggle-alignment {:tooltip (ds/meta "\\")
:command (ds/c-mod "\\") :command (ds/c-mod "\\")
:fn #(st/emit! (toggle-layout-flag :dynamic-alignment))} :fn #(st/emit! (toggle-layout-flag :dynamic-alignment))}
:toggle-scale-text {:tooltip "K" :toggle-scale-text {:tooltip "K"
:command "k" :command "k"
:fn #(st/emit! (toggle-layout-flag :scale-text))} :fn #(st/emit! (toggle-layout-flag :scale-text))}
:increase-zoom {:tooltip "+" :increase-zoom {:tooltip "+"
:command ["+" "="] :command ["+" "="]
:fn #(st/emit! (dw/increase-zoom nil))} :fn #(st/emit! (dw/increase-zoom nil))}
:decrease-zoom {:tooltip "-" :decrease-zoom {:tooltip "-"
:command ["-" "_"] :command ["-" "_"]
:fn #(st/emit! (dw/decrease-zoom nil))} :fn #(st/emit! (dw/decrease-zoom nil))}
:group {:tooltip (ds/meta "G") :group {:tooltip (ds/meta "G")
:command (ds/c-mod "g") :command (ds/c-mod "g")
:fn #(st/emit! dw/group-selected)} :fn #(st/emit! dw/group-selected)}
:ungroup {:tooltip (ds/shift "G") :ungroup {:tooltip (ds/shift "G")
:command "shift+g" :command "shift+g"
:fn #(st/emit! dw/ungroup-selected)} :fn #(st/emit! dw/ungroup-selected)}
:mask {:tooltip (ds/meta "M") :mask {:tooltip (ds/meta "M")
:command (ds/c-mod "m") :command (ds/c-mod "m")
:fn #(st/emit! dw/mask-group)} :fn #(st/emit! dw/mask-group)}
:unmask {:tooltip (ds/meta-shift "M") :unmask {:tooltip (ds/meta-shift "M")
:command (ds/c-mod "shift+m") :command (ds/c-mod "shift+m")
:fn #(st/emit! dw/unmask-group)} :fn #(st/emit! dw/unmask-group)}
:create-component {:tooltip (ds/meta "K") :create-component {:tooltip (ds/meta "K")
:command (ds/c-mod "k") :command (ds/c-mod "k")
:fn #(st/emit! (dwl/add-component))} :fn #(st/emit! (dwl/add-component))}
:detach-component {:tooltip (ds/meta-shift "K") :detach-component {:tooltip (ds/meta-shift "K")
:command (ds/c-mod "shift+k") :command (ds/c-mod "shift+k")
:fn #(st/emit! dwl/detach-selected-components)} :fn #(st/emit! dwl/detach-selected-components)}
:flip-vertical {:tooltip (ds/shift "V") :flip-vertical {:tooltip (ds/shift "V")
:command "shift+v" :command "shift+v"
:fn #(st/emit! (dw/flip-vertical-selected))} :fn #(st/emit! (dw/flip-vertical-selected))}
:flip-horizontal {:tooltip (ds/shift "H") :flip-horizontal {:tooltip (ds/shift "H")
:command "shift+h" :command "shift+h"
:fn #(st/emit! (dw/flip-horizontal-selected))} :fn #(st/emit! (dw/flip-horizontal-selected))}
:reset-zoom {:tooltip (ds/shift "0") :reset-zoom {:tooltip (ds/shift "0")
:command "shift+0" :command "shift+0"
:fn #(st/emit! dw/reset-zoom)} :fn #(st/emit! dw/reset-zoom)}
:fit-all {:tooltip (ds/shift "1") :fit-all {:tooltip (ds/shift "1")
:command "shift+1" :command "shift+1"
:fn #(st/emit! dw/zoom-to-fit-all)} :fn #(st/emit! dw/zoom-to-fit-all)}
:zoom-selected {:tooltip (ds/shift "2") :zoom-selected {:tooltip (ds/shift "2")
:command "shift+2" :command "shift+2"
:fn #(st/emit! dw/zoom-to-selected-shape)} :fn #(st/emit! dw/zoom-to-selected-shape)}
:duplicate {:tooltip (ds/meta "D") :duplicate {:tooltip (ds/meta "D")
:command (ds/c-mod "d") :command (ds/c-mod "d")
:fn #(st/emit! (dw/duplicate-selected true))} :fn #(st/emit! (dw/duplicate-selected true))}
:undo {:tooltip (ds/meta "Z") :undo {:tooltip (ds/meta "Z")
:command (ds/c-mod "z") :command (ds/c-mod "z")
:fn #(st/emit! dwc/undo)} :fn #(st/emit! dwc/undo)}
:redo {:tooltip (ds/meta "Y") :redo {:tooltip (ds/meta "Y")
:command [(ds/c-mod "shift+z") (ds/c-mod "y")] :command [(ds/c-mod "shift+z") (ds/c-mod "y")]
:fn #(st/emit! dwc/redo)} :fn #(st/emit! dwc/redo)}
:clear-undo {:tooltip (ds/meta "Q") :clear-undo {:tooltip (ds/meta "Q")
:command (ds/c-mod "q") :command (ds/c-mod "q")
:fn #(st/emit! dwu/reinitialize-undo)} :fn #(st/emit! dwu/reinitialize-undo)}
:draw-frame {:tooltip "A" :draw-frame {:tooltip "A"
:command "a" :command "a"
:fn #(st/emit! (dwd/select-for-drawing :frame))} :fn #(st/emit! (dwd/select-for-drawing :frame))}
:move {:tooltip "V" :move {:tooltip "V"
:command "v" :command "v"
:fn #(st/emit! :interrupt)} :fn #(st/emit! :interrupt)}
:draw-rect {:tooltip "R" :draw-rect {:tooltip "R"
:command "r" :command "r"
:fn #(st/emit! (dwd/select-for-drawing :rect))} :fn #(st/emit! (dwd/select-for-drawing :rect))}
:draw-ellipse {:tooltip "E" :draw-ellipse {:tooltip "E"
:command "e" :command "e"
:fn #(st/emit! (dwd/select-for-drawing :circle))} :fn #(st/emit! (dwd/select-for-drawing :circle))}
:draw-text {:tooltip "T" :draw-text {:tooltip "T"
:command "t" :command "t"
:fn #(st/emit! dwtxt/start-edit-if-selected :fn #(st/emit! dwtxt/start-edit-if-selected
(dwd/select-for-drawing :text))} (dwd/select-for-drawing :text))}
:draw-path {:tooltip "P" :draw-path {:tooltip "P"
:command "p" :command "p"
:fn #(st/emit! (dwd/select-for-drawing :path))} :fn #(st/emit! (dwd/select-for-drawing :path))}
:draw-curve {:tooltip (ds/shift "C") :draw-curve {:tooltip (ds/shift "C")
:command "shift+c" :command "shift+c"
:fn #(st/emit! (dwd/select-for-drawing :curve))} :fn #(st/emit! (dwd/select-for-drawing :curve))}
:add-comment {:tooltip "C" :add-comment {:tooltip "C"
:command "c" :command "c"
:fn #(st/emit! (dwd/select-for-drawing :comments))} :fn #(st/emit! (dwd/select-for-drawing :comments))}
:insert-image {:tooltip (ds/shift "K") :insert-image {:tooltip (ds/shift "K")
:command "shift+k" :command "shift+k"
:fn #(-> "image-upload" dom/get-element dom/click)} :fn #(-> "image-upload" dom/get-element dom/click)}
:copy {:tooltip (ds/meta "C") :copy {:tooltip (ds/meta "C")
:command (ds/c-mod "c") :command (ds/c-mod "c")
:fn #(st/emit! (dw/copy-selected))} :fn #(st/emit! (dw/copy-selected))}
:cut {:tooltip (ds/meta "X") :cut {:tooltip (ds/meta "X")
:command (ds/c-mod "x") :command (ds/c-mod "x")
:fn #(st/emit! (dw/copy-selected) :fn #(st/emit! (dw/copy-selected)
(dw/delete-selected))} (dw/delete-selected))}
:paste {:tooltip (ds/meta "V") :paste {:tooltip (ds/meta "V")
:disabled true :disabled true
:command (ds/c-mod "v") :command (ds/c-mod "v")
:fn (constantly nil)} :fn (constantly nil)}
:delete {:tooltip (ds/supr) :delete {:tooltip (ds/supr)
:command ["del" "backspace"] :command ["del" "backspace"]
:fn #(st/emit! (dw/delete-selected))} :fn #(st/emit! (dw/delete-selected))}
:bring-forward {:tooltip (ds/meta ds/up-arrow) :bring-forward {:tooltip (ds/meta ds/up-arrow)
:command (ds/c-mod "up") :command (ds/c-mod "up")
:fn #(st/emit! (dw/vertical-order-selected :up))} :fn #(st/emit! (dw/vertical-order-selected :up))}
:bring-backward {:tooltip (ds/meta ds/down-arrow) :bring-backward {:tooltip (ds/meta ds/down-arrow)
:command (ds/c-mod "down") :command (ds/c-mod "down")
:fn #(st/emit! (dw/vertical-order-selected :down))} :fn #(st/emit! (dw/vertical-order-selected :down))}
:bring-front {:tooltip (ds/meta-shift ds/up-arrow) :bring-front {:tooltip (ds/meta-shift ds/up-arrow)
:command (ds/c-mod "shift+up") :command (ds/c-mod "shift+up")
:fn #(st/emit! (dw/vertical-order-selected :top))} :fn #(st/emit! (dw/vertical-order-selected :top))}
:bring-back {:tooltip (ds/meta-shift ds/down-arrow) :bring-back {:tooltip (ds/meta-shift ds/down-arrow)
:command (ds/c-mod "shift+down") :command (ds/c-mod "shift+down")
:fn #(st/emit! (dw/vertical-order-selected :bottom))} :fn #(st/emit! (dw/vertical-order-selected :bottom))}
:move-fast-up {:tooltip (ds/shift ds/up-arrow) :move-fast-up {:tooltip (ds/shift ds/up-arrow)
:command "shift+up" :command "shift+up"
:fn #(st/emit! (dwt/move-selected :up true))} :fn #(st/emit! (dwt/move-selected :up true))}
:move-fast-down {:tooltip (ds/shift ds/down-arrow) :move-fast-down {:tooltip (ds/shift ds/down-arrow)
:command "shift+down" :command "shift+down"
:fn #(st/emit! (dwt/move-selected :down true))} :fn #(st/emit! (dwt/move-selected :down true))}
:move-fast-right {:tooltip (ds/shift ds/right-arrow) :move-fast-right {:tooltip (ds/shift ds/right-arrow)
:command "shift+right" :command "shift+right"
:fn #(st/emit! (dwt/move-selected :right true))} :fn #(st/emit! (dwt/move-selected :right true))}
:move-fast-left {:tooltip (ds/shift ds/left-arrow) :move-fast-left {:tooltip (ds/shift ds/left-arrow)
:command "shift+left" :command "shift+left"
:fn #(st/emit! (dwt/move-selected :left true))} :fn #(st/emit! (dwt/move-selected :left true))}
:move-unit-up {:tooltip ds/up-arrow :move-unit-up {:tooltip ds/up-arrow
:command "up" :command "up"
:fn #(st/emit! (dwt/move-selected :up false))} :fn #(st/emit! (dwt/move-selected :up false))}
:move-unit-down {:tooltip ds/down-arrow :move-unit-down {:tooltip ds/down-arrow
:command "down" :command "down"
:fn #(st/emit! (dwt/move-selected :down false))} :fn #(st/emit! (dwt/move-selected :down false))}
:move-unit-left {:tooltip ds/right-arrow :move-unit-left {:tooltip ds/right-arrow
:command "right" :command "right"
:fn #(st/emit! (dwt/move-selected :right false))} :fn #(st/emit! (dwt/move-selected :right false))}
:move-unit-right {:tooltip ds/left-arrow :move-unit-right {:tooltip ds/left-arrow
:command "left" :command "left"
:fn #(st/emit! (dwt/move-selected :left false))} :fn #(st/emit! (dwt/move-selected :left false))}
:open-color-picker {:tooltip "I" :open-color-picker {:tooltip "I"
:command "i" :command "i"
:fn #(st/emit! (mdc/picker-for-selected-shape))} :fn #(st/emit! (mdc/picker-for-selected-shape))}
:open-viewer {:tooltip "G V" :open-viewer {:tooltip "G V"
:command "g v" :command "g v"
:fn #(st/emit! (dw/go-to-viewer))} :fn #(st/emit! (dw/go-to-viewer))}
:open-handoff {:tooltip "G H" :open-handoff {:tooltip "G H"
:command "g h" :command "g h"
:fn #(st/emit! (dw/go-to-viewer {:section :handoff}))} :fn #(st/emit! (dw/go-to-viewer {:section :handoff}))}
:open-comments {:tooltip "G C" :open-comments {:tooltip "G C"
:command "g c" :command "g c"
:fn #(st/emit! (dw/go-to-viewer {:section :comments}))} :fn #(st/emit! (dw/go-to-viewer {:section :comments}))}
:open-dashboard {:tooltip "G D" :open-dashboard {:tooltip "G D"
:command "g d" :command "g d"
:fn #(st/emit! (dw/go-to-dashboard))} :fn #(st/emit! (dw/go-to-dashboard))}
:escape {:tooltip (ds/esc) :escape {:tooltip (ds/esc)
:command "escape" :command "escape"
:fn #(st/emit! :interrupt (dw/deselect-all true))} :fn #(st/emit! :interrupt (dw/deselect-all true))}
:start-editing {:tooltip (ds/enter) :start-editing {:tooltip (ds/enter)
:command "enter" :command "enter"
:fn #(st/emit! (dw/start-editing-selected))} :fn #(st/emit! (dw/start-editing-selected))}
:start-measure {:tooltip (ds/alt "") :start-measure {:tooltip (ds/alt "")
:command ["alt" "."] :command ["alt" "."]
:type "keydown" :type "keydown"
:fn #(st/emit! (dw/toggle-distances-display true))} :fn #(st/emit! (dw/toggle-distances-display true))}
:stop-measure {:tooltip (ds/alt "") :stop-measure {:tooltip (ds/alt "")
:command ["alt" "."] :command ["alt" "."]
:type "keyup" :type "keyup"
:fn #(st/emit! (dw/toggle-distances-display false))} :fn #(st/emit! (dw/toggle-distances-display false))}
:bool-union {:tooltip (ds/meta (ds/alt "U")) :bool-union {:tooltip (ds/meta (ds/alt "U"))
:command (ds/c-mod "alt+u") :command (ds/c-mod "alt+u")
:fn #(st/emit! (dw/create-bool :union))} :fn #(st/emit! (dw/create-bool :union))}
:bool-difference {:tooltip (ds/meta (ds/alt "D")) :bool-difference {:tooltip (ds/meta (ds/alt "D"))
:command (ds/c-mod "alt+d") :command (ds/c-mod "alt+d")
:fn #(st/emit! (dw/create-bool :difference))} :fn #(st/emit! (dw/create-bool :difference))}
:bool-intersection {:tooltip (ds/meta (ds/alt "I")) :bool-intersection {:tooltip (ds/meta (ds/alt "I"))
:command (ds/c-mod "alt+i") :command (ds/c-mod "alt+i")
@ -329,7 +329,7 @@
:align-vcenter {:tooltip (ds/alt "V") :align-vcenter {:tooltip (ds/alt "V")
:command "alt+v" :command "alt+v"
:fn #(st/emit! (dw/align-objects :vcenter))} :fn #(st/emit! (dw/align-objects :vcenter))}
:align-bottom {:tooltip (ds/alt "S") :align-bottom {:tooltip (ds/alt "S")
:command "alt+s" :command "alt+s"
:fn #(st/emit! (dw/align-objects :vbottom))} :fn #(st/emit! (dw/align-objects :vbottom))}
@ -354,9 +354,9 @@
:command (ds/c-mod "alt+l") :command (ds/c-mod "alt+l")
:fn #(st/emit! (dw/toggle-proportion-lock))} :fn #(st/emit! (dw/toggle-proportion-lock))}
:create-artboard-from-selection {:tooltip (ds/meta (ds/alt "G")) :artboard-selection {:tooltip (ds/meta (ds/alt "G"))
:command (ds/c-mod "alt+g") :command (ds/c-mod "alt+g")
:fn #(st/emit! (dw/create-artboard-from-selection))} :fn #(st/emit! (dw/create-artboard-from-selection))}
:hide-ui {:tooltip "\\" :hide-ui {:tooltip "\\"
:command "\\" :command "\\"
@ -368,8 +368,16 @@
:thumbnail-set {:tooltip (ds/shift "T") :thumbnail-set {:tooltip (ds/shift "T")
:command "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 (def opacity-shortcuts
(into {} (->> (into {} (->>

View File

@ -11,7 +11,7 @@
[app.common.geom.matrix :as gmt] [app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh] [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.changes-builder :as pcb]
[app.common.pages.helpers :as cph] [app.common.pages.helpers :as cph]
[app.common.spec :refer [max-safe-int min-safe-int]] [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}) (defonce default-image {:x 0 :y 0 :width 1 :height 1 :rx 0 :ry 0})
(defn- assert-valid-num [attr num] (defn- assert-valid-num [attr num]
(when (or (nil? num) (when (or (not (d/num? num))
(mth/nan? num)
(not (mth/finite? num))
(>= num max-safe-int ) (>= num max-safe-int )
(<= num min-safe-int)) (<= num min-safe-int))
(ex/raise (str (d/name attr) " attribute invalid: " num))) (ex/raise (str (d/name attr) " attribute invalid: " num)))
@ -168,7 +166,7 @@
(assoc :svg-attrs attrs) (assoc :svg-attrs attrs)
(assoc :svg-viewbox (-> (select-keys svg-data [:width :height]) (assoc :svg-viewbox (-> (select-keys svg-data [:width :height])
(assoc :x offset-x :y offset-y))) (assoc :x offset-x :y offset-y)))
(gsh/setup-selrect)))) (cp/setup-rect-selrect))))
(defn create-svg-root [frame-id svg-data] (defn create-svg-root [frame-id svg-data]
(let [{:keys [name x y width height offset-x offset-y]} svg-data] (let [{:keys [name x y width height offset-x offset-y]} svg-data]
@ -180,7 +178,7 @@
:height height :height height
:x (+ x offset-x) :x (+ x offset-x)
:y (+ y offset-y)} :y (+ y offset-y)}
(gsh/setup-selrect) (cp/setup-rect-selrect)
(assoc :svg-attrs (-> (:attrs svg-data) (assoc :svg-attrs (-> (:attrs svg-data)
(dissoc :viewBox :xmlns) (dissoc :viewBox :xmlns)
(d/without-keys usvg/inheritable-props)))))) (d/without-keys usvg/inheritable-props))))))
@ -200,7 +198,7 @@
(assoc :svg-attrs (d/without-keys attrs usvg/inheritable-props)) (assoc :svg-attrs (d/without-keys attrs usvg/inheritable-props))
(assoc :svg-viewbox (-> (select-keys svg-data [:width :height]) (assoc :svg-viewbox (-> (select-keys svg-data [:width :height])
(assoc :x offset-x :y offset-y))) (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}] (defn create-path-shape [name frame-id svg-data {:keys [attrs] :as data}]
(when (and (contains? attrs :d) (seq (:d attrs))) (when (and (contains? attrs :d) (seq (:d attrs)))
@ -230,14 +228,9 @@
(let [points (-> (gsh/rect->points rect-data) (let [points (-> (gsh/rect->points rect-data)
(gsh/transform-points transform)) (gsh/transform-points transform))
center (gsh/center-points points) center (gsh/center-points points)
rect-shape (gsh/center->rect center (:width rect-data) (:height rect-data))
rect-shape (-> (gsh/make-centered-rect center (:width rect-data) (:height rect-data)) selrect (gsh/rect->selrect rect-shape)
(update :width max 1)
(update :height max 1))
selrect (gsh/rect->selrect rect-shape)
rect-points (gsh/rect->points rect-shape) rect-points (gsh/rect->points rect-shape)
[shape-transform shape-transform-inv rotation] [shape-transform shape-transform-inv rotation]

View File

@ -289,14 +289,6 @@
;; --- RESIZE UTILS ;; --- 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 (def start-edit-if-selected
(ptk/reify ::start-edit-if-selected (ptk/reify ::start-edit-if-selected
ptk/UpdateEvent ptk/UpdateEvent
@ -325,22 +317,13 @@
update-fn update-fn
(fn [shape] (fn [shape]
(let [[new-width new-height] (get changes-map (:id 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 {shape-width :width shape-height :height} selrect
modifier-width (gsh/resize-modifiers shape :width new-width) modifier-width (gsh/resize-modifiers shape :width new-width)
modifier-height (gsh/resize-modifiers shape :height new-height)] modifier-height (gsh/resize-modifiers shape :height new-height)]
(cond-> shape (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)) (and (not-changed? shape-width new-width) (= grow-type :auto-width))
(-> (assoc :modifiers modifier-width) (-> (assoc :modifiers modifier-width)
(gsh/transform-shape)) (gsh/transform-shape))

View File

@ -114,23 +114,32 @@
(declare get-ignore-tree) (declare get-ignore-tree)
(defn- set-modifiers (defn- set-modifiers
([ids] (set-modifiers ids nil false)) ([ids]
([ids modifiers] (set-modifiers ids modifiers false)) (set-modifiers ids nil false))
([ids modifiers]
(set-modifiers ids modifiers false))
([ids modifiers ignore-constraints] ([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) (us/verify (s/coll-of uuid?) ids)
(ptk/reify ::set-modifiers (ptk/reify ::set-modifiers
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(let [modifiers (or modifiers (get-in state [:workspace-local :modifiers] {})) (let [modifiers (or modifiers (get-in state [:workspace-local :modifiers] {}))
page-id (:current-page-id state) page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id) objects (wsh/lookup-page-objects state page-id)
ids (into #{} (remove #(get-in objects [% :blocked] false)) ids) 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 setup-modifiers
(fn [state id] (fn [state id]
(let [shape (get objects id)] (let [shape (get objects id)]
(update state :workspace-modifiers (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)))))) (reduce setup-modifiers state ids))))))
@ -181,9 +190,12 @@
:ignore-tree ignore-tree :ignore-tree ignore-tree
;; Attributes that can change in the transform. This way we don't have to check ;; Attributes that can change in the transform. This way we don't have to check
;; all the attributes ;; all the attributes
:attrs [:selrect :points :attrs [:selrect
:x :y :points
:width :height :x
:y
:width
:height
:content :content
:transform :transform
:transform-inverse :transform-inverse
@ -232,9 +244,97 @@
[root transformed-root ignore-geometry?])) [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 (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)) (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) transformed-rect (gsh/transform-selrect (:selrect shape) modifiers)
set-child set-child
@ -242,7 +342,7 @@
(let [child-modifiers (gsh/calc-child-modifiers shape child modifiers ignore-constraints transformed-rect)] (let [child-modifiers (gsh/calc-child-modifiers shape child modifiers ignore-constraints transformed-rect)]
(cond-> modif-tree (cond-> modif-tree
(not (gsh/empty-modifiers? child-modifiers)) (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
(-> modif-tree (-> modif-tree
@ -280,7 +380,6 @@
(dissoc :workspace-modifiers) (dissoc :workspace-modifiers)
(update :workspace-local dissoc :current-move-selected))))) (update :workspace-local dissoc :current-move-selected)))))
;; -- Resize -------------------------------------------------------- ;; -- Resize --------------------------------------------------------
(defn start-resize (defn start-resize
@ -291,8 +390,8 @@
{:keys [rotation]} shape {:keys [rotation]} shape
shape-center (gsh/center-shape shape) shape-center (gsh/center-shape shape)
shape-transform (:transform shape (gmt/matrix)) shape-transform (:transform shape)
shape-transform-inverse (:transform-inverse shape (gmt/matrix)) shape-transform-inverse (:transform-inverse shape)
rotation (or rotation 0) rotation (or rotation 0)
@ -399,7 +498,8 @@
(finish-transform)))))))) (finish-transform))))))))
(defn update-dimensions (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] [ids attr value]
(us/verify (s/coll-of ::us/uuid) ids) (us/verify (s/coll-of ::us/uuid) ids)
(us/verify #{:width :height} attr) (us/verify #{:width :height} attr)
@ -408,15 +508,15 @@
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(let [page-id (:current-page-id 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] update-modifiers
(let [shape (get objects id) (fn [state id]
modifiers (gsh/resize-modifiers shape attr value)] (let [shape (get objects id)
(update state :workspace-modifiers modifiers (gsh/resize-modifiers shape attr value)]
#(set-modifiers-recursive % objects shape modifiers false)))) (update state :workspace-modifiers
state #(set-modifiers-recursive % objects shape modifiers false false))))]
ids))) (reduce update-modifiers state ids)))
ptk/WatchEvent ptk/WatchEvent
(watch [_ _ _] (watch [_ _ _]
@ -439,26 +539,29 @@
group (gsh/selection-rect shapes) group (gsh/selection-rect shapes)
group-center (gsh/center-selrect group) group-center (gsh/center-selrect group)
initial-angle (gpt/angle @ms/mouse-position group-center) initial-angle (gpt/angle @ms/mouse-position group-center)
calculate-angle (fn [pos ctrl? shift?]
(let [angle (- (gpt/angle pos group-center) initial-angle) calculate-angle
angle (if (neg? angle) (+ 360 angle) angle) (fn [pos ctrl? shift?]
angle (if (= angle 360) (let [angle (- (gpt/angle pos group-center) initial-angle)
0 angle (if (neg? angle) (+ 360 angle) angle)
angle) angle (if (= angle 360)
angle (if ctrl? 0
(* (mth/floor (/ angle 45)) 45) angle)
angle) angle (if ctrl?
angle (if shift? (* (mth/floor (/ angle 45)) 45)
(* (mth/floor (/ angle 15)) 15) angle)
angle)] angle (if shift?
angle))] (* (mth/floor (/ angle 15)) 15)
angle)]
angle))]
(rx/concat (rx/concat
(->> ms/mouse-position (->> ms/mouse-position
(rx/with-latest vector ms/mouse-position-ctrl) (rx/with-latest vector ms/mouse-position-ctrl)
(rx/with-latest vector ms/mouse-position-shift) (rx/with-latest vector ms/mouse-position-shift)
(rx/map (fn [[[pos ctrl?] shift?]] (rx/map
(let [delta-angle (calculate-angle pos ctrl? shift?)] (fn [[[pos ctrl?] shift?]]
(set-rotation-modifiers delta-angle shapes group-center)))) (let [delta-angle (calculate-angle pos ctrl? shift?)]
(set-rotation-modifiers delta-angle shapes group-center))))
(rx/take-until stoper)) (rx/take-until stoper))
(rx/of (apply-modifiers (map :id shapes)) (rx/of (apply-modifiers (map :id shapes))
(finish-transform))))))) (finish-transform)))))))
@ -495,12 +598,13 @@
(watch [_ state stream] (watch [_ state stream]
(let [initial (deref ms/mouse-position) (let [initial (deref ms/mouse-position)
selected (wsh/lookup-selected state {:omit-blocked? true}) 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) (when-not (empty? selected)
(->> ms/mouse-position (->> ms/mouse-position
(rx/map #(gpt/to-vec initial %)) (rx/map #(gpt/to-vec initial %))
(rx/map #(gpt/length %)) (rx/map #(gpt/length %))
(rx/filter #(> % 1)) (rx/filter #(> % (/ 10 zoom)))
(rx/take 1) (rx/take 1)
(rx/with-latest vector ms/mouse-position-alt) (rx/with-latest vector ms/mouse-position-alt)
(rx/mapcat (rx/mapcat
@ -574,7 +678,9 @@
(rx/of (finish-transform)) (rx/of (finish-transform))
(rx/concat (rx/concat
(->> position (->> position
;; We ask for the snap position but we continue even if the result is not available
(rx/with-latest vector snap-delta) (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 snap/correct-snap-point)
(rx/map #(hash-map :displacement (gmt/translate-matrix %))) (rx/map #(hash-map :displacement (gmt/translate-matrix %)))
(rx/map (partial set-modifiers ids)) (rx/map (partial set-modifiers ids))
@ -654,8 +760,10 @@
cpos (gpt/point (:x bbox) (:y bbox)) cpos (gpt/point (:x bbox) (:y bbox))
pos (gpt/point (or (:x position) (:x bbox)) pos (gpt/point (or (:x position) (:x bbox))
(or (:y position) (:y bbox))) (or (:y position) (:y bbox)))
displ (gmt/translate-matrix (gpt/subtract pos cpos))] delta (gpt/subtract pos cpos)
(rx/of (set-modifiers [id] {:displacement displ}) displ (gmt/translate-matrix delta)]
(rx/of (set-modifiers [id] {:displacement displ} false true)
(apply-modifiers [id])))))) (apply-modifiers [id]))))))
(defn- calculate-frame-for-move (defn- calculate-frame-for-move

View File

@ -141,6 +141,9 @@
(def workspace-layout (def workspace-layout
(l/derived :workspace-layout st/state)) (l/derived :workspace-layout st/state))
(def snap-pixel?
(l/derived #(contains? % :snap-pixel-grid) workspace-layout))
(def workspace-file (def workspace-file
"A ref to a striped vision of file (without data)." "A ref to a striped vision of file (without data)."
(l/derived (fn [state] (l/derived (fn [state]

View File

@ -59,15 +59,14 @@
(defn- calculate-dimensions (defn- calculate-dimensions
[{:keys [objects] :as data} vport] [{:keys [objects] :as data} vport]
(let [shapes (cph/get-immediate-children objects) (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) rect (cond->> (gsh/selection-rect shapes)
(some? vport) (some? vport)
(gal/adjust-to-viewport vport))] (gal/adjust-to-viewport vport))]
(-> rect (-> rect
(update :x to-finite 0) (update :x mth/finite 0)
(update :y to-finite 0) (update :y mth/finite 0)
(update :width to-finite 100000) (update :width mth/finite 100000)
(update :height to-finite 100000)))) (update :height mth/finite 100000))))
(declare shape-wrapper-factory) (declare shape-wrapper-factory)

View File

@ -22,7 +22,7 @@
(def ^:const snap-accuracy 10) (def ^:const snap-accuracy 10)
(def ^:const snap-path-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 (defn- remove-from-snap-points
[remove-snap?] [remove-snap?]
@ -55,10 +55,6 @@
(and (d/not-empty? focus) (and (d/not-empty? focus)
(not (cp/is-in-focus? objects focus id))))))) (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] (defn- calculate-distance [query-result point coord]
(->> query-result (->> query-result
(map (fn [[value _]] [(mth/abs (- value (coord point))) [(coord point) value]])))) (map (fn [[value _]] [(mth/abs (- value (coord point))) [(coord point) value]]))))
@ -82,16 +78,15 @@
;; Otherwise the root frame is the common ;; Otherwise the root frame is the common
:else zero))) :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)] (let [value (get point coord)]
(->> (uw/ask! {:cmd :snaps/range-query (->> (uw/ask! {:cmd :snaps/range-query
:page-id page-id :page-id page-id
:frame-id frame-id :frame-id frame-id
:axis coord :axis coord
:ranges [[(- value 0.5) (+ value 0.5)]]}) :ranges [[(- value (/ 0.5 zoom)) (+ value (/ 0.5 zoom))]]})
(rx/take 1) (rx/take 1)
(rx/map (remove-from-snap-points remove-snap?)) (rx/map (remove-from-snap-points remove-snap?)))))
(rx/map flatten-to-points))))
(defn- search-snap (defn- search-snap
[page-id frame-id points coord remove-snap? zoom] [page-id frame-id points coord remove-snap? zoom]
@ -195,7 +190,7 @@
snap-list (d/concat-vec lt-snap gt-snap between-snap) snap-list (d/concat-vec lt-snap gt-snap between-snap)
min-snap (reduce best-snap ##Inf snap-list)] 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] (defn search-snap-distance [selrect coord shapes-lt shapes-gt zoom]
(->> (rx/combine-latest shapes-lt shapes-gt) (->> (rx/combine-latest shapes-lt shapes-gt)
@ -238,25 +233,35 @@
(rx/map #(or % (gpt/point 0 0))) (rx/map #(or % (gpt/point 0 0)))
(rx/map #(gpt/add point %))))) (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 (defn closest-snap-move
[page-id shapes objects layout zoom focus movev] [page-id shapes objects layout zoom focus movev]
(let [frame-id (snap-frame-id shapes) (let [frame-id (snap-frame-id shapes)
filter-shapes (into #{} (map :id shapes)) filter-shapes (into #{} (map :id shapes))
remove-snap? (make-remove-snap layout filter-shapes objects focus) remove-snap? (make-remove-snap layout filter-shapes objects focus)
shape (if (> (count shapes) 1) snap-points
(->> shapes (map gsh/transform-shape) gsh/selection-rect (gsh/setup {:type :rect})) (->> shapes
(->> shapes (first))) (gsh/selection-rect)
(sp/selrect-snap-points)
;; Move the points in the translation vector
(map #(gpt/add % movev)))]
shapes-points (->> shape (->> (rx/merge (closest-snap page-id frame-id snap-points remove-snap? zoom)
(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)
(when (contains? layout :dynamic-alignment) (when (contains? layout :dynamic-alignment)
(closest-distance-snap page-id shapes objects zoom movev))) (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)))))) (rx/map #(or % (gpt/point 0 0))))))
@ -359,9 +364,14 @@
0) 0)
dy (if (not= 0 (:y snap-delta)) dy (if (not= 0 (:y snap-delta))
(- (+ (:y snap-pos) (:y snap-delta)) (:y position)) (- (+ (: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)) position))

View File

@ -7,7 +7,7 @@
(ns app.main.ui.components.editable-select (ns app.main.ui.components.editable-select
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.math :as math] [app.common.math :as mth]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.main.ui.components.dropdown :refer [dropdown]] [app.main.ui.components.dropdown :refer [dropdown]]
[app.main.ui.icons :as i] [app.main.ui.icons :as i]
@ -28,10 +28,6 @@
min-val (get params :min) min-val (get params :min)
max-val (get params :max) max-val (get params :max)
num? (fn [val] (and (number? val)
(not (math/nan? val))
(math/finite? val)))
emit-blur? (mf/use-ref nil) emit-blur? (mf/use-ref nil)
font-size-wrapper-ref (mf/use-ref) font-size-wrapper-ref (mf/use-ref)
@ -58,8 +54,7 @@
handle-change-input handle-change-input
(fn [event] (fn [event]
(let [value (-> event dom/get-target dom/get-value) (let [value (-> event dom/get-target dom/get-value)
value (-> (or (d/parse-double value) value) value (or (d/parse-double value) value)]
(math/precision 2))]
(set-value value))) (set-value value)))
on-node-load on-node-load
@ -89,8 +84,7 @@
(when (or up? down?) (when (or up? down?)
(dom/prevent-default event) (dom/prevent-default event)
(let [value (-> event dom/get-target dom/get-value) (let [value (-> event dom/get-target dom/get-value)
value (-> (or (d/parse-double value) value) value (or (d/parse-double value) value)
(math/precision 2))
increment (cond increment (cond
(kbd/shift? event) (kbd/shift? event)
@ -102,12 +96,11 @@
:else :else
(if up? 1 -1)) (if up? 1 -1))
new-value (-> (+ value increment) new-value (+ value increment)
(math/precision 2))
new-value (cond new-value (cond
(and (num? min-val) (< new-value min-val)) min-val (and (d/num? min-val) (< new-value min-val)) min-val
(and (num? max-val) (> new-value max-val)) max-val (and (d/num? max-val) (> new-value max-val)) max-val
:else new-value)] :else new-value)]
(set-value new-value))))))) (set-value new-value)))))))
@ -135,7 +128,7 @@
(let [wrapper-node (mf/ref-val font-size-wrapper-ref) (let [wrapper-node (mf/ref-val font-size-wrapper-ref)
node (dom/get-element-by-class "checked-element is-selected" wrapper-node) node (dom/get-element-by-class "checked-element is-selected" wrapper-node)
nodes (dom/get-elements-by-class "checked-element-value" 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))] closest-value (str (closest options value))]
(when (:is-open? @state) (when (:is-open? @state)
(if (some? node) (if (some? node)

View File

@ -7,23 +7,17 @@
(ns app.main.ui.components.numeric-input (ns app.main.ui.components.numeric-input
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.math :as math]
[app.common.spec :as us] [app.common.spec :as us]
[app.main.ui.formats :as fmt]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.globals :as globals] [app.util.globals :as globals]
[app.util.keyboard :as kbd] [app.util.keyboard :as kbd]
[app.util.object :as obj] [app.util.object :as obj]
[app.util.simple-math :as sm] [app.util.simple-math :as sm]
[app.util.strings :as ust]
[goog.events :as events] [goog.events :as events]
[rumext.alpha :as mf]) [rumext.alpha :as mf])
(:import goog.events.EventType)) (:import goog.events.EventType))
(defn num? [val]
(and (number? val)
(not (math/nan? val))
(math/finite? val)))
(mf/defc numeric-input (mf/defc numeric-input
{::mf/wrap-props false {::mf/wrap-props false
::mf/forward-ref true} ::mf/forward-ref true}
@ -36,8 +30,8 @@
on-change (obj/get props "onChange") on-change (obj/get props "onChange")
on-blur (obj/get props "onBlur") on-blur (obj/get props "onBlur")
title (obj/get props "title") title (obj/get props "title")
default-val (obj/get props "default" 0) default-val (obj/get props "default")
precision (obj/get props "precision") nillable (obj/get props "nillable")
;; We need a ref pointing to the input dom element, but the user ;; We need a ref pointing to the input dom element, but the user
;; of this component may provide one (that is forwarded here). ;; of this component may provide one (that is forwarded here).
@ -78,34 +72,34 @@
parse-value parse-value
(mf/use-callback (mf/use-callback
(mf/deps ref min-val max-val value) (mf/deps ref min-val max-val value nillable default-val)
(fn [] (fn []
(let [input-node (mf/ref-val ref) (let [input-node (mf/ref-val ref)
new-value (-> (dom/get-value input-node) new-value (-> (dom/get-value input-node)
(sm/expr-eval value))] (sm/expr-eval value))]
(when (num? new-value) (cond
(d/num? new-value)
(-> new-value (-> new-value
(cond-> (number? precision)
(math/precision precision))
(cond-> (nil? precision)
(math/round))
(cljs.core/max us/min-safe-int) (cljs.core/max us/min-safe-int)
(cljs.core/min us/max-safe-int) (cljs.core/min us/max-safe-int)
(cond-> (cond->
(num? min-val) (d/num? min-val)
(cljs.core/max min-val) (cljs.core/max min-val)
(num? max-val) (d/num? max-val)
(cljs.core/min max-val))))))) (cljs.core/min max-val)))
nillable
default-val
:else value))))
update-input update-input
(mf/use-callback (mf/use-callback
(mf/deps ref) (mf/deps ref)
(fn [new-value] (fn [new-value]
(let [input-node (mf/ref-val ref)] (let [input-node (mf/ref-val ref)]
(dom/set-value! input-node (if (some? precision) (dom/set-value! input-node (fmt/format-number new-value)))))
(ust/format-precision new-value precision)
(str new-value))))))
apply-value apply-value
(mf/use-callback (mf/use-callback
@ -122,24 +116,30 @@
(fn [event up? down?] (fn [event up? down?]
(let [current-value (parse-value)] (let [current-value (parse-value)]
(when current-value (when current-value
(let [increment (if (kbd/shift? event) (let [increment (cond
(kbd/shift? event)
(if up? (* step-val 10) (* step-val -10)) (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))) (if up? step-val (- step-val)))
new-value (+ current-value increment) new-value (+ current-value increment)
new-value (cond 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) up?)
(-> new-value (- max-val) (+ min-val) (- step-val)) (-> 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) down?)
(-> new-value (- min-val) (+ max-val) (+ step-val)) (-> 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 min-val
(and (num? max-val) (> new-value max-val)) (and (d/num? max-val) (> new-value max-val))
max-val max-val
:else new-value)] :else new-value)]
@ -167,14 +167,19 @@
(mf/use-callback (mf/use-callback
(mf/deps set-delta) (mf/deps set-delta)
(fn [event] (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 handle-blur
(mf/use-callback (mf/use-callback
(mf/deps parse-value apply-value update-input on-blur) (mf/deps parse-value apply-value update-input on-blur)
(fn [_] (fn [_]
(let [new-value (or (parse-value) default-val)] (let [new-value (or (parse-value) default-val)]
(if new-value (if (or nillable new-value)
(apply-value new-value) (apply-value new-value)
(update-input new-value))) (update-input new-value)))
(when on-blur (on-blur)))) (when on-blur (on-blur))))
@ -189,21 +194,20 @@
(dom/blur! current))))))) (dom/blur! current)))))))
props (-> props props (-> props
(obj/without ["value" "onChange"]) (obj/without ["value" "onChange" "nillable"])
(obj/set! "className" "input-text") (obj/set! "className" "input-text")
(obj/set! "type" "text") (obj/set! "type" "text")
(obj/set! "ref" ref) (obj/set! "ref" ref)
(obj/set! "defaultValue" value-str) (obj/set! "defaultValue" (fmt/format-number value))
(obj/set! "title" title) (obj/set! "title" title)
(obj/set! "onWheel" handle-mouse-wheel)
(obj/set! "onKeyDown" handle-key-down) (obj/set! "onKeyDown" handle-key-down)
(obj/set! "onBlur" handle-blur))] (obj/set! "onBlur" handle-blur))]
(mf/use-effect (mf/use-effect
(mf/deps value-str) (mf/deps value)
(fn [] (fn []
(when-let [input-node (mf/ref-val ref)] (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/use-effect
(mf/deps handle-blur) (mf/deps handle-blur)
@ -216,6 +220,13 @@
(let [handle-blur (:fn (mf/ref-val handle-blur-ref))] (let [handle-blur (:fn (mf/ref-val handle-blur-ref))]
(handle-blur))))) (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 (mf/use-layout-effect
(fn [] (fn []
(let [keys [(events/listen globals/window EventType.POINTERDOWN on-click) (let [keys [(events/listen globals/window EventType.POINTERDOWN on-click)
@ -225,4 +236,3 @@
(events/unlistenByKey key))))) (events/unlistenByKey key)))))
[:> :input props])) [:> :input props]))

View File

@ -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))))

View File

@ -12,6 +12,7 @@
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.math :as mth] [app.common.math :as mth]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.main.ui.formats :as fmt]
[rumext.alpha :as mf])) [rumext.alpha :as mf]))
;; ------------------------------------------------ ;; ------------------------------------------------
@ -97,7 +98,7 @@
(mf/defc size-display [{:keys [selrect zoom]}] (mf/defc size-display [{:keys [selrect zoom]}]
(let [{:keys [x y width height]} selrect (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-height (/ size-display-height zoom)
rect-width (/ (if (<= (count size-label) 9) rect-width (/ (if (<= (count size-label) 9)
@ -164,7 +165,7 @@
:height distance-pill-height :height distance-pill-height
:style {:fill distance-text-color :style {:fill distance-text-color
:font-size font-size}} :font-size font-size}}
distance]])) (fmt/format-pixels distance)]]))
(mf/defc selection-rect [{:keys [selrect zoom]}] (mf/defc selection-rect [{:keys [selrect zoom]}]
(let [{:keys [x y width height]} selrect (let [{:keys [x y width height]} selrect
@ -214,7 +215,7 @@
{:x center-x {:x center-x
:y center-y :y center-y
:zoom zoom :zoom zoom
:distance (dm/str (mth/round distance) "px") :distance distance
:bounds bounds}]]))))) :bounds bounds}]])))))
(mf/defc selection-guides [{:keys [bounds selrect zoom]}] (mf/defc selection-guides [{:keys [bounds selrect zoom]}]

View File

@ -71,8 +71,8 @@
font-variant-id (:font-variant-id data) font-variant-id (:font-variant-id data)
font-size (:font-size data) font-size (:font-size data)
fill-color (:fill-color data) fill-color (or (-> data :fills first :fill-color) (:fill-color data))
fill-opacity (:fill-opacity data) fill-opacity (or (-> data :fills first :fill-opacity) (:fill-opacity data))
[r g b a] (uc/hex->rgba fill-color fill-opacity) [r g b a] (uc/hex->rgba fill-color fill-opacity)
text-color (when (and (some? fill-color) (some? fill-opacity)) text-color (when (and (some? fill-color) (some? fill-opacity))

View File

@ -8,6 +8,7 @@
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.math :as mth]
[app.main.ui.context :as muc] [app.main.ui.context :as muc]
[app.main.ui.shapes.attrs :as attrs] [app.main.ui.shapes.attrs :as attrs]
[app.main.ui.shapes.custom-stroke :refer [shape-custom-strokes]] [app.main.ui.shapes.custom-stroke :refer [shape-custom-strokes]]
@ -24,6 +25,7 @@
(let [render-id (mf/use-ctx muc/render-ctx) (let [render-id (mf/use-ctx muc/render-ctx)
{:keys [x y width height position-data] :as shape} (obj/get props "shape") {:keys [x y width height position-data] :as shape} (obj/get props "shape")
transform (str (gsh/transform-matrix shape)) transform (str (gsh/transform-matrix shape))
;; These position attributes are not really necesary but they are convenient for for the export ;; These position attributes are not really necesary but they are convenient for for the export
@ -48,8 +50,8 @@
[:> :g group-props [:> :g group-props
(for [[index data] (d/enumerate position-data)] (for [[index data] (d/enumerate position-data)]
(let [props (-> #js {:x (:x data) (let [props (-> #js {:x (mth/round (:x data))
:y (:y data) :y (mth/round (:y data))
:dominantBaseline "ideographic" :dominantBaseline "ideographic"
:style (-> #js {:fontFamily (:font-family data) :style (-> #js {:fontFamily (:font-family data)
:fontSize (:font-size data) :fontSize (:font-size data)
@ -61,7 +63,6 @@
:whiteSpace "pre"} :whiteSpace "pre"}
(obj/set! "fill" (str "url(#fill-" index "-" render-id ")")))}) (obj/set! "fill" (str "url(#fill-" index "-" render-id ")")))})
shape (assoc shape :fills (:fills data))] shape (assoc shape :fills (:fills data))]
[:& shape-custom-strokes {:shape shape} [:& shape-custom-strokes {:shape shape}
[:> :text props (:text data)]]))]])) [:> :text props (:text data)]]))]]))

View File

@ -6,7 +6,6 @@
(ns app.main.ui.viewer.handoff.attributes.common (ns app.main.ui.viewer.handoff.attributes.common
(:require (:require
[app.common.math :as mth]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.components.color-bullet :refer [color-bullet color-name]] [app.main.ui.components.color-bullet :refer [color-bullet color-name]]
[app.main.ui.components.copy-button :refer [copy-button]] [app.main.ui.components.copy-button :refer [copy-button]]
@ -50,9 +49,9 @@
(if (:gradient color) (if (:gradient color)
[:& color-name {:color color}] [:& color-name {:color color}]
(case format (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)]) [: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)]) [:div (str/fmt "%s, %s, %s, %s" h s l a)])
[:* [:*
[:& color-name {:color color}] [:& color-name {:color color}]

View File

@ -6,7 +6,6 @@
(ns app.main.ui.viewer.handoff.attributes.layout (ns app.main.ui.viewer.handoff.attributes.layout
(:require (:require
[app.common.math :as mth]
[app.common.spec.radius :as ctr] [app.common.spec.radius :as ctr]
[app.main.ui.components.copy-button :refer [copy-button]] [app.main.ui.components.copy-button :refer [copy-button]]
[app.util.code-gen :as cg] [app.util.code-gen :as cg]
@ -39,46 +38,46 @@
[:* [:*
[:div.attributes-unit-row [:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.layout.width")] [: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)}]] [:& copy-button {:data (copy-data selrect :width)}]]
[:div.attributes-unit-row [:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.layout.height")] [: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)}]] [:& copy-button {:data (copy-data selrect :height)}]]
(when (not= (:x shape) 0) (when (not= (:x shape) 0)
[:div.attributes-unit-row [:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.layout.left")] [: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)}]]) [:& copy-button {:data (copy-data selrect :x)}]])
(when (not= (:y shape) 0) (when (not= (:y shape) 0)
[:div.attributes-unit-row [:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.layout.top")] [: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)}]]) [:& copy-button {:data (copy-data selrect :y)}]])
(when (ctr/radius-1? shape) (when (ctr/radius-1? shape)
[:div.attributes-unit-row [:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.layout.radius")] [: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)}]]) [:& copy-button {:data (copy-data shape :rx)}]])
(when (ctr/radius-4? shape) (when (ctr/radius-4? shape)
[:div.attributes-unit-row [:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.layout.radius")] [:div.attributes-label (t locale "handoff.attributes.layout.radius")]
[:div.attributes-value [:div.attributes-value
(mth/precision (:r1 shape) 2) ", " (:r1 shape) ", "
(mth/precision (:r2 shape) 2) ", " (:r2 shape) ", "
(mth/precision (:r3 shape) 2) ", " (:r3 shape) ", "
(mth/precision (:r4 shape) 2) "px"] (:r4 shape) "px"]
[:& copy-button {:data (copy-data shape :r1)}]]) [:& copy-button {:data (copy-data shape :r1)}]])
(when (not= (:rotation shape 0) 0) (when (not= (:rotation shape 0) 0)
[:div.attributes-unit-row [:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.layout.rotation")] [: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)}]])])) [:& copy-button {:data (copy-data shape :rotation)}]])]))

View File

@ -7,7 +7,6 @@
(ns app.main.ui.viewer.handoff.attributes.stroke (ns app.main.ui.viewer.handoff.attributes.stroke
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.math :as mth]
[app.main.ui.components.copy-button :refer [copy-button]] [app.main.ui.components.copy-button :refer [copy-button]]
[app.main.ui.viewer.handoff.attributes.common :refer [color-row]] [app.main.ui.viewer.handoff.attributes.common :refer [color-row]]
[app.util.code-gen :as cg] [app.util.code-gen :as cg]
@ -67,7 +66,7 @@
stroke-alignment (or stroke-alignment :center)] stroke-alignment (or stroke-alignment :center)]
[:div.attributes-stroke-row [:div.attributes-stroke-row
[:div.attributes-label (t locale "handoff.attributes.stroke.width")] [: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-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))] [:div.attributes-label (->> stroke-alignment d/name (str "handoff.attributes.stroke.alignment.") (t locale))]
[:& copy-button {:data (copy-stroke-data shape)}]])])) [:& copy-button {:data (copy-stroke-data shape)}]])]))

View File

@ -6,16 +6,12 @@
(ns app.main.ui.viewer.handoff.attributes.svg (ns app.main.ui.viewer.handoff.attributes.svg
(:require (:require
#_[app.common.math :as mth]
#_[app.main.ui.icons :as i]
#_[app.util.code-gen :as cg]
[app.common.data :as d] [app.common.data :as d]
[app.main.ui.components.copy-button :refer [copy-button]] [app.main.ui.components.copy-button :refer [copy-button]]
[app.util.i18n :refer [tr]] [app.util.i18n :refer [tr]]
[cuerdas.core :as str] [cuerdas.core :as str]
[rumext.alpha :as mf])) [rumext.alpha :as mf]))
(defn map->css [attr] (defn map->css [attr]
(->> attr (->> attr
(map (fn [[attr-key attr-value]] (str (d/name attr-key) ":" attr-value))) (map (fn [[attr-key attr-value]] (str (d/name attr-key) ":" attr-value)))

View File

@ -6,13 +6,13 @@
(ns app.main.ui.viewer.header (ns app.main.ui.viewer.header
(:require (:require
[app.common.math :as mth]
[app.main.data.modal :as modal] [app.main.data.modal :as modal]
[app.main.data.viewer :as dv] [app.main.data.viewer :as dv]
[app.main.data.viewer.shortcuts :as sc] [app.main.data.viewer.shortcuts :as sc]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.components.dropdown :refer [dropdown]] [app.main.ui.components.dropdown :refer [dropdown]]
[app.main.ui.formats :as fmt]
[app.main.ui.icons :as i] [app.main.ui.icons :as i]
[app.main.ui.viewer.comments :refer [comments-menu]] [app.main.ui.viewer.comments :refer [comments-menu]]
[app.main.ui.viewer.interactions :refer [flows-menu interactions-menu]] [app.main.ui.viewer.interactions :refer [flows-menu interactions-menu]]
@ -32,7 +32,7 @@
:as props}] :as props}]
(let [show-dropdown? (mf/use-state false)] (let [show-dropdown? (mf/use-state false)]
[:div.zoom-widget {:on-click #(reset! show-dropdown? true)} [: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] [:span.icon i/arrow-down]
[:& dropdown {:show @show-dropdown? [:& dropdown {:show @show-dropdown?
:on-close #(reset! show-dropdown? false)} :on-close #(reset! show-dropdown? false)}
@ -43,7 +43,7 @@
(dom/stop-propagation event) (dom/stop-propagation event)
(dom/prevent-default event) (dom/prevent-default event)
(on-decrease))} "-"] (on-decrease))} "-"]
[:p.zoom-size {} (str (mth/round (* 100 zoom)) "%")] [:p.zoom-size (fmt/format-percent zoom)]
[:button {:on-click (fn [event] [:button {:on-click (fn [event]
(dom/stop-propagation event) (dom/stop-propagation event)
(dom/prevent-default event) (dom/prevent-default event)

View File

@ -6,7 +6,6 @@
(ns app.main.ui.workspace.colorpalette (ns app.main.ui.workspace.colorpalette
(:require (:require
[app.common.math :as mth]
[app.main.data.workspace.colors :as mdc] [app.main.data.workspace.colors :as mdc]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
@ -57,7 +56,7 @@
(let [state (mf/use-state {:show-menu false}) (let [state (mf/use-state {:show-menu false})
width (:width @state 0) width (:width @state 0)
visible (mth/round (/ width 66)) visible (/ width 66)
offset (:offset @state 0) offset (:offset @state 0)
max-offset (- (count current-colors) max-offset (- (count current-colors)
@ -75,7 +74,7 @@
(swap! state update :offset (swap! state update :offset
(fn [offset] (fn [offset]
(if (pos? offset) (if (pos? offset)
(max (- offset (mth/round (/ visible 2))) 0) (max (- offset (/ visible 2)) 0)
offset))))) offset)))))
on-right-arrow-click on-right-arrow-click
@ -85,7 +84,7 @@
(swap! state update :offset (swap! state update :offset
(fn [offset] (fn [offset]
(if (< offset max-offset) (if (< offset max-offset)
(min max-offset (+ offset (mth/round (/ visible 2)))) (min max-offset (+ offset (/ visible 2)))
offset))))) offset)))))
on-scroll on-scroll

View File

@ -6,7 +6,7 @@
(ns app.main.ui.workspace.colorpicker.color-inputs (ns app.main.ui.workspace.colorpicker.color-inputs
(:require (:require
[app.common.math :as math] [app.common.math :as mth]
[app.util.color :as uc] [app.util.color :as uc]
[app.util.dom :as dom] [app.util.dom :as dom]
[rumext.alpha :as mf])) [rumext.alpha :as mf]))
@ -52,7 +52,7 @@
on-change-property on-change-property
(fn [property max-value] (fn [property max-value]
(fn [e] (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)] val (if (#{:s} property) (/ val 100) val)]
(when (not (nil? val)) (when (not (nil? val))
(if (#{:r :g :b} property) (if (#{:r :g :b} property)
@ -72,7 +72,7 @@
on-change-opacity on-change-opacity
(fn [e] (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})))] (on-change {:alpha new-alpha})))]
@ -86,9 +86,9 @@
(when (and property-val property-ref) (when (and property-val property-ref)
(when-let [node (mf/ref-val property-ref)] (when-let [node (mf/ref-val property-ref)]
(case ref-key (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) :hex (dom/set-value! node property-val)
(dom/set-value! node (math/round property-val))))))))) (dom/set-value! node property-val))))))))
[:div.color-values [:div.color-values
{:class (when disable-opacity "disable-opacity")} {:class (when disable-opacity "disable-opacity")}
@ -156,7 +156,7 @@
:min 0 :min 0
:step 1 :step 1
:max 100 :max 100
:default-value (if (= alpha :multiple) "" (math/precision alpha 2)) :default-value (if (= alpha :multiple) "" alpha)
:on-change on-change-opacity}]) :on-change on-change-opacity}])
[:label.hex-label {:for "hex-value"} "HEX"] [:label.hex-label {:for "hex-value"} "HEX"]

View File

@ -7,7 +7,7 @@
(ns app.main.ui.workspace.colorpicker.harmony (ns app.main.ui.workspace.colorpicker.harmony
(:require (:require
[app.common.geom.point :as gpt] [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.main.ui.workspace.colorpicker.slider-selector :refer [slider-selector]]
[app.util.color :as uc] [app.util.color :as uc]
[app.util.dom :as dom] [app.util.dom :as dom]
@ -28,9 +28,9 @@
(.clearRect ctx 0 0 width height) (.clearRect ctx 0 0 width height)
(doseq [degrees (range 0 360 step)] (doseq [degrees (range 0 360 step)]
(let [degrees-rad (math/radians degrees) (let [degrees-rad (mth/radians degrees)
x (* radius (math/cos (- degrees-rad))) x (* radius (mth/cos (- degrees-rad)))
y (* radius (math/sin (- degrees-rad)))] y (* radius (mth/sin (- degrees-rad)))]
(obj/set! ctx "strokeStyle" (str/format "hsl(%s, 100%, 50%)" degrees)) (obj/set! ctx "strokeStyle" (str/format "hsl(%s, 100%, 50%)" degrees))
(.beginPath ctx) (.beginPath ctx)
(.moveTo ctx cx cy) (.moveTo ctx cx cy)
@ -43,15 +43,15 @@
(obj/set! ctx "fillStyle" grd) (obj/set! ctx "fillStyle" grd)
(.beginPath ctx) (.beginPath ctx)
(.arc ctx cx cy radius 0 (* 2 math/PI) true) (.arc ctx cx cy radius 0 (* 2 mth/PI) true)
(.closePath ctx) (.closePath ctx)
(.fill ctx)))) (.fill ctx))))
(defn color->point (defn color->point
[canvas-side hue saturation] [canvas-side hue saturation]
(let [hue-rad (math/radians (- hue)) (let [hue-rad (mth/radians (- hue))
comp-x (* saturation (math/cos hue-rad)) comp-x (* saturation (mth/cos hue-rad))
comp-y (* saturation (math/sin hue-rad)) comp-y (* saturation (mth/sin hue-rad))
x (+ (/ canvas-side 2) (* comp-x (/ canvas-side 2))) x (+ (/ canvas-side 2) (* comp-x (/ canvas-side 2)))
y (+ (/ canvas-side 2) (* comp-y (/ canvas-side 2)))] y (+ (/ canvas-side 2) (* comp-y (/ canvas-side 2)))]
(gpt/point x y))) (gpt/point x y)))
@ -68,15 +68,15 @@
calculate-pos (fn [ev] calculate-pos (fn [ev]
(let [{:keys [left right top bottom]} (-> ev dom/get-target dom/get-bounding-rect) (let [{:keys [left right top bottom]} (-> ev dom/get-target dom/get-bounding-rect)
{:keys [x y]} (-> ev dom/get-client-position) {:keys [x y]} (-> ev dom/get-client-position)
px (math/clamp (/ (- x left) (- right left)) 0 1) px (mth/clamp (/ (- x left) (- right left)) 0 1)
py (math/clamp (/ (- y top) (- bottom top)) 0 1) py (mth/clamp (/ (- y top) (- bottom top)) 0 1)
px (- (* 2 px) 1) px (- (* 2 px) 1)
py (- (* 2 py) 1) py (- (* 2 py) 1)
angle (math/degrees (math/atan2 px py)) angle (mth/degrees (mth/atan2 px py))
new-hue (math/precision (mod (- angle 90 ) 360) 2) new-hue (mod (- angle 90 ) 360)
new-saturation (math/clamp (math/distance [px py] [0 0]) 0 1) new-saturation (mth/clamp (mth/distance [px py] [0 0]) 0 1)
hex (uc/hsv->hex [new-hue new-saturation value]) hex (uc/hsv->hex [new-hue new-saturation value])
[r g b] (uc/hex->rgb hex)] [r g b] (uc/hex->rgb hex)]
(on-change {:hex hex (on-change {:hex hex

View File

@ -6,7 +6,7 @@
(ns app.main.ui.workspace.colorpicker.ramp (ns app.main.ui.workspace.colorpicker.ramp
(:require (:require
[app.common.math :as math] [app.common.math :as mth]
[app.main.ui.components.color-bullet :refer [color-bullet]] [app.main.ui.components.color-bullet :refer [color-bullet]]
[app.main.ui.workspace.colorpicker.slider-selector :refer [slider-selector]] [app.main.ui.workspace.colorpicker.slider-selector :refer [slider-selector]]
[app.util.color :as uc] [app.util.color :as uc]
@ -19,8 +19,8 @@
(fn [ev] (fn [ev]
(let [{:keys [left right top bottom]} (-> ev dom/get-target dom/get-bounding-rect) (let [{:keys [left right top bottom]} (-> ev dom/get-target dom/get-bounding-rect)
{:keys [x y]} (-> ev dom/get-client-position) {:keys [x y]} (-> ev dom/get-client-position)
px (math/clamp (/ (- x left) (- right left)) 0 1) px (mth/clamp (/ (- x left) (- right left)) 0 1)
py (* 255 (- 1 (math/clamp (/ (- y top) (- bottom top)) 0 1)))] py (* 255 (- 1 (mth/clamp (/ (- y top) (- bottom top)) 0 1)))]
(on-change px py))) (on-change px py)))
handle-start-drag handle-start-drag

View File

@ -6,7 +6,7 @@
(ns app.main.ui.workspace.colorpicker.slider-selector (ns app.main.ui.workspace.colorpicker.slider-selector
(:require (:require
[app.common.math :as math] [app.common.math :as mth]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.object :as obj] [app.util.object :as obj]
[rumext.alpha :as mf])) [rumext.alpha :as mf]))
@ -41,13 +41,13 @@
(let [{:keys [left right top bottom]} (-> ev dom/get-target dom/get-bounding-rect) (let [{:keys [left right top bottom]} (-> ev dom/get-target dom/get-bounding-rect)
{:keys [x y]} (-> ev dom/get-client-position) {:keys [x y]} (-> ev dom/get-client-position)
unit-value (if vertical? unit-value (if vertical?
(math/clamp (/ (- bottom y) (- bottom top)) 0 1) (mth/clamp (/ (- bottom y) (- bottom top)) 0 1)
(math/clamp (/ (- x left) (- right left)) 0 1)) (mth/clamp (/ (- x left) (- right left)) 0 1))
unit-value (if reverse? unit-value (if reverse?
(math/abs (- unit-value 1.0)) (mth/abs (- unit-value 1.0))
unit-value) unit-value)
value (+ min-value (* unit-value (- max-value min-value)))] value (+ min-value (* unit-value (- max-value min-value)))]
(on-change (math/precision value 2)))))] (on-change value))))]
[:div.slider-selector [:div.slider-selector
{:class (str (if vertical? "vertical " "") class) {:class (str (if vertical? "vertical " "") class)
@ -60,7 +60,7 @@
(- max-value min-value)) 100) (- max-value min-value)) 100)
value-percent (if reverse? value-percent (if reverse?
(math/abs (- value-percent 100)) (mth/abs (- value-percent 100))
value-percent) value-percent)
value-percent-str (str value-percent "%") value-percent-str (str value-percent "%")

View File

@ -225,7 +225,7 @@
(when (not has-frame?) (when (not has-frame?)
[:* [:*
[:& menu-entry {:title (tr "workspace.shape.menu.create-artboard-from-selection") [:& 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}] :on-click do-create-artboard-from-selection}]
[:& menu-separator]])])) [:& menu-separator]])]))

View File

@ -7,7 +7,6 @@
(ns app.main.ui.workspace.header (ns app.main.ui.workspace.header
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.math :as mth]
[app.config :as cf] [app.config :as cf]
[app.main.data.events :as ev] [app.main.data.events :as ev]
[app.main.data.messages :as dm] [app.main.data.messages :as dm]
@ -18,6 +17,7 @@
[app.main.repo :as rp] [app.main.repo :as rp]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.components.dropdown :refer [dropdown]] [app.main.ui.components.dropdown :refer [dropdown]]
[app.main.ui.formats :as fmt]
[app.main.ui.hooks.resize :as r] [app.main.ui.hooks.resize :as r]
[app.main.ui.icons :as i] [app.main.ui.icons :as i]
[app.main.ui.workspace.presence :refer [active-sessions]] [app.main.ui.workspace.presence :refer [active-sessions]]
@ -71,7 +71,7 @@
:as props}] :as props}]
(let [show-dropdown? (mf/use-state false)] (let [show-dropdown? (mf/use-state false)]
[:div.zoom-widget {:on-click #(reset! show-dropdown? true)} [: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] [:span.icon i/arrow-down]
[:& dropdown {:show @show-dropdown? [:& dropdown {:show @show-dropdown?
:on-close #(reset! show-dropdown? false)} :on-close #(reset! show-dropdown? false)}
@ -82,7 +82,7 @@
(dom/stop-propagation event) (dom/stop-propagation event)
(dom/prevent-default event) (dom/prevent-default event)
(on-decrease))} "-"] (on-decrease))} "-"]
[:p.zoom-size {} (str (mth/round (* 100 zoom)) "%")] [:p.zoom-size {} (fmt/format-percent zoom {:precision 0})]
[:button {:on-click (fn [event] [:button {:on-click (fn [event]
(dom/stop-propagation event) (dom/stop-propagation event)
(dom/prevent-default event) (dom/prevent-default event)
@ -346,6 +346,13 @@
(tr "workspace.header.menu.hide-artboard-names") (tr "workspace.header.menu.hide-artboard-names")
(tr "workspace.header.menu.show-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) [:li {:on-click #(st/emit! (-> (toggle-flag :hide-ui)
(vary-meta assoc ::ev/origin "workspace-menu")))} (vary-meta assoc ::ev/origin "workspace-menu")))}
[:span [:span
@ -376,6 +383,13 @@
(tr "workspace.header.menu.enable-dynamic-alignment"))] (tr "workspace.header.menu.enable-dynamic-alignment"))]
[:span.shortcut (sc/get-tooltip :toggle-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}))} [:li {:on-click #(st/emit! (modal/show {:type :nudge-option}))}
[:span (tr "modals.nudge-title")]]]]])) [:span (tr "modals.nudge-title")]]]]]))

View File

@ -7,7 +7,6 @@
(ns app.main.ui.workspace.left-toolbar (ns app.main.ui.workspace.left-toolbar
(:require (:require
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.math :as mth]
[app.common.media :as cm] [app.common.media :as cm]
[app.main.data.events :as ev] [app.main.data.events :as ev]
[app.main.data.workspace :as dw] [app.main.data.workspace :as dw]
@ -40,8 +39,8 @@
;; We don't want to add a ref because that redraws the component ;; We don't want to add a ref because that redraws the component
;; for everychange. Better direct access on the callback. ;; for everychange. Better direct access on the callback.
(let [vbox (deref refs/vbox) (let [vbox (deref refs/vbox)
x (mth/round (+ (:x vbox) (/ (:width vbox) 2))) x (+ (:x vbox) (/ (:width vbox) 2))
y (mth/round (+ (:y vbox) (/ (:height vbox) 2))) y (+ (:y vbox) (/ (:height vbox) 2))
params {:file-id (:id file) params {:file-id (:id file)
:blobs (seq blobs) :blobs (seq blobs)
:position (gpt/point x y)}] :position (gpt/point x y)}]

View File

@ -49,12 +49,12 @@
[:div.input-wrapper [:div.input-wrapper
[:span [:span
[:p.nudge-subtitle (tr "modals.small-nudge")] [:p.nudge-subtitle (tr "modals.small-nudge")]
[:> numeric-input {:min 1 [:> numeric-input {:min 0.01
:value (:small nudge) :value (:small nudge)
:on-change update-small}]]] :on-change update-small}]]]
[:div.input-wrapper [:div.input-wrapper
[:span [:span
[:p.nudge-subtitle (tr "modals.big-nudge")] [:p.nudge-subtitle (tr "modals.big-nudge")]
[:> numeric-input {:min 1 [:> numeric-input {:min 0.01
:value (:big nudge) :value (:big nudge)
:on-change update-big}]]]]]])) :on-change update-big}]]]]]]))

View File

@ -89,6 +89,7 @@
:style {:cursor (cond :style {:cursor (cond
(= edit-mode :draw) cur/pen-node (= edit-mode :draw) cur/pen-node
(= edit-mode :move) cur/pointer-node) (= edit-mode :move) cur/pointer-node)
:stroke-width 0
:fill "none"}}]])) :fill "none"}}]]))
(mf/defc path-handler [{:keys [index prefix point handler zoom selected? hover? edit-mode snap-angle?]}] (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-enter on-enter
:on-mouse-leave on-leave :on-mouse-leave on-leave
:style {:cursor (when (= edit-mode :move) cur/pointer-move) :style {:cursor (when (= edit-mode :move) cur/pointer-move)
:fill "none"}}]]))) :fill "none"
:stroke-width 0}}]])))
(mf/defc path-preview [{:keys [zoom command from]}] (mf/defc path-preview [{:keys [zoom command from]}]
[:g.preview {:style {:pointer-events "none"}} [:g.preview {:style {:pointer-events "none"}}

View File

@ -6,7 +6,6 @@
(ns app.main.ui.workspace.sidebar.options.menus.frame-grid (ns app.main.ui.workspace.sidebar.options.menus.frame-grid
(:require (:require
[app.common.math :as mth]
[app.main.data.workspace.grid :as dw] [app.main.data.workspace.grid :as dw]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
@ -71,8 +70,7 @@
(let [{:keys [margin gutter item-length]} (:params grid) (let [{:keys [margin gutter item-length]} (:params grid)
frame-length (if (= :column (:type grid)) frame-width frame-height) frame-length (if (= :column (:type grid)) frame-width frame-height)
item-length (if (nil? size) item-length (if (nil? size)
(-> (gg/calculate-default-item-length frame-length margin gutter) (gg/calculate-default-item-length frame-length margin gutter)
(mth/precision 2))
item-length)] item-length)]
(-> grid (-> grid
(update :params assoc :size size :item-length item-length) (update :params assoc :size size :item-length item-length)
@ -140,7 +138,7 @@
(if (= type :square) (if (= type :square)
[:div.input-element.pixels {:title (tr "workspace.options.size")} [:div.input-element.pixels {:title (tr "workspace.options.size")}
[:> numeric-input {:min 1 [:> numeric-input {:min 0.01
:value (or (:size params) "") :value (or (:size params) "")
:no-validate true :no-validate true
:on-change (handle-change :params :size)}]] :on-change (handle-change :params :size)}]]
@ -162,7 +160,7 @@
(when (= :square type) (when (= :square type)
[:& input-row {:label (tr "workspace.options.grid.params.size") [:& input-row {:label (tr "workspace.options.grid.params.size")
:class "pixels" :class "pixels"
:min 1 :min 0.01
:value (:size params) :value (:size params)
:on-change (handle-change :params :size)}]) :on-change (handle-change :params :size)}])
@ -207,7 +205,7 @@
[:> numeric-input [:> numeric-input
{:placeholder "Auto" {:placeholder "Auto"
:value (or (:item-length params) "") :value (or (:item-length params) "")
:default nil :nillable true
:on-change handle-change-item-length}]]) :on-change handle-change-item-length}]])
(when (#{:row :column} type) (when (#{:row :column} type)
@ -216,11 +214,15 @@
:class "pixels" :class "pixels"
:value (:gutter params) :value (:gutter params)
:min 0 :min 0
:nillable true
:default 0
:placeholder "0" :placeholder "0"
:on-change (handle-change :params :gutter)}] :on-change (handle-change :params :gutter)}]
[:& input-row {:label (tr "workspace.options.grid.params.margin") [:& input-row {:label (tr "workspace.options.grid.params.margin")
:class "pixels" :class "pixels"
:min 0 :min 0
:nillable true
:default 0
:placeholder "0" :placeholder "0"
:value (:margin params) :value (:margin params)
:on-change (handle-change :params :margin)}]]) :on-change (handle-change :params :margin)}]])

View File

@ -7,7 +7,6 @@
(ns app.main.ui.workspace.sidebar.options.menus.layer (ns app.main.ui.workspace.sidebar.options.menus.layer
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.math :as mth]
[app.main.data.workspace.changes :as dch] [app.main.data.workspace.changes :as dch]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.components.numeric-input :refer [numeric-input]] [app.main.ui.components.numeric-input :refer [numeric-input]]
@ -23,8 +22,7 @@
"" ""
(str (-> opacity (str (-> opacity
(d/coalesce 1) (d/coalesce 1)
(* 100) (* 100)))))
(mth/round)))))
(defn select-all [event] (defn select-all [event]
(dom/select-text! (dom/get-target event))) (dom/select-text! (dom/get-target event)))

View File

@ -8,7 +8,6 @@
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.math :as math]
[app.common.spec.radius :as ctr] [app.common.spec.radius :as ctr]
[app.main.data.workspace :as udw] [app.main.data.workspace :as udw]
[app.main.data.workspace.changes :as dch] [app.main.data.workspace.changes :as dch]
@ -42,14 +41,6 @@
:svg-raw #{:size :position :rotation} :svg-raw #{:size :position :rotation}
:text #{: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+) (declare +size-presets+)
;; -- User/drawing coords ;; -- User/drawing coords
@ -204,8 +195,8 @@
(fn [] (fn []
(when (and (= radius-mode :radius-1) (when (and (= radius-mode :radius-1)
(= @radius-multi? false)) (= @radius-multi? false))
;; when going back from radius-multi to normal radius-1, ;; when going back from radius-multi to normal radius-1,
;; restore focus to the newly created numeric-input ;; restore focus to the newly created numeric-input
(let [radius-input (mf/ref-val radius-input-ref)] (let [radius-input (mf/ref-val radius-input-ref)]
(dom/focus! radius-input))))) (dom/focus! radius-input)))))
@ -239,20 +230,20 @@
[:div.row-flex [:div.row-flex
[:span.element-set-subtitle (tr "workspace.options.size")] [:span.element-set-subtitle (tr "workspace.options.size")]
[:div.input-element.width {:title (tr "workspace.options.width")} [:div.input-element.width {:title (tr "workspace.options.width")}
[:> numeric-input {:min 1 [:> numeric-input {:min 0.01
:no-validate true :no-validate true
:placeholder "--" :placeholder "--"
:on-click select-all :on-click select-all
:on-change on-width-change :on-change on-width-change
:value (attr->string :width values)}]] :value (:width values)}]]
[:div.input-element.height {:title (tr "workspace.options.height")} [:div.input-element.height {:title (tr "workspace.options.height")}
[:> numeric-input {:min 1 [:> numeric-input {:min 0.01
:no-validate true :no-validate true
:placeholder "--" :placeholder "--"
:on-click select-all :on-click select-all
:on-change on-height-change :on-change on-height-change
:value (attr->string :height values)}]] :value (:height values)}]]
[:div.lock-size {:class (dom/classnames [:div.lock-size {:class (dom/classnames
:selected (true? proportion-lock) :selected (true? proportion-lock)
@ -271,15 +262,13 @@
:placeholder "--" :placeholder "--"
:on-click select-all :on-click select-all
:on-change on-pos-x-change :on-change on-pos-x-change
:value (attr->string :x values) :value (:x values)}]]
:precision 2}]]
[:div.input-element.Yaxis {:title (tr "workspace.options.y")} [:div.input-element.Yaxis {:title (tr "workspace.options.y")}
[:> numeric-input {:no-validate true [:> numeric-input {:no-validate true
:placeholder "--" :placeholder "--"
:on-click select-all :on-click select-all
:on-change on-pos-y-change :on-change on-pos-y-change
:value (attr->string :y values) :value (:y values)}]]])
:precision 2}]]])
;; ROTATION ;; ROTATION
(when (options :rotation) (when (options :rotation)
@ -290,19 +279,12 @@
{:no-validate true {:no-validate true
:min 0 :min 0
:max 359 :max 359
:default 0
:data-wrap true :data-wrap true
:placeholder "--" :placeholder "--"
:on-click select-all :on-click select-all
:on-change on-rotation-change :on-change on-rotation-change
:value (attr->string :rotation values)}]] :value (: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)}]])
;; RADIUS ;; RADIUS
(when (options :radius) (when (options :radius)
@ -330,13 +312,14 @@
:min 0 :min 0
:on-click select-all :on-click select-all
:on-change on-radius-1-change :on-change on-radius-1-change
:value (attr->string :rx values)}]] :value (:rx values)}]]
@radius-multi? @radius-multi?
[:div.input-element.mini {:title (tr "workspace.options.radius")} [:div.input-element.mini {:title (tr "workspace.options.radius")}
[:input.input-text [:input.input-text
{:type "number" {:type "number"
:placeholder "--" :placeholder "--"
:min 0
:on-click select-all :on-click select-all
:on-change on-radius-multi-change :on-change on-radius-multi-change
:value ""}]] :value ""}]]
@ -349,7 +332,7 @@
:min 0 :min 0
:on-click select-all :on-click select-all
:on-change on-radius-r1-change :on-change on-radius-r1-change
:value (attr->string :r1 values)}]] :value (:r1 values)}]]
[:div.input-element.mini {:title (tr "workspace.options.radius")} [:div.input-element.mini {:title (tr "workspace.options.radius")}
[:> numeric-input [:> numeric-input
@ -357,7 +340,7 @@
:min 0 :min 0
:on-click select-all :on-click select-all
:on-change on-radius-r2-change :on-change on-radius-r2-change
:value (attr->string :r2 values)}]] :value (:r2 values)}]]
[:div.input-element.mini {:title (tr "workspace.options.radius")} [:div.input-element.mini {:title (tr "workspace.options.radius")}
[:> numeric-input [:> numeric-input
@ -365,7 +348,7 @@
:min 0 :min 0
:on-click select-all :on-click select-all
:on-change on-radius-r3-change :on-change on-radius-r3-change
:value (attr->string :r3 values)}]] :value (:r3 values)}]]
[:div.input-element.mini {:title (tr "workspace.options.radius")} [:div.input-element.mini {:title (tr "workspace.options.radius")}
[:> numeric-input [:> numeric-input
@ -373,7 +356,7 @@
:min 0 :min 0
:on-click select-all :on-click select-all
:on-change on-radius-r4-change :on-change on-radius-r4-change
:value (attr->string :r4 values)}]]])])]]])) :value (:r4 values)}]]])])]]]))
(def +size-presets+ (def +size-presets+
[{:name "APPLE"} [{:name "APPLE"}

View File

@ -386,7 +386,6 @@
{:min -200 {:min -200
:max 200 :max 200
:step 0.1 :step 0.1
:precision 2
:value (attr->string line-height) :value (attr->string line-height)
:placeholder (tr "settings.multiple") :placeholder (tr "settings.multiple")
:on-change #(handle-change % :line-height) :on-change #(handle-change % :line-height)
@ -400,7 +399,6 @@
{:min -200 {:min -200
:max 200 :max 200
:step 0.1 :step 0.1
:precision 2
:value (attr->string letter-spacing) :value (attr->string letter-spacing)
:placeholder (tr "settings.multiple") :placeholder (tr "settings.multiple")
:on-change #(handle-change % :letter-spacing) :on-change #(handle-change % :letter-spacing)

View File

@ -7,7 +7,6 @@
(ns app.main.ui.workspace.sidebar.options.rows.color-row (ns app.main.ui.workspace.sidebar.options.rows.color-row
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.math :as math]
[app.common.pages :as cp] [app.common.pages :as cp]
[app.main.data.modal :as modal] [app.main.data.modal :as modal]
[app.main.refs :as refs] [app.main.refs :as refs]
@ -54,8 +53,7 @@
"" ""
(str (-> opacity (str (-> opacity
(d/coalesce 1) (d/coalesce 1)
(* 100) (* 100)))))
(math/round)))))
(defn remove-multiple [v] (defn remove-multiple [v]
(if (= v :multiple) nil v)) (if (= v :multiple) nil v))

View File

@ -12,7 +12,7 @@
[app.util.object :as obj] [app.util.object :as obj]
[rumext.alpha :as mf])) [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 [:div.row-flex.input-row
[:span.element-set-subtitle label] [:span.element-set-subtitle label]
[:div.input-element {:class class} [:div.input-element {:class class}
@ -43,6 +43,8 @@
{:placeholder placeholder {:placeholder placeholder
:min min :min min
:max max :max max
:default default
:nillable nillable
:on-change on-change :on-change on-change
:value (or value "")}])]]) :value (or value "")}])]])

View File

@ -97,7 +97,6 @@
[:> numeric-input [:> numeric-input
{:min 0 {:min 0
:value (-> (:stroke-width stroke) width->string) :value (-> (:stroke-width stroke) width->string)
:precision 2
:placeholder (tr "settings.multiple") :placeholder (tr "settings.multiple")
:on-change (on-stroke-width-change index) :on-change (on-stroke-width-change index)
:on-click select-all :on-click select-all

View File

@ -149,7 +149,8 @@
show-gradient-handlers? (= (count selected) 1) show-gradient-handlers? (= (count selected) 1)
show-grids? (contains? layout :display-grid) show-grids? (contains? layout :display-grid)
show-outlines? (and (nil? transform) (not edition) (not drawing-obj) (not (#{:comments :path :curve} drawing-tool))) 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-presence? page-id
show-prototypes? (= options-mode :prototype) show-prototypes? (= options-mode :prototype)
show-selection-handlers? (seq selected) show-selection-handlers? (seq selected)
@ -345,6 +346,7 @@
:zoom zoom :zoom zoom
:transform transform :transform transform
:selected selected :selected selected
:selected-shapes selected-shapes
:page-id page-id}]) :page-id page-id}])
(when show-cursor-tooltip? (when show-cursor-tooltip?

View File

@ -6,6 +6,7 @@
(ns app.main.ui.workspace.viewport.frame-grid (ns app.main.ui.workspace.viewport.frame-grid
(:require (:require
[app.common.data :as d]
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.math :as mth] [app.common.math :as mth]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
@ -42,27 +43,71 @@
:fill (str "url(#" grid-id ")")}]])) :fill (str "url(#" grid-id ")")}]]))
(mf/defc layout-grid (mf/defc layout-grid
[{:keys [key frame grid]}] [{:keys [key frame grid zoom]}]
(let [{color-value :color color-opacity :opacity} (-> grid :params :color) (let [{color-value :color color-opacity :opacity} (-> grid :params :color)
;; Support for old color format ;; Support for old color format
color-value (or color-value (:value (get-in grid [:params :color :value]))) color-value (or color-value (:value (get-in grid [:params :color :value])))
gutter (-> grid :params :gutter) gutter (gg/grid-gutter frame grid)
gutter? (and (not (nil? gutter)) (not= gutter 0)) 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 [:g.grid
(for [{:keys [x y width height] :as area} (gg/grid-areas frame grid)] (for [[idx {:keys [x y width height] :as area}] (d/enumerate (gg/grid-areas frame grid))]
[:rect {:key (str key "-" x "-" y) (cond
:x (mth/round x) gutter?
:y (mth/round y) [:rect {:key (str key "-" x "-" y)
:width (- (mth/round (+ x width)) (mth/round x)) :x x
:height (- (mth/round (+ y height)) (mth/round y)) :y y
:style style}])])) :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 (mf/defc grid-display-frame
[{:keys [frame zoom]}] [{:keys [frame zoom]}]

View File

@ -7,6 +7,7 @@
(ns app.main.ui.workspace.viewport.gradients (ns app.main.ui.workspace.viewport.gradients
"Gradients handlers and renders" "Gradients handlers and renders"
(:require (:require
[app.common.data :as d]
[app.common.geom.matrix :as gmt] [app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
@ -281,9 +282,7 @@
(fn [point] (fn [point]
(let [point (gpt/transform point transform-inverse) (let [point (gpt/transform point transform-inverse)
start-x (/ (- (:x point) x) width) start-x (/ (- (:x point) x) width)
start-y (/ (- (:y point) y) height) start-y (/ (- (:y point) y) height)]
start-x (mth/precision start-x 2)
start-y (mth/precision start-y 2)]
(change! {:start-x start-x :start-y start-y})))) (change! {:start-x start-x :start-y start-y}))))
on-change-finish on-change-finish
@ -292,9 +291,7 @@
(fn [point] (fn [point]
(let [point (gpt/transform point transform-inverse) (let [point (gpt/transform point transform-inverse)
end-x (/ (- (:x point) x) width) end-x (/ (- (:x point) x) width)
end-y (/ (- (:y point) y) height) end-y (/ (- (:y point) y) height)]
end-x (mth/precision end-x 2)
end-y (mth/precision end-y 2)]
(change! {:end-x end-x :end-y end-y})))) (change! {:end-x end-x :end-y end-y}))))
on-change-width on-change-width
@ -304,7 +301,7 @@
(let [scale-factor-y (/ gradient-length (/ height 2)) (let [scale-factor-y (/ gradient-length (/ height 2))
norm-dist (/ (gpt/distance point from-p) norm-dist (/ (gpt/distance point from-p)
(* (/ width 2) scale-factor-y))] (* (/ 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})))))] (change! {:width norm-dist})))))]
(when (and gradient (when (and gradient

View File

@ -16,6 +16,7 @@
[app.main.store :as st] [app.main.store :as st]
[app.main.streams :as ms] [app.main.streams :as ms]
[app.main.ui.cursors :as cur] [app.main.ui.cursors :as cur]
[app.main.ui.formats :as fmt]
[app.main.ui.workspace.viewport.rules :as rules] [app.main.ui.workspace.viewport.rules :as rules]
[app.util.dom :as dom] [app.util.dom :as dom]
[rumext.alpha :as mf])) [rumext.alpha :as mf]))
@ -49,6 +50,8 @@
frame-ref (mf/use-memo (mf/deps frame-id) #(refs/object-by-id frame-id)) frame-ref (mf/use-memo (mf/deps frame-id) #(refs/object-by-id frame-id))
frame (mf/deref frame-ref) frame (mf/deref frame-ref)
snap-pixel? (mf/deref refs/snap-pixel?)
on-pointer-enter on-pointer-enter
(mf/use-callback (mf/use-callback
(fn [] (fn []
@ -89,7 +92,7 @@
on-mouse-move on-mouse-move
(mf/use-callback (mf/use-callback
(mf/deps position zoom) (mf/deps position zoom snap-pixel?)
(fn [event] (fn [event]
(when-let [_ (mf/ref-val dragging-ref)] (when-let [_ (mf/ref-val dragging-ref)]
@ -101,8 +104,10 @@
(+ position delta) (+ position delta)
(+ start-pos delta)) (+ start-pos delta))
;; TODO: Change when pixel-grid flag exists new-position (if snap-pixel?
new-position (mth/round new-position) (mth/round new-position)
new-position)
new-frame-id (:id (get-hover-frame))] new-frame-id (:id (get-hover-frame))]
(swap! state assoc (swap! state assoc
:new-position new-position :new-position new-position
@ -366,7 +371,7 @@
:style {:font-size (/ rules/font-size zoom) :style {:font-size (/ rules/font-size zoom)
:font-family rules/font-family :font-family rules/font-family
:fill colors/black}} :fill colors/black}}
(str (mth/round pos))]]))]))) (fmt/format-number pos)]]))])))
(mf/defc new-guide-area (mf/defc new-guide-area
[{:keys [vbox zoom axis get-hover-frame disabled-guides?]}] [{:keys [vbox zoom axis get-hover-frame disabled-guides?]}]

View File

@ -146,12 +146,12 @@
:else :else
(connect-to-point orig-shape (connect-to-point orig-shape
{:x (+ (:x2 (:selrect orig-shape)) 100) {:x (+ (:x2 (:selrect orig-shape)) (/ 100 zoom))
:y (+ (- (:y1 (:selrect orig-shape)) 50) :y (+ (- (:y1 (:selrect orig-shape)) (/ 50 zoom))
(/ (* level 32) zoom))})) (/ (* level 32) zoom))}))
orig-dx (if (= orig-pos :right) 100 -100) orig-dx (/ (if (= orig-pos :right) 100 -100) zoom)
dest-dx (if (= dest-pos :right) 100 -100) 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] 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) pdata (str/join " " path)
@ -182,7 +182,8 @@
:d pdata}] :d pdata}]
(when dest-shape (when dest-shape
[:& outline {:shape dest-shape [:& outline {:zoom zoom
:shape dest-shape
:color "var(--color-primary)"}]) :color "var(--color-primary)"}])
[:& interaction-marker {:index index [:& interaction-marker {:index index

View File

@ -10,6 +10,7 @@
[app.common.data :as d] [app.common.data :as d]
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.math :as mth] [app.common.math :as mth]
[app.main.ui.formats :as fmt]
[app.main.ui.hooks :as hooks] [app.main.ui.hooks :as hooks]
[app.util.object :as obj] [app.util.object :as obj]
[rumext.alpha :as mf])) [rumext.alpha :as mf]))
@ -131,9 +132,9 @@
(let [{:keys [start end]} (get-rule-params vbox axis) (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) minv (* (mth/ceil (/ minv step)) step)
maxv (min (mth/round end) 100000) maxv (min end 100000)
maxv (* (mth/floor (/ maxv step)) step)] maxv (* (mth/floor (/ maxv step)) step)]
(for [step-val (range minv (inc maxv) step)] (for [step-val (range minv (inc maxv) step)]
@ -149,7 +150,7 @@
:style {:font-size (/ font-size zoom) :style {:font-size (/ font-size zoom)
:font-family font-family :font-family font-family
:fill colors/gray-30}} :fill colors/gray-30}}
(str (mth/round step-val))] (fmt/format-number step-val)]
[:line {:key (str "line-" (d/name axis) "-" step-val) [:line {:key (str "line-" (d/name axis) "-" step-val)
:x1 line-x1 :x1 line-x1
@ -184,7 +185,7 @@
:style {:font-size (/ font-size zoom) :style {:font-size (/ font-size zoom)
:font-family font-family :font-family font-family
:fill selection-area-color}} :fill selection-area-color}}
(str (mth/round (:x1 selection-rect)))] (fmt/format-number (:x1 selection-rect))]
[:rect {:x (:x2 selection-rect) [:rect {:x (:x2 selection-rect)
:y (:y vbox) :y (:y vbox)
@ -200,7 +201,7 @@
:style {:font-size (/ font-size zoom) :style {:font-size (/ font-size zoom)
:font-family font-family :font-family font-family
:fill selection-area-color}} :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)) (let [center-x (+ (:x vbox) (/ rule-area-half-size zoom))
center-y (- (+ (:y selection-rect) (/ (:height selection-rect) 2)) (/ 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) :style {:font-size (/ font-size zoom)
:font-family font-family :font-family font-family
:fill selection-area-color}} :fill selection-area-color}}
(str (mth/round (:y2 selection-rect)))] (fmt/format-number (:y2 selection-rect))]
[:text {:x (+ center-x (/ (:height selection-rect) 2) ) [:text {:x (+ center-x (/ (:height selection-rect) 2) )
:y center-y :y center-y
@ -243,7 +244,7 @@
:style {:font-size (/ font-size zoom) :style {:font-size (/ font-size zoom)
:font-family font-family :font-family font-family
:fill selection-area-color}} :fill selection-area-color}}
(str (mth/round (:y1 selection-rect)))]])]) (fmt/format-number (:y1 selection-rect))]])])
(mf/defc rules (mf/defc rules
{::mf/wrap-props false {::mf/wrap-props false

View File

@ -7,7 +7,6 @@
(ns app.main.ui.workspace.viewport.scroll-bars (ns app.main.ui.workspace.viewport.scroll-bars
(:require (:require
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.geom.shapes.rect :as gpr]
[app.common.pages.helpers :as cph] [app.common.pages.helpers :as cph]
[app.main.data.workspace :as dw] [app.main.data.workspace :as dw]
[app.main.store :as st] [app.main.store :as st]
@ -131,7 +130,7 @@
(let [viewport (mf/ref-val viewport-ref) (let [viewport (mf/ref-val viewport-ref)
start-pt (mf/ref-val start-ref) start-pt (mf/ref-val start-ref)
current-pt (dom/get-client-position event) 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) 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) x-delta (/ (* (mf/ref-val width-factor-ref) (- (:x current-pt) (:x start-pt))) zoom)
new-v-scrollbar-y (-> current-pt-viewport new-v-scrollbar-y (-> current-pt-viewport
@ -156,8 +155,9 @@
(fn [event axis] (fn [event axis]
(let [viewport (mf/ref-val viewport-ref) (let [viewport (mf/ref-val viewport-ref)
start-pt (dom/get-client-position event) start-pt (dom/get-client-position event)
new-v-scrollbar-y (-> (utils/translate-point-to-viewport-raw viewport zoom start-pt) :y) viewport-point (utils/translate-point-to-viewport viewport zoom start-pt)
new-h-scrollbar-x (-> (utils/translate-point-to-viewport-raw viewport zoom start-pt) :x) 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) v-scrollbar-y-padding (- v-scrollbar-y new-v-scrollbar-y)
h-scrollbar-x-padding (- h-scrollbar-x new-h-scrollbar-x) h-scrollbar-x-padding (- h-scrollbar-x new-h-scrollbar-x)
vbox-rect {:x vbox-x vbox-rect {:x vbox-x
@ -168,7 +168,7 @@
:y2 (+ vbox-y (:height vbox)) :y2 (+ vbox-y (:height vbox))
:width (:width vbox) :width (:width vbox)
:height (:height 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) height-factor (/ (:height containing-rect) vbox-height)
width-factor (/ (:width containing-rect) vbox-width)] width-factor (/ (:width containing-rect) vbox-width)]
(mf/set-ref-val! start-ref start-pt) (mf/set-ref-val! start-ref start-pt)
@ -206,7 +206,7 @@
:x v-scrollbar-x :x v-scrollbar-x
:y v-scrollbar-y :y v-scrollbar-y
:style {:stroke "white" :style {:stroke "white"
:stroke-width 0.15}}]]) :stroke-width (/ 0.15 zoom)}}]])
(when show-h-scroll? (when show-h-scroll?
[:g.h-scroll [:g.h-scroll
[:rect {:on-mouse-move #(on-mouse-move % :x) [:rect {:on-mouse-move #(on-mouse-move % :x)
@ -220,4 +220,4 @@
:x h-scrollbar-x :x h-scrollbar-x
:y h-scrollbar-y :y h-scrollbar-y
:style {:stroke "white" :style {:stroke "white"
:stroke-width 0.15}}]])])) :stroke-width (/ 0.15 zoom)}}]])]))

View File

@ -7,9 +7,11 @@
(ns app.main.ui.workspace.viewport.selection (ns app.main.ui.workspace.viewport.selection
"Selection handlers component." "Selection handlers component."
(:require (:require
[app.common.data.macros :as dm]
[app.common.geom.matrix :as gmt] [app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt] [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.data.workspace :as dw]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
@ -17,7 +19,6 @@
[app.main.ui.workspace.shapes.path.editor :refer [path-editor]] [app.main.ui.workspace.shapes.path.editor :refer [path-editor]]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.object :as obj] [app.util.object :as obj]
[cuerdas.core :as str]
[debug :refer [debug?]] [debug :refer [debug?]]
[rumext.alpha :as mf] [rumext.alpha :as mf]
[rumext.util :refer [map->obj]])) [rumext.util :refer [map->obj]]))
@ -33,7 +34,8 @@
(def min-selrect-side 10) (def min-selrect-side 10)
(def small-selrect-side 30) (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 (when rect
(let [{:keys [x y width height]} rect] (let [{:keys [x y width height]} rect]
[:rect.main.viewport-selrect [:rect.main.viewport-selrect
@ -41,7 +43,7 @@
:y y :y y
:width width :width width
:height height :height height
:transform transform :transform (str transform)
:on-mouse-down on-move-selected :on-mouse-down on-move-selected
:on-context-menu on-context-menu :on-context-menu on-context-menu
:style {:stroke color :style {:stroke color
@ -49,75 +51,106 @@
:fill "none"}}]))) :fill "none"}}])))
(defn- handlers-for-selection [{:keys [x y width height]} {:keys [type]} zoom] (defn- handlers-for-selection [{:keys [x y width height]} {:keys [type]} zoom]
(let [zoom-width (* width zoom) (let [threshold-small (/ 25 zoom)
zoom-height (* height zoom) threshold-tiny (/ 10 zoom)
align (when (or (<= zoom-width small-selrect-side) small-width? (<= width threshold-small)
(<= zoom-height small-selrect-side)) tiny-width? (<= width threshold-tiny)
:outside)
show-resize-point? (or (not= type :path) small-height? (<= height threshold-small)
(and tiny-height? (<= height threshold-tiny)
(> zoom-width min-selrect-side)
(> zoom-height min-selrect-side))) vertical-line? (and (= type :path) tiny-width?)
min-side-top? (or (not= type :path) (> zoom-height min-selrect-side)) horizontal-line? (and (= type :path) tiny-height?)
min-side-side? (or (not= type :path) (> zoom-width min-selrect-side))]
align (if (or small-width? small-height?)
:outside
:inside)]
(->> (->>
[ ;; TOP-LEFT [ ;; TOP-LEFT
{:type :rotation {:type :rotation
:position :top-left :position :top-left
:props {:cx x :cy y}} :props {:cx x :cy y}}
(when show-resize-point?
{:type :resize-point
:position :top-left
:props {:cx x :cy y :align align}})
{:type :rotation {:type :rotation
:position :top-right :position :top-right
:props {:cx (+ x width) :cy y}} :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 {:type :rotation
:position :bottom-right :position :bottom-right
:props {:cx (+ x width) :cy (+ y height)}} :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 {:type :rotation
:position :bottom-left :position :bottom-left
:props {:cx x :cy (+ y height)}} :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 {:type :resize-point
:position :bottom-left :position :bottom-left
:props {:cx x :cy (+ y height) :align align}}) :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}})]
(filterv (comp not nil?))))) (filterv (comp not nil?)))))
@ -135,12 +168,13 @@
:y y :y y
:width size :width size
:height size :height size
:fill (if (debug? :rotation-handler) "blue" "none") :fill (if (debug? :handlers) "blue" "none")
:transform transform :stroke-width 0
:transform (str transform)
:on-mouse-down on-rotate}])) :on-mouse-down on-rotate}]))
(mf/defc resize-point-handler (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) (let [cursor (if (#{:top-left :bottom-right} position)
(cur/resize-nesw rotation) (cur/resize-nwse rotation)) (cur/resize-nesw rotation) (cur/resize-nwse rotation))
{cx' :x cy' :y} (gpt/transform (gpt/point cx cy) transform)] {cx' :x cy' :y} (gpt/transform (gpt/point cx cy) transform)]
@ -151,7 +185,7 @@
:strokeWidth "1px" :strokeWidth "1px"
:vectorEffect "non-scaling-stroke"} :vectorEffect "non-scaling-stroke"}
:fill "var(--color-white)" :fill "var(--color-white)"
:stroke (if (and (= position :bottom-right) overflow-text) "red" color) :stroke color
:cx cx' :cx cx'
:cy cy'}] :cy cy'}]
@ -166,8 +200,9 @@
:y cy' :y cy'
:width resize-point-circle-radius :width resize-point-circle-radius
:height resize-point-circle-radius :height resize-point-circle-radius
:transform (when rotation (str/fmt "rotate(%s, %s, %s)" rotation cx' cy')) :transform (when rotation (dm/fmt "rotate(%, %, %)" rotation cx' cy'))
:style {:fill (if (debug? :resize-handler) "red" "none") :style {:fill (if (debug? :handlers) "red" "none")
:stroke-width 0
:cursor cursor} :cursor cursor}
:on-mouse-down #(on-resize {:x cx' :y cy'} %)}]) :on-mouse-down #(on-resize {:x cx' :y cy'} %)}])
@ -175,36 +210,43 @@
:r (/ resize-point-circle-radius zoom) :r (/ resize-point-circle-radius zoom)
:cx cx' :cx cx'
:cy cy' :cy cy'
:style {:fill (if (debug? :resize-handler) "red" "none") :style {:fill (if (debug? :handlers) "red" "none")
:stroke-width 0
:cursor cursor}}])])) :cursor cursor}}])]))
(mf/defc resize-side-handler (mf/defc resize-side-handler
"The side handler is always rendered horizontally and then rotated" "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) (let [res-point (if (#{:top :bottom} position)
{:y y} {:y y}
{:x x}) {: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) height (/ resize-side-height zoom)
offset-x (/ (- length width) 2)
offset-y (if (= align :outside) (- height) (- (/ height 2))) offset-y (if (= align :outside) (- height) (- (/ height 2)))
target-y (+ y offset-y)
target-x (+ x offset-x) transform-str (str (gmt/multiply transform (gmt/rotate-matrix angle (gpt/point x y))))]
target-y (+ y offset-y)] [:g.resize-handler
[:rect {:x target-x (when show-handler?
:y target-y [:circle {:r (/ resize-point-radius zoom)
:width width :style {:fillOpacity 1
:height height :stroke color
:transform (gmt/multiply transform :strokeWidth "1px"
(gmt/rotate-matrix angle (gpt/point x y))) :fill "var(--color-white)"
:on-mouse-down #(on-resize res-point %) :vectorEffect "non-scaling-stroke"}
:style {:fill (if (debug? :resize-handler) "yellow" "none") :cx (+ x (/ length 2))
:cursor (if (#{:left :right} position) :cy y
(cur/resize-ew rotation) :transform transform-str}])
(cur/resize-ns rotation)) }}])) [: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}] (defn minimum-selrect [{:keys [x y width height] :as selrect}]
(let [final-width (max width min-selrect-side) (let [final-width (max width min-selrect-side)
@ -229,13 +271,13 @@
current-transform (mf/deref refs/current-transform) current-transform (mf/deref refs/current-transform)
selrect (:selrect shape) 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)) (when (not (#{:move :rotate} current-transform))
[:g.controls {:pointer-events (if disable-handlers "none" "visible")} [:g.controls {:pointer-events (if disable-handlers "none" "visible")}
;; Selection rect ;; Selection rect
[:& selection-rect {:rect selrect [:& selection-rect {:rect selrect
:transform transform :transform (str transform)
:zoom zoom :zoom zoom
:color color :color color
:on-move-selected on-move-selected :on-move-selected on-move-selected
@ -244,7 +286,7 @@
(mf/defc controls-handlers (mf/defc controls-handlers
{::mf/wrap-props false} {::mf/wrap-props false}
[props] [props]
(let [{:keys [overflow-text] :as shape} (obj/get props "shape") (let [shape (obj/get props "shape")
zoom (obj/get props "zoom") zoom (obj/get props "zoom")
color (obj/get props "color") color (obj/get props "color")
on-resize (obj/get props "on-resize") on-resize (obj/get props "on-resize")
@ -253,7 +295,7 @@
current-transform (mf/deref refs/current-transform) current-transform (mf/deref refs/current-transform)
selrect (:selrect shape) selrect (:selrect shape)
transform (geom/transform-matrix shape {:no-flip true}) transform (gsh/transform-matrix shape {:no-flip true})
rotation (-> (gpt/point 1 0) rotation (-> (gpt/point 1 0)
(gpt/transform (:transform shape)) (gpt/transform (:transform shape))
@ -264,15 +306,14 @@
[:g.controls {:pointer-events (if disable-handlers "none" "visible")} [:g.controls {:pointer-events (if disable-handlers "none" "visible")}
;; Handlers ;; Handlers
(for [{:keys [type position props]} (handlers-for-selection selrect shape zoom)] (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 :zoom zoom
:position position :position position
:on-rotate on-rotate :on-rotate on-rotate
:on-resize (partial on-resize position) :on-resize (partial on-resize position)
:transform transform :transform transform
:rotation rotation :rotation rotation
:color color :color color}
:overflow-text overflow-text}
props (map->obj (merge common-props props))] props (map->obj (merge common-props props))]
(case type (case type
:rotation (when (not= :frame (:type shape)) [:> rotation-handler props]) :rotation (when (not= :frame (:type shape)) [:> rotation-handler props])
@ -286,13 +327,13 @@
(let [{:keys [x y width height]} shape] (let [{:keys [x y width height]} shape]
[:g.controls [:g.controls
[:rect.main {:x x :y y [:rect.main {:x x :y y
:transform (geom/transform-matrix shape) :transform (str (gsh/transform-matrix shape))
:width width :width width
:height height :height height
:pointer-events "visible" :pointer-events "visible"
:style {:stroke color :style {:stroke color
:stroke-width (/ 0.5 zoom) :stroke-width (/ 0.5 zoom)
:stroke-opacity "1" :stroke-opacity 1
:fill "none"}}]])) :fill "none"}}]]))
(mf/defc multiple-handlers (mf/defc multiple-handlers
@ -300,10 +341,9 @@
(let [shape (mf/use-memo (let [shape (mf/use-memo
(mf/deps shapes) (mf/deps shapes)
#(->> shapes #(->> shapes
(map geom/transform-shape) (map gsh/transform-shape)
(geom/selection-rect) (gsh/selection-rect)
(geom/setup {:type :rect}))) (cp/setup-shape)))
on-resize on-resize
(fn [current-position _initial-position event] (fn [current-position _initial-position event]
(when (dom/left-mouse? event) (when (dom/left-mouse? event)
@ -329,9 +369,9 @@
(let [shape (mf/use-memo (let [shape (mf/use-memo
(mf/deps shapes) (mf/deps shapes)
#(->> shapes #(->> shapes
(map geom/transform-shape) (map gsh/transform-shape)
(geom/selection-rect) (gsh/selection-rect)
(geom/setup {:type :rect})))] (cp/setup-shape)))]
[:& controls-selection [:& controls-selection
{:shape shape {:shape shape
@ -344,7 +384,7 @@
(mf/defc single-handlers (mf/defc single-handlers
[{:keys [shape zoom color disable-handlers] :as props}] [{:keys [shape zoom color disable-handlers] :as props}]
(let [shape-id (:id shape) (let [shape-id (:id shape)
shape (geom/transform-shape shape {:round-coords? false}) shape (gsh/transform-shape shape)
on-resize on-resize
(fn [current-position _initial-position event] (fn [current-position _initial-position event]
@ -368,8 +408,7 @@
(mf/defc single-selection (mf/defc single-selection
[{:keys [shape zoom color disable-handlers on-move-selected on-context-menu] :as props}] [{: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 [:& controls-selection
{:shape shape {:shape shape
:zoom zoom :zoom zoom

View File

@ -11,6 +11,7 @@
[app.common.math :as mth] [app.common.math :as mth]
[app.common.pages.helpers :as cph] [app.common.pages.helpers :as cph]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.ui.formats :as fmt]
[app.main.worker :as uw] [app.main.worker :as uw]
[beicon.core :as rx] [beicon.core :as rx]
[clojure.set :as set] [clojure.set :as set]
@ -44,6 +45,7 @@
(def pill-text-font-size 12) (def pill-text-font-size 12)
(def pill-text-height 20) (def pill-text-height 20)
(def pill-text-border-radius 4) (def pill-text-border-radius 4)
(def pill-text-padding 4)
(mf/defc shape-distance-segment (mf/defc shape-distance-segment
"Displays a segment between two selrects with the distance between them" "Displays a segment between two selrects with the distance between them"
@ -54,12 +56,13 @@
(get sr2 (if (= :x coord) :x1 :y1))) (get sr2 (if (= :x coord) :x1 :y1)))
distance (- to-c from-c) 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) half-point (half-point coord sr1 sr2)
width (-> distance-str width (-> distance-str
count count
(* (/ pill-text-width-letter zoom)) (* (/ pill-text-width-letter zoom))
(+ (/ pill-text-width-margin zoom)))] (+ (/ pill-text-width-margin zoom))
(+ (* (/ pill-text-width-margin zoom) 2)))]
[:g.distance-segment [:g.distance-segment
(let [point [(+ from-c (/ distance 2)) (let [point [(+ from-c (/ distance 2))
@ -81,7 +84,7 @@
:font-size (/ pill-text-font-size zoom) :font-size (/ pill-text-font-size zoom)
:fill "var(--color-white)" :fill "var(--color-white)"
:text-anchor "middle"} :text-anchor "middle"}
(mth/precision distance 0)]]) (fmt/format-number distance)]])
(let [p1 [(+ from-c (/ segment-gap zoom)) (+ half-point (/ segment-gap-side zoom))] (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))] p2 [(+ from-c (/ segment-gap zoom)) (- half-point (/ segment-gap-side zoom))]
@ -110,7 +113,7 @@
sr2 (:selrect sh2) sr2 (:selrect sh2)
c1 (if (= coord :x) :x1 :y1) c1 (if (= coord :x) :x1 :y1)
c2 (if (= coord :x) :x2 :y2) c2 (if (= coord :x) :x2 :y2)
dist (mth/precision (- (c1 sr2) (c2 sr1)) 0)] dist (- (c1 sr2) (c2 sr1))]
[dist [sh1 sh2]])) [dist [sh1 sh2]]))
(defn overlap? [coord sh1 sh2] (defn overlap? [coord sh1 sh2]
@ -134,8 +137,7 @@
(-> (if (<= (coord sr) (coord selrect)) (-> (if (<= (coord sr) (coord selrect))
(gsh/distance-selrect sr selrect) (gsh/distance-selrect sr selrect)
(gsh/distance-selrect selrect sr)) (gsh/distance-selrect selrect sr))
coord coord)))
(mth/precision 0))))
get-shapes-match get-shapes-match
(fn [pred? shapes] (fn [pred? shapes]
@ -149,9 +151,9 @@
check-in-set check-in-set
(fn [value number-set] (fn [value number-set]
(->> 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 ;; Gets the distance to the current selection
distances-xf (comp (map distance-to-selrect) (filter pos?)) distances-xf (comp (map distance-to-selrect) (filter pos?))
@ -195,6 +197,7 @@
(map #(vector selrect (:selrect %)))) (map #(vector selrect (:selrect %))))
segments-to-display (d/concat-set other-shapes-segments selection-segments)] segments-to-display (d/concat-set other-shapes-segments selection-segments)]
segments-to-display)) segments-to-display))
(mf/defc shape-distance (mf/defc shape-distance
@ -217,8 +220,9 @@
container-selrec (or (:selrect frame) container-selrec (or (:selrect frame)
(gsh/rect->selrect @refs/vbox)) (gsh/rect->selrect @refs/vbox))
areas (gsh/selrect->areas container-selrec selrect) areas (gsh/selrect->areas container-selrec selrect)
query-side (fn [side] query-side (fn [side]
(let [rect (gsh/pad-selrec (areas side))] (let [rect (get areas side)]
(if (and (> (:width rect) 0) (> (:height rect) 0)) (if (and (> (:width rect) 0) (> (:height rect) 0))
(->> (uw/ask! {:cmd :selection/query (->> (uw/ask! {:cmd :selection/query
:page-id page-id :page-id page-id
@ -264,15 +268,10 @@
(let [page-id (unchecked-get props "page-id") (let [page-id (unchecked-get props "page-id")
zoom (unchecked-get props "zoom") zoom (unchecked-get props "zoom")
selected (unchecked-get props "selected") 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-id (-> selected-shapes first :frame-id)
frame (mf/deref (refs/object-by-id frame-id)) frame (mf/deref (refs/object-by-id frame-id))
local (mf/deref refs/workspace-local) selrect (gsh/selection-rect selected-shapes)]
update-shape (fn [shape] (-> shape
(update :modifiers merge (:modifiers local))
gsh/transform-shape))
selrect (->> selected-shapes (map update-shape) gsh/selection-rect)]
[:g.distance [:g.distance
[:& shape-distance [:& shape-distance
{:selrect selrect {:selrect selrect

View File

@ -8,7 +8,6 @@
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.math :as mth]
[app.common.pages.helpers :as cph] [app.common.pages.helpers :as cph]
[app.common.spec :as us] [app.common.spec :as us]
[app.main.snap :as snap] [app.main.snap :as snap]
@ -28,8 +27,6 @@
(mf/defc snap-point (mf/defc snap-point
[{:keys [point zoom]}] [{:keys [point zoom]}]
(let [{:keys [x y]} point (let [{:keys [x y]} point
x (mth/round x)
y (mth/round y)
cross-width (/ 3 zoom)] cross-width (/ 3 zoom)]
[:g [:g
[:line {:x1 (- x cross-width) [:line {:x1 (- x cross-width)
@ -45,32 +42,36 @@
(mf/defc snap-line (mf/defc snap-line
[{:keys [snap point zoom]}] [{:keys [snap point zoom]}]
[:line {:x1 (mth/round (:x snap)) [:line {:x1 (:x snap)
:y1 (mth/round (:y snap)) :y1 (:y snap)
:x2 (mth/round (:x point)) :x2 (:x point)
:y2 (mth/round (:y point)) :y2 (:y point)
:style {:stroke line-color :stroke-width (str (/ line-width zoom))} :style {:stroke line-color :stroke-width (str (/ line-width zoom))}
:opacity line-opacity}]) :opacity line-opacity}])
(defn get-snap (defn get-snap
[coord {:keys [shapes page-id remove-snap? modifiers]}] [coord {:keys [shapes page-id remove-snap? zoom modifiers]}]
(let [shape (if (> (count shapes) 1) (let [shapes-sr
(->> shapes (map gsh/transform-shape) gsh/selection-rect (gsh/setup {:type :rect})) (->> shapes
(->> shapes (first))) ;; Merge modifiers into shapes
(map #(merge % (get modifiers (:id %))))
shape (if modifiers ;; Create the bounding rectangle for the shapes
(-> shape (merge (get modifiers (:id shape))) gsh/transform-shape) (gsh/selection-rect))
shape)
frame-id (snap/snap-frame-id shapes)] frame-id (snap/snap-frame-id shapes)]
(->> (rx/of shape) (->> (rx/of shapes-sr)
(rx/flat-map (fn [shape] (rx/flat-map
(->> (sp/shape-snap-points shape) (fn [selrect]
(map #(vector frame-id %))))) (->> (sp/selrect-snap-points selrect)
(rx/flat-map (fn [[frame-id point]] (map #(vector frame-id %)))))
(->> (snap/get-snap-points page-id frame-id remove-snap? point coord)
(rx/map #(vector point % coord))))) (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 [])))) (rx/reduce conj []))))
(defn- flip (defn- flip
@ -119,12 +120,19 @@
(mf/use-effect (mf/use-effect
(fn [] (fn []
(let [sub (->> subject (let [sub (->> subject
(rx/switch-map #(rx/combine-latest (get-snap :x %) (rx/switch-map
(get-snap :y %))) (fn [props]
(rx/map (fn [result] (->> (get-snap :y props)
(apply d/concat-vec (seq result)))) (rx/combine-latest (get-snap :x props)))))
(rx/subs #(let [rs (filter (fn [[_ snaps _]] (> (count snaps) 0)) %)]
(reset! state rs))))] (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 ;; On unmount callback
#(rx/dispose! sub)))) #(rx/dispose! sub))))
@ -151,22 +159,29 @@
(mf/defc snap-points (mf/defc snap-points
{::mf/wrap [mf/memo]} {::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) (us/assert set? selected)
(let [shapes (into [] (keep (d/getf objects)) selected) (let [shapes (into [] (keep (d/getf objects)) selected)
filter-shapes filter-shapes
(into selected (mapcat #(cph/get-children-ids objects %)) selected) (into selected (mapcat #(cph/get-children-ids objects %)) selected)
remove-snap? remove-snap-base?
(mf/with-memo [layout filter-shapes objects focus] (mf/with-memo [layout filter-shapes objects focus]
(snap/make-remove-snap layout filter-shapes objects focus)) (snap/make-remove-snap layout filter-shapes objects focus))
shapes (if drawing [drawing] shapes)] remove-snap?
(when (or drawing transform) (mf/use-callback
[:& snap-feedback {:shapes shapes (mf/deps remove-snap-base?)
:page-id page-id (fn [{:keys [type grid] :as snap}]
:remove-snap? remove-snap? (or (remove-snap-base? snap)
:zoom zoom (and (= type :layout) (= grid :square))
:modifiers modifiers}]))) (= type :guide))))
shapes (if drawing [drawing] shapes)]
[:& snap-feedback {:shapes shapes
:page-id page-id
:remove-snap? remove-snap?
:zoom zoom
:modifiers modifiers}]))

View File

@ -6,6 +6,7 @@
(ns app.main.ui.workspace.viewport.thumbnail-renderer (ns app.main.ui.workspace.viewport.thumbnail-renderer
(:require (:require
[app.common.math :as mth]
[app.main.data.workspace.persistence :as dwp] [app.main.data.workspace.persistence :as dwp]
[app.main.store :as st] [app.main.store :as st]
[app.util.dom :as dom] [app.util.dom :as dom]
@ -21,6 +22,10 @@
(let [thumbnail-img (mf/use-ref nil) (let [thumbnail-img (mf/use-ref nil)
thumbnail-canvas (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 on-dom-rendered
(mf/use-callback (mf/use-callback
(mf/deps (:id shape)) (mf/deps (:id shape))
@ -71,23 +76,23 @@
_ (.rect canvas-context 0 0 canvas-width canvas-height) _ (.rect canvas-context 0 0 canvas-width canvas-height)
_ (set! (.-fillStyle canvas-context) background) _ (set! (.-fillStyle canvas-context) background)
_ (.fill canvas-context) _ (.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))))] (on-thumbnail-data data))))]
[:div.frame-renderer {:ref on-dom-rendered [:div.frame-renderer {:ref on-dom-rendered
:style {:display "none"}} :style {:display "none"}}
[:img.thumbnail-img [:img.thumbnail-img
{:ref thumbnail-img {:ref thumbnail-img
:width (:width shape) :width width
:height (:height shape) :height height
:on-load on-image-load}] :on-load on-image-load}]
[:canvas.thumbnail-canvas [:canvas.thumbnail-canvas
{:ref thumbnail-canvas {:ref thumbnail-canvas
:width (:width shape) :width fixed-width
:height (:height shape)}]])) :height fixed-height}]]))
(mf/defc frame-renderer (mf/defc frame-renderer
"Component in charge of creating thumbnails and storing them" "Component in charge of creating thumbnails and storing them"

View File

@ -163,7 +163,7 @@
(:width vbox 0) (:width vbox 0)
(:height 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) (let [vbox (.. ^js viewport -viewBox -baseVal)
brect (dom/get-bounding-rect viewport) brect (dom/get-bounding-rect viewport)
brect (gpt/point (d/parse-integer (:left brect)) brect (gpt/point (d/parse-integer (:left brect))
@ -174,10 +174,6 @@
(gpt/divide zoom) (gpt/divide zoom)
(gpt/add box)))) (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] (defn get-cursor [cursor]
(case cursor (case cursor
:hand cur/hand :hand cur/hand

View File

@ -32,8 +32,8 @@
:pattern-units "userSpaceOnUse"} :pattern-units "userSpaceOnUse"}
[:path {:d "M 1 0 L 0 0 0 1" [:path {:d "M 1 0 L 0 0 0 1"
:style {:fill "none" :style {:fill "none"
:stroke "var(--color-info)" :stroke "var(--color-gray-20)"
:stroke-opacity "0.2" :stroke-opacity "1"
:stroke-width (str (/ 1 zoom))}}]]] :stroke-width (str (/ 1 zoom))}}]]]
[:rect {:x (:x vbox) [:rect {:x (:x vbox)
:y (:y vbox) :y (:y vbox)

View File

@ -7,7 +7,6 @@
(ns app.util.code-gen (ns app.util.code-gen
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.math :as mth]
[app.common.text :as txt] [app.common.text :as txt]
[app.util.color :as uc] [app.util.color :as uc]
[cuerdas.core :as str])) [cuerdas.core :as str]))
@ -109,7 +108,7 @@
(every? #(or (nil? %) (= % 0)) value) (every? #(or (nil? %) (= % 0)) value)
(or (nil? value) (= value 0)))) (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] format-property (fn [prop]
(let [css-prop (or (prop to-prop) (name prop)) (let [css-prop (or (prop to-prop) (name prop))
format-fn (or (prop format) default-format) format-fn (or (prop format) default-format)

View File

@ -167,4 +167,3 @@
(let [st (str/trim (str/lower search-term)) (let [st (str/trim (str/lower search-term))
nm (str/trim (str/lower name))] nm (str/trim (str/lower name))]
(str/includes? nm st)))) (str/includes? nm st))))

View File

@ -6,6 +6,7 @@
(ns app.util.geom.grid (ns app.util.geom.grid
(:require (:require
[app.common.data :as d]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.math :as mth])) [app.common.math :as mth]))
@ -39,13 +40,13 @@
gutter (if (= :stretch type) gutter (if (= :stretch type)
(let [gutter (/ (- width (* width' size) (* margin 2)) (dec size))] (let [gutter (/ (- width (* width' size) (* margin 2)) (dec size))]
(if (mth/finite? gutter) gutter 0)) (if (d/num? gutter) gutter 0))
gutter) gutter)
next-v (fn [cur-val] next-v (fn [cur-val]
(+ offset v (* (+ width' gutter) cur-val)))] (+ offset v (* (+ width' gutter) cur-val)))]
[size width' next-v])) [size width' next-v gutter]))
(defn- calculate-column-grid (defn- calculate-column-grid
[{:keys [width height x y] :as frame} params] [{:keys [width height x y] :as frame} params]
@ -69,6 +70,20 @@
[(* col-size row-size) size size next-x next-y])) [(* 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 (defn grid-areas
"Given a frame and the grid parameters returns the areas defined on the grid" "Given a frame and the grid parameters returns the areas defined on the grid"
[frame grid] [frame grid]
@ -92,28 +107,25 @@
(defn grid-snap-points (defn grid-snap-points
"Returns the snap points for a given grid" "Returns the snap points for a given grid"
([shape coord] [shape {:keys [type params] :as grid} coord]
(mapcat #(grid-snap-points shape % coord) (:grids shape))) (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] :column
(when (:display grid) (when (= coord :x)
(case type (->> (grid-areas shape grid)
:square (mapcat grid-area-points)))
(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 :row
(when (= coord :x) (when (= coord :y)
(->> (grid-areas shape grid) (->> (grid-areas shape grid)
(mapcat grid-area-points))) (mapcat grid-area-points))))))
:row
(when (= coord :y)
(->> (grid-areas shape grid)
(mapcat grid-area-points)))))))

View File

@ -9,25 +9,28 @@
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh])) [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 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))
(gsh/center-selrect selrect)})
(defn- frame-snap-points [{:keys [x y width height] :as selrect}] (defn frame-snap-points [{:keys [x y width height blocked hidden] :as selrect}]
(into (selrect-snap-points selrect) (when (and (not blocked) (not hidden))
#{(gpt/point (+ x (/ width 2)) y) (into (selrect-snap-points selrect)
(gpt/point (+ x width) (+ y (/ height 2))) #{(gpt/point (+ x (/ width 2)) y)
(gpt/point (+ x (/ width 2)) (+ y height)) (gpt/point (+ x width) (+ y (/ height 2)))
(gpt/point x (+ y (/ height 2)))})) (gpt/point (+ x (/ width 2)) (+ y height))
(gpt/point x (+ y (/ height 2)))})))
(defn shape-snap-points (defn shape-snap-points
[shape] [{:keys [hidden blocked] :as shape}]
(let [shape (gsh/transform-shape shape)] (when (and (not blocked) (not hidden))
(case (:type shape) (let [shape (gsh/transform-shape shape)]
:frame (-> shape :selrect frame-snap-points) (case (:type shape)
(into #{(gsh/center-shape shape)} (:points shape))))) :frame (-> shape :selrect frame-snap-points)
(into #{(gsh/center-shape shape)} (:points shape))))))
(defn guide-snap-points (defn guide-snap-points
[guide] [guide]

View File

@ -9,7 +9,6 @@
[app.common.data :as d] [app.common.data :as d]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.shapes.path :as upg] [app.common.geom.shapes.path :as upg]
[app.common.math :as mth]
[app.common.path.commands :as upc] [app.common.path.commands :as upc]
[clojure.set :as set])) [clojure.set :as set]))
@ -402,9 +401,7 @@
(rest segments)))))) (rest segments))))))
(defn calculate-merge-points [group-segments points] (defn calculate-merge-points [group-segments points]
(let [index-merge-point (fn [group] (vector group (-> (gpt/center-points group) (let [index-merge-point (fn [group] (vector group (gpt/center-points group)))
(update :x mth/round)
(update :y mth/round))))
index-group (fn [point] (vector point (d/seek #(contains? % point) group-segments))) index-group (fn [point] (vector point (d/seek #(contains? % point) group-segments)))
group->merge-point (into {} (map index-merge-point) group-segments) group->merge-point (into {} (map index-merge-point) group-segments)

View File

@ -8,7 +8,7 @@
"Performance profiling for react components." "Performance profiling for react components."
(:require-macros [app.util.perf]) (:require-macros [app.util.perf])
(:require (:require
[app.common.math :as math] [app.common.math :as mth]
[rumext.alpha :as mf] [rumext.alpha :as mf]
[goog.functions :as f] [goog.functions :as f]
["react" :as react] ["react" :as react]

View File

@ -53,6 +53,19 @@
(assoc-in [frame-id :x] (rt/make-tree)) (assoc-in [frame-id :x] (rt/make-tree))
(assoc-in [frame-id :y] (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 (defn- add-frame
[page-data frame] [page-data frame]
(let [frame-id (:id frame) (let [frame-id (:id frame)
@ -61,17 +74,8 @@
(mapv #(array-map :type :shape (mapv #(array-map :type :shape
:id frame-id :id frame-id
:pt %))) :pt %)))
grid-x-data (get-grids-snap-points frame :x)
grid-x-data (->> (gg/grid-snap-points frame :x) grid-y-data (get-grids-snap-points frame :y)]
(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 %)))]
(-> page-data (-> page-data
;; Update root frame information ;; Update root frame information
(assoc-in [uuid/zero :objects-data frame-id] frame-data) (assoc-in [uuid/zero :objects-data frame-id] frame-data)
@ -107,6 +111,7 @@
(mapv #(array-map (mapv #(array-map
:type :guide :type :guide
:id (:id guide) :id (:id guide)
:axis (:axis guide)
:frame-id (:frame-id guide) :frame-id (:frame-id guide)
:pt %)))] :pt %)))]
(if-let [frame-id (:frame-id guide)] (if-let [frame-id (:frame-id guide)]

View File

@ -7,7 +7,6 @@
(ns debug (ns debug
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.math :as mth]
[app.common.pages.helpers :as cph] [app.common.pages.helpers :as cph]
[app.common.transit :as t] [app.common.transit :as t]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
@ -32,11 +31,8 @@
;; Displays in the console log the events through the application ;; Displays in the console log the events through the application
:events :events
;; Display the boxes that represent the rotation handlers ;; Display the boxes that represent the rotation and resize handlers
:rotation-handler :handlers
;; Display the boxes that represent the resize handlers
:resize-handler
;; Displays the center of a selection ;; Displays the center of a selection
:selection-center :selection-center
@ -130,7 +126,7 @@
ts (/ 1000 (* (- cur @last))) ts (/ 1000 (* (- cur @last)))
val (+ @avg (* (- ts @avg) 0.1))] val (+ @avg (* (- ts @avg) 0.1))]
(obj/set! node "innerText" (mth/precision val 0)) (obj/set! node "innerText" val)
(vreset! last cur) (vreset! last cur)
(vreset! avg val) (vreset! avg val)
(do-thing)))))] (do-thing)))))]

View File

@ -70,7 +70,7 @@
(let [page (current-page state) (let [page (current-page state)
frame (cph/get-frame (:objects page)) frame (cph/get-frame (:objects page))
shape (-> (cp/make-minimal-shape type) 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))] (merge props))]
(swap! idmap assoc label (:id shape)) (swap! idmap assoc label (:id shape))
(update state :workspace-data (update state :workspace-data

View File

@ -2230,6 +2230,18 @@ msgstr "Show rules"
msgid "workspace.header.menu.show-textpalette" msgid "workspace.header.menu.show-textpalette"
msgstr "Show fonts palette" 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 #: src/app/main/ui/workspace/header.cljs
msgid "workspace.header.reset-zoom" msgid "workspace.header.reset-zoom"
msgstr "Reset" msgstr "Reset"

View File

@ -2246,6 +2246,18 @@ msgstr "Mostrar reglas"
msgid "workspace.header.menu.show-textpalette" msgid "workspace.header.menu.show-textpalette"
msgstr "Mostrar paleta de textos" 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 #: src/app/main/ui/workspace/header.cljs
msgid "workspace.header.reset-zoom" msgid "workspace.header.reset-zoom"
msgstr "Restablecer" msgstr "Restablecer"