diff --git a/common/src/app/common/geom/shapes/layout.cljc b/common/src/app/common/geom/shapes/layout.cljc index d0c616e85..8d1fbed87 100644 --- a/common/src/app/common/geom/shapes/layout.cljc +++ b/common/src/app/common/geom/shapes/layout.cljc @@ -8,6 +8,7 @@ (:require [app.common.data :as d] [app.common.geom.point :as gpt] + [app.common.geom.shapes.rect :as gsr] [app.common.geom.shapes.transforms :as gst])) ;; :layout ;; true if active, false if not @@ -571,3 +572,136 @@ (conj {:type :move :vector move-vec}))] [modifiers layout-line])) + + +(defn drop-areas + [{:keys [margin-x margin-y] :as frame} layout-data children] + + (let [col? (col? frame) + row? (row? frame) + h-center? (and row? (h-center? frame)) + h-end? (and row? (h-end? frame)) + v-center? (and col? (v-center? frame)) + v-end? (and row? (v-end? frame)) + layout-gap (:layout-gap frame 0) + + children (vec (cond->> children + (:reverse? layout-data) reverse)) + + redfn-child + (fn [[result parent-rect prev-x prev-y] [child next]] + (let [prev-x (or prev-x (:x parent-rect)) + prev-y (or prev-y (:y parent-rect)) + last? (nil? next) + + box-x (-> child :selrect :x) + box-y (-> child :selrect :y) + box-width (-> child :selrect :width) + box-height(-> child :selrect :height) + + + x (if row? (:x parent-rect) prev-x) + y (if col? (:y parent-rect) prev-y) + + width (cond + (and col? last?) + (- (+ (:x parent-rect) (:width parent-rect)) x) + + row? + (:width parent-rect) + + :else + (+ box-width (- box-x prev-x) (/ layout-gap 2))) + + height (cond + (and row? last?) + (- (+ (:y parent-rect) (:height parent-rect)) y) + + col? + (:height parent-rect) + + :else + (+ box-height (- box-y prev-y) (/ layout-gap 2))) + + line-area (gsr/make-rect x y width height) + result (conj result line-area)] + + [result parent-rect (+ x width) (+ y height)])) + + redfn-lines + (fn [[result from-idx prev-x prev-y] [{:keys [start-p layout-gap num-children line-width line-height]} next]] + (let [prev-x (or prev-x (:x frame)) + prev-y (or prev-y (:y frame)) + last? (nil? next) + + line-width + (if col? + (:width frame) + (+ line-width margin-x + (if col? (* layout-gap (dec num-children)) 0))) + + line-height + (if row? + (:height frame) + (+ line-height margin-y + (if row? + (* layout-gap (dec num-children)) + 0))) + + box-x + (- (:x start-p) + (cond + h-center? (/ line-width 2) + h-end? line-width + :else 0)) + + box-y + (- (:y start-p) + (cond + v-center? (/ line-height 2) + v-end? line-height + :else 0)) + + x (if col? (:x frame) prev-x) + y (if row? (:y frame) prev-y) + + width (cond + (and row? last?) + (- (+ (:x frame) (:width frame)) x) + + col? + (:width frame) + + :else + (+ line-width (- box-x prev-x) (/ layout-gap 2))) + + height (cond + (and col? last?) + (- (+ (:y frame) (:height frame)) y) + + row? + (:height frame) + + :else + (+ line-height (- box-y prev-y) (/ layout-gap 2))) + + line-area (gsr/make-rect x y width height) + + children (subvec children from-idx (+ from-idx num-children)) + + + ;; To debug the lines + ;;result (conj result line-area) + + result (first (reduce redfn-child [result line-area] (d/with-next children)))] + + [result (+ from-idx num-children) (+ x width) (+ y height)])) + + ret (first (reduce redfn-lines [[] 0] (d/with-next (:layout-lines layout-data)))) + ] + + + ;;(.log js/console "RET" (clj->js ret)) + ret + + )) diff --git a/common/src/app/common/pages/helpers.cljc b/common/src/app/common/pages/helpers.cljc index a680833d2..5d243c363 100644 --- a/common/src/app/common/pages/helpers.cljc +++ b/common/src/app/common/pages/helpers.cljc @@ -28,8 +28,10 @@ (= frame-id uuid/zero))) (defn frame-shape? - [{:keys [type]}] - (= type :frame)) + ([objects id] + (= (get-in objects [id :type]) id)) + ([{:keys [type]}] + (= type :frame))) (defn group-shape? [{:keys [type]}] diff --git a/common/src/app/common/types/shape_tree.cljc b/common/src/app/common/types/shape_tree.cljc index e39869c98..72388c216 100644 --- a/common/src/app/common/types/shape_tree.cljc +++ b/common/src/app/common/types/shape_tree.cljc @@ -234,6 +234,20 @@ (if (nil? child-frame-id) (or current-id uuid/zero) (recur child-frame-id)))))) +(defn top-nested-frame-ids + "Search the top nested frame in a list of ids" + [objects ids] + + (let [frame-ids (->> ids (filter #(cph/frame-shape? objects %))) + frame-set (set frame-ids)] + (loop [current-id (first frame-ids)] + (let [current-shape (get objects current-id) + child-frame-id (d/seek #(contains? frame-set %) + (-> (:shapes current-shape) reverse))] + (if (nil? child-frame-id) + (or current-id uuid/zero) + (recur child-frame-id))))) + ) (defn get-viewer-frames ([objects] diff --git a/frontend/src/app/main/ui/workspace/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/shapes/frame.cljs index 1de768035..a3745f16d 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame.cljs @@ -24,7 +24,87 @@ [app.main.ui.workspace.shapes.frame.node-store :as fns] [app.main.ui.workspace.shapes.frame.thumbnail-render :as ftr] [beicon.core :as rx] - [rumext.v2 :as mf])) + [rumext.v2 :as mf] + + [app.common.geom.shapes :as gsh] + [app.common.geom.shapes.layout :as gsl] + [app.main.data.workspace.state-helpers :as wsh] + [app.main.store :as st])) + +(mf/defc debug-layout + {::mf/wrap-props false} + [props] + + (let [shape (unchecked-get props "shape") + children (-> (wsh/lookup-page-objects @st/state) + (cph/get-immediate-children (:id shape))) + + layout-data (gsl/calc-layout-data shape children) + + drop-areas + (gsl/drop-areas shape layout-data children) + + ] + + [:g.debug-layout {:pointer-events "none"} + (for [[idx drop-area] (d/enumerate drop-areas)] + [:rect {:x (:x drop-area) + :y (:y drop-area) + :width (:width drop-area) + :height (:height drop-area) + :style {:fill "blue" + :fill-opacity 0.3 + :stroke "red" + :stroke-width 1 + :stroke-dasharray "3 6"}}]) + + + #_(for [[idx layout-line] (d/enumerate (:layout-lines layout-data))] + (let [col? (gsl/col? shape) + row? (gsl/row? shape) + h-center? (and row? (gsl/h-center? shape)) + h-end? (and row? (gsl/h-end? shape)) + v-center? (and col? (gsl/v-center? shape)) + v-end? (and row? (gsl/v-end? shape)) + + line-width + (+ (-> layout-line :line-width) + (:margin-x shape) + (if col? + (* (:layout-gap layout-line) (dec (-> layout-line :num-children))) + 0)) + + line-height + (+ (-> layout-line :line-height) + (:margin-y shape) + (if row? + (* (:layout-gap layout-line) (dec (-> layout-line :num-children))) + 0)) + ] + [:g {:key (dm/str "line-" idx)} + [:rect {:x (- (-> layout-line :start-p :x) + (cond + h-center? (/ line-width 2) + h-end? line-width + :else 0)) + :y (- (-> layout-line :start-p :y) + (cond + v-center? (/ line-height 2) + v-end? line-height + :else 0)) + :width line-width + :height line-height + :style {:fill "blue" + :fill-opacity 0.3} + }] + #_[:line {:x1 (-> layout-line :start-p :x) + :y1 (-> layout-line :start-p :y) + :x2 (+ (-> layout-line :start-p :x) (if col? line-width 0)) + :y2 (+ (-> layout-line :start-p :y) (if row? line-height 0)) + :transform (gsh/transform-str shape) + :style {:fill "none" + :stroke "red" + :stroke-width 2}}]]))])) (defn frame-shape-factory [shape-wrapper] @@ -39,9 +119,12 @@ childs-ref (mf/use-memo (mf/deps (:id shape)) #(refs/children-objects (:id shape))) childs (mf/deref childs-ref)] - [:& (mf/provider embed/context) {:value true} - [:& shape-container {:shape shape :ref ref :disable-shadows? (cph/root-frame? shape)} - [:& frame-shape {:shape shape :childs childs} ]]])))) + [:* + [:& (mf/provider embed/context) {:value true} + [:& shape-container {:shape shape :ref ref :disable-shadows? (cph/root-frame? shape)} + [:& frame-shape {:shape shape :childs childs} ]]] + + #_[:& debug-layout {:shape shape}]])))) (defn check-props [new-props old-props] diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 5a4be44ca..4c219d3ac 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -41,7 +41,85 @@ [app.main.ui.workspace.viewport.widgets :as widgets] [beicon.core :as rx] [debug :refer [debug?]] - [rumext.v2 :as mf])) + [rumext.v2 :as mf] + + [app.common.uuid :as uuid] + [app.common.geom.shapes :as gsh] + [app.common.geom.shapes.layout :as gsl] + [app.main.data.workspace.state-helpers :as wsh] + [app.main.store :as st] + + )) + +(mf/defc debug-layout + {::mf/wrap-props false} + [props] + + (let [shape (unchecked-get props "shape") + objects (unchecked-get props "objects") + children (cph/get-immediate-children objects (:id shape)) + layout-data (gsl/calc-layout-data shape children) + drop-areas (gsl/drop-areas shape layout-data children)] + + [:g.debug-layout {:pointer-events "none"} + (for [[idx drop-area] (d/enumerate drop-areas)] + [:rect {:x (:x drop-area) + :y (:y drop-area) + :width (:width drop-area) + :height (:height drop-area) + :style {:fill "blue" + :fill-opacity 0.3 + :stroke "red" + :stroke-width 1 + :stroke-dasharray "3 6"}}]) + + + #_(for [[idx layout-line] (d/enumerate (:layout-lines layout-data))] + (let [col? (gsl/col? shape) + row? (gsl/row? shape) + h-center? (and row? (gsl/h-center? shape)) + h-end? (and row? (gsl/h-end? shape)) + v-center? (and col? (gsl/v-center? shape)) + v-end? (and row? (gsl/v-end? shape)) + + line-width + (+ (-> layout-line :line-width) + (:margin-x shape) + (if col? + (* (:layout-gap layout-line) (dec (-> layout-line :num-children))) + 0)) + + line-height + (+ (-> layout-line :line-height) + (:margin-y shape) + (if row? + (* (:layout-gap layout-line) (dec (-> layout-line :num-children))) + 0)) + ] + [:g {:key (dm/str "line-" idx)} + [:rect {:x (- (-> layout-line :start-p :x) + (cond + h-center? (/ line-width 2) + h-end? line-width + :else 0)) + :y (- (-> layout-line :start-p :y) + (cond + v-center? (/ line-height 2) + v-end? line-height + :else 0)) + :width line-width + :height line-height + :style {:fill "blue" + :fill-opacity 0.3} + }] + #_[:line {:x1 (-> layout-line :start-p :x) + :y1 (-> layout-line :start-p :y) + :x2 (+ (-> layout-line :start-p :x) (if col? line-width 0)) + :y2 (+ (-> layout-line :start-p :y) (if row? line-height 0)) + :transform (gsh/transform-str shape) + :style {:fill "none" + :stroke "red" + :stroke-width 2}}]]))])) ;; --- Viewport @@ -90,6 +168,7 @@ hover-ids (mf/use-state nil) hover (mf/use-state nil) hover-disabled? (mf/use-state false) + hover-top-frame-id (mf/use-state nil) frame-hover (mf/use-state nil) active-frames (mf/use-state #{}) @@ -186,7 +265,7 @@ (hooks/setup-viewport-size viewport-ref) (hooks/setup-cursor cursor alt? mod? space? panning drawing-tool drawing-path? node-editing?) (hooks/setup-keyboard alt? mod? space?) - (hooks/setup-hover-shapes page-id move-stream base-objects transform selected mod? hover hover-ids @hover-disabled? focus zoom) + (hooks/setup-hover-shapes page-id move-stream base-objects transform selected mod? hover hover-ids hover-top-frame-id @hover-disabled? focus zoom) (hooks/setup-viewport-modifiers modifiers base-objects) (hooks/setup-shortcuts node-editing? drawing-path?) (hooks/setup-active-frames base-objects hover-ids selected active-frames zoom transform vbox) @@ -414,6 +493,16 @@ :hover-frame frame-parent :disabled-guides? disabled-guides?}]) + (let [selected-frame (when (= 1 (count selected-shapes)) + (let [selected-shape (get objects-modified (first selected))] + (when (= :frame (:type selected-shape)) + selected-shape))) + + top-frame (or selected-frame (get objects-modified @hover-top-frame-id))] + (when (and top-frame (not= uuid/zero top-frame) (:layout top-frame)) + [:& debug-layout {:shape top-frame + :objects objects-modified}])) + (when show-selection-handlers? [:g.selection-handlers {:clipPath "url(#clip-handlers)"} [:defs diff --git a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs index c9071afc8..cc6c7bf8d 100644 --- a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs @@ -28,7 +28,8 @@ [beicon.core :as rx] [debug :refer [debug?]] [goog.events :as events] - [rumext.v2 :as mf]) + [rumext.v2 :as mf] + [app.common.types.shape-tree :as ctst]) (:import goog.events.EventType)) (defn setup-dom-events [viewport-ref zoom disable-paste in-viewport?] @@ -104,7 +105,8 @@ (some #(cph/is-parent? objects % group-id)) (not)))) -(defn setup-hover-shapes [page-id move-stream objects transform selected mod? hover hover-ids hover-disabled? focus zoom] +(defn setup-hover-shapes + [page-id move-stream objects transform selected mod? hover hover-ids hover-top-frame-id hover-disabled? focus zoom] (let [;; We use ref so we don't recreate the stream on a change zoom-ref (mf/use-ref zoom) mod-ref (mf/use-ref @mod?) @@ -143,9 +145,10 @@ (rx/map #(deref last-point-ref))) (->> move-stream + (rx/tap #(reset! last-point-ref %)) ;; When transforming shapes we stop querying the worker (rx/merge-map query-point) - (rx/tap #(reset! last-point-ref %))))))] + ))))] ;; Refresh the refs on a value change (mf/use-effect @@ -213,7 +216,8 @@ (first) (get objects))] (reset! hover hover-shape) - (reset! hover-ids ids)))))) + (reset! hover-ids ids) + (reset! hover-top-frame-id (ctst/top-nested-frame objects (deref last-point-ref)))))))) (defn setup-viewport-modifiers [modifiers objects] diff --git a/frontend/src/debug.cljs b/frontend/src/debug.cljs index a87d0cbac..96c986604 100644 --- a/frontend/src/debug.cljs +++ b/frontend/src/debug.cljs @@ -74,7 +74,7 @@ #{:app.main.data.workspace.notifications/handle-pointer-update :app.main.data.workspace.selection/change-hover-state}) -(defonce ^:dynamic *debug* (atom #{#_:events #_:text-outline})) +(defonce ^:dynamic *debug* (atom #{#_:events})) (defn debug-all! [] (reset! *debug* debug-options)) (defn debug-none! [] (reset! *debug* #{}))