Pixel precision on modifiers

This commit is contained in:
alonso.torres 2022-03-10 17:42:16 +01:00
parent 9332d6f36c
commit e5206e65e7
15 changed files with 398 additions and 246 deletions

View file

@ -128,6 +128,7 @@
(dm/export gtr/merge-modifiers) (dm/export gtr/merge-modifiers)
(dm/export gtr/transform-shape) (dm/export gtr/transform-shape)
(dm/export gtr/transform-selrect) (dm/export gtr/transform-selrect)
(dm/export gtr/transform-bounds)
(dm/export gtr/modifiers->transform) (dm/export gtr/modifiers->transform)
(dm/export gtr/empty-modifiers?) (dm/export gtr/empty-modifiers?)

View file

@ -41,7 +41,7 @@
(transform-points points nil matrix)) (transform-points points nil matrix))
([points center 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)) (let [prev (if center (gmt/translate-matrix center) (gmt/matrix))
post (if center (gmt/translate-matrix (gpt/negate center)) (gmt/matrix)) post (if center (gmt/translate-matrix (gpt/negate center)) (gmt/matrix))

View file

@ -171,12 +171,13 @@
(defn transform-point-center (defn transform-point-center
"Transform a point around the shape center" "Transform a point around the shape center"
[point center matrix] [point center matrix]
(when point (if (and (some? point) (some? matrix) (some? center))
(gpt/transform (gpt/transform
point point
(gmt/multiply (gmt/translate-matrix center) (gmt/multiply (gmt/translate-matrix center)
matrix matrix
(gmt/translate-matrix (gpt/negate center)))))) (gmt/translate-matrix (gpt/negate center))))
point))
(defn transform-rect (defn transform-rect
"Transform a rectangles and changes its attributes" "Transform a rectangles and changes its attributes"
@ -465,24 +466,28 @@
(normalize-scale (:y resize-v2)))) (normalize-scale (:y resize-v2))))
resize-transform (:resize-transform modifiers (gmt/matrix)) resize-transform (:resize-transform modifiers)
resize-transform-inverse (:resize-transform-inverse modifiers (gmt/matrix)) resize-transform-inverse (:resize-transform-inverse modifiers)
rt-modif (:rotation modifiers)] rt-modif (:rotation modifiers)]
(cond-> (gmt/matrix) (cond-> (gmt/matrix)
(some? resize-1) (some? resize-1)
(-> (gmt/translate origin-1) (-> (gmt/translate origin-1)
(gmt/multiply resize-transform) (cond-> (some? resize-transform)
(gmt/multiply resize-transform))
(gmt/scale resize-1) (gmt/scale resize-1)
(gmt/multiply resize-transform-inverse) (cond-> (some? resize-transform-inverse)
(gmt/multiply resize-transform-inverse))
(gmt/translate (gpt/negate origin-1))) (gmt/translate (gpt/negate origin-1)))
(some? resize-2) (some? resize-2)
(-> (gmt/translate origin-2) (-> (gmt/translate origin-2)
(gmt/multiply resize-transform) (cond-> (some? resize-transform)
(gmt/multiply resize-transform))
(gmt/scale resize-2) (gmt/scale resize-2)
(gmt/multiply resize-transform-inverse) (cond-> (some? resize-transform-inverse)
(gmt/multiply resize-transform-inverse))
(gmt/translate (gpt/negate origin-2))) (gmt/translate (gpt/negate origin-2)))
(some? displacement) (some? displacement)
@ -562,8 +567,8 @@
:always :always
(dissoc :modifiers)))))) (dissoc :modifiers))))))
(defn transform-selrect (defn transform-bounds
[selrect {:keys [displacement resize-transform-inverse resize-vector resize-origin resize-vector-2 resize-origin-2]}] [points center {:keys [displacement resize-transform-inverse resize-vector resize-origin resize-vector-2 resize-origin-2]}]
;; FIXME: Improve Performance ;; FIXME: Improve Performance
(let [resize-transform-inverse (or resize-transform-inverse (gmt/matrix)) (let [resize-transform-inverse (or resize-transform-inverse (gmt/matrix))
@ -573,19 +578,16 @@
resize-origin resize-origin
(when (some? resize-origin) (when (some? resize-origin)
(transform-point-center resize-origin (gco/center-selrect selrect) resize-transform-inverse)) (transform-point-center resize-origin center resize-transform-inverse))
resize-origin-2 resize-origin-2
(when (some? resize-origin-2) (when (some? resize-origin-2)
(transform-point-center resize-origin-2 (gco/center-selrect selrect) resize-transform-inverse))] (transform-point-center resize-origin-2 center resize-transform-inverse))]
(if (and (nil? displacement) (nil? resize-origin) (nil? resize-origin-2)) (if (and (nil? displacement) (nil? resize-origin) (nil? resize-origin-2))
selrect points
(cond-> selrect
:always
(gpr/rect->points)
(cond-> points
(some? displacement) (some? displacement)
(gco/transform-points displacement) (gco/transform-points displacement)
@ -593,11 +595,15 @@
(gco/transform-points resize-origin (gmt/scale-matrix resize-vector)) (gco/transform-points resize-origin (gmt/scale-matrix resize-vector))
(some? resize-origin-2) (some? resize-origin-2)
(gco/transform-points resize-origin-2 (gmt/scale-matrix resize-vector-2)) (gco/transform-points resize-origin-2 (gmt/scale-matrix resize-vector-2))))))
:always
(gpr/points->selrect)))))
(defn transform-selrect
[selrect modifiers]
(let [center (gco/center-selrect selrect)]
(-> selrect
(gpr/rect->points)
(transform-bounds center modifiers)
(gpr/points->selrect))))
(defn selection-rect (defn selection-rect
"Returns a rect that contains all the shapes and is aware of the "Returns a rect that contains all the shapes and is aware of the

View file

@ -106,6 +106,11 @@
#?(:cljs (js/Math.round v) #?(:cljs (js/Math.round v)
:clj (Math/round (float v)))) :clj (Math/round (float v))))
(defn half-round
"Returns a value rounded to the next point or half point"
[v]
(/ (round (* v 2)) 2))
(defn ceil (defn ceil
"Returns the smallest integer greater than "Returns the smallest integer greater than
or equal to a given number." or equal to a given number."

View file

@ -54,11 +54,14 @@
(watch [_ state stream] (watch [_ state stream]
(let [stoper? #(or (ms/mouse-up? %) (= % :interrupt)) (let [stoper? #(or (ms/mouse-up? %) (= % :interrupt))
stoper (rx/filter stoper? stream) stoper (rx/filter stoper? stream)
initial @ms/mouse-position layout (get state :workspace-layout)
snap-pixel? (contains? layout :snap-pixel-grid)
initial (cond-> @ms/mouse-position
snap-pixel? gpt/round)
page-id (:current-page-id state) page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id) objects (wsh/lookup-page-objects state page-id)
layout (get state :workspace-layout)
focus (:workspace-focus-selected state) focus (:workspace-focus-selected state)
zoom (get-in state [:workspace-local :zoom] 1) zoom (get-in state [:workspace-local :zoom] 1)
@ -95,7 +98,7 @@
(rx/map #(conj current %))))) (rx/map #(conj current %)))))
(rx/map (rx/map
(fn [[_ shift? point]] (fn [[_ shift? point]]
#(update-drawing % point shift?))) #(update-drawing % (cond-> point snap-pixel? gpt/round) shift?)))
(rx/take-until stoper)) (rx/take-until stoper))
(rx/of common/handle-finish-drawing)))))) (rx/of common/handle-finish-drawing))))))

View file

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

View file

@ -354,7 +354,7 @@
:command (ds/c-mod "alt+l") :command (ds/c-mod "alt+l")
:fn #(st/emit! (dw/toggle-proportion-lock))} :fn #(st/emit! (dw/toggle-proportion-lock))}
:create-artboard-from-selection {:tooltip (ds/meta (ds/alt "G")) :artboard-selection {:tooltip (ds/meta (ds/alt "G"))
:command (ds/c-mod "alt+g") :command (ds/c-mod "alt+g")
:fn #(st/emit! (dw/create-artboard-from-selection))} :fn #(st/emit! (dw/create-artboard-from-selection))}
@ -368,8 +368,16 @@
:thumbnail-set {:tooltip (ds/shift "T") :thumbnail-set {:tooltip (ds/shift "T")
:command "shift+t" :command "shift+t"
:fn #(st/emit! (dw/toggle-file-thumbnail-selected))}}) :fn #(st/emit! (dw/toggle-file-thumbnail-selected))}
:show-pixel-grid {:tooltip (ds/shift ",")
:command "shift+,"
:fn #(st/emit! (toggle-layout-flag :show-pixel-grid))}
:snap-pixel-grid {:command ","
:tooltip ","
:fn #(st/emit! (toggle-layout-flag :snap-pixel-grid))}
})
(def opacity-shortcuts (def opacity-shortcuts
(into {} (->> (into {} (->>

View file

@ -114,8 +114,12 @@
(declare get-ignore-tree) (declare get-ignore-tree)
(defn- set-modifiers (defn- set-modifiers
([ids] (set-modifiers ids nil false)) ([ids]
([ids modifiers] (set-modifiers ids modifiers false)) (set-modifiers ids nil false))
([ids modifiers]
(set-modifiers ids modifiers false))
([ids modifiers ignore-constraints] ([ids modifiers ignore-constraints]
(us/verify (s/coll-of uuid?) ids) (us/verify (s/coll-of uuid?) ids)
(ptk/reify ::set-modifiers (ptk/reify ::set-modifiers
@ -125,12 +129,14 @@
page-id (:current-page-id state) page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id) objects (wsh/lookup-page-objects state page-id)
ids (into #{} (remove #(get-in objects [% :blocked] false)) ids) ids (into #{} (remove #(get-in objects [% :blocked] false)) ids)
layout (get state :workspace-layout)
snap-pixel? (contains? layout :snap-pixel-grid)
setup-modifiers setup-modifiers
(fn [state id] (fn [state id]
(let [shape (get objects id)] (let [shape (get objects id)]
(update state :workspace-modifiers (update state :workspace-modifiers
#(set-modifiers-recursive % objects shape modifiers ignore-constraints))))] #(set-modifiers-recursive % objects shape modifiers ignore-constraints snap-pixel?))))]
(reduce setup-modifiers state ids)))))) (reduce setup-modifiers state ids))))))
@ -235,9 +241,77 @@
[root transformed-root ignore-geometry?])) [root transformed-root ignore-geometry?]))
(defn set-pixel-precision
"Adjust modifiers so they adjust to the pixel grid"
[modifiers shape]
(if (or (some? (:resize-transform modifiers))
(some? (:resize-transform-2 modifiers)))
;; If we're working with a rotation we don't handle pixel precision because
;; the transformation won't have the precision anyway
modifiers
(let [center (gsh/center-shape shape)
base-bounds (-> (:points shape) (gsh/points->rect))
raw-bounds
(-> (gsh/transform-bounds (:points shape) center modifiers)
(gsh/points->rect))
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 (defn- set-modifiers-recursive
[modif-tree objects shape modifiers ignore-constraints] [modif-tree objects shape modifiers ignore-constraints snap-pixel?]
(let [children (map (d/getf objects) (:shapes shape)) (let [children (map (d/getf objects) (:shapes shape))
modifiers (cond-> modifiers snap-pixel? (set-pixel-precision shape))
transformed-rect (gsh/transform-selrect (:selrect shape) modifiers) transformed-rect (gsh/transform-selrect (:selrect shape) modifiers)
set-child set-child
@ -245,7 +319,7 @@
(let [child-modifiers (gsh/calc-child-modifiers shape child modifiers ignore-constraints transformed-rect)] (let [child-modifiers (gsh/calc-child-modifiers shape child modifiers ignore-constraints transformed-rect)]
(cond-> modif-tree (cond-> modif-tree
(not (gsh/empty-modifiers? child-modifiers)) (not (gsh/empty-modifiers? child-modifiers))
(set-modifiers-recursive objects child child-modifiers ignore-constraints)))) (set-modifiers-recursive objects child child-modifiers ignore-constraints snap-pixel?))))
modif-tree modif-tree
(-> modif-tree (-> modif-tree
@ -283,7 +357,6 @@
(dissoc :workspace-modifiers) (dissoc :workspace-modifiers)
(update :workspace-local dissoc :current-move-selected))))) (update :workspace-local dissoc :current-move-selected)))))
;; -- Resize -------------------------------------------------------- ;; -- Resize --------------------------------------------------------
(defn start-resize (defn start-resize
@ -294,8 +367,8 @@
{:keys [rotation]} shape {:keys [rotation]} shape
shape-center (gsh/center-shape shape) shape-center (gsh/center-shape shape)
shape-transform (:transform shape (gmt/matrix)) shape-transform (:transform shape)
shape-transform-inverse (:transform-inverse shape (gmt/matrix)) shape-transform-inverse (:transform-inverse shape)
rotation (or rotation 0) rotation (or rotation 0)
@ -411,13 +484,15 @@
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(let [page-id (:current-page-id state) (let [page-id (:current-page-id state)
objects (get-in state [:workspace-data :pages-index page-id :objects])] objects (get-in state [:workspace-data :pages-index page-id :objects])
layout (get state :workspace-layout)
snap-pixel? (contains? layout :snap-pixel-grid)]
(reduce (fn [state id] (reduce (fn [state id]
(let [shape (get objects id) (let [shape (get objects id)
modifiers (gsh/resize-modifiers shape attr value)] modifiers (gsh/resize-modifiers shape attr value)]
(update state :workspace-modifiers (update state :workspace-modifiers
#(set-modifiers-recursive % objects shape modifiers false)))) #(set-modifiers-recursive % objects shape modifiers false snap-pixel?))))
state state
ids))) ids)))

View file

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

View file

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

View file

@ -376,6 +376,20 @@
(tr "workspace.header.menu.enable-dynamic-alignment"))] (tr "workspace.header.menu.enable-dynamic-alignment"))]
[:span.shortcut (sc/get-tooltip :toggle-alignment)]] [:span.shortcut (sc/get-tooltip :toggle-alignment)]]
[:li {:on-click #(st/emit! (toggle-flag :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}))} [:li {:on-click #(st/emit! (modal/show {:type :nudge-option}))}
[:span (tr "modals.nudge-title")]]]]])) [:span (tr "modals.nudge-title")]]]]]))

View file

@ -9,12 +9,14 @@
[app.common.colors :as colors] [app.common.colors :as colors]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.math :as mth]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.main.data.workspace :as dw] [app.main.data.workspace :as dw]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
[app.main.streams :as ms] [app.main.streams :as ms]
[app.main.ui.cursors :as cur] [app.main.ui.cursors :as cur]
[app.main.ui.formats :as fmt]
[app.main.ui.workspace.viewport.rules :as rules] [app.main.ui.workspace.viewport.rules :as rules]
[app.util.dom :as dom] [app.util.dom :as dom]
[rumext.alpha :as mf])) [rumext.alpha :as mf]))
@ -48,6 +50,8 @@
frame-ref (mf/use-memo (mf/deps frame-id) #(refs/object-by-id frame-id)) frame-ref (mf/use-memo (mf/deps frame-id) #(refs/object-by-id frame-id))
frame (mf/deref frame-ref) frame (mf/deref frame-ref)
snap-pixel? (mf/deref refs/snap-pixel?)
on-pointer-enter on-pointer-enter
(mf/use-callback (mf/use-callback
(fn [] (fn []
@ -88,7 +92,7 @@
on-mouse-move on-mouse-move
(mf/use-callback (mf/use-callback
(mf/deps position zoom) (mf/deps position zoom snap-pixel?)
(fn [event] (fn [event]
(when-let [_ (mf/ref-val dragging-ref)] (when-let [_ (mf/ref-val dragging-ref)]
@ -100,7 +104,10 @@
(+ position delta) (+ position delta)
(+ start-pos 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))] new-frame-id (:id (get-hover-frame))]
(swap! state assoc (swap! state assoc
:new-position new-position :new-position new-position
@ -364,7 +371,7 @@
:style {:font-size (/ rules/font-size zoom) :style {:font-size (/ rules/font-size zoom)
:font-family rules/font-family :font-family rules/font-family
:fill colors/black}} :fill colors/black}}
(str pos)]]))]))) (fmt/format-number pos)]]))])))
(mf/defc new-guide-area (mf/defc new-guide-area
[{:keys [vbox zoom axis get-hover-frame disabled-guides?]}] [{:keys [vbox zoom axis get-hover-frame disabled-guides?]}]

View file

@ -45,6 +45,7 @@
(def pill-text-font-size 12) (def pill-text-font-size 12)
(def pill-text-height 20) (def pill-text-height 20)
(def pill-text-border-radius 4) (def pill-text-border-radius 4)
(def pill-text-padding 4)
(mf/defc shape-distance-segment (mf/defc shape-distance-segment
"Displays a segment between two selrects with the distance between them" "Displays a segment between two selrects with the distance between them"
@ -55,12 +56,13 @@
(get sr2 (if (= :x coord) :x1 :y1))) (get sr2 (if (= :x coord) :x1 :y1)))
distance (- to-c from-c) distance (- to-c from-c)
distance-str (str distance) distance-str (fmt/format-number distance)
half-point (half-point coord sr1 sr2) half-point (half-point coord sr1 sr2)
width (-> distance-str width (-> distance-str
count count
(* (/ pill-text-width-letter zoom)) (* (/ pill-text-width-letter zoom))
(+ (/ pill-text-width-margin zoom)))] (+ (/ pill-text-width-margin zoom))
(+ (* (/ pill-text-width-margin zoom) 2)))]
[:g.distance-segment [:g.distance-segment
(let [point [(+ from-c (/ distance 2)) (let [point [(+ from-c (/ distance 2))

View file

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

View file

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