diff --git a/src/uxbox/main/ui/workspace/sidebar/options/interactions.cljs b/src/uxbox/main/ui/workspace/sidebar/options/interactions.cljs index bfbc9cbf0..4ea8c5225 100644 --- a/src/uxbox/main/ui/workspace/sidebar/options/interactions.cljs +++ b/src/uxbox/main/ui/workspace/sidebar/options/interactions.cljs @@ -48,14 +48,13 @@ :hover "Hover" :mousein "Mouse In" :mouseout "Mouse Out" - :swiperight "Swipe Right" - :swipeleft "Swipe Left" - :swipedown "Swipe Down" - :touchandhold "Touch and Hold" - :holdrelease "Hold release" + ;; :swiperight "Swipe Right" + ;; :swipeleft "Swipe Left" + ;; :swipedown "Swipe Down" + ;; :touchandhold "Touch and Hold" + ;; :holdrelease "Hold release" (pr-str trigger))) - (defn- interactions-list-render [own shape form-ref] (letfn [(on-edit [item event] @@ -105,11 +104,11 @@ [:option {:value ":hover"} "Hover"] [:option {:value ":mousein"} "Mouse in"] [:option {:value ":mouseout"} "Mouse out"] - [:option {:value ":swiperight"} "Swipe right"] - [:option {:value ":swipeleft"} "Swipe left"] - [:option {:value ":swipedown"} "Swipe dpwn"] - [:option {:value ":touchandhold"} "Touch and hold"] - [:option {:value ":holdrelease"} "Hold release"] + #_[:option {:value ":swiperight"} "Swipe right"] + #_[:option {:value ":swipeleft"} "Swipe left"] + #_[:option {:value ":swipedown"} "Swipe dpwn"] + #_[:option {:value ":touchandhold"} "Touch and hold"] + #_[:option {:value ":holdrelease"} "Hold release"] #_[:option {:value ":keypress"} "Key press"] #_[:option {:value ":pageisloaded"} "Page is loaded"] #_[:option {:value ":windowscroll"} "Window is scrolled to"]]]])) @@ -236,12 +235,12 @@ {:placeholder "X" :on-change (partial on-change form-ref :moveto-x) :type "number" - :value (:moveto-x @form-ref)}] + :value (:moveto-x @form-ref "")}] [:input.input-text {:placeholder "Y" :on-change (partial on-change form-ref :moveto-y) :type "number" - :value (:moveto-y @form-ref)}]]])) + :value (:moveto-y @form-ref "")}]]])) (def moveto-input (mx/component @@ -264,12 +263,12 @@ {:placeholder "X" :on-change (partial on-change form-ref :moveby-x) :type "number" - :value (:moveby-x @form-ref)}] + :value (:moveby-x @form-ref "")}] [:input.input-text {:placeholder "Y" :on-change (partial on-change form-ref :moveby-y) :type "number" - :value (:moveby-y @form-ref)}]]])) + :value (:moveby-y @form-ref "")}]]])) (def moveby-input (mx/component @@ -292,7 +291,7 @@ :min "0" :max "100" :type "number" - :value (:opacity @form-ref)}]]])) + :value (:opacity @form-ref "")}]]])) (def opacity-input (mx/component @@ -308,10 +307,10 @@ [:span "Rotate (dg)"] [:div.row-flex [:input.input-text - {:placeholder "px" - :on-change (partial on-change form-ref :rotate) + {:placeholder "dg" + :on-change (partial on-change form-ref :rotation) :type "number" - :value ""}]]])) + :value (:rotation @form-ref "")}]]])) (def rotate-input (mx/component @@ -330,12 +329,12 @@ {:placeholder "Width" :on-change (partial on-change form-ref :resize-width) :type "number" - :value ""}] + :value (:resize-width @form-ref "")}] [:input.input-text {:placeholder "Height" :on-change (partial on-change form-ref :resize-height) :type "number" - :value ""}]]])) + :value (:resize-height @form-ref "")}]]])) (def resize-input (mx/component @@ -416,7 +415,7 @@ (defn- easing-input-render [own form-ref] (when-not (:easing @form-ref) - (swap! form-ref assoc :easing :none)) + (swap! form-ref assoc :easing :linear)) (html [:div [:span "Easing"] @@ -425,7 +424,6 @@ {:placeholder "Easing" :on-change (partial on-change form-ref :easing) :value (pr-str (:easing @form-ref))} - [:option {:value ":none"} "None"] [:option {:value ":linear"} "Linear"] [:option {:value ":easein"} "Ease in"] [:option {:value ":easeout"} "Ease out"] @@ -486,12 +484,12 @@ [:option {:value ":show"} "Show"] [:option {:value ":hide"} "Hide"] [:option {:value ":toggle"} "Toggle"] - [:option {:value ":moveto"} "Move to"] + ;; [:option {:value ":moveto"} "Move to"] [:option {:value ":moveby"} "Move by"] [:option {:value ":opacity"} "Opacity"] [:option {:value ":size"} "Size"] [:option {:value ":color"} "Color"] - [:option {:value ":rotate"} "Rotate"] + ;; [:option {:value ":rotate"} "Rotate"] [:option {:value ":gotopage"} "Go to page"] [:option {:value ":gotourl"} "Go to URL"] #_[:option {:value ":goback"} "Go back"] diff --git a/src/uxbox/util/dom.cljs b/src/uxbox/util/dom.cljs index 0d6808f51..e526563b0 100644 --- a/src/uxbox/util/dom.cljs +++ b/src/uxbox/util/dom.cljs @@ -30,6 +30,10 @@ ([classname node] (dom/getElementByClass classname node))) +(defn get-element + [id] + (dom/getElement id)) + (defn stop-propagation [e] (when e diff --git a/src/uxbox/util/mixins.cljs b/src/uxbox/util/mixins.cljs index c437072b0..082aa5cf2 100644 --- a/src/uxbox/util/mixins.cljs +++ b/src/uxbox/util/mixins.cljs @@ -45,3 +45,4 @@ (def ref-node rum/ref-node) (def react rum/react) (def reactive rum/reactive) +(def dom-node rum/dom-node) diff --git a/src/uxbox/view/ui/viewer/interactions.cljs b/src/uxbox/view/ui/viewer/interactions.cljs index 4b69846a3..c6dba0475 100644 --- a/src/uxbox/view/ui/viewer/interactions.cljs +++ b/src/uxbox/view/ui/viewer/interactions.cljs @@ -5,82 +5,259 @@ ;; Copyright (c) 2016 Andrey Antukh (ns uxbox.view.ui.viewer.interactions - (:require [uxbox.util.dom :as dom] - [vendor.animejs])) + (:require [promesa.core :as p] + [uxbox.util.dom :as dom] + [uxbox.main.geom :as geom] + [uxbox.main.geom.matrix :as gmt] + [uxbox.main.geom.point :as gpt] + [uxbox.main.state :as st] + [vendor.snapsvg]) + (:import goog.events.EventType)) (defn- translate-trigger "Translates the interaction trigger name (keyword) into approriate dom event name (keyword)." [trigger] - {:pre [(keyword? trigger)] - :post [(keyword? %)]} + {:pre [(keyword? trigger)]} (case trigger - :click :on-click - :hover :on-hover - :doubleclick :on-double-click - :rightclick :on-context-menu - :mousein :on-mouse-enter - :mouseout :on-mouse-leave + :click EventType.CLICK + :doubleclick EventType.DBLCLICK + :rightclick EventType.CONTEXTMENU + :mousein EventType.MOUSEENTER + :mouseout EventType.MOUSELEAVE + :hover ::hover (throw (ex-info "not supported at this moment" {:trigger trigger})))) (defn- translate-ease "Translates the uxbox ease settings to one that are compatible with anime.js library." [ease] - {:pre [(keyword? ease)] - :post [(string? %)]} + {:pre [(keyword? ease)]} (case ease - :easein "easeInCubic" - :easeout "easeOutCubic" - :easeinout "easeInOutCubic" - (name ease))) + :linear js/mina.linear + :easein js/mina.easin + :easeout js/mina.easout + :easeinout js/mina.easeinout + (throw (ex-info "invalid ease value" {:ease ease})))) + +(defn- animate + [& opts] + (js/anime (clj->js (apply hash-map opts)))) + +(defn- animate* + [dom {:keys [delay duration easing] :as opts}] + (let [props (dissoc opts :delay :duration :easing) + snap (js/Snap. dom)] + (p/schedule delay #(.animate snap (clj->js props) duration easing)))) ;; --- Interactions to Animation Compilation -(defn- build-moveby-interaction - [{:keys [element moveby-x moveby-y easing delay duration]}] - (let [opts (clj->js {:targets [(str "#shape-" element)] - :translateX (str moveby-x "px") - :translateY (str moveby-y "px") - :easing (translate-ease easing) - :delay delay - :duration duration - :loop false})] - #(js/anime opts))) +(defn- run-moveby-interaction + [{:keys [element moveby-x moveby-y easing delay duration direction]}] + (let [dom (dom/get-element (str "shape-" element))] + (if (= direction :reverse) + (animate* dom {:transform (str "translate(" (- moveby-x)" " (- moveby-y) ")") + :easing (translate-ease easing) + :delay delay + :duration duration}) + (animate* dom {:transform (str "translate(" moveby-x " " moveby-y ")") + :easing (translate-ease easing) + :delay delay + :duration duration})))) -(defn- build-gotourl-interaction +(declare run-hide-interaction) + +(defn- run-show-interaction + [{:keys [element easing delay duration + animation direction] :as itx}] + (let [dom (dom/get-element (str "shape-" element))] + (if (= direction :reverse) + (run-hide-interaction (dissoc itx :direction)) + (animate* dom {:fillOpacity "1" + :strokeOpacity "1" + :easing (translate-ease easing) + :delay delay + :duration duration})))) + +(defn- run-hide-interaction + [{:keys [element easing delay duration + animation direction] :as itx}] + (let [dom (dom/get-element (str "shape-" element))] + (if (= direction :reverse) + (run-show-interaction (dissoc itx :direction)) + (animate* dom {:fillOpacity "0" + :strokeOpacity "0" + :easing (translate-ease easing) + :delay delay + :duration duration})))) + +(defn- run-opacity-interaction + [{:keys [element opacity easing delay + duration animation direction]}] + (let [shape (get-in @st/state [:shapes-by-id element]) + dom (dom/get-element (str "shape-" element))] + (if (= direction :reverse) + (animate* dom {:fillOpacity (:fill-opacity shape "1") + :strokeOpacity (:stroke-opacity shape "1") + :easing (translate-ease easing) + :delay delay + :duration duration}) + (animate* dom {:fillOpacity opacity + :strokeOpacity opacity + :easing (translate-ease easing) + :delay delay + :duration duration})))) + +;; (defn- run-size-interaction +;; [{:keys [element resize-width resize-height +;; easing delay duration] :as opts}] +;; (let [shape (get-in @st/state [:shapes-by-id element]) +;; tf (geom/transformation-matrix +;; (geom/resize shape (gpt/point resize-width resize-height)))] +;; (animate :targets [(str "#shape-" element)] +;; :transform (str tf) +;; :easing (translate-ease easing) +;; :delay delay +;; :duration duration +;; :loop false))) + +(defn- run-size-interaction-icon + [{:keys [x1 y1 view-box rotation] :as shape} + {:keys [resize-width resize-height easing + element delay duration direction] :as opts}] + (if (= direction :reverse) + (let [end (geom/transformation-matrix shape)] + (animate :targets [(str "#shape-" element)] + :transform (str end) + :easing (translate-ease easing) + :delay delay + :duration duration + :loop false)) + (let [orig-width (nth view-box 2) + orig-height (nth view-box 3) + scale-x (/ resize-width orig-width) + scale-y (/ resize-height orig-height) + center-x (- resize-width (/ resize-width 2)) + center-y (- resize-height (/ resize-height 2)) + end (-> (gmt/matrix) + (gmt/translate x1 y1) + (gmt/translate center-x center-y) + (gmt/rotate rotation) + (gmt/translate (- center-x) (- center-y)) + (gmt/scale scale-x scale-y)) + dom (dom/get-element (str "shape-" element))] + (animate* dom {:transform (str end) + :easing (translate-ease easing) + :delay delay + :duration duration})))) + +(defn- run-size-interaction-rect + [{:keys [x1 y1 rotation] :as shape} + {:keys [resize-width resize-height easing + element delay duration direction] :as opts}] + (if (= direction :reverse) + (let [end (geom/transformation-matrix shape)] + (animate :targets [(str "#shape-" element)] + :transform (str end) + :easing (translate-ease easing) + :delay delay + :duration duration + :loop false)) + (let [dom (dom/get-element (str "shape-" element))] + (animate* dom {:easing (translate-ease easing) + :delay delay + :duration duration + :width resize-width + :height resize-height})))) + +(defn- run-size-interaction + [{:keys [element] :as opts}] + (let [shape (get-in @st/state [:shapes-by-id element])] + (case (:type shape) + :icon (run-size-interaction-icon shape opts) + :rect (run-size-interaction-rect shape opts)))) + +(defn- run-gotourl-interaction [{:keys [url]}] - #(set! (.-href js/location) url)) + (set! (.-href js/location) url)) -(defn- build-interaction +(defn- run-color-interaction + [{:keys [element fill-color stroke-color direction easing delay duration]}] + (let [shape (get-in @st/state [:shapes-by-id element]) + dom (dom/get-element (str "shape-" element))] + (if (= direction :reverse) + (animate* dom {:easing (translate-ease easing) + :delay delay + :duration duration + :fill (:fill shape "#000000") + :stroke (:stroke shape "#000000")}) + (animate* dom {:easing (translate-ease easing) + :delay delay + :duration duration + :fill fill-color + :stroke stroke-color})))) + +(defn- run-rotate-interaction + [{:keys [element rotation direction easing delay duration] :as opts}] + (let [shape (get-in @st/state [:shapes-by-id element]) + dom (dom/get-element (str "shape-" element)) + mtx1 (geom/transformation-matrix (update shape :rotation + rotation)) + mtx2 (geom/transformation-matrix shape)] + (if (= direction :reverse) + (animate* dom {:easing (translate-ease easing) + :delay delay + :duration duration + :transform (str mtx2)}) + (animate* dom {:easing (translate-ease easing) + :delay delay + :duration duration + :transform (str mtx1)})))) + +(defn- run-interaction "Given an interaction data structure return a precompiled animation." [{:keys [action] :as itx}] (case action - :moveby (build-moveby-interaction itx) - :gotourl (build-gotourl-interaction itx) + :moveby (run-moveby-interaction itx) + :show (run-show-interaction itx) + :hide (run-hide-interaction itx) + :size (run-size-interaction itx) + :opacity (run-opacity-interaction itx) + :gotourl (run-gotourl-interaction itx) + :color (run-color-interaction itx) + :rotate (run-rotate-interaction itx) (throw (ex-info "undefined interaction" {:action action})))) ;; --- Main Api -(defn- default-callback - "A default user action callback that prevents default event." - [itx event] - (dom/prevent-default event) - (itx)) +(defn- build-hover-evt + "A special case for hover event." + [itx] + (letfn [(on-mouse-enter [event] + (dom/prevent-default event) + (run-interaction itx)) + (on-mouse-leave [event] + (dom/prevent-default event) + (run-interaction (assoc itx :direction :reverse)))] + [[EventType.MOUSEENTER on-mouse-enter] + [EventType.MOUSELEAVE on-mouse-leave]])) -(defn- build-attr +(defn- build-generic-evt "A reducer function that compiles interaction data structures into apropriate event handler attributes." - [acc {:keys [trigger] :as interaction}] - (let [evt (translate-trigger trigger) - itx (build-interaction interaction)] - (assoc acc evt (partial default-callback itx)))) + [evt itx] + (letfn [(on-event [event] + (dom/prevent-default event) + (run-interaction itx))] + [[evt on-event]])) -(defn build-attrs +(defn build-events "Compile a sequence of interactions into a hash-map of event-handlers." - [items] - (reduce build-attr {} items)) - - - + [shape] + (reduce (fn [acc itx] + (let [evt (translate-trigger (:trigger itx))] + (if (= evt ::hover) + (into acc (build-hover-evt itx)) + (into acc (build-generic-evt evt itx))))) + [] + (vals (:interactions shape)))) diff --git a/src/uxbox/view/ui/viewer/shapes.cljs b/src/uxbox/view/ui/viewer/shapes.cljs index 20bacf3ff..c1965205a 100644 --- a/src/uxbox/view/ui/viewer/shapes.cljs +++ b/src/uxbox/view/ui/viewer/shapes.cljs @@ -5,7 +5,8 @@ ;; Copyright (c) 2016 Andrey Antukh (ns uxbox.view.ui.viewer.shapes - (:require [uxbox.util.mixins :as mx :include-macros true] + (:require [goog.events :as events] + [uxbox.util.mixins :as mx :include-macros true] [uxbox.main.state :as st] [uxbox.main.ui.shapes.rect :refer (rect-shape)] [uxbox.main.ui.shapes.icon :refer (icon-shape)] @@ -13,15 +14,33 @@ [uxbox.main.ui.shapes.group :refer (group-shape)] [uxbox.main.ui.shapes.line :refer (line-shape)] [uxbox.main.ui.shapes.circle :refer (circle-shape)] - [uxbox.view.ui.viewer.interactions :as itx :refer (build-attrs)])) + [uxbox.view.ui.viewer.interactions :as itx]) + (:import goog.events.EventType)) ;; --- Interactions Wrapper +(defn- interactions-wrapper-did-mount + [own] + (let [dom (mx/dom-node own) + shape (first (:rum/args own)) + evnts (itx/build-events shape) + keys (reduce (fn [acc [evt callback]] + (conj acc (events/listen dom evt callback))) + [] + evnts)] + (assoc own ::keys keys))) + +(defn- interactions-wrapper-will-unmount + [own] + (let [keys (::keys own)] + (run! #(events/unlistenByKey %) keys) + (dissoc own ::keys))) + (mx/defc interactions-wrapper + {:did-mount interactions-wrapper-did-mount + :will-unmount interactions-wrapper-will-unmount} [shape factory] - (let [interactions (vals (:interactions shape)) - attrs (itx/build-attrs interactions)] - [:g attrs (factory shape)])) + [:g {:id (str "itx-" (:id shape))} (factory shape)]) ;; --- Shapes