mirror of
https://github.com/penpot/penpot.git
synced 2025-07-26 01:57:23 +02:00
♻️ Redone the snap calculation and added guides
This commit is contained in:
parent
0766938f98
commit
64e7cad292
11 changed files with 726 additions and 185 deletions
|
@ -28,3 +28,12 @@
|
|||
(case (:type shape)
|
||||
:frame (-> shape :selrect frame-snap-points)
|
||||
(into #{(gsh/center-shape shape)} (:points shape)))))
|
||||
|
||||
(defn guide-snap-points
|
||||
[guide]
|
||||
|
||||
;; TODO: The line will be displayed from the position to the axis. Maybe
|
||||
;; revisit this
|
||||
(if (= :x (:axis guide))
|
||||
#{(gpt/point (:position guide) 0)}
|
||||
#{(gpt/point 0 (:position guide))}))
|
||||
|
|
244
frontend/src/app/util/snap_data.cljs
Normal file
244
frontend/src/app/util/snap_data.cljs
Normal file
|
@ -0,0 +1,244 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) UXBOX Labs SL
|
||||
|
||||
(ns app.util.snap-data
|
||||
"Data structure that holds and retrieves the data to make the snaps. Internaly
|
||||
is implemented with a balanced binary tree that queries by range.
|
||||
https://en.wikipedia.org/wiki/Range_tree"
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.pages :as cp]
|
||||
[app.common.pages.diff :as diff]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.util.geom.grid :as gg]
|
||||
[app.util.geom.snap-points :as snap]
|
||||
[app.util.range-tree :as rt]))
|
||||
|
||||
(def snap-attrs [:frame-id :x :y :width :height :hidden :selrect :grids])
|
||||
|
||||
;; PRIVATE FUNCTIONS
|
||||
|
||||
(defn make-insert-tree-data
|
||||
[shape-data axis]
|
||||
(fn [tree]
|
||||
(let [tree (or tree (rt/make-tree))]
|
||||
(as-> tree $
|
||||
(reduce (fn [tree data]
|
||||
(rt/insert tree (get-in data [:pt axis]) data))
|
||||
$ shape-data)))))
|
||||
|
||||
(defn make-delete-tree-data
|
||||
[shape-data axis]
|
||||
(fn [tree]
|
||||
(let [tree (or tree (rt/make-tree))]
|
||||
(as-> tree $
|
||||
(reduce (fn [tree data]
|
||||
(rt/remove tree (get-in data [:pt axis]) data))
|
||||
$ shape-data)))))
|
||||
|
||||
(defn add-root-frame
|
||||
[page-data]
|
||||
(let [frame-id uuid/zero]
|
||||
|
||||
(-> page-data
|
||||
(assoc-in [frame-id :x] (rt/make-tree))
|
||||
(assoc-in [frame-id :y] (rt/make-tree)))))
|
||||
|
||||
(defn add-frame
|
||||
[page-data frame]
|
||||
(let [frame-id (:id frame)
|
||||
parent-id (:parent-id frame)
|
||||
frame-data (->> (snap/shape-snap-points frame)
|
||||
(map #(hash-map :type :shape
|
||||
:id frame-id
|
||||
:pt %)))
|
||||
|
||||
grid-x-data (->> (gg/grid-snap-points frame :x)
|
||||
(map #(hash-map :type :grid-x
|
||||
:id frame-id
|
||||
:pt %)))
|
||||
|
||||
grid-y-data (->> (gg/grid-snap-points frame :y)
|
||||
(map #(hash-map :type :grid-y
|
||||
:id frame-id
|
||||
:pt %)))]
|
||||
|
||||
(-> page-data
|
||||
;; Update root frame information
|
||||
(assoc-in [uuid/zero :objects-data frame-id] frame-data)
|
||||
(update-in [parent-id :x] (make-insert-tree-data frame-data :x))
|
||||
(update-in [parent-id :y] (make-insert-tree-data frame-data :y))
|
||||
|
||||
;; Update frame information
|
||||
(assoc-in [frame-id :objects-data frame-id] (d/concat-vec frame-data grid-x-data grid-y-data))
|
||||
(update-in [frame-id :x] #(or % (rt/make-tree)))
|
||||
(update-in [frame-id :y] #(or % (rt/make-tree)))
|
||||
(update-in [frame-id :x] (make-insert-tree-data (d/concat-vec frame-data grid-x-data) :x))
|
||||
(update-in [frame-id :y] (make-insert-tree-data (d/concat-vec frame-data grid-y-data) :y)))))
|
||||
|
||||
(defn add-shape
|
||||
[page-data shape]
|
||||
(let [frame-id (:frame-id shape)
|
||||
snap-points (snap/shape-snap-points shape)
|
||||
shape-data (->> snap-points
|
||||
(mapv #(hash-map
|
||||
:type :shape
|
||||
:id (:id shape)
|
||||
:pt %)))]
|
||||
(-> page-data
|
||||
(assoc-in [frame-id :objects-data (:id shape)] shape-data)
|
||||
(update-in [frame-id :x] (make-insert-tree-data shape-data :x))
|
||||
(update-in [frame-id :y] (make-insert-tree-data shape-data :y)))))
|
||||
|
||||
|
||||
(defn add-guide
|
||||
[page-data guide]
|
||||
|
||||
(let [guide-data (->> (snap/guide-snap-points guide)
|
||||
(mapv #(hash-map
|
||||
:type :guide
|
||||
:id (:id guide)
|
||||
:pt %)))]
|
||||
|
||||
(if-let [frame-id (:frame-id guide)]
|
||||
;; Guide inside frame, we add the information only on that frame
|
||||
(-> page-data
|
||||
(assoc-in [frame-id :objects-data (:id guide)] guide-data)
|
||||
(update-in [frame-id (:axis guide)] (make-insert-tree-data guide-data (:axis guide))))
|
||||
|
||||
;; Guide outside the frame. We add the information in the global guides data
|
||||
(-> page-data
|
||||
(assoc-in [:guides :objects-data (:id guide)] [guide-data])
|
||||
(update-in [:guides (:axis guide)] (make-insert-tree-data guide-data (:axis guide)))))))
|
||||
|
||||
(defn remove-frame
|
||||
[page-data frame]
|
||||
(let [frame-id (:id frame)
|
||||
root-data (get-in page-data [uuid/zero :objects-data frame-id])]
|
||||
(-> page-data
|
||||
(d/dissoc-in [uuid/zero :objects-data frame-id])
|
||||
(update-in [uuid/zero :x] (make-delete-tree-data root-data :x))
|
||||
(update-in [uuid/zero :y] (make-delete-tree-data root-data :y))
|
||||
(dissoc frame-id))))
|
||||
|
||||
(defn remove-shape
|
||||
[page-data shape]
|
||||
|
||||
(let [frame-id (:frame-id shape)
|
||||
shape-data (get-in page-data [frame-id :objects-data (:id shape)])]
|
||||
(-> page-data
|
||||
(d/dissoc-in [frame-id :objects-data (:id shape)])
|
||||
(update-in [frame-id :x] (make-delete-tree-data shape-data :x))
|
||||
(update-in [frame-id :y] (make-delete-tree-data shape-data :y)))))
|
||||
|
||||
(defn remove-guide
|
||||
[page-data guide]
|
||||
(if-let [frame-id (:frame-id guide)]
|
||||
(let [guide-data (get-in page-data [frame-id :objects-data (:id guide)])]
|
||||
(-> page-data
|
||||
(d/dissoc-in [frame-id :objects-data (:id guide)])
|
||||
(update-in [frame-id (:axis guide)] (make-delete-tree-data guide-data (:axis guide)))))
|
||||
|
||||
;; Guide outside the frame. We add the information in the global guides data
|
||||
(let [guide-data (get-in page-data [:guides :objects-data (:id guide)])]
|
||||
(-> page-data
|
||||
(d/dissoc-in [:guides :objects-data (:id guide)])
|
||||
(update-in [:guides (:axis guide)] (make-delete-tree-data guide-data (:axis guide)))))))
|
||||
|
||||
(defn update-frame
|
||||
[page-data [_ new-frame]]
|
||||
(let [frame-id (:id new-frame)
|
||||
root-data (get-in page-data [uuid/zero :objects-data frame-id])
|
||||
frame-data (get-in page-data [frame-id :objects-data frame-id])]
|
||||
(-> page-data
|
||||
(update-in [uuid/zero :x] (make-delete-tree-data root-data :x))
|
||||
(update-in [uuid/zero :y] (make-delete-tree-data root-data :y))
|
||||
(update-in [frame-id :x] (make-delete-tree-data frame-data :x))
|
||||
(update-in [frame-id :y] (make-delete-tree-data frame-data :y))
|
||||
(add-frame new-frame))))
|
||||
|
||||
(defn update-shape
|
||||
[page-data [old-shape new-shape]]
|
||||
(-> page-data
|
||||
(remove-shape old-shape)
|
||||
(add-shape new-shape)))
|
||||
|
||||
(defn update-guide
|
||||
[page-data [old-guide new-guide]]
|
||||
(-> page-data
|
||||
(remove-guide old-guide)
|
||||
(add-guide new-guide)))
|
||||
|
||||
;; PUBLIC API
|
||||
(defn make-snap-data
|
||||
"Creates an empty snap index"
|
||||
[]
|
||||
{})
|
||||
|
||||
(defn add-page
|
||||
"Adds page information"
|
||||
[snap-data {:keys [objects options] :as page}]
|
||||
|
||||
(let [frames (cp/select-frames objects)
|
||||
shapes (cp/select-objects #(not= :frame (:type %)) page)
|
||||
guides (vals (:guides options))
|
||||
|
||||
page-data
|
||||
(as-> {} $
|
||||
(add-root-frame $)
|
||||
(reduce add-frame $ frames)
|
||||
(reduce add-shape $ shapes)
|
||||
(reduce add-guide $ guides))]
|
||||
(assoc snap-data (:id page) page-data)))
|
||||
|
||||
(defn update-page
|
||||
"Updates a previously inserted page with new data"
|
||||
[snap-data old-page page]
|
||||
|
||||
(if (contains? snap-data (:id page))
|
||||
;; Update page
|
||||
(update snap-data (:id page)
|
||||
(fn [page-data]
|
||||
(let [{:keys [change-frame-shapes
|
||||
change-frame-guides
|
||||
removed-frames
|
||||
removed-shapes
|
||||
removed-guides
|
||||
updated-frames
|
||||
updated-shapes
|
||||
updated-guides
|
||||
new-frames
|
||||
new-shapes
|
||||
new-guides]}
|
||||
(diff/calculate-page-diff old-page page snap-attrs)]
|
||||
|
||||
(as-> page-data $
|
||||
(reduce update-shape $ change-frame-shapes)
|
||||
(reduce remove-frame $ removed-frames)
|
||||
(reduce remove-shape $ removed-shapes)
|
||||
(reduce update-frame $ updated-frames)
|
||||
(reduce update-shape $ updated-shapes)
|
||||
(reduce add-frame $ new-frames)
|
||||
(reduce add-shape $ new-shapes)
|
||||
(reduce update-guide $ change-frame-guides)
|
||||
(reduce remove-guide $ removed-guides)
|
||||
(reduce update-guide $ updated-guides)
|
||||
(reduce add-guide $ new-guides)))))
|
||||
|
||||
;; Page doesn't exist, we create a new entry
|
||||
(add-page snap-data page)))
|
||||
|
||||
(defn query
|
||||
[snap-data page-id frame-id axis [from to]]
|
||||
|
||||
(d/concat-vec
|
||||
(-> snap-data
|
||||
(get-in [page-id frame-id axis])
|
||||
(rt/range-query from to))
|
||||
|
||||
(-> snap-data
|
||||
(get-in [page-id :guides axis])
|
||||
(rt/range-query from to))))
|
Loading…
Add table
Add a link
Reference in a new issue