diff --git a/frontend/resources/styles/main/partials/colorpicker.scss b/frontend/resources/styles/main/partials/colorpicker.scss index 9b3d268e4..fc3f6c457 100644 --- a/frontend/resources/styles/main/partials/colorpicker.scss +++ b/frontend/resources/styles/main/partials/colorpicker.scss @@ -72,10 +72,16 @@ margin-top: 0.5rem; margin-bottom: 1rem; - .gradient-background { + .gradient-background-wrapper { height: 100%; width: 100%; border: 1px solid $color-gray-10; + background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAADFJREFUOE9jZGBgEAFifOANPknGUQMYhkkYEEgG+NMJKAwIAbwJbdQABnBCIgRoG4gAIF8IsXB/Rs4AAAAASUVORK5CYII=") left center; + } + + .gradient-background { + height: 100%; + width: 100%; } .gradient-stop-wrapper { @@ -85,16 +91,21 @@ } .gradient-stop { + display: grid; + grid-template-columns: 50% 50%; position: absolute; - width: 14px; - height: 14px; + width: 15px; + height: 15px; 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 { + background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAADFJREFUOE9jZGBgEAFifOANPknGUQMYhkkYEEgG+NMJKAwIAbwJbdQABnBCIgRoG4gAIF8IsXB/Rs4AAAAASUVORK5CYII=") left center; + background-color: $color-white; + + &.active { border-color: $color-primary; } } diff --git a/frontend/src/app/main/data/colors.cljs b/frontend/src/app/main/data/colors.cljs index 217f28aad..0153f0334 100644 --- a/frontend/src/app/main/data/colors.cljs +++ b/frontend/src/app/main/data/colors.cljs @@ -104,6 +104,34 @@ (assoc-in [:workspace-local :picked-color-select] value) (assoc-in [:workspace-local :picked-shift?] shift?))))) +(defn change-fill2 + ([ids color] + (ptk/reify ::change-fill + ptk/WatchEvent + (watch [_ state s] + (let [pid (:current-page-id state) + objects (get-in state [:workspace-data :pages-index pid :objects]) + children (mapcat #(cph/get-children % objects) ids) + ids (into ids children) + + is-text? #(= :text (:type (get objects %))) + text-ids (filter is-text? ids) + shape-ids (filter (comp not is-text?) ids) + + attrs (cond-> {:fill-color (:color color) + :fill-color-ref-id (:id color) + :fill-color-ref-file (:file-id color) + :fill-color-gradient (:gradient color) + :fill-opacity (:opacity color)}) + + update-fn (fn [shape] (merge shape attrs)) + editors (get-in state [:workspace-local :editors]) + reduce-fn (fn [state id] + (update-in state [:workspace-data :pages-index pid :objects id] update-fn))] + + (rx/from (conj + (map #(dwt/update-text-attrs {:id % :editor (get editors %) :attrs attrs}) text-ids) + (dwc/update-shapes shape-ids update-fn)))))))) (defn change-fill ([ids color id file-id] diff --git a/frontend/src/app/main/ui/modal.cljs b/frontend/src/app/main/ui/modal.cljs index e3894e042..ac6a3c9aa 100644 --- a/frontend/src/app/main/ui/modal.cljs +++ b/frontend/src/app/main/ui/modal.cljs @@ -43,11 +43,14 @@ (st/emit! (dm/hide))))) (defn- on-click-outside - [event wrapper-ref allow-click-outside] + [event wrapper-ref type allow-click-outside] (let [wrapper (mf/ref-val wrapper-ref) current (dom/get-target event)] - (when (and wrapper (not allow-click-outside) (not (.contains wrapper current))) + (when (and wrapper + (not allow-click-outside) + (not (.contains wrapper current)) + (not (= type (keyword (.getAttribute current "data-allow-click-modal"))))) (dom/stop-propagation event) (dom/prevent-default event) (st/emit! (dm/hide))))) @@ -61,7 +64,7 @@ handle-click-outside (fn [event] - (on-click-outside event wrapper-ref (:allow-click-outside data)))] + (on-click-outside event wrapper-ref (:type data) (:allow-click-outside data)))] (mf/use-layout-effect (fn [] diff --git a/frontend/src/app/main/ui/workspace/colorpicker.cljs b/frontend/src/app/main/ui/workspace/colorpicker.cljs index a8901ba2e..cdacccd70 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker.cljs @@ -83,7 +83,7 @@ (math/abs (- unit-value 1.0)) unit-value) value (+ min-value (* unit-value (- max-value min-value)))] - (on-change value))))] + (on-change (math/precision value 2)))))] [:div.slider-selector {:class (str (if vertical? "vertical " "") class) @@ -449,8 +449,7 @@ [:label.blue-label {:for "value-value"} "V"]]) [:label.alpha-label {:for "alpha-value"} "A"]])) - -(defn as-color-components [value opacity] +(defn color->components [value opacity] (let [value (if (uc/hex? value) value "#000000") [r g b] (uc/hex->rgb value) [h s v] (uc/hex->hsv value)] @@ -460,13 +459,61 @@ :r r :g g :b b :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)) +(defn data->state [{:keys [color opacity gradient]}] + (let [type (cond + (nil? gradient) :color + (= :linear (:type gradient)) :linear-gradient + (= :radial (:type gradient)) :radial-gradient) + parse-stop (fn [{:keys [offset color opacity]}] + (vector offset (color->components color opacity))) + + stops (when gradient + (map parse-stop (:stops gradient))) + + current-color (if (nil? gradient) + (color->components color opacity) + (-> stops first second)) + + gradient-data (select-keys gradient [:start-x :start-y + :end-x :end-y + :width])] + (cond-> {:type type + :current-color current-color} + gradient (assoc :gradient-data gradient-data) + stops (assoc :stops (into {} stops)) + stops (assoc :editing-stop (-> stops first first))))) + +(defn state->data [{:keys [type current-color stops gradient-data]}] + (if (= type :color) + {:color (:hex current-color) + :opacity (:alpha current-color)} + + (let [gradient-type (case type + :linear-gradient :linear + :radial-gradient :radial) + parse-stop (fn [[offset {:keys [hex alpha]}]] + (hash-map :offset offset + :color hex + :opacity alpha))] + {:gradient (-> {:type gradient-type + :stops (mapv parse-stop stops)} + (merge gradient-data))}))) + +(defn create-gradient-data [type] + {:start-x 0.5 + :start-y (if (= type :linear-gradient) 0.0 0.5) + :end-x 0.5 + :end-y 1 + :width 1.0}) + +(mf/defc colorpicker + [{:keys [data on-change on-accept]}] + (let [state (mf/use-state (data->state data)) 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) file-colors (mf/deref refs/workspace-file-colors) @@ -480,38 +527,72 @@ locale (mf/deref i18n/locale) - value-ref (mf/use-var value) + ;; data-ref (mf/use-var data) - on-change (or on-change identity) + current-color (:current-color @state) - parse-selected (fn [selected] - (if (#{"recent" "file"} selected) - (keyword selected) - (uuid selected)) ) + parse-selected + (fn [selected] + (if (#{"recent" "file"} selected) + (keyword selected) + (uuid selected)) ) - change-tab (fn [tab] #(reset! active-tab tab)) + 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))))] + handle-change-color + (fn [changes] + (let [editing-stop (:editing-stop @state)] + (swap! state update :current-color merge changes) + (swap! state update-in [:stops editing-stop] merge changes) + + #_(when (:hex changes) + (reset! value-ref (:hex changes))) + + ;; TODO: CHANGE TO SUPPORT GRADIENTS + #_(on-change (:hex changes (:hex current-color)) + (:alpha changes (:alpha current-color))))) + + handle-change-stop + (fn [offset] + (let [offset-color (get-in @state [:stops offset])] + (swap! state assoc :current-color offset-color) + (swap! state assoc :editing-stop offset))) + + on-activate-gradient + (fn [type] + (fn [] + (if (= type (:type @state)) + (do + (swap! state assoc :type :color) + (swap! state dissoc :editing-stop :stops :gradient-data)) + (do + (swap! state assoc :type type) + (when (not (:stops @state)) + (swap! state assoc + :editing-stop 0 + :gradient-data (create-gradient-data type) + :stops {0 (:current-color @state) + 1 (-> (:current-color @state) + (assoc :alpha 0))}))))))] ;; Update state when there is a change in the props upstream - (mf/use-effect + ;; TODO: Review for gradients + #_(mf/use-effect (mf/deps value opacity) (fn [] - (reset! current-color (as-color-components value opacity)))) + (swap! state assoc current-color (as-color-components value opacity)))) ;; Updates the CSS color variable when there is a change in the color (mf/use-effect - (mf/deps @current-color) + (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]) - hsl-from (uc/hsv->hsl [(:h @current-color) 0 (:v @current-color)]) - hsl-to (uc/hsv->hsl [(:h @current-color) 1 (:v @current-color)]) + {:keys [r g b h s v]} current-color + rgb [r g b] + hue-rgb (uc/hsv->rgb [h 1.0 255]) + hsl-from (uc/hsv->hsl [h 0.0 v]) + hsl-to (uc/hsv->hsl [h 1.0 v]) format-hsl (fn [[h s l]] (str/fmt "hsl(%s, %s, %s)" @@ -548,11 +629,10 @@ (reset! current-library-colors (into [] colors)))))) ;; When closing the modal we update the recent-color list - (mf/use-effect + #_(mf/use-effect (fn [] (fn [] (st/emit! (dwc/stop-picker)) - (when @value-ref - (st/emit! (dwl/add-recent-color @value-ref)))))) + (st/emit! (dwl/add-recent-color (state->data @state)))))) (mf/use-effect (mf/deps picking-color? picked-color) @@ -560,18 +640,27 @@ (let [[r g b] (or picked-color [0 0 0]) hex (uc/rgb->hex [r g b]) [h s v] (uc/hex->hsv hex)] - (swap! current-color assoc + + (swap! update :current-color assoc :r r :g g :b b :h h :s s :v v :hex hex) - (reset! value-ref hex) - (when picked-color-select - (on-change hex (:alpha @current-color) nil nil picked-shift?)))))) - (mf/use-effect + ;; TODO: UPDATE TO USE GRADIENTS + #_(reset! value-ref hex) + #_(when picked-color-select + (on-change hex (:alpha current-color) nil nil picked-shift?)))))) + + ;; TODO: UPDATE TO USE GRADIENTS + #_(mf/use-effect (mf/deps picking-color? picked-color-select) (fn [] (when (and picking-color? picked-color-select) - (on-change (:hex @current-color) (:alpha @current-color) nil nil picked-shift?)))) + (on-change (:hex current-color) (:alpha current-color) nil nil picked-shift?)))) + + (mf/use-effect + (mf/deps @state) + (fn [] + (on-change (state->data @state)))) [:div.colorpicker {:ref ref-picker} [:div.colorpicker-content @@ -584,27 +673,49 @@ i/picker] [:div.gradients-buttons - [:button.gradient.linear-gradient #_{:class "active"}] - [:button.gradient.radial-gradient]]] + [:button.gradient.linear-gradient + {:on-click (on-activate-gradient :linear-gradient) + :class (when (= :linear-gradient (:type @state)) "active")}] - #_[: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%"}}]]] + [:button.gradient.radial-gradient + {:on-click (on-activate-gradient :radial-gradient) + :class (when (= :radial-gradient (:type @state)) "active")}]]] + + (when (#{:linear-gradient :radial-gradient} (:type @state)) + [:div.gradient-stops + (let [format-stop (fn [[offset {:keys [r g b alpha]}]] + (str/fmt "rgba(%s, %s, %s, %s) %s" + r g b alpha + (str (* offset 100) "%"))) + gradient-data (str/join "," (map format-stop (:stops @state))) + + ] + [:div.gradient-background-wrapper + [:div.gradient-background {:style {:background (str/fmt "linear-gradient(90deg, %s)" gradient-data) }}]]) + [:div.gradient-stop-wrapper + (for [[offset value] (:stops @state)] + [:div.gradient-stop {:class (when (= (:editing-stop @state) offset) "active") + :on-click (partial handle-change-stop offset) + :style {:left (str (* offset 100) "%")}} + + (let [{:keys [hex r g b alpha]} value] + [:* + [:div.gradient-stop-color {:style {:background-color hex}}] + [:div.gradient-stop-alpha {:style {:background-color (str/format "rgba(%s, %s, %s, %s)" r g b alpha)}}]]) + + ])]]) (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}] + :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}] + [:& color-inputs {:type (if (= @active-tab :hsva) :hsv :rgb) :color current-color :on-change handle-change-color}] [:div.libraries [:select {:on-change (fn [e] @@ -620,7 +731,7 @@ [: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)))} + :on-click #(st/emit! (dwl/add-color (:hex current-color)))} i/plus]) [:div.color-bullet.button {:style {:background-color "white"} @@ -630,14 +741,15 @@ (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) + (swap! update :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 + (swap! update current-color assoc :r r :g g :b b :h h :s s :v v) - (on-change value (:alpha @current-color) id file-id))) + ;; TODO: CHANGE TO SUPPORT GRADIENTS + #_(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") @@ -650,7 +762,8 @@ [:div.actions [:button.btn-primary.btn-large {:on-click (fn [] - (on-accept @value-ref) + ;; TODO: REVIEW FOR GRADIENTS + #_(on-accept @value-ref) (modal/hide!))} (t locale "workspace.libraries.colors.save-color")]])]) ) @@ -673,31 +786,35 @@ (mf/defc colorpicker-modal {::mf/register modal/components ::mf/register-as :colorpicker} - [{:keys [x y default value opacity page on-change on-close disable-opacity position on-accept] :as props}] + [{:keys [x y default data page position on-change on-close on-accept] :as props}] (let [vport (mf/deref viewport) dirty? (mf/use-var false) last-change (mf/use-var nil) position (or position :left) style (calculate-position vport position x y) - handle-change (fn [new-value new-opacity id file-id shift-clicked?] - (when (or (not= new-value value) (not= new-opacity opacity)) - (reset! dirty? true)) - (reset! last-change [new-value new-opacity id file-id]) + handle-change (fn [new-data shift-clicked?] + (reset! dirty? (not= data new-data)) + (reset! last-change new-data) (when on-change - (on-change new-value new-opacity id file-id shift-clicked?)))] + (on-change new-data))) + + ;; handle-change (fn [new-value new-opacity id file-id shift-clicked?] + ;; (when (or (not= new-value value) (not= new-opacity opacity)) + ;; (reset! dirty? true)) + ;; (reset! last-change [new-value new-opacity id file-id]) + ;; (when on-change + ;; (on-change new-value new-opacity id file-id shift-clicked?))) + ] (mf/use-effect (fn [] - #(when (and @dirty? on-close) - (when-let [[value opacity id file-id] @last-change] - (on-close value opacity id file-id))))) + #(when (and @dirty? @last-change on-close) + (on-close @last-change)))) [:div.colorpicker-tooltip {:style (clj->js style)} - [:& colorpicker {:value (or value default) - :opacity (or opacity 1) + [:& colorpicker {:data data :on-change handle-change - :on-accept on-accept - :disable-opacity disable-opacity}]])) + :on-accept on-accept}]])) diff --git a/frontend/src/app/main/ui/workspace/gradients.cljs b/frontend/src/app/main/ui/workspace/gradients.cljs index 0fc1e92a8..739b29666 100644 --- a/frontend/src/app/main/ui/workspace/gradients.cljs +++ b/frontend/src/app/main/ui/workspace/gradients.cljs @@ -13,13 +13,14 @@ [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])) + [app.common.geom.matrix :as gmt] + [app.util.dom :as dom] + [app.main.store :as st] + [app.main.refs :as refs] + [app.main.streams :as ms] + [app.main.data.workspace.common :as dwc])) (def gradient-line-stroke-width 2) (def gradient-line-stroke-color "white") @@ -114,7 +115,8 @@ :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)) + [:rect {:data-allow-click-modal "colorpicker" + :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) @@ -220,73 +222,68 @@ :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)) + [{:keys [id zoom]}] + (let [shape (mf/deref (refs/object-by-id id)) + {:keys [x y width height] :as sr} (:selrect shape) + gradient (:fill-color-gradient shape) [{start-color :color start-opacity :opacity} - {end-color :color end-opacity :opacity}] (:stops @state) + {end-color :color end-opacity :opacity}] (:stops gradient) - from-p (gpt/point (+ x (* width (:start-x @state))) - (+ y (* height (:start-y @state)))) + from-p (gpt/point (+ x (* width (:start-x gradient))) + (+ y (* height (:start-y gradient)))) - to-p (gpt/point (+ x (* width (:end-x @state))) - (+ y (* height (:end-y @state)))) + to-p (gpt/point (+ x (* width (:end-x gradient))) + (+ y (* height (:end-y gradient)))) 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 gradient) (/ gradient-length (/ height 2) )))) (gpt/multiply (gpt/point (/ width 2)))) width-p (gpt/add from-p width-v) + change! (fn [change] + (st/emit! (dwc/update-shapes + [(:id shape)] + #(update % :fill-color-gradient merge change)))) + 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 ))) + start-y (/ (- (:y point) y) height) + start-x (mth/precision start-x 2) + start-y (mth/precision start-y 2)] + (change! {: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))) + end-y (/ (- (:y point) y) height) + + end-x (mth/precision end-x 2) + end-y (mth/precision end-y 2)] + (change! {: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))) + + (change! {: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}])) + (when gradient + [:& gradient-handler-transformed + {:from-p from-p + :to-p to-p + :width-p (when (= :radial (:type gradient)) 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 b5e281510..eda6c1888 100644 --- a/frontend/src/app/main/ui/workspace/selection.cljs +++ b/frontend/src/app/main/ui/workspace/selection.cljs @@ -28,8 +28,7 @@ [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.gradients :refer [gradient-handlers]])) + [app.main.ui.workspace.shapes.outline :refer [outline]])) (def rotation-handler-size 25) (def resize-point-radius 4) @@ -210,11 +209,7 @@ (case type :rotation (when (not= :frame (:type shape)) [:> rotation-handler props]) :resize-point [:> resize-point-handler props] - :resize-side [:> resize-side-handler props]))) - - #_(when (= :rect (:type shape)) - [:& gradient-handlers {:shape tr-shape - :zoom zoom}])]))) + :resize-side [:> resize-side-handler props])))]))) ;; --- Selection Handlers (Component) (mf/defc path-edition-selection-handlers diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/fill.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/fill.cljs index 511ebcdfa..409b101ab 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/fill.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/fill.cljs @@ -21,7 +21,7 @@ [app.util.i18n :as i18n :refer [tr t]] [app.util.object :as obj])) -(def fill-attrs [:fill-color :fill-opacity :fill-color-ref-id :fill-color-ref-file]) +(def fill-attrs [:fill-color :fill-opacity :fill-color-ref-id :fill-color-ref-file :fill-color-gradient]) (defn- fill-menu-props-equals? [np op] @@ -36,24 +36,28 @@ (= (:fill-color new-values) (:fill-color old-values)) (= (:fill-opacity new-values) - (:fill-opacity old-values))))) + (:fill-opacity old-values)) + (= (:fill-color-gradient new-values) + (:fill-color-gradient old-values))))) (mf/defc fill-menu {::mf/wrap [#(mf/memo' % fill-menu-props-equals?)]} [{:keys [ids type values editor] :as props}] (let [locale (mf/deref i18n/locale) - show? (not (nil? (:fill-color values))) + show? (or (not (nil? (:fill-color values))) + (not (nil? (:fill-color-gradient values)))) label (case type :multiple (t locale "workspace.options.selection-fill") :group (t locale "workspace.options.group-fill") (t locale "workspace.options.fill")) - color {:value (:fill-color values) + color {:color (:fill-color values) :opacity (:fill-opacity values) :id (:fill-color-ref-id values) - :file-id (:fill-color-ref-file values)} + :file-id (:fill-color-ref-file values) + :gradient (:fill-color-gradient values)} on-add (mf/use-callback @@ -70,8 +74,8 @@ on-change (mf/use-callback (mf/deps ids) - (fn [value opacity id file-id] - (st/emit! (dc/change-fill ids value opacity id file-id)))) + (fn [color] + (st/emit! (dc/change-fill2 ids color)))) on-open-picker (mf/use-callback diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs index 021fb8bc1..c6462d3b2 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs @@ -10,16 +10,18 @@ (ns app.main.ui.workspace.sidebar.options.rows.color-row (:require [rumext.alpha :as mf] + [cuerdas.core :as str] [app.common.math :as math] [app.util.dom :as dom] [app.util.data :refer [classnames]] [app.util.i18n :as i18n :refer [tr]] [app.main.data.modal :as modal] [app.common.data :as d] - [app.main.refs :as refs])) + [app.main.refs :as refs] + [app.util.color :as uc])) (defn color-picker-callback - [color handle-change-color handle-open handle-close disable-opacity] + [color handle-change-color handle-open handle-close] (fn [event] (let [x (.-clientX event) y (.-clientY event) @@ -27,14 +29,22 @@ :y y :on-change handle-change-color :on-close handle-close - :value (:value color) - :opacity (:opacity color) - :disable-opacity disable-opacity}] + :data color}] (handle-open) (modal/show! :colorpicker props)))) -(defn value-to-background [value] - (if (= value :multiple) "transparent" value)) + +;; TODO: REMOVE `VALUE` WHEN COLOR IS INTEGRATED +(defn as-background [{:keys [color opacity gradient value] :as tt}] + (cond + (and gradient (not= :multiple gradient)) + (uc/gradient->css gradient) + + (not= color :multiple) + (let [[r g b] (uc/hex->rgb (or color value))] + (str/fmt "rgba(%s, %s, %s, %s)" r g b opacity)) + + :else "transparent")) (defn remove-hash [value] (if (or (nil? value) (= value :multiple)) "" (subs value 1))) @@ -59,38 +69,39 @@ (if (= v :multiple) nil v)) (mf/defc color-row - [{:keys [color on-change on-open on-close disable-opacity]}] - (let [;; - file-colors (mf/deref refs/workspace-file-colors) + [{:keys [color on-change on-open on-close]}] + (let [file-colors (mf/deref refs/workspace-file-colors) shared-libs (mf/deref refs/workspace-libraries) get-color-name (fn [{:keys [id file-id]}] (let [src-colors (if file-id (get-in shared-libs [file-id :data :colors]) file-colors)] (get-in src-colors [id :name]))) - default-color {:value "#000000" :opacity 1} - parse-color (fn [color] - (-> (merge default-color color) - (update :value #(or % "#000000")) - (update :opacity #(or % 1)))) + (-> color + (update :color #(or % (:value color))))) state (mf/use-state (parse-color color)) - value (:value @state) + value (:color @state) opacity (:opacity @state) change-value (fn [new-value] - (swap! state assoc :value new-value) + (swap! state assoc :color new-value) (when on-change (on-change new-value (remove-multiple opacity)))) change-opacity (fn [new-opacity] (swap! state assoc :opacity new-opacity) (when on-change (on-change (remove-multiple value) new-opacity))) - handle-pick-color (fn [new-value new-opacity id file-id] - (reset! state {:value new-value :opacity new-opacity}) - (when on-change (on-change new-value new-opacity id file-id))) + ;;handle-pick-color (fn [new-value new-opacity id file-id] + ;; (reset! state {:color new-value :opacity new-opacity}) + ;; (when on-change (on-change new-value new-opacity id file-id))) + + handle-pick-color (fn [color] + (reset! state color) + (when on-change + (on-change color))) handle-open (fn [] (when on-open (on-open))) @@ -123,28 +134,38 @@ [:div.row-flex.color-data [:span.color-th {:class (when (and (:id color) (not= (:id color) :multiple)) "color-name") - :style {:background-color (-> value value-to-background)} - :on-click (color-picker-callback @state handle-pick-color handle-open handle-close disable-opacity)} + :style {:background (as-background color)} + :on-click (color-picker-callback @state handle-pick-color handle-open handle-close)} (when (= value :multiple) "?")] - (if (:id color) + (cond + ;; Rendering a color with ID + (:id color) [:div.color-info [:div.color-name (str (get-color-name color))]] + + ;; Rendering a gradient + (:gradient color) [:div.color-info - [:input {:value (-> value remove-hash) - :pattern "^[0-9a-fA-F]{0,6}$" - :placeholder (tr "settings.multiple") - :on-click select-all - :on-change handle-value-change}]]) + [:div.color-name (str (get-in color [:gradient :type]))]] - (when (not disable-opacity) - [:div.input-element - {:class (classnames :percentail (not= opacity :multiple))} - [:input.input-text {:type "number" - :value (-> opacity opacity->string) - :placeholder (tr "settings.multiple") - :on-click select-all - :on-change handle-opacity-change - :min "0" - :max "100"}]])])) + ;; Rendering a plain color/opacity + :else + [:* + [:div.color-info + [:input {:value (-> value remove-hash) + :pattern "^[0-9a-fA-F]{0,6}$" + :placeholder (tr "settings.multiple") + :on-click select-all + :on-change handle-value-change}]] + + [:div.input-element + {:class (classnames :percentail (not= opacity :multiple))} + [:input.input-text {:type "number" + :value (-> opacity opacity->string) + :placeholder (tr "settings.multiple") + :on-click select-all + :on-change handle-opacity-change + :min "0" + :max "100"}]]])])) diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index ce0ef64f9..e69c427d3 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -40,6 +40,7 @@ [app.main.ui.workspace.snap-distances :refer [snap-distances]] [app.main.ui.workspace.frame-grid :refer [frame-grid]] [app.main.ui.workspace.shapes.outline :refer [outline]] + [app.main.ui.workspace.gradients :refer [gradient-handlers]] [app.common.math :as mth] [app.util.dom :as dom] [app.util.dom.dnd :as dnd] @@ -642,6 +643,10 @@ :zoom zoom :edition edition}]) + (when (= (count selected) 1) + [:& gradient-handlers {:id (first selected) + :zoom zoom}]) + (when drawing-obj [:& draw-area {:shape drawing-obj :zoom zoom diff --git a/frontend/src/app/util/color.cljs b/frontend/src/app/util/color.cljs index d80f4436c..b1163563c 100644 --- a/frontend/src/app/util/color.cljs +++ b/frontend/src/app/util/color.cljs @@ -77,3 +77,16 @@ (defn hsv->hsl [hsv] (hex->hsl (hsv->hex hsv))) + +(defn gradient->css [{:keys [type stops]}] + (let [parse-stop + (fn [{:keys [offset color opacity]}] + (let [[r g b] (hex->rgb color)] + (str/fmt "rgba(%s, %s, %s, %s) %s" r g b opacity (str (* offset 100) "%")))) + + stops-css (str/join "," (map parse-stop stops))] + + (if (= type :linear) + (str/fmt "linear-gradient(to bottom, %s)" stops-css) + (str/fmt "radial-gradient(circle, %s" stops-css)))) +