mirror of
https://github.com/penpot/penpot.git
synced 2025-01-24 23:49:45 -05:00
✨ Integration of the range-tree into the application
This commit is contained in:
parent
e596555932
commit
dc97056fcf
4 changed files with 84 additions and 27 deletions
|
@ -494,9 +494,10 @@
|
||||||
name (generate-unique-name names (:name obj))
|
name (generate-unique-name names (:name obj))
|
||||||
renamed-obj (assoc obj :id id :name name)
|
renamed-obj (assoc obj :id id :name name)
|
||||||
moved-obj (geom/move renamed-obj delta)
|
moved-obj (geom/move renamed-obj delta)
|
||||||
|
frames (cp/select-frames objects)
|
||||||
frame-id (if frame-id
|
frame-id (if frame-id
|
||||||
frame-id
|
frame-id
|
||||||
(dwc/calculate-frame-overlap objects moved-obj))
|
(dwc/calculate-frame-overlap frames moved-obj))
|
||||||
|
|
||||||
parent-id (or parent-id frame-id)
|
parent-id (or parent-id frame-id)
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,8 @@
|
||||||
[uxbox.util.math :as mth]
|
[uxbox.util.math :as mth]
|
||||||
[uxbox.common.uuid :refer [zero]]
|
[uxbox.common.uuid :refer [zero]]
|
||||||
[uxbox.util.geom.shapes :as gsh]
|
[uxbox.util.geom.shapes :as gsh]
|
||||||
[uxbox.util.geom.point :as gpt]))
|
[uxbox.util.geom.point :as gpt]
|
||||||
|
[uxbox.util.range-tree :as rt]))
|
||||||
|
|
||||||
(def ^:private snap-accuracy 10)
|
(def ^:private snap-accuracy 10)
|
||||||
|
|
||||||
|
@ -73,14 +74,15 @@
|
||||||
(into #{shape-center} (-> modified-path :segments)))))
|
(into #{shape-center} (-> modified-path :segments)))))
|
||||||
|
|
||||||
(defn create-coord-data [shapes coord]
|
(defn create-coord-data [shapes coord]
|
||||||
(let [process-shape
|
(let [process-shape (fn [coord]
|
||||||
(fn [coord]
|
(fn [shape]
|
||||||
(fn [shape]
|
(let [points (shape-snap-points shape)]
|
||||||
(let [points (shape-snap-points shape)]
|
(map #(vector % (:id shape)) points))))
|
||||||
(map #(vector % (:id shape)) points))))]
|
into-tree (fn [tree [point _ :as data]]
|
||||||
|
(rt/insert tree (coord point) data))]
|
||||||
(->> shapes
|
(->> shapes
|
||||||
(mapcat (process-shape coord))
|
(mapcat (process-shape coord))
|
||||||
(group-by (comp coord first)))))
|
(reduce into-tree (rt/make-tree)))))
|
||||||
|
|
||||||
(defn initialize-snap-data
|
(defn initialize-snap-data
|
||||||
"Initialize the snap information with the current workspace information"
|
"Initialize the snap information with the current workspace information"
|
||||||
|
@ -98,13 +100,6 @@
|
||||||
:y (create-coord-data shapes :y)})
|
:y (create-coord-data shapes :y)})
|
||||||
frame-shapes)))
|
frame-shapes)))
|
||||||
|
|
||||||
(defn range-query
|
|
||||||
"Queries the snap-data within a range of values"
|
|
||||||
[snap-data from-value to-value]
|
|
||||||
(filter (fn [[value _]] (and (>= value from-value)
|
|
||||||
(<= value to-value)))
|
|
||||||
snap-data))
|
|
||||||
|
|
||||||
(defn remove-from-snap-points [snap-points ids-to-remove]
|
(defn remove-from-snap-points [snap-points ids-to-remove]
|
||||||
(->> snap-points
|
(->> snap-points
|
||||||
(map (fn [[value data]] [value (remove (comp ids-to-remove second) data)]))
|
(map (fn [[value data]] [value (remove (comp ids-to-remove second) data)]))
|
||||||
|
@ -119,7 +114,7 @@
|
||||||
;; This gives a list of [value [[point1 uuid1] [point2 uuid2] ...] we need to remove
|
;; This gives a list of [value [[point1 uuid1] [point2 uuid2] ...] we need to remove
|
||||||
;; the shapes in filter shapes
|
;; the shapes in filter shapes
|
||||||
candidates (-> snap-data
|
candidates (-> snap-data
|
||||||
(range-query (- coord-value snap-accuracy) (+ coord-value snap-accuracy))
|
(rt/range-query (- coord-value snap-accuracy) (+ coord-value snap-accuracy))
|
||||||
(remove-from-snap-points filter-shapes))
|
(remove-from-snap-points filter-shapes))
|
||||||
|
|
||||||
;; Now return with the distance and the from-to pair that we'll return if this is the chosen
|
;; Now return with the distance and the from-to pair that we'll return if this is the chosen
|
||||||
|
@ -187,8 +182,8 @@
|
||||||
|
|
||||||
;; Search for values within 1 pixel
|
;; Search for values within 1 pixel
|
||||||
snap-matches (-> (get-in snap-data [frame-id coord])
|
snap-matches (-> (get-in snap-data [frame-id coord])
|
||||||
(range-query (- value 1) (+ value 1))
|
(rt/range-query (- value 1) (+ value 1))
|
||||||
(remove-from-snap-points filter-shapes))
|
(remove-from-snap-points filter-shapes))
|
||||||
|
|
||||||
snap-points (mapcat (fn [[v data]] (map (fn [[point _]] point) data)) snap-matches)]
|
snap-points (mapcat (fn [[v data]] (map (fn [[point _]] point) data)) snap-matches)]
|
||||||
snap-points))
|
snap-points))
|
||||||
|
@ -196,5 +191,5 @@
|
||||||
(defn is-snapping? [snap-data frame-id shape-id point coord]
|
(defn is-snapping? [snap-data frame-id shape-id point coord]
|
||||||
(let [value (coord point)
|
(let [value (coord point)
|
||||||
;; Search for values within 1 pixel
|
;; Search for values within 1 pixel
|
||||||
snap-points (range-query (get-in snap-data [frame-id coord]) (- value 1.0) (+ value 1.0))]
|
snap-points (rt/range-query (get-in snap-data [frame-id coord]) (- value 1.0) (+ value 1.0))]
|
||||||
(some (fn [[point other-shape-id]] (not (= shape-id other-shape-id))) snap-points)))
|
(some (fn [[point other-shape-id]] (not (= shape-id other-shape-id))) snap-points)))
|
||||||
|
|
|
@ -24,8 +24,8 @@ goog.scope(function() {
|
||||||
const nil = cljs.core.nil;
|
const nil = cljs.core.nil;
|
||||||
|
|
||||||
const Color = {
|
const Color = {
|
||||||
RED: "RED",
|
RED: 1,
|
||||||
BLACK: "BLACK"
|
BLACK: 2
|
||||||
}
|
}
|
||||||
|
|
||||||
class Node {
|
class Node {
|
||||||
|
@ -97,6 +97,12 @@ goog.scope(function() {
|
||||||
isEmpty() {
|
isEmpty() {
|
||||||
return this.root === null;
|
return this.root === null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
const result = [];
|
||||||
|
recToString(this.root, result);
|
||||||
|
return result.join(", ");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tree implementation functions
|
// Tree implementation functions
|
||||||
|
@ -237,7 +243,8 @@ goog.scope(function() {
|
||||||
recRangeQuery(branch.left, fromValue, toValue, result);
|
recRangeQuery(branch.left, fromValue, toValue, result);
|
||||||
}
|
}
|
||||||
if (fromValue <= branch.value && toValue >= branch.value) {
|
if (fromValue <= branch.value && toValue >= branch.value) {
|
||||||
Array.prototype.push.apply(result, branch.data);
|
// Array.prototype.push.apply(result, branch.data);
|
||||||
|
result.push(vec([branch.value, vec(branch.data)]))
|
||||||
}
|
}
|
||||||
if (toValue > branch.value) {
|
if (toValue > branch.value) {
|
||||||
recRangeQuery(branch.right, fromValue, toValue, result);
|
recRangeQuery(branch.right, fromValue, toValue, result);
|
||||||
|
@ -317,6 +324,19 @@ goog.scope(function() {
|
||||||
return 1 + curHeight;
|
return 1 + curHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This will return the string representation. We don't care about internal structure
|
||||||
|
// only the data
|
||||||
|
function recToString(branch, result) {
|
||||||
|
if (branch === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
recToString(branch.left, result);
|
||||||
|
result.push(`${branch.value}: [${branch.data.join(", ")}]`)
|
||||||
|
recToString(branch.right, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function prints the tree structure, not the data
|
||||||
function printTree(tree) {
|
function printTree(tree) {
|
||||||
if (!tree) {
|
if (!tree) {
|
||||||
return "";
|
return "";
|
||||||
|
|
|
@ -85,10 +85,18 @@
|
||||||
(t/is (= (rt/get tree 175) [:g]))))
|
(t/is (= (rt/get tree 175) [:g]))))
|
||||||
|
|
||||||
(t/testing "Adds a bunch of nodes and then delete. The tree should be empty"
|
(t/testing "Adds a bunch of nodes and then delete. The tree should be empty"
|
||||||
|
;; Try an increase range
|
||||||
(let [size 10000
|
(let [size 10000
|
||||||
tree (rt/make-tree)
|
tree (rt/make-tree)
|
||||||
tree (reduce #(rt/insert %1 %2 :x) tree (range 0 (dec size)))
|
tree (reduce #(rt/insert %1 %2 :x) tree (range 0 (dec size)))
|
||||||
tree (reduce #(rt/remove %1 %2 :x) tree (range 0 (dec size)))]
|
tree (reduce #(rt/remove %1 %2 :x) tree (range 0 (dec size)))]
|
||||||
|
(t/is (rt/empty? tree)))
|
||||||
|
|
||||||
|
;; Try a decreasing range
|
||||||
|
(let [size 10000
|
||||||
|
tree (rt/make-tree)
|
||||||
|
tree (reduce #(rt/insert %1 %2 :x) tree (range (dec size) -1 -1))
|
||||||
|
tree (reduce #(rt/remove %1 %2 :x) tree (range (dec size) -1 -1))]
|
||||||
(t/is (rt/empty? tree)))))
|
(t/is (rt/empty? tree)))))
|
||||||
|
|
||||||
(t/deftest test-update-elements
|
(t/deftest test-update-elements
|
||||||
|
@ -126,11 +134,33 @@
|
||||||
(rt/insert 175 :i)
|
(rt/insert 175 :i)
|
||||||
(rt/insert 200 :j)
|
(rt/insert 200 :j)
|
||||||
(rt/insert 200 :k))]
|
(rt/insert 200 :k))]
|
||||||
(t/is (= (rt/range-query tree 0 200) [:a :b :c :d :e :f :g :h :i :j :k]))
|
(t/is (= (rt/range-query tree 0 200)
|
||||||
(t/is (= (rt/range-query tree 0 100) [:a :b :c :d :e :f]))
|
[[0 [:a]]
|
||||||
(t/is (= (rt/range-query tree 100 200) [:e :f :g :h :i :j :k]))
|
[25 [:b]]
|
||||||
(t/is (= (rt/range-query tree 10 60) [:b :c]))
|
[50 [:c]]
|
||||||
(t/is (= (rt/range-query tree 199.5 200.5) [:j :k]))))
|
[75 [:d]]
|
||||||
|
[100 [:e :f]]
|
||||||
|
[125 [:g]]
|
||||||
|
[150 [:h]]
|
||||||
|
[175 [:i]]
|
||||||
|
[200 [:j :k]]]))
|
||||||
|
(t/is (= (rt/range-query tree 0 100)
|
||||||
|
[[0 [:a]]
|
||||||
|
[25 [:b]]
|
||||||
|
[50 [:c]]
|
||||||
|
[75 [:d]]
|
||||||
|
[100 [:e :f]]]))
|
||||||
|
(t/is (= (rt/range-query tree 100 200)
|
||||||
|
[[100 [:e :f]]
|
||||||
|
[125 [:g]]
|
||||||
|
[150 [:h]]
|
||||||
|
[175 [:i]]
|
||||||
|
[200 [:j :k]]]))
|
||||||
|
(t/is (= (rt/range-query tree 10 60)
|
||||||
|
[[25 [:b]]
|
||||||
|
[50 [:c]]]))
|
||||||
|
(t/is (= (rt/range-query tree 199.5 200.5)
|
||||||
|
[[200 [:j :k]]]))))
|
||||||
|
|
||||||
(t/testing "Empty range query"
|
(t/testing "Empty range query"
|
||||||
(let [tree (-> (rt/make-tree)
|
(let [tree (-> (rt/make-tree)
|
||||||
|
@ -151,3 +181,14 @@
|
||||||
tree (reduce #(rt/insert %1 %2 :x) (rt/make-tree) (range 0 (dec size)))
|
tree (reduce #(rt/insert %1 %2 :x) (rt/make-tree) (range 0 (dec size)))
|
||||||
height (rt/height tree)]
|
height (rt/height tree)]
|
||||||
(t/is (= height (inc (js/Math.log2 size)))))))
|
(t/is (= height (inc (js/Math.log2 size)))))))
|
||||||
|
|
||||||
|
(t/deftest test-to-string
|
||||||
|
(t/testing "Creates a tree and prints it"
|
||||||
|
(let [tree (-> (rt/make-tree)
|
||||||
|
(rt/insert 50 :a)
|
||||||
|
(rt/insert 25 :b)
|
||||||
|
(rt/insert 25 :c)
|
||||||
|
(rt/insert 100 :d)
|
||||||
|
(rt/insert 75 :e))
|
||||||
|
result (str tree)]
|
||||||
|
(t/is (= result "25: [:b, :c], 50: [:a], 75: [:e], 100: [:d]")))))
|
||||||
|
|
Loading…
Add table
Reference in a new issue