From 216454f66f9a34187826c2575162487a1d774460 Mon Sep 17 00:00:00 2001 From: Aitor Date: Wed, 14 Jun 2023 15:45:37 +0200 Subject: [PATCH] :zap: Add CSS cursor classes --- frontend/src/app/main.cljs | 2 + frontend/src/app/main/ui/cursors.cljs | 59 +++++++++++++++++++ frontend/src/app/main/ui/measurements.cljs | 8 +-- .../main/ui/workspace/shapes/path/editor.cljs | 11 ++-- .../main/ui/workspace/shapes/text/editor.cljs | 4 +- .../src/app/main/ui/workspace/viewport.cljs | 4 +- .../viewport/grid_layout_editor.cljs | 11 ++-- .../main/ui/workspace/viewport/guides.cljs | 8 +-- .../ui/workspace/viewport/pixel_overlay.cljs | 2 +- .../main/ui/workspace/viewport/selection.cljs | 24 ++++---- .../app/main/ui/workspace/viewport/utils.cljs | 26 ++++---- frontend/src/app/util/css.cljs | 27 +++++++++ 12 files changed, 136 insertions(+), 50 deletions(-) create mode 100644 frontend/src/app/util/css.cljs diff --git a/frontend/src/app/main.cljs b/frontend/src/app/main.cljs index 08ef4fdc1..0c0a70f74 100644 --- a/frontend/src/app/main.cljs +++ b/frontend/src/app/main.cljs @@ -18,6 +18,7 @@ [app.main.ui :as ui] [app.main.ui.alert] [app.main.ui.confirm] + [app.main.ui.cursors :as cursors] [app.main.ui.delete-shared] [app.main.ui.modal :refer [modal]] [app.main.ui.routes :as rt] @@ -44,6 +45,7 @@ (defn init-ui [] + (cursors/init-styles) (mf/mount (mf/element ui/app) (dom/get-element "app")) (mf/mount (mf/element modal) (dom/get-element "modal"))) diff --git a/frontend/src/app/main/ui/cursors.cljs b/frontend/src/app/main/ui/cursors.cljs index 6b4786c58..acfbe43f2 100644 --- a/frontend/src/app/main/ui/cursors.cljs +++ b/frontend/src/app/main/ui/cursors.cljs @@ -7,6 +7,8 @@ (ns app.main.ui.cursors (:require-macros [app.main.ui.cursors :refer [cursor-ref cursor-fn]]) (:require + [app.common.data.macros :as dm] + [app.util.css :as css] [app.util.timers :as ts] [cuerdas.core :as str] [rumext.v2 :as mf])) @@ -51,6 +53,63 @@ (def resize-ew-2 (cursor-fn :resize-h-2 0)) (def resize-ns-2 (cursor-fn :resize-h-2 90)) +(defn get-static + [name] + (dm/str "cursor-" name)) + +(defn get-dynamic + [name rotation] + (dm/str "cursor-" name "-" (.floor js/Math rotation))) + +(defn init-static-cursor-style + [style name value] + (.add style (dm/str ".cursor-" name) (js-obj "cursor" (dm/str value " !important")))) + +(defn init-dynamic-cursor-style + [style name fn] + (let [rotations (seq (range 0 360 1))] + (doseq [rotation rotations] + (.add style (dm/str ".cursor-" name "-" rotation) (js-obj "cursor" (dm/str (fn rotation) " !important")))))) + +(defn init-styles + [] + (let [style (css/create-style)] + ;; static + (init-static-cursor-style style "comments" comments) + (init-static-cursor-style style "create-artboard" create-artboard) + (init-static-cursor-style style "create-ellipse" create-ellipse) + (init-static-cursor-style style "create-polygon" create-polygon) + (init-static-cursor-style style "create-rectangle" create-rectangle) + (init-static-cursor-style style "create-shape" create-shape) + (init-static-cursor-style style "duplicate" duplicate) + (init-static-cursor-style style "hand" hand) + (init-static-cursor-style style "move-pointer" move-pointer) + (init-static-cursor-style style "pen" pen) + (init-static-cursor-style style "pen-node" pen-node) + (init-static-cursor-style style "pencil" pencil) + (init-static-cursor-style style "picker" picker) + (init-static-cursor-style style "pointer-inner" pointer-inner) + (init-static-cursor-style style "pointer-move" pointer-move) + (init-static-cursor-style style "pointer-node" pointer-node) + (init-static-cursor-style style "resize-alt" resize-alt) + (init-static-cursor-style style "zoom" zoom) + (init-static-cursor-style style "zoom-in" zoom-in) + (init-static-cursor-style style "zoom-out" zoom-out) + + ;; dynamic + (init-dynamic-cursor-style style "resize-ew" resize-ew) + (init-dynamic-cursor-style style "resize-nesw" resize-nesw) + (init-dynamic-cursor-style style "resize-ns" resize-ns) + (init-dynamic-cursor-style style "resize-nwse" resize-nwse) + (init-dynamic-cursor-style style "rotate" rotate) + (init-dynamic-cursor-style style "text" text) + (init-dynamic-cursor-style style "scale-ew" scale-ew) + (init-dynamic-cursor-style style "scale-nesw" scale-nesw) + (init-dynamic-cursor-style style "scale-ns" scale-ns) + (init-dynamic-cursor-style style "scale-nwse" scale-nwse) + (init-dynamic-cursor-style style "resize-ew-2" resize-ew-2) + (init-dynamic-cursor-style style "resize-ns-2" resize-ns-2))) + (mf/defc debug-preview {::mf/wrap-props false} [] diff --git a/frontend/src/app/main/ui/measurements.cljs b/frontend/src/app/main/ui/measurements.cljs index d2da470a7..441e39ba1 100644 --- a/frontend/src/app/main/ui/measurements.cljs +++ b/frontend/src/app/main/ui/measurements.cljs @@ -358,9 +358,9 @@ :on-pointer-down on-pointer-down :on-lost-pointer-capture on-lost-pointer-capture :on-pointer-move on-pointer-move + :class (when (or hover? selected?) + (if (= (:resize-axis rect-data) :x) (cur/get-dynamic "resize-ew" 0) (cur/get-dynamic "resize-ew" 90))) :style {:fill (if (or hover? selected?) distance-color "none") - :cursor (when (or hover? selected?) - (if (= (:resize-axis rect-data) :x) (cur/resize-ew 0) (cur/resize-ew 90))) :opacity (if selected? 0.5 0.25)}}])) (mf/defc padding-rects [{:keys [frame zoom alt? shift?]}] @@ -661,9 +661,9 @@ :on-pointer-down on-pointer-down :on-lost-pointer-capture on-lost-pointer-capture :on-pointer-move on-pointer-move + :class (when (or hover? selected?) + (if (= (:resize-axis rect-data) :x) (cur/get-dynamic "resize-ew" 0) (cur/get-dynamic "resize-ew" 90))) :style {:fill (if (or hover? selected?) distance-color "none") - :cursor (when (or hover? selected?) - (if (= (:resize-axis rect-data) :x) (cur/resize-ew 0) (cur/resize-ew 90))) :opacity (if selected? 0.5 0.25)}}])) (mf/defc gap-rects [{:keys [frame zoom]}] diff --git a/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs b/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs index 8eddab085..57559919a 100644 --- a/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs @@ -99,10 +99,9 @@ :on-pointer-enter on-enter :on-pointer-leave on-leave :pointer-events (when-not preview? "visible") - :style {:cursor (cond - (= edit-mode :draw) cur/pen-node - (= edit-mode :move) cur/pointer-node) - :stroke-width 0 + :class (cond (= edit-mode :draw) (cur/get-static "pen-node") + (= edit-mode :move) (cur/get-static "pointer-node")) + :style {:stroke-width 0 :fill "none"}}]])) (mf/defc path-handler [{:keys [index prefix point handler zoom selected? hover? edit-mode snap-angle?]}] @@ -160,8 +159,8 @@ :on-pointer-down on-pointer-down :on-pointer-enter on-enter :on-pointer-leave on-leave - :style {:cursor (when (= edit-mode :move) cur/pointer-move) - :fill "none" + :class (when (= edit-mode :move) (cur/get-static "pointer-move")) + :style {:fill "none" :stroke-width 0}}]]))) (mf/defc path-preview [{:keys [zoom command from]}] diff --git a/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs b/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs index 030b79b63..455084405 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs @@ -222,8 +222,7 @@ [:div.text-editor {:ref self-ref - :style {:cursor (cur/text (:rotation shape)) - :width (:width shape) + :style {:width (:width shape) :height (:height shape) ;; We hide the editor when is blurred because otherwise the selection won't let us see ;; the underlying text. Use opacity because display or visibility won't allow to recover @@ -231,6 +230,7 @@ :opacity (when @blurred 0)} :on-pointer-down on-pointer-down :class (dom/classnames + (cur/get-dynamic "text" (:rotation shape)) true :align-top (= (:vertical-align content "top") "top") :align-center (= (:vertical-align content) "center") :align-bottom (= (:vertical-align content) "bottom"))} diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index a4a988823..889b6a0cd 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -330,8 +330,8 @@ :key (str "viewport" page-id) :view-box (utils/format-viewbox vbox) :ref on-viewport-ref - :class (when drawing-tool "drawing") - :style {:cursor @cursor :touch-action "none"} + :class (dm/str @cursor (when drawing-tool "drawing")) + :style {:touch-action "none"} :fill "none" :on-click on-click 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 938aa2802..c9f351e90 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 @@ -15,7 +15,6 @@ [app.main.data.workspace.grid-layout.editor :as dwge] [app.main.refs :as refs] [app.main.store :as st] - [app.main.ui.cursors :as cur] [app.util.dom :as dom] [cuerdas.core :as str] [rumext.v2 :as mf])) @@ -230,15 +229,15 @@ [:rect.resize-handler {:x x :y y + :class (if (= type :column) + "resize-ew-0" + "resize-ns-0") :height height :width width :on-pointer-down on-pointer-down :on-lost-pointer-capture on-lost-pointer-capture - :on-pointer-move on-pointer-move - :style {:fill "transparent" - :cursor (if (= type :column) - (cur/resize-ew 0) - (cur/resize-ns 0))}}])) + :on-pointer-move on-pointer-move + :style {:fill "transparent"}}])) (mf/defc editor {::mf/wrap-props false} diff --git a/frontend/src/app/main/ui/workspace/viewport/guides.cljs b/frontend/src/app/main/ui/workspace/viewport/guides.cljs index b1d4a6c2b..89e10adbd 100644 --- a/frontend/src/app/main/ui/workspace/viewport/guides.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/guides.cljs @@ -302,9 +302,9 @@ :y y :width width :height height + :class (if (= axis :x) (cur/get-dynamic "resize-ew" 0) (cur/get-dynamic "resize-ns" 0)) :style {:fill "none" - :pointer-events (if frame-guide-outside? "none" "fill") - :cursor (if (= axis :x) (cur/resize-ew 0) (cur/resize-ns 0))} + :pointer-events (if frame-guide-outside? "none" "fill")} :on-pointer-enter on-pointer-enter :on-pointer-leave on-pointer-leave :on-pointer-down on-pointer-down @@ -417,9 +417,9 @@ :on-pointer-up on-pointer-up :on-lost-pointer-capture on-lost-pointer-capture :on-pointer-move on-pointer-move + :class (if (= axis :x) (cur/get-dynamic "resize-ew" 0) (cur/get-dynamic "resize-ns" 0)) :style {:fill "none" - :pointer-events "fill" - :cursor (if (= axis :x) (cur/resize-ew 0) (cur/resize-ns 0))}}])) + :pointer-events "fill"}}])) (when (:new-position @state) [:& guide {:guide {:axis axis diff --git a/frontend/src/app/main/ui/workspace/viewport/pixel_overlay.cljs b/frontend/src/app/main/ui/workspace/viewport/pixel_overlay.cljs index c3a2e71ad..d0513030e 100644 --- a/frontend/src/app/main/ui/workspace/viewport/pixel_overlay.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/pixel_overlay.cljs @@ -206,7 +206,7 @@ [:div.pixel-overlay {:id "pixel-overlay" :tab-index 0 - :style {:cursor cur/picker} + :class (cur/get-static "picker") :on-pointer-down handle-pointer-down-picker :on-pointer-up handle-pointer-up-picker :on-pointer-move handle-pointer-move-picker} diff --git a/frontend/src/app/main/ui/workspace/viewport/selection.cljs b/frontend/src/app/main/ui/workspace/viewport/selection.cljs index 28f985110..e91312c1d 100644 --- a/frontend/src/app/main/ui/workspace/viewport/selection.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/selection.cljs @@ -165,9 +165,9 @@ :top-right 90 :bottom-right 180 :bottom-left 270)] - [:rect {:style {:cursor (cur/rotate (+ rotation angle))} - :x x + [:rect {:x x :y y + :class (cur/get-dynamic "rotate" (+ rotation angle)) :width size :height size :fill (if (debug? :handlers) "blue" "none") @@ -180,8 +180,8 @@ (let [layout (mf/deref refs/workspace-layout) scale-text (:scale-text layout) cursor (if (#{:top-left :bottom-right} position) - (if scale-text (cur/scale-nesw rotation) (cur/resize-nesw rotation)) - (if scale-text (cur/scale-nwse rotation) (cur/resize-nwse rotation))) + (if scale-text (cur/get-dynamic "scale-nesw" rotation) (cur/get-dynamic "resize-nesw" rotation)) + (if scale-text (cur/get-dynamic "scale-nwse" rotation) (cur/get-dynamic "resize-nwse" rotation))) {cx' :x cy' :y} (gpt/transform (gpt/point cx cy) transform)] [:g.resize-handler @@ -203,21 +203,21 @@ {cx' :x cy' :y} (gpt/transform (gpt/point cx cy) transform)] [:rect {:x cx' :y cy' + :class cursor :width resize-point-circle-radius :height resize-point-circle-radius :transform (when rotation (dm/fmt "rotate(%, %, %)" rotation cx' cy')) :style {:fill (if (debug? :handlers) "red" "none") - :stroke-width 0 - :cursor cursor} + :stroke-width 0} :on-pointer-down #(on-resize {:x cx' :y cy'} %)}]) [:circle {:on-pointer-down #(on-resize {:x cx' :y cy'} %) :r (/ resize-point-circle-radius zoom) :cx cx' :cy cy' + :class cursor :style {:fill (if (debug? :handlers) "red" "none") - :stroke-width 0 - :cursor cursor}}])])) + :stroke-width 0}}])])) (mf/defc resize-side-handler "The side handler is always rendered horizontally and then rotated" @@ -246,13 +246,13 @@ :y target-y :width length :height height + :class (if (#{:left :right} position) + (if scale-text (cur/get-dynamic "scale-ew" rotation) (cur/get-dynamic "resize-ew" rotation)) + (if scale-text (cur/get-dynamic "scale-ns" rotation) (cur/get-dynamic "resize-ns" rotation))) :transform transform-str :on-pointer-down #(on-resize res-point %) :style {:fill (if (debug? :handlers) "yellow" "none") - :stroke-width 0 - :cursor (if (#{:left :right} position) - (if scale-text (cur/scale-ew rotation) (cur/resize-ew rotation)) - (if scale-text (cur/scale-ns rotation) (cur/resize-ns rotation))) }}]])) + :stroke-width 0}}]])) (defn minimum-selrect [{:keys [x y width height] :as selrect}] (let [final-width (max width min-selrect-side) diff --git a/frontend/src/app/main/ui/workspace/viewport/utils.cljs b/frontend/src/app/main/ui/workspace/viewport/utils.cljs index 71124b0a1..dec88a0e0 100644 --- a/frontend/src/app/main/ui/workspace/viewport/utils.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/utils.cljs @@ -20,19 +20,19 @@ (defn get-cursor [cursor] (case cursor - :hand cur/hand - :comments cur/comments - :create-artboard cur/create-artboard - :create-rectangle cur/create-rectangle - :create-ellipse cur/create-ellipse - :pen cur/pen - :pencil cur/pencil - :create-shape cur/create-shape - :duplicate cur/duplicate - :zoom cur/zoom - :zoom-in cur/zoom-in - :zoom-out cur/zoom-out - cur/pointer-inner)) + :hand (cur/get-static "hand") + :comments (cur/get-static "comments") + :create-artboard (cur/get-static "create-artboard") + :create-rectangle (cur/get-static "create-rectangle") + :create-ellipse (cur/get-static "create-ellipse") + :pen (cur/get-static "pen") + :pencil (cur/get-static "pencil") + :create-shape (cur/get-static "create-shape") + :duplicate (cur/get-static "duplicate") + :zoom (cur/get-static "zoom") + :zoom-in (cur/get-static "zoom-in") + :zoom-out (cur/get-static "zoom-out") + (cur/get-static "pointer-inner"))) ;; Ensure that the label has always the same font ;; size, regardless of zoom diff --git a/frontend/src/app/util/css.cljs b/frontend/src/app/util/css.cljs new file mode 100644 index 000000000..2e822b957 --- /dev/null +++ b/frontend/src/app/util/css.cljs @@ -0,0 +1,27 @@ +(ns app.util.css + (:require + [app.common.data.macros :as dm] + [app.util.dom :as dom])) + +(defn declarations->str + "Converts an object of CSS declarations to a string" + [declarations] + (let [entries (.from js/Array (.entries js/Object declarations))] + (.reduce entries (fn [acc [k v]] + (dm/str acc k ": " v ";")) ""))) + +(defn add-rule + "Adds a CSS rule to a CSS Style Sheet" + [styleSheet selector declarations] + (.insertRule styleSheet (dm/str selector " {" (declarations->str declarations) "}"))) + +;; FIXME: Maybe we should rename this to `create-dynamic-style`? +(defn create-style + "Creates a new CSS Style Sheet and returns an object that allows adding rules to it" + [] + (let [style (dom/create-element "style")] + (dom/set-attribute! style "type" "text/css") + (dom/append-child! js/document.head style) + (js-obj "add" (partial add-rule (.-sheet style))))) + +