diff --git a/common/app/common/pages.cljc b/common/app/common/pages.cljc index 8282cf12c..b0f311af3 100644 --- a/common/app/common/pages.cljc +++ b/common/app/common/pages.cljc @@ -67,6 +67,7 @@ ;; Indices (d/export indices/calculate-z-index) +(d/export indices/update-z-index) (d/export indices/generate-child-all-parents-index) (d/export indices/generate-child-parent-index) (d/export indices/create-mask-index) diff --git a/common/app/common/pages/indices.cljc b/common/app/common/pages/indices.cljc index d58accd00..fa3993799 100644 --- a/common/app/common/pages/indices.cljc +++ b/common/app/common/pages/indices.cljc @@ -9,43 +9,70 @@ [app.common.data :as d] [app.common.geom.shapes :as gsh] [app.common.pages.helpers :as helpers] - [app.common.uuid :as uuid])) + [app.common.uuid :as uuid] + [clojure.set :as set])) +(defn calculate-frame-z-index [z-index frame-id objects] + (let [is-frame? (fn [id] (= :frame (get-in objects [id :type]))) + frame-shapes (->> objects (vals) (filterv #(= (:frame-id %) frame-id))) + children (or (get-in objects [frame-id :shapes]) [])] + + (if (empty? children) + z-index + + (loop [current (peek children) + pending (pop children) + current-idx (count frame-shapes) + z-index z-index] + + (let [children (get-in objects [current :shapes]) + is-frame? (is-frame? current) + pending (if (not is-frame?) + (d/concat pending children) + pending)] + + (if (empty? pending) + (-> z-index + (assoc current current-idx)) + + (recur (peek pending) + (pop pending) + (dec current-idx) + (assoc z-index current current-idx)))))))) + +;; The z-index is really calculated per-frame. Every frame will have its own +;; internal z-index. To calculate the "final" z-index we add the shape z-index with +;; the z-index of its frame. This way we can update the z-index per frame without +;; the need of recalculate all the frames (defn calculate-z-index "Given a collection of shapes calculates their z-index. Greater index means is displayed over other shapes with less index." [objects] - (let [is-frame? (fn [id] (= :frame (get-in objects [id :type]))) - root-children (or (get-in objects [uuid/zero :shapes]) []) - num-frames (->> root-children (filterv is-frame?) count)] - (when-not (empty? root-children) - (loop [current (peek root-children) - pending (pop root-children) - current-idx (+ (count objects) num-frames -1) - z-index (transient {})] + (let [frames (helpers/select-frames objects) + z-index (calculate-frame-z-index {} uuid/zero objects)] + (->> frames + (map :id) + (reduce #(calculate-frame-z-index %1 %2 objects) z-index)))) - (let [children (get-in objects [current :shapes]) - assigned? (contains? z-index current) - is-frame? (is-frame? current) +(defn update-z-index + "Updates the z-index given a set of ids to change and the old and new objects + representations" + [z-index changed-ids old-objects new-objects] - pending (cond - (not is-frame?) - (d/concat pending children) + (let [old-frames (into #{} (map #(get-in old-objects [% :frame-id])) changed-ids) + new-frames (into #{} (map #(get-in new-objects [% :frame-id])) changed-ids) - (not assigned?) - (d/concat pending [current] children) + changed-frames (set/union old-frames new-frames) - :else - pending)] + frames (->> (helpers/select-frames new-objects) + (map :id) + (filter #(contains? changed-frames %))) - (if (empty? pending) - (-> (assoc! z-index current current-idx) - (persistent!)) - (recur (peek pending) - (pop pending) - (dec current-idx) - (assoc! z-index current current-idx)))))))) + z-index (calculate-frame-z-index z-index uuid/zero new-objects)] + + (->> frames + (reduce #(calculate-frame-z-index %1 %2 new-objects) z-index)))) (defn generate-child-parent-index [objects] diff --git a/frontend/src/app/util/range_tree.js b/frontend/src/app/util/range_tree.js index 64e3d3fdd..3f823a9f2 100644 --- a/frontend/src/app/util/range_tree.js +++ b/frontend/src/app/util/range_tree.js @@ -111,7 +111,7 @@ goog.scope(function() { // Tree implementation functions function isRed(branch) { - return branch !== null && branch.color === Color.RED; + return branch && branch.color === Color.RED; } // Insert recursively in the tree diff --git a/frontend/src/app/worker/selection.cljs b/frontend/src/app/worker/selection.cljs index b49c2fe93..ce62b3434 100644 --- a/frontend/src/app/worker/selection.cljs +++ b/frontend/src/app/worker/selection.cljs @@ -20,41 +20,44 @@ (defonce state (l/atom {})) -(defn- index-object - [objects z-index parents-index masks-index index obj] - (let [{:keys [x y width height]} (:selrect obj) - shape-bound #js {:x x :y y :width width :height height} +(defn index-shape + [objects parents-index masks-index] + (fn [index shape] + (let [{:keys [x y width height]} (:selrect shape) + shape-bound #js {:x x :y y :width width :height height} - parents (get parents-index (:id obj)) - masks (get masks-index (:id obj)) - z (get z-index (:id obj)) + parents (get parents-index (:id shape)) + masks (get masks-index (:id shape)) - frame (when (and (not= :frame (:type obj)) - (not= (:frame-id obj) uuid/zero)) - (get objects (:frame-id obj)))] - (qdt/insert index - (:id obj) - shape-bound - (assoc obj :frame frame :masks masks :parents parents :z z)))) + frame (when (and (not= :frame (:type shape)) + (not= (:frame-id shape) uuid/zero)) + (get objects (:frame-id shape)))] + (qdt/insert index + (:id shape) + shape-bound + (assoc shape :frame frame :masks masks :parents parents))))) (defn- create-index [objects] (let [shapes (-> objects (dissoc uuid/zero) (vals)) - z-index (cp/calculate-z-index objects) parents-index (cp/generate-child-all-parents-index objects) masks-index (cp/create-mask-index objects parents-index) bounds (gsh/selection-rect shapes) bounds #js {:x (:x bounds) :y (:y bounds) :width (:width bounds) - :height (:height bounds)}] + :height (:height bounds)} - (reduce (partial index-object objects z-index parents-index masks-index) - (qdt/create bounds) - shapes))) + index (reduce (index-shape objects parents-index masks-index) + (qdt/create bounds) + shapes) + + z-index (cp/calculate-z-index objects)] + + {:index index :z-index z-index})) (defn- update-index - [index old-objects new-objects] + [{index :index z-index :z-index} old-objects new-objects] (let [changes? (fn [id] (not= (get old-objects id) @@ -66,18 +69,21 @@ (keys new-objects))) shapes (->> changed-ids (mapv #(get new-objects %)) (filterv (comp not nil?))) - z-index (cp/calculate-z-index new-objects) parents-index (cp/generate-child-all-parents-index new-objects shapes) masks-index (cp/create-mask-index new-objects parents-index) - new-index (qdt/remove-all index changed-ids)] + new-index (qdt/remove-all index changed-ids) - (reduce (partial index-object new-objects z-index parents-index masks-index) - new-index - shapes))) + index (reduce (index-shape new-objects parents-index masks-index) + new-index + shapes) + + z-index (cp/update-z-index z-index changed-ids old-objects new-objects)] + + {:index index :z-index z-index})) (defn- query-index - [index rect frame-id include-frames? include-groups? disabled-masks reverse?] + [{index :index z-index :z-index} rect frame-id include-frames? include-groups? disabled-masks reverse?] (let [result (-> (qdt/search index (clj->js rect)) (es6-iterator-seq)) @@ -102,6 +108,11 @@ (some (comp not overlaps?)) not)) + add-z-index + (fn [{:keys [id frame-id] :as shape}] + (assoc shape :z (+ (get z-index id) + (get z-index frame-id 0)))) + ;; Shapes after filters of overlapping and criteria matching-shapes (into [] @@ -109,7 +120,8 @@ (filter match-criteria?) (filter (comp overlaps? :frame)) (filter (comp overlaps-masks? :masks)) - (filter overlaps?)) + (filter overlaps?) + (map add-z-index)) result) keyfn (if reverse? (comp - :z) :z)]