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 ;; --- Shape / Selection Alignment
(defn initial-selection-align (declare align-object-to-frame)
"Align the selection of shapes." (declare align-objects-list)
[ids]
(us/verify ::set-of-uuid ids) (defn align-objects
(ptk/reify ::initialize-shapes-align-in-bulk [axis]
ptk/WatchEvent (us/verify ::geom/axis axis)
(watch [_ state stream] (ptk/reify :align-objects
#_(let [shapes-by-id (get-in state [:workspace-data :objects]) IBatchedChange
shapes (mapv #(get shapes-by-id %) ids) ptk/UpdateEvent
sshape (geom/shapes->rect-shape shapes) (update [_ state]
point (gpt/point (:x1 sshape) (let [page-id (::page-id state)
(:y1 sshape))] objects (get-in state [:workspace-data page-id :objects])
(->> (uwrk/align-point point) selected (get-in state [:workspace-local :selected])
(rx/map (fn [{:keys [x y] :as pt}] moved-objs (if (= 1 (count selected))
(apply-displacement-in-bulk ids (gpt/subtract pt point))))))))) [(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 ;; --- Temportal displacement for Shape / Selection

View file

@ -525,12 +525,12 @@
(gmt/matrix? displacement-modifier) (gmt/matrix? displacement-modifier)
(transform 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 ;; update the x1 x2 y1 y2 attributes on each step; this is because
;; some transform functions still uses that attributes. WE NEED TO ;; some transform functions still uses that attributes. WE NEED TO
;; REFACTOR this, and remove any usage of the old xN yN attributes. ;; 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) (comp (map shape->rect-shape)
(map resolve-modifier) (map resolve-modifier)
(map shape->rect-shape) (map shape->rect-shape)
@ -541,7 +541,7 @@
"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
rotation of each shape. Mainly used for multiple selection." rotation of each shape. Mainly used for multiple selection."
[shapes] [shapes]
(let [shapes (into [] xf-resolve-shapes shapes) (let [shapes (into [] xf-resolve-shape shapes)
minx (transduce (map :x1) min shapes) minx (transduce (map :x1) min shapes)
miny (transduce (map :y1) min shapes) miny (transduce (map :y1) min shapes)
maxx (transduce (map :x2) max shapes) maxx (transduce (map :x2) max shapes)
@ -564,6 +564,52 @@
[shape {:keys [x y] :as frame}] [shape {:keys [x y] :as frame}]
(move shape (gpt/point (+ x) (+ y)))) (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 ;; --- Helpers
(defn contained-in? (defn contained-in?

View file

@ -10,13 +10,14 @@
[rumext.alpha :as mf] [rumext.alpha :as mf]
[uxbox.builtins.icons :as i] [uxbox.builtins.icons :as i]
[uxbox.main.refs :as refs] [uxbox.main.refs :as refs]
[uxbox.main.store :as st]
[uxbox.main.data.workspace :as dw]
[uxbox.util.uuid :as uuid])) [uxbox.util.uuid :as uuid]))
(mf/defc align-options (mf/defc align-options
[] []
(let [data (mf/deref refs/workspace-data) (let [selected (mf/deref refs/selected-shapes)
objects (:objects data) objects (deref refs/objects) ; don't need to watch objects, only read the value
selected (mf/deref refs/selected-shapes)
disabled (cond disabled (cond
(empty? selected) true (empty? selected) true
@ -25,7 +26,7 @@
(= uuid/zero (:frame-id (get objects (first selected))))) (= uuid/zero (:frame-id (get objects (first selected)))))
on-align-button-clicked 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-options
[:div.align-button {:class (when disabled "disabled") [:div.align-button {:class (when disabled "disabled")