Changes to the colorpicker to support gradients

This commit is contained in:
alonso.torres 2020-10-08 16:08:00 +02:00
parent a7335533bb
commit 381aef77ee
10 changed files with 368 additions and 174 deletions

View file

@ -72,10 +72,16 @@
margin-top: 0.5rem; margin-top: 0.5rem;
margin-bottom: 1rem; margin-bottom: 1rem;
.gradient-background { .gradient-background-wrapper {
height: 100%; height: 100%;
width: 100%; width: 100%;
border: 1px solid $color-gray-10; 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 { .gradient-stop-wrapper {
@ -85,16 +91,21 @@
} }
.gradient-stop { .gradient-stop {
display: grid;
grid-template-columns: 50% 50%;
position: absolute; position: absolute;
width: 14px; width: 15px;
height: 14px; height: 15px;
border-radius: 2px; border-radius: 2px;
border: 1px solid $color-gray-20; border: 1px solid $color-gray-20;
margin-top: -2px; margin-top: -2px;
margin-left: -7px; margin-left: -7px;
box-shadow: 0 2px 2px rgb(0 0 0 / 15%); 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; border-color: $color-primary;
} }
} }

View file

@ -104,6 +104,34 @@
(assoc-in [:workspace-local :picked-color-select] value) (assoc-in [:workspace-local :picked-color-select] value)
(assoc-in [:workspace-local :picked-shift?] shift?))))) (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 (defn change-fill
([ids color id file-id] ([ids color id file-id]

View file

@ -43,11 +43,14 @@
(st/emit! (dm/hide))))) (st/emit! (dm/hide)))))
(defn- on-click-outside (defn- on-click-outside
[event wrapper-ref allow-click-outside] [event wrapper-ref type allow-click-outside]
(let [wrapper (mf/ref-val wrapper-ref) (let [wrapper (mf/ref-val wrapper-ref)
current (dom/get-target event)] 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/stop-propagation event)
(dom/prevent-default event) (dom/prevent-default event)
(st/emit! (dm/hide))))) (st/emit! (dm/hide)))))
@ -61,7 +64,7 @@
handle-click-outside handle-click-outside
(fn [event] (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 (mf/use-layout-effect
(fn [] (fn []

View file

@ -83,7 +83,7 @@
(math/abs (- unit-value 1.0)) (math/abs (- unit-value 1.0))
unit-value) unit-value)
value (+ min-value (* unit-value (- max-value min-value)))] value (+ min-value (* unit-value (- max-value min-value)))]
(on-change value))))] (on-change (math/precision value 2)))))]
[:div.slider-selector [:div.slider-selector
{:class (str (if vertical? "vertical " "") class) {:class (str (if vertical? "vertical " "") class)
@ -449,8 +449,7 @@
[:label.blue-label {:for "value-value"} "V"]]) [:label.blue-label {:for "value-value"} "V"]])
[:label.alpha-label {:for "alpha-value"} "A"]])) [:label.alpha-label {:for "alpha-value"} "A"]]))
(defn color->components [value opacity]
(defn as-color-components [value opacity]
(let [value (if (uc/hex? value) value "#000000") (let [value (if (uc/hex? value) value "#000000")
[r g b] (uc/hex->rgb value) [r g b] (uc/hex->rgb value)
[h s v] (uc/hex->hsv value)] [h s v] (uc/hex->hsv value)]
@ -460,13 +459,61 @@
:r r :g g :b b :r r :g g :b b
:h h :s s :v v})) :h h :s s :v v}))
(mf/defc colorpicker (defn data->state [{:keys [color opacity gradient]}]
[{:keys [value opacity on-change on-accept]}] (let [type (cond
(let [current-color (mf/use-state (as-color-components value opacity)) (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) active-tab (mf/use-state :ramp #_:harmony #_:hsva)
selected-library (mf/use-state "recent") selected-library (mf/use-state "recent")
current-library-colors (mf/use-state []) current-library-colors (mf/use-state [])
ref-picker (mf/use-ref) ref-picker (mf/use-ref)
file-colors (mf/deref refs/workspace-file-colors) file-colors (mf/deref refs/workspace-file-colors)
@ -480,38 +527,72 @@
locale (mf/deref i18n/locale) 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] parse-selected
(fn [selected]
(if (#{"recent" "file"} selected) (if (#{"recent" "file"} selected)
(keyword selected) (keyword selected)
(uuid selected)) ) (uuid selected)) )
change-tab (fn [tab] #(reset! active-tab tab)) change-tab
(fn [tab]
#(reset! active-tab tab))
handle-change-color (fn [changes] handle-change-color
(swap! current-color merge changes) (fn [changes]
(when (:hex 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))) (reset! value-ref (:hex changes)))
(on-change (:hex changes (:hex @current-color))
(:alpha changes (:alpha @current-color))))] ;; 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 ;; 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) (mf/deps value opacity)
(fn [] (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 ;; Updates the CSS color variable when there is a change in the color
(mf/use-effect (mf/use-effect
(mf/deps @current-color) (mf/deps current-color)
(fn [] (let [node (mf/ref-val ref-picker) (fn [] (let [node (mf/ref-val ref-picker)
rgb [(:r @current-color) (:g @current-color) (:b @current-color)] {:keys [r g b h s v]} current-color
hue-rgb (uc/hsv->rgb [(:h @current-color) 1.0 255]) rgb [r g b]
hsl-from (uc/hsv->hsl [(:h @current-color) 0 (:v @current-color)]) hue-rgb (uc/hsv->rgb [h 1.0 255])
hsl-to (uc/hsv->hsl [(:h @current-color) 1 (:v @current-color)]) hsl-from (uc/hsv->hsl [h 0.0 v])
hsl-to (uc/hsv->hsl [h 1.0 v])
format-hsl (fn [[h s l]] format-hsl (fn [[h s l]]
(str/fmt "hsl(%s, %s, %s)" (str/fmt "hsl(%s, %s, %s)"
@ -548,11 +629,10 @@
(reset! current-library-colors (into [] colors)))))) (reset! current-library-colors (into [] colors))))))
;; When closing the modal we update the recent-color list ;; When closing the modal we update the recent-color list
(mf/use-effect #_(mf/use-effect
(fn [] (fn [] (fn [] (fn []
(st/emit! (dwc/stop-picker)) (st/emit! (dwc/stop-picker))
(when @value-ref (st/emit! (dwl/add-recent-color (state->data @state))))))
(st/emit! (dwl/add-recent-color @value-ref))))))
(mf/use-effect (mf/use-effect
(mf/deps picking-color? picked-color) (mf/deps picking-color? picked-color)
@ -560,18 +640,27 @@
(let [[r g b] (or picked-color [0 0 0]) (let [[r g b] (or picked-color [0 0 0])
hex (uc/rgb->hex [r g b]) hex (uc/rgb->hex [r g b])
[h s v] (uc/hex->hsv hex)] [h s v] (uc/hex->hsv hex)]
(swap! current-color assoc
(swap! update :current-color assoc
:r r :g g :b b :r r :g g :b b
:h h :s s :v v :h h :s s :v v
:hex hex) :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) (mf/deps picking-color? picked-color-select)
(fn [] (when (and 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 {:ref ref-picker}
[:div.colorpicker-content [:div.colorpicker-content
@ -584,27 +673,49 @@
i/picker] i/picker]
[:div.gradients-buttons [:div.gradients-buttons
[:button.gradient.linear-gradient #_{:class "active"}] [:button.gradient.linear-gradient
[:button.gradient.radial-gradient]]] {:on-click (on-activate-gradient :linear-gradient)
:class (when (= :linear-gradient (:type @state)) "active")}]
#_[:div.gradient-stops [:button.gradient.radial-gradient
[:div.gradient-background {:style {:background "linear-gradient(90deg, #EC0BE5, #CDCDCD)" }}] {: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 [:div.gradient-stop-wrapper
[:div.gradient-stop.start {:style {:background-color "#EC0BE5"}}] (for [[offset value] (:stops @state)]
[:div.gradient-stop.end {:style {:background-color "#CDCDCD" [:div.gradient-stop {:class (when (= (:editing-stop @state) offset) "active")
:left "100%"}}]]] :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? (if picking-color?
[:div.picker-detail-wrapper [:div.picker-detail-wrapper
[:div.center-circle] [:div.center-circle]
[:canvas#picker-detail {:width 200 :height 160}]] [:canvas#picker-detail {:width 200 :height 160}]]
(case @active-tab (case @active-tab
:ramp [:& ramp-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}] :harmony [:& harmony-selector {:color current-color :on-change handle-change-color}]
:hsva [:& hsva-selector {:color @current-color :on-change handle-change-color}] :hsva [:& hsva-selector {:color current-color :on-change handle-change-color}]
nil)) 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 [:div.libraries
[:select {:on-change (fn [e] [:select {:on-change (fn [e]
@ -620,7 +731,7 @@
[:div.selected-colors [:div.selected-colors
(when (= "file" @selected-library) (when (= "file" @selected-library)
[:div.color-bullet.button.plus-button {:style {:background-color "white"} [: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]) i/plus])
[:div.color-bullet.button {:style {:background-color "white"} [: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)] (for [[idx {:keys [id file-id value]}] (map-indexed vector @current-library-colors)]
[:div.color-bullet {:key (str "color-" idx) [:div.color-bullet {:key (str "color-" idx)
:on-click (fn [] :on-click (fn []
(swap! current-color assoc :hex value) (swap! update :current-color assoc :hex value)
(reset! value-ref value) #_(reset! value-ref value)
(let [[r g b] (uc/hex->rgb value) (let [[r g b] (uc/hex->rgb value)
[h s v] (uc/hex->hsv value)] [h s v] (uc/hex->hsv value)]
(swap! current-color assoc (swap! update current-color assoc
:r r :g g :b b :r r :g g :b b
:h h :s s :v v) :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}}])]]] :style {:background-color value}}])]]]
[:div.colorpicker-tabs [:div.colorpicker-tabs
[:div.colorpicker-tab {:class (when (= @active-tab :ramp) "active") [:div.colorpicker-tab {:class (when (= @active-tab :ramp) "active")
@ -650,7 +762,8 @@
[:div.actions [:div.actions
[:button.btn-primary.btn-large [:button.btn-primary.btn-large
{:on-click (fn [] {:on-click (fn []
(on-accept @value-ref) ;; TODO: REVIEW FOR GRADIENTS
#_(on-accept @value-ref)
(modal/hide!))} (modal/hide!))}
(t locale "workspace.libraries.colors.save-color")]])]) (t locale "workspace.libraries.colors.save-color")]])])
) )
@ -673,31 +786,35 @@
(mf/defc colorpicker-modal (mf/defc colorpicker-modal
{::mf/register modal/components {::mf/register modal/components
::mf/register-as :colorpicker} ::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) (let [vport (mf/deref viewport)
dirty? (mf/use-var false) dirty? (mf/use-var false)
last-change (mf/use-var nil) last-change (mf/use-var nil)
position (or position :left) position (or position :left)
style (calculate-position vport position x y) style (calculate-position vport position x y)
handle-change (fn [new-value new-opacity id file-id shift-clicked?] handle-change (fn [new-data shift-clicked?]
(when (or (not= new-value value) (not= new-opacity opacity)) (reset! dirty? (not= data new-data))
(reset! dirty? true)) (reset! last-change new-data)
(reset! last-change [new-value new-opacity id file-id])
(when on-change (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 (mf/use-effect
(fn [] (fn []
#(when (and @dirty? on-close) #(when (and @dirty? @last-change on-close)
(when-let [[value opacity id file-id] @last-change] (on-close @last-change))))
(on-close value opacity id file-id)))))
[:div.colorpicker-tooltip [:div.colorpicker-tooltip
{:style (clj->js style)} {:style (clj->js style)}
[:& colorpicker {:value (or value default) [:& colorpicker {:data data
:opacity (or opacity 1)
:on-change handle-change :on-change handle-change
:on-accept on-accept :on-accept on-accept}]]))
:disable-opacity disable-opacity}]]))

View file

@ -13,13 +13,14 @@
[rumext.alpha :as mf] [rumext.alpha :as mf]
[cuerdas.core :as str] [cuerdas.core :as str]
[beicon.core :as rx] [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.common.math :as mth]
[app.util.dom :as dom]
[app.common.geom.point :as gpt] [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-width 2)
(def gradient-line-stroke-color "white") (def gradient-line-stroke-color "white")
@ -114,7 +115,8 @@
:on-mouse-down (partial on-mouse-down :to-p) :on-mouse-down (partial on-mouse-down :to-p)
:on-mouse-up (partial on-mouse-up :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)) :y (- (:y point) (/ gradient-square-width 2 zoom))
:rx (/ gradient-square-radius zoom) :rx (/ gradient-square-radius zoom)
:width (/ gradient-square-width zoom) :width (/ gradient-square-width zoom)
@ -220,73 +222,68 @@
:on-mouse-up (partial on-mouse-up :to-p)}]])) :on-mouse-up (partial on-mouse-up :to-p)}]]))
(mf/defc gradient-handlers (mf/defc gradient-handlers
[{:keys [shape zoom]}] [{:keys [id zoom]}]
(let [{:keys [x y width height] :as sr} (:selrect shape) (let [shape (mf/deref (refs/object-by-id id))
{:keys [x y width height] :as sr} (:selrect shape)
state (mf/use-state (:fill-color-gradient shape default-gradient)) gradient (:fill-color-gradient shape)
[{start-color :color start-opacity :opacity} [{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))) from-p (gpt/point (+ x (* width (:start-x gradient)))
(+ y (* height (:start-y @state)))) (+ y (* height (:start-y gradient))))
to-p (gpt/point (+ x (* width (:end-x @state))) to-p (gpt/point (+ x (* width (:end-x gradient)))
(+ y (* height (:end-y @state)))) (+ y (* height (:end-y gradient))))
gradient-vec (gpt/to-vec from-p to-p) gradient-vec (gpt/to-vec from-p to-p)
gradient-length (gpt/length gradient-vec) gradient-length (gpt/length gradient-vec)
width-v (-> gradient-vec width-v (-> gradient-vec
(gpt/normal-left) (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)))) (gpt/multiply (gpt/point (/ width 2))))
width-p (gpt/add from-p width-v) 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] on-change-start (fn [point]
(let [start-x (/ (- (:x point) x) width) (let [start-x (/ (- (:x point) x) width)
start-y (/ (- (:y point) y) height)] start-y (/ (- (:y point) y) height)
(swap! state assoc start-x (mth/precision start-x 2)
:start-x start-x start-y (mth/precision start-y 2)]
:start-y start-y ))) (change! {:start-x start-x :start-y start-y})))
on-change-finish (fn [point] on-change-finish (fn [point]
(let [end-x (/ (- (:x point) x) width) (let [end-x (/ (- (:x point) x) width)
end-y (/ (- (:y point) y) height)] end-y (/ (- (:y point) y) height)
(swap! state assoc
:end-x end-x end-x (mth/precision end-x 2)
:end-y end-y))) end-y (mth/precision end-y 2)]
(change! {:end-x end-x :end-y end-y})))
on-change-width (fn [point] on-change-width (fn [point]
(let [scale-factor-y (/ gradient-length (/ height 2)) (let [scale-factor-y (/ gradient-length (/ height 2))
norm-dist (/ (gpt/distance point from-p) norm-dist (/ (gpt/distance point from-p)
(* (/ width 2) scale-factor-y))] (* (/ 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"))] on-change-stop-color (fn [offset color opacity] (println "change-color"))]
(mf/use-effect (when gradient
(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 [:& gradient-handler-transformed
{:from-p from-p {:from-p from-p
:to-p to-p :to-p to-p
:width-p (when (= :radial (:type @state)) width-p) :width-p (when (= :radial (:type gradient)) width-p)
:from-color {:value start-color :opacity start-opacity} :from-color {:value start-color :opacity start-opacity}
:to-color {:value end-color :opacity end-opacity} :to-color {:value end-color :opacity end-opacity}
:zoom zoom :zoom zoom
:on-change-start on-change-start :on-change-start on-change-start
:on-change-finish on-change-finish :on-change-finish on-change-finish
:on-change-width on-change-width :on-change-width on-change-width
:on-change-stop-color on-change-stop-color}])) :on-change-stop-color on-change-stop-color}])))

View file

@ -28,8 +28,7 @@
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.matrix :as gmt] [app.common.geom.matrix :as gmt]
[app.util.debug :refer [debug?]] [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 rotation-handler-size 25)
(def resize-point-radius 4) (def resize-point-radius 4)
@ -210,11 +209,7 @@
(case type (case type
:rotation (when (not= :frame (:type shape)) [:> rotation-handler props]) :rotation (when (not= :frame (:type shape)) [:> rotation-handler props])
:resize-point [:> resize-point-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) ;; --- Selection Handlers (Component)
(mf/defc path-edition-selection-handlers (mf/defc path-edition-selection-handlers

View file

@ -21,7 +21,7 @@
[app.util.i18n :as i18n :refer [tr t]] [app.util.i18n :as i18n :refer [tr t]]
[app.util.object :as obj])) [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? (defn- fill-menu-props-equals?
[np op] [np op]
@ -36,24 +36,28 @@
(= (:fill-color new-values) (= (:fill-color new-values)
(:fill-color old-values)) (:fill-color old-values))
(= (:fill-opacity new-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/defc fill-menu
{::mf/wrap [#(mf/memo' % fill-menu-props-equals?)]} {::mf/wrap [#(mf/memo' % fill-menu-props-equals?)]}
[{:keys [ids type values editor] :as props}] [{:keys [ids type values editor] :as props}]
(let [locale (mf/deref i18n/locale) (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 label (case type
:multiple (t locale "workspace.options.selection-fill") :multiple (t locale "workspace.options.selection-fill")
:group (t locale "workspace.options.group-fill") :group (t locale "workspace.options.group-fill")
(t locale "workspace.options.fill")) (t locale "workspace.options.fill"))
color {:value (:fill-color values) color {:color (:fill-color values)
:opacity (:fill-opacity values) :opacity (:fill-opacity values)
:id (:fill-color-ref-id 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 on-add
(mf/use-callback (mf/use-callback
@ -70,8 +74,8 @@
on-change on-change
(mf/use-callback (mf/use-callback
(mf/deps ids) (mf/deps ids)
(fn [value opacity id file-id] (fn [color]
(st/emit! (dc/change-fill ids value opacity id file-id)))) (st/emit! (dc/change-fill2 ids color))))
on-open-picker on-open-picker
(mf/use-callback (mf/use-callback

View file

@ -10,16 +10,18 @@
(ns app.main.ui.workspace.sidebar.options.rows.color-row (ns app.main.ui.workspace.sidebar.options.rows.color-row
(:require (:require
[rumext.alpha :as mf] [rumext.alpha :as mf]
[cuerdas.core :as str]
[app.common.math :as math] [app.common.math :as math]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.data :refer [classnames]] [app.util.data :refer [classnames]]
[app.util.i18n :as i18n :refer [tr]] [app.util.i18n :as i18n :refer [tr]]
[app.main.data.modal :as modal] [app.main.data.modal :as modal]
[app.common.data :as d] [app.common.data :as d]
[app.main.refs :as refs])) [app.main.refs :as refs]
[app.util.color :as uc]))
(defn color-picker-callback (defn color-picker-callback
[color handle-change-color handle-open handle-close disable-opacity] [color handle-change-color handle-open handle-close]
(fn [event] (fn [event]
(let [x (.-clientX event) (let [x (.-clientX event)
y (.-clientY event) y (.-clientY event)
@ -27,14 +29,22 @@
:y y :y y
:on-change handle-change-color :on-change handle-change-color
:on-close handle-close :on-close handle-close
:value (:value color) :data color}]
:opacity (:opacity color)
:disable-opacity disable-opacity}]
(handle-open) (handle-open)
(modal/show! :colorpicker props)))) (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] (defn remove-hash [value]
(if (or (nil? value) (= value :multiple)) "" (subs value 1))) (if (or (nil? value) (= value :multiple)) "" (subs value 1)))
@ -59,38 +69,39 @@
(if (= v :multiple) nil v)) (if (= v :multiple) nil v))
(mf/defc color-row (mf/defc color-row
[{:keys [color on-change on-open on-close disable-opacity]}] [{:keys [color on-change on-open on-close]}]
(let [;; (let [file-colors (mf/deref refs/workspace-file-colors)
file-colors (mf/deref refs/workspace-file-colors)
shared-libs (mf/deref refs/workspace-libraries) shared-libs (mf/deref refs/workspace-libraries)
get-color-name (fn [{:keys [id file-id]}] get-color-name (fn [{:keys [id file-id]}]
(let [src-colors (if file-id (get-in shared-libs [file-id :data :colors]) file-colors)] (let [src-colors (if file-id (get-in shared-libs [file-id :data :colors]) file-colors)]
(get-in src-colors [id :name]))) (get-in src-colors [id :name])))
default-color {:value "#000000" :opacity 1}
parse-color (fn [color] parse-color (fn [color]
(-> (merge default-color color) (-> color
(update :value #(or % "#000000")) (update :color #(or % (:value color)))))
(update :opacity #(or % 1))))
state (mf/use-state (parse-color color)) state (mf/use-state (parse-color color))
value (:value @state) value (:color @state)
opacity (:opacity @state) opacity (:opacity @state)
change-value (fn [new-value] 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)))) (when on-change (on-change new-value (remove-multiple opacity))))
change-opacity (fn [new-opacity] change-opacity (fn [new-opacity]
(swap! state assoc :opacity new-opacity) (swap! state assoc :opacity new-opacity)
(when on-change (on-change (remove-multiple value) new-opacity))) (when on-change (on-change (remove-multiple value) new-opacity)))
handle-pick-color (fn [new-value new-opacity id file-id] ;;handle-pick-color (fn [new-value new-opacity id file-id]
(reset! state {:value new-value :opacity new-opacity}) ;; (reset! state {:color new-value :opacity new-opacity})
(when on-change (on-change new-value new-opacity id file-id))) ;; (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))) handle-open (fn [] (when on-open (on-open)))
@ -123,21 +134,31 @@
[:div.row-flex.color-data [:div.row-flex.color-data
[:span.color-th [:span.color-th
{:class (when (and (:id color) (not= (:id color) :multiple)) "color-name") {:class (when (and (:id color) (not= (:id color) :multiple)) "color-name")
:style {:background-color (-> value value-to-background)} :style {:background (as-background color)}
:on-click (color-picker-callback @state handle-pick-color handle-open handle-close disable-opacity)} :on-click (color-picker-callback @state handle-pick-color handle-open handle-close)}
(when (= value :multiple) "?")] (when (= value :multiple) "?")]
(if (:id color) (cond
;; Rendering a color with ID
(:id color)
[:div.color-info [:div.color-info
[:div.color-name (str (get-color-name color))]] [:div.color-name (str (get-color-name color))]]
;; Rendering a gradient
(:gradient color)
[:div.color-info
[:div.color-name (str (get-in color [:gradient :type]))]]
;; Rendering a plain color/opacity
:else
[:*
[:div.color-info [:div.color-info
[:input {:value (-> value remove-hash) [:input {:value (-> value remove-hash)
:pattern "^[0-9a-fA-F]{0,6}$" :pattern "^[0-9a-fA-F]{0,6}$"
:placeholder (tr "settings.multiple") :placeholder (tr "settings.multiple")
:on-click select-all :on-click select-all
:on-change handle-value-change}]]) :on-change handle-value-change}]]
(when (not disable-opacity)
[:div.input-element [:div.input-element
{:class (classnames :percentail (not= opacity :multiple))} {:class (classnames :percentail (not= opacity :multiple))}
[:input.input-text {:type "number" [:input.input-text {:type "number"
@ -146,5 +167,5 @@
:on-click select-all :on-click select-all
:on-change handle-opacity-change :on-change handle-opacity-change
:min "0" :min "0"
:max "100"}]])])) :max "100"}]]])]))

View file

@ -40,6 +40,7 @@
[app.main.ui.workspace.snap-distances :refer [snap-distances]] [app.main.ui.workspace.snap-distances :refer [snap-distances]]
[app.main.ui.workspace.frame-grid :refer [frame-grid]] [app.main.ui.workspace.frame-grid :refer [frame-grid]]
[app.main.ui.workspace.shapes.outline :refer [outline]] [app.main.ui.workspace.shapes.outline :refer [outline]]
[app.main.ui.workspace.gradients :refer [gradient-handlers]]
[app.common.math :as mth] [app.common.math :as mth]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.dom.dnd :as dnd] [app.util.dom.dnd :as dnd]
@ -642,6 +643,10 @@
:zoom zoom :zoom zoom
:edition edition}]) :edition edition}])
(when (= (count selected) 1)
[:& gradient-handlers {:id (first selected)
:zoom zoom}])
(when drawing-obj (when drawing-obj
[:& draw-area {:shape drawing-obj [:& draw-area {:shape drawing-obj
:zoom zoom :zoom zoom

View file

@ -77,3 +77,16 @@
(defn hsv->hsl (defn hsv->hsl
[hsv] [hsv]
(hex->hsl (hsv->hex 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))))