mirror of
https://github.com/penpot/penpot.git
synced 2025-05-10 10:46:38 +02:00
✨ Drop-zone autolayout calculation
This commit is contained in:
parent
8bcb9e1976
commit
025cac0228
7 changed files with 339 additions and 13 deletions
|
@ -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
|
||||
|
||||
))
|
||||
|
|
|
@ -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]}]
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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} ]]]))))
|
||||
[:& frame-shape {:shape shape :childs childs} ]]]
|
||||
|
||||
#_[:& debug-layout {:shape shape}]]))))
|
||||
|
||||
(defn check-props
|
||||
[new-props old-props]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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* #{}))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue