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:
parent
b798f7a988
commit
8e573abf9e
3 changed files with 85 additions and 22 deletions
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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?
|
||||||
|
|
|
@ -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")
|
||||||
|
|
Loading…
Add table
Reference in a new issue