diff --git a/frontend/src/uxbox/main/data/workspace.cljs b/frontend/src/uxbox/main/data/workspace.cljs index 0b2f345f5..24fc879c4 100644 --- a/frontend/src/uxbox/main/data/workspace.cljs +++ b/frontend/src/uxbox/main/data/workspace.cljs @@ -77,7 +77,8 @@ :drawing nil :drawing-tool nil :tooltip nil - :options-mode :design}) + :options-mode :design + :draw-interaction-to nil}) (def initialize-layout (ptk/reify ::initialize-layout @@ -1418,6 +1419,63 @@ :index index-in-parent}]] (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})))))))) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Interactions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(declare move-create-interaction) +(declare finish-create-interaction) + +(defn start-create-interaction + [] + (ptk/reify ::start-create-interaction + ptk/WatchEvent + (watch [_ state stream] + (let [initial-pos @ms/mouse-position + selected (get-in state [:workspace-local :selected]) + stopper (rx/filter ms/mouse-up? stream)] + (when (= 1 (count selected)) + (rx/concat + (->> ms/mouse-position + (rx/take-until stopper) + (rx/map #(move-create-interaction initial-pos %))) + (rx/of (finish-create-interaction initial-pos)))))))) + +(defn move-create-interaction + [initial-pos position] + (ptk/reify ::move-create-interaction + ptk/UpdateEvent + (update [_ state] + (if (= position initial-pos) + state + (assoc-in state [:workspace-local :draw-interaction-to] position))))) + +(defn finish-create-interaction + [initial-pos] + (ptk/reify ::finish-create-interaction + ptk/UpdateEvent + (update [_ state] + (assoc-in state [:workspace-local :draw-interaction-to] nil)) + + ptk/WatchEvent + (watch [_ state stream] + (let [position @ms/mouse-position + + page-id (:current-page-id state) + objects (get-in state [:workspace-data page-id :objects]) + frame (dwc/get-frame-at-point objects position) + + shape-id (first (get-in state [:workspace-local :selected]))] + + (when-not (= position initial-pos) + (if (and frame shape-id (not= (:id frame) shape-id)) + (rx/of (update-shape shape-id + {:interactions [{:event-type :click + :action-type :navigate + :destination (:id frame)}]})) + (rx/of (update-shape shape-id + {:interactions []})))))))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Exports ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/frontend/src/uxbox/main/data/workspace/common.cljs b/frontend/src/uxbox/main/data/workspace/common.cljs index 624ce3fad..bfcb67f9a 100644 --- a/frontend/src/uxbox/main/data/workspace/common.cljs +++ b/frontend/src/uxbox/main/data/workspace/common.cljs @@ -210,6 +210,15 @@ (when-not (empty? rch) (rx/of (commit-changes rch uch {:commit-local? true}))))))) + +(defn get-frame-at-point + [objects point] + (let [frames (cp/select-frames objects)] + (loop [frame (first frames) + rest (rest frames)] + (d/seek #(geom/has-point? % point) frames)))) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Undo / Redo ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/frontend/src/uxbox/main/ui/workspace/shapes/common.cljs b/frontend/src/uxbox/main/ui/workspace/shapes/common.cljs index b702ada78..236248fea 100644 --- a/frontend/src/uxbox/main/ui/workspace/shapes/common.cljs +++ b/frontend/src/uxbox/main/ui/workspace/shapes/common.cljs @@ -24,6 +24,7 @@ (let [selected @refs/selected-shapes selected? (contains? selected id) drawing? @refs/selected-drawing-tool + options-mode @refs/options-mode button (.-which (.-nativeEvent event))] (when-not (:blocked shape) (cond @@ -36,27 +37,20 @@ (= type :frame) (when selected? (dom/stop-propagation event) - (st/emit! (dw/start-move-selected))) + (if (= options-mode :design) + (st/emit! (dw/start-move-selected)) + (st/emit! (dw/start-create-interaction)))) - (and (not selected?) (empty? selected)) - (do - (dom/stop-propagation event) - (st/emit! dw/deselect-all - (dw/select-shape id) - (dw/start-move-selected))) - - (and (not selected?) (not (empty? selected))) - (do - (dom/stop-propagation event) - (if (kbd/shift? event) - (st/emit! (dw/select-shape id)) - (st/emit! dw/deselect-all - (dw/select-shape id) - (dw/start-move-selected)))) :else (do (dom/stop-propagation event) - (st/emit! (dw/start-move-selected))))))) + (when-not selected? + (when-not (or (empty? selected) (kbd/shift? event)) + (st/emit! dw/deselect-all)) + (st/emit! (dw/select-shape id))) + (if (= options-mode :design) + (st/emit! (dw/start-move-selected)) + (st/emit! (dw/start-create-interaction)))))))) (defn on-context-menu [event shape] diff --git a/frontend/src/uxbox/main/ui/workspace/shapes/interactions.cljs b/frontend/src/uxbox/main/ui/workspace/shapes/interactions.cljs index 4f5dbd587..0b3cfe2f9 100644 --- a/frontend/src/uxbox/main/ui/workspace/shapes/interactions.cljs +++ b/frontend/src/uxbox/main/ui/workspace/shapes/interactions.cljs @@ -26,62 +26,21 @@ [shape] (first (filter #(= (:event-type %) :click) (:interactions shape)))) -(defn- on-mouse-down-unselected + +(defn- on-mouse-down [event {:keys [id type] :as shape} selected] (do (dom/stop-propagation event) - (if (empty? selected) - (st/emit! (dw/select-shape id) - (dw/start-move-selected)) + (when-not (empty? selected) + (st/emit! dw/deselect-all)) + (st/emit! (dw/select-shape id)) + (st/emit! (dw/start-create-interaction)))) - (if (kbd/shift? event) - (st/emit! (dw/select-shape id)) - (st/emit! dw/deselect-all - (dw/select-shape id) - (dw/start-move-selected)))))) -;; TODO: add more mouse behavior, to create interactions by drag&drop -;; (defn- on-mouse-down -;; [event {:keys [id type] :as shape}] -;; (let [selected @refs/selected-shapes -;; selected? (contains? selected id) -;; drawing? @refs/selected-drawing-tool -;; button (.-which (.-nativeEvent event))] -;; (when-not (:blocked shape) -;; (cond -;; (not= 1 button) -;; nil -;; -;; drawing? -;; nil -;; -;; (= type :frame) -;; (when selected? -;; (dom/stop-propagation event) -;; (st/emit! (dw/start-move-selected))) -;; -;; (and (not selected?) (empty? selected)) -;; (do -;; (dom/stop-propagation event) -;; (st/emit! dw/deselect-all -;; (dw/select-shape id) -;; (dw/start-move-selected))) -;; -;; (and (not selected?) (not (empty? selected))) -;; (do -;; (dom/stop-propagation event) -;; (if (kbd/shift? event) -;; (st/emit! (dw/select-shape id)) -;; (st/emit! dw/deselect-all -;; (dw/select-shape id) -;; (dw/start-move-selected)))) -;; :else -;; (do -;; (dom/stop-propagation event) -;; (st/emit! (dw/start-move-selected))))))) - -(mf/defc interaction-path - [{:keys [orig-shape dest-shape selected selected?] :as props}] +(defn connect-to-shape + "Calculate the best position to draw an interaction line + between two shapes" + [orig-shape dest-shape] (let [orig-rect (geom/selection-rect-shape orig-shape) dest-rect (geom/selection-rect-shape dest-shape) @@ -103,12 +62,48 @@ orig-x (if (= orig-pos :right) orig-x-right orig-x-left) dest-x (if (= dest-pos :right) dest-x-right dest-x-left) + orig-y (+ (:y orig-rect) (/ (:height orig-rect) 2)) + dest-y (+ (:y dest-rect) (/ (:height dest-rect) 2))] + + [orig-pos orig-x orig-y dest-pos dest-x dest-y])) + + +(defn connect-to-point + "Calculate the best position to draw an interaction line + between one shape and one point" + [orig-shape dest-point] + (let [orig-rect (geom/selection-rect-shape orig-shape) + + orig-x-left (:x orig-rect) + orig-x-right (+ orig-x-left (:width orig-rect)) + orig-x-center (+ orig-x-left (/ (:width orig-rect) 2)) + + dest-x (:x dest-point) + dest-y (:y dest-point) + + orig-pos (if (<= orig-x-right dest-x) :right + (if (>= orig-x-left dest-x) :left + (if (<= orig-x-center dest-x) :right :left))) + dest-pos (if (<= orig-x-right dest-x) :left + (if (>= orig-x-left dest-x) :right + (if (<= orig-x-center dest-x) :right :left))) + + orig-x (if (= orig-pos :right) orig-x-right orig-x-left) + orig-y (+ (:y orig-rect) (/ (:height orig-rect) 2))] + + [orig-pos orig-x orig-y dest-pos dest-x dest-y])) + + +(mf/defc interaction-path + [{:keys [orig-shape dest-shape dest-point selected selected?] :as props}] + (let [[orig-pos orig-x orig-y dest-pos dest-x dest-y] + (if dest-shape + (connect-to-shape orig-shape dest-shape) + (connect-to-point orig-shape dest-point)) + orig-dx (if (= orig-pos :right) 100 -100) dest-dx (if (= dest-pos :right) 100 -100) - orig-y (+ (:y orig-rect) (/ (:height orig-rect) 2)) - dest-y (+ (:y dest-rect) (/ (:height dest-rect) 2)) - path ["M" orig-x orig-y "C" (+ orig-x orig-dx) orig-y (+ dest-x dest-dx) dest-y dest-x dest-y] pdata (str/join " " path) @@ -122,16 +117,16 @@ :fill "transparent" :stroke-width 2 :d pdata - :on-mouse-down #(on-mouse-down-unselected % orig-shape selected)}] + :on-mouse-down #(on-mouse-down % orig-shape selected)}] - [:g + [:g {:on-mouse-down #(on-mouse-down % orig-shape selected)} [:path {:stroke "#31EFB8" :fill "transparent" :stroke-width 2 :d pdata}] [:circle {:cx orig-x :cy orig-y - :r 4 + :r 8 :stroke "#31EFB8" :stroke-width 2 :fill "#FFFFFF"}] @@ -149,9 +144,12 @@ (mf/defc interactions [{:keys [selected] :as props}] (let [data (mf/deref refs/workspace-data) + local (mf/deref refs/workspace-local) objects (:objects data) active-shapes (filter #(first (get-click-interaction %)) (vals objects)) - selected-shapes (map #(get objects %) selected)] + selected-shapes (map #(get objects %) selected) + draw-interaction-to (:draw-interaction-to local) + first-selected (first selected-shapes)] [:* (for [shape active-shapes] (let [interaction (get-click-interaction shape) @@ -164,13 +162,19 @@ :selected selected :selected? false}]))) - (for [shape selected-shapes] - (let [interaction (get-click-interaction shape) - dest-shape (get objects (:destination interaction))] - (when dest-shape - [:& interaction-path {:key (:id shape) - :orig-shape shape - :dest-shape dest-shape - :selected selected - :selected? true}])))])) + (if (and draw-interaction-to first-selected) + [:& interaction-path {:key "interactive" + :orig-shape first-selected + :dest-point draw-interaction-to + :selected? true}] + + (for [shape selected-shapes] + (let [interaction (get-click-interaction shape) + dest-shape (get objects (:destination interaction))] + (when dest-shape + [:& interaction-path {:key (:id shape) + :orig-shape shape + :dest-shape dest-shape + :selected selected + :selected? true}]))))]))