diff --git a/frontend/src/uxbox/main/data/workspace.cljs b/frontend/src/uxbox/main/data/workspace.cljs index fa4b87005..80dab0176 100644 --- a/frontend/src/uxbox/main/data/workspace.cljs +++ b/frontend/src/uxbox/main/data/workspace.cljs @@ -494,9 +494,10 @@ name (generate-unique-name names (:name obj)) renamed-obj (assoc obj :id id :name name) moved-obj (geom/move renamed-obj delta) + frames (cp/select-frames objects) frame-id (if 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) diff --git a/frontend/src/uxbox/util/geom/snap.cljs b/frontend/src/uxbox/util/geom/snap.cljs index b61e2b056..0a882d093 100644 --- a/frontend/src/uxbox/util/geom/snap.cljs +++ b/frontend/src/uxbox/util/geom/snap.cljs @@ -14,7 +14,8 @@ [uxbox.util.math :as mth] [uxbox.common.uuid :refer [zero]] [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) @@ -73,14 +74,15 @@ (into #{shape-center} (-> modified-path :segments))))) (defn create-coord-data [shapes coord] - (let [process-shape - (fn [coord] - (fn [shape] - (let [points (shape-snap-points shape)] - (map #(vector % (:id shape)) points))))] + (let [process-shape (fn [coord] + (fn [shape] + (let [points (shape-snap-points shape)] + (map #(vector % (:id shape)) points)))) + into-tree (fn [tree [point _ :as data]] + (rt/insert tree (coord point) data))] (->> shapes (mapcat (process-shape coord)) - (group-by (comp coord first))))) + (reduce into-tree (rt/make-tree))))) (defn initialize-snap-data "Initialize the snap information with the current workspace information" @@ -98,13 +100,6 @@ :y (create-coord-data shapes :y)}) 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] (->> snap-points (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 ;; the shapes in filter shapes 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)) ;; 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 snap-matches (-> (get-in snap-data [frame-id coord]) - (range-query (- value 1) (+ value 1)) - (remove-from-snap-points filter-shapes)) + (rt/range-query (- value 1) (+ value 1)) + (remove-from-snap-points filter-shapes)) snap-points (mapcat (fn [[v data]] (map (fn [[point _]] point) data)) snap-matches)] snap-points)) @@ -196,5 +191,5 @@ (defn is-snapping? [snap-data frame-id shape-id point coord] (let [value (coord point) ;; 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))) diff --git a/frontend/src/uxbox/util/range_tree.js b/frontend/src/uxbox/util/range_tree.js index 81626c5f3..5910e3492 100644 --- a/frontend/src/uxbox/util/range_tree.js +++ b/frontend/src/uxbox/util/range_tree.js @@ -24,8 +24,8 @@ goog.scope(function() { const nil = cljs.core.nil; const Color = { - RED: "RED", - BLACK: "BLACK" + RED: 1, + BLACK: 2 } class Node { @@ -97,6 +97,12 @@ goog.scope(function() { isEmpty() { return this.root === null; } + + toString() { + const result = []; + recToString(this.root, result); + return result.join(", "); + } } // Tree implementation functions @@ -237,7 +243,8 @@ goog.scope(function() { recRangeQuery(branch.left, fromValue, toValue, result); } 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) { recRangeQuery(branch.right, fromValue, toValue, result); @@ -317,6 +324,19 @@ goog.scope(function() { 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) { if (!tree) { return ""; diff --git a/frontend/tests/uxbox/test_util_range_tree.cljs b/frontend/tests/uxbox/test_util_range_tree.cljs index eec8fd187..56769b677 100644 --- a/frontend/tests/uxbox/test_util_range_tree.cljs +++ b/frontend/tests/uxbox/test_util_range_tree.cljs @@ -85,10 +85,18 @@ (t/is (= (rt/get tree 175) [:g])))) (t/testing "Adds a bunch of nodes and then delete. The tree should be empty" + ;; Try an increase range (let [size 10000 tree (rt/make-tree) tree (reduce #(rt/insert %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/deftest test-update-elements @@ -126,11 +134,33 @@ (rt/insert 175 :i) (rt/insert 200 :j) (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 100) [:a :b :c :d :e :f])) - (t/is (= (rt/range-query tree 100 200) [:e :f :g :h :i :j :k])) - (t/is (= (rt/range-query tree 10 60) [:b :c])) - (t/is (= (rt/range-query tree 199.5 200.5) [:j :k])))) + (t/is (= (rt/range-query tree 0 200) + [[0 [:a]] + [25 [:b]] + [50 [:c]] + [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" (let [tree (-> (rt/make-tree) @@ -151,3 +181,14 @@ tree (reduce #(rt/insert %1 %2 :x) (rt/make-tree) (range 0 (dec size))) height (rt/height tree)] (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]")))))