mirror of
https://github.com/penpot/penpot.git
synced 2025-02-26 08:45:34 -05:00
More work on interactions.
This commit is contained in:
parent
eb62b106f3
commit
fbbffe0167
5 changed files with 276 additions and 77 deletions
|
@ -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"]
|
||||
|
|
|
@ -30,6 +30,10 @@
|
|||
([classname node]
|
||||
(dom/getElementByClass classname node)))
|
||||
|
||||
(defn get-element
|
||||
[id]
|
||||
(dom/getElement id))
|
||||
|
||||
(defn stop-propagation
|
||||
[e]
|
||||
(when e
|
||||
|
|
|
@ -45,3 +45,4 @@
|
|||
(def ref-node rum/ref-node)
|
||||
(def react rum/react)
|
||||
(def reactive rum/reactive)
|
||||
(def dom-node rum/dom-node)
|
||||
|
|
|
@ -5,82 +5,259 @@
|
|||
;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(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))))
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(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
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue