mirror of
https://github.com/penpot/penpot.git
synced 2025-03-12 15:51:37 -05:00
🎉 Implement actual object alignment
This commit is contained in:
parent
b798f7a988
commit
8e573abf9e
3 changed files with 85 additions and 22 deletions
|
@ -1447,21 +1447,37 @@
|
|||
|
||||
;; --- Shape / Selection Alignment
|
||||
|
||||
(defn initial-selection-align
|
||||
"Align the selection of shapes."
|
||||
[ids]
|
||||
(us/verify ::set-of-uuid ids)
|
||||
(ptk/reify ::initialize-shapes-align-in-bulk
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
#_(let [shapes-by-id (get-in state [:workspace-data :objects])
|
||||
shapes (mapv #(get shapes-by-id %) ids)
|
||||
sshape (geom/shapes->rect-shape shapes)
|
||||
point (gpt/point (:x1 sshape)
|
||||
(:y1 sshape))]
|
||||
(->> (uwrk/align-point point)
|
||||
(rx/map (fn [{:keys [x y] :as pt}]
|
||||
(apply-displacement-in-bulk ids (gpt/subtract pt point)))))))))
|
||||
(declare align-object-to-frame)
|
||||
(declare align-objects-list)
|
||||
|
||||
(defn align-objects
|
||||
[axis]
|
||||
(us/verify ::geom/axis axis)
|
||||
(ptk/reify :align-objects
|
||||
IBatchedChange
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [page-id (::page-id state)
|
||||
objects (get-in state [:workspace-data page-id :objects])
|
||||
selected (get-in state [:workspace-local :selected])
|
||||
moved-objs (if (= 1 (count selected))
|
||||
[(align-object-to-frame objects (first selected) axis)]
|
||||
(align-objects-list objects selected axis))
|
||||
updated-objs (merge objects (d/index-by :id moved-objs))]
|
||||
(assoc-in state [:workspace-data page-id :objects] updated-objs)))))
|
||||
|
||||
(defn align-object-to-frame
|
||||
[objects object-id axis]
|
||||
(let [object (get objects object-id)
|
||||
frame (get objects (:frame-id object))]
|
||||
(geom/align-to-rect object frame axis)))
|
||||
|
||||
(defn align-objects-list
|
||||
[objects selected axis]
|
||||
(let [selected-objs (map #(get objects %) selected)
|
||||
rect (geom/selection-rect selected-objs)]
|
||||
(map #(geom/align-to-rect % rect axis) selected-objs)))
|
||||
|
||||
|
||||
;; --- Temportal displacement for Shape / Selection
|
||||
|
||||
|
|
|
@ -525,12 +525,12 @@
|
|||
(gmt/matrix? displacement-modifier)
|
||||
(transform displacement-modifier)))
|
||||
|
||||
;; NOTE: we need applu `shape->rect-shape` 3 times because we need to
|
||||
;; NOTE: we need apply `shape->rect-shape` 3 times because we need to
|
||||
;; update the x1 x2 y1 y2 attributes on each step; this is because
|
||||
;; some transform functions still uses that attributes. WE NEED TO
|
||||
;; REFACTOR this, and remove any usage of the old xN yN attributes.
|
||||
|
||||
(def ^:private xf-resolve-shapes
|
||||
(def ^:private xf-resolve-shape
|
||||
(comp (map shape->rect-shape)
|
||||
(map resolve-modifier)
|
||||
(map shape->rect-shape)
|
||||
|
@ -541,7 +541,7 @@
|
|||
"Returns a rect that contains all the shapes and is aware of the
|
||||
rotation of each shape. Mainly used for multiple selection."
|
||||
[shapes]
|
||||
(let [shapes (into [] xf-resolve-shapes shapes)
|
||||
(let [shapes (into [] xf-resolve-shape shapes)
|
||||
minx (transduce (map :x1) min shapes)
|
||||
miny (transduce (map :y1) min shapes)
|
||||
maxx (transduce (map :x2) max shapes)
|
||||
|
@ -564,6 +564,52 @@
|
|||
[shape {:keys [x y] :as frame}]
|
||||
(move shape (gpt/point (+ x) (+ y))))
|
||||
|
||||
|
||||
;; --- Alignment
|
||||
|
||||
(s/def ::axis #{:hleft :hcenter :hright :vtop :vcenter :vbottom})
|
||||
|
||||
(declare calc-align-pos)
|
||||
|
||||
(defn align-to-rect
|
||||
"Move the shape so that it is aligned with the given rectangle
|
||||
in the given axis. Take account the form of the shape and the
|
||||
possible rotation. What is aligned is the rectangle that wraps
|
||||
the shape with the given rectangle."
|
||||
[shape rect axis]
|
||||
(let [wrapper-rect (selection-rect [shape])
|
||||
align-pos (calc-align-pos wrapper-rect rect axis)
|
||||
delta {:x (- (:x align-pos) (:x wrapper-rect))
|
||||
:y (- (:y align-pos) (:y wrapper-rect))}]
|
||||
(move shape delta)))
|
||||
|
||||
(defn calc-align-pos
|
||||
[wrapper-rect rect axis]
|
||||
(case axis
|
||||
:hleft (let [left (:x rect)]
|
||||
{:x left
|
||||
:y (:y wrapper-rect)})
|
||||
|
||||
:hcenter (let [center (+ (:x rect) (/ (:width rect) 2))]
|
||||
{:x (- center (/ (:width wrapper-rect) 2))
|
||||
:y (:y wrapper-rect)})
|
||||
|
||||
:hright (let [right (+ (:x rect) (:width rect))]
|
||||
{:x (- right (:width wrapper-rect))
|
||||
:y (:y wrapper-rect)})
|
||||
|
||||
:vtop (let [top (:y rect)]
|
||||
{:x (:x wrapper-rect)
|
||||
:y top})
|
||||
|
||||
:vcenter (let [center (+ (:y rect) (/ (:height rect) 2))]
|
||||
{:x (:x wrapper-rect)
|
||||
:y (- center (/ (:height wrapper-rect) 2))})
|
||||
|
||||
:vbottom (let [bottom (+ (:y rect) (:height rect))]
|
||||
{:x (:x wrapper-rect)
|
||||
:y (- bottom (:height wrapper-rect))})))
|
||||
|
||||
;; --- Helpers
|
||||
|
||||
(defn contained-in?
|
||||
|
|
|
@ -10,13 +10,14 @@
|
|||
[rumext.alpha :as mf]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.data.workspace :as dw]
|
||||
[uxbox.util.uuid :as uuid]))
|
||||
|
||||
(mf/defc align-options
|
||||
[]
|
||||
(let [data (mf/deref refs/workspace-data)
|
||||
objects (:objects data)
|
||||
selected (mf/deref refs/selected-shapes)
|
||||
(let [selected (mf/deref refs/selected-shapes)
|
||||
objects (deref refs/objects) ; don't need to watch objects, only read the value
|
||||
|
||||
disabled (cond
|
||||
(empty? selected) true
|
||||
|
@ -25,7 +26,7 @@
|
|||
(= uuid/zero (:frame-id (get objects (first selected)))))
|
||||
|
||||
on-align-button-clicked
|
||||
(fn [axis] (when-not disabled (println axis)))]
|
||||
(fn [axis] (when-not disabled (st/emit! (dw/align-objects axis))))]
|
||||
|
||||
[:div.align-options
|
||||
[:div.align-button {:class (when disabled "disabled")
|
||||
|
|
Loading…
Add table
Reference in a new issue