From e5206e65e70e9aa36ef9f8c19e2e184e9ef2965d Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 10 Mar 2022 17:42:16 +0100 Subject: [PATCH] :sparkles: Pixel precision on modifiers --- common/src/app/common/geom/shapes.cljc | 1 + common/src/app/common/geom/shapes/common.cljc | 2 +- .../app/common/geom/shapes/transforms.cljc | 48 ++- common/src/app/common/math.cljc | 5 + .../app/main/data/workspace/drawing/box.cljs | 9 +- .../src/app/main/data/workspace/layout.cljs | 8 +- .../app/main/data/workspace/shortcuts.cljs | 406 +++++++++--------- .../app/main/data/workspace/transforms.cljs | 103 ++++- frontend/src/app/main/refs.cljs | 3 + .../app/main/ui/workspace/context_menu.cljs | 2 +- .../src/app/main/ui/workspace/header.cljs | 14 + .../main/ui/workspace/viewport/guides.cljs | 13 +- .../ui/workspace/viewport/snap_distances.cljs | 6 +- frontend/translations/en.po | 12 + frontend/translations/es.po | 12 + 15 files changed, 398 insertions(+), 246 deletions(-) diff --git a/common/src/app/common/geom/shapes.cljc b/common/src/app/common/geom/shapes.cljc index b866d9cd8..75fe17862 100644 --- a/common/src/app/common/geom/shapes.cljc +++ b/common/src/app/common/geom/shapes.cljc @@ -128,6 +128,7 @@ (dm/export gtr/merge-modifiers) (dm/export gtr/transform-shape) (dm/export gtr/transform-selrect) +(dm/export gtr/transform-bounds) (dm/export gtr/modifiers->transform) (dm/export gtr/empty-modifiers?) diff --git a/common/src/app/common/geom/shapes/common.cljc b/common/src/app/common/geom/shapes/common.cljc index f68a36b66..4c85437ec 100644 --- a/common/src/app/common/geom/shapes/common.cljc +++ b/common/src/app/common/geom/shapes/common.cljc @@ -41,7 +41,7 @@ (transform-points points nil matrix)) ([points center matrix] - (if (and (d/not-empty? points) (some? matrix)) + (if (and (d/not-empty? points) (gmt/matrix? matrix)) (let [prev (if center (gmt/translate-matrix center) (gmt/matrix)) post (if center (gmt/translate-matrix (gpt/negate center)) (gmt/matrix)) diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc index e20fa49bb..7528e16ae 100644 --- a/common/src/app/common/geom/shapes/transforms.cljc +++ b/common/src/app/common/geom/shapes/transforms.cljc @@ -171,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" @@ -465,24 +466,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) @@ -562,8 +567,8 @@ :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)) @@ -573,19 +578,16 @@ 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) @@ -593,11 +595,15 @@ (gco/transform-points resize-origin (gmt/scale-matrix resize-vector)) (some? resize-origin-2) - (gco/transform-points resize-origin-2 (gmt/scale-matrix resize-vector-2)) - - :always - (gpr/points->selrect))))) + (gco/transform-points resize-origin-2 (gmt/scale-matrix resize-vector-2)))))) +(defn transform-selrect + [selrect modifiers] + (let [center (gco/center-selrect selrect)] + (-> selrect + (gpr/rect->points) + (transform-bounds center modifiers) + (gpr/points->selrect)))) (defn selection-rect "Returns a rect that contains all the shapes and is aware of the diff --git a/common/src/app/common/math.cljc b/common/src/app/common/math.cljc index d695504c4..f5bb08774 100644 --- a/common/src/app/common/math.cljc +++ b/common/src/app/common/math.cljc @@ -106,6 +106,11 @@ #?(:cljs (js/Math.round v) :clj (Math/round (float v)))) +(defn half-round + "Returns a value rounded to the next point or half point" + [v] + (/ (round (* v 2)) 2)) + (defn ceil "Returns the smallest integer greater than or equal to a given number." diff --git a/frontend/src/app/main/data/workspace/drawing/box.cljs b/frontend/src/app/main/data/workspace/drawing/box.cljs index 6b81787d9..2218845f6 100644 --- a/frontend/src/app/main/data/workspace/drawing/box.cljs +++ b/frontend/src/app/main/data/workspace/drawing/box.cljs @@ -54,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) @@ -95,7 +98,7 @@ (rx/map #(conj current %))))) (rx/map (fn [[_ shift? point]] - #(update-drawing % point shift?))) + #(update-drawing % (cond-> point snap-pixel? gpt/round) shift?))) (rx/take-until stoper)) (rx/of common/handle-finish-drawing)))))) diff --git a/frontend/src/app/main/data/workspace/layout.cljs b/frontend/src/app/main/data/workspace/layout.cljs index 9693a1030..32e641c76 100644 --- a/frontend/src/app/main/data/workspace/layout.cljs +++ b/frontend/src/app/main/data/workspace/layout.cljs @@ -27,7 +27,9 @@ :scale-text :dynamic-alignment :display-artboard-names - :snap-guides}) + :snap-guides + :show-pixel-grid + :snap-pixel-grid}) (def presets {:assets @@ -53,7 +55,9 @@ :snap-grid :dynamic-alignment :display-artboard-names - :snap-guides}) + :snap-guides + :show-pixel-grid + :snap-pixel-grid}) (def default-global {:options-mode :design}) diff --git a/frontend/src/app/main/data/workspace/shortcuts.cljs b/frontend/src/app/main/data/workspace/shortcuts.cljs index 242e2fcd8..b1b71a820 100644 --- a/frontend/src/app/main/data/workspace/shortcuts.cljs +++ b/frontend/src/app/main/data/workspace/shortcuts.cljs @@ -33,17 +33,17 @@ ;; Shortcuts format https://github.com/ccampbell/mousetrap (def base-shortcuts - {:toggle-layers {:tooltip (ds/alt "L") - :command (ds/a-mod "l") - :fn #(st/emit! (dw/go-to-layout :layers))} + {:toggle-layers {:tooltip (ds/alt "L") + :command (ds/a-mod "l") + :fn #(st/emit! (dw/go-to-layout :layers))} - :toggle-assets {:tooltip (ds/alt "I") - :command (ds/a-mod "i") - :fn #(st/emit! (dw/go-to-layout :assets))} + :toggle-assets {:tooltip (ds/alt "I") + :command (ds/a-mod "i") + :fn #(st/emit! (dw/go-to-layout :assets))} - :toggle-history {:tooltip (ds/alt "H") - :command (ds/a-mod "h") - :fn #(st/emit! (dw/go-to-layout :document-history))} + :toggle-history {:tooltip (ds/alt "H") + :command (ds/a-mod "h") + :fn #(st/emit! (dw/go-to-layout :document-history))} :toggle-colorpalette {:tooltip (ds/alt "P") :command (ds/a-mod "p") @@ -57,250 +57,250 @@ (st/emit! (dw/remove-layout-flag :colorpalette) (toggle-layout-flag :textpalette)))} - :toggle-rules {:tooltip (ds/meta-shift "R") - :command (ds/c-mod "shift+r") - :fn #(st/emit! (toggle-layout-flag :rules))} + :toggle-rules {:tooltip (ds/meta-shift "R") + :command (ds/c-mod "shift+r") + :fn #(st/emit! (toggle-layout-flag :rules))} - :select-all {:tooltip (ds/meta "A") - :command (ds/c-mod "a") - :fn #(st/emit! (dw/select-all))} + :select-all {:tooltip (ds/meta "A") + :command (ds/c-mod "a") + :fn #(st/emit! (dw/select-all))} - :toggle-grid {:tooltip (ds/meta "'") - :command (ds/c-mod "'") - :fn #(st/emit! (toggle-layout-flag :display-grid))} + :toggle-grid {:tooltip (ds/meta "'") + :command (ds/c-mod "'") + :fn #(st/emit! (toggle-layout-flag :display-grid))} - :toggle-snap-grid {:tooltip (ds/meta-shift "'") - :command (ds/c-mod "shift+'") - :fn #(st/emit! (toggle-layout-flag :snap-grid))} + :toggle-snap-grid {:tooltip (ds/meta-shift "'") + :command (ds/c-mod "shift+'") + :fn #(st/emit! (toggle-layout-flag :snap-grid))} - :toggle-snap-guide {:tooltip (ds/meta-shift "G") - :command (ds/c-mod "shift+G") - :fn #(st/emit! (toggle-layout-flag :snap-guides))} + :toggle-snap-guide {:tooltip (ds/meta-shift "G") + :command (ds/c-mod "shift+G") + :fn #(st/emit! (toggle-layout-flag :snap-guides))} - :toggle-alignment {:tooltip (ds/meta "\\") - :command (ds/c-mod "\\") - :fn #(st/emit! (toggle-layout-flag :dynamic-alignment))} + :toggle-alignment {:tooltip (ds/meta "\\") + :command (ds/c-mod "\\") + :fn #(st/emit! (toggle-layout-flag :dynamic-alignment))} - :toggle-scale-text {:tooltip "K" - :command "k" - :fn #(st/emit! (toggle-layout-flag :scale-text))} + :toggle-scale-text {:tooltip "K" + :command "k" + :fn #(st/emit! (toggle-layout-flag :scale-text))} - :increase-zoom {:tooltip "+" - :command ["+" "="] - :fn #(st/emit! (dw/increase-zoom nil))} + :increase-zoom {:tooltip "+" + :command ["+" "="] + :fn #(st/emit! (dw/increase-zoom nil))} - :decrease-zoom {:tooltip "-" - :command ["-" "_"] - :fn #(st/emit! (dw/decrease-zoom nil))} + :decrease-zoom {:tooltip "-" + :command ["-" "_"] + :fn #(st/emit! (dw/decrease-zoom nil))} - :group {:tooltip (ds/meta "G") - :command (ds/c-mod "g") - :fn #(st/emit! dw/group-selected)} + :group {:tooltip (ds/meta "G") + :command (ds/c-mod "g") + :fn #(st/emit! dw/group-selected)} - :ungroup {:tooltip (ds/shift "G") - :command "shift+g" - :fn #(st/emit! dw/ungroup-selected)} + :ungroup {:tooltip (ds/shift "G") + :command "shift+g" + :fn #(st/emit! dw/ungroup-selected)} - :mask {:tooltip (ds/meta "M") - :command (ds/c-mod "m") - :fn #(st/emit! dw/mask-group)} + :mask {:tooltip (ds/meta "M") + :command (ds/c-mod "m") + :fn #(st/emit! dw/mask-group)} - :unmask {:tooltip (ds/meta-shift "M") - :command (ds/c-mod "shift+m") - :fn #(st/emit! dw/unmask-group)} + :unmask {:tooltip (ds/meta-shift "M") + :command (ds/c-mod "shift+m") + :fn #(st/emit! dw/unmask-group)} - :create-component {:tooltip (ds/meta "K") - :command (ds/c-mod "k") - :fn #(st/emit! (dwl/add-component))} + :create-component {:tooltip (ds/meta "K") + :command (ds/c-mod "k") + :fn #(st/emit! (dwl/add-component))} - :detach-component {:tooltip (ds/meta-shift "K") - :command (ds/c-mod "shift+k") - :fn #(st/emit! dwl/detach-selected-components)} + :detach-component {:tooltip (ds/meta-shift "K") + :command (ds/c-mod "shift+k") + :fn #(st/emit! dwl/detach-selected-components)} - :flip-vertical {:tooltip (ds/shift "V") - :command "shift+v" - :fn #(st/emit! (dw/flip-vertical-selected))} + :flip-vertical {:tooltip (ds/shift "V") + :command "shift+v" + :fn #(st/emit! (dw/flip-vertical-selected))} - :flip-horizontal {:tooltip (ds/shift "H") - :command "shift+h" - :fn #(st/emit! (dw/flip-horizontal-selected))} + :flip-horizontal {:tooltip (ds/shift "H") + :command "shift+h" + :fn #(st/emit! (dw/flip-horizontal-selected))} - :reset-zoom {:tooltip (ds/shift "0") - :command "shift+0" - :fn #(st/emit! dw/reset-zoom)} + :reset-zoom {:tooltip (ds/shift "0") + :command "shift+0" + :fn #(st/emit! dw/reset-zoom)} - :fit-all {:tooltip (ds/shift "1") - :command "shift+1" - :fn #(st/emit! dw/zoom-to-fit-all)} + :fit-all {:tooltip (ds/shift "1") + :command "shift+1" + :fn #(st/emit! dw/zoom-to-fit-all)} - :zoom-selected {:tooltip (ds/shift "2") - :command "shift+2" - :fn #(st/emit! dw/zoom-to-selected-shape)} + :zoom-selected {:tooltip (ds/shift "2") + :command "shift+2" + :fn #(st/emit! dw/zoom-to-selected-shape)} - :duplicate {:tooltip (ds/meta "D") - :command (ds/c-mod "d") - :fn #(st/emit! (dw/duplicate-selected true))} + :duplicate {:tooltip (ds/meta "D") + :command (ds/c-mod "d") + :fn #(st/emit! (dw/duplicate-selected true))} - :undo {:tooltip (ds/meta "Z") - :command (ds/c-mod "z") - :fn #(st/emit! dwc/undo)} + :undo {:tooltip (ds/meta "Z") + :command (ds/c-mod "z") + :fn #(st/emit! dwc/undo)} - :redo {:tooltip (ds/meta "Y") - :command [(ds/c-mod "shift+z") (ds/c-mod "y")] - :fn #(st/emit! dwc/redo)} + :redo {:tooltip (ds/meta "Y") + :command [(ds/c-mod "shift+z") (ds/c-mod "y")] + :fn #(st/emit! dwc/redo)} - :clear-undo {:tooltip (ds/meta "Q") - :command (ds/c-mod "q") - :fn #(st/emit! dwu/reinitialize-undo)} + :clear-undo {:tooltip (ds/meta "Q") + :command (ds/c-mod "q") + :fn #(st/emit! dwu/reinitialize-undo)} - :draw-frame {:tooltip "A" - :command "a" - :fn #(st/emit! (dwd/select-for-drawing :frame))} + :draw-frame {:tooltip "A" + :command "a" + :fn #(st/emit! (dwd/select-for-drawing :frame))} - :move {:tooltip "V" - :command "v" - :fn #(st/emit! :interrupt)} + :move {:tooltip "V" + :command "v" + :fn #(st/emit! :interrupt)} - :draw-rect {:tooltip "R" - :command "r" - :fn #(st/emit! (dwd/select-for-drawing :rect))} + :draw-rect {:tooltip "R" + :command "r" + :fn #(st/emit! (dwd/select-for-drawing :rect))} - :draw-ellipse {:tooltip "E" - :command "e" - :fn #(st/emit! (dwd/select-for-drawing :circle))} + :draw-ellipse {:tooltip "E" + :command "e" + :fn #(st/emit! (dwd/select-for-drawing :circle))} - :draw-text {:tooltip "T" - :command "t" - :fn #(st/emit! dwtxt/start-edit-if-selected - (dwd/select-for-drawing :text))} + :draw-text {:tooltip "T" + :command "t" + :fn #(st/emit! dwtxt/start-edit-if-selected + (dwd/select-for-drawing :text))} - :draw-path {:tooltip "P" - :command "p" - :fn #(st/emit! (dwd/select-for-drawing :path))} + :draw-path {:tooltip "P" + :command "p" + :fn #(st/emit! (dwd/select-for-drawing :path))} - :draw-curve {:tooltip (ds/shift "C") - :command "shift+c" - :fn #(st/emit! (dwd/select-for-drawing :curve))} + :draw-curve {:tooltip (ds/shift "C") + :command "shift+c" + :fn #(st/emit! (dwd/select-for-drawing :curve))} - :add-comment {:tooltip "C" - :command "c" - :fn #(st/emit! (dwd/select-for-drawing :comments))} + :add-comment {:tooltip "C" + :command "c" + :fn #(st/emit! (dwd/select-for-drawing :comments))} - :insert-image {:tooltip (ds/shift "K") - :command "shift+k" - :fn #(-> "image-upload" dom/get-element dom/click)} + :insert-image {:tooltip (ds/shift "K") + :command "shift+k" + :fn #(-> "image-upload" dom/get-element dom/click)} - :copy {:tooltip (ds/meta "C") - :command (ds/c-mod "c") - :fn #(st/emit! (dw/copy-selected))} + :copy {:tooltip (ds/meta "C") + :command (ds/c-mod "c") + :fn #(st/emit! (dw/copy-selected))} - :cut {:tooltip (ds/meta "X") - :command (ds/c-mod "x") - :fn #(st/emit! (dw/copy-selected) - (dw/delete-selected))} + :cut {:tooltip (ds/meta "X") + :command (ds/c-mod "x") + :fn #(st/emit! (dw/copy-selected) + (dw/delete-selected))} - :paste {:tooltip (ds/meta "V") - :disabled true - :command (ds/c-mod "v") - :fn (constantly nil)} + :paste {:tooltip (ds/meta "V") + :disabled true + :command (ds/c-mod "v") + :fn (constantly nil)} - :delete {:tooltip (ds/supr) - :command ["del" "backspace"] - :fn #(st/emit! (dw/delete-selected))} + :delete {:tooltip (ds/supr) + :command ["del" "backspace"] + :fn #(st/emit! (dw/delete-selected))} - :bring-forward {:tooltip (ds/meta ds/up-arrow) - :command (ds/c-mod "up") - :fn #(st/emit! (dw/vertical-order-selected :up))} + :bring-forward {:tooltip (ds/meta ds/up-arrow) + :command (ds/c-mod "up") + :fn #(st/emit! (dw/vertical-order-selected :up))} - :bring-backward {:tooltip (ds/meta ds/down-arrow) - :command (ds/c-mod "down") - :fn #(st/emit! (dw/vertical-order-selected :down))} + :bring-backward {:tooltip (ds/meta ds/down-arrow) + :command (ds/c-mod "down") + :fn #(st/emit! (dw/vertical-order-selected :down))} - :bring-front {:tooltip (ds/meta-shift ds/up-arrow) - :command (ds/c-mod "shift+up") - :fn #(st/emit! (dw/vertical-order-selected :top))} + :bring-front {:tooltip (ds/meta-shift ds/up-arrow) + :command (ds/c-mod "shift+up") + :fn #(st/emit! (dw/vertical-order-selected :top))} - :bring-back {:tooltip (ds/meta-shift ds/down-arrow) - :command (ds/c-mod "shift+down") - :fn #(st/emit! (dw/vertical-order-selected :bottom))} + :bring-back {:tooltip (ds/meta-shift ds/down-arrow) + :command (ds/c-mod "shift+down") + :fn #(st/emit! (dw/vertical-order-selected :bottom))} - :move-fast-up {:tooltip (ds/shift ds/up-arrow) - :command "shift+up" - :fn #(st/emit! (dwt/move-selected :up true))} + :move-fast-up {:tooltip (ds/shift ds/up-arrow) + :command "shift+up" + :fn #(st/emit! (dwt/move-selected :up true))} - :move-fast-down {:tooltip (ds/shift ds/down-arrow) - :command "shift+down" - :fn #(st/emit! (dwt/move-selected :down true))} + :move-fast-down {:tooltip (ds/shift ds/down-arrow) + :command "shift+down" + :fn #(st/emit! (dwt/move-selected :down true))} - :move-fast-right {:tooltip (ds/shift ds/right-arrow) - :command "shift+right" - :fn #(st/emit! (dwt/move-selected :right true))} + :move-fast-right {:tooltip (ds/shift ds/right-arrow) + :command "shift+right" + :fn #(st/emit! (dwt/move-selected :right true))} - :move-fast-left {:tooltip (ds/shift ds/left-arrow) - :command "shift+left" - :fn #(st/emit! (dwt/move-selected :left true))} + :move-fast-left {:tooltip (ds/shift ds/left-arrow) + :command "shift+left" + :fn #(st/emit! (dwt/move-selected :left true))} - :move-unit-up {:tooltip ds/up-arrow - :command "up" - :fn #(st/emit! (dwt/move-selected :up false))} + :move-unit-up {:tooltip ds/up-arrow + :command "up" + :fn #(st/emit! (dwt/move-selected :up false))} - :move-unit-down {:tooltip ds/down-arrow - :command "down" - :fn #(st/emit! (dwt/move-selected :down false))} + :move-unit-down {:tooltip ds/down-arrow + :command "down" + :fn #(st/emit! (dwt/move-selected :down false))} - :move-unit-left {:tooltip ds/right-arrow - :command "right" - :fn #(st/emit! (dwt/move-selected :right false))} + :move-unit-left {:tooltip ds/right-arrow + :command "right" + :fn #(st/emit! (dwt/move-selected :right false))} - :move-unit-right {:tooltip ds/left-arrow - :command "left" - :fn #(st/emit! (dwt/move-selected :left false))} + :move-unit-right {:tooltip ds/left-arrow + :command "left" + :fn #(st/emit! (dwt/move-selected :left false))} - :open-color-picker {:tooltip "I" - :command "i" - :fn #(st/emit! (mdc/picker-for-selected-shape))} + :open-color-picker {:tooltip "I" + :command "i" + :fn #(st/emit! (mdc/picker-for-selected-shape))} - :open-viewer {:tooltip "G V" - :command "g v" - :fn #(st/emit! (dw/go-to-viewer))} + :open-viewer {:tooltip "G V" + :command "g v" + :fn #(st/emit! (dw/go-to-viewer))} - :open-handoff {:tooltip "G H" - :command "g h" - :fn #(st/emit! (dw/go-to-viewer {:section :handoff}))} + :open-handoff {:tooltip "G H" + :command "g h" + :fn #(st/emit! (dw/go-to-viewer {:section :handoff}))} - :open-comments {:tooltip "G C" - :command "g c" - :fn #(st/emit! (dw/go-to-viewer {:section :comments}))} + :open-comments {:tooltip "G C" + :command "g c" + :fn #(st/emit! (dw/go-to-viewer {:section :comments}))} - :open-dashboard {:tooltip "G D" - :command "g d" - :fn #(st/emit! (dw/go-to-dashboard))} + :open-dashboard {:tooltip "G D" + :command "g d" + :fn #(st/emit! (dw/go-to-dashboard))} - :escape {:tooltip (ds/esc) - :command "escape" - :fn #(st/emit! :interrupt (dw/deselect-all true))} + :escape {:tooltip (ds/esc) + :command "escape" + :fn #(st/emit! :interrupt (dw/deselect-all true))} - :start-editing {:tooltip (ds/enter) - :command "enter" - :fn #(st/emit! (dw/start-editing-selected))} + :start-editing {:tooltip (ds/enter) + :command "enter" + :fn #(st/emit! (dw/start-editing-selected))} - :start-measure {:tooltip (ds/alt "") - :command ["alt" "."] - :type "keydown" - :fn #(st/emit! (dw/toggle-distances-display true))} + :start-measure {:tooltip (ds/alt "") + :command ["alt" "."] + :type "keydown" + :fn #(st/emit! (dw/toggle-distances-display true))} - :stop-measure {:tooltip (ds/alt "") - :command ["alt" "."] - :type "keyup" - :fn #(st/emit! (dw/toggle-distances-display false))} + :stop-measure {:tooltip (ds/alt "") + :command ["alt" "."] + :type "keyup" + :fn #(st/emit! (dw/toggle-distances-display false))} - :bool-union {:tooltip (ds/meta (ds/alt "U")) - :command (ds/c-mod "alt+u") - :fn #(st/emit! (dw/create-bool :union))} + :bool-union {:tooltip (ds/meta (ds/alt "U")) + :command (ds/c-mod "alt+u") + :fn #(st/emit! (dw/create-bool :union))} - :bool-difference {:tooltip (ds/meta (ds/alt "D")) - :command (ds/c-mod "alt+d") - :fn #(st/emit! (dw/create-bool :difference))} + :bool-difference {:tooltip (ds/meta (ds/alt "D")) + :command (ds/c-mod "alt+d") + :fn #(st/emit! (dw/create-bool :difference))} :bool-intersection {:tooltip (ds/meta (ds/alt "I")) :command (ds/c-mod "alt+i") @@ -329,7 +329,7 @@ :align-vcenter {:tooltip (ds/alt "V") :command "alt+v" :fn #(st/emit! (dw/align-objects :vcenter))} - + :align-bottom {:tooltip (ds/alt "S") :command "alt+s" :fn #(st/emit! (dw/align-objects :vbottom))} @@ -354,9 +354,9 @@ :command (ds/c-mod "alt+l") :fn #(st/emit! (dw/toggle-proportion-lock))} - :create-artboard-from-selection {:tooltip (ds/meta (ds/alt "G")) - :command (ds/c-mod "alt+g") - :fn #(st/emit! (dw/create-artboard-from-selection))} + :artboard-selection {:tooltip (ds/meta (ds/alt "G")) + :command (ds/c-mod "alt+g") + :fn #(st/emit! (dw/create-artboard-from-selection))} :hide-ui {:tooltip "\\" :command "\\" @@ -368,8 +368,16 @@ :thumbnail-set {:tooltip (ds/shift "T") :command "shift+t" - :fn #(st/emit! (dw/toggle-file-thumbnail-selected))}}) + :fn #(st/emit! (dw/toggle-file-thumbnail-selected))} + :show-pixel-grid {:tooltip (ds/shift ",") + :command "shift+," + :fn #(st/emit! (toggle-layout-flag :show-pixel-grid))} + + :snap-pixel-grid {:command "," + :tooltip "," + :fn #(st/emit! (toggle-layout-flag :snap-pixel-grid))} + }) (def opacity-shortcuts (into {} (->> diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index 6a15acdc3..b5431cdca 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -114,23 +114,29 @@ (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] (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? (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)))))) @@ -235,9 +241,77 @@ [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)) + + target-p (gpt/round (gpt/point raw-bounds)) + + ratio-width (/ (mth/round (:width raw-bounds)) (:width raw-bounds)) + ratio-height (/ (mth/round (:height raw-bounds)) (: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 @@ -245,7 +319,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 @@ -283,7 +357,6 @@ (dissoc :workspace-modifiers) (update :workspace-local dissoc :current-move-selected))))) - ;; -- Resize -------------------------------------------------------- (defn start-resize @@ -294,8 +367,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) @@ -411,13 +484,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]) + layout (get state :workspace-layout) + snap-pixel? (contains? layout :snap-pixel-grid)] (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)))) + #(set-modifiers-recursive % objects shape modifiers false snap-pixel?)))) state ids))) diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 99e338e56..1528dd1cc 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -141,6 +141,9 @@ (def workspace-layout (l/derived :workspace-layout st/state)) +(def snap-pixel? + (l/derived #(contains? % :snap-pixel-grid) workspace-layout)) + (def workspace-file "A ref to a striped vision of file (without data)." (l/derived (fn [state] diff --git a/frontend/src/app/main/ui/workspace/context_menu.cljs b/frontend/src/app/main/ui/workspace/context_menu.cljs index 6f4a88ddc..c1a4e00a0 100644 --- a/frontend/src/app/main/ui/workspace/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/context_menu.cljs @@ -225,7 +225,7 @@ (when (not has-frame?) [:* [:& menu-entry {:title (tr "workspace.shape.menu.create-artboard-from-selection") - :shortcut (sc/get-tooltip :create-artboard-from-selection) + :shortcut (sc/get-tooltip :artboard-selection) :on-click do-create-artboard-from-selection}] [:& menu-separator]])])) diff --git a/frontend/src/app/main/ui/workspace/header.cljs b/frontend/src/app/main/ui/workspace/header.cljs index 0862decdb..4f669687e 100644 --- a/frontend/src/app/main/ui/workspace/header.cljs +++ b/frontend/src/app/main/ui/workspace/header.cljs @@ -376,6 +376,20 @@ (tr "workspace.header.menu.enable-dynamic-alignment"))] [:span.shortcut (sc/get-tooltip :toggle-alignment)]] + [: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 :snap-pixel-grid))} + [:span + (if (contains? layout :snap-pixel-grid) + (tr "workspace.header.menu.disable-snap-pixel-grid") + (tr "workspace.header.menu.enable-snap-pixel-grid"))] + [:span.shortcut (sc/get-tooltip :snap-pixel-grid)]] + [:li {:on-click #(st/emit! (modal/show {:type :nudge-option}))} [:span (tr "modals.nudge-title")]]]]])) diff --git a/frontend/src/app/main/ui/workspace/viewport/guides.cljs b/frontend/src/app/main/ui/workspace/viewport/guides.cljs index 7615b3b4d..c12d31850 100644 --- a/frontend/src/app/main/ui/workspace/viewport/guides.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/guides.cljs @@ -9,12 +9,14 @@ [app.common.colors :as colors] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] + [app.common.math :as mth] [app.common.uuid :as uuid] [app.main.data.workspace :as dw] [app.main.refs :as refs] [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])) @@ -48,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 [] @@ -88,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)] @@ -100,7 +104,10 @@ (+ position delta) (+ start-pos delta)) - ;; TODO: Change when pixel-grid flag exists + 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 @@ -364,7 +371,7 @@ :style {:font-size (/ rules/font-size zoom) :font-family rules/font-family :fill colors/black}} - (str pos)]]))]))) + (fmt/format-number pos)]]))]))) (mf/defc new-guide-area [{:keys [vbox zoom axis get-hover-frame disabled-guides?]}] diff --git a/frontend/src/app/main/ui/workspace/viewport/snap_distances.cljs b/frontend/src/app/main/ui/workspace/viewport/snap_distances.cljs index 63572334f..6fbd5b842 100644 --- a/frontend/src/app/main/ui/workspace/viewport/snap_distances.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/snap_distances.cljs @@ -45,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" @@ -55,12 +56,13 @@ (get sr2 (if (= :x coord) :x1 :y1))) distance (- to-c from-c) - distance-str (str distance) + 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)) diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 6eacdf3a1..180e7def9 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -2230,6 +2230,18 @@ msgstr "Show rules" msgid "workspace.header.menu.show-textpalette" msgstr "Show fonts palette" +msgid "workspace.header.menu.hide-pixel-grid" +msgstr "Hide pixel grid" + +msgid "workspace.header.menu.show-pixel-grid" +msgstr "Show pixel grid" + +msgid "workspace.header.menu.disable-snap-pixel-grid" +msgstr "Disable snap to pixel" + +msgid "workspace.header.menu.enable-snap-pixel-grid" +msgstr "Enable snap to pixel" + #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.reset-zoom" msgstr "Reset" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 963881642..d0794d6e5 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -2246,6 +2246,18 @@ msgstr "Mostrar reglas" msgid "workspace.header.menu.show-textpalette" msgstr "Mostrar paleta de textos" +msgid "workspace.header.menu.hide-pixel-grid" +msgstr "Ocultar rejilla de pixeles" + +msgid "workspace.header.menu.show-pixel-grid" +msgstr "Mostrar rejilla de pixeles" + +msgid "workspace.header.menu.disable-snap-pixel-grid" +msgstr "Desactivar ajuste al pixel" + +msgid "workspace.header.menu.enable-snap-pixel-grid" +msgstr "Activar ajuste al pixel" + #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.reset-zoom" msgstr "Restablecer"