From 8e573abf9eaa9b3d0eac897cef807b4c0f82648a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Tue, 7 Apr 2020 15:27:58 +0200 Subject: [PATCH] :tada: Implement actual object alignment --- frontend/src/uxbox/main/data/workspace.cljs | 46 ++++++++++------ frontend/src/uxbox/main/geom.cljs | 52 +++++++++++++++++-- .../main/ui/workspace/sidebar/align.cljs | 9 ++-- 3 files changed, 85 insertions(+), 22 deletions(-) diff --git a/frontend/src/uxbox/main/data/workspace.cljs b/frontend/src/uxbox/main/data/workspace.cljs index d4bedb3b2..0902e1ac3 100644 --- a/frontend/src/uxbox/main/data/workspace.cljs +++ b/frontend/src/uxbox/main/data/workspace.cljs @@ -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 diff --git a/frontend/src/uxbox/main/geom.cljs b/frontend/src/uxbox/main/geom.cljs index 0558e826b..11e318fcc 100644 --- a/frontend/src/uxbox/main/geom.cljs +++ b/frontend/src/uxbox/main/geom.cljs @@ -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? diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/align.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/align.cljs index 98543b2ea..f6e82b61f 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/align.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/align.cljs @@ -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")