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]
(if (some? v) v default))
(defn num?
"Checks if a value `val` is a number but not an Infinite or NaN"
([val]
(and (number? val)
(mth/finite? val)
(not (mth/nan? val))))
([val & vals]
(and (num? val)
(->> vals (every? num?)))))
(defn check-num
"Function that checks if a number is nil or nan. Will return 0 when not
valid and the number otherwise."
([v]
(check-num v 0))
([v default]
(if (or (not v)
(not (mth/finite? v))
(mth/nan? v)) default v)))
(if (num? v) v default)))
(defn any-key? [element & rest]
(some #(contains? element %) rest))

View File

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

View File

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

View File

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

View File

@ -17,39 +17,6 @@
[app.common.geom.shapes.transforms :as gtr]
[app.common.math :as mth]))
;; --- Setup (Initialize)
;; FIXME: Is this the correct place for these functions?
(defn- setup-rect
"A specialized function for setup rect-like shapes."
[shape {:keys [x y width height]}]
(let [rect {:x x :y y :width width :height height}
points (gpr/rect->points rect)
selrect (gpr/points->selrect points)]
(assoc shape
:x x
:y y
:width width
:height height
:points points
:selrect selrect)))
(defn- setup-image
[{:keys [metadata] :as shape} props]
(-> (setup-rect shape props)
(assoc
:proportion (/ (:width metadata)
(:height metadata))
:proportion-lock true)))
(defn setup
"A function that initializes the first coordinates for
the shape. Used mainly for draw operations."
[shape props]
(case (:type shape)
:image (setup-image shape props)
(setup-rect shape props)))
;; --- Outer Rect
(defn selection-rect
@ -106,12 +73,12 @@
:width (- x2 x1)
:height (- y2 y1)
:type :rect}))
{frame-x1 :x1 frame-x2 :x2 frame-y1 :y1 frame-y2 :y2} bounds
{bound-x1 :x1 bound-x2 :x2 bound-y1 :y1 bound-y2 :y2} bounds
{sr-x1 :x1 sr-x2 :x2 sr-y1 :y1 sr-y2 :y2} selrect]
{:left (make-selrect frame-x1 sr-y1 (- sr-x1 2) sr-y2)
:top (make-selrect sr-x1 frame-y1 sr-x2 (- sr-y1 2))
:right (make-selrect (+ sr-x2 2) sr-y1 frame-x2 sr-y2)
:bottom (make-selrect sr-x1 (+ sr-y2 2) sr-x2 frame-y2)}))
{:left (make-selrect bound-x1 sr-y1 sr-x1 sr-y2)
:top (make-selrect sr-x1 bound-y1 sr-x2 sr-y1)
:right (make-selrect sr-x2 sr-y1 bound-x2 sr-y2)
:bottom (make-selrect sr-x1 sr-y2 sr-x2 bound-y2)}))
(defn distance-selrect [selrect other]
(let [{:keys [x1 y1]} other
@ -121,13 +88,6 @@
(defn distance-shapes [shape other]
(distance-selrect (:selrect shape) (:selrect other)))
(defn setup-selrect [shape]
(let [selrect (gpr/rect->selrect shape)
points (gpr/rect->points shape)]
(-> shape
(assoc :selrect selrect
:points points))))
(defn shape-stroke-margin
[shape stroke-width]
(if (= (:type shape) :path)
@ -141,15 +101,17 @@
(dm/export gco/center-selrect)
(dm/export gco/center-rect)
(dm/export gco/center-points)
(dm/export gco/make-centered-rect)
(dm/export gco/transform-points)
(dm/export gpr/make-rect)
(dm/export gpr/rect->selrect)
(dm/export gpr/rect->points)
(dm/export gpr/points->selrect)
(dm/export gpr/points->rect)
(dm/export gpr/center->rect)
(dm/export gpr/center->selrect)
(dm/export gpr/join-rects)
(dm/export gpr/join-selrects)
(dm/export gpr/contains-selrect?)
(dm/export gtr/move)
@ -166,6 +128,7 @@
(dm/export gtr/merge-modifiers)
(dm/export gtr/transform-shape)
(dm/export gtr/transform-selrect)
(dm/export gtr/transform-bounds)
(dm/export gtr/modifiers->transform)
(dm/export gtr/empty-modifiers?)

View File

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

View File

@ -6,30 +6,24 @@
(ns app.common.geom.shapes.common
(:require
[app.common.data :as d]
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
[app.common.math :as mth]))
[app.common.geom.point :as gpt]))
(defn center-rect
[{:keys [x y width height]}]
(when (and (mth/finite? x)
(mth/finite? y)
(mth/finite? width)
(mth/finite? height))
(when (d/num? x y width height)
(gpt/point (+ x (/ width 2.0))
(+ y (/ height 2.0)))))
(defn center-selrect
"Calculate the center of the shape."
"Calculate the center of the selrect."
[selrect]
(center-rect selrect))
(def map-x-xf (comp (map :x) (remove nil?)))
(def map-y-xf (comp (map :y) (remove nil?)))
(defn center-points [points]
(let [ptx (into [] map-x-xf points)
pty (into [] map-y-xf points)
(let [ptx (into [] (keep :x) points)
pty (into [] (keep :y) points)
minx (reduce min ##Inf ptx)
miny (reduce min ##Inf pty)
maxx (reduce max ##-Inf ptx)
@ -42,35 +36,12 @@
[shape]
(center-rect (:selrect shape)))
(defn make-centered-rect
"Creates a rect given a center and a width and height"
[center width height]
{:x (- (:x center) (/ width 2.0))
:y (- (:y center) (/ height 2.0))
:width width
:height height})
(defn make-centered-selrect
"Creates a rect given a center and a width and height"
[center width height]
(let [x1 (- (:x center) (/ width 2.0))
y1 (- (:y center) (/ height 2.0))
x2 (+ x1 width)
y2 (+ y1 height)]
{:x x1
:y y1
:x1 x1
:x2 x2
:y1 y1
:y2 y2
:width width
:height height}))
(defn transform-points
([points matrix]
(transform-points points nil matrix))
([points center matrix]
(if (some? matrix)
(if (and (d/not-empty? points) (gmt/matrix? matrix))
(let [prev (if center (gmt/translate-matrix center) (gmt/matrix))
post (if center (gmt/translate-matrix (gpt/negate center)) (gmt/matrix))

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,6 +9,7 @@
[app.common.colors :as clr]
[app.common.data :as d]
[app.common.exceptions :as ex]
[app.common.geom.shapes :as gsh]
[app.common.pages.common :refer [file-version default-color]]
[app.common.uuid :as uuid]))
@ -91,8 +92,8 @@
(def empty-selrect
{:x 0 :y 0
:x1 0 :y1 0
:x2 1 :y2 1
:width 1 :height 1})
:x2 0.01 :y2 0.01
:width 0.01 :height 0.01})
(defn make-minimal-shape
[type]
@ -111,16 +112,16 @@
(not= :path (:type shape))
(assoc :x 0
:y 0
:width 1
:height 1
:width 0.01
:height 0.01
:selrect {:x 0
:y 0
:x1 0
:y1 0
:x2 1
:y2 1
:width 1
:height 1}))))
:x2 0.01
:y2 0.01
:width 0.01
:height 0.01}))))
(defn make-minimal-group
[frame-id selection-rect group-name]
@ -146,3 +147,40 @@
(assoc :id file-id)
(update :pages conj page-id)
(update :pages-index assoc page-id pd)))))
(defn setup-rect-selrect
"Initializes the selrect and points for a shape"
[shape]
(let [selrect (gsh/rect->selrect shape)
points (gsh/rect->points shape)]
(-> shape
(assoc :selrect selrect
:points points))))
(defn- setup-rect
"A specialized function for setup rect-like shapes."
[shape {:keys [x y width height]}]
(-> shape
(assoc :x x :y y :width width :height height)
(setup-rect-selrect)))
(defn- setup-image
[{:keys [metadata] :as shape} props]
(-> (setup-rect shape props)
(assoc
:proportion (/ (:width metadata)
(:height metadata))
:proportion-lock true)))
(defn setup-shape
"A function that initializes the first coordinates for
the shape. Used mainly for draw operations."
([props]
(setup-shape {:type :rect} props))
([shape props]
(case (:type shape)
:image (setup-image shape props)
(setup-rect shape props))))

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,8 +6,10 @@
(ns app.main.data.workspace.drawing.common
(:require
[app.common.geom.point :as gpt]
[app.common.geom.matrix :as gmt]
[app.common.geom.shapes :as gsh]
[app.common.math :as mth]
[app.common.pages :as cp]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.undo :as dwu]
[app.main.worker :as uw]
@ -29,27 +31,30 @@
(rx/concat
(when (:initialized? shape)
(let [page-id (:current-page-id state)
shape-click-width (case (:type shape)
:text 3
20)
shape-click-height (case (:type shape)
:text 16
20)
shape (if (:click-draw? shape)
(-> shape
(assoc-in [:modifiers :resize-vector]
(gpt/point shape-click-width shape-click-height))
(assoc-in [:modifiers :resize-origin]
(gpt/point (:x shape) (:y shape))))
shape)
shape (cond-> shape
(= (:type shape) :text) (assoc :grow-type
(if (:click-draw? shape) :auto-width :fixed)))
click-draw? (:click-draw? shape)
text? (= :text (:type shape))
shape (-> shape
(gsh/transform-shape)
(dissoc :initialized? :click-draw?))]
min-side (min 100
(mth/floor (get-in state [:workspace-local :vbox :width]))
(mth/floor (get-in state [:workspace-local :vbox :height])))
shape
(cond-> shape
(and click-draw? (not text?))
(-> (assoc :width min-side :height min-side)
(assoc-in [:modifiers :displacement]
(gmt/translate-matrix (- (/ min-side 2)) (- (/ min-side 2)))))
(and click-draw? text?)
(assoc :height 17 :width 4 :grow-type :auto-width)
click-draw?
(cp/setup-rect-selrect)
:always
(-> (gsh/transform-shape)
(dissoc :initialized? :click-draw?)))]
;; Add & select the created shape to the workspace
(rx/concat
(if (= :text (:type shape))

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -289,14 +289,6 @@
;; --- RESIZE UTILS
(defn update-overflow-text [id value]
(ptk/reify ::update-overflow-text
ptk/UpdateEvent
(update [_ state]
(let [page-id (:current-page-id state)]
(update-in state [:workspace-data :pages-index page-id :objects id] assoc :overflow-text value)))))
(def start-edit-if-selected
(ptk/reify ::start-edit-if-selected
ptk/UpdateEvent
@ -325,22 +317,13 @@
update-fn
(fn [shape]
(let [[new-width new-height] (get changes-map (:id shape))
{:keys [selrect grow-type overflow-text]} (gsh/transform-shape shape)
{:keys [selrect grow-type]} (gsh/transform-shape shape)
{shape-width :width shape-height :height} selrect
modifier-width (gsh/resize-modifiers shape :width new-width)
modifier-height (gsh/resize-modifiers shape :height new-height)]
(cond-> shape
(and overflow-text (not= :fixed grow-type))
(assoc :overflow-text false)
(and (= :fixed grow-type) (not overflow-text) (> new-height shape-height))
(assoc :overflow-text true)
(and (= :fixed grow-type) overflow-text (<= new-height shape-height))
(assoc :overflow-text false)
(and (not-changed? shape-width new-width) (= grow-type :auto-width))
(-> (assoc :modifiers modifier-width)
(gsh/transform-shape))

View File

@ -114,23 +114,32 @@
(declare get-ignore-tree)
(defn- set-modifiers
([ids] (set-modifiers ids nil false))
([ids modifiers] (set-modifiers ids modifiers false))
([ids]
(set-modifiers ids nil false))
([ids modifiers]
(set-modifiers ids modifiers false))
([ids modifiers ignore-constraints]
(set-modifiers ids modifiers ignore-constraints false))
([ids modifiers ignore-constraints ignore-snap-pixel]
(us/verify (s/coll-of uuid?) ids)
(ptk/reify ::set-modifiers
ptk/UpdateEvent
(update [_ state]
(let [modifiers (or modifiers (get-in state [:workspace-local :modifiers] {}))
page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id)
ids (into #{} (remove #(get-in objects [% :blocked] false)) ids)
(let [modifiers (or modifiers (get-in state [:workspace-local :modifiers] {}))
page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id)
ids (into #{} (remove #(get-in objects [% :blocked] false)) ids)
layout (get state :workspace-layout)
snap-pixel? (and (not ignore-snap-pixel) (contains? layout :snap-pixel-grid))
setup-modifiers
(fn [state id]
(let [shape (get objects id)]
(update state :workspace-modifiers
#(set-modifiers-recursive % objects shape modifiers ignore-constraints))))]
#(set-modifiers-recursive % objects shape modifiers ignore-constraints snap-pixel?))))]
(reduce setup-modifiers state ids))))))
@ -181,9 +190,12 @@
:ignore-tree ignore-tree
;; Attributes that can change in the transform. This way we don't have to check
;; all the attributes
:attrs [:selrect :points
:x :y
:width :height
:attrs [:selrect
:points
:x
:y
:width
:height
:content
:transform
:transform-inverse
@ -232,9 +244,97 @@
[root transformed-root ignore-geometry?]))
(defn set-pixel-precision
"Adjust modifiers so they adjust to the pixel grid"
[modifiers shape]
(if (or (some? (:resize-transform modifiers))
(some? (:resize-transform-2 modifiers)))
;; If we're working with a rotation we don't handle pixel precision because
;; the transformation won't have the precision anyway
modifiers
(let [center (gsh/center-shape shape)
base-bounds (-> (:points shape) (gsh/points->rect))
raw-bounds
(-> (gsh/transform-bounds (:points shape) center modifiers)
(gsh/points->rect))
flip-x? (neg? (get-in modifiers [:resize-vector :x]))
flip-y? (neg? (get-in modifiers [:resize-vector :y]))
path? (= :path (:type shape))
vertical-line? (and path? (<= (:width raw-bounds) 0.01))
horizontal-line? (and path? (<= (:height raw-bounds) 0.01))
target-width (if vertical-line?
(:width raw-bounds)
(max 1 (mth/round (:width raw-bounds))))
target-height (if horizontal-line?
(:height raw-bounds)
(max 1 (mth/round (:height raw-bounds))))
target-p (cond-> (gpt/round (gpt/point raw-bounds))
flip-x?
(update :x + target-width)
flip-y?
(update :y + target-height))
ratio-width (/ target-width (:width raw-bounds))
ratio-height (/ target-height (:height raw-bounds))
modifiers
(-> modifiers
(d/without-nils)
(d/update-in-when
[:resize-vector :x] #(* % ratio-width))
;; If the resize-vector-2 modifier arrives means the resize-vector
;; will only resize on the x axis
(cond-> (nil? (:resize-vector-2 modifiers))
(d/update-in-when
[:resize-vector :y] #(* % ratio-height)))
(d/update-in-when
[:resize-vector-2 :y] #(* % ratio-height)))
origin (get modifiers :resize-origin)
origin-2 (get modifiers :resize-origin-2)
resize-v (get modifiers :resize-vector)
resize-v-2 (get modifiers :resize-vector-2)
displacement (get modifiers :displacement)
target-p-inv
(-> target-p
(gpt/transform
(cond-> (gmt/matrix)
(some? displacement)
(gmt/multiply (gmt/inverse displacement))
(and (some? resize-v) (some? origin))
(gmt/scale (gpt/inverse resize-v) origin)
(and (some? resize-v-2) (some? origin-2))
(gmt/scale (gpt/inverse resize-v-2) origin-2))))
delta-v (gpt/subtract target-p-inv (gpt/point base-bounds))
modifiers
(-> modifiers
(d/update-when :displacement #(gmt/multiply (gmt/translate-matrix delta-v) %))
(cond-> (nil? (:displacement modifiers))
(assoc :displacement (gmt/translate-matrix delta-v))))]
modifiers)))
(defn- set-modifiers-recursive
[modif-tree objects shape modifiers ignore-constraints]
[modif-tree objects shape modifiers ignore-constraints snap-pixel?]
(let [children (map (d/getf objects) (:shapes shape))
modifiers (cond-> modifiers snap-pixel? (set-pixel-precision shape))
transformed-rect (gsh/transform-selrect (:selrect shape) modifiers)
set-child
@ -242,7 +342,7 @@
(let [child-modifiers (gsh/calc-child-modifiers shape child modifiers ignore-constraints transformed-rect)]
(cond-> modif-tree
(not (gsh/empty-modifiers? child-modifiers))
(set-modifiers-recursive objects child child-modifiers ignore-constraints))))
(set-modifiers-recursive objects child child-modifiers ignore-constraints snap-pixel?))))
modif-tree
(-> modif-tree
@ -280,7 +380,6 @@
(dissoc :workspace-modifiers)
(update :workspace-local dissoc :current-move-selected)))))
;; -- Resize --------------------------------------------------------
(defn start-resize
@ -291,8 +390,8 @@
{:keys [rotation]} shape
shape-center (gsh/center-shape shape)
shape-transform (:transform shape (gmt/matrix))
shape-transform-inverse (:transform-inverse shape (gmt/matrix))
shape-transform (:transform shape)
shape-transform-inverse (:transform-inverse shape)
rotation (or rotation 0)
@ -399,7 +498,8 @@
(finish-transform))))))))
(defn update-dimensions
"Change size of shapes, from the sideber options form."
"Change size of shapes, from the sideber options form.
Will ignore pixel snap used in the options side panel"
[ids attr value]
(us/verify (s/coll-of ::us/uuid) ids)
(us/verify #{:width :height} attr)
@ -408,15 +508,15 @@
ptk/UpdateEvent
(update [_ state]
(let [page-id (:current-page-id state)
objects (get-in state [:workspace-data :pages-index page-id :objects])]
objects (get-in state [:workspace-data :pages-index page-id :objects])
(reduce (fn [state id]
(let [shape (get objects id)
modifiers (gsh/resize-modifiers shape attr value)]
(update state :workspace-modifiers
#(set-modifiers-recursive % objects shape modifiers false))))
state
ids)))
update-modifiers
(fn [state id]
(let [shape (get objects id)
modifiers (gsh/resize-modifiers shape attr value)]
(update state :workspace-modifiers
#(set-modifiers-recursive % objects shape modifiers false false))))]
(reduce update-modifiers state ids)))
ptk/WatchEvent
(watch [_ _ _]
@ -439,26 +539,29 @@
group (gsh/selection-rect shapes)
group-center (gsh/center-selrect group)
initial-angle (gpt/angle @ms/mouse-position group-center)
calculate-angle (fn [pos ctrl? shift?]
(let [angle (- (gpt/angle pos group-center) initial-angle)
angle (if (neg? angle) (+ 360 angle) angle)
angle (if (= angle 360)
0
angle)
angle (if ctrl?
(* (mth/floor (/ angle 45)) 45)
angle)
angle (if shift?
(* (mth/floor (/ angle 15)) 15)
angle)]
angle))]
calculate-angle
(fn [pos ctrl? shift?]
(let [angle (- (gpt/angle pos group-center) initial-angle)
angle (if (neg? angle) (+ 360 angle) angle)
angle (if (= angle 360)
0
angle)
angle (if ctrl?
(* (mth/floor (/ angle 45)) 45)
angle)
angle (if shift?
(* (mth/floor (/ angle 15)) 15)
angle)]
angle))]
(rx/concat
(->> ms/mouse-position
(rx/with-latest vector ms/mouse-position-ctrl)
(rx/with-latest vector ms/mouse-position-shift)
(rx/map (fn [[[pos ctrl?] shift?]]
(let [delta-angle (calculate-angle pos ctrl? shift?)]
(set-rotation-modifiers delta-angle shapes group-center))))
(rx/map
(fn [[[pos ctrl?] shift?]]
(let [delta-angle (calculate-angle pos ctrl? shift?)]
(set-rotation-modifiers delta-angle shapes group-center))))
(rx/take-until stoper))
(rx/of (apply-modifiers (map :id shapes))
(finish-transform)))))))
@ -495,12 +598,13 @@
(watch [_ state stream]
(let [initial (deref ms/mouse-position)
selected (wsh/lookup-selected state {:omit-blocked? true})
stopper (rx/filter ms/mouse-up? stream)]
stopper (rx/filter ms/mouse-up? stream)
zoom (get-in state [:workspace-local :zoom] 1)]
(when-not (empty? selected)
(->> ms/mouse-position
(rx/map #(gpt/to-vec initial %))
(rx/map #(gpt/length %))
(rx/filter #(> % 1))
(rx/filter #(> % (/ 10 zoom)))
(rx/take 1)
(rx/with-latest vector ms/mouse-position-alt)
(rx/mapcat
@ -574,7 +678,9 @@
(rx/of (finish-transform))
(rx/concat
(->> position
;; We ask for the snap position but we continue even if the result is not available
(rx/with-latest vector snap-delta)
;; We try to use the previous snap so we don't have to wait for the result of the new
(rx/map snap/correct-snap-point)
(rx/map #(hash-map :displacement (gmt/translate-matrix %)))
(rx/map (partial set-modifiers ids))
@ -654,8 +760,10 @@
cpos (gpt/point (:x bbox) (:y bbox))
pos (gpt/point (or (:x position) (:x bbox))
(or (:y position) (:y bbox)))
displ (gmt/translate-matrix (gpt/subtract pos cpos))]
(rx/of (set-modifiers [id] {:displacement displ})
delta (gpt/subtract pos cpos)
displ (gmt/translate-matrix delta)]
(rx/of (set-modifiers [id] {:displacement displ} false true)
(apply-modifiers [id]))))))
(defn- calculate-frame-for-move

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

@ -6,7 +6,6 @@
(ns app.main.ui.viewer.handoff.attributes.common
(:require
[app.common.math :as mth]
[app.main.store :as st]
[app.main.ui.components.color-bullet :refer [color-bullet color-name]]
[app.main.ui.components.copy-button :refer [copy-button]]
@ -50,9 +49,9 @@
(if (:gradient color)
[:& color-name {:color color}]
(case format
:rgba (let [[r g b a] (->> (uc/hex->rgba (:color color) (:opacity color)) (map #(mth/precision % 2)))]
:rgba (let [[r g b a] (uc/hex->rgba (:color color) (:opacity color))]
[:div (str/fmt "%s, %s, %s, %s" r g b a)])
:hsla (let [[h s l a] (->> (uc/hex->hsla (:color color) (:opacity color)) (map #(mth/precision % 2)))]
:hsla (let [[h s l a] (uc/hex->hsla (:color color) (:opacity color))]
[:div (str/fmt "%s, %s, %s, %s" h s l a)])
[:*
[:& color-name {:color color}]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -225,7 +225,7 @@
(when (not has-frame?)
[:*
[:& menu-entry {:title (tr "workspace.shape.menu.create-artboard-from-selection")
:shortcut (sc/get-tooltip :create-artboard-from-selection)
:shortcut (sc/get-tooltip :artboard-selection)
:on-click do-create-artboard-from-selection}]
[:& menu-separator]])]))

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,6 +6,7 @@
(ns app.main.ui.workspace.viewport.frame-grid
(:require
[app.common.data :as d]
[app.common.geom.shapes :as gsh]
[app.common.math :as mth]
[app.common.uuid :as uuid]
@ -42,27 +43,71 @@
:fill (str "url(#" grid-id ")")}]]))
(mf/defc layout-grid
[{:keys [key frame grid]}]
[{:keys [key frame grid zoom]}]
(let [{color-value :color color-opacity :opacity} (-> grid :params :color)
;; Support for old color format
color-value (or color-value (:value (get-in grid [:params :color :value])))
gutter (-> grid :params :gutter)
gutter? (and (not (nil? gutter)) (not= gutter 0))
gutter (gg/grid-gutter frame grid)
gutter? (and (not (nil? gutter)) (not (mth/almost-zero? gutter)))]
style (if gutter?
#js {:fill color-value
:opacity color-opacity}
#js {:stroke color-value
:strokeOpacity color-opacity
:fill "none"})]
[:g.grid
(for [{:keys [x y width height] :as area} (gg/grid-areas frame grid)]
[:rect {:key (str key "-" x "-" y)
:x (mth/round x)
:y (mth/round y)
:width (- (mth/round (+ x width)) (mth/round x))
:height (- (mth/round (+ y height)) (mth/round y))
:style style}])]))
(for [[idx {:keys [x y width height] :as area}] (d/enumerate (gg/grid-areas frame grid))]
(cond
gutter?
[:rect {:key (str key "-" x "-" y)
:x x
:y y
:width (- (+ x width) x)
:height (- (+ y height) y)
:style {:fill color-value
:stroke-width 0
:opacity color-opacity}}]
(and (not gutter?) (= :column (:type grid)))
[:*
(when (= idx 0)
[:line {:key (str key "-" x "-" y "-start")
:x1 x
:y1 y
:x2 x
:y2 (+ y height)
:style {:stroke color-value
:stroke-width (/ 1 zoom)
:strokeOpacity color-opacity
:fill "none"}}])
[:line {:key (str key "-" x "-" y "-end")
:x1 (+ x width)
:y1 y
:x2 (+ x width)
:y2 (+ y height)
:style {:stroke color-value
:stroke-width (/ 1 zoom)
:strokeOpacity color-opacity
:fill "none"}}]]
(and (not gutter?) (= :row (:type grid)))
[:*
(when (= idx 0)
[:line {:key (str key "-" x "-" y "-start")
:x1 x
:y1 y
:x2 (+ x width)
:y2 y
:style {:stroke color-value
:stroke-width (/ 1 zoom)
:strokeOpacity color-opacity
:fill "none"}}])
[:line {:key (str key "-" x "-" y "-end")
:x1 x
:y1 (+ y height)
:x2 (+ x width)
:y2 (+ y height)
:style {:stroke color-value
:stroke-width (/ 1 zoom)
:strokeOpacity color-opacity
:fill "none"}}]]))]))
(mf/defc grid-display-frame
[{:keys [frame zoom]}]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -70,7 +70,7 @@
(let [page (current-page state)
frame (cph/get-frame (:objects page))
shape (-> (cp/make-minimal-shape type)
(gsh/setup {:x 0 :y 0 :width 1 :height 1})
(cp/setup-shape {:x 0 :y 0 :width 1 :height 1})
(merge props))]
(swap! idmap assoc label (:id shape))
(update state :workspace-data

View File

@ -2230,6 +2230,18 @@ msgstr "Show rules"
msgid "workspace.header.menu.show-textpalette"
msgstr "Show fonts palette"
msgid "workspace.header.menu.hide-pixel-grid"
msgstr "Hide pixel grid"
msgid "workspace.header.menu.show-pixel-grid"
msgstr "Show pixel grid"
msgid "workspace.header.menu.disable-snap-pixel-grid"
msgstr "Disable snap to pixel"
msgid "workspace.header.menu.enable-snap-pixel-grid"
msgstr "Enable snap to pixel"
#: src/app/main/ui/workspace/header.cljs
msgid "workspace.header.reset-zoom"
msgstr "Reset"

View File

@ -2246,6 +2246,18 @@ msgstr "Mostrar reglas"
msgid "workspace.header.menu.show-textpalette"
msgstr "Mostrar paleta de textos"
msgid "workspace.header.menu.hide-pixel-grid"
msgstr "Ocultar rejilla de pixeles"
msgid "workspace.header.menu.show-pixel-grid"
msgstr "Mostrar rejilla de pixeles"
msgid "workspace.header.menu.disable-snap-pixel-grid"
msgstr "Desactivar ajuste al pixel"
msgid "workspace.header.menu.enable-snap-pixel-grid"
msgstr "Activar ajuste al pixel"
#: src/app/main/ui/workspace/header.cljs
msgid "workspace.header.reset-zoom"
msgstr "Restablecer"