mirror of
https://github.com/penpot/penpot.git
synced 2025-01-06 14:50:20 -05:00
🎉 Add new gradients UI
This commit is contained in:
parent
bc250c962d
commit
838c1324b9
21 changed files with 1490 additions and 281 deletions
|
@ -10,6 +10,8 @@
|
|||
|
||||
### :sparkles: New features
|
||||
|
||||
- New gradients UI with multi-stop support.
|
||||
|
||||
### :bug: Bugs fixed
|
||||
|
||||
|
||||
|
|
|
@ -478,3 +478,63 @@
|
|||
a (+ (* ah 100) (* av 10))
|
||||
b (+ (* bh 100) (* bv 10))]
|
||||
(compare a b)))
|
||||
|
||||
(defn interpolate-color
|
||||
[c1 c2 offset]
|
||||
(cond
|
||||
(<= offset (:offset c1)) (assoc c1 :offset offset)
|
||||
(>= offset (:offset c2)) (assoc c2 :offset offset)
|
||||
|
||||
:else
|
||||
(let [tr-offset (/ (- offset (:offset c1)) (- (:offset c2) (:offset c1)))
|
||||
[r1 g1 b1] (hex->rgb (:color c1))
|
||||
[r2 g2 b2] (hex->rgb (:color c2))
|
||||
a1 (:opacity c1)
|
||||
a2 (:opacity c2)
|
||||
r (+ r1 (* (- r2 r1) tr-offset))
|
||||
g (+ g1 (* (- g2 g1) tr-offset))
|
||||
b (+ b1 (* (- b2 b1) tr-offset))
|
||||
a (+ a1 (* (- a2 a1) tr-offset))]
|
||||
{:color (rgb->hex [r g b])
|
||||
:opacity a
|
||||
:r r
|
||||
:g g
|
||||
:b b
|
||||
:alpha a
|
||||
:offset offset})))
|
||||
|
||||
(defn- offset-spread
|
||||
[from to num]
|
||||
(->> (range 0 num)
|
||||
(map #(mth/precision (+ from (* (/ (- to from) (dec num)) %)) 2))))
|
||||
|
||||
(defn uniform-spread?
|
||||
"Checks if the gradient stops are spread uniformly"
|
||||
[stops]
|
||||
(let [cs (count stops)
|
||||
from (first stops)
|
||||
to (last stops)
|
||||
expect-vals (offset-spread (:offset from) (:offset to) cs)
|
||||
|
||||
calculate-expected
|
||||
(fn [expected-offset stop]
|
||||
(and (mth/close? (:offset stop) expected-offset)
|
||||
(let [ec (interpolate-color from to expected-offset)]
|
||||
(and (= (:color ec) (:color stop))
|
||||
(= (:opacity ec) (:opacity stop))))))]
|
||||
(->> (map calculate-expected expect-vals stops)
|
||||
(every? true?))))
|
||||
|
||||
(defn uniform-spread
|
||||
"Assign an uniform spread to the offset values for the gradient"
|
||||
[from to num-stops]
|
||||
(->> (offset-spread (:offset from) (:offset to) num-stops)
|
||||
(mapv (fn [offset]
|
||||
(interpolate-color from to offset)))))
|
||||
|
||||
(defn interpolate-gradient
|
||||
[stops offset]
|
||||
(let [idx (d/index-of-pred stops #(<= offset (:offset %)))
|
||||
start (if (= idx 0) (first stops) (get stops (dec idx)))
|
||||
end (if (nil? idx) (last stops) (get stops idx))]
|
||||
(interpolate-color start end offset)))
|
||||
|
|
|
@ -74,7 +74,7 @@
|
|||
(-> p2 (gpt/add right-v) (gpt/add bottom-v))
|
||||
(-> p3 (gpt/add left-v) (gpt/add bottom-v))])))
|
||||
|
||||
(defn- project-t
|
||||
(defn project-t
|
||||
"Given a point and a line returns the parametric t the cross point with the line going through the other axis projected"
|
||||
[point [start end] other-axis-vec]
|
||||
|
||||
|
|
|
@ -22,3 +22,143 @@ test("Bug 7549 - User clicks on color swatch to display the color picker next to
|
|||
const distance = swatchBox.x - (pickerBox.x + pickerBox.width);
|
||||
expect(distance).toBeLessThan(60);
|
||||
});
|
||||
|
||||
test("Create a LINEAR gradient", async ({ page }) => {
|
||||
const workspacePage = new WorkspacePage(page);
|
||||
await workspacePage.setupEmptyFile();
|
||||
await workspacePage.mockRPC(
|
||||
/get\-file\?/,
|
||||
"workspace/get-file-not-empty.json",
|
||||
);
|
||||
await workspacePage.mockRPC(
|
||||
"update-file?id=*",
|
||||
"workspace/update-file-create-rect.json",
|
||||
);
|
||||
|
||||
await workspacePage.goToWorkspace({
|
||||
fileId: "6191cd35-bb1f-81f7-8004-7cc63d087374",
|
||||
pageId: "6191cd35-bb1f-81f7-8004-7cc63d087375",
|
||||
});
|
||||
await workspacePage.clickLeafLayer("Rectangle");
|
||||
|
||||
const swatch = workspacePage.page.getByRole("button", { name: "#B1B2B5" });
|
||||
const swatchBox = await swatch.boundingBox();
|
||||
await swatch.click();
|
||||
|
||||
const select = await workspacePage.page.getByText("Solid");
|
||||
await select.click();
|
||||
|
||||
const gradOption = await workspacePage.page.getByText("Gradient");
|
||||
await gradOption.click();
|
||||
|
||||
const addStopBtn = await workspacePage.page.getByRole("button", {
|
||||
name: "Add stop",
|
||||
});
|
||||
await addStopBtn.click();
|
||||
await addStopBtn.click();
|
||||
await addStopBtn.click();
|
||||
|
||||
const removeBtn = await workspacePage.page
|
||||
.getByTestId("colorpicker")
|
||||
.getByRole("button", { name: "Remove color" })
|
||||
.nth(2);
|
||||
await removeBtn.click();
|
||||
await removeBtn.click();
|
||||
|
||||
const inputColor1 = await workspacePage.page.getByPlaceholder("Mixed").nth(1);
|
||||
await inputColor1.fill("fabada");
|
||||
|
||||
const inputOpacity1 = await workspacePage.page
|
||||
.getByTestId("colorpicker")
|
||||
.getByPlaceholder("--")
|
||||
.nth(1);
|
||||
await inputOpacity1.fill("100");
|
||||
|
||||
const inputColor2 = await workspacePage.page.getByPlaceholder("Mixed").nth(2);
|
||||
await inputColor2.fill("red");
|
||||
|
||||
const inputOpacity2 = await workspacePage.page
|
||||
.getByTestId("colorpicker")
|
||||
.getByPlaceholder("--")
|
||||
.nth(2);
|
||||
await inputOpacity2.fill("100");
|
||||
|
||||
const inputOpacityGlobal = await workspacePage.page
|
||||
.locator("div")
|
||||
.filter({ hasText: /^FillLinear gradient%$/ })
|
||||
.getByPlaceholder("--");
|
||||
await inputOpacityGlobal.fill("100");
|
||||
});
|
||||
|
||||
test("Create a RADIAL gradient", async ({ page }) => {
|
||||
const workspacePage = new WorkspacePage(page);
|
||||
await workspacePage.setupEmptyFile();
|
||||
await workspacePage.mockRPC(
|
||||
/get\-file\?/,
|
||||
"workspace/get-file-not-empty.json",
|
||||
);
|
||||
await workspacePage.mockRPC(
|
||||
"update-file?id=*",
|
||||
"workspace/update-file-create-rect.json",
|
||||
);
|
||||
|
||||
await workspacePage.goToWorkspace({
|
||||
fileId: "6191cd35-bb1f-81f7-8004-7cc63d087374",
|
||||
pageId: "6191cd35-bb1f-81f7-8004-7cc63d087375",
|
||||
});
|
||||
await workspacePage.clickLeafLayer("Rectangle");
|
||||
|
||||
const swatch = workspacePage.page.getByRole("button", { name: "#B1B2B5" });
|
||||
const swatchBox = await swatch.boundingBox();
|
||||
await swatch.click();
|
||||
|
||||
const select = await workspacePage.page.getByText("Solid");
|
||||
await select.click();
|
||||
|
||||
const gradOption = await workspacePage.page.getByText("Gradient");
|
||||
await gradOption.click();
|
||||
|
||||
const gradTypeOptions = await workspacePage.page
|
||||
.getByTestId("colorpicker")
|
||||
.locator("div")
|
||||
.filter({ hasText: "Linear" })
|
||||
.nth(3);
|
||||
await gradTypeOptions.click();
|
||||
|
||||
const gradRadialOption = await workspacePage.page
|
||||
.locator("li")
|
||||
.filter({ hasText: "Radial" });
|
||||
await gradRadialOption.click();
|
||||
|
||||
const addStopBtn = await workspacePage.page.getByRole("button", {
|
||||
name: "Add stop",
|
||||
});
|
||||
await addStopBtn.click();
|
||||
await addStopBtn.click();
|
||||
await addStopBtn.click();
|
||||
|
||||
const removeBtn = await workspacePage.page
|
||||
.getByTestId("colorpicker")
|
||||
.getByRole("button", { name: "Remove color" })
|
||||
.nth(2);
|
||||
await removeBtn.click();
|
||||
await removeBtn.click();
|
||||
|
||||
const inputColor1 = await workspacePage.page.getByPlaceholder("Mixed").nth(1);
|
||||
await inputColor1.fill("fabada");
|
||||
|
||||
const inputOpacity1 = await workspacePage.page
|
||||
.getByTestId("colorpicker")
|
||||
.getByPlaceholder("--")
|
||||
.nth(1);
|
||||
await inputOpacity1.fill("100");
|
||||
|
||||
const inputColor2 = await workspacePage.page.getByPlaceholder("Mixed").nth(2);
|
||||
await inputColor2.fill("red");
|
||||
|
||||
const inputOpacity2 = await workspacePage.page
|
||||
.getByTestId("colorpicker")
|
||||
.getByPlaceholder("--")
|
||||
.nth(2);
|
||||
await inputOpacity2.fill("100");
|
||||
});
|
||||
|
|
3
frontend/resources/images/icons/reorder.svg
Normal file
3
frontend/resources/images/icons/reorder.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" stroke-width="2" stroke-linecap="round">
|
||||
<path d="M6.05 4h-.1m.1 4h-.1m.1 4h-.1m4.1-8h-.1m.1 4h-.1m.1 4h-.1"/>
|
||||
</svg>
|
After Width: | Height: | Size: 203 B |
|
@ -19,7 +19,7 @@
|
|||
[potok.v2.core :as ptk]))
|
||||
|
||||
;; Change this to :info :debug or :trace to debug this module
|
||||
(log/set-level! :debug)
|
||||
(log/set-level! :info)
|
||||
|
||||
(def page-change?
|
||||
#{:add-page :mod-page :del-page :mov-page})
|
||||
|
|
|
@ -541,16 +541,34 @@
|
|||
(ctc/check-color! color))
|
||||
|
||||
(ptk/reify ::apply-color-from-colorpicker
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
;; FIXME: revisit this
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [gradient-type (dm/get-in color [:gradient :type])]
|
||||
(rx/of
|
||||
(cond
|
||||
(:image color) (activate-colorpicker-image)
|
||||
(:color color) (activate-colorpicker-color)
|
||||
(= :linear gradient-type) (activate-colorpicker-gradient :linear-gradient)
|
||||
(= :radial gradient-type) (activate-colorpicker-gradient :radial-gradient)))))))
|
||||
(update state :colorpicker
|
||||
(fn [state]
|
||||
(cond
|
||||
(:image color)
|
||||
(-> state
|
||||
(assoc :type :image)
|
||||
(dissoc :editing-stop :stops :gradient))
|
||||
|
||||
(:color color)
|
||||
(-> state
|
||||
(assoc :type :color)
|
||||
(dissoc :editing-stop :stops :gradient))
|
||||
|
||||
|
||||
(= :linear gradient-type)
|
||||
(-> state
|
||||
(assoc :type :linear-gradient)
|
||||
(assoc :editing-stop 0)
|
||||
(d/dissoc-in [:current-color :image]))
|
||||
|
||||
(= :radial gradient-type)
|
||||
(-> state
|
||||
(assoc :type :radial-gradient)
|
||||
(assoc :editing-stop 0)
|
||||
(d/dissoc-in [:current-color :image])))))))))
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
@ -591,7 +609,7 @@
|
|||
:width 1.0})
|
||||
|
||||
(defn get-color-from-colorpicker-state
|
||||
[{:keys [type current-color stops gradient] :as state}]
|
||||
[{:keys [type current-color stops gradient opacity] :as state}]
|
||||
(cond
|
||||
(= type :color)
|
||||
(clear-color-components current-color)
|
||||
|
@ -600,7 +618,8 @@
|
|||
(clear-image-components current-color)
|
||||
|
||||
:else
|
||||
{:gradient (-> gradient
|
||||
{:opacity opacity
|
||||
:gradient (-> gradient
|
||||
(assoc :type (case type
|
||||
:linear-gradient :linear
|
||||
:radial-gradient :radial))
|
||||
|
@ -625,13 +644,19 @@
|
|||
(let [stopper (rx/merge
|
||||
(rx/filter (ptk/type? ::finalize-colorpicker) stream)
|
||||
(rx/filter (ptk/type? ::initialize-colorpicker) stream))]
|
||||
|
||||
(->> (rx/merge
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::update-colorpicker-gradient))
|
||||
(rx/debounce 200))
|
||||
(rx/debounce 20))
|
||||
(rx/filter (ptk/type? ::update-colorpicker-color) stream)
|
||||
(rx/filter (ptk/type? ::activate-colorpicker-gradient) stream))
|
||||
(->> (rx/filter (ptk/type? ::activate-colorpicker-gradient) stream)
|
||||
(rx/debounce 20))
|
||||
(rx/filter (ptk/type? ::update-colorpicker-stops) stream)
|
||||
(rx/filter (ptk/type? ::update-colorpicker-gradient-opacity) stream)
|
||||
(rx/filter (ptk/type? ::update-colorpicker-add-stop) stream)
|
||||
(rx/filter (ptk/type? ::update-colorpicker-add-auto) stream)
|
||||
(rx/filter (ptk/type? ::remove-gradient-stop) stream))
|
||||
(rx/debounce 40)
|
||||
(rx/map (constantly (colorpicker-onchange-runner on-change)))
|
||||
(rx/take-until stopper))))
|
||||
|
||||
|
@ -660,14 +685,18 @@
|
|||
(let [current-color (:current-color state)]
|
||||
(if (some? gradient)
|
||||
(let [stop (or (:editing-stop state) 0)
|
||||
stops (mapv split-color-components (:stops gradient))]
|
||||
(-> state
|
||||
(assoc :current-color (nth stops stop))
|
||||
(assoc :stops stops)
|
||||
(assoc :gradient (-> gradient
|
||||
(dissoc :stops)
|
||||
(assoc :shape-id shape-id)))
|
||||
(assoc :editing-stop stop)))
|
||||
new-stops (mapv split-color-components (:stops gradient))
|
||||
new-gradient (-> gradient
|
||||
(dissoc :stops)
|
||||
(assoc :shape-id shape-id))]
|
||||
(if (and (= (:stops state) new-stops) (= (:gradient state) new-gradient))
|
||||
state
|
||||
(-> state
|
||||
(assoc :opacity (:opacity data))
|
||||
(assoc :current-color (get new-stops stop))
|
||||
(assoc :stops new-stops)
|
||||
(assoc :gradient new-gradient)
|
||||
(assoc :editing-stop stop))))
|
||||
|
||||
(-> state
|
||||
(cond-> (or (nil? current-color)
|
||||
|
@ -678,6 +707,132 @@
|
|||
(dissoc :gradient)
|
||||
(dissoc :stops))))))))))
|
||||
|
||||
(defn update-colorpicker-gradient-opacity
|
||||
[opacity]
|
||||
(ptk/reify ::update-colorpicker-gradient-opacity
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :colorpicker
|
||||
(fn [state]
|
||||
(-> state
|
||||
(assoc :opacity opacity)))))))
|
||||
|
||||
(defn update-colorpicker-add-auto
|
||||
[]
|
||||
(ptk/reify ::update-colorpicker-add-auto
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :colorpicker
|
||||
(fn [{:keys [stops editing-stop] :as state}]
|
||||
(if (cc/uniform-spread? stops)
|
||||
;; Add to uniform
|
||||
(let [stops (->> (cc/uniform-spread (first stops) (last stops) (inc (count stops)))
|
||||
(mapv split-color-components))]
|
||||
(-> state
|
||||
(assoc :current-color (get stops editing-stop))
|
||||
(assoc :stops stops)))
|
||||
|
||||
;; We add the stop to the middle point between the selected
|
||||
;; and the next one.
|
||||
;; If the last stop is selected then it's added between the
|
||||
;; last two stops.
|
||||
(let [index
|
||||
(if (= editing-stop (dec (count stops)))
|
||||
(dec editing-stop)
|
||||
editing-stop)
|
||||
|
||||
{from-offset :offset} (get stops index)
|
||||
{to-offset :offset} (get stops (inc index))
|
||||
|
||||
half-point-offset
|
||||
(+ from-offset (/ (- to-offset from-offset) 2))
|
||||
|
||||
new-stop (-> (cc/interpolate-gradient stops half-point-offset)
|
||||
(split-color-components))
|
||||
|
||||
stops (conj stops new-stop)
|
||||
stops (into [] (sort-by :offset stops))
|
||||
editing-stop (d/index-of-pred stops #(= new-stop %))]
|
||||
(-> state
|
||||
(assoc :editing-stop editing-stop)
|
||||
(assoc :current-color (get stops editing-stop))
|
||||
(assoc :stops stops)))))))))
|
||||
|
||||
(defn update-colorpicker-add-stop
|
||||
[offset]
|
||||
(ptk/reify ::update-colorpicker-add-stop
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :colorpicker
|
||||
(fn [state]
|
||||
(let [stops (:stops state)
|
||||
new-stop (-> (cc/interpolate-gradient stops offset)
|
||||
(split-color-components))
|
||||
stops (conj stops new-stop)
|
||||
stops (into [] (sort-by :offset stops))
|
||||
editing-stop (d/index-of-pred stops #(= new-stop %))]
|
||||
(-> state
|
||||
(assoc :editing-stop editing-stop)
|
||||
(assoc :current-color (get stops editing-stop))
|
||||
(assoc :stops stops))))))))
|
||||
|
||||
(defn update-colorpicker-stops
|
||||
[stops]
|
||||
(ptk/reify ::update-colorpicker-stops
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :colorpicker
|
||||
(fn [state]
|
||||
(let [stop (or (:editing-stop state) 0)
|
||||
stops (mapv split-color-components stops)]
|
||||
(-> state
|
||||
(assoc :current-color (get stops stop))
|
||||
(assoc :stops stops))))))))
|
||||
|
||||
(defn sort-colorpicker-stops
|
||||
[]
|
||||
(ptk/reify ::sort-colorpicker-stops
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :colorpicker
|
||||
(fn [state]
|
||||
(let [stop (or (:editing-stop state) 0)
|
||||
stops (mapv split-color-components (:stops state))
|
||||
stop-val (get stops stop)
|
||||
stops (into [] (sort-by :offset stops))
|
||||
stop (d/index-of-pred stops #(= % stop-val))]
|
||||
(-> state
|
||||
(assoc :editing-stop stop)
|
||||
(assoc :stops stops))))))))
|
||||
|
||||
(defn remove-gradient-stop
|
||||
([]
|
||||
(remove-gradient-stop nil))
|
||||
|
||||
([index]
|
||||
(ptk/reify ::remove-gradient-stop
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :colorpicker
|
||||
(fn [{:keys [editing-stop stops] :as state}]
|
||||
(if (> (count stops) 2)
|
||||
(let [delete-index (or index editing-stop 0)
|
||||
delete-stop (get stops delete-index)
|
||||
stops (into [] (remove #(= delete-stop %)) stops)
|
||||
|
||||
editing-stop
|
||||
(cond
|
||||
(< editing-stop delete-index) editing-stop
|
||||
(> editing-stop delete-index) (dec editing-stop)
|
||||
(>= (count stops) editing-stop) (dec (count stops))
|
||||
:else editing-stop)]
|
||||
(-> state
|
||||
(assoc :editing-stop editing-stop)
|
||||
(assoc :stops stops)))
|
||||
|
||||
;; Cannot delete
|
||||
state)))))))
|
||||
|
||||
(defn update-colorpicker-color
|
||||
[changes add-recent?]
|
||||
(ptk/reify ::update-colorpicker-color
|
||||
|
@ -723,16 +878,16 @@
|
|||
(update-in state [:colorpicker :gradient] merge changes))))
|
||||
|
||||
(defn select-colorpicker-gradient-stop
|
||||
[stop]
|
||||
[index]
|
||||
(ptk/reify ::select-colorpicket-gradient-stop
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :colorpicker
|
||||
(fn [state]
|
||||
(if-let [color (get-in state [:stops stop])]
|
||||
(if-let [color (get-in state [:stops index])]
|
||||
(assoc state
|
||||
:current-color color
|
||||
:editing-stop stop)
|
||||
:editing-stop index)
|
||||
state))))))
|
||||
|
||||
(defn activate-colorpicker-color
|
||||
|
|
|
@ -211,9 +211,12 @@
|
|||
|
||||
handle-focus
|
||||
(mf/use-callback
|
||||
(mf/deps on-focus select-on-focus?)
|
||||
(fn [event]
|
||||
(reset! last-value* (parse-value))
|
||||
(let [target (dom/get-target event)]
|
||||
(when on-focus
|
||||
(mf/set-ref-val! dirty-ref true)
|
||||
(on-focus event))
|
||||
|
||||
(when select-on-focus?
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
(def ^:private schema:icon-button
|
||||
[:map
|
||||
[:class {:optional true} :string]
|
||||
[:icon-class {:optional true} :string]
|
||||
[:icon
|
||||
[:and :string [:fn #(contains? icon-list %)]]]
|
||||
[:aria-label :string]
|
||||
|
@ -24,7 +25,7 @@
|
|||
(mf/defc icon-button*
|
||||
{::mf/props :obj
|
||||
::mf/schema schema:icon-button}
|
||||
[{:keys [class icon variant aria-label children] :rest props}]
|
||||
[{:keys [class icon icon-class variant aria-label children] :rest props}]
|
||||
(let [variant (or variant "primary")
|
||||
class (dm/str class " " (stl/css-case :icon-button true
|
||||
:icon-button-primary (= variant "primary")
|
||||
|
@ -33,4 +34,4 @@
|
|||
:icon-button-action (= variant "action")
|
||||
:icon-button-destructive (= variant "destructive")))
|
||||
props (mf/spread-props props {:class class :title aria-label})]
|
||||
[:> "button" props [:> icon* {:id icon :aria-label aria-label}] children]))
|
||||
[:> "button" props [:> icon* {:id icon :aria-label aria-label :class icon-class}] children]))
|
||||
|
|
|
@ -50,10 +50,10 @@
|
|||
(def ^:icon-id align-top "align-top")
|
||||
(def ^:icon-id align-vertical-center "align-vertical-center")
|
||||
(def ^:icon-id arrow "arrow")
|
||||
(def ^:icon-id arrow-up "arrow-up")
|
||||
(def ^:icon-id arrow-down "arrow-down")
|
||||
(def ^:icon-id arrow-left "arrow-left")
|
||||
(def ^:icon-id arrow-right "arrow-right")
|
||||
(def ^:icon-id arrow-up "arrow-up")
|
||||
(def ^:icon-id asc-sort "asc-sort")
|
||||
(def ^:icon-id board "board")
|
||||
(def ^:icon-id boards-thumbnail "boards-thumbnail")
|
||||
|
@ -93,27 +93,27 @@
|
|||
(def ^:icon-id clip-content "clip-content")
|
||||
(def ^:icon-id clipboard "clipboard")
|
||||
(def ^:icon-id clock "clock")
|
||||
(def ^:icon-id close-small "close-small")
|
||||
(def ^:icon-id close "close")
|
||||
(def ^:icon-id close-small "close-small")
|
||||
(def ^:icon-id code "code")
|
||||
(def ^:icon-id column-reverse "column-reverse")
|
||||
(def ^:icon-id column "column")
|
||||
(def ^:icon-id column-reverse "column-reverse")
|
||||
(def ^:icon-id comments "comments")
|
||||
(def ^:icon-id component-copy "component-copy")
|
||||
(def ^:icon-id component "component")
|
||||
(def ^:icon-id component-copy "component-copy")
|
||||
(def ^:icon-id constraint-horizontal "constraint-horizontal")
|
||||
(def ^:icon-id constraint-vertical "constraint-vertical")
|
||||
(def ^:icon-id corner-bottom "corner-bottom")
|
||||
(def ^:icon-id corner-bottom-left "corner-bottom-left")
|
||||
(def ^:icon-id corner-bottom-right "corner-bottom-right")
|
||||
(def ^:icon-id corner-bottom "corner-bottom")
|
||||
(def ^:icon-id corner-center "corner-center")
|
||||
(def ^:icon-id corner-radius "corner-radius")
|
||||
(def ^:icon-id corner-top "corner-top")
|
||||
(def ^:icon-id corner-top-left "corner-top-left")
|
||||
(def ^:icon-id corner-top-right "corner-top-right")
|
||||
(def ^:icon-id curve "curve")
|
||||
(def ^:icon-id delete-text "delete-text")
|
||||
(def ^:icon-id delete "delete")
|
||||
(def ^:icon-id delete-text "delete-text")
|
||||
(def ^:icon-id desc-sort "desc-sort")
|
||||
(def ^:icon-id detach "detach")
|
||||
(def ^:icon-id detached "detached")
|
||||
|
@ -122,33 +122,34 @@
|
|||
(def ^:icon-id document "document")
|
||||
(def ^:icon-id download "download")
|
||||
(def ^:icon-id drop "drop")
|
||||
(def ^:icon-id easing-ease-in-out "easing-ease-in-out")
|
||||
(def ^:icon-id easing-ease-in "easing-ease-in")
|
||||
(def ^:icon-id easing-ease-out "easing-ease-out")
|
||||
(def ^:icon-id easing-ease "easing-ease")
|
||||
(def ^:icon-id easing-ease-in "easing-ease-in")
|
||||
(def ^:icon-id easing-ease-in-out "easing-ease-in-out")
|
||||
(def ^:icon-id easing-ease-out "easing-ease-out")
|
||||
(def ^:icon-id easing-linear "easing-linear")
|
||||
(def ^:icon-id effects "effects")
|
||||
(def ^:icon-id elipse "elipse")
|
||||
(def ^:icon-id exit "exit")
|
||||
(def ^:icon-id expand "expand")
|
||||
(def ^:icon-id external-link "external-link")
|
||||
(def ^:icon-id feedback "feedback")
|
||||
(def ^:icon-id fill-content "fill-content")
|
||||
(def ^:icon-id filter "filter")
|
||||
(def ^:icon-id fixed-width "fixed-width")
|
||||
(def ^:icon-id flex "flex")
|
||||
(def ^:icon-id flex-grid "flex-grid")
|
||||
(def ^:icon-id flex-horizontal "flex-horizontal")
|
||||
(def ^:icon-id flex-vertical "flex-vertical")
|
||||
(def ^:icon-id flex "flex")
|
||||
(def ^:icon-id flip-horizontal "flip-horizontal")
|
||||
(def ^:icon-id flip-vertical "flip-vertical")
|
||||
(def ^:icon-id gap-horizontal "gap-horizontal")
|
||||
(def ^:icon-id gap-vertical "gap-vertical")
|
||||
(def ^:icon-id graphics "graphics")
|
||||
(def ^:icon-id grid "grid")
|
||||
(def ^:icon-id grid-column "grid-column")
|
||||
(def ^:icon-id grid-columns "grid-columns")
|
||||
(def ^:icon-id grid-gutter "grid-gutter")
|
||||
(def ^:icon-id grid-margin "grid-margin")
|
||||
(def ^:icon-id grid "grid")
|
||||
(def ^:icon-id grid-row "grid-row")
|
||||
(def ^:icon-id grid-rows "grid-rows")
|
||||
(def ^:icon-id grid-square "grid-square")
|
||||
|
@ -165,7 +166,6 @@
|
|||
(def ^:icon-id info "info")
|
||||
(def ^:icon-id interaction "interaction")
|
||||
(def ^:icon-id join-nodes "join-nodes")
|
||||
(def ^:icon-id external-link "external-link")
|
||||
(def ^:icon-id justify-content-column-around "justify-content-column-around")
|
||||
(def ^:icon-id justify-content-column-between "justify-content-column-between")
|
||||
(def ^:icon-id justify-content-column-center "justify-content-column-center")
|
||||
|
@ -216,6 +216,7 @@
|
|||
(def ^:icon-id rectangle "rectangle")
|
||||
(def ^:icon-id reload "reload")
|
||||
(def ^:icon-id remove "remove")
|
||||
(def ^:icon-id reorder "reorder")
|
||||
(def ^:icon-id rgba "rgba")
|
||||
(def ^:icon-id rgba-complementary "rgba-complementary")
|
||||
(def ^:icon-id rotation "rotation")
|
||||
|
@ -285,7 +286,7 @@
|
|||
|
||||
(def ^:private schema:icon
|
||||
[:map
|
||||
[:class {:optional true} :string]
|
||||
[:class {:optional true} [:maybe :string]]
|
||||
[:id [:and :string [:fn #(contains? icon-list %)]]]
|
||||
[:size {:optional true}
|
||||
[:maybe [:enum "s" "m"]]]])
|
||||
|
|
|
@ -37,7 +37,8 @@
|
|||
(when (and wrapper
|
||||
(not allow-click-outside)
|
||||
(not (.contains wrapper current))
|
||||
(not (= type (keyword (dom/get-data current "allow-click-modal")))))
|
||||
(not (= type (keyword (dom/get-data current "allow-click-modal"))))
|
||||
(= (.-button event) 0))
|
||||
(dom/stop-propagation event)
|
||||
(dom/prevent-default event)
|
||||
(st/emit! (dm/hide)))))
|
||||
|
|
|
@ -47,8 +47,8 @@
|
|||
(add-metadata! props gradient))
|
||||
|
||||
[:> :linearGradient props
|
||||
(for [{:keys [offset color opacity]} (:stops gradient)]
|
||||
[:stop {:key (dm/str id "-stop-" offset)
|
||||
(for [[index {:keys [offset color opacity]}] (d/enumerate (sort-by :offset (:stops gradient)))]
|
||||
[:stop {:key (dm/str id "-stop-" index)
|
||||
:offset (d/nilv offset 0)
|
||||
:stop-color color
|
||||
:stop-opacity opacity}])]))
|
||||
|
@ -109,8 +109,8 @@
|
|||
(add-metadata! props gradient))
|
||||
|
||||
[:> :radialGradient props
|
||||
(for [{:keys [offset color opacity]} (:stops gradient)]
|
||||
[:stop {:key (dm/str id "-stop-" offset)
|
||||
(for [[index {:keys [offset color opacity]}] (d/enumerate (:stops gradient))]
|
||||
[:stop {:key (dm/str id "-stop-" index)
|
||||
:offset (d/nilv offset 0)
|
||||
:stop-color color
|
||||
:stop-opacity opacity}])]))
|
||||
|
|
|
@ -10,9 +10,12 @@
|
|||
[app.common.colors :as cc]
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.config :as cfg]
|
||||
[app.main.data.events :as-alias ev]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.shortcuts :as dsc]
|
||||
[app.main.data.workspace.colors :as dc]
|
||||
[app.main.data.workspace.libraries :as dwl]
|
||||
[app.main.data.workspace.media :as dwm]
|
||||
|
@ -20,6 +23,7 @@
|
|||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.file-uploader :refer [file-uploader]]
|
||||
[app.main.ui.components.numeric-input :refer [numeric-input*]]
|
||||
[app.main.ui.components.select :refer [select]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as ic]
|
||||
[app.main.ui.ds.layout.tab-switcher :refer [tab-switcher*]]
|
||||
|
@ -30,8 +34,10 @@
|
|||
[app.main.ui.workspace.colorpicker.hsva :refer [hsva-selector]]
|
||||
[app.main.ui.workspace.colorpicker.libraries :refer [libraries]]
|
||||
[app.main.ui.workspace.colorpicker.ramp :refer [ramp-selector]]
|
||||
[app.main.ui.workspace.colorpicker.shortcuts :as sc]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.timers :as ts]
|
||||
[cuerdas.core :as str]
|
||||
[okulary.core :as l]
|
||||
[potok.v2.core :as ptk]
|
||||
|
@ -51,6 +57,14 @@
|
|||
(def viewport
|
||||
(l/derived :vport refs/workspace-local))
|
||||
|
||||
(defn opacity->string
|
||||
[opacity]
|
||||
(if (not= opacity :multiple)
|
||||
(dm/str (-> opacity
|
||||
(d/coalesce 1)
|
||||
(* 100)))
|
||||
:multiple))
|
||||
|
||||
;; --- Color Picker Modal
|
||||
|
||||
(defn use-color-picker-css-variables! [node-ref current-color]
|
||||
|
@ -78,6 +92,8 @@
|
|||
(let [state (mf/deref refs/colorpicker)
|
||||
node-ref (mf/use-ref)
|
||||
|
||||
should-update? (mf/use-var true)
|
||||
|
||||
;; TODO: I think we need to put all this picking state under
|
||||
;; the same object for avoid creating adhoc refs for each
|
||||
;; value
|
||||
|
@ -99,7 +115,12 @@
|
|||
|
||||
fill-image-ref (mf/use-ref nil)
|
||||
|
||||
selected-mode (get state :type :color)
|
||||
color-type (get state :type :color)
|
||||
selected-mode (case color-type
|
||||
(:linear-gradient :radial-gradient)
|
||||
:gradient
|
||||
|
||||
color-type)
|
||||
|
||||
disabled-color-accept? (and
|
||||
(= selected-mode :image)
|
||||
|
@ -147,9 +168,9 @@
|
|||
(fn [value]
|
||||
(case value
|
||||
:color (st/emit! (dc/activate-colorpicker-color))
|
||||
:linear-gradient (st/emit! (dc/activate-colorpicker-gradient :linear-gradient))
|
||||
:radial-gradient (st/emit! (dc/activate-colorpicker-gradient :radial-gradient))
|
||||
:image (st/emit! (dc/activate-colorpicker-image)))))
|
||||
:gradient (st/emit! (dc/activate-colorpicker-gradient :linear-gradient))
|
||||
:image (st/emit! (dc/activate-colorpicker-image))
|
||||
nil)))
|
||||
|
||||
handle-change-color
|
||||
(mf/use-fn
|
||||
|
@ -173,14 +194,6 @@
|
|||
(do (modal/allow-click-outside!)
|
||||
(st/emit! (dc/start-picker))))))
|
||||
|
||||
handle-change-stop
|
||||
(mf/use-fn
|
||||
(fn [event]
|
||||
(let [offset (-> (dom/get-current-target event)
|
||||
(dom/get-data "value")
|
||||
(d/parse-integer))]
|
||||
(st/emit! (dc/select-colorpicker-gradient-stop offset)))))
|
||||
|
||||
on-select-library-color
|
||||
(mf/use-fn
|
||||
(mf/deps data handle-change-color)
|
||||
|
@ -203,6 +216,7 @@
|
|||
(mf/use-fn
|
||||
(mf/deps drag? node-ref)
|
||||
(fn []
|
||||
(reset! should-update? false)
|
||||
(reset! drag? true)
|
||||
(st/emit! (dwu/start-undo-transaction (mf/ref-val node-ref)))))
|
||||
|
||||
|
@ -210,6 +224,7 @@
|
|||
(mf/use-fn
|
||||
(mf/deps drag? node-ref)
|
||||
(fn []
|
||||
(reset! should-update? true)
|
||||
(reset! drag? false)
|
||||
(st/emit! (dwu/commit-undo-transaction (mf/ref-val node-ref)))))
|
||||
|
||||
|
@ -225,11 +240,104 @@
|
|||
(d/concat-vec
|
||||
[{:value :color :label (tr "media.solid")}]
|
||||
(when (not disable-gradient)
|
||||
[{:value :linear-gradient :label (tr "media.linear")}
|
||||
{:value :radial-gradient :label (tr "media.radial")}])
|
||||
[{:value :gradient :label (tr "media.gradient")}])
|
||||
(when (not disable-image)
|
||||
[{:value :image :label (tr "media.image")}])))
|
||||
|
||||
handle-change-gradient-selected-stop
|
||||
(mf/use-fn
|
||||
(fn [index]
|
||||
(st/emit! (dc/select-colorpicker-gradient-stop index))))
|
||||
|
||||
handle-change-gradient-type
|
||||
(mf/use-fn
|
||||
(fn [type]
|
||||
(st/emit! (dc/activate-colorpicker-gradient type))))
|
||||
|
||||
handle-gradient-change-stop
|
||||
(mf/use-fn
|
||||
(mf/deps state)
|
||||
(fn [prev-stop new-stop]
|
||||
(let [stops (->> (:stops state)
|
||||
(mapv #(if (= % prev-stop) new-stop %)))]
|
||||
(st/emit! (dc/update-colorpicker-stops stops)))))
|
||||
|
||||
handle-gradient-add-stop-auto
|
||||
(mf/use-fn
|
||||
(fn []
|
||||
(st/emit! (dc/update-colorpicker-add-auto))))
|
||||
|
||||
handle-gradient-add-stop-preview
|
||||
(mf/use-fn
|
||||
(fn [offset]
|
||||
(st/emit! (dc/update-colorpicker-add-stop offset))))
|
||||
|
||||
handle-gradient-remove-stop
|
||||
(mf/use-fn
|
||||
(mf/deps state)
|
||||
(fn [stop]
|
||||
(when (> (count (:stops state)) 2)
|
||||
(when-let [index (d/index-of-pred (:stops state) #(= % stop))]
|
||||
(st/emit! (dc/remove-gradient-stop index))))))
|
||||
|
||||
handle-stop-edit-start
|
||||
(mf/use-fn
|
||||
(fn []
|
||||
(reset! should-update? false)))
|
||||
|
||||
handle-stop-edit-finish
|
||||
(mf/use-fn
|
||||
(mf/deps state)
|
||||
(fn []
|
||||
(reset! should-update? true)
|
||||
|
||||
;; Update asynchronously so we can update with the new stops
|
||||
(ts/schedule #(st/emit! (dc/sort-colorpicker-stops)))))
|
||||
|
||||
handle-rotate-stops
|
||||
(mf/use-fn
|
||||
(mf/deps state)
|
||||
(fn []
|
||||
(let [gradient (:gradient state)
|
||||
mtx (gmt/rotate-matrix 90 (gpt/point 0.5 0.5))
|
||||
|
||||
start-p (-> (gpt/point (:start-x gradient) (:start-y gradient))
|
||||
(gpt/transform mtx))
|
||||
|
||||
end-p (-> (gpt/point (:end-x gradient) (:end-y gradient))
|
||||
(gpt/transform mtx))]
|
||||
(st/emit! (dc/update-colorpicker-gradient {:start-x (:x start-p)
|
||||
:start-y (:y start-p)
|
||||
:end-x (:x end-p)
|
||||
:end-y (:y end-p)})))))
|
||||
|
||||
handle-reverse-stops
|
||||
(mf/use-fn
|
||||
(mf/deps (:stops state))
|
||||
(fn []
|
||||
(let [stops (->> (:stops state)
|
||||
(map (fn [it] (update it :offset #(+ 1 (* -1 %)))))
|
||||
(sort-by :offset)
|
||||
(into []))]
|
||||
(st/emit! (dc/update-colorpicker-stops stops)))))
|
||||
|
||||
handle-reorder-stops
|
||||
(mf/use-fn
|
||||
(mf/deps (:stops state))
|
||||
(fn [from-index to-index]
|
||||
(let [stops (:stops state)
|
||||
new-stops
|
||||
(-> stops
|
||||
(d/insert-at-index to-index [(get stops from-index)]))
|
||||
stops
|
||||
(mapv #(assoc %2 :offset (:offset %1)) stops new-stops)]
|
||||
(st/emit! (dc/update-colorpicker-stops stops)))))
|
||||
|
||||
handle-change-gradient-opacity
|
||||
(mf/use-fn
|
||||
(fn [value]
|
||||
(st/emit! (dc/update-colorpicker-gradient-opacity (/ value 100)))))
|
||||
|
||||
tabs
|
||||
#js [#js {:aria-label (tr "workspace.libraries.colors.rgba")
|
||||
:icon ic/rgba
|
||||
|
@ -280,7 +388,8 @@
|
|||
|
||||
;; Update colorpicker with external color changes
|
||||
(mf/with-effect [data]
|
||||
(st/emit! (dc/update-colorpicker data)))
|
||||
(when @should-update?
|
||||
(st/emit! (dc/update-colorpicker data))))
|
||||
|
||||
;; Updates the CSS color variable when there is a change in the color
|
||||
(use-color-picker-css-variables! node-ref current-color)
|
||||
|
@ -300,24 +409,46 @@
|
|||
:ref node-ref
|
||||
:style {:touch-action "none"}}
|
||||
[:div {:class (stl/css :top-actions)}
|
||||
(when (or (not disable-gradient) (not disable-image))
|
||||
[:div {:class (stl/css :select)}
|
||||
[:& select
|
||||
{:default-value selected-mode
|
||||
:options options
|
||||
:on-change handle-change-mode}]])
|
||||
[:div {:class (stl/css :top-actions-right)}
|
||||
(when (= :gradient selected-mode)
|
||||
[:div {:class (stl/css :opacity-input-wrapper)}
|
||||
[:span {:class (stl/css :icon-text)} "%"]
|
||||
[:> numeric-input*
|
||||
{:value (-> data :opacity opacity->string)
|
||||
:on-change handle-change-gradient-opacity
|
||||
:default 100
|
||||
:min 0
|
||||
:max 100}]])
|
||||
|
||||
(when (or (not disable-gradient) (not disable-image))
|
||||
[:div {:class (stl/css :select)}
|
||||
[:& select
|
||||
{:default-value selected-mode
|
||||
:options options
|
||||
:on-change handle-change-mode}]])]
|
||||
|
||||
(when (not= selected-mode :image)
|
||||
[:button {:class (stl/css-case :picker-btn true
|
||||
:selected picking-color?)
|
||||
:on-click handle-click-picker}
|
||||
i/picker])]
|
||||
|
||||
(when (or (= selected-mode :linear-gradient)
|
||||
(= selected-mode :radial-gradient))
|
||||
(when (= selected-mode :gradient)
|
||||
[:& gradients
|
||||
{:stops (:stops state)
|
||||
{:type (:type state)
|
||||
:stops (:stops state)
|
||||
:editing-stop (:editing-stop state)
|
||||
:on-select-stop handle-change-stop}])
|
||||
:on-stop-edit-start handle-stop-edit-start
|
||||
:on-stop-edit-finish handle-stop-edit-finish
|
||||
:on-select-stop handle-change-gradient-selected-stop
|
||||
:on-change-type handle-change-gradient-type
|
||||
:on-change-stop handle-gradient-change-stop
|
||||
:on-add-stop-auto handle-gradient-add-stop-auto
|
||||
:on-add-stop-preview handle-gradient-add-stop-preview
|
||||
:on-remove-stop handle-gradient-remove-stop
|
||||
:on-rotate-stops handle-rotate-stops
|
||||
:on-reverse-stops handle-reverse-stops
|
||||
:on-reorder-stops handle-reorder-stops}])
|
||||
|
||||
(if (= selected-mode :image)
|
||||
(let [uri (cfg/resolve-file-media (:image current-color))
|
||||
|
@ -383,9 +514,9 @@
|
|||
|
||||
(defn calculate-position
|
||||
"Calculates the style properties for the given coordinates and position"
|
||||
[{vh :height} position x y]
|
||||
[{vh :height} position x y gradient?]
|
||||
(let [;; picker size in pixels
|
||||
h 510
|
||||
h (if gradient? 820 510)
|
||||
w 284
|
||||
;; Checks for overflow outside the viewport height
|
||||
max-y (- vh h)
|
||||
|
@ -433,22 +564,25 @@
|
|||
dirty? (mf/use-var false)
|
||||
last-change (mf/use-var nil)
|
||||
position (d/nilv position :left)
|
||||
style (calculate-position vport position x y)
|
||||
style (calculate-position vport position x y (some? (:gradient data)))
|
||||
|
||||
on-change'
|
||||
(mf/use-fn
|
||||
(mf/deps on-change)
|
||||
(fn [new-data]
|
||||
(reset! dirty? (not= data new-data))
|
||||
(reset! last-change new-data)
|
||||
|
||||
(if (fn? on-change)
|
||||
(on-change new-data)
|
||||
(st/emit! (dc/update-colorpicker new-data)))))]
|
||||
(when (not= new-data @last-change)
|
||||
(reset! last-change new-data)
|
||||
(if (fn? on-change)
|
||||
(on-change new-data)
|
||||
(st/emit! (dc/update-colorpicker new-data))))))]
|
||||
|
||||
(mf/with-effect []
|
||||
#(when (and @dirty? @last-change on-close)
|
||||
(on-close @last-change)))
|
||||
(st/emit! (st/emit! (dsc/push-shortcuts ::colorpicker sc/shortcuts)))
|
||||
#(do
|
||||
(st/emit! (dsc/pop-shortcuts ::colorpicker))
|
||||
(when (and @dirty? @last-change on-close)
|
||||
(on-close @last-change))))
|
||||
|
||||
[:div {:class (stl/css :colorpicker-tooltip)
|
||||
:data-testid "colorpicker"
|
||||
|
@ -460,4 +594,3 @@
|
|||
:disable-image disable-image
|
||||
:on-change on-change'
|
||||
:on-accept on-accept}]]))
|
||||
|
||||
|
|
|
@ -28,6 +28,17 @@
|
|||
height: $s-40;
|
||||
}
|
||||
|
||||
.top-actions-right {
|
||||
display: flex;
|
||||
gap: $s-8;
|
||||
}
|
||||
|
||||
.opacity-input-wrapper {
|
||||
@extend .input-element;
|
||||
@include bodySmallTypography;
|
||||
width: $s-68;
|
||||
}
|
||||
|
||||
.picker-btn {
|
||||
@include buttonStyle;
|
||||
@include flexCenter;
|
||||
|
|
|
@ -7,35 +7,368 @@
|
|||
(ns app.main.ui.workspace.colorpicker.gradients
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.colors :as cc]
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.math :as mth]
|
||||
[app.main.ui.components.numeric-input :refer [numeric-input*]]
|
||||
[app.main.ui.components.select :refer [select]]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
|
||||
[app.main.ui.formats :as fmt]
|
||||
[app.main.ui.hooks :as h]
|
||||
[app.main.ui.workspace.sidebar.options.rows.color-row :refer [color-row]]
|
||||
[app.util.dom :as dom]
|
||||
[cuerdas.core :as str]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(defn- format-rgba
|
||||
[{:keys [r g b alpha offset]}]
|
||||
(str/ffmt "rgba(%1, %2, %3, %4) %5%%" r g b alpha (* offset 100)))
|
||||
(defn offset->string
|
||||
[opacity]
|
||||
(str (-> opacity
|
||||
(d/coalesce 1)
|
||||
(* 100)
|
||||
(fmt/format-number))))
|
||||
|
||||
(defn- event->offset
|
||||
[^js event]
|
||||
(/ (.. event -nativeEvent -offsetX)
|
||||
(-> event dom/get-current-target dom/get-bounding-rect :width)))
|
||||
|
||||
;; (defn- format-rgba
|
||||
;; [{:keys [r g b alpha offset]}]
|
||||
;; (str/ffmt "rgba(%1, %2, %3, %4) %5%%" r g b alpha (* offset 100)))
|
||||
|
||||
(defn- format-rgb
|
||||
[{:keys [r g b offset]}]
|
||||
(str/ffmt "rgb(%1, %2, %3) %4%%" r g b (* offset 100)))
|
||||
|
||||
(defn- gradient->string [stops]
|
||||
(let [gradient-css (str/join ", " (map format-rgba stops))]
|
||||
(str/ffmt "linear-gradient(90deg, %1)" gradient-css)))
|
||||
(->> stops
|
||||
(sort-by :offset)
|
||||
(map (fn [{:keys [color opacity offset]}]
|
||||
(let [[r g b] (cc/hex->rgb color)]
|
||||
{:r r :g g :b b :alpha opacity :offset offset})))
|
||||
(map format-rgb)
|
||||
(str/join ", ")
|
||||
(str/ffmt "linear-gradient(90deg, %1)")))
|
||||
|
||||
(mf/defc stop-input-row
|
||||
[{:keys [stop
|
||||
index
|
||||
is-selected
|
||||
|
||||
on-select-stop
|
||||
on-change-stop
|
||||
on-remove-stop
|
||||
on-reorder-stops
|
||||
on-focus-stop-offset
|
||||
on-blur-stop-offset
|
||||
on-focus-stop-color
|
||||
on-blur-stop-color]}]
|
||||
(let [{:keys [color opacity offset]} stop
|
||||
|
||||
handle-change-stop-color
|
||||
(mf/use-callback
|
||||
(mf/deps on-change-stop stop)
|
||||
(fn [value]
|
||||
(on-change-stop stop (merge stop value))))
|
||||
|
||||
handle-change-offset
|
||||
(mf/use-callback
|
||||
(mf/deps on-change-stop stop)
|
||||
(fn [value]
|
||||
(on-change-stop stop (assoc stop :offset (mth/precision (/ value 100) 2)))))
|
||||
|
||||
handle-remove-stop
|
||||
(mf/use-callback
|
||||
(mf/deps on-remove-stop stop)
|
||||
(fn []
|
||||
(when on-remove-stop
|
||||
(on-remove-stop stop))))
|
||||
|
||||
handle-focus-stop-offset
|
||||
(mf/use-fn
|
||||
(mf/deps on-select-stop on-focus-stop-offset index)
|
||||
(fn []
|
||||
(on-select-stop index)
|
||||
(when on-focus-stop-offset
|
||||
(on-focus-stop-offset))))
|
||||
|
||||
handle-blur-stop-offset
|
||||
(mf/use-fn
|
||||
(mf/deps on-select-stop on-blur-stop-offset index)
|
||||
(fn []
|
||||
(on-select-stop index)
|
||||
(when on-blur-stop-offset
|
||||
(on-blur-stop-offset))))
|
||||
|
||||
handle-focus-stop-color
|
||||
(mf/use-fn
|
||||
(mf/deps on-select-stop on-focus-stop-offset index)
|
||||
(fn []
|
||||
(on-select-stop index)
|
||||
(when on-focus-stop-color
|
||||
(on-focus-stop-offset))))
|
||||
|
||||
handle-blur-stop-color
|
||||
(mf/use-fn
|
||||
(mf/deps on-select-stop on-blur-stop-color index)
|
||||
(fn []
|
||||
(on-select-stop index)
|
||||
(when on-blur-stop-color
|
||||
(on-blur-stop-color))))
|
||||
|
||||
on-drop
|
||||
(mf/use-fn
|
||||
(mf/deps index on-reorder-stops)
|
||||
(fn [position data]
|
||||
(let [from-index (:index data)
|
||||
to-index (if (= position :bot) (inc index) index)]
|
||||
(when on-reorder-stops
|
||||
(on-reorder-stops from-index to-index)))))
|
||||
|
||||
[dprops dref]
|
||||
(h/use-sortable
|
||||
:data-type "penpot/stops"
|
||||
:on-drop on-drop
|
||||
:data {:index index}
|
||||
:draggable? true)]
|
||||
|
||||
[:div {:class (stl/css-case :gradient-stops-entry true
|
||||
:is-selected is-selected
|
||||
:dnd-over (= (:over dprops) :center)
|
||||
:dnd-over-top (= (:over dprops) :top)
|
||||
:dnd-over-bot (= (:over dprops) :bot))}
|
||||
|
||||
[:div {:ref dref
|
||||
:class (stl/css :reorder)}
|
||||
[:> icon*
|
||||
{:id i/reorder
|
||||
:class (stl/css :reorder-icon)
|
||||
:aria-hidden true}]]
|
||||
[:hr {:class (stl/css :reorder-separator-top)}]
|
||||
[:hr {:class (stl/css :reorder-separator-bottom)}]
|
||||
|
||||
[:div {:class (stl/css :offset-input-wrapper)}
|
||||
[:span {:class (stl/css :icon-text)} "%"]
|
||||
[:> numeric-input*
|
||||
{:value (-> offset offset->string)
|
||||
:on-change handle-change-offset
|
||||
:default 100
|
||||
:min 0
|
||||
:max 100
|
||||
:on-focus handle-focus-stop-offset
|
||||
:on-blur handle-blur-stop-offset}]]
|
||||
|
||||
[:& color-row
|
||||
{:disable-gradient true
|
||||
:disable-picker true
|
||||
:color {:color color
|
||||
:opacity opacity}
|
||||
:index index
|
||||
:on-change handle-change-stop-color
|
||||
:on-remove handle-remove-stop
|
||||
:on-focus handle-focus-stop-color
|
||||
:on-blur handle-blur-stop-color}]]))
|
||||
|
||||
(mf/defc gradients
|
||||
[{:keys [stops editing-stop on-select-stop]}]
|
||||
[:div {:class (stl/css :gradient-stops)}
|
||||
[:div {:class (stl/css :gradient-background-wrapper)}
|
||||
[:div {:class (stl/css :gradient-background)
|
||||
:style {:background (gradient->string stops)}}]]
|
||||
[{:keys [type
|
||||
stops
|
||||
editing-stop
|
||||
on-select-stop
|
||||
on-change-type
|
||||
on-change-stop
|
||||
on-add-stop-preview
|
||||
on-add-stop-auto
|
||||
on-remove-stop
|
||||
on-stop-edit-start
|
||||
on-stop-edit-finish
|
||||
on-reverse-stops
|
||||
on-rotate-stops
|
||||
on-reorder-stops]}]
|
||||
|
||||
[:div {:class (stl/css :gradient-stop-wrapper)}
|
||||
(for [{:keys [offset hex r g b alpha] :as value} stops]
|
||||
[:button {:class (stl/css-case :gradient-stop true
|
||||
:selected (= editing-stop offset))
|
||||
:data-value (str offset)
|
||||
:on-click on-select-stop
|
||||
:style {:left (dm/str (* offset 100) "%")}
|
||||
:key (dm/str offset)}
|
||||
(let [preview-state (mf/use-state {:hover? false :offset 0.5})
|
||||
dragging-ref (mf/use-ref false)
|
||||
start-ref (mf/use-ref nil)
|
||||
start-offset (mf/use-ref nil)
|
||||
background-ref (mf/use-ref nil)
|
||||
|
||||
[:div {:class (stl/css :gradient-stop-color)
|
||||
:style {:background-color hex}}]
|
||||
[:div {:class (stl/css :gradient-stop-alpha)
|
||||
:style {:background-color (str/ffmt "rgba(%1, %2, %3, %4)" r g b alpha)}}]])]])
|
||||
handle-select-stop
|
||||
(mf/use-callback
|
||||
(mf/deps on-select-stop)
|
||||
(fn [event]
|
||||
(when on-select-stop
|
||||
(let [index (-> event dom/get-current-target (dom/get-data "index") d/read-string)]
|
||||
(on-select-stop index)))))
|
||||
|
||||
handle-change-type
|
||||
(mf/use-callback
|
||||
(mf/deps on-change-type)
|
||||
(fn [event]
|
||||
(when on-change-type
|
||||
(on-change-type event))))
|
||||
|
||||
handle-add-stop
|
||||
(mf/use-callback
|
||||
(mf/deps on-add-stop-auto)
|
||||
(fn []
|
||||
(when on-add-stop-auto
|
||||
(on-add-stop-auto))))
|
||||
|
||||
handle-preview-enter
|
||||
(mf/use-fn
|
||||
(fn []
|
||||
(swap! preview-state assoc :hover? true)))
|
||||
|
||||
handle-preview-leave
|
||||
(mf/use-fn
|
||||
(fn []
|
||||
(swap! preview-state assoc :hover? false)))
|
||||
|
||||
handle-preview-move
|
||||
(mf/use-fn
|
||||
(fn [^js e]
|
||||
(let [offset (-> (event->offset e)
|
||||
(mth/precision 2))]
|
||||
(swap! preview-state assoc :offset offset))))
|
||||
|
||||
handle-preview-down
|
||||
(mf/use-fn
|
||||
(mf/deps on-add-stop-preview)
|
||||
(fn [^js e]
|
||||
(let [offset (-> (event->offset e)
|
||||
(mth/precision 2))]
|
||||
(when on-add-stop-preview
|
||||
(on-add-stop-preview offset)))))
|
||||
|
||||
handle-stop-marker-pointer-down
|
||||
(mf/use-fn
|
||||
(mf/deps on-stop-edit-start handle-select-stop stops)
|
||||
(fn [event]
|
||||
(let [index (-> event dom/get-current-target (dom/get-data "index") d/read-string)
|
||||
stop (get stops index)]
|
||||
(dom/capture-pointer event)
|
||||
(handle-select-stop event)
|
||||
(mf/set-ref-val! dragging-ref true)
|
||||
(mf/set-ref-val! start-ref (dom/get-client-position event))
|
||||
(mf/set-ref-val! start-offset (:offset stop))
|
||||
(on-stop-edit-start))))
|
||||
|
||||
handle-stop-marker-pointer-move
|
||||
(mf/use-fn
|
||||
(mf/deps on-change-stop stops)
|
||||
(fn [event]
|
||||
(when-let [_ (mf/ref-val dragging-ref)]
|
||||
(let [start-pt (mf/ref-val start-ref)
|
||||
start-offset (mf/ref-val start-offset)
|
||||
|
||||
index (-> event dom/get-target (dom/get-data "index") d/read-string)
|
||||
current-pt (dom/get-client-position event)
|
||||
delta-x (- (:x current-pt) (:x start-pt))
|
||||
background-node (mf/ref-val background-ref)
|
||||
background-width (-> background-node dom/get-bounding-rect :width)
|
||||
|
||||
delta-offset (/ delta-x background-width)
|
||||
stop (get stops index)
|
||||
|
||||
new-offset (mth/precision (mth/clamp (+ start-offset delta-offset) 0 1) 2)]
|
||||
(on-change-stop stop (assoc stop :offset new-offset))))))
|
||||
|
||||
handle-stop-marker-lost-pointer-capture
|
||||
(mf/use-fn
|
||||
(mf/deps on-stop-edit-finish)
|
||||
(fn [event]
|
||||
(dom/release-pointer event)
|
||||
(mf/set-ref-val! dragging-ref false)
|
||||
(mf/set-ref-val! start-ref nil)
|
||||
(on-stop-edit-finish)))
|
||||
|
||||
handle-rotate-gradient
|
||||
(mf/use-fn
|
||||
(mf/deps on-rotate-stops)
|
||||
(fn []
|
||||
(when on-rotate-stops
|
||||
(on-rotate-stops))))
|
||||
|
||||
handle-reverse-gradient
|
||||
(mf/use-fn
|
||||
(mf/deps on-reverse-stops)
|
||||
(fn []
|
||||
(when on-reverse-stops
|
||||
(on-reverse-stops))))]
|
||||
|
||||
[:div {:class (stl/css :gradient-panel)}
|
||||
[:div {:class (stl/css :gradient-preview)}
|
||||
[:div {:class (stl/css :gradient-background)
|
||||
:ref background-ref
|
||||
:style {:background (gradient->string stops)}
|
||||
:on-pointer-enter handle-preview-enter
|
||||
:on-pointer-leave handle-preview-leave
|
||||
:on-pointer-move handle-preview-move
|
||||
:on-pointer-down handle-preview-down}
|
||||
[:div {:class (stl/css :gradient-preview-stop-preview)
|
||||
:style {:display (if (:hover? @preview-state) "block" "none")
|
||||
"--preview-position" (dm/str (* 100 (:offset @preview-state)) "%")}}]]
|
||||
|
||||
[:div {:class (stl/css :gradient-preview-stop-wrapper)}
|
||||
(for [[index {:keys [color offset r g b alpha]}] (d/enumerate stops)]
|
||||
[:* {:key (dm/str "preview-stop-" index)}
|
||||
[:div
|
||||
{:class (stl/css-case :gradient-preview-stop true
|
||||
:is-selected (= editing-stop index))
|
||||
:style {"--color-solid" color
|
||||
"--color-alpha" (str/ffmt "rgba(%1, %2, %3, %4)" r g b alpha)
|
||||
"--position" (dm/str (* offset 100) "%")}
|
||||
:data-index index
|
||||
|
||||
:on-pointer-down handle-stop-marker-pointer-down
|
||||
:on-pointer-move handle-stop-marker-pointer-move
|
||||
:on-lost-pointer-capture handle-stop-marker-lost-pointer-capture}
|
||||
[:div {:class (stl/css :gradient-preview-stop-color)
|
||||
:style {:pointer-events "none"}}]
|
||||
[:div {:class (stl/css :gradient-preview-stop-alpha)
|
||||
:style {:pointer-events "none"}}]]
|
||||
[:div {:class (stl/css :gradient-preview-stop-decoration)
|
||||
:style {"--position" (dm/str (* offset 100) "%")}}]])]]
|
||||
|
||||
[:div {:class (stl/css :gradient-options)}
|
||||
[:& select
|
||||
{:default-value type
|
||||
:options [{:value :linear-gradient :label "Linear"}
|
||||
{:value :radial-gradient :label "Radial"}]
|
||||
:on-change handle-change-type
|
||||
:class (stl/css :gradient-options-select)}]
|
||||
|
||||
[:div {:class (stl/css :gradient-options-buttons)}
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-label "Rotate gradient"
|
||||
:on-click handle-rotate-gradient
|
||||
:icon-class (stl/css :rotate-icon)
|
||||
:icon "reload"}]
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-label "Reverse gradient"
|
||||
:on-click handle-reverse-gradient
|
||||
:icon "switch"}]
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-label "Add stop"
|
||||
:on-click handle-add-stop
|
||||
:icon "add"}]]]
|
||||
|
||||
[:div {:class (stl/css :gradient-stops-list)}
|
||||
[:& h/sortable-container {}
|
||||
(for [[index stop] (d/enumerate stops)]
|
||||
[:& stop-input-row
|
||||
{:key index
|
||||
:stop stop
|
||||
:index index
|
||||
:is-selected (= editing-stop index)
|
||||
:on-select-stop on-select-stop
|
||||
:on-change-stop on-change-stop
|
||||
:on-remove-stop on-remove-stop
|
||||
:on-focus-stop-offset on-stop-edit-start
|
||||
:on-blur-stop-offset on-stop-edit-finish
|
||||
:on-focus-stop-color on-stop-edit-start
|
||||
:on-blur-stop-color on-stop-edit-finish
|
||||
:on-reorder-stops on-reorder-stops}])]]
|
||||
|
||||
[:hr {:class (stl/css :gradient-separator)}]]))
|
||||
|
|
|
@ -6,57 +6,197 @@
|
|||
|
||||
@import "refactor/common-refactor.scss";
|
||||
|
||||
.gradient-stops {
|
||||
display: flex;
|
||||
height: $s-20;
|
||||
width: 100%;
|
||||
margin: $s-12 0;
|
||||
background-color: var(--colorpicker-handlers-color);
|
||||
border-radius: $br-6;
|
||||
.gradient-panel {
|
||||
margin-top: $s-12;
|
||||
display: grid;
|
||||
gap: $s-4;
|
||||
grid-template-rows: $s-56 $s-32 1fr;
|
||||
}
|
||||
|
||||
.gradient-background-wrapper {
|
||||
height: 100%;
|
||||
.gradient-preview {
|
||||
width: 100%;
|
||||
border-radius: $br-6;
|
||||
background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAExJREFUSIljvHnz5n8GLEBNTQ2bMMOtW7ewiuNSz4RVlIpg1IKBt4Dx////WFMRqakFl/qhH0SjFhAELNRKLaNl0Qi2YLQsGrWAcgAA0gAgQPhT2rAAAAAASUVORK5CYII=")
|
||||
left center;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: $s-12;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.gradient-background {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border-radius: $br-6;
|
||||
border: $s-2 solid var(--colorpicker-details-color);
|
||||
}
|
||||
|
||||
.gradient-stop-wrapper {
|
||||
position: absolute;
|
||||
width: calc(100% - $s-40);
|
||||
left: $s-20;
|
||||
}
|
||||
|
||||
.gradient-stop {
|
||||
position: absolute;
|
||||
display: grid;
|
||||
grid-template-columns: 50% 50%;
|
||||
padding: 0;
|
||||
width: $s-16;
|
||||
height: $s-24;
|
||||
height: $s-20;
|
||||
border-radius: $br-4;
|
||||
margin-top: calc(-1 * $s-2);
|
||||
margin-left: calc(-1 * $s-8);
|
||||
border: $s-2 solid var(--colorpicker-handlers-color);
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.gradient-preview-stop-wrapper {
|
||||
position: absolute;
|
||||
width: calc(100% - $s-24 - $s-4);
|
||||
height: 100%;
|
||||
left: $s-2;
|
||||
top: calc(-1 * $s-4);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.gradient-preview-stop {
|
||||
background-color: var(--color-foreground-primary);
|
||||
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAADFJREFUOE9jZGBgEAFifOANPknGUQMYhkkYEEgG+NMJKAwIAbwJbdQABnBCIgRoG4gAIF8IsXB/Rs4AAAAASUVORK5CYII=");
|
||||
background-position: left center;
|
||||
background-size: 8px;
|
||||
&.selected {
|
||||
border: $s-2 solid var(--colorpicker-details-color-selected);
|
||||
background-size: $s-8;
|
||||
border-radius: $br-6;
|
||||
border: $s-2 solid var(--color-foreground-primary);
|
||||
box-shadow: 0px 0px $s-4 0px var(--menu-shadow-color);
|
||||
height: calc($s-24 - $s-2);
|
||||
left: var(--position);
|
||||
overflow: hidden;
|
||||
pointer-events: initial;
|
||||
position: absolute;
|
||||
width: calc($s-24 - $s-2);
|
||||
|
||||
&.is-selected,
|
||||
&:hover {
|
||||
outline: $s-2 solid var(--color-accent-primary);
|
||||
}
|
||||
}
|
||||
.gradient-preview-stop-decoration {
|
||||
background: var(--color-foreground-primary);
|
||||
border-radius: 100%;
|
||||
bottom: $s-32;
|
||||
box-shadow: 0px 0px $s-4 0px var(--menu-shadow-color);
|
||||
height: $s-4;
|
||||
left: calc(var(--position) + $s-8);
|
||||
position: absolute;
|
||||
width: $s-4;
|
||||
}
|
||||
|
||||
.gradient-preview-stop-color {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
background: var(--color-solid);
|
||||
}
|
||||
|
||||
.gradient-preview-stop-alpha {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
background: var(--color-alpha);
|
||||
}
|
||||
|
||||
.gradient-options {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.gradient-options-buttons {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.gradient-options-select {
|
||||
width: $s-140;
|
||||
}
|
||||
|
||||
.rotate-icon {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
.gradient-stops-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $s-4;
|
||||
max-height: $s-180;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding: $s-1 0;
|
||||
}
|
||||
|
||||
.gradient-stops-entry {
|
||||
display: flex;
|
||||
gap: $s-4;
|
||||
padding: $s-2;
|
||||
border-radius: $br-12;
|
||||
border: $s-1 solid transparent;
|
||||
|
||||
&:hover .reorder-icon {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
&.is-selected {
|
||||
border-color: var(--color-accent-primary-muted);
|
||||
}
|
||||
|
||||
&.dnd-over-top .reorder-separator-top {
|
||||
display: block;
|
||||
}
|
||||
|
||||
&.dnd-over-bot .reorder-separator-bottom {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.gradient-stop-color,
|
||||
.gradient-stop-alpha {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
.reorder {
|
||||
cursor: grab;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: $s-36;
|
||||
justify-content: center;
|
||||
left: calc(-1 * $s-2);
|
||||
margin-top: calc(-1 * $s-2);
|
||||
position: absolute;
|
||||
width: $s-16;
|
||||
}
|
||||
|
||||
.reorder-icon {
|
||||
height: $s-16;
|
||||
pointer-events: none;
|
||||
stroke: var(--color-foreground-secondary);
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.reorder-separator-top {
|
||||
border-color: var(--color-accent-primary);
|
||||
display: none;
|
||||
left: 0;
|
||||
margin-left: $s-12;
|
||||
margin-top: calc(-1 * $s-6);
|
||||
position: absolute;
|
||||
width: calc(100% - $s-24);
|
||||
}
|
||||
|
||||
.reorder-separator-bottom {
|
||||
border-color: var(--color-accent-primary);
|
||||
display: none;
|
||||
left: 0;
|
||||
margin-left: $s-12;
|
||||
margin-top: $s-36;
|
||||
position: absolute;
|
||||
width: calc(100% - $s-24);
|
||||
}
|
||||
|
||||
.offset-input-wrapper {
|
||||
@extend .input-element;
|
||||
@include bodySmallTypography;
|
||||
width: $s-92;
|
||||
}
|
||||
|
||||
.gradient-separator {
|
||||
border-color: var(--color-background-quaternary);
|
||||
border-width: $s-3;
|
||||
margin-left: -4%;
|
||||
position: relative;
|
||||
width: 108%;
|
||||
}
|
||||
|
||||
.gradient-preview-stop-preview {
|
||||
background: var(--color-foreground-primary);
|
||||
border-radius: 50%;
|
||||
height: $s-4;
|
||||
left: calc(var(--preview-position, 0%) - $s-2);
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
top: calc(50% - $s-2);
|
||||
width: $s-4;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
;; 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/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.main.ui.workspace.colorpicker.shortcuts
|
||||
(:require
|
||||
[app.main.data.shortcuts :as ds]
|
||||
[app.main.data.workspace.colors :as dwc]
|
||||
[app.main.data.workspace.shortcuts :as wsc]
|
||||
[app.main.store :as st]))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Shortcuts
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
;; Shortcuts format https://github.com/ccampbell/mousetrap
|
||||
|
||||
(def shortcuts
|
||||
(merge
|
||||
wsc/shortcuts
|
||||
|
||||
{:delete-stop {:tooltip (ds/supr)
|
||||
:command ["del" "backspace"]
|
||||
:subsections [:edit]
|
||||
:fn #(st/emit! (dwc/remove-gradient-stop))}}))
|
||||
|
|
@ -46,7 +46,7 @@
|
|||
(if (= v :multiple) nil v))
|
||||
|
||||
(mf/defc color-row
|
||||
[{:keys [index color disable-gradient disable-opacity disable-image on-change
|
||||
[{:keys [index color disable-gradient disable-opacity disable-image disable-picker on-change
|
||||
on-reorder on-detach on-open on-close on-remove
|
||||
disable-drag on-focus on-blur select-only select-on-focus]}]
|
||||
(let [current-file-id (mf/use-ctx ctx/current-file-id)
|
||||
|
@ -72,8 +72,7 @@
|
|||
editing-text? (deref editing-text*)
|
||||
|
||||
opacity?
|
||||
(and (not gradient-color?)
|
||||
(not multiple-colors?)
|
||||
(and (not multiple-colors?)
|
||||
(not library-color?)
|
||||
(not disable-opacity))
|
||||
|
||||
|
@ -134,7 +133,7 @@
|
|||
|
||||
handle-click-color
|
||||
(mf/use-fn
|
||||
(mf/deps disable-gradient disable-opacity disable-image on-change on-close on-open)
|
||||
(mf/deps disable-gradient disable-opacity disable-image disable-picker on-change on-close on-open)
|
||||
(fn [color event]
|
||||
(let [color (cond
|
||||
multiple-colors?
|
||||
|
@ -166,7 +165,8 @@
|
|||
(when (fn? on-open)
|
||||
(on-open color))
|
||||
|
||||
(modal/show! :colorpicker props))))
|
||||
(when-not disable-picker
|
||||
(modal/show! :colorpicker props)))))
|
||||
|
||||
prev-color (h/use-previous color)
|
||||
|
||||
|
@ -187,8 +187,8 @@
|
|||
:name (str "Color row" index)})
|
||||
[nil nil])]
|
||||
|
||||
(mf/with-effect [color prev-color]
|
||||
(when (not= prev-color color)
|
||||
(mf/with-effect [color prev-color disable-picker]
|
||||
(when (and (not disable-picker) (not= prev-color color))
|
||||
(modal/update-props! :colorpicker {:data (parse-color color)})))
|
||||
|
||||
[:div {:class (stl/css-case
|
||||
|
|
|
@ -153,14 +153,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.gradient-name-wrapper {
|
||||
border-radius: 0 $br-8 $br-8 0;
|
||||
.color-name {
|
||||
@include flexRow;
|
||||
border-radius: 0 $br-8 $br-8 0;
|
||||
}
|
||||
}
|
||||
|
||||
.library-name-wrapper {
|
||||
border-radius: $br-8;
|
||||
}
|
||||
|
|
|
@ -7,14 +7,18 @@
|
|||
(ns app.main.ui.workspace.viewport.gradients
|
||||
"Gradients handlers and renders"
|
||||
(:require
|
||||
[app.common.colors :as cc]
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.geom.shapes.points :as gsp]
|
||||
[app.common.math :as mth]
|
||||
[app.main.data.workspace.colors :as dc]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.workspace.viewport.viewport-ref :as uwvv]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.mouse :as mse]
|
||||
[beicon.v2.core :as rx]
|
||||
|
@ -23,21 +27,27 @@
|
|||
|
||||
(def gradient-line-stroke-width 2)
|
||||
(def gradient-line-stroke-color "var(--app-white)")
|
||||
(def gradient-square-width 15)
|
||||
(def gradient-square-radius 2)
|
||||
(def gradient-square-width 20.5)
|
||||
(def gradient-square-radius 4)
|
||||
(def gradient-square-stroke-width 2)
|
||||
(def gradient-width-handler-radius 5)
|
||||
(def gradient-width-handler-radius 4)
|
||||
(def gradient-width-handler-radius-selected 6)
|
||||
(def gradient-width-handler-radius-handler 15)
|
||||
(def gradient-width-handler-color "var(--app-white)")
|
||||
(def gradient-square-stroke-color "var(--app-white)")
|
||||
(def gradient-square-stroke-color-selected "var(--color-accent-tertiary)")
|
||||
|
||||
(mf/defc shadow [{:keys [id x y width height offset]}]
|
||||
(def gradient-endpoint-radius 4)
|
||||
(def gradient-endpoint-radius-selected 6)
|
||||
(def gradient-endpoint-radius-handler 20)
|
||||
|
||||
(mf/defc shadow [{:keys [id offset]}]
|
||||
[:filter {:id id
|
||||
:x x
|
||||
:y y
|
||||
:width width
|
||||
:height height
|
||||
:filterUnits "userSpaceOnUse"
|
||||
:x "-10%"
|
||||
:y "-10%"
|
||||
:width "120%"
|
||||
:height "120%"
|
||||
:filterUnits "objectBoundingBox"
|
||||
: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"}]
|
||||
|
@ -47,53 +57,22 @@
|
|||
[: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 checkerboard "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAIAAAC0tAIdAAACvUlEQVQoFQGyAk39AeLi4gAAAAAAAB0dHQAAAAAAAOPj4wAAAAAAAB0dHQAAAAAAAOPj4wAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB////AAAAAAAA4+PjAAAAAAAAHR0dAAAAAAAA4+PjAAAAAAAAHR0dAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATj4+MAAAAAAAAdHR0AAAAAAADj4+MAAAAAAAAdHR0AAAAAAADj4+MAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAjScaa0cU7nIAAAAASUVORK5CYII=")
|
||||
|
||||
#_(def checkerboard "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAADFJREFUOE9jZGBgEAFifOANPknGUQMYhkkYEEgG+NMJKAwIAbwJbdQABnBCIgRoG4gAIF8IsXB/Rs4AAAAASUVORK5CYII=")
|
||||
|
||||
(mf/defc gradient-color-handler
|
||||
[{:keys [filter-id zoom point color angle selected
|
||||
on-click on-pointer-down on-pointer-up]}]
|
||||
[:g {:filter (str/fmt "url(#%s)" filter-id)
|
||||
[{:keys [zoom point color angle selected index
|
||||
on-click on-pointer-down on-pointer-up on-pointer-move on-lost-pointer-capture]}]
|
||||
[:g {:filter "url(#gradient-drop-shadow)"
|
||||
:style {:cursor "pointer"}
|
||||
:transform (gmt/rotate-matrix angle point)}
|
||||
|
||||
[:image {:href checkerboard
|
||||
:x (- (:x point) (/ gradient-square-width 2 zoom))
|
||||
:x (+ (- (:x point) (/ gradient-square-width 2 zoom)) (/ 12 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))
|
||||
[:rect {:x (+ (- (:x point) (/ gradient-square-width 2 zoom)) (/ 12 zoom))
|
||||
:y (- (:y point) (/ gradient-square-width 2 zoom))
|
||||
:rx (/ gradient-square-radius zoom)
|
||||
:width (/ gradient-square-width zoom 2)
|
||||
|
@ -103,40 +82,60 @@
|
|||
:on-pointer-down (partial on-pointer-down :to-p)
|
||||
:on-pointer-up (partial on-pointer-up :to-p)}]
|
||||
|
||||
(when selected
|
||||
[:rect {:pointer-events "none"
|
||||
:x (- (+ (- (:x point) (/ gradient-square-width 2 zoom)) (/ 12 zoom)) (/ 2 zoom))
|
||||
:y (- (- (:y point) (/ gradient-square-width 2 zoom)) (/ 2 zoom))
|
||||
:rx (/ (+ gradient-square-radius (/ 2 zoom)) zoom)
|
||||
:width (+ (/ gradient-square-width zoom) (/ 4 zoom))
|
||||
:height (+ (/ gradient-square-width zoom) (/ 4 zoom))
|
||||
:stroke "var(--color-accent-tertiary)"
|
||||
:stroke-width (/ gradient-square-stroke-width zoom)
|
||||
:fill "transparent"}])
|
||||
|
||||
[:rect {:data-allow-click-modal "colorpicker"
|
||||
:x (- (:x point) (/ gradient-square-width 2 zoom))
|
||||
:data-index index
|
||||
:pointer-events "all"
|
||||
:x (+ (- (:x point) (/ gradient-square-width 2 zoom)) (/ 12 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 (if selected "var(--color-accent-tertiary)" "var(--app-white)")
|
||||
:stroke "var(--app-white)"
|
||||
:stroke-width (/ gradient-square-stroke-width zoom)
|
||||
:fill (:value color)
|
||||
:fill-opacity (:opacity color)
|
||||
:on-click on-click
|
||||
:on-pointer-down on-pointer-down
|
||||
:on-pointer-up on-pointer-up}]])
|
||||
:on-pointer-up on-pointer-up
|
||||
:on-pointer-move on-pointer-move
|
||||
:on-lost-pointer-capture on-lost-pointer-capture}]
|
||||
|
||||
[:circle {:cx (:x point)
|
||||
:cy (:y point)
|
||||
:r (/ 2 zoom)
|
||||
:fill "var(--app-white)"}]])
|
||||
|
||||
(mf/defc gradient-handler-transformed
|
||||
[{:keys [from-p to-p width-p from-color to-color zoom editing
|
||||
[{:keys [from-p
|
||||
to-p
|
||||
width-p
|
||||
zoom
|
||||
editing
|
||||
stops
|
||||
on-change-start on-change-finish on-change-width]}]
|
||||
(let [moving-point (mf/use-var nil)
|
||||
angle (+ 90 (gpt/angle from-p to-p))
|
||||
angle (+ 90 (gpt/angle from-p to-p))
|
||||
dragging-ref (mf/use-ref false)
|
||||
start-offset (mf/use-ref nil)
|
||||
|
||||
on-click
|
||||
(fn [position event]
|
||||
(dom/stop-propagation event)
|
||||
(dom/prevent-default event)
|
||||
(when (#{:from-p :to-p} position)
|
||||
(st/emit! (dc/select-colorpicker-gradient-stop
|
||||
(case position
|
||||
:from-p 0
|
||||
:to-p 1)))))
|
||||
|
||||
on-pointer-down
|
||||
handler-state (mf/use-state {:display? false :offset 0 :hover nil})
|
||||
|
||||
endpoint-on-pointer-down
|
||||
(fn [position event]
|
||||
(dom/stop-propagation event)
|
||||
(dom/prevent-default event)
|
||||
(dom/capture-pointer event)
|
||||
(reset! moving-point position)
|
||||
(when (#{:from-p :to-p} position)
|
||||
(st/emit! (dc/select-colorpicker-gradient-stop
|
||||
|
@ -144,11 +143,102 @@
|
|||
:from-p 0
|
||||
:to-p 1)))))
|
||||
|
||||
on-pointer-up
|
||||
endpoint-on-pointer-up
|
||||
(fn [_position event]
|
||||
(dom/release-pointer event)
|
||||
(dom/stop-propagation event)
|
||||
(dom/prevent-default event)
|
||||
(reset! moving-point nil))]
|
||||
(reset! moving-point nil)
|
||||
(swap! handler-state assoc :hover nil))
|
||||
|
||||
endpoint-on-pointer-enter
|
||||
(mf/use-fn
|
||||
(fn [position]
|
||||
(swap! handler-state assoc :hover position)))
|
||||
|
||||
endpoint-on-pointer-leave
|
||||
(mf/use-fn
|
||||
(fn [_]
|
||||
(swap! handler-state assoc :hover nil)))
|
||||
|
||||
points-on-pointer-enter
|
||||
(mf/use-fn
|
||||
(fn []
|
||||
(swap! handler-state assoc :display? true)))
|
||||
|
||||
points-on-pointer-leave
|
||||
(mf/use-fn
|
||||
(fn []
|
||||
(swap! handler-state assoc :display? false)))
|
||||
|
||||
points-on-pointer-down
|
||||
(mf/use-fn
|
||||
(mf/deps stops)
|
||||
(fn [e]
|
||||
(dom/prevent-default e)
|
||||
(dom/stop-propagation e)
|
||||
|
||||
(let [raw-pt (dom/get-client-position e)
|
||||
position (uwvv/point->viewport raw-pt)
|
||||
lv (-> (gpt/to-vec from-p to-p) (gpt/unit))
|
||||
nv (gpt/normal-left lv)
|
||||
offset (-> (gsp/project-t position [from-p to-p] nv)
|
||||
(mth/precision 2))
|
||||
new-stop (cc/interpolate-gradient stops offset)
|
||||
stops (conj stops new-stop)
|
||||
stops (->> stops (sort-by :offset) (into []))]
|
||||
(st/emit! (dc/update-colorpicker-stops stops)))))
|
||||
|
||||
points-on-pointer-move
|
||||
(mf/use-fn
|
||||
(mf/deps from-p to-p)
|
||||
(fn [e]
|
||||
(let [raw-pt (dom/get-client-position e)
|
||||
position (uwvv/point->viewport raw-pt)
|
||||
lv (-> (gpt/to-vec from-p to-p) (gpt/unit))
|
||||
nv (gpt/normal-left lv)
|
||||
offset (gsp/project-t position [from-p to-p] nv)]
|
||||
(swap! handler-state assoc :offset offset))))
|
||||
|
||||
handle-marker-pointer-down
|
||||
(mf/use-fn
|
||||
(mf/deps stops)
|
||||
(fn [event]
|
||||
|
||||
(let [index (-> event dom/get-current-target (dom/get-data "index") d/read-string)
|
||||
stop (get stops index)]
|
||||
(dom/capture-pointer event)
|
||||
(st/emit! (dc/select-colorpicker-gradient-stop index))
|
||||
(mf/set-ref-val! dragging-ref true)
|
||||
(mf/set-ref-val! start-offset (:offset stop)))))
|
||||
|
||||
handle-marker-pointer-move
|
||||
(mf/use-fn
|
||||
(mf/deps stops)
|
||||
(fn [event]
|
||||
(when-let [_ (mf/ref-val dragging-ref)]
|
||||
(let [index (-> event dom/get-target (dom/get-data "index") d/read-string)
|
||||
|
||||
raw-pt (dom/get-client-position event)
|
||||
position (uwvv/point->viewport raw-pt)
|
||||
lv (-> (gpt/to-vec from-p to-p) (gpt/unit))
|
||||
nv (gpt/normal-left lv)
|
||||
offset (gsp/project-t position [from-p to-p] nv)
|
||||
offset (mth/precision (mth/clamp offset 0 1) 2)]
|
||||
|
||||
(st/emit! (dc/update-colorpicker-stops (assoc-in stops [index :offset] offset)))))))
|
||||
|
||||
handle-marker-lost-pointer-capture
|
||||
(mf/use-fn
|
||||
(mf/deps stops)
|
||||
(fn [event]
|
||||
(dom/release-pointer event)
|
||||
(mf/set-ref-val! dragging-ref false)
|
||||
(mf/set-ref-val! start-offset nil)
|
||||
(let [stops (->> stops
|
||||
(sort-by :offset)
|
||||
(into []))]
|
||||
(st/emit! (dc/update-colorpicker-stops stops)))))]
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps @moving-point from-p to-p width-p)
|
||||
|
@ -171,73 +261,193 @@
|
|||
(on-change-width new-width-p)))
|
||||
nil))))]
|
||||
(fn [] (rx/dispose! subs)))))
|
||||
[:g.gradient-handlers
|
||||
|
||||
[:g.gradient-handlers {:pointer-events "none"}
|
||||
[: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-width-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}]]
|
||||
[:& shadow {:id "gradient-drop-shadow" :offset (/ 2 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)}]]
|
||||
(let [lv (-> (gpt/to-vec from-p to-p)
|
||||
(gpt/unit))
|
||||
nv (gpt/normal-left lv)
|
||||
width (/ 40 zoom)
|
||||
points [(gpt/add from-p (gpt/scale nv (/ width -2)))
|
||||
(gpt/add from-p (gpt/scale nv (/ width 2)))
|
||||
(gpt/add to-p (gpt/scale nv (/ width 2)))
|
||||
(gpt/add to-p (gpt/scale nv (/ width -2)))]
|
||||
points-str
|
||||
(->> points
|
||||
(map #(dm/str (:x %) "," (:y %)))
|
||||
(str/join ", "))]
|
||||
|
||||
[:polygon {:points points-str
|
||||
:data-allow-click-modal "colorpicker"
|
||||
:fill "transparent"
|
||||
:pointer-events "all"
|
||||
:on-pointer-enter points-on-pointer-enter
|
||||
:on-pointer-leave points-on-pointer-leave
|
||||
:on-pointer-down points-on-pointer-down
|
||||
:on-pointer-move points-on-pointer-move}])
|
||||
|
||||
[:g {:filter "url(#gradient-drop-shadow)"}
|
||||
(let [pu
|
||||
(-> (gpt/to-vec from-p to-p)
|
||||
(gpt/normal-right)
|
||||
(gpt/unit))
|
||||
|
||||
sc (/ gradient-line-stroke-width zoom 2)
|
||||
|
||||
points
|
||||
[(gpt/add from-p (gpt/scale pu (- sc)))
|
||||
(gpt/add from-p (gpt/scale pu sc))
|
||||
(gpt/add to-p (gpt/scale pu sc))
|
||||
(gpt/add to-p (gpt/scale pu (- sc)))]]
|
||||
;; Use the polygon shape instead of lines because horizontal/vertical lines won't work
|
||||
;; with shadows
|
||||
[:polygon
|
||||
{:points
|
||||
(->> points
|
||||
(map #(dm/fmt "%, %" (:x %) (:y %)))
|
||||
(str/join " "))
|
||||
|
||||
:fill gradient-line-stroke-color}])]
|
||||
|
||||
(when width-p
|
||||
[:g {:filter "url(#gradient-width-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)}]])
|
||||
[:g {:filter "url(#gradient-drop-shadow)"}
|
||||
(let [pu
|
||||
(-> (gpt/to-vec from-p width-p)
|
||||
(gpt/normal-right)
|
||||
(gpt/unit))
|
||||
|
||||
sc (/ gradient-line-stroke-width zoom 2)
|
||||
|
||||
points
|
||||
[(gpt/add from-p (gpt/scale pu (- sc)))
|
||||
(gpt/add from-p (gpt/scale pu sc))
|
||||
(gpt/add width-p (gpt/scale pu sc))
|
||||
(gpt/add width-p (gpt/scale pu (- sc)))]]
|
||||
;; Use the polygon shape instead of lines because horizontal/vertical lines won't work
|
||||
;; with shadows
|
||||
[:polygon {:points
|
||||
(->> points
|
||||
(map #(dm/fmt "%, %" (:x %) (:y %)))
|
||||
(str/join " "))
|
||||
|
||||
:fill gradient-line-stroke-color}])])
|
||||
|
||||
(when width-p
|
||||
[:g {:filter "url(#gradient-width-handler-drop-shadow)"}
|
||||
[:g {:filter "url(#gradient-drop-shadow)"}
|
||||
(when (= :width-p (:hover @handler-state))
|
||||
[:circle {:filter "url(#gradient-drop-shadow)"
|
||||
:cx (:x width-p)
|
||||
:cy (:y width-p)
|
||||
:fill gradient-square-stroke-color-selected
|
||||
:r (/ gradient-width-handler-radius-selected zoom)}])
|
||||
|
||||
[:circle {:data-allow-click-modal "colorpicker"
|
||||
:cx (:x width-p)
|
||||
:cy (:y width-p)
|
||||
:r (/ gradient-width-handler-radius zoom)
|
||||
:fill gradient-width-handler-color
|
||||
:on-pointer-down (partial on-pointer-down :width-p)
|
||||
:on-pointer-up (partial on-pointer-up :width-p)}]])
|
||||
:fill gradient-width-handler-color}]
|
||||
|
||||
[:& gradient-color-handler
|
||||
{:selected (or (not editing) (= editing 0))
|
||||
: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-pointer-down (partial on-pointer-down :from-p)
|
||||
:on-pointer-up (partial on-pointer-up :from-p)}]
|
||||
[:circle {:data-allow-click-modal "colorpicker"
|
||||
:pointer-events "all"
|
||||
:cx (:x width-p)
|
||||
:cy (:y width-p)
|
||||
:r (/ gradient-width-handler-radius-handler zoom)
|
||||
:fill "transpgarent"
|
||||
:on-pointer-down (partial endpoint-on-pointer-down :width-p)
|
||||
:on-pointer-enter (partial endpoint-on-pointer-enter :width-p)
|
||||
:on-pointer-leave (partial endpoint-on-pointer-leave :width-p)
|
||||
:on-pointer-up (partial endpoint-on-pointer-up :width-p)}]])
|
||||
|
||||
[:& gradient-color-handler
|
||||
{:selected (= editing 1)
|
||||
: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-pointer-down (partial on-pointer-down :to-p)
|
||||
:on-pointer-up (partial on-pointer-up :to-p)}]]))
|
||||
[:g
|
||||
(when (= :from-p (:hover @handler-state))
|
||||
[:circle {:filter "url(#gradient-drop-shadow)"
|
||||
:cx (:x from-p)
|
||||
:cy (:y from-p)
|
||||
:fill gradient-square-stroke-color-selected
|
||||
:r (/ gradient-endpoint-radius-selected zoom)}])
|
||||
|
||||
[:circle {:filter "url(#gradient-drop-shadow)"
|
||||
:cx (:x from-p)
|
||||
:cy (:y from-p)
|
||||
:fill "var(--app-white)"
|
||||
:r (/ gradient-endpoint-radius zoom)}]
|
||||
|
||||
[:circle {:data-allow-click-modal "colorpicker"
|
||||
:pointer-events "all"
|
||||
:cx (:x from-p)
|
||||
:cy (:y from-p)
|
||||
:fill "transparent"
|
||||
:r (/ gradient-endpoint-radius-handler zoom)
|
||||
:on-pointer-down (partial endpoint-on-pointer-down :from-p)
|
||||
:on-pointer-up (partial endpoint-on-pointer-up :from-p)
|
||||
:on-pointer-enter (partial endpoint-on-pointer-enter :from-p)
|
||||
:on-pointer-leave (partial endpoint-on-pointer-leave :from-p)
|
||||
:on-lost-pointer-capture (partial endpoint-on-pointer-up :from-p)}]]
|
||||
|
||||
[:g
|
||||
(when (= :to-p (:hover @handler-state))
|
||||
[:circle {:filter "url(#gradient-drop-shadow)"
|
||||
:cx (:x to-p)
|
||||
:cy (:y to-p)
|
||||
:fill gradient-square-stroke-color-selected
|
||||
:r (/ gradient-endpoint-radius-selected zoom)}])
|
||||
|
||||
[:circle {:filter "url(#gradient-drop-shadow)"
|
||||
:cx (:x to-p)
|
||||
:cy (:y to-p)
|
||||
:fill "var(--app-white)"
|
||||
:r (/ gradient-endpoint-radius zoom)}]
|
||||
|
||||
[:circle {:data-allow-click-modal "colorpicker"
|
||||
:pointer-events "all"
|
||||
:cx (:x to-p)
|
||||
:cy (:y to-p)
|
||||
:fill "transparent"
|
||||
:r (/ gradient-endpoint-radius-handler zoom)
|
||||
:on-pointer-down (partial endpoint-on-pointer-down :to-p)
|
||||
:on-pointer-up (partial endpoint-on-pointer-up :to-p)
|
||||
:on-pointer-enter (partial endpoint-on-pointer-enter :to-p)
|
||||
:on-pointer-leave (partial endpoint-on-pointer-leave :to-p)
|
||||
:on-lost-pointer-capture (partial endpoint-on-pointer-up :from-p)}]]
|
||||
|
||||
(for [[index stop] (d/enumerate stops)]
|
||||
(let [stop-p
|
||||
(gpt/add
|
||||
from-p
|
||||
(-> (gpt/to-vec from-p to-p)
|
||||
(gpt/scale (:offset stop))))]
|
||||
|
||||
[:& gradient-color-handler
|
||||
{:key index
|
||||
:selected (= editing index)
|
||||
:zoom zoom
|
||||
:point stop-p
|
||||
:color {:value (:color stop) :opacity (:opacity stop)}
|
||||
:angle angle
|
||||
:index index
|
||||
:on-pointer-down handle-marker-pointer-down
|
||||
:on-pointer-move handle-marker-pointer-move
|
||||
:on-lost-pointer-capture handle-marker-lost-pointer-capture}]))
|
||||
|
||||
(when (:display? @handler-state)
|
||||
(let [p (gpt/add from-p
|
||||
(-> (gpt/to-vec from-p to-p)
|
||||
(gpt/scale (:offset @handler-state))))]
|
||||
[:circle {:filter "url(#gradient-drop-shadow)"
|
||||
:cx (:x p)
|
||||
:cy (:y p)
|
||||
:r (/ 4 zoom)
|
||||
:fill "var(--app-white)"}]))]))
|
||||
|
||||
(mf/defc gradient-handlers*
|
||||
[{:keys [zoom stops gradient editing-stop shape]}]
|
||||
[{:keys [zoom stops gradient editing shape] :as kk}]
|
||||
(let [transform (gsh/transform-matrix shape)
|
||||
transform-inverse (gsh/inverse-transform-matrix shape)
|
||||
|
||||
{:keys [x y width height] :as sr} (:selrect shape)
|
||||
|
||||
[{start-color :color start-opacity :opacity}
|
||||
{end-color :color end-opacity :opacity}] stops
|
||||
|
||||
from-p (-> (gpt/point (+ x (* width (:start-x gradient)))
|
||||
(+ y (* height (:start-y gradient))))
|
||||
(gpt/transform transform))
|
||||
|
@ -249,7 +459,7 @@
|
|||
gradient-length (gpt/length gradient-vec)
|
||||
|
||||
width-v (-> gradient-vec
|
||||
(gpt/normal-left)
|
||||
(gpt/normal-right)
|
||||
(gpt/multiply (gpt/point (* (:width gradient) (/ gradient-length (/ height 2)))))
|
||||
(gpt/multiply (gpt/point (/ width 2))))
|
||||
|
||||
|
@ -289,12 +499,11 @@
|
|||
(change! {:width norm-dist})))))]
|
||||
|
||||
[:& gradient-handler-transformed
|
||||
{:editing editing-stop
|
||||
{:editing editing
|
||||
: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}
|
||||
:stops stops
|
||||
:zoom zoom
|
||||
:on-change-start on-change-start
|
||||
:on-change-finish on-change-finish
|
||||
|
@ -305,17 +514,15 @@
|
|||
[{:keys [id zoom]}]
|
||||
(let [shape-ref (mf/use-memo (mf/deps id) #(refs/object-by-id id))
|
||||
shape (mf/deref shape-ref)
|
||||
|
||||
state (mf/deref refs/colorpicker)
|
||||
gradient (:gradient state)
|
||||
stops (:stops state)
|
||||
editing-stop (:editing-stop state)]
|
||||
|
||||
(when (and (some? gradient)
|
||||
(= id (:shape-id gradient)))
|
||||
(when (and (some? gradient) (= id (:shape-id gradient)))
|
||||
[:& gradient-handlers*
|
||||
{:zoom zoom
|
||||
:gradient gradient
|
||||
:stops stops
|
||||
:editing-stop editing-stop
|
||||
:editing editing-stop
|
||||
:shape shape}])))
|
||||
|
|
|
@ -2146,7 +2146,6 @@ msgstr "YouTube"
|
|||
msgid "media.choose-image"
|
||||
msgstr "Choose image"
|
||||
|
||||
#, unused
|
||||
msgid "media.gradient"
|
||||
msgstr "Gradient"
|
||||
|
||||
|
|
Loading…
Reference in a new issue