0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-03-13 00:01:51 -05:00

🎉 Implement actual object alignment

This commit is contained in:
Andrés Moya 2020-04-07 15:27:58 +02:00
parent b798f7a988
commit 8e573abf9e
3 changed files with 85 additions and 22 deletions

View file

@ -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

View file

@ -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?

View file

@ -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")