diff --git a/common/src/app/common/geom/line.cljc b/common/src/app/common/geom/line.cljc new file mode 100644 index 0000000000..6ab28d5fc1 --- /dev/null +++ b/common/src/app/common/geom/line.cljc @@ -0,0 +1,18 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.common.geom.line) + +(defn line-value + [[{px :x py :y} {vx :x vy :y}] {:keys [x y]}] + (let [a vy + b (- vx) + c (+ (* (- vy) px) (* vx py))] + (+ (* a x) (* b y) c))) + +(defn is-inside-lines? + [line-1 line-2 pos] + (< (* (line-value line-1 pos) (line-value line-2 pos)) 0)) diff --git a/common/src/app/common/geom/shapes/grid_layout/positions.cljc b/common/src/app/common/geom/shapes/grid_layout/positions.cljc index e6504519f5..6ab0c0bb75 100644 --- a/common/src/app/common/geom/shapes/grid_layout/positions.cljc +++ b/common/src/app/common/geom/shapes/grid_layout/positions.cljc @@ -9,6 +9,7 @@ [app.common.data :as d] [app.common.data.macros :as dm] [app.common.files.helpers :as cfh] + [app.common.geom.line :as gl] [app.common.geom.point :as gpt] [app.common.geom.shapes.common :as gco] [app.common.geom.shapes.grid-layout.layout-data :as ld] @@ -182,18 +183,6 @@ (-> (ctm/add-modifiers fill-modifiers) (ctm/move position-delta))))) - -(defn line-value - [[{px :x py :y} {vx :x vy :y}] {:keys [x y]}] - (let [a vy - b (- vx) - c (+ (* (- vy) px) (* vx py))] - (+ (* a x) (* b y) c))) - -(defn is-inside-lines? - [line-1 line-2 pos] - (< (* (line-value line-1 pos) (line-value line-2 pos)) 0)) - (defn get-position-grid-coord [{:keys [layout-bounds row-tracks column-tracks]} position] @@ -206,7 +195,7 @@ (fn is-inside-track? [{:keys [start-p size] :as track}] (let [unit-v (vfn 1) end-p (gpt/add start-p (ofn size))] - (is-inside-lines? [start-p unit-v] [end-p unit-v] position))))) + (gl/is-inside-lines? [start-p unit-v] [end-p unit-v] position))))) make-min-distance-track (fn [type] @@ -214,8 +203,8 @@ (fn [[selected selected-dist] [cur-idx {:keys [start-p size] :as track}]] (let [unit-v (vfn 1) end-p (gpt/add start-p (ofn size)) - dist-1 (mth/abs (line-value [start-p unit-v] position)) - dist-2 (mth/abs (line-value [end-p unit-v] position))] + dist-1 (mth/abs (gl/line-value [start-p unit-v] position)) + dist-2 (mth/abs (gl/line-value [end-p unit-v] position))] (if (or (< dist-1 selected-dist) (< dist-2 selected-dist)) [[cur-idx track] (min dist-1 dist-2)] diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc index 0745a4f9e3..58ccb6a0a3 100644 --- a/common/src/app/common/types/shape/layout.cljc +++ b/common/src/app/common/types/shape/layout.cljc @@ -327,6 +327,20 @@ [pad-top pad-right pad-top pad-right] [pad-top pad-right pad-bottom pad-left]))) +(defn h-padding + [{:keys [layout-padding-type layout-padding]}] + (let [{pad-top :p1 pad-right :p2 pad-bottom :p3 pad-left :p4} layout-padding] + (if (= :simple layout-padding-type) + (+ pad-right pad-right) + (+ pad-right pad-left)))) + +(defn v-padding + [{:keys [layout-padding-type layout-padding]}] + (let [{pad-top :p1 pad-right :p2 pad-bottom :p3 pad-left :p4} layout-padding] + (if (= :simple layout-padding-type) + (+ pad-top pad-top) + (+ pad-top pad-bottom)))) + (defn child-min-width [child] (if (and (fill-width? child) diff --git a/frontend/src/app/main/ui/workspace/viewport/grid_layout_editor.cljs b/frontend/src/app/main/ui/workspace/viewport/grid_layout_editor.cljs index 8066696b00..ddac9377a5 100644 --- a/frontend/src/app/main/ui/workspace/viewport/grid_layout_editor.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/grid_layout_editor.cljs @@ -10,6 +10,7 @@ [app.common.data :as d] [app.common.data.macros :as dm] [app.common.files.helpers :as cfh] + [app.common.geom.line :as gl] [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] @@ -163,7 +164,7 @@ (mf/set-ref-val! dragging-ref true) (mf/set-ref-val! start-pos-ref raw-pt) (mf/set-ref-val! current-pos-ref raw-pt) - (when on-drag-start (on-drag-start position))))) + (when on-drag-start (on-drag-start event position))))) handle-lost-pointer-capture (mf/use-callback @@ -174,7 +175,7 @@ (dom/release-pointer event) (mf/set-ref-val! dragging-ref false) (mf/set-ref-val! start-pos-ref nil) - (when on-drag-end (on-drag-end position))))) + (when on-drag-end (on-drag-end event position))))) handle-pointer-move (mf/use-callback @@ -185,8 +186,8 @@ pos (dom/get-client-position event) pt (uwvv/point->viewport pos)] (mf/set-ref-val! current-pos-ref pos) - (when on-drag-delta (on-drag-delta (gpt/to-vec start pos))) - (when on-drag-position (on-drag-position pt))))))] + (when on-drag-delta (on-drag-delta event (gpt/to-vec start pos))) + (when on-drag-position (on-drag-position event pt))))))] {:handle-pointer-down handle-pointer-down :handle-lost-pointer-capture handle-lost-pointer-capture @@ -212,7 +213,7 @@ handle-drag-position (mf/use-callback (mf/deps shape row column row-span column-span) - (fn [position] + (fn [_ position] (let [[drag-row drag-column] (gsg/get-position-grid-coord layout-data position) [new-row new-column new-row-span new-column-span] @@ -410,7 +411,7 @@ handle-drag-position (mf/use-callback (mf/deps shape track-before track-after) - (fn [position] + (fn [_ position] (let [[tracks-prop axis] (if (= :column type) [:layout-grid-columns :x] [:layout-grid-rows :y]) @@ -451,11 +452,14 @@ (let [shape (unchecked-get props "shape") index (unchecked-get props "index") last? (unchecked-get props "last?") + drop? (unchecked-get props "drop?") track-before (unchecked-get props "track-before") track-after (unchecked-get props "track-after") snap-pixel? (unchecked-get props "snap-pixel?") - {:keys [column-total-size column-total-gap row-total-size row-total-gap]} (unchecked-get props "layout-data") + {:keys [column-total-size column-total-gap row-total-size row-total-gap] :as layout-data} + (unchecked-get props "layout-data") + start-p (unchecked-get props "start-p") type (unchecked-get props "type") zoom (unchecked-get props "zoom") @@ -477,7 +481,7 @@ [(+ column-total-size column-total-gap) (max 0 (- layout-gap-row (/ 10 zoom)) (/ 8 zoom))]) - start-p + start-p-resize (cond-> start-p (and (= type :column) (= index 0)) (gpt/subtract (hv (/ width 2))) @@ -491,22 +495,52 @@ (and (= type :row) (not= index 0) (not last?)) (-> (gpt/subtract (vv (/ layout-gap-row 2))) - (gpt/subtract (vv (/ height 2)))))] + (gpt/subtract (vv (/ height 2))))) - [:rect.resize-track-handler - {:x (:x start-p) - :y (:y start-p) - :height height - :width width - :on-pointer-down handle-pointer-down - :on-lost-pointer-capture handle-lost-pointer-capture - :on-pointer-move handle-pointer-move - :transform (dm/str (gmt/transform-in start-p (:transform shape))) - :class (if (= type :column) - (cur/get-dynamic "resize-ew" (:rotation shape)) - (cur/get-dynamic "resize-ns" (:rotation shape))) - :style {:fill "transparent" - :stroke-width 0}}])) + start-p-drop + (cond-> start-p + (and (= type :column) (= index 0)) + (gpt/subtract (hv (/ width 2))) + + (and (= type :row) (= index 0)) + (gpt/subtract (vv (/ height 2))) + + (and (= type :column) last?) + (gpt/add (hv (/ width 2))) + + (and (= type :row) last?) + (gpt/add (vv (/ height 2))) + + (and (= type :column) (not= index 0) (not last?)) + (-> (gpt/subtract (hv (/ layout-gap-col 2))) + (gpt/subtract (hv (/ 5 zoom)))) + + (and (= type :row) (not= index 0) (not last?)) + (-> (gpt/subtract (vv (/ layout-gap-row 2))) + (gpt/subtract (vv (/ 5 zoom)))))] + [:* + (when drop? + [:rect.drop + {:x (:x start-p-drop) + :y (:y start-p-drop) + :width (if (= type :column)(/ 10 zoom) width) + :height (if (= type :row) (/ 10 zoom) height) + :fill "var(--grid-editor-area-background)"}]) + + [:rect.resize-track-handler + {:x (:x start-p-resize) + :y (:y start-p-resize) + :height height + :width width + :on-pointer-down handle-pointer-down + :on-lost-pointer-capture handle-lost-pointer-capture + :on-pointer-move handle-pointer-move + :transform (dm/str (gmt/transform-in start-p (:transform shape))) + :class (if (= type :column) + (cur/get-dynamic "resize-ew" (:rotation shape)) + (cur/get-dynamic "resize-ns" (:rotation shape))) + :style {:fill "transparent" + :stroke-width 0}}]])) (def marker-width 24) (def marker-h1 20) @@ -620,7 +654,7 @@ (dm/str value)]])) (mf/defc track - {::mf/wrap [#(mf/memo' % (mf/check-props ["shape" "zoom" "index" "type" "track-data" "layout-data" "hovering?"]))] + {::mf/wrap [mf/memo] ::mf/wrap-props false} [props] (let [shape (unchecked-get props "shape") @@ -631,6 +665,11 @@ track-data (unchecked-get props "track-data") layout-data (unchecked-get props "layout-data") hovering? (unchecked-get props "hovering?") + drop? (unchecked-get props "drop?") + + on-start-reorder-track (unchecked-get props "on-start-reorder-track") + on-move-reorder-track (unchecked-get props "on-move-reorder-track") + on-end-reorder-track (unchecked-get props "on-end-reorder-track") track-input-ref (mf/use-ref) [layout-gap-row layout-gap-col] (ctl/gaps shape) @@ -719,17 +758,41 @@ [(:x text-p) (- (:y text-p) (/ 36 zoom)) (max 0 (:size track-data)) (/ 36 zoom)] [(- (:x text-p) (max 0 (:size track-data))) (- (:y text-p) (/ 36 zoom)) (max 0 (:size track-data)) (/ 36 zoom)]) + handle-drag-start + (mf/use-callback + (mf/deps on-start-reorder-track type index) + (fn [] + (on-start-reorder-track type index))) + + handle-drag-end + (mf/use-callback + (mf/deps on-end-reorder-track type index) + (fn [event position] + (on-end-reorder-track type index position (not (kbd/mod? event))))) + + handle-drag-position + (mf/use-callback + (mf/deps on-move-reorder-track type index) + (fn [_ position] + (on-move-reorder-track type index position))) + trackwidth (* text-width zoom) medium? (and (>= trackwidth small-size-limit) (< trackwidth medium-size-limit)) small? (< trackwidth small-size-limit) - track-before (get-in layout-data [track-list-prop (dec index)])] + track-before (get-in layout-data [track-list-prop (dec index)]) + + {:keys [handle-pointer-down handle-lost-pointer-capture handle-pointer-move]} + (use-drag {:on-drag-start handle-drag-start + :on-drag-end handle-drag-end + :on-drag-position handle-drag-position})] (mf/use-effect (mf/deps track-data) (fn [] (dom/set-value! (mf/ref-val track-input-ref) (format-size track-data)))) + [:g.track [:g {:on-pointer-enter handle-pointer-enter :on-pointer-leave handle-pointer-leave @@ -737,17 +800,20 @@ (dm/str (gmt/transform-in text-p (:transform shape))) (dm/str (gmt/transform-in text-p (gmt/rotate (:transform shape) -90))))} - (when (and hovering? (not small?)) - [:rect {:x (+ text-x (/ 18 zoom)) - :y text-y - :width (- text-width (/ 36 zoom)) - :height (- text-height (/ 5 zoom)) - :rx (/ 3 zoom) - :fill "var(--grid-editor-marker-color)" - :opacity 0.2}]) + [:rect {:class (stl/css :grid-editor-header-hover) + :x (+ text-x (/ 18 zoom)) + :y text-y + :width (- text-width (/ 36 zoom)) + :height (- text-height (/ 5 zoom)) + :rx (/ 3 zoom) + :style {:cursor "pointer"} + :opacity (if (and hovering? (not small?)) 0.2 0)}] (when (not small?) [:foreignObject {:x text-x :y text-y :width text-width :height text-height} - [:div {:class (stl/css :grid-editor-wrapper)} + [:div {:class (stl/css :grid-editor-wrapper) + :on-pointer-down handle-pointer-down + :on-lost-pointer-capture handle-lost-pointer-capture + :on-pointer-move handle-pointer-move} [:input {:ref track-input-ref :style {} @@ -778,6 +844,7 @@ :layout-data layout-data :shape shape :snap-pixel? snap-pixel? + :drop? drop? :start-p start-p :track-after track-data :track-before track-before @@ -855,8 +922,8 @@ (mf/deps shape children) #(gsg/calc-layout-data shape bounds children all-bounds objects)) - width (max (gpo/width-points bounds) (+ column-total-size column-total-gap)) - height (max (gpo/height-points bounds) (+ row-total-size row-total-gap)) + width (max (gpo/width-points bounds) (+ column-total-size column-total-gap (ctl/h-padding shape))) + height (max (gpo/height-points bounds) (+ row-total-size row-total-gap (ctl/v-padding shape))) handle-pointer-down (mf/use-callback @@ -875,7 +942,68 @@ (mf/use-callback (mf/deps (:id shape)) (fn [] - (st/emit! (st/emit! (dwsl/add-layout-track [(:id shape)] :row ctl/default-track-value)))))] + (st/emit! (st/emit! (dwsl/add-layout-track [(:id shape)] :row ctl/default-track-value))))) + + + target-tracks* (mf/use-ref nil) + drop-track-type* (mf/use-state nil) + drop-track-target* (mf/use-state nil) + + handle-start-reorder-track + (mf/use-callback + (mf/deps layout-data) + (fn [type from-idx] + ;; Initialize target-tracks + (let [line-vec (if (= type :column) (vv 1) (hv 1)) + + first-point origin + last-point (if (= type :column) (nth bounds 1) (nth bounds 3)) + mid-points + (if (= type :column) + (->> (:column-tracks layout-data) + (mapv #(gpt/add (:start-p %) (hv (/ (:size %) 2))))) + + (->> (:row-tracks layout-data) + (mapv #(gpt/add (:start-p %) (vv (/ (:size %) 2)))))) + + tracks + (->> (d/with-prev (d/concat-vec [first-point] mid-points [last-point])) + (d/enumerate) + (keep + (fn [[index [current prev]]] + (when (some? prev) + [[prev current line-vec] (dec index)]))))] + + (mf/set-ref-val! target-tracks* tracks) + (reset! drop-track-type* type)))) + + handle-move-reorder-track + (mf/use-callback + (fn [type from-idx position] + (let [index + (->> (mf/ref-val target-tracks*) + (d/seek (fn [[[p1 p2 v] _]] + (gl/is-inside-lines? [p1 v] [p2 v] position))) + (second))] + (when (some? index) + (reset! drop-track-target* index))))) + + handle-end-reorder-track + (mf/use-callback + (mf/deps base-shape @drop-track-target*) + (fn [type from-index position move-content?] + (when-let [to-index @drop-track-target*] + (let [ids [(:id base-shape)]] + (cond + (< from-index to-index) + (st/emit! (dwsl/reorder-layout-track ids type from-index (dec to-index) move-content?)) + + (> from-index to-index) + (st/emit! (dwsl/reorder-layout-track ids type from-index (dec to-index) move-content?))))) + + (mf/set-ref-val! target-tracks* nil) + (reset! drop-track-type* nil) + (reset! drop-track-target* nil)))] (mf/use-effect (fn [] @@ -914,15 +1042,21 @@ :on-click handle-add-row}]]) (for [[idx column-data] (d/enumerate column-tracks)] - [:& track {:key (dm/str "column-track-" idx) - :shape shape - :zoom zoom - :type :column - :index idx - :layout-data layout-data - :snap-pixel? snap-pixel? - :track-data column-data - :hovering? (contains? hover-columns idx)}]) + (let [drop? (and (= :column @drop-track-type*) + (= idx @drop-track-target*))] + [:& track {:key (dm/str "column-track-" idx) + :shape shape + :zoom zoom + :type :column + :index idx + :layout-data layout-data + :snap-pixel? snap-pixel? + :drop? drop? + :track-data column-data + :hovering? (contains? hover-columns idx) + :on-start-reorder-track handle-start-reorder-track + :on-move-reorder-track handle-move-reorder-track + :on-end-reorder-track handle-end-reorder-track}])) ;; Last track resize handler (when-not (empty? column-tracks) @@ -940,27 +1074,36 @@ :type :column :value (dm/str (inc (count column-tracks))) :zoom zoom}] - [:& resize-track-handler - {:index (count column-tracks) - :last? true - :shape shape - :layout-data layout-data - :snap-pixel? snap-pixel? - :start-p end-p - :type :column - :track-before (last column-tracks) - :zoom zoom}]])) + (let [drop? (and (= :column @drop-track-type*) + (= (count column-tracks) @drop-track-target*))] + [:& resize-track-handler + {:index (count column-tracks) + :last? true + :drop? drop? + :shape shape + :layout-data layout-data + :snap-pixel? snap-pixel? + :start-p end-p + :type :column + :track-before (last column-tracks) + :zoom zoom}])])) (for [[idx row-data] (d/enumerate row-tracks)] - [:& track {:index idx - :key (dm/str "row-track-" idx) - :layout-data layout-data - :shape shape - :snap-pixel? snap-pixel? - :track-data row-data - :type :row - :zoom zoom - :hovering? (contains? hover-rows idx)}]) + (let [drop? (and (= :row @drop-track-type*) + (= idx @drop-track-target*))] + [:& track {:index idx + :key (dm/str "row-track-" idx) + :layout-data layout-data + :shape shape + :snap-pixel? snap-pixel? + :drop? drop? + :track-data row-data + :type :row + :zoom zoom + :hovering? (contains? hover-rows idx) + :on-start-reorder-track handle-start-reorder-track + :on-move-reorder-track handle-move-reorder-track + :on-end-reorder-track handle-end-reorder-track}])) (when-not (empty? row-tracks) (let [last-track (last row-tracks) start-p (:start-p last-track) @@ -977,13 +1120,16 @@ :type :row :value (dm/str (inc (count row-tracks))) :zoom zoom}]] - [:& resize-track-handler - {:index (count row-tracks) - :last? true - :shape shape - :layout-data layout-data - :start-p end-p - :type :row - :track-before (last row-tracks) - :snap-pixel? snap-pixel? - :zoom zoom}]]))])]))) + (let [drop? (and (= :row @drop-track-type*) + (= (count row-tracks) @drop-track-target*))] + [:& resize-track-handler + {:index (count row-tracks) + :last? true + :drop? drop? + :shape shape + :layout-data layout-data + :start-p end-p + :type :row + :track-before (last row-tracks) + :snap-pixel? snap-pixel? + :zoom zoom}])]))])]))) diff --git a/frontend/src/app/main/ui/workspace/viewport/grid_layout_editor.scss b/frontend/src/app/main/ui/workspace/viewport/grid_layout_editor.scss index c519a0356d..497bc2e879 100644 --- a/frontend/src/app/main/ui/workspace/viewport/grid_layout_editor.scss +++ b/frontend/src/app/main/ui/workspace/viewport/grid_layout_editor.scss @@ -18,30 +18,35 @@ } .grid-editor-wrapper { + cursor: grab; width: 100%; height: 80%; display: flex; + flex-direction: column; justify-content: center; align-items: center; } +.grid-editor-header-hover { + fill: var(--grid-editor-marker-color); +} + .grid-editor-label { + flex: 1; background: none; - border-bottom: calc($s-1 / var(--zoom)) solid transparent; border: 0; color: var(--grid-editor-marker-text); font-family: worksans; font-size: calc($fs-12 / var(--zoom)); font-weight: 400; margin: 0; - max-width: calc($s-80 / var(--zoom)); + max-width: calc($s-60 / var(--zoom)); padding: 0; - padding: $s-4; + padding: calc($s-4 / var(--zoom)); text-align: center; &:focus { outline: none; - border-bottom: calc($s-1 / var(--zoom)) solid var(--grid-editor-marker-text); } }