diff --git a/frontend/src/uxbox/main/data/shapes.cljs b/frontend/src/uxbox/main/data/shapes.cljs index 4c2ae4359..cd9a0e791 100644 --- a/frontend/src/uxbox/main/data/shapes.cljs +++ b/frontend/src/uxbox/main/data/shapes.cljs @@ -8,17 +8,18 @@ (:require [beicon.core :as rx] [uxbox.util.uuid :as uuid] [potok.core :as ptk] - [uxbox.util.router :as r] + [uxbox.store :as st] [uxbox.util.forms :as sc] + [uxbox.util.geom.point :as gpt] + [uxbox.util.geom.matrix :as gmt] + [uxbox.util.router :as r] + [uxbox.util.rlocks :as rlocks] [uxbox.util.workers :as uw] [uxbox.main.constants :as c] [uxbox.main.geom :as geom] - [uxbox.store :as st] [uxbox.main.data.core :refer (worker)] [uxbox.main.data.shapes-impl :as impl] - [uxbox.main.data.pages :as udp] - [uxbox.util.rlocks :as rlocks] - [uxbox.util.geom.point :as gpt])) + [uxbox.main.data.pages :as udp])) ;; --- Shapes CRUD @@ -70,18 +71,21 @@ (gpt/point c/canvas-start-x c/canvas-start-y)) +(declare apply-temporal-displacement) + (defn initial-align-shape [id] (reify ptk/WatchEvent (watch [_ state s] - (let [shape (get-in state [:shapes id]) - shape (geom/outer-rect state shape) - point (gpt/point (:x shape) (:y shape)) - point (gpt/add point canvas-coords)] - (->> (align-point point) - (rx/map #(gpt/subtract % point)) - (rx/map #(move-shape id %))))))) + (let [{:keys [x1 y1] :as shape} (->> (get-in state [:shapes id]) + (geom/shape->rect-shape state)) + point1 (gpt/point x1 y1) + point2 (gpt/add point1 canvas-coords)] + (->> (align-point point2) + (rx/map #(gpt/subtract % canvas-coords)) + (rx/map (fn [{:keys [x y] :as pt}] + (apply-temporal-displacement id (gpt/subtract pt point1))))))))) (defn update-line-attrs [sid {:keys [x1 y1 x2 y2] :as opts}] @@ -119,6 +123,92 @@ (update [_ state] (update-in state [:shapes sid] geom/resize-dim opts)))) +;; --- Apply Temporal Displacement + +(deftype ApplyTemporalDisplacement [id delta] + ptk/UpdateEvent + (update [_ state] + (let [current-delta (get-in state [:shapes id :tmp-displacement] (gpt/point 0 0)) + delta (gpt/add current-delta delta)] + (assoc-in state [:shapes id :tmp-displacement] delta)))) + +(defn apply-temporal-displacement + [id pt] + {:pre [(uuid? id) (gpt/point? pt)]} + (ApplyTemporalDisplacement. id pt)) + +;; --- Apply Displacement + +(deftype ApplyDisplacement [id] + ptk/UpdateEvent + (update [_ state] + (let [{:keys [tmp-displacement type] :as shape} (get-in state [:shapes id]) + xfmt (gmt/translate-matrix tmp-displacement)] + + (if (= type :group) + (letfn [(update-item [state id] + (let [{:keys [type items] :as shape} (get-in state [:shapes id])] + (if (= type :group) + (reduce update-item state items) + (update-in state [:shapes id] + (fn [shape] + (as-> (dissoc shape :tmp-displacement) $ + (geom/transform state $ xfmt)))))))] + (-> (reduce update-item state (:items shape)) + (update-in [:shapes id] dissoc :tmp-displacement))) + + (update-in state [:shapes id] (fn [shape] + (as-> (dissoc shape :tmp-displacement) $ + (geom/transform state $ xfmt)))))))) + +(defn apply-displacement + [id] + {:pre [(uuid? id)]} + (ApplyDisplacement. id)) + +;; --- Apply Temporal Resize Matrix + +(deftype ApplyTemporalResizeMatrix [id mx] + ptk/UpdateEvent + (update [_ state] + (assoc-in state [:shapes id :tmp-resize-xform] mx))) + +(defn apply-temporal-resize-matrix + "Attach temporal resize matrix transformation to the shape." + [id mx] + (ApplyTemporalResizeMatrix. id mx)) + +;; --- Apply Resize Matrix + +(declare apply-resize-matrix) + +(deftype ApplyResizeMatrix [id] + ptk/UpdateEvent + (update [_ state] + (let [{:keys [type tmp-resize-xform] + :or {tmp-resize-xform (gmt/matrix)} + :as shape} (get-in state [:shapes id])] + (if (= type :group) + (letfn [(update-item [state id] + (let [{:keys [type items] :as shape} (get-in state [:shapes id])] + (if (= type :group) + (reduce update-item state items) + (update-in state [:shapes id] + (fn [shape] + (as-> (dissoc shape :tmp-resize-xform) $ + (geom/transform state $ tmp-resize-xform)))))))] + (-> (reduce update-item state (:items shape)) + (update-in [:shapes id] dissoc :tmp-resize-xform))) + (update-in state [:shapes id] (fn [shape] + (as-> (dissoc shape :tmp-resize-xform) $ + (geom/transform state $ tmp-resize-xform)))))))) + +(defn apply-resize-matrix + "Apply definitivelly the resize matrix transformation to the shape." + [id] + {:pre [(uuid? id)]} + (ApplyResizeMatrix. id)) + (defn update-vertex-position [id {:keys [vid delta]}] (reify @@ -208,7 +298,6 @@ (when rx {:rx rx}) (when ry {:ry ry}))))) - ;; --- Shape Proportions (defn lock-proportions @@ -378,17 +467,20 @@ id (first (get-in state [:pages page :shapes]))] (assoc-in state [:workspace :selected] #{id}))))) + +(deftype SelectShape [id] + ptk/UpdateEvent + (update [_ state] + (let [selected (get-in state [:workspace :selected]) + state (if (contains? selected id) + (update-in state [:workspace :selected] disj id) + (update-in state [:workspace :selected] conj id))] + (update-in state [:workspace :flags] conj :element-options)))) + (defn select-shape "Mark a shape selected for drawing in the canvas." [id] - (reify - ptk/UpdateEvent - (update [_ state] - (let [selected (get-in state [:workspace :selected]) - state (if (contains? selected id) - (update-in state [:workspace :selected] disj id) - (update-in state [:workspace :selected] conj id))] - (update-in state [:workspace :flags] conj :element-options))))) + (SelectShape. id)) ;; --- Select Shapes diff --git a/frontend/src/uxbox/main/geom.cljs b/frontend/src/uxbox/main/geom.cljs index c764df03e..ffec05463 100644 --- a/frontend/src/uxbox/main/geom.cljs +++ b/frontend/src/uxbox/main/geom.cljs @@ -555,6 +555,7 @@ (case type :rect (transform-rect shape xfmt) :icon (transform-rect shape xfmt) + :text (transform-rect shape xfmt) :image (transform-rect shape xfmt) :path (transform-path shape xfmt) :circle (transform-circle shape xfmt)))) diff --git a/frontend/src/uxbox/main/ui/shapes/circle.cljs b/frontend/src/uxbox/main/ui/shapes/circle.cljs index 8f334fe16..7c4e3e7e4 100644 --- a/frontend/src/uxbox/main/ui/shapes/circle.cljs +++ b/frontend/src/uxbox/main/ui/shapes/circle.cljs @@ -5,50 +5,39 @@ ;; Copyright (c) 2016 Andrey Antukh (ns uxbox.main.ui.shapes.circle - (:require [sablono.core :refer-macros [html]] - [rum.core :as rum] - [lentes.core :as l] - [uxbox.util.mixins :as mx :include-macros true] + (:require [lentes.core :as l] [uxbox.main.ui.shapes.common :as common] [uxbox.main.ui.shapes.attrs :as attrs] - [uxbox.main.geom :as geom])) + [uxbox.main.geom :as geom] + [uxbox.util.geom.matrix :as gmt] + [uxbox.util.geom.point :as gpt] + [uxbox.util.mixins :as mx :include-macros true])) ;; --- Circle Component (declare circle-shape) -(defn- circle-component-render - [own shape] +(mx/defc circle-component + {:mixins [mx/reactive mx/static]} + [shape] (let [{:keys [id x y width height group]} shape selected (mx/react common/selected-ref) selected? (contains? selected id) on-mouse-down #(common/on-mouse-down % shape selected)] - (html - [:g.shape {:class (when selected? "selected") - :on-mouse-down on-mouse-down} - (circle-shape shape identity)]))) - -(def circle-component - (mx/component - {:render circle-component-render - :name "circle-component" - :mixins [mx/static mx/reactive]})) + [:g.shape {:class (when selected? "selected") + :on-mouse-down on-mouse-down} + (circle-shape shape identity)])) ;; --- Circle Shape -(defn- circle-shape-render - [own {:keys [id] :as shape}] - (let [key (str "shape-" id) - rfm (geom/transformation-matrix shape) - props (select-keys shape [:cx :cy :rx :ry]) - attrs (-> (attrs/extract-style-attrs shape) - (merge {:id key :key key :transform (str rfm)}) - (merge props))] - (html - [:ellipse attrs]))) +(mx/defc circle-shape + {:mixins [mx/static]} + [{:keys [id tmp-resize-xform tmp-displacement] :as shape}] + (let [xfmt (cond-> (or tmp-resize-xform (gmt/matrix)) + tmp-displacement (gmt/translate tmp-displacement)) -(def circle-shape - (mx/component - {:render circle-shape-render - :name "circle-shape" - :mixins [mx/static]})) + props {:transform (str xfmt) :id (str id)} + attrs (merge props + (attrs/extract-style-attrs shape) + (select-keys shape [:cx :cy :rx :ry]))] + [:ellipse attrs])) diff --git a/frontend/src/uxbox/main/ui/shapes/common.cljs b/frontend/src/uxbox/main/ui/shapes/common.cljs index a84755b0e..b807c89b9 100644 --- a/frontend/src/uxbox/main/ui/shapes/common.cljs +++ b/frontend/src/uxbox/main/ui/shapes/common.cljs @@ -10,11 +10,12 @@ [beicon.core :as rx] [potok.core :as ptk] [uxbox.store :as st] + [uxbox.main.geom :as geom] [uxbox.main.data.shapes :as uds] [uxbox.main.ui.keyboard :as kbd] [uxbox.main.ui.workspace.base :as wb] + [uxbox.util.geom.point :as gpt] [uxbox.util.rlocks :as rlocks] - [uxbox.main.geom :as geom] [uxbox.util.dom :as dom])) ;; --- Refs @@ -36,13 +37,19 @@ (defn start-move [] - (letfn [(on-start [shape] + (letfn [(on-move [shape delta] + (st/emit! (uds/apply-temporal-displacement shape delta))) + (on-stop [{:keys [id] :as shape}] + (rlocks/release! :shape/move) + (st/emit! (uds/apply-displacement shape))) + (on-start [shape] (let [stoper (->> (rx/map first wb/events-s) (rx/filter #(= % :mouse/up)) (rx/take 1)) - stream (rx/take-until stoper wb/mouse-delta-s) - on-move #(st/emit! (uds/move-shape shape %)) - on-stop #(rlocks/release! :shape/move)] + stream (->> wb/mouse-delta-s + (rx/take-until stoper)) + on-move (partial on-move shape) + on-stop (partial on-stop shape)] (when @wb/alignment-ref (st/emit! (uds/initial-align-shape shape))) (rx/subscribe stream on-move nil on-stop)))] diff --git a/frontend/src/uxbox/main/ui/shapes/group.cljs b/frontend/src/uxbox/main/ui/shapes/group.cljs index 172b02ec0..8c873add7 100644 --- a/frontend/src/uxbox/main/ui/shapes/group.cljs +++ b/frontend/src/uxbox/main/ui/shapes/group.cljs @@ -7,7 +7,7 @@ (ns uxbox.main.ui.shapes.group (:require [lentes.core :as l] [uxbox.store :as st] - [uxbox.util.mixins :as mx :include-macros true] + [uxbox.main.geom :as geom] [uxbox.main.ui.shapes.common :as common] [uxbox.main.ui.shapes.attrs :as attrs] [uxbox.main.ui.shapes.icon :as icon] @@ -16,7 +16,9 @@ [uxbox.main.ui.shapes.text :as text] [uxbox.main.ui.shapes.path :as path] [uxbox.main.ui.shapes.image :as image] - [uxbox.main.geom :as geom])) + [uxbox.util.geom.matrix :as gmt] + [uxbox.util.mixins :as mx :include-macros true])) + ;; --- Helpers @@ -64,12 +66,14 @@ (mx/defc group-shape {:mixins [mx/static mx/reactive]} - [{:keys [items id dx dy rotation] :as shape} factory] - (let [key (str "shape-" id) - rfm (geom/transformation-matrix shape) - attrs (merge {:id key :key key :transform (str rfm)} - (attrs/extract-style-attrs shape) - (attrs/make-debug-attrs shape))] + [{:keys [id items tmp-resize-xform tmp-displacement] :as shape} factory] + (let [xfmt (cond-> (or tmp-resize-xform (gmt/matrix)) + tmp-displacement (gmt/translate tmp-displacement)) + + props {:id (str id) + :transform (str xfmt)} + + attrs (merge props (attrs/extract-style-attrs shape))] [:g attrs (for [item (reverse items) :let [key (str item)]] diff --git a/frontend/src/uxbox/main/ui/shapes/icon.cljs b/frontend/src/uxbox/main/ui/shapes/icon.cljs index 64208eda7..cd161d0e2 100644 --- a/frontend/src/uxbox/main/ui/shapes/icon.cljs +++ b/frontend/src/uxbox/main/ui/shapes/icon.cljs @@ -5,10 +5,11 @@ ;; Copyright (c) 2016 Andrey Antukh (ns uxbox.main.ui.shapes.icon - (:require [uxbox.util.mixins :as mx :include-macros true] - [uxbox.main.ui.shapes.common :as common] + (:require [uxbox.main.ui.shapes.common :as common] [uxbox.main.ui.shapes.attrs :as attrs] - [uxbox.main.geom :as geom])) + [uxbox.main.geom :as geom] + [uxbox.util.mixins :as mx :include-macros true] + [uxbox.util.geom.matrix :as gmt])) ;; --- Icon Component @@ -28,19 +29,30 @@ (mx/defc icon-shape {:mixins [mx/static]} - [{:keys [x1 y1 content id metadata] :as shape} factory] - (let [key (str "shape-" id) - ;; rfm (geom/transformation-matrix shape) + [shape] + (let [{:keys [x1 y1 content id metadata + width height + tmp-resize-xform + tmp-displacement]} (geom/size shape) + + [_ _ orw orh] (:view-box metadata) + scalex (/ width orw) + scaley (/ height orh) + view-box (apply str (interpose " " (:view-box metadata))) - size (geom/size shape) - attrs (merge {:id key :key key ;; :transform (str rfm) - :x x1 :y y1 :view-box view-box - :preserve-aspect-ratio "none" - :dangerouslySetInnerHTML {:__html content}} - size - (attrs/extract-style-attrs shape) - (attrs/make-debug-attrs shape))] - [:svg attrs])) + + xfmt (cond-> (or tmp-resize-xform (gmt/matrix)) + tmp-displacement (gmt/translate tmp-displacement) + true (gmt/translate x1 y1) + true (gmt/scale scalex scaley)) + + props {:id (str id) + :preserve-aspect-ratio "none" + :dangerouslySetInnerHTML {:__html content} + :transform (str xfmt)} + + attrs (merge props (attrs/extract-style-attrs shape))] + [:g attrs])) ;; --- Icon SVG @@ -48,7 +60,7 @@ {:mixins [mx/static]} [{:keys [content id metadata] :as shape}] (let [view-box (apply str (interpose " " (:view-box metadata))) - id (str "icon-svg-" id) - props {:view-box view-box :id id + props {:view-box view-box + :id (str id) :dangerouslySetInnerHTML {:__html content}}] [:svg props])) diff --git a/frontend/src/uxbox/main/ui/shapes/image.cljs b/frontend/src/uxbox/main/ui/shapes/image.cljs index f1c60c3a1..9478d5498 100644 --- a/frontend/src/uxbox/main/ui/shapes/image.cljs +++ b/frontend/src/uxbox/main/ui/shapes/image.cljs @@ -7,14 +7,14 @@ (ns uxbox.main.ui.shapes.image (:require [beicon.core :as rx] [lentes.core :as l] - [uxbox.util.mixins :as mx :include-macros true] - [uxbox.util.http :as http] [potok.core :as ptk] [uxbox.store :as st] [uxbox.main.ui.shapes.common :as common] [uxbox.main.ui.shapes.attrs :as attrs] [uxbox.main.data.images :as udi] - [uxbox.main.geom :as geom])) + [uxbox.main.geom :as geom] + [uxbox.util.mixins :as mx :include-macros true] + [uxbox.util.geom.matrix :as gmt])) ;; --- Refs @@ -51,13 +51,22 @@ (mx/defc image-shape {:mixins [mx/static]} - [{:keys [id x1 y1 image] :as shape}] - (let [key (str "shape-" id) - ;; rfm (geom/transformation-matrix shape) - size (geom/size shape) - props {:x x1 :y y1 :id key :key key + [shape] + (let [{:keys [id x1 y1 image + width height + tmp-resize-xform + tmp-displacement]} (geom/size shape) + + xfmt (cond-> (or tmp-resize-xform (gmt/matrix)) + tmp-displacement (gmt/translate tmp-displacement)) + + props {:x x1 :y y1 + :id (str id) :preserve-aspect-ratio "none" - :xlink-href (:url image)} - attrs (-> (attrs/extract-style-attrs shape) - (merge props size))] + :xlink-href (:url image) + :transform (str xfmt) + :width width + :height height} + + attrs (merge props (attrs/extract-style-attrs shape))] [:image attrs])) diff --git a/frontend/src/uxbox/main/ui/shapes/path.cljs b/frontend/src/uxbox/main/ui/shapes/path.cljs index 91ca3cfcd..4143c4b89 100644 --- a/frontend/src/uxbox/main/ui/shapes/path.cljs +++ b/frontend/src/uxbox/main/ui/shapes/path.cljs @@ -5,13 +5,14 @@ ;; Copyright (c) 2016 Andrey Antukh (ns uxbox.main.ui.shapes.path - (:require [uxbox.util.mixins :as mx :include-macros true] - [potok.core :as ptk] + (:require [potok.core :as ptk] [uxbox.store :as st] [uxbox.main.ui.shapes.common :as common] [uxbox.main.ui.shapes.attrs :as attrs] [uxbox.main.data.shapes :as uds] - [uxbox.main.geom :as geom])) + [uxbox.main.geom :as geom] + [uxbox.util.geom.matrix :as gmt] + [uxbox.util.mixins :as mx :include-macros true])) ;; --- Path Component @@ -45,11 +46,11 @@ (mx/defc path-shape {:mixins [mx/static]} - [{:keys [id drawing?] :as shape}] - (let [key (str "shape-" id) - rfm (geom/transformation-matrix shape) - attrs (-> (attrs/extract-style-attrs shape) - (merge {:id key :key key :d (render-path shape)}) - (merge (when-not drawing? - #_{:transform (str rfm)})))] + [{:keys [id tmp-resize-xform tmp-displacement] :as shape}] + (let [xfmt (cond-> (or tmp-resize-xform (gmt/matrix)) + tmp-displacement (gmt/translate tmp-displacement)) + props {:transform (str xfmt) + :id (str id) + :d (render-path shape)} + attrs (merge props (attrs/extract-style-attrs shape))] [:path attrs])) diff --git a/frontend/src/uxbox/main/ui/shapes/rect.cljs b/frontend/src/uxbox/main/ui/shapes/rect.cljs index 2ce55e0ef..598d85ced 100644 --- a/frontend/src/uxbox/main/ui/shapes/rect.cljs +++ b/frontend/src/uxbox/main/ui/shapes/rect.cljs @@ -7,8 +7,10 @@ (ns uxbox.main.ui.shapes.rect (:require [uxbox.main.ui.shapes.common :as common] [uxbox.main.ui.shapes.attrs :as attrs] - [uxbox.util.mixins :as mx :include-macros true] [uxbox.main.geom :as geom] + [uxbox.util.geom.matrix :as gmt] + [uxbox.util.geom.point :as gpt] + [uxbox.util.mixins :as mx :include-macros true] [uxbox.util.dom :as dom])) ;; --- Rect Component @@ -23,18 +25,26 @@ selected? (contains? selected id) on-mouse-down #(common/on-mouse-down % shape selected)] [:g.shape {:class (when selected? "selected") - :on-mouse-down on-mouse-down} + :on-mouse-down on-mouse-down + } (rect-shape shape identity)])) ;; --- Rect Shape (mx/defc rect-shape {:mixins [mx/static]} - [{:keys [id x1 y1] :as shape}] - (let [key (str "shape-" id) - rfm (geom/transformation-matrix shape) - size (geom/size shape) - props {:x x1 :y y1 :id key :key key :transform (str rfm)} - attrs (-> (attrs/extract-style-attrs shape) - (merge props size))] + [shape] + (let [{:keys [id x1 y1 width height + tmp-resize-xform + tmp-displacement]} (geom/size shape) + + xfmt (cond-> (or tmp-resize-xform (gmt/matrix)) + tmp-displacement (gmt/translate tmp-displacement)) + + props {:x x1 :y y1 :id id + :width width + :height height + :transform (str xfmt)} + + attrs (merge (attrs/extract-style-attrs shape) props)] [:rect attrs])) diff --git a/frontend/src/uxbox/main/ui/shapes/selection.cljs b/frontend/src/uxbox/main/ui/shapes/selection.cljs index e4035ab1b..cd4d5b723 100644 --- a/frontend/src/uxbox/main/ui/shapes/selection.cljs +++ b/frontend/src/uxbox/main/ui/shapes/selection.cljs @@ -9,14 +9,16 @@ "Multiple selection handlers component." (:require [lentes.core :as l] [beicon.core :as rx] - [uxbox.store :as st] - [uxbox.util.mixins :as mx :include-macros true] [potok.core :as ptk] + [uxbox.store :as st] + [uxbox.main.constants :as c] [uxbox.main.data.shapes :as uds] [uxbox.main.ui.workspace.base :as wb] - [uxbox.util.rlocks :as rlocks] [uxbox.main.ui.shapes.common :as scommon] [uxbox.main.geom :as geom] + [uxbox.util.mixins :as mx :include-macros true] + [uxbox.util.rlocks :as rlocks] + [uxbox.util.geom.matrix :as gmt] [uxbox.util.geom.point :as gpt] [uxbox.util.dom :as dom])) @@ -41,115 +43,257 @@ (def edition-ref scommon/edition-ref) +(defn transform-rect + [{:keys [x1 y1 x2 y2] :as shape} xfmt] + (if xfmt + (let [tl (gpt/transform [x1 y1] xfmt) + tr (gpt/transform [x2 y1] xfmt) + bl (gpt/transform [x1 y2] xfmt) + br (gpt/transform [x2 y2] xfmt) + minx (apply min (map :x [tl tr bl br])) + maxx (apply max (map :x [tl tr bl br])) + miny (apply min (map :y [tl tr bl br])) + maxy (apply max (map :y [tl tr bl br]))] + (assoc shape + :x1 minx + :y1 miny + :x2 maxx + :y2 maxy)) + shape)) + ;; --- Resize Implementation (defn- start-resize - [vid sid] - (letfn [(on-resize [[delta ctrl?]] - (let [params {:vid vid :delta (assoc delta :lock ctrl?)}] - (st/emit! (uds/update-vertex-position sid params)))) + [vid ids shape] + (letfn [(gen-matrix [shape {scalex :x scaley :y}] + (case vid + :top-left + (-> (gmt/matrix) + (gmt/translate (+ (:x2 shape)) + (+ (:y2 shape))) + (gmt/scale scalex scaley) + (gmt/translate (- (:x2 shape)) + (- (:y2 shape)))) + + :top-right + (-> (gmt/matrix) + (gmt/translate (+ (:x1 shape)) + (+ (:y2 shape))) + (gmt/scale scalex scaley) + (gmt/translate (- (:x1 shape)) + (- (:y2 shape)))) + + :top + (-> (gmt/matrix) + (gmt/translate (+ (:x1 shape)) + (+ (:y2 shape))) + (gmt/scale scalex scaley) + (gmt/translate (- (:x1 shape)) + (- (:y2 shape)))) + + :bottom-left + (-> (gmt/matrix) + (gmt/translate (+ (:x2 shape)) + (+ (:y1 shape))) + (gmt/scale scalex scaley) + (gmt/translate (- (:x2 shape)) + (- (:y1 shape)))) + + :bottom-right + (-> (gmt/matrix) + (gmt/translate (+ (:x1 shape)) + (+ (:y1 shape))) + (gmt/scale scalex scaley) + (gmt/translate (- (:x1 shape)) + (- (:y1 shape)))) + + :bottom + (-> (gmt/matrix) + (gmt/translate (+ (:x1 shape)) + (+ (:y1 shape))) + (gmt/scale scalex scaley) + (gmt/translate (- (:x1 shape)) + (- (:y1 shape)))) + + :right + (-> (gmt/matrix) + (gmt/translate (+ (:x1 shape)) + (+ (:y1 shape))) + (gmt/scale scalex scaley) + (gmt/translate (- (:x1 shape)) + (- (:y1 shape)))) + + :left + (-> (gmt/matrix) + (gmt/translate (+ (:x2 shape)) + (+ (:y1 shape))) + (gmt/scale scalex scaley) + (gmt/translate (- (:x2 shape)) + (- (:y1 shape)))) + )) + + (on-resize [shape scale] + (let [mt (gen-matrix shape scale) + xf (map #(uds/apply-temporal-resize-matrix % mt))] + (apply st/emit! (sequence xf ids)))) + (on-end [] - (rlocks/release! :shape/resize))] + (apply st/emit! (map uds/apply-resize-matrix ids)) + (rlocks/release! :shape/resize)) + (calculate-ratio [orig-shape {:keys [width height] :as shape}] + {:x (/ width (:width orig-shape)) + :y (/ height (:height orig-shape))}) + (apply-delta [shape [{:keys [x y] :as point} ctrl?]] + (case vid + :top-left + (let [width (- (:x2 shape) x) + height (- (:y2 shape) y) + proportion (:proportion shape)] + (assoc shape + :width width + :height (if ctrl? (/ width proportion) height))) + + :top-right + (let [width (- x (:x1 shape)) + height (- (:y2 shape) y) + proportion (:proportion shape)] + (assoc shape + :width width + :height (if ctrl? (/ width proportion) height))) + + :top + (let [width (- (:x2 shape) (:x1 shape)) + height (- (:y2 shape) y) + proportion (:proportion shape)] + (assoc shape + :width width + :height (if ctrl? (/ width proportion) height))) + + :bottom-left + (let [width (- (:x2 shape) x) + height (- y (:y1 shape)) + proportion (:proportion shape)] + (assoc shape + :width width + :height (if ctrl? (/ width proportion) height))) + + :bottom-right + (let [width (- x (:x1 shape)) + height (- y (:y1 shape)) + proportion (:proportion shape)] + (assoc shape + :width width + :height (if ctrl? (/ width proportion) height))) + + :bottom + (let [width (- (:x2 shape) (:x1 shape)) + height (- y (:y1 shape)) + proportion (:proportion shape)] + (assoc shape + :width width + :height (if ctrl? (/ width proportion) height))) + + :left + (let [width (- (:x2 shape) x) + height (- (:y2 shape) (:y1 shape)) + proportion (:proportion shape)] + (assoc shape + :width width + :height (if ctrl? (/ width proportion) height))) + + :right + (let [width (- x (:x1 shape)) + height (- (:y2 shape) (:y1 shape)) + proportion (:proportion shape)] + (assoc shape + :width width + :height (if ctrl? (/ width proportion) height))) + + ))] + (let [stoper (->> wb/events-s (rx/map first) (rx/filter #(= % :mouse/up)) (rx/take 1)) - stream (->> wb/mouse-delta-s + stream (->> wb/mouse-canvas-s + (rx/map #(gpt/divide % @wb/zoom-ref)) + (rx/mapcat (fn [point] + (if @wb/alignment-ref + (uds/align-point point) + (rx/of point)))) (rx/take-until stoper) - (rx/with-latest-from vector wb/mouse-ctrl-s))] + (rx/with-latest-from vector wb/mouse-ctrl-s) + (rx/scan apply-delta shape) + (rx/map (partial calculate-ratio shape)))] (rlocks/acquire! :shape/resize) - (when @wb/alignment-ref - (st/emit! (uds/initial-vertext-align sid vid))) - (rx/subscribe stream on-resize nil on-end)))) + (rx/subscribe stream + (partial on-resize shape) + nil + on-end)))) + +;; --- Controls (Component) + +(mx/defc controls + {:mixins [mx/static]} + [{:keys [width height x1 y1]} zoom on-mouse-down] + [:g.controls + [:rect.main {:x x1 :y y1 + :width width + :height height + :stroke-dasharray (str (/ 5.0 zoom) "," (/ 5 zoom)) + :style {:stroke "#333" :fill "transparent" + :stroke-opacity "1"}}] + [:circle.top + (merge +circle-props+ + {:on-mouse-down #(on-mouse-down :top %) + :r (/ 6.0 zoom) + :cx (+ x1 (/ width 2)) + :cy (- y1 2)})] + [:circle.right + (merge +circle-props+ + {:on-mouse-down #(on-mouse-down :right %) + :r (/ 6.0 zoom) + :cy (+ y1 (/ height 2)) + :cx (+ x1 width 1)})] + [:circle.bottom + (merge +circle-props+ + {:on-mouse-down #(on-mouse-down :bottom %) + :r (/ 6.0 zoom) + :cx (+ x1 (/ width 2)) + :cy (+ y1 height 2)})] + [:circle.left + (merge +circle-props+ + {:on-mouse-down #(on-mouse-down :left %) + :r (/ 6.0 zoom) + :cy (+ y1 (/ height 2)) + :cx (- x1 3)})] + [:circle.top-left + (merge +circle-props+ + {:on-mouse-down #(on-mouse-down :top-left %) + :r (/ 6.0 zoom) + :cx x1 + :cy y1})] + [:circle.top-right + (merge +circle-props+ + {:on-mouse-down #(on-mouse-down :top-right %) + :r (/ 6.0 zoom) + :cx (+ x1 width) + :cy y1})] + [:circle.bottom-left + (merge +circle-props+ + {:on-mouse-down #(on-mouse-down :bottom-left %) + :r (/ 6.0 zoom) + :cx x1 + :cy (+ y1 height)})] + [:circle.bottom-right + (merge +circle-props+ + {:on-mouse-down #(on-mouse-down :bottom-right %) + :r (/ 6.0 zoom) + :cx (+ x1 width) + :cy (+ y1 height)})]]) ;; --- Selection Handlers (Component) -(mx/defc multiple-selection-handlers - [shapes] - (let [{:keys [width height x y]} (geom/outer-rect-coll shapes)] - [:g.controls - [:rect.main {:x x :y y - :width width - :height height - :stroke-dasharray "5,5" - :style {:stroke "#333" :fill "transparent" - :stroke-opacity "1"}}]])) - -(mx/defc single-not-editable-selection-handlers - [{:keys [id] :as shape} zoom] - (let [{:keys [width height x y]} (geom/outer-rect shape)] - [:g.controls - [:rect.main {:x x :y y - :width width - :height height - :stroke-dasharray (str (/ 5.0 zoom) "," (/ 5.0 zoom)) - :style {:stroke "#333" - :fill "transparent" - :stroke-opacity "1"}}]])) - -(mx/defc single-selection-handlers - [{:keys [id] :as shape} zoom] - (letfn [(on-mouse-down [vid event] - (dom/stop-propagation event) - (start-resize vid id))] - (let [{:keys [x y width height]} (geom/outer-rect shape)] - [:g.controls - [:rect.main {:x x :y y - :width width - :height height - :stroke-dasharray (str (/ 5.0 zoom) "," (/ 5.0 zoom)) - :style {:stroke "#333" - :fill "transparent" - :stroke-opacity "1"}}] - [:circle.top - (merge +circle-props+ - {:on-mouse-down #(on-mouse-down :top %) - :r (/ 6.0 zoom) - :cx (+ x (/ width 2)) - :cy (- y 2)})] - [:circle.right - (merge +circle-props+ - {:on-mouse-down #(on-mouse-down :right %) - :r (/ 6.0 zoom) - :cy (+ y (/ height 2)) - :cx (+ x width 1)})] - [:circle.bottom - (merge +circle-props+ - {:on-mouse-down #(on-mouse-down :bottom %) - :r (/ 6.0 zoom) - :cx (+ x (/ width 2)) - :cy (+ y height 2)})] - [:circle.left - (merge +circle-props+ - {:on-mouse-down #(on-mouse-down :left %) - :r (/ 6.0 zoom) - :cy (+ y (/ height 2)) - :cx (- x 3)})] - [:circle.top-left - (merge +circle-props+ - {:on-mouse-down #(on-mouse-down :top-left %) - :r (/ 6.0 zoom) - :cx x - :cy y})] - [:circle.top-right - (merge +circle-props+ - {:on-mouse-down #(on-mouse-down :top-right %) - :r (/ 6.0 zoom) - :cx (+ x width) - :cy y})] - [:circle.bottom-left - (merge +circle-props+ - {:on-mouse-down #(on-mouse-down :bottom-left %) - :r (/ 6.0 zoom) - :cx x - :cy (+ y height)})] - [:circle.bottom-right - (merge +circle-props+ - {:on-mouse-down #(on-mouse-down :bottom-right %) - :r (/ 6.0 zoom) - :cx (+ x width) - :cy (+ y height)})]]))) - (defn start-path-edition [shape-id index] (letfn [(on-move [delta] @@ -181,6 +325,43 @@ :stroke "#28c4d4" :style {:cursor "pointer"}}])])) +(mx/defc multiple-selection-handlers + {:mixins [mx/static]} + [[shape & rest :as shapes] zoom] + (let [resize-xf (:tmp-resize-xform shape (gmt/matrix)) + displc-xf (-> (:tmp-displacement shape (gpt/point 0 0)) + (gmt/translate-matrix)) + selection (-> (geom/shapes->rect-shape shapes) + (assoc :type :rect) + (geom/transform resize-xf) + (geom/transform displc-xf) + (geom/size)) + on-click #(do (dom/stop-propagation %2) + (start-resize %1 (map :id shapes) selection))] + ;; (println "single-selection-handlers" displc-xf) + ;; (println "single-selection-handlers" (select-keys selection [:x1 :y1 :x2 :y2])) + ;; (println "single-selection-handlers" (select-keys selection [:x1 :y1 :width :height])) + (controls selection zoom on-click))) + +(mx/defc single-selection-handlers + {:mixins [mx/static]} + [{:keys [id] :as shape} zoom] + (let [resize-xf (:tmp-resize-xform shape (gmt/matrix)) + displc-xf (-> (:tmp-displacement shape (gpt/point 0 0)) + (gmt/translate-matrix)) + selection (-> (geom/shape->rect-shape shape) + ;; (transform-rect resize-xf) + (assoc :type :rect) + (geom/transform resize-xf) + (geom/transform displc-xf) + (geom/size)) + on-click #(do (dom/stop-propagation %2) + (start-resize %1 #{id} selection))] + ;; (println "single-selection-handlers" displc-xf) + ;; (println "single-selection-handlers" (select-keys selection [:x1 :y1 :x2 :y2])) + ;; (println "single-selection-handlers" (select-keys selection [:x1 :y1 :width :height])) + (controls selection zoom on-click))) + (mx/defc selection-handlers {:mixins [mx/reactive mx/static]} [] @@ -196,20 +377,10 @@ (> shapes-num 1) (multiple-selection-handlers shapes zoom) + (= :path (:type shape)) + (if (= @edition-ref (:id shape)) + (path-edition-selection-handlers shape zoom) + (single-selection-handlers shape zoom)) + :else - (cond - (= :path (:type shape)) - (if (= @edition-ref (:id shape)) - (path-edition-selection-handlers shape zoom) - (single-not-editable-selection-handlers shape zoom)) - - (= :text (:type shape)) - (if (= @edition-ref (:id shape)) - (single-not-editable-selection-handlers shape zoom) - (single-selection-handlers (first shapes) zoom)) - - (= :group (:type shape)) - (single-not-editable-selection-handlers shape zoom) - - :else - (single-selection-handlers (first shapes) zoom))))) + (single-selection-handlers shape zoom)))) diff --git a/frontend/src/uxbox/main/ui/shapes/text.cljs b/frontend/src/uxbox/main/ui/shapes/text.cljs index 8acd4e75d..584e5df06 100644 --- a/frontend/src/uxbox/main/ui/shapes/text.cljs +++ b/frontend/src/uxbox/main/ui/shapes/text.cljs @@ -9,15 +9,16 @@ [lentes.core :as l] [goog.events :as events] [potok.core :as ptk] - [uxbox.util.mixins :as mx :include-macros true] - [uxbox.util.color :as color] - [uxbox.util.dom :as dom] [uxbox.store :as st] + [uxbox.main.geom :as geom] [uxbox.main.data.shapes :as uds] [uxbox.main.ui.shapes.common :as common] [uxbox.main.ui.shapes.attrs :as attrs] + [uxbox.util.color :as color] + [uxbox.util.dom :as dom] + [uxbox.util.geom.matrix :as gmt] [uxbox.util.rlocks :as rlocks] - [uxbox.main.geom :as geom]) + [uxbox.util.mixins :as mx :include-macros true]) (:import goog.events.EventType)) ;; --- Events @@ -106,8 +107,8 @@ [{:keys [id x1 y1 content] :as shape}] (let [size (geom/size shape) style (make-style shape) - rfm (geom/transformation-matrix shape) - props {:x x1 :y y1 :transform (str rfm)} + ;; rfm (geom/transformation-matrix shape) + props {:x x1 :y y1} ;; :transform (str rfm)} props (merge props size)] (letfn [#_(on-blur [ev] (rlocks/release! :ui/text-edit) @@ -126,15 +127,38 @@ ;; --- Text Shape +;; NOTE: this is a hack for the browser rendering. +;; +;; Without forcing rerender, when the shape is displaced +;; and only x and y attrs are updated in dom, the whole content +;; of the foreignObject becomes sometimes partially or +;; completelly invisible. The complete dom rerender fixes that +;; problem. + +(defn- text-shape-did-update + [own] + (let [pref (mx/ref-node own "fo") + html (.-innerHTML pref)] + (set! (.-innerHTML pref) html) + own)) + (mx/defc text-shape - {:mixins [mx/static]} - [{:keys [id x1 y1 content] :as shape}] - (let [key (str "shape-" id) - rfm (geom/transformation-matrix shape) - size (geom/size shape) - props {:x x1 :y y1 - :transform (str rfm)} - attrs (merge props size) - style (make-style shape)] - [:foreignObject attrs - [:p {:style style} content]])) + {:mixins [mx/static] + :did-update text-shape-did-update} + [{:keys [tmp-resize-xform] :as shape}] + (let [shape (cond-> (geom/size shape) + tmp-resize-xform (geom/transform shape tmp-resize-xform)) + + {:keys [id x1 y1 content + width height + tmp-displacement]} (geom/size shape) + + xfmt (cond-> (gmt/matrix) + tmp-displacement (gmt/translate tmp-displacement)) + + style (make-style shape) + props {:x x1 :y y1 :id (str id) :ref "fo" + :width width :height height + :transform (str xfmt)}] + [:foreignObject props + [:p {:ref "p" :style style} content]])) diff --git a/frontend/src/uxbox/util/transit.cljs b/frontend/src/uxbox/util/transit.cljs index 534c97f4d..47d2fb752 100644 --- a/frontend/src/uxbox/util/transit.cljs +++ b/frontend/src/uxbox/util/transit.cljs @@ -65,5 +65,10 @@ (defn encode [data] - (let [w (t/writer :json {:handlers +write-handlers+})] - (t/write w data))) + (try + (let [w (t/writer :json {:handlers +write-handlers+})] + (t/write w data)) + (catch :default e + (println "data:" data) + (throw e)))) + diff --git a/frontend/src/uxbox/view/ui/viewer/interactions.cljs b/frontend/src/uxbox/view/ui/viewer/interactions.cljs index 2bddaa1bf..2cfa213f7 100644 --- a/frontend/src/uxbox/view/ui/viewer/interactions.cljs +++ b/frontend/src/uxbox/view/ui/viewer/interactions.cljs @@ -116,7 +116,7 @@ [{:keys [x1 y1 rotation] :as shape} {:keys [resize-width resize-height easing element delay duration direction] :as opts}] - (if (= direction :reverse) + #_(if (= direction :reverse) (let [end (geom/transformation-matrix shape)] (animate :targets [(str "#shape-" element)] :transform (str end) @@ -165,7 +165,7 @@ (defn- run-rotate-interaction [{:keys [element rotation direction easing delay duration] :as opts}] - (let [shape (get-in @st/state [:shapes element]) + #_(let [shape (get-in @st/state [:shapes element]) dom (dom/get-element (str "shape-" element)) mtx1 (geom/transformation-matrix (update shape :rotation + rotation)) mtx2 (geom/transformation-matrix shape)] diff --git a/frontend/src/uxbox/view/ui/viewer/shapes.cljs b/frontend/src/uxbox/view/ui/viewer/shapes.cljs index 8f76d4e73..7623f651d 100644 --- a/frontend/src/uxbox/view/ui/viewer/shapes.cljs +++ b/frontend/src/uxbox/view/ui/viewer/shapes.cljs @@ -52,15 +52,15 @@ {:pre [(map? shape)]} (let [show-itx? (and (mx/react itx-flag-ref) (not (empty? (:interactions shape)))) - rect (geom/inner-rect shape)] + rect (geom/shape->rect-shape shape)] [:g {:id (str "itx-" (:id shape)) :style (when show-itx? {:cursor "pointer"})} (factory shape) (when show-itx? [:circle {:fill "#78dbbe" - :cx (:x rect) - :cy (:y rect) + :cx (:x1 rect) + :cy (:y1 rect) :r 5}])])) ;; --- Shapes