mirror of
https://github.com/penpot/penpot.git
synced 2025-02-10 00:58:26 -05:00
✨ More functionality to dynamic alignment
This commit is contained in:
parent
ffd0c95760
commit
8cbc12ef94
10 changed files with 159 additions and 75 deletions
|
@ -65,19 +65,21 @@
|
|||
:bottom-left [ex sy])]
|
||||
(gpt/point x y)))
|
||||
|
||||
(defn finish-transform [state]
|
||||
(update state :workspace-local dissoc :transform))
|
||||
|
||||
;; -- RESIZE
|
||||
(defn start-resize
|
||||
[handler ids shape]
|
||||
(letfn [(resize [shape initial [point lock?]]
|
||||
(letfn [(resize [shape initial resizing-shapes snap-data [point lock?]]
|
||||
(let [{:keys [width height rotation]} shape
|
||||
|
||||
shapev (-> (gpt/point width height))
|
||||
|
||||
;; Vector modifiers depending on the handler
|
||||
handler-modif (let [[x y] (handler-modifiers handler)] (gpt/point x y))
|
||||
|
||||
;; Difference between the origin point in the coordinate system of the rotation
|
||||
deltav (-> (gpt/subtract point initial)
|
||||
deltav (-> (snap/closest-snap snap-data resizing-shapes (gpt/to-vec initial point))
|
||||
(gpt/transform (gmt/rotate-matrix (- rotation)))
|
||||
(gpt/multiply handler-modif))
|
||||
|
||||
|
@ -115,27 +117,41 @@
|
|||
;; (rx/of point)))
|
||||
]
|
||||
(reify
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(assoc-in [:workspace-local :transform] :resize)))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [initial (apply-zoom @ms/mouse-position)
|
||||
shape (gsh/shape->rect-shape shape)
|
||||
stoper (rx/filter ms/mouse-up? stream)]
|
||||
stoper (rx/filter ms/mouse-up? stream)
|
||||
snap-data (get state :workspace-snap-data)
|
||||
page-id (get state :current-page-id)
|
||||
resizing-shapes (map #(get-in state [:workspace-data page-id :objects %]) ids)]
|
||||
(rx/concat
|
||||
(->> ms/mouse-position
|
||||
(rx/map apply-zoom)
|
||||
;; (rx/mapcat apply-grid-alignment)
|
||||
(rx/with-latest vector ms/mouse-position-ctrl)
|
||||
(rx/map normalize-proportion-lock)
|
||||
(rx/mapcat (partial resize shape initial))
|
||||
(rx/mapcat (partial resize shape initial resizing-shapes snap-data))
|
||||
(rx/take-until stoper))
|
||||
#_(rx/empty)
|
||||
(rx/of (apply-modifiers ids))))))))
|
||||
(rx/of (apply-modifiers ids)
|
||||
finish-transform)))))))
|
||||
|
||||
|
||||
;; -- ROTATE
|
||||
(defn start-rotate
|
||||
[shapes]
|
||||
(ptk/reify ::start-rotate
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(assoc-in [:workspace-local :transform] :rotate)))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [stoper (rx/filter ms/mouse-up? stream)
|
||||
|
@ -163,13 +179,19 @@
|
|||
(let [delta-angle (calculate-angle pos ctrl?)]
|
||||
(set-rotation delta-angle shapes group-center))))
|
||||
(rx/take-until stoper))
|
||||
(rx/of (apply-modifiers (map :id shapes))))))))
|
||||
(rx/of (apply-modifiers (map :id shapes))
|
||||
finish-transform))))))
|
||||
|
||||
;; -- MOVE
|
||||
|
||||
(defn start-move-selected
|
||||
[]
|
||||
(ptk/reify ::start-move-selected
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(assoc-in [:workspace-local :transform] :move)))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [selected (get-in state [:workspace-local :selected])
|
||||
|
@ -192,11 +214,14 @@
|
|||
(rx/map #(set-modifiers selected {:displacement %}))
|
||||
(rx/tap #(vswap! counter inc))
|
||||
(rx/take-until stoper))
|
||||
(->> (rx/create (fn [sink] (sink @counter)))
|
||||
(->> (rx/create (fn [sink] (sink (reduced @counter))))
|
||||
(rx/mapcat (fn [n]
|
||||
(if (zero? n)
|
||||
(rx/empty)
|
||||
(rx/of (apply-modifiers selected)))))))))))
|
||||
(rx/of (apply-modifiers selected))))))
|
||||
|
||||
(rx/of finish-transform)
|
||||
)))))
|
||||
|
||||
(defn- get-displacement-with-grid
|
||||
"Retrieve the correct displacement delta point for the
|
||||
|
|
|
@ -93,6 +93,15 @@
|
|||
(def selected-shapes
|
||||
(l/derived :selected workspace-local))
|
||||
|
||||
(def selected-shapes-with-children
|
||||
(letfn [(selector [state]
|
||||
(let [selected (get-in state [:workspace-local :selected])
|
||||
page-id (get-in state [:workspace-page :id])
|
||||
objects (get-in state [:workspace-data page-id :objects])
|
||||
children (mapcat #(helpers/get-children % objects) selected)]
|
||||
(into selected children)))]
|
||||
(l/derived selector st/state)))
|
||||
|
||||
(defn make-selected
|
||||
[id]
|
||||
(l/derived #(contains? % id) selected-shapes))
|
||||
|
@ -105,3 +114,6 @@
|
|||
|
||||
(def selected-edition
|
||||
(l/derived :edition workspace-local))
|
||||
|
||||
(def current-transform
|
||||
(l/derived :transform workspace-local))
|
||||
|
|
|
@ -119,8 +119,8 @@
|
|||
(let [shape (get-in state [:workspace-local :drawing])
|
||||
shape (geom/setup shape {:x (:x point)
|
||||
:y (:y point)
|
||||
:width 10
|
||||
:height 10})]
|
||||
:width 1
|
||||
:height 1})]
|
||||
(assoc-in state [:workspace-local :drawing] (assoc shape ::initialized? true))))
|
||||
|
||||
(resize-shape [{:keys [x y] :as shape} initial point lock?]
|
||||
|
@ -280,8 +280,16 @@
|
|||
(rx/concat
|
||||
(rx/of dw/clear-drawing)
|
||||
(when (::initialized? shape)
|
||||
(let [shape (-> shape
|
||||
(let [shape-min-width (case (:type shape)
|
||||
:text 20
|
||||
5)
|
||||
shape-min-height (case (:type shape)
|
||||
:text 16
|
||||
5)
|
||||
shape (-> shape
|
||||
(geom/transform-shape)
|
||||
(update :width #(max shape-min-width %))
|
||||
(update :height #(max shape-min-height %))
|
||||
(dissoc shape ::initialized?))]
|
||||
;; Add & select the created shape to the workspace
|
||||
(rx/of dw/deselect-all
|
||||
|
|
|
@ -22,7 +22,6 @@
|
|||
[uxbox.util.object :as obj]
|
||||
[uxbox.util.geom.point :as gpt]
|
||||
[uxbox.util.geom.matrix :as gmt]
|
||||
[uxbox.main.ui.workspace.snap-feedback :refer [snap-feedback]]
|
||||
[uxbox.util.debug :refer [debug?]]))
|
||||
|
||||
;; --- Controls (Component)
|
||||
|
@ -97,7 +96,7 @@
|
|||
zoom (obj/get props "zoom")
|
||||
on-resize (obj/get props "on-resize")
|
||||
on-rotate (obj/get props "on-rotate")
|
||||
|
||||
current-transform (mf/deref refs/current-transform)
|
||||
{:keys [x y width height rotation] :as shape} (geom/shape->rect-shape shape)
|
||||
|
||||
radius (if (> (max width height) handler-size-threshold) 4.0 4.0)
|
||||
|
@ -112,23 +111,26 @@
|
|||
:bottom-right [(+ x width) (+ y height)]}]
|
||||
|
||||
[:g.controls
|
||||
[:rect.main {:transform transform
|
||||
:x (- x 1) :y (- y 1)
|
||||
:width (+ width 2)
|
||||
:height (+ height 2)
|
||||
:style {:stroke "#1FDEA7"
|
||||
:stroke-width "1"
|
||||
:fill "transparent"}}]
|
||||
(when (not (#{:move :rotate :resize} current-transform))
|
||||
[:rect.main {:transform transform
|
||||
:x (- x 1) :y (- y 1)
|
||||
:width (+ width 2)
|
||||
:height (+ height 2)
|
||||
:style {:stroke "#1FDEA7"
|
||||
:stroke-width "1"
|
||||
:fill "transparent"}}])
|
||||
|
||||
(for [[position [cx cy]] resize-handlers]
|
||||
(let [tp (gpt/transform (gpt/point cx cy) transform)]
|
||||
[:* {:key (name position)}
|
||||
[:& rotation-handler {:cx (:x tp)
|
||||
:cy (:y tp)
|
||||
:position position
|
||||
:rotation (:rotation shape)
|
||||
:zoom zoom
|
||||
:on-mouse-down on-rotate}]
|
||||
(when (not (#{:move :rotate} current-transform))
|
||||
(for [[position [cx cy]] resize-handlers]
|
||||
(let [tp (gpt/transform (gpt/point cx cy) transform)]
|
||||
[:* {:key (name position)}
|
||||
[:& rotation-handler {:key (str "rotation-" (name position))
|
||||
:cx (:x tp)
|
||||
:cy (:y tp)
|
||||
:position position
|
||||
:rotation (:rotation shape)
|
||||
:zoom zoom
|
||||
:on-mouse-down on-rotate}]
|
||||
|
||||
[:& control-item {:class (name position)
|
||||
:on-click #(on-resize position %)
|
||||
|
@ -195,7 +197,6 @@
|
|||
[{:keys [shapes selected zoom] :as props}]
|
||||
(let [shape (geom/selection-rect shapes)
|
||||
shape-center (geom/center shape)
|
||||
|
||||
on-resize #(do (dom/stop-propagation %2)
|
||||
(st/emit! (dw/start-resize %1 selected shape)))
|
||||
|
||||
|
@ -207,7 +208,6 @@
|
|||
:zoom zoom
|
||||
:on-resize on-resize
|
||||
:on-rotate on-rotate}]
|
||||
[:& snap-feedback {:shapes shapes}]
|
||||
(when (debug? :selection-center)
|
||||
[:circle {:cx (:x shape-center) :cy (:y shape-center) :r 5 :fill "yellow"}])]))
|
||||
|
||||
|
@ -229,8 +229,7 @@
|
|||
[:& controls {:shape shape'
|
||||
:zoom zoom
|
||||
:on-rotate on-rotate
|
||||
:on-resize on-resize}]
|
||||
[:& snap-feedback {:shapes [shape]}]]))
|
||||
:on-resize on-resize}]]))
|
||||
|
||||
(mf/defc selection-handlers
|
||||
[{:keys [selected edition zoom] :as props}]
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
alotor@bloodraven.68367:1587963441
|
|
@ -5,44 +5,43 @@
|
|||
[uxbox.util.geom.snap :as snap]
|
||||
[uxbox.util.geom.point :as gpt]))
|
||||
|
||||
|
||||
(def ^:private line-color "#D383DA")
|
||||
|
||||
(mf/defc snap-feedback
|
||||
[{:keys [shapes] :as props}]
|
||||
(let [snap-data (mf/deref refs/workspace-snap-data)]
|
||||
(for [shape shapes]
|
||||
(for [point (snap/shape-snap-points shape)]
|
||||
(let [frame-id (:frame-id shape)
|
||||
shape-id (:id shape)
|
||||
(mf/defc snap-feedback []
|
||||
(let [selected (mf/deref refs/selected-shapes)
|
||||
shapes (mf/deref (refs/objects-by-id selected))
|
||||
filter-shapes (mf/deref refs/selected-shapes-with-children)
|
||||
current-transform (mf/deref refs/current-transform)
|
||||
snap-data (mf/deref refs/workspace-snap-data)]
|
||||
(when (not (nil? current-transform))
|
||||
(for [shape shapes]
|
||||
(for [point (snap/shape-snap-points shape)]
|
||||
(let [frame-id (:frame-id shape)
|
||||
shape-id (:id shape)
|
||||
snaps (into #{}
|
||||
(concat
|
||||
(snap/get-snap-points snap-data frame-id filter-shapes point :x)
|
||||
(snap/get-snap-points snap-data frame-id filter-shapes point :y)))]
|
||||
(if (not-empty snaps)
|
||||
[:* {:key (str "point-" (:id shape) "-" (:x point) "-" (:y point))}
|
||||
[:circle {:cx (:x point)
|
||||
:cy (:y point)
|
||||
:r 2
|
||||
:fill line-color}]
|
||||
|
||||
snaps-x (snap/get-snap-points snap-data frame-id shape-id point :x)
|
||||
snaps-y (snap/get-snap-points snap-data frame-id shape-id point :y)]
|
||||
(if (or (not-empty snaps-x) (not-empty snaps-y))
|
||||
[:* {:key (str "point-" (:id shape) "-" (:x point) "-" (:y point))}
|
||||
[:circle {:cx (:x point)
|
||||
:cy (:y point)
|
||||
:r 2
|
||||
:fill line-color}]
|
||||
|
||||
(for [snap (concat snaps-x snaps-y)]
|
||||
[:*
|
||||
[:circle {:cx (:x snap)
|
||||
(for [snap snaps]
|
||||
[:circle {:key (str "snap-" (:id shape) "-" (:x point) "-" (:y point) "-" (:x snap) "-" (:y snap))
|
||||
:cx (:x snap)
|
||||
:cy (:y snap)
|
||||
:r 2
|
||||
:fill line-color}]
|
||||
[:line {:x1 (:x snap)
|
||||
:fill line-color}])
|
||||
|
||||
(for [snap snaps]
|
||||
[:line {:key (str "line-" (:id shape) "-" (:x point) "-" (:y point) "-" (:x snap) "-" (:y snap))
|
||||
:x1 (:x snap)
|
||||
:y1 (:y snap)
|
||||
:x2 (:x point)
|
||||
:y2 (:y point)
|
||||
:style {:stroke line-color :stroke-width "1"}
|
||||
:opacity 0.4}]])
|
||||
|
||||
#_(when is-snap-y?
|
||||
[:line {:x1 -10000
|
||||
:y1 (:y point)
|
||||
:x2 10000
|
||||
:y2 (:y point)
|
||||
:style {:stroke line-color :stroke-width "1"}
|
||||
:opacity 0.4}])]))))))
|
||||
:opacity 0.4}])])))))))
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
[uxbox.main.ui.workspace.ruler :refer [ruler]]
|
||||
[uxbox.main.ui.workspace.selection :refer [selection-handlers]]
|
||||
[uxbox.main.ui.workspace.presence :as presence]
|
||||
[uxbox.main.ui.workspace.snap-feedback :refer [snap-feedback]]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.geom.point :as gpt]
|
||||
[uxbox.util.perf :as perf]
|
||||
|
@ -305,6 +306,8 @@
|
|||
:zoom zoom
|
||||
:edition edition}])
|
||||
|
||||
[:& snap-feedback]
|
||||
|
||||
(when-let [drawing-shape (:drawing local)]
|
||||
[:& draw-area {:shape drawing-shape
|
||||
:zoom zoom
|
||||
|
|
|
@ -8,8 +8,9 @@
|
|||
;; Copyright (c) 2015-2020 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.util.geom.point
|
||||
(:refer-clojure :exclude [divide])
|
||||
(:refer-clojure :exclude [divide min max])
|
||||
(:require
|
||||
[cljs.core :as c]
|
||||
[cuerdas.core :as str]
|
||||
[uxbox.util.math :as mth]
|
||||
[cognitect.transit :as t]))
|
||||
|
@ -70,6 +71,15 @@
|
|||
(assert (point? other))
|
||||
(Point. (/ x ox) (/ y oy)))
|
||||
|
||||
|
||||
(defn min
|
||||
[{x1 :x y1 :y :as p1} {x2 :x y2 :y :as p2}]
|
||||
(Point. (c/min x1 x2) (c/min y1 y2)))
|
||||
|
||||
(defn max
|
||||
[{x1 :x y1 :y :as p1} {x2 :x y2 :y :as p2}]
|
||||
(Point. (c/max x1 x2) (c/max y1 y2)))
|
||||
|
||||
(defn inverse
|
||||
[{:keys [x y] :as p}]
|
||||
(assert (point? p))
|
||||
|
|
|
@ -738,6 +738,15 @@
|
|||
(gpt/divide (gpt/point (:width shape-path-temp-rec) (:height shape-path-temp-rec))
|
||||
(gpt/point (:width shape-path-temp-dim) (:height shape-path-temp-dim)))))
|
||||
|
||||
(defn- fix-invalid-rect-values [rect-shape]
|
||||
(letfn [(check [num] (if (or (nil? num) (mth/nan? num)) 0 num))
|
||||
(to-positive [num] (if (< num 1) 1 num))]
|
||||
(-> rect-shape
|
||||
(update :x check)
|
||||
(update :y check)
|
||||
(update :width (comp to-positive check))
|
||||
(update :height (comp to-positive check)))))
|
||||
|
||||
(defn transform-rect-shape
|
||||
[shape]
|
||||
(let [;; Apply modifiers to the rect as a path so we have the end shape expected
|
||||
|
@ -785,6 +794,7 @@
|
|||
(merge rec)
|
||||
(update :x #(mth/precision % 2))
|
||||
(update :y #(mth/precision % 2))
|
||||
(fix-invalid-rect-values)
|
||||
(update :transform #(gmt/multiply (or % (gmt/matrix)) stretch-matrix))
|
||||
(update :transform-inverse #(gmt/multiply stretch-matrix-inverse (or % (gmt/matrix)))))]
|
||||
|
||||
|
|
|
@ -16,17 +16,29 @@
|
|||
[uxbox.util.geom.point :as gpt]
|
||||
[uxbox.util.debug :refer [logjs]]))
|
||||
|
||||
(def ^:private snap-accuracy 5)
|
||||
(def ^:private snap-accuracy 8)
|
||||
|
||||
(defn mapm
|
||||
"Map over the values of a map"
|
||||
[mfn coll]
|
||||
(into {} (map (fn [[key val]] [key (mfn val)]) coll)))
|
||||
|
||||
(defn- frame-snap-points [{:keys [x y width height]}]
|
||||
#{(gpt/point x y)
|
||||
(gpt/point (+ x (/ width 2)) y)
|
||||
(gpt/point (+ x width) y)
|
||||
(gpt/point (+ x width) (+ y (/ height 2)))
|
||||
(gpt/point (+ x width) (+ y height))
|
||||
(gpt/point (+ x (/ width 2)) (+ y height))
|
||||
(gpt/point x (+ y height))
|
||||
(gpt/point x (+ y (/ height 2)))})
|
||||
|
||||
(defn shape-snap-points [shape]
|
||||
(let [modified-path (gsh/transform-apply-modifiers shape)
|
||||
shape-center (gsh/center modified-path)]
|
||||
(into #{shape-center} (:segments modified-path))))
|
||||
(if (= :frame (:type shape))
|
||||
(frame-snap-points shape)
|
||||
(let [modified-path (gsh/transform-apply-modifiers shape)
|
||||
shape-center (gsh/center modified-path)]
|
||||
(into #{shape-center} (:segments modified-path)))))
|
||||
|
||||
(defn create-coord-data [shapes coord]
|
||||
(let [process-shape
|
||||
|
@ -42,7 +54,14 @@
|
|||
"Initialize the snap information with the current workspace information"
|
||||
[objects]
|
||||
(let [shapes (vals objects)
|
||||
frame-shapes (group-by :frame-id (filter (comp not nil? :frame-id) shapes))]
|
||||
frame-shapes (->> shapes
|
||||
(filter (comp not nil? :frame-id))
|
||||
(group-by :frame-id))
|
||||
|
||||
frame-shapes (->> shapes
|
||||
(filter #(= :frame (:type %)))
|
||||
(remove #(= zero (:id %)))
|
||||
(reduce #(update %1 (:id %2) conj %2) frame-shapes))]
|
||||
(logjs "snap-data"
|
||||
(mapm (fn [shapes] {:x (create-coord-data shapes :x)
|
||||
:y (create-coord-data shapes :y)})
|
||||
|
@ -125,13 +144,13 @@
|
|||
|
||||
(gpt/add trans-vec snapv))))
|
||||
|
||||
(defn get-snap-points [snap-data frame-id shape-id point coord]
|
||||
(defn get-snap-points [snap-data frame-id filter-shapes point coord]
|
||||
(let [value (coord point)
|
||||
|
||||
;; Search for values within 1 pixel
|
||||
snap-matches (-> (get-in snap-data [frame-id coord])
|
||||
(range-query (- value 0.5) (+ value 0.5))
|
||||
(remove-from-snap-points #{shape-id}))
|
||||
(remove-from-snap-points filter-shapes))
|
||||
|
||||
snap-points (mapcat (fn [[v data]] (map (fn [[point _]] point) data)) snap-matches)]
|
||||
snap-points))
|
||||
|
@ -139,5 +158,5 @@
|
|||
(defn is-snapping? [snap-data frame-id shape-id point coord]
|
||||
(let [value (coord point)
|
||||
;; Search for values within 1 pixel
|
||||
snap-points (range-query (get-in snap-data [frame-id coord]) (- value 0.25) (+ value 0.25))]
|
||||
snap-points (range-query (get-in snap-data [frame-id coord]) (- value 1.0) (+ value 1.0))]
|
||||
(some (fn [[point other-shape-id]] (not (= shape-id other-shape-id))) snap-points)))
|
||||
|
|
Loading…
Add table
Reference in a new issue