From 08b537a158114667842f9150f20431937e100a65 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 5 Oct 2020 13:23:23 +0200 Subject: [PATCH 1/2] :sparkles: Linear and radial gradient handlers --- common/app/common/geom/point.cljc | 23 ++ frontend/src/app/main/ui/shapes/attrs.cljs | 34 +- .../src/app/main/ui/shapes/gradients.cljs | 78 +++++ frontend/src/app/main/ui/shapes/rect.cljs | 23 +- .../src/app/main/ui/workspace/gradients.cljs | 290 ++++++++++++++++++ .../src/app/main/ui/workspace/selection.cljs | 20 +- 6 files changed, 442 insertions(+), 26 deletions(-) create mode 100644 frontend/src/app/main/ui/shapes/gradients.cljs create mode 100644 frontend/src/app/main/ui/workspace/gradients.cljs diff --git a/common/app/common/geom/point.cljc b/common/app/common/geom/point.cljc index b523dae2c..54f3f59f4 100644 --- a/common/app/common/geom/point.cljc +++ b/common/app/common/geom/point.cljc @@ -199,3 +199,26 @@ (defn center-points [points] (let [k (point (count points))] (reduce #(add %1 (divide %2 k)) (point) points))) + +(defn normal-left + "Returns the normal unit vector on the left side" + [{:keys [x y]}] + (unit (point (- y) x))) + +(defn normal-right + "Returns the normal unit vector on the right side" + [{:keys [x y]}] + (unit (point y (- x)))) + +(defn point-line-distance + [point line-point1 line-point2] + (let [{x0 :x y0 :y} point + {x1 :x y1 :y} line-point1 + {x2 :x y2 :y} line-point2 + num (mth/abs + (+ (* x0 (- y2 y1)) + (- (* y0 (- x2 x1))) + (* x2 y1) + (- (* y2 x1)))) + dist (distance line-point2 line-point1)] + (/ num dist))) diff --git a/frontend/src/app/main/ui/shapes/attrs.cljs b/frontend/src/app/main/ui/shapes/attrs.cljs index 52c028170..185c5e1f4 100644 --- a/frontend/src/app/main/ui/shapes/attrs.cljs +++ b/frontend/src/app/main/ui/shapes/attrs.cljs @@ -8,7 +8,9 @@ ;; Copyright (c) 2016-2020 UXBOX Labs SL (ns app.main.ui.shapes.attrs - (:require [app.util.object :as obj])) + (:require + [cuerdas.core :as str] + [app.util.object :as obj])) (defn- stroke-type->dasharray [style] @@ -19,16 +21,20 @@ nil)) (defn extract-style-attrs - [shape] - (let [stroke-style (:stroke-style shape :none) - attrs #js {:fill (or (:fill-color shape) "transparent") - :fillOpacity (:fill-opacity shape nil) - :rx (:rx shape nil) - :ry (:ry shape nil)}] - (when (not= stroke-style :none) - (obj/merge! attrs - #js {:stroke (:stroke-color shape nil) - :strokeWidth (:stroke-width shape 1) - :strokeOpacity (:stroke-opacity shape nil) - :strokeDasharray (stroke-type->dasharray stroke-style)})) - attrs)) + ([shape] (extract-style-attrs shape nil)) + ([shape gradient-id] + (let [stroke-style (:stroke-style shape :none) + attrs #js {:rx (:rx shape nil) + :ry (:ry shape nil)} + attrs (obj/merge! attrs + (if gradient-id + #js {:fill (str/format "url(#%s)" gradient-id)} + #js {:fill (or (:fill-color shape) "transparent") + :fillOpacity (:fill-opacity shape nil)}))] + (when (not= stroke-style :none) + (obj/merge! attrs + #js {:stroke (:stroke-color shape nil) + :strokeWidth (:stroke-width shape 1) + :strokeOpacity (:stroke-opacity shape nil) + :strokeDasharray (stroke-type->dasharray stroke-style)})) + attrs))) diff --git a/frontend/src/app/main/ui/shapes/gradients.cljs b/frontend/src/app/main/ui/shapes/gradients.cljs new file mode 100644 index 000000000..f86b025b0 --- /dev/null +++ b/frontend/src/app/main/ui/shapes/gradients.cljs @@ -0,0 +1,78 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.main.ui.shapes.gradients + (:require + [rumext.alpha :as mf] + [cuerdas.core :as str] + [goog.object :as gobj] + [app.common.uuid :as uuid] + [app.common.geom.point :as gpt])) + +(mf/defc linear-gradient [{:keys [id shape gradient]}] + (let [{:keys [x y width height]} shape] + [:defs + [:linearGradient {:id id + :x1 (:start-x gradient) + :y1 (:start-y gradient) + :x2 (:end-x gradient) + :y2 (:end-y gradient)} + (for [{:keys [offset color opacity]} (:stops gradient)] + [:stop {:key (str id "-stop-" offset) + :offset (or offset 0) + :stop-color color + :stop-opacity opacity}])]])) + +(mf/defc radial-gradient [{:keys [id shape gradient]}] + (let [{:keys [x y width height]} shape] + [:defs + (let [translate-vec (gpt/point (+ x (* width (:start-x gradient))) + (+ y (* height (:start-y gradient)))) + + + gradient-vec (gpt/to-vec (gpt/point (* width (:start-x gradient)) + (* height (:start-y gradient))) + (gpt/point (* width (:end-x gradient)) + (* height (:end-y gradient)))) + + angle (gpt/angle gradient-vec + (gpt/point 1 0)) + + shape-height-vec (gpt/point 0 (/ height 2)) + + scale-factor-y (/ (gpt/length gradient-vec) (/ height 2)) + scale-factor-x (* scale-factor-y (:width gradient)) + + scale-vec (gpt/point (* scale-factor-y (/ height 2)) + (* scale-factor-x (/ width 2)) + ) + tr-translate (str/fmt "translate(%s, %s)" (:x translate-vec) (:y translate-vec)) + tr-rotate (str/fmt "rotate(%s)" angle) + tr-scale (str/fmt "scale(%s, %s)" (:x scale-vec) (:y scale-vec)) + transform (str/fmt "%s %s %s" tr-translate tr-rotate tr-scale)] + [:radialGradient {:id id + :cx 0 + :cy 0 + :r 1 + :gradientUnits "userSpaceOnUse" + :gradientTransform transform} + (for [{:keys [offset color opacity]} (:stops gradient)] + [:stop {:key (str id "-stop-" offset) + :offset (or offset 0) + :stop-color color + :stop-opacity opacity}])])])) + +(mf/defc gradient + {::mf/wrap-props false} + [props] + (let [gradient (gobj/get props "gradient")] + (case (:type gradient) + :linear [:> linear-gradient props] + :radial [:> radial-gradient props] + nil))) diff --git a/frontend/src/app/main/ui/shapes/rect.cljs b/frontend/src/app/main/ui/shapes/rect.cljs index a1a7c8457..5cac4b6b3 100644 --- a/frontend/src/app/main/ui/shapes/rect.cljs +++ b/frontend/src/app/main/ui/shapes/rect.cljs @@ -13,7 +13,12 @@ [app.main.ui.shapes.attrs :as attrs] [app.main.ui.shapes.custom-stroke :refer [shape-custom-stroke]] [app.common.geom.shapes :as geom] - [app.util.object :as obj])) + [app.util.object :as obj] + [app.main.ui.shapes.gradients :refer [gradient]] + + [cuerdas.core :as str] + [app.common.uuid :as uuid] + [app.common.geom.point :as gpt])) (mf/defc rect-shape {::mf/wrap-props false} @@ -21,7 +26,10 @@ (let [shape (unchecked-get props "shape") {:keys [id x y width height]} shape transform (geom/transform-matrix shape) - props (-> (attrs/extract-style-attrs shape) + + gradient-id (when (:fill-color-gradient shape) (str (uuid/next))) + + props (-> (attrs/extract-style-attrs shape gradient-id) (obj/merge! #js {:x x :y y @@ -30,7 +38,12 @@ :width width :height height}))] - [:& shape-custom-stroke {:shape shape - :base-props props - :elem-name "rect"}])) + [:* + (when gradient-id + [:& gradient {:id gradient-id + :shape shape + :gradient (:fill-color-gradient shape)}]) + [:& shape-custom-stroke {:shape shape + :base-props props + :elem-name "rect"}]])) diff --git a/frontend/src/app/main/ui/workspace/gradients.cljs b/frontend/src/app/main/ui/workspace/gradients.cljs new file mode 100644 index 000000000..dad8b2099 --- /dev/null +++ b/frontend/src/app/main/ui/workspace/gradients.cljs @@ -0,0 +1,290 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.main.ui.workspace.gradients + "Gradients handlers and renders" + (:require + [rumext.alpha :as mf] + [cuerdas.core :as str] + [beicon.core :as rx] + [app.main.data.workspace.common :as dwc] + [app.main.store :as st] + [app.main.streams :as ms] + [app.common.math :as mth] + [app.util.dom :as dom] + [app.common.geom.point :as gpt] + [app.common.geom.matrix :as gmt])) + +(def gradient-line-stroke-width 2) +(def gradient-line-stroke-color "white") +(def gradient-square-width 15) +(def gradient-square-radius 2) +(def gradient-square-stroke-width 2) +(def gradient-width-handler-radius 5) +(def gradient-width-handler-color "white") +(def gradient-square-stroke-color "white") +(def gradient-square-stroke-color-selected "#1FDEA7") + +(mf/defc shadow [{:keys [id x y width height offset]}] + [:filter {:id id + :x x + :y y + :width width + :height height + :filterUnits "userSpaceOnUse" + :color-interpolation-filters "sRGB"} + [:feFlood {:flood-opacity "0" :result "BackgroundImageFix"}] + [:feColorMatrix {:in "SourceAlpha" :type "matrix" :values "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"}] + [:feOffset {:dy offset}] + [:feGaussianBlur {:stdDeviation "1"}] + [:feColorMatrix {:type "matrix" :values "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"}] + [:feBlend {:mode "normal" :in2 "BackgroundImageFix" :result id}] + [:feBlend {:mode "normal" :in "SourceGraphic" :in2 id :result "shape"}]]) + +(mf/defc gradient-line-drop-shadow-filter [{:keys [id zoom from-p to-p]}] + [:& shadow + {:id id + :x (min (- (:x from-p) (/ 2 zoom)) + (- (:x to-p) (/ 2 zoom))) + :y (min (- (:y from-p) (/ 2 zoom)) + (- (:y to-p) (/ 2 zoom))) + :width (+ (mth/abs (- (:x to-p) (:x from-p))) (/ 4 zoom)) + :height (+ (mth/abs (- (:y to-p) (:y from-p))) (/ 4 zoom)) + :offset (/ 2 zoom)}]) + + +(mf/defc gradient-square-drop-shadow-filter [{:keys [id zoom point]}] + [:& shadow + {:id id + :x (- (:x point) (/ gradient-square-width zoom 2) 2) + :y (- (:y point) (/ gradient-square-width zoom 2) 2) + :width (+ (/ gradient-square-width zoom) (/ 2 zoom) 4) + :height (+ (/ gradient-square-width zoom) (/ 2 zoom) 4) + :offset (/ 2 zoom)}]) + +(mf/defc gradient-width-handler-shadow-filter [{:keys [id zoom point]}] + [:& shadow + {:id id + :x (- (:x point) (/ gradient-width-handler-radius zoom) 2) + :y (- (:y point) (/ gradient-width-handler-radius zoom) 2) + :width (+ (/ (* 2 gradient-width-handler-radius) zoom) (/ 2 zoom) 4) + :height (+ (/ (* 2 gradient-width-handler-radius) zoom) (/ 2 zoom) 4) + :offset (/ 2 zoom)}]) + +(def default-gradient + {:type :linear + :start-x 0.5 :start-y 0.5 + :end-x 0.5 :end-y 1 + :width 1.0 + :stops [{:offset 0 + :color "#FF0000" + :opacity 1} + {:offset 1 + :color "#FF0000" + :opacity 0.2}]}) + +(def checkboard "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAIAAAC0tAIdAAACvUlEQVQoFQGyAk39AeLi4gAAAAAAAB0dHQAAAAAAAOPj4wAAAAAAAB0dHQAAAAAAAOPj4wAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB////AAAAAAAA4+PjAAAAAAAAHR0dAAAAAAAA4+PjAAAAAAAAHR0dAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATj4+MAAAAAAAAdHR0AAAAAAADj4+MAAAAAAAAdHR0AAAAAAADj4+MAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAjScaa0cU7nIAAAAASUVORK5CYII=") + +(mf/defc gradient-color-handler + [{:keys [filter-id zoom point color angle on-click on-mouse-down on-mouse-up]}] + [:g {:filter (str/fmt "url(#%s)" filter-id) + :transform (gmt/rotate-matrix angle point)} + + [:image {:href checkboard + :x (- (:x point) (/ gradient-square-width 2 zoom)) + :y (- (:y point) (/ gradient-square-width 2 zoom)) + :width (/ gradient-square-width zoom) + :height (/ gradient-square-width zoom)}] + + [:rect {:x (- (:x point) (/ gradient-square-width 2 zoom)) + :y (- (:y point) (/ gradient-square-width 2 zoom)) + :rx (/ gradient-square-radius zoom) + :width (/ gradient-square-width zoom 2) + :height (/ gradient-square-width zoom) + :fill (:value color) + :on-click (partial on-click :to-p) + :on-mouse-down (partial on-mouse-down :to-p) + :on-mouse-up (partial on-mouse-up :to-p)}] + + [:rect {:x (- (:x point) (/ gradient-square-width 2 zoom)) + :y (- (:y point) (/ gradient-square-width 2 zoom)) + :rx (/ gradient-square-radius zoom) + :width (/ gradient-square-width zoom) + :height (/ gradient-square-width zoom) + :stroke "white" + :stroke-width (/ gradient-square-stroke-width zoom) + :fill (:value color) + :fill-opacity (:opacity color) + :on-click on-click + :on-mouse-down on-mouse-down + :on-mouse-up on-mouse-up}]]) + +(mf/defc gradient-handler-transformed + [{:keys [from-p to-p width-p from-color to-color zoom on-change-start on-change-finish on-change-width on-change-stop-color]}] + (let [moving-point (mf/use-var nil) + angle (+ 90 (gpt/angle from-p to-p)) + + on-click (fn [position event] + (dom/stop-propagation event) + (dom/prevent-default event)) + + on-mouse-down (fn [position event] + (dom/stop-propagation event) + (dom/prevent-default event) + (reset! moving-point position)) + + on-mouse-up (fn [position event] + (dom/stop-propagation event) + (dom/prevent-default event) + (reset! moving-point nil))] + + (mf/use-effect + (mf/deps @moving-point from-p to-p width-p) + (fn [] + (let [subs (->> st/stream + (rx/filter ms/pointer-event?) + (rx/filter #(= :viewport (:source %))) + (rx/map :pt) + (rx/subs #(case @moving-point + :from-p (when on-change-start (on-change-start %)) + :to-p (when on-change-finish (on-change-finish %)) + :width-p (when on-change-width + (let [width-v (gpt/unit (gpt/to-vec from-p width-p)) + distance (gpt/point-line-distance % from-p to-p) + new-width-p (gpt/add + from-p + (gpt/multiply width-v (gpt/point distance)))] + (on-change-width new-width-p))) + nil)))] + (fn [] (rx/dispose! subs))))) + [:g.gradient-handlers + [:defs + [:& gradient-line-drop-shadow-filter {:id "gradient_line_drop_shadow" :from-p from-p :to-p to-p :zoom zoom}] + [:& gradient-line-drop-shadow-filter {:id "gradient_widh_line_drop_shadow" :from-p from-p :to-p width-p :zoom zoom}] + [:& gradient-square-drop-shadow-filter {:id "gradient_square_from_drop_shadow" :point from-p :zoom zoom}] + [:& gradient-square-drop-shadow-filter {:id "gradient_square_to_drop_shadow" :point to-p :zoom zoom}] + [:& gradient-width-handler-shadow-filter {:id "gradient_width_handler_drop_shadow" :point width-p :zoom zoom}]] + + [:g {:filter "url(#gradient_line_drop_shadow)"} + [:line {:x1 (:x from-p) + :y1 (:y from-p) + :x2 (:x to-p) + :y2 (:y to-p) + :stroke gradient-line-stroke-color + :stroke-width (/ gradient-line-stroke-width zoom)}]] + + (when width-p + [:g {:filter "url(#gradient_widh_line_drop_shadow)"} + [:line {:x1 (:x from-p) + :y1 (:y from-p) + :x2 (:x width-p) + :y2 (:y width-p) + :stroke gradient-line-stroke-color + :stroke-width (/ gradient-line-stroke-width zoom)}]]) + + (when width-p + [:g {:filter "url(#gradient_width_handler_drop_shadow)"} + [:circle {:cx (:x width-p) + :cy (:y width-p) + :r (/ gradient-width-handler-radius zoom) + :fill gradient-width-handler-color + :on-mouse-down (partial on-mouse-down :width-p) + :on-mouse-up (partial on-mouse-up :width-p)}]]) + + [:& gradient-color-handler + {:filter-id "gradient_square_from_drop_shadow" + :zoom zoom + :point from-p + :color from-color + :angle angle + :on-click (partial on-click :from-p) + :on-mouse-down (partial on-mouse-down :from-p) + :on-mouse-up (partial on-mouse-up :from-p)}] + + [:& gradient-color-handler + {:filter-id "gradient_square_to_drop_shadow" + :zoom zoom + :point to-p + :color to-color + :angle angle + :on-click (partial on-click :to-p) + :on-mouse-down (partial on-mouse-down :to-p) + :on-mouse-up (partial on-mouse-up :to-p)}]])) + +(mf/defc gradient-handlers + [{:keys [shape zoom]}] + (let [{:keys [x y width height] :as sr} (:selrect shape) + + state (mf/use-state (:fill-color-gradient shape default-gradient)) + + [{start-color :color start-opacity :opacity} + {end-color :color end-opacity :opacity}] (:stops @state) + + from-p (gpt/point (+ x (* width (:start-x @state))) + (+ y (* height (:start-y @state)))) + + to-p (gpt/point (+ x (* width (:end-x @state))) + (+ y (* height (:end-y @state)))) + + gradient-vec (gpt/to-vec from-p to-p) + gradient-length (gpt/length gradient-vec) + + width-v (-> gradient-vec + (gpt/normal-left) + (gpt/multiply (gpt/point (* (:width @state) (/ gradient-length (/ height 2) )))) + (gpt/multiply (gpt/point (/ width 2)))) + + width-p (gpt/add from-p width-v) + + on-change-start (fn [point] + (let [start-x (/ (- (:x point) x) width) + start-y (/ (- (:y point) y) height)] + (swap! state assoc + :start-x start-x + :start-y start-y ))) + + on-change-finish (fn [point] + (let [end-x (/ (- (:x point) x) width) + end-y (/ (- (:y point) y) height)] + (swap! state assoc + :end-x end-x + :end-y end-y))) + + on-change-width (fn [point] + (let [scale-factor-y (/ gradient-length (/ height 2)) + norm-dist (/ (gpt/distance point from-p) + (* (/ width 2) scale-factor-y))] + (swap! state assoc :width norm-dist))) + + on-change-stop-color (fn [offset color opacity] (println "change-color"))] + + (mf/use-effect + (mf/deps shape) + (fn [] + (reset! state (:fill-color-gradient shape default-gradient)))) + + (mf/use-effect + (mf/deps @state) + (fn [] + (when (not= (:fill-color-gradient shape) @state) + (st/emit! (dwc/update-shapes + [(:id shape)] + #(assoc % :fill-color-gradient @state)))))) + + [:& gradient-handler-transformed + {:from-p from-p + :to-p to-p + :width-p (when (= :radial (:type @state)) width-p) + :from-color {:value start-color :opacity start-opacity} + :to-color {:value end-color :opacity end-opacity} + :zoom zoom + :on-change-start on-change-start + :on-change-finish on-change-finish + :on-change-width on-change-width + :on-change-stop-color on-change-stop-color}])) diff --git a/frontend/src/app/main/ui/workspace/selection.cljs b/frontend/src/app/main/ui/workspace/selection.cljs index 8e54075e5..41480a39a 100644 --- a/frontend/src/app/main/ui/workspace/selection.cljs +++ b/frontend/src/app/main/ui/workspace/selection.cljs @@ -16,18 +16,20 @@ [rumext.alpha :as mf] [rumext.util :refer [map->obj]] [app.main.data.workspace :as dw] + [app.main.data.workspace.common :as dwc] [app.main.refs :as refs] [app.main.store :as st] [app.main.streams :as ms] [app.main.ui.cursors :as cur] + [app.common.math :as mth] [app.util.dom :as dom] [app.util.object :as obj] [app.common.geom.shapes :as geom] [app.common.geom.point :as gpt] [app.common.geom.matrix :as gmt] [app.util.debug :refer [debug?]] - [app.main.ui.workspace.shapes.outline :refer [outline]])) - + [app.main.ui.workspace.shapes.outline :refer [outline]] + [app.main.ui.workspace.gradients :refer [gradient-handlers]])) (def rotation-handler-size 25) (def resize-point-radius 4) @@ -168,7 +170,6 @@ :cursor (if (#{:left :right} position) (cur/resize-ew rotation) (cur/resize-ns rotation)) }}])) - (mf/defc controls {::mf/wrap-props false} [props] @@ -180,7 +181,9 @@ current-transform (mf/deref refs/current-transform) selrect (geom/shape->rect-shape shape) - transform (geom/transform-matrix shape)] + transform (geom/transform-matrix shape) + + tr-shape (geom/transform-shape shape)] (when (not (#{:move :rotate} current-transform)) [:g.controls @@ -190,8 +193,7 @@ :transform transform :zoom zoom :color color}] - [:& outline {:shape (geom/transform-shape shape) - :color color}] + [:& outline {:shape tr-shape :color color}] ;; Handlers (for [{:keys [type position props]} (handlers-for-selection selrect)] @@ -208,7 +210,11 @@ (case type :rotation (when (not= :frame (:type shape)) [:> rotation-handler props]) :resize-point [:> resize-point-handler props] - :resize-side [:> resize-side-handler props])))]))) + :resize-side [:> resize-side-handler props]))) + + (when (= :rect (:type shape)) + [:& gradient-handlers {:shape tr-shape + :zoom zoom}])]))) ;; --- Selection Handlers (Component) (mf/defc path-edition-selection-handlers From 2c31b074c80c056cf84c25a0217ad36d2611effa Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 7 Oct 2020 14:33:34 +0200 Subject: [PATCH 2/2] :sparkles: New options in color picker (harmony & hsv) --- common/app/common/geom/point.cljc | 1 + common/app/common/math.cljc | 4 + .../resources/images/icons/picker-harmony.svg | 2 + .../resources/images/icons/picker-hsv.svg | 1 + .../resources/images/icons/picker-ramp.svg | 1 + frontend/resources/images/icons/picker.svg | 5 +- .../styles/main/partials/colorpicker.scss | 699 +++++++++++------- frontend/src/app/main/ui/icons.cljs | 3 + .../app/main/ui/workspace/colorpicker.cljs | 674 ++++++++++++----- .../src/app/main/ui/workspace/gradients.cljs | 2 + .../src/app/main/ui/workspace/selection.cljs | 2 +- 11 files changed, 947 insertions(+), 447 deletions(-) create mode 100644 frontend/resources/images/icons/picker-harmony.svg create mode 100644 frontend/resources/images/icons/picker-hsv.svg create mode 100644 frontend/resources/images/icons/picker-ramp.svg diff --git a/common/app/common/geom/point.cljc b/common/app/common/geom/point.cljc index 54f3f59f4..65b453b56 100644 --- a/common/app/common/geom/point.cljc +++ b/common/app/common/geom/point.cljc @@ -211,6 +211,7 @@ (unit (point y (- x)))) (defn point-line-distance + "Returns the distance from a point to a line defined by two points" [point line-point1 line-point2] (let [{x0 :x y0 :y} point {x1 :x y1 :y} line-point1 diff --git a/common/app/common/math.cljc b/common/app/common/math.cljc index d49bcf42c..9125c7c35 100644 --- a/common/app/common/math.cljc +++ b/common/app/common/math.cljc @@ -12,6 +12,10 @@ #?(:cljs (:require [goog.math :as math]))) +(def PI + #?(:cljs (.-PI js/Math) + :clj Math/PI)) + (defn nan? [v] #?(:cljs (js/isNaN v) diff --git a/frontend/resources/images/icons/picker-harmony.svg b/frontend/resources/images/icons/picker-harmony.svg new file mode 100644 index 000000000..c108e2812 --- /dev/null +++ b/frontend/resources/images/icons/picker-harmony.svg @@ -0,0 +1,2 @@ + + diff --git a/frontend/resources/images/icons/picker-hsv.svg b/frontend/resources/images/icons/picker-hsv.svg new file mode 100644 index 000000000..2218c82a1 --- /dev/null +++ b/frontend/resources/images/icons/picker-hsv.svg @@ -0,0 +1 @@ + diff --git a/frontend/resources/images/icons/picker-ramp.svg b/frontend/resources/images/icons/picker-ramp.svg new file mode 100644 index 000000000..0e078a017 --- /dev/null +++ b/frontend/resources/images/icons/picker-ramp.svg @@ -0,0 +1 @@ + diff --git a/frontend/resources/images/icons/picker.svg b/frontend/resources/images/icons/picker.svg index f486028b4..be86a1808 100644 --- a/frontend/resources/images/icons/picker.svg +++ b/frontend/resources/images/icons/picker.svg @@ -1 +1,4 @@ - \ No newline at end of file + + + + diff --git a/frontend/resources/styles/main/partials/colorpicker.scss b/frontend/resources/styles/main/partials/colorpicker.scss index 2ff6badc4..9b3d268e4 100644 --- a/frontend/resources/styles/main/partials/colorpicker.scss +++ b/frontend/resources/styles/main/partials/colorpicker.scss @@ -6,257 +6,487 @@ // Copyright (c) 2015-2016 Juan de la Cruz .colorpicker { + box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); + background-color: $color-white; +} + +.colorpicker-content { + display: flex; + flex-direction: column; + padding: 0.5rem; + + & > * { + width: 200px; + } + + .top-actions { display: flex; - flex-direction: column; - padding: 0.5rem; - background-color: $color-white; - box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); + margin-bottom: 0.25rem; + justify-content: space-between; - & > * { - width: 200px; + .picker-btn { + background: none; + border: none; + cursor: pointer; + + &.active, + &:hover svg { + fill: $color-primary; + } + + svg { + width: 14px; + height: 14px; + } + } + } + + .gradients-buttons { + .gradient { + cursor: pointer; + width: 15px; + height: 15px; + padding: 0; + margin: 0; + border: 1px solid $color-gray-20; + border-radius: 2px; + margin-left: 0.25rem; } - .top-actions { - display: flex; - margin-bottom: 0.25rem; - - .picker-btn { - background: none; - border: none; - cursor: pointer; - - &.active, - &:hover svg { - fill: $color-primary; - } - - svg { - width: 14px; - height: 14px; - } - } + .active { + border-color: $color-primary; } - .picker-detail-wrapper { - position: relative; + .linear-gradient { + background: linear-gradient(180deg, $color-gray-20, transparent); + } - .center-circle { - width: 14px; - height: 14px; - border: 2px solid $color-white; - border-radius: 8px; - position: absolute; - left: 50%; - top: 50%; - transform: translate(-7px, -7px); - filter: drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.25)); - } + .radial-gradient { + background: radial-gradient(transparent, $color-gray-20); } - #picker-detail { - border: 1px solid $color-gray-10; + } + + .gradient-stops { + height: 10px; + display: flex; + margin-top: 0.5rem; + margin-bottom: 1rem; + + .gradient-background { + height: 100%; + width: 100%; + border: 1px solid $color-gray-10; } + .gradient-stop-wrapper { + position: absolute; + width: calc(100% - 2rem); + margin-left: 0.5rem; + } + + .gradient-stop { + position: absolute; + width: 14px; + height: 14px; + border-radius: 2px; + border: 1px solid $color-gray-20; + margin-top: -2px; + margin-left: -7px; + box-shadow: 0 2px 2px rgb(0 0 0 / 15%); + + .selected { + border-color: $color-primary; + } + } + } + + .picker-detail-wrapper { + position: relative; + + .center-circle { + width: 14px; + height: 14px; + border: 2px solid $color-white; + border-radius: 8px; + position: absolute; + left: 50%; + top: 50%; + transform: translate(-7px, -7px); + filter: drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.25)); + } + } + + #picker-detail { + border: 1px solid $color-gray-10; + } + + .slider-selector { + --gradient-direction: 90deg; + --background-repeat: left; + + &.vertical { + --gradient-direction: 0deg; + --background-repeat: top; + } + + border: 1px solid $color-gray-10; + + background: linear-gradient(var(--gradient-direction), rgba(var(--color), 0) 0%, rgba(var(--color), 1.0) 100%); + align-self: center; + position: relative; + cursor: pointer; + + width: 100%; + height: calc(0.5rem + 1px); + + &.vertical { + width: calc(0.5rem + 1px); + height: 100%; + } + + &.hue { + background: linear-gradient( + var(--gradient-direction), + #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, + #00f 67%, #f0f 83%, #f00 100%); + } + + &.saturation { + background: linear-gradient( + var(--gradient-direction), + var(--saturation-grad-from) 0%, + var(--saturation-grad-to) 100% + ) + } + + &.opacity { + background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAADFJREFUOE9jZGBgEAFifOANPknGUQMYhkkYEEgG+NMJKAwIAbwJbdQABnBCIgRoG4gAIF8IsXB/Rs4AAAAASUVORK5CYII=") var(--background-repeat) center; + + &::after { + content: ""; + position: absolute; + width: 100%; + height: 100%; + background: linear-gradient(var(--gradient-direction), rgba(var(--color), 0) 0%, rgba(var(--color), 1.0) 100%); + } + + } + + &.value { + background: linear-gradient(var(--gradient-direction), #FFF 0%, #000 100%); + } + + .handler { + background-color: $color-white;; + box-shadow: rgba(0, 0, 0, 0.37) 0px 1px 4px 0px; + transform: translate(-6px, -2px); + left: 50%; + position: absolute; + width: 12px; + height: 12px; + border-radius: 6px; + z-index: 1; + } + + &.vertical .handler { + transform: translate(-6px, 6px); + } + } + + .value-saturation-selector { + background-color: rgba(var(--hue-rgb)); + position: relative; + height: 6.75rem; + cursor: pointer; + + .handler { + position: absolute; + width: 12px; + height: 12px; + border-radius: 6px; + z-index: 1; + border: 1px solid $color-white; + box-shadow: rgb(255, 255, 255) 0px 0px 0px 1px inset, rgb(0 0 0 / 0.25) 0px 4px 4px inset, rgb(0 0 0 / 0.25) 0px 4px 4px; + transform: translate(-6px, -6px); + left: 50%; + top: 50%; + } + + &::before { + content: ""; + position: absolute; + width: 100%; + height: 100%; + background: linear-gradient(to right, #fff, rgba(255,255,255,0)); + } + + &::after { + content: ""; + position: absolute; + width: 100%; + height: 100%; + background: linear-gradient(to top, #000, rgba(0,0,0,0)); + } + } + + .color-bullet { + grid-area: color; + width: 20px; + height: 20px; + background-color: rgba(var(--color)); + border-radius: 12px; + border: 1px solid $color-gray-10; + } + + .shade-selector { + display: grid; + justify-items: center; + align-items: center; + grid-template-areas: "color hue" + "color opacity"; + grid-template-columns: 2.5rem 1fr; + height: 3.5rem; + grid-row-gap: 0.5rem; + cursor: pointer; + margin-bottom: 0.25rem; + + .slider-selector.hue { + grid-area: "hue"; + align-self: end; + } + + .slider-selector.opacity { + grid-area: "opacity"; + align-self: start; + } + } + + .color-values { + display: grid; + grid-template-columns: 3.5rem repeat(4, 1fr); + grid-row-gap: 0.25rem; + justify-items: center; + grid-column-gap: 0.25rem; + + input { + width: 100%; + margin: 0; + border: 1px solid $color-gray-10; + border-radius: 2px; + font-size: $fs11; + height: 1.5rem; + padding: 0 $x-small; + color: $color-gray-40; + } + + label { + font-size: $fs11; + } + } + + .libraries { + border-top: 1px solid $color-gray-10; + padding-top: 0.5rem; + margin-top: 0.25rem; + width: 200px; + + select { + background-image: url(/images/icons/arrow-down.svg); + background-repeat: no-repeat; + background-position: 95% 48%; + background-size: 10px; + margin: 0; + margin-bottom: 0.5rem; + width: 100%; + padding: 2px 0.25rem; + font-size: 0.75rem; + color: $color-gray-40; + border-color: $color-gray-10; + border-radius: 2px; + + option { + padding: 0; + } + } + + .selected-colors { + display: grid; + grid-template-columns: repeat(8, 1fr); + justify-content: space-between; + margin-right: -8px; + overflow-x: hidden; + overflow-y: auto; + max-height: 5.5rem; + } + + + .selected-colors::after { + content: ""; + flex: auto; + } + + .selected-colors .color-bullet { + grid-area: auto; + margin-bottom: 0.25rem; + cursor: pointer; + + &:hover { + border-color: $color-primary; + } + + &.button { + display: flex; + align-items: center; + justify-content: center; + } + + &.button svg { + width: 12px; + height: 12px; + fill: $color-gray-30; + } + + &.plus-button svg { + width: 8px; + height: 8px; + fill: $color-black; + } + } + } + + .actions { + margin-top: 0.5rem; + display: flex; + flex-direction: row; + justify-content: center; + + .btn-primary { + height: 1.5rem; + padding: 0 2.5rem; + font-size: $fs12; + } + } + + .harmony-selector { + display: flex; + flex-direction: row; + margin-bottom: 0.5rem; + + .hue-wheel-wrapper { + position: relative; + + .hue-wheel { + width: 152px; + height: 152px; + } + + .handler { position: absolute; width: 12px; height: 12px; border-radius: 6px; z-index: 1; - } - - .value-selector { - background-color: rgba(var(--hue)); - position: relative; - height: 6.75rem; - cursor: pointer; - - .handler { - box-shadow: rgb(255, 255, 255) 0px 0px 0px 1px inset; - transform: translate(-6px, -6px); - left: 50%; - top: 50%; - } - } - - .value-selector::before { - content: ""; - position: absolute; - width: 100%; - height: 100%; - background: linear-gradient(to right, #fff, rgba(255,255,255,0)); - } - - .value-selector::after { - content: ""; - position: absolute; - width: 100%; - height: 100%; - background: linear-gradient(to top, #000, rgba(0,0,0,0)); - } - - .shade-selector { - display: grid; - justify-items: center; - align-items: center; - grid-template-areas: "color hue" "color opacity"; - grid-template-columns: 2.5rem 1fr; - height: 3.5rem; - grid-row-gap: 0.5rem; - cursor: pointer; - } - - .color-bullet { - grid-area: color; - width: 20px; - height: 20px; - background-color: rgba(var(--color)); - border-radius: 12px; - border: 1px solid $color-gray-10; - } - - .hue-selector { - align-self: end; - grid-area: hue; - height: 0.5rem; - width: 100%; - background: linear-gradient( - to right, - #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, - #00f 67%, #f0f 83%, #f00 100%); - position: relative; - cursor: pointer; - } - - .hue-selector .handler, - .opacity-selector .handler { - background-color: rgb(248, 248, 248); - box-shadow: rgba(0, 0, 0, 0.37) 0px 1px 4px 0px; - transform: translate(-6px, -2px); + border: 1px solid $color-white; + box-shadow: rgb(255, 255, 255) 0px 0px 0px 1px inset, rgb(0 0 0 / 0.25) 0px 4px 4px inset, rgb(0 0 0 / 0.25) 0px 4px 4px; + transform: translate(-6px, -6px); left: 50%; + top: 50%; + } + + .handler.complement { + background-color: $color-white; + box-shadow: rgb(0 0 0 / 0.25) 0px 4px 4px; + } } - .opacity-selector { - align-self: start; - grid-area: opacity; - height: 0.5rem; - width: 100%; - position: relative; - background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAADFJREFUOE9jZGBgEAFifOANPknGUQMYhkkYEEgG+NMJKAwIAbwJbdQABnBCIgRoG4gAIF8IsXB/Rs4AAAAASUVORK5CYII=") left center; - } - - .opacity-selector::after { - content: ""; - background: linear-gradient(to right, rgba(var(--color), 0) 0%, rgba(var(--color), 1.0) 100%); - position: absolute; - width: 100%; + .handlers-wrapper { + height: 152px; + display: flex; + flex-direction: row; + flex-grow: 1; + justify-content: space-around; + padding-top: 0.5rem; + + & > * { height: 100%; + } + } + } + + .hsva-selector { + display: grid; + padding: 0.25rem; + grid-template-columns: 20px 1fr; + grid-template-rows: repeat(4, 2rem); + grid-row-gap: 0.5rem; + margin-bottom: 0.5rem; + + .hue, + .saturation, + .value, + .opacity { + border-radius: 10px; } - .color-values { - display: grid; - grid-template-columns: 3.5rem repeat(4, 1fr); - grid-row-gap: 0.25rem; - justify-items: center; - grid-column-gap: 0.25rem; + .hsva-selector-label { + grid-column: 1; + align-self: center; + } + } +} - input { - width: 100%; - margin: 0; - border: 1px solid $color-gray-10; - border-radius: 2px; - font-size: $fs11; - height: 1.5rem; - padding: 0 $x-small; - color: $color-gray-40; - } +.colorpicker-tooltip { + border-radius: $br-small; + display: flex; + flex-direction: column; + left: 1400px; + top: 100px; + position: absolute; + z-index: 11; + width: auto; - label { - font-size: $fs11; - } + span { + color: $color-gray-20; + font-size: $fs12; + } + + .inputs-area { + + .input-text { + color: $color-gray-60; + font-size: $fs13; + margin: 5px; + padding: 5px; + width: 100%; } - .libraries { - border-top: 1px solid $color-gray-10; - padding-top: 0.5rem; - margin-top: 0.25rem; - width: 200px; - - select { - background-image: url(/images/icons/arrow-down.svg); - background-repeat: no-repeat; - background-position: 95% 48%; - background-size: 10px; - margin: 0; - margin-bottom: 0.5rem; - width: 100%; - padding: 2px 0.25rem; - font-size: 0.75rem; - color: $color-gray-40; - border-color: $color-gray-10; - border-radius: 2px; + } - option { - padding: 0; - } - } + .colorpicker-tabs { + display: flex; + margin-top: 0.25rem; + height: 2rem; + background-color: $color-gray-10; - .selected-colors { - display: grid; - grid-template-columns: repeat(8, 1fr); - justify-content: space-between; - margin-right: -8px; - overflow-x: hidden; - overflow-y: auto; - max-height: 5.5rem; - } - - - .selected-colors::after { - content: ""; - flex: auto; - } - - .selected-colors .color-bullet { - grid-area: auto; - margin-bottom: 0.25rem; - cursor: pointer; - - &:hover { - border-color: $color-primary; - } - - &.button { - display: flex; - align-items: center; - justify-content: center; - } - - &.button svg { - width: 12px; - height: 12px; - fill: $color-gray-30; - } - - &.plus-button svg { - width: 8px; - height: 8px; - fill: $color-black; - } - } + .active { + background-color: $color-white; } - .actions { - margin-top: 0.5rem; - display: flex; - flex-direction: row; - justify-content: center; + .colorpicker-tab { + cursor: pointer; + display: flex; + flex-grow: 1; + justify-content: center; + align-items: center; - .btn-primary { - height: 1.5rem; - padding: 0 2.5rem; - font-size: $fs12; - } + svg { + width: 16px; + height: 16px; + fill: $color-gray-30; + } } + } } .color-data { @@ -265,8 +495,8 @@ position: relative; .color-name { - font-size: $fs13; - margin: 5px 6px 0px 6px; + font-size: $fs13; + margin: 5px 6px 0px 6px; } .color-info { @@ -310,30 +540,3 @@ } } -.colorpicker-tooltip { - border-radius: $br-small; - display: flex; - flex-direction: column; - left: 1400px; - top: 100px; - position: absolute; - z-index: 11; - width: auto; - - span { - color: $color-gray-20; - font-size: $fs12; - } - - .inputs-area { - - .input-text { - color: $color-gray-60; - font-size: $fs13; - margin: 5px; - padding: 5px; - width: 100%; - } - - } -} diff --git a/frontend/src/app/main/ui/icons.cljs b/frontend/src/app/main/ui/icons.cljs index 34c34f550..4e28fc2ca 100644 --- a/frontend/src/app/main/ui/icons.cljs +++ b/frontend/src/app/main/ui/icons.cljs @@ -122,6 +122,9 @@ (def uppercase (icon-xref :uppercase)) (def user (icon-xref :user)) (def tick (icon-xref :tick)) +(def picker-harmony (icon-xref :picker-harmony)) +(def picker-hsv (icon-xref :picker-hsv)) +(def picker-ramp (icon-xref :picker-ramp)) (def loader-pencil (mf/html diff --git a/frontend/src/app/main/ui/workspace/colorpicker.cljs b/frontend/src/app/main/ui/workspace/colorpicker.cljs index 85d58aeb3..a8901ba2e 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker.cljs @@ -10,18 +10,20 @@ (ns app.main.ui.workspace.colorpicker (:require [rumext.alpha :as mf] - [app.main.store :as st] + [okulary.core :as l] [cuerdas.core :as str] - [app.util.dom :as dom] - [app.util.color :as uc] - [app.main.ui.icons :as i] + [app.common.geom.point :as gpt] [app.common.math :as math] [app.common.uuid :refer [uuid]] + [app.util.dom :as dom] + [app.util.color :as uc] + [app.util.object :as obj] + [app.main.store :as st] + [app.main.refs :as refs] [app.main.data.workspace.libraries :as dwl] [app.main.data.colors :as dwc] [app.main.data.modal :as modal] - [okulary.core :as l] - [app.main.refs :as refs] + [app.main.ui.icons :as i] [app.util.i18n :as i18n :refer [t]])) ;; --- Refs @@ -44,7 +46,7 @@ ;; --- Color Picker Modal -(mf/defc value-selector [{:keys [hue saturation value on-change]}] +(mf/defc value-saturation-selector [{:keys [hue saturation value on-change]}] (let [dragging? (mf/use-state false) calculate-pos (fn [ev] @@ -53,7 +55,7 @@ px (math/clamp (/ (- x left) (- right left)) 0 1) py (* 255 (- 1 (math/clamp (/ (- y top) (- bottom top)) 0 1)))] (on-change px py)))] - [:div.value-selector + [:div.value-saturation-selector {:on-mouse-down #(reset! dragging? true) :on-mouse-up #(reset! dragging? false) :on-pointer-down (partial dom/capture-pointer) @@ -64,41 +66,389 @@ :left (str (* 100 saturation) "%") :top (str (* 100 (- 1 (/ value 255))) "%")}}]])) -(mf/defc hue-selector [{:keys [hue on-change]}] - (let [dragging? (mf/use-state false) - calculate-pos - (fn [ev] - (let [{:keys [left right]} (-> ev dom/get-target dom/get-bounding-rect) - {:keys [x]} (-> ev dom/get-client-position) - px (math/clamp (/ (- x left) (- right left)) 0 1)] - (on-change (* px 360))))] - [:div.hue-selector - {:on-mouse-down #(reset! dragging? true) - :on-mouse-up #(reset! dragging? false) - :on-pointer-down (partial dom/capture-pointer) - :on-pointer-up (partial dom/release-pointer) - :on-click calculate-pos - :on-mouse-move #(when @dragging? (calculate-pos %))} - [:div.handler {:style {:pointer-events "none" - :left (str (* (/ hue 360) 100) "%")}}]])) -(mf/defc opacity-selector [{:keys [opacity on-change]}] - (let [dragging? (mf/use-state false) +(mf/defc slider-selector [{:keys [value class min-value max-value vertical? reverse? on-change]}] + (let [min-value (or min-value 0) + max-value (or max-value 1) + dragging? (mf/use-state false) calculate-pos (fn [ev] - (let [{:keys [left right]} (-> ev dom/get-target dom/get-bounding-rect) - {:keys [x]} (-> ev dom/get-client-position) - px (math/clamp (/ (- x left) (- right left)) 0 1)] - (on-change px)))] - [:div.opacity-selector - {:on-mouse-down #(reset! dragging? true) + (when on-change + (let [{:keys [left right top bottom]} (-> ev dom/get-target dom/get-bounding-rect) + {:keys [x y]} (-> ev dom/get-client-position) + unit-value (if vertical? + (math/clamp (/ (- bottom y) (- bottom top)) 0 1) + (math/clamp (/ (- x left) (- right left)) 0 1)) + unit-value (if reverse? + (math/abs (- unit-value 1.0)) + unit-value) + value (+ min-value (* unit-value (- max-value min-value)))] + (on-change value))))] + + [:div.slider-selector + {:class (str (if vertical? "vertical " "") class) + :on-mouse-down #(reset! dragging? true) :on-mouse-up #(reset! dragging? false) :on-pointer-down (partial dom/capture-pointer) :on-pointer-up (partial dom/release-pointer) :on-click calculate-pos :on-mouse-move #(when @dragging? (calculate-pos %))} - [:div.handler {:style {:pointer-events "none" - :left (str (* opacity 100) "%")}}]])) + + (let [value-percent (* (/ (- value min-value) + (- max-value min-value)) 100) + + value-percent (if reverse? + (math/abs (- value-percent 100)) + value-percent) + value-percent-str (str value-percent "%") + + style-common #js {:pointerEvents "none"} + style-horizontal (obj/merge! #js {:left value-percent-str} style-common) + style-vertical (obj/merge! #js {:bottom value-percent-str} style-common)] + [:div.handler {:style (if vertical? style-vertical style-horizontal)}])])) + + +(defn create-color-wheel + [canvas-node] + (let [ctx (.getContext canvas-node "2d") + width (obj/get canvas-node "width") + height (obj/get canvas-node "height") + radius (/ width 2) + cx (/ width 2) + cy (/ width 2) + step 0.2] + + (.clearRect ctx 0 0 width height) + + (doseq [degrees (range 0 360 step)] + (let [degrees-rad (math/radians degrees) + x (* radius (math/cos (- degrees-rad))) + y (* radius (math/sin (- degrees-rad)))] + (obj/set! ctx "strokeStyle" (str/format "hsl(%s, 100%, 50%)" degrees)) + (.beginPath ctx) + (.moveTo ctx cx cy) + (.lineTo ctx (+ cx x) (+ cy y)) + (.stroke ctx))) + + (let [grd (.createRadialGradient ctx cx cy 0 cx cx radius)] + (.addColorStop grd 0 "white") + (.addColorStop grd 1 "rgba(255, 255, 255, 0") + (obj/set! ctx "fillStyle" grd) + + (.beginPath ctx) + (.arc ctx cx cy radius 0 (* 2 math/PI) true) + (.closePath ctx) + (.fill ctx)))) + +(mf/defc ramp-selector [{:keys [color on-change]}] + (let [{hue :h saturation :s value :v alpha :alpha} color + + on-change-value-saturation + (fn [new-saturation new-value] + (let [hex (uc/hsv->hex [hue new-saturation new-value]) + [r g b] (uc/hex->rgb hex)] + (on-change {:hex hex + :r r :g g :b b + :s new-saturation + :v new-value}))) + + on-change-hue + (fn [new-hue] + (let [hex (uc/hsv->hex [new-hue saturation value]) + [r g b] (uc/hex->rgb hex)] + (on-change {:hex hex + :r r :g g :b b + :h new-hue} ))) + + on-change-opacity + (fn [new-opacity] + (on-change {:alpha new-opacity} ))] + [:* + [:& value-saturation-selector + {:hue hue + :saturation saturation + :value value + :on-change on-change-value-saturation}] + + [:div.shade-selector + [:div.color-bullet] + [:& slider-selector {:class "hue" + :max-value 360 + :value hue + :on-change on-change-hue}] + + [:& slider-selector {:class "opacity" + :max-value 1 + :value alpha + :on-change on-change-opacity}]]])) + +(defn color->point + [canvas-side hue saturation] + (let [hue-rad (math/radians (- hue)) + comp-x (* saturation (math/cos hue-rad)) + comp-y (* saturation (math/sin hue-rad)) + x (+ (/ canvas-side 2) (* comp-x (/ canvas-side 2))) + y (+ (/ canvas-side 2) (* comp-y (/ canvas-side 2)))] + (gpt/point x y))) + +(mf/defc harmony-selector [{:keys [color on-change]}] + (let [canvas-ref (mf/use-ref nil) + {hue :h saturation :s value :v alpha :alpha} color + + canvas-side 152 + pos-current (color->point canvas-side hue saturation) + pos-complement (color->point canvas-side (mod (+ hue 180) 360) saturation) + dragging? (mf/use-state false) + + calculate-pos (fn [ev] + (let [{:keys [left right top bottom]} (-> ev dom/get-target dom/get-bounding-rect) + {:keys [x y]} (-> ev dom/get-client-position) + px (math/clamp (/ (- x left) (- right left)) 0 1) + py (math/clamp (/ (- y top) (- bottom top)) 0 1) + + px (- (* 2 px) 1) + py (- (* 2 py) 1) + + angle (math/degrees (math/atan2 px py)) + new-hue (math/precision (mod (- angle 90 ) 360) 2) + new-saturation (math/clamp (math/distance [px py] [0 0]) 0 1) + hex (uc/hsv->hex [new-hue new-saturation value]) + [r g b] (uc/hex->rgb hex)] + (on-change {:hex hex + :r r :g g :b b + :h new-hue + :s new-saturation}))) + + on-change-value (fn [new-value] + (let [hex (uc/hsv->hex [hue saturation new-value]) + [r g b] (uc/hex->rgb hex)] + (on-change {:hex hex + :r r :g g :b b + :v new-value}))) + on-complement-click (fn [ev] + (let [new-hue (mod (+ hue 180) 360) + hex (uc/hsv->hex [new-hue saturation value]) + [r g b] (uc/hex->rgb hex)] + (on-change {:hex hex + :r r :g g :b b + :h new-hue + :s saturation}))) + + on-change-opacity (fn [new-alpha] (on-change {:alpha new-alpha}))] + + (mf/use-effect + (mf/deps canvas-ref) + (fn [] (when canvas-ref + (create-color-wheel (mf/ref-val canvas-ref))))) + + [:div.harmony-selector + [:div.hue-wheel-wrapper + [:canvas.hue-wheel + {:ref canvas-ref + :width canvas-side + :height canvas-side + :on-mouse-down #(reset! dragging? true) + :on-mouse-up #(reset! dragging? false) + :on-pointer-down (partial dom/capture-pointer) + :on-pointer-up (partial dom/release-pointer) + :on-click calculate-pos + :on-mouse-move #(when @dragging? (calculate-pos %))}] + [:div.handler {:style {:pointer-events "none" + :left (:x pos-current) + :top (:y pos-current)}}] + [:div.handler.complement {:style {:left (:x pos-complement) + :top (:y pos-complement) + :cursor "pointer"} + :on-click on-complement-click}]] + [:div.handlers-wrapper + [:& slider-selector {:class "value" + :vertical? true + :reverse? true + :value value + :max-value 255 + :vertical true + :on-change on-change-value}] + [:& slider-selector {:class "opacity" + :vertical? true + :value alpha + :max-value 1 + :vertical true + :on-change on-change-opacity}]]])) + +(mf/defc hsva-selector [{:keys [color on-change]}] + (let [{hue :h saturation :s value :v alpha :alpha} color + handle-change-slider (fn [key] + (fn [new-value] + (let [change (hash-map key new-value) + {:keys [h s v]} (merge color change) + hex (uc/hsv->hex [h s v]) + [r g b] (uc/hex->rgb hex)] + (on-change (merge change + {:hex hex + :r r :g g :b b}))))) + on-change-opacity (fn [new-alpha] (on-change {:alpha new-alpha}))] + [:div.hsva-selector + [:span.hsva-selector-label "H"] + [:& slider-selector + {:class "hue" :max-value 360 :value hue :on-change (handle-change-slider :h)}] + + [:span.hsva-selector-label "S"] + [:& slider-selector + {:class "saturation" :max-value 1 :value saturation :on-change (handle-change-slider :s)}] + + [:span.hsva-selector-label "V"] + [:& slider-selector + {:class "value" :reverse? true :max-value 255 :value value :on-change (handle-change-slider :v)}] + + [:span.hsva-selector-label "A"] + [:& slider-selector + {:class "opacity" :max-value 1 :value alpha :on-change on-change-opacity}]])) + +(mf/defc color-inputs [{:keys [type color on-change]}] + (let [{red :r green :g blue :b + hue :h saturation :s value :v + hex :hex alpha :alpha} color + + parse-hex (fn [val] (if (= (first val) \#) val (str \# val))) + + refs {:hex (mf/use-ref nil) + :r (mf/use-ref nil) + :g (mf/use-ref nil) + :b (mf/use-ref nil) + :h (mf/use-ref nil) + :s (mf/use-ref nil) + :v (mf/use-ref nil) + :alpha (mf/use-ref nil)} + + on-change-hex + (fn [e] + (let [val (-> e dom/get-target-val parse-hex)] + (when (uc/hex? val) + (let [[r g b] (uc/hex->rgb val) + [h s v] (uc/hex->hsv hex)] + (on-change {:hex val + :h h :s s :v v + :r r :g g :b b}))))) + + on-change-property + (fn [property max-value] + (fn [e] + (let [val (-> e dom/get-target-val (math/clamp 0 max-value)) + val (if (#{:s} property) (/ val 100) val)] + (when (not (nil? val)) + (if (#{:r :g :b} property) + (let [{:keys [r g b]} (merge color (hash-map property val)) + hex (uc/rgb->hex [r g b]) + [h s v] (uc/hex->hsv hex)] + (on-change {:hex hex + :h h :s s :v v + :r r :g g :b b})) + + (let [{:keys [h s v]} (merge color (hash-map property val)) + hex (uc/hsv->hex [h s v]) + [r g b] (uc/hex->rgb hex)] + (on-change {:hex hex + :h h :s s :v v + :r r :g g :b b}))))))) + + on-change-opacity + (fn [e] + (when-let [new-alpha (-> e dom/get-target-val (math/clamp 0 100) (/ 100))] + (on-change {:alpha new-alpha})))] + + + ;; Updates the inputs values when a property is changed in the parent + (mf/use-effect + (mf/deps color type) + (fn [] + (doseq [ref-key (keys refs)] + (let [property-val (get color ref-key) + property-ref (get refs ref-key)] + (when (and property-val property-ref) + (when-let [node (mf/ref-val property-ref)] + (case ref-key + (:s :alpha) (dom/set-value! node (math/round (* property-val 100))) + :hex (dom/set-value! node property-val) + (dom/set-value! node (math/round property-val))))))))) + + [:div.color-values + [:input {:id "hex-value" + :ref (:hex refs) + :default-value hex + :on-change on-change-hex}] + + (if (= type :rgb) + [:* + [:input {:id "red-value" + :ref (:r refs) + :type "number" + :min 0 + :max 255 + :default-value red + :on-change (on-change-property :r 255)}] + + [:input {:id "green-value" + :ref (:g refs) + :type "number" + :min 0 + :max 255 + :default-value green + :on-change (on-change-property :g 255)}] + + [:input {:id "blue-value" + :ref (:b refs) + :type "number" + :min 0 + :max 255 + :default-value blue + :on-change (on-change-property :b 255)}]] + [:* + [:input {:id "hue-value" + :ref (:h refs) + :type "number" + :min 0 + :max 360 + :default-value hue + :on-change (on-change-property :h 360)}] + + [:input {:id "saturation-value" + :ref (:s refs) + :type "number" + :min 0 + :max 100 + :step 1 + :default-value saturation + :on-change (on-change-property :s 100)}] + + [:input {:id "value-value" + :ref (:v refs) + :type "number" + :min 0 + :max 255 + :default-value value + :on-change (on-change-property :v 255)}]]) + + [:input.alpha-value {:id "alpha-value" + :ref (:alpha refs) + :type "number" + :min 0 + :step 1 + :max 100 + :default-value (if (= alpha :multiple) "" (math/precision alpha 2)) + :on-change on-change-opacity}] + + [:label.hex-label {:for "hex-value"} "HEX"] + (if (= type :rgb) + [:* + [:label.red-label {:for "red-value"} "R"] + [:label.green-label {:for "green-value"} "G"] + [:label.blue-label {:for "blue-value"} "B"]] + [:* + [:label.red-label {:for "hue-value"} "H"] + [:label.green-label {:for "saturation-value"} "S"] + [:label.blue-label {:for "value-value"} "V"]]) + [:label.alpha-label {:for "alpha-value"} "A"]])) + (defn as-color-components [value opacity] (let [value (if (uc/hex? value) value "#000000") @@ -108,12 +458,13 @@ {:hex (or value "000000") :alpha (or opacity 1) :r r :g g :b b - :h h :s s :v v} - )) + :h h :s s :v v})) (mf/defc colorpicker [{:keys [value opacity on-change on-accept]}] (let [current-color (mf/use-state (as-color-components value opacity)) + + active-tab (mf/use-state :ramp #_:harmony #_:hsva) selected-library (mf/use-state "recent") current-library-colors (mf/use-state []) ref-picker (mf/use-ref) @@ -136,7 +487,16 @@ parse-selected (fn [selected] (if (#{"recent" "file"} selected) (keyword selected) - (uuid selected)) )] + (uuid selected)) ) + + change-tab (fn [tab] #(reset! active-tab tab)) + + handle-change-color (fn [changes] + (swap! current-color merge changes) + (when (:hex changes) + (reset! value-ref (:hex changes))) + (on-change (:hex changes (:hex @current-color)) + (:alpha changes (:alpha @current-color))))] ;; Update state when there is a change in the props upstream (mf/use-effect @@ -149,9 +509,19 @@ (mf/deps @current-color) (fn [] (let [node (mf/ref-val ref-picker) rgb [(:r @current-color) (:g @current-color) (:b @current-color)] - hue-rgb (uc/hsv->rgb [(:h @current-color) 1.0 255])] + hue-rgb (uc/hsv->rgb [(:h @current-color) 1.0 255]) + hsl-from (uc/hsv->hsl [(:h @current-color) 0 (:v @current-color)]) + hsl-to (uc/hsv->hsl [(:h @current-color) 1 (:v @current-color)]) + + format-hsl (fn [[h s l]] + (str/fmt "hsl(%s, %s, %s)" + h + (str (* s 100) "%") + (str (* l 100) "%")))] (dom/set-css-property node "--color" (str/join ", " rgb)) - (dom/set-css-property node "--hue" (str/join ", " hue-rgb))))) + (dom/set-css-property node "--hue-rgb" (str/join ", " hue-rgb)) + (dom/set-css-property node "--saturation-grad-from" (format-hsl hsl-from)) + (dom/set-css-property node "--saturation-grad-to" (format-hsl hsl-to))))) ;; Load library colors when the select is changed (mf/use-effect @@ -204,168 +574,78 @@ (on-change (:hex @current-color) (:alpha @current-color) nil nil picked-shift?)))) [:div.colorpicker {:ref ref-picker} - [:div.top-actions - [:button.picker-btn - {:class (when picking-color? "active") - :on-click (fn [] - (modal/allow-click-outside!) - (st/emit! (dwc/start-picker)))} - i/picker]] + [:div.colorpicker-content + [:div.top-actions + [:button.picker-btn + {:class (when picking-color? "active") + :on-click (fn [] + (modal/allow-click-outside!) + (st/emit! (dwc/start-picker)))} + i/picker] - (if picking-color? - [:div.picker-detail-wrapper - [:div.center-circle] - [:canvas#picker-detail {:width 200 - :height 160}]] - [:& value-selector {:hue (:h @current-color) - :saturation (:s @current-color) - :value (:v @current-color) - :on-change (fn [s v] - (let [hex (uc/hsv->hex [(:h @current-color) s v]) - [r g b] (uc/hex->rgb hex)] - (swap! current-color assoc - :hex hex - :r r :g g :b b - :s s :v v) - (reset! value-ref hex) - (on-change hex (:alpha @current-color))))}]) - (when (not picking-color?) - [:div.shade-selector - [:div.color-bullet] - [:& hue-selector {:hue (:h @current-color) - :on-change (fn [h] - (let [hex (uc/hsv->hex [h (:s @current-color) (:v @current-color)]) - [r g b] (uc/hex->rgb hex)] - (swap! current-color assoc - :hex hex - :r r :g g :b b - :h h ) - (reset! value-ref hex) - (on-change hex (:alpha @current-color))))}] - [:& opacity-selector {:opacity (:alpha @current-color) - :on-change (fn [alpha] - (swap! current-color assoc :alpha alpha) - (on-change (:hex @current-color) alpha))}]]) + [:div.gradients-buttons + [:button.gradient.linear-gradient #_{:class "active"}] + [:button.gradient.radial-gradient]]] - [:div.color-values - [:input.hex-value {:id "hex-value" - :value (:hex @current-color) - :on-change (fn [e] - (let [val (-> e dom/get-target dom/get-value) - val (if (= (first val) \#) val (str \# val))] - (swap! current-color assoc :hex val) - (when (uc/hex? val) - (reset! value-ref val) - (let [[r g b] (uc/hex->rgb val) - [h s v] (uc/hex->hsv val)] + #_[:div.gradient-stops + [:div.gradient-background {:style {:background "linear-gradient(90deg, #EC0BE5, #CDCDCD)" }}] + [:div.gradient-stop-wrapper + [:div.gradient-stop.start {:style {:background-color "#EC0BE5"}}] + [:div.gradient-stop.end {:style {:background-color "#CDCDCD" + :left "100%"}}]]] + + (if picking-color? + [:div.picker-detail-wrapper + [:div.center-circle] + [:canvas#picker-detail {:width 200 :height 160}]] + (case @active-tab + :ramp [:& ramp-selector {:color @current-color :on-change handle-change-color}] + :harmony [:& harmony-selector {:color @current-color :on-change handle-change-color}] + :hsva [:& hsva-selector {:color @current-color :on-change handle-change-color}] + nil)) + + [:& color-inputs {:type (if (= @active-tab :hsva) :hsv :rgb) :color @current-color :on-change handle-change-color}] + + [:div.libraries + [:select {:on-change (fn [e] + (let [val (-> e dom/get-target dom/get-value)] + (reset! selected-library val))) + :value @selected-library} + [:option {:value "recent"} (t locale "workspace.libraries.colors.recent-colors")] + [:option {:value "file"} (t locale "workspace.libraries.colors.file-library")] + (for [[_ {:keys [name id]}] shared-libs] + [:option {:key id + :value id} name])] + + [:div.selected-colors + (when (= "file" @selected-library) + [:div.color-bullet.button.plus-button {:style {:background-color "white"} + :on-click #(st/emit! (dwl/add-color (:hex @current-color)))} + i/plus]) + + [:div.color-bullet.button {:style {:background-color "white"} + :on-click #(st/emit! (dwc/show-palette (parse-selected @selected-library)))} + i/palette] + + (for [[idx {:keys [id file-id value]}] (map-indexed vector @current-library-colors)] + [:div.color-bullet {:key (str "color-" idx) + :on-click (fn [] + (swap! current-color assoc :hex value) + (reset! value-ref value) + (let [[r g b] (uc/hex->rgb value) + [h s v] (uc/hex->hsv value)] (swap! current-color assoc :r r :g g :b b :h h :s s :v v) - (on-change val (:alpha @current-color))))))}] - [:input.red-value {:id "red-value" - :type "number" - :min 0 - :max 255 - :value (:r @current-color) - :on-change (fn [e] - (let [val (-> e dom/get-target dom/get-value (math/clamp 0 255))] - (swap! current-color assoc :r val) - (when (not (nil? val)) - (let [{:keys [g b]} @current-color - hex (uc/rgb->hex [val g b]) - [h s v] (uc/hex->hsv hex)] - (reset! value-ref hex) - (swap! current-color assoc - :hex hex - :h h :s s :v v) - (on-change hex (:alpha @current-color))))))}] - [:input.green-value {:id "green-value" - :type "number" - :min 0 - :max 255 - :value (:g @current-color) - :on-change (fn [e] - (let [val (-> e dom/get-target dom/get-value (math/clamp 0 255))] - (swap! current-color assoc :g val) - (when (not (nil? val)) - (let [{:keys [r b]} @current-color - hex (uc/rgb->hex [r val b]) - [h s v] (uc/hex->hsv hex)] - (reset! value-ref hex) - (swap! current-color assoc - :hex hex - :h h :s s :v v) - (on-change hex (:alpha @current-color))))))}] - [:input.blue-value {:id "blue-value" - :type "number" - :min 0 - :max 255 - :value (:b @current-color) - :on-change (fn [e] - (let [val (-> e dom/get-target dom/get-value (math/clamp 0 255))] - (swap! current-color assoc :b val) - (when (not (nil? val)) - (let [{:keys [r g]} @current-color - hex (uc/rgb->hex [r g val]) - [h s v] (uc/hex->hsv hex)] - (reset! value-ref hex) - (swap! current-color assoc - :hex hex - :h h :s s :v v) - (on-change hex (:alpha @current-color))))))}] - [:input.alpha-value {:id "alpha-value" - :type "number" - :min 0 - :step 0.1 - :max 1 - :value (if (= (:alpha @current-color) :multiple) - "" - (math/precision (:alpha @current-color) 2)) - :on-change (fn [e] - (let [val (-> e dom/get-target dom/get-value (math/clamp 0 1))] - (swap! current-color assoc :alpha val) - (on-change (:hex @current-color) val)))}] - [:label.hex-label {:for "hex-value"} "HEX"] - [:label.red-label {:for "red-value"} "R"] - [:label.green-label {:for "green-value"} "G"] - [:label.blue-label {:for "blue-value"} "B"] - [:label.alpha-label {:for "alpha-value"} "A"]] - - [:div.libraries - [:select {:on-change (fn [e] - (let [val (-> e dom/get-target dom/get-value)] - (reset! selected-library val))) - :value @selected-library} - [:option {:value "recent"} (t locale "workspace.libraries.colors.recent-colors")] - [:option {:value "file"} (t locale "workspace.libraries.colors.file-library")] - (for [[_ {:keys [name id]}] shared-libs] - [:option {:key id - :value id} name])] - - [:div.selected-colors - (when (= "file" @selected-library) - [:div.color-bullet.button.plus-button {:style {:background-color "white"} - :on-click #(st/emit! (dwl/add-color (:hex @current-color)))} - i/plus]) - - [:div.color-bullet.button {:style {:background-color "white"} - :on-click #(st/emit! (dwc/show-palette (parse-selected @selected-library)))} - i/palette] - - (for [[idx {:keys [id file-id value]}] (map-indexed vector @current-library-colors)] - [:div.color-bullet {:key (str "color-" idx) - :on-click (fn [] - (swap! current-color assoc :hex value) - (reset! value-ref value) - (let [[r g b] (uc/hex->rgb value) - [h s v] (uc/hex->hsv value)] - (swap! current-color assoc - :r r :g g :b b - :h h :s s :v v) - (on-change value (:alpha @current-color) id file-id))) - :style {:background-color value}}])] - - ] + (on-change value (:alpha @current-color) id file-id))) + :style {:background-color value}}])]]] + [:div.colorpicker-tabs + [:div.colorpicker-tab {:class (when (= @active-tab :ramp) "active") + :on-click (change-tab :ramp)} i/picker-ramp] + [:div.colorpicker-tab {:class (when (= @active-tab :harmony) "active") + :on-click (change-tab :harmony)} i/picker-harmony] + [:div.colorpicker-tab {:class (when (= @active-tab :hsva) "active") + :on-click (change-tab :hsva)} i/picker-hsv]] (when on-accept [:div.actions [:button.btn-primary.btn-large diff --git a/frontend/src/app/main/ui/workspace/gradients.cljs b/frontend/src/app/main/ui/workspace/gradients.cljs index dad8b2099..0fc1e92a8 100644 --- a/frontend/src/app/main/ui/workspace/gradients.cljs +++ b/frontend/src/app/main/ui/workspace/gradients.cljs @@ -91,6 +91,8 @@ (def checkboard "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAIAAAC0tAIdAAACvUlEQVQoFQGyAk39AeLi4gAAAAAAAB0dHQAAAAAAAOPj4wAAAAAAAB0dHQAAAAAAAOPj4wAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB////AAAAAAAA4+PjAAAAAAAAHR0dAAAAAAAA4+PjAAAAAAAAHR0dAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATj4+MAAAAAAAAdHR0AAAAAAADj4+MAAAAAAAAdHR0AAAAAAADj4+MAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAjScaa0cU7nIAAAAASUVORK5CYII=") +#_(def checkboard "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAADFJREFUOE9jZGBgEAFifOANPknGUQMYhkkYEEgG+NMJKAwIAbwJbdQABnBCIgRoG4gAIF8IsXB/Rs4AAAAASUVORK5CYII=") + (mf/defc gradient-color-handler [{:keys [filter-id zoom point color angle on-click on-mouse-down on-mouse-up]}] [:g {:filter (str/fmt "url(#%s)" filter-id) diff --git a/frontend/src/app/main/ui/workspace/selection.cljs b/frontend/src/app/main/ui/workspace/selection.cljs index 41480a39a..b5e281510 100644 --- a/frontend/src/app/main/ui/workspace/selection.cljs +++ b/frontend/src/app/main/ui/workspace/selection.cljs @@ -212,7 +212,7 @@ :resize-point [:> resize-point-handler props] :resize-side [:> resize-side-handler props]))) - (when (= :rect (:type shape)) + #_(when (= :rect (:type shape)) [:& gradient-handlers {:shape tr-shape :zoom zoom}])])))