mirror of
https://github.com/penpot/penpot.git
synced 2025-05-21 16:16:10 +02:00
🎉 Cap stop amount in UI for wasm (#6438)
* 🎉 Cap in the colorpicker the amount of stops a gradient can have * 🎉 Cap the stops amount in gradient handlers * 🎉 Disable add stop in gradient handlers (viewport + colorpicker) * ✨ Add integration test for gradient limits * 💄 Address PR suggestion
This commit is contained in:
parent
69cc4fb4c2
commit
91fbe8f8ef
10 changed files with 438 additions and 102 deletions
|
@ -761,3 +761,5 @@
|
|||
(d/patch-object (select-keys props basic-extract-props))
|
||||
(cond-> (cfh/text-shape? shape) (patch-text-props props))
|
||||
(cond-> (cfh/frame-shape? shape) (patch-layout-props props)))))
|
||||
|
||||
(def MAX-GRADIENT-STOPS 16)
|
|
@ -0,0 +1,279 @@
|
|||
{
|
||||
"~:id": "~u66697432-c33d-8055-8006-2c62de27d653",
|
||||
"~:file-id": "~u66697432-c33d-8055-8006-2c62cc084cac",
|
||||
"~:created-at": "~m1747053122715",
|
||||
"~:data": {
|
||||
"~:options": {},
|
||||
"~:objects": {
|
||||
"~u00000000-0000-0000-0000-000000000000": {
|
||||
"~#shape": {
|
||||
"~:y": 0,
|
||||
"~:hide-fill-on-export": false,
|
||||
"~:transform": {
|
||||
"~#matrix": {
|
||||
"~:a": 1.0,
|
||||
"~:b": 0.0,
|
||||
"~:c": 0.0,
|
||||
"~:d": 1.0,
|
||||
"~:e": 0.0,
|
||||
"~:f": 0.0
|
||||
}
|
||||
},
|
||||
"~:rotation": 0,
|
||||
"~:name": "Root Frame",
|
||||
"~:width": 0.01,
|
||||
"~:type": "~:frame",
|
||||
"~:points": [
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 0.0,
|
||||
"~:y": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 0.01,
|
||||
"~:y": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 0.01,
|
||||
"~:y": 0.01
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 0.0,
|
||||
"~:y": 0.01
|
||||
}
|
||||
}
|
||||
],
|
||||
"~:r2": 0,
|
||||
"~:proportion-lock": false,
|
||||
"~:transform-inverse": {
|
||||
"~#matrix": {
|
||||
"~:a": 1.0,
|
||||
"~:b": 0.0,
|
||||
"~:c": 0.0,
|
||||
"~:d": 1.0,
|
||||
"~:e": 0.0,
|
||||
"~:f": 0.0
|
||||
}
|
||||
},
|
||||
"~:r3": 0,
|
||||
"~:r1": 0,
|
||||
"~:id": "~u00000000-0000-0000-0000-000000000000",
|
||||
"~:parent-id": "~u00000000-0000-0000-0000-000000000000",
|
||||
"~:frame-id": "~u00000000-0000-0000-0000-000000000000",
|
||||
"~:strokes": [],
|
||||
"~:x": 0,
|
||||
"~:proportion": 1.0,
|
||||
"~:r4": 0,
|
||||
"~:selrect": {
|
||||
"~#rect": {
|
||||
"~:x": 0,
|
||||
"~:y": 0,
|
||||
"~:width": 0.01,
|
||||
"~:height": 0.01,
|
||||
"~:x1": 0,
|
||||
"~:y1": 0,
|
||||
"~:x2": 0.01,
|
||||
"~:y2": 0.01
|
||||
}
|
||||
},
|
||||
"~:fills": [
|
||||
{
|
||||
"~:fill-color": "#FFFFFF",
|
||||
"~:fill-opacity": 1
|
||||
}
|
||||
],
|
||||
"~:flip-x": null,
|
||||
"~:height": 0.01,
|
||||
"~:flip-y": null,
|
||||
"~:shapes": [
|
||||
"~u4a1d24c0-c794-80ff-8006-2c62cd7f23e0"
|
||||
]
|
||||
}
|
||||
},
|
||||
"~u4a1d24c0-c794-80ff-8006-2c62cd7f23e0": {
|
||||
"~#shape": {
|
||||
"~:y": 303,
|
||||
"~:transform": {
|
||||
"~#matrix": {
|
||||
"~:a": 1.0,
|
||||
"~:b": 0.0,
|
||||
"~:c": 0.0,
|
||||
"~:d": 1.0,
|
||||
"~:e": 0.0,
|
||||
"~:f": 0.0
|
||||
}
|
||||
},
|
||||
"~:rotation": 0,
|
||||
"~:hide-in-viewer": false,
|
||||
"~:name": "Rectangle",
|
||||
"~:width": 100,
|
||||
"~:type": "~:rect",
|
||||
"~:points": [
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 205,
|
||||
"~:y": 303
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 305,
|
||||
"~:y": 303
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 305,
|
||||
"~:y": 403
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 205,
|
||||
"~:y": 403
|
||||
}
|
||||
}
|
||||
],
|
||||
"~:r2": 0,
|
||||
"~:proportion-lock": false,
|
||||
"~:transform-inverse": {
|
||||
"~#matrix": {
|
||||
"~:a": 1.0,
|
||||
"~:b": 0.0,
|
||||
"~:c": 0.0,
|
||||
"~:d": 1.0,
|
||||
"~:e": 0.0,
|
||||
"~:f": 0.0
|
||||
}
|
||||
},
|
||||
"~:r3": 0,
|
||||
"~:r1": 0,
|
||||
"~:id": "~u4a1d24c0-c794-80ff-8006-2c62cd7f23e0",
|
||||
"~:parent-id": "~u00000000-0000-0000-0000-000000000000",
|
||||
"~:frame-id": "~u00000000-0000-0000-0000-000000000000",
|
||||
"~:strokes": [],
|
||||
"~:x": 205,
|
||||
"~:proportion": 1,
|
||||
"~:r4": 0,
|
||||
"~:selrect": {
|
||||
"~#rect": {
|
||||
"~:x": 205,
|
||||
"~:y": 303,
|
||||
"~:width": 100,
|
||||
"~:height": 100,
|
||||
"~:x1": 205,
|
||||
"~:y1": 303,
|
||||
"~:x2": 305,
|
||||
"~:y2": 403
|
||||
}
|
||||
},
|
||||
"~:fills": [
|
||||
{
|
||||
"~:fill-color-gradient": {
|
||||
"~:start-x": 0.5,
|
||||
"~:start-y": 0,
|
||||
"~:end-x": 0.5,
|
||||
"~:end-y": 1,
|
||||
"~:width": 1,
|
||||
"~:type": "~:linear",
|
||||
"~:stops": [
|
||||
{
|
||||
"~:color": "#003fff",
|
||||
"~:offset": 0,
|
||||
"~:opacity": 1
|
||||
},
|
||||
{
|
||||
"~:color": "#003fff",
|
||||
"~:offset": 0.07,
|
||||
"~:opacity": 0.9299999999999999
|
||||
},
|
||||
{
|
||||
"~:color": "#003fff",
|
||||
"~:offset": 0.13,
|
||||
"~:opacity": 0.87
|
||||
},
|
||||
{
|
||||
"~:color": "#003fff",
|
||||
"~:offset": 0.2,
|
||||
"~:opacity": 0.8
|
||||
},
|
||||
{
|
||||
"~:color": "#003fff",
|
||||
"~:offset": 0.27,
|
||||
"~:opacity": 0.73
|
||||
},
|
||||
{
|
||||
"~:color": "#003fff",
|
||||
"~:offset": 0.33,
|
||||
"~:opacity": 0.6699999999999999
|
||||
},
|
||||
{
|
||||
"~:color": "#003fff",
|
||||
"~:offset": 0.4,
|
||||
"~:opacity": 0.6
|
||||
},
|
||||
{
|
||||
"~:color": "#003fff",
|
||||
"~:offset": 0.47,
|
||||
"~:opacity": 0.53
|
||||
},
|
||||
{
|
||||
"~:color": "#003fff",
|
||||
"~:offset": 0.53,
|
||||
"~:opacity": 0.47
|
||||
},
|
||||
{
|
||||
"~:color": "#003fff",
|
||||
"~:offset": 0.6,
|
||||
"~:opacity": 0.4
|
||||
},
|
||||
{
|
||||
"~:color": "#003fff",
|
||||
"~:offset": 0.67,
|
||||
"~:opacity": 0.32999999999999996
|
||||
},
|
||||
{
|
||||
"~:color": "#003fff",
|
||||
"~:offset": 0.73,
|
||||
"~:opacity": 0.27
|
||||
},
|
||||
{
|
||||
"~:color": "#003fff",
|
||||
"~:offset": 0.8,
|
||||
"~:opacity": 0.19999999999999996
|
||||
},
|
||||
{
|
||||
"~:color": "#003fff",
|
||||
"~:offset": 0.87,
|
||||
"~:opacity": 0.13
|
||||
},
|
||||
{
|
||||
"~:color": "#003fff",
|
||||
"~:offset": 0.93,
|
||||
"~:opacity": 0.06999999999999995
|
||||
},
|
||||
{
|
||||
"~:color": "#003fff",
|
||||
"~:offset": 1,
|
||||
"~:opacity": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"~:flip-x": null,
|
||||
"~:height": 100,
|
||||
"~:flip-y": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"~:id": "~u66697432-c33d-8055-8006-2c62cc084cad",
|
||||
"~:name": "Page 1"
|
||||
}
|
||||
}
|
|
@ -36,6 +36,17 @@ export class BasePage {
|
|||
async mockRPC(path, jsonFilename, options) {
|
||||
return BasePage.mockRPC(this.page, path, jsonFilename, options);
|
||||
}
|
||||
|
||||
async mockConfigFlags(flags) {
|
||||
const url = "**/js/config.js?ts=*";
|
||||
return await this.page.route(url, (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/javascript",
|
||||
body: `var penpotFlags = "${flags.join(" ")}";`,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default BasePage;
|
||||
|
|
|
@ -49,6 +49,12 @@ export class WorkspacePage extends BaseWebSocketPage {
|
|||
"get-profiles-for-file-comments?file-id=*",
|
||||
"workspace/get-profile-for-file-comments.json",
|
||||
);
|
||||
|
||||
await BaseWebSocketPage.mockRPC(
|
||||
page,
|
||||
"update-profile-props",
|
||||
"workspace/update-profile-empty.json",
|
||||
);
|
||||
}
|
||||
|
||||
static anyTeamId = "c7ce0794-0992-8105-8004-38e630f7920a";
|
||||
|
|
|
@ -43,48 +43,44 @@ test("Create a LINEAR gradient", async ({ page }) => {
|
|||
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");
|
||||
const select = workspacePage.page.getByText("Solid");
|
||||
await select.click();
|
||||
|
||||
const gradOption = await workspacePage.page.getByText("Gradient");
|
||||
const gradOption = workspacePage.page.getByText("Gradient");
|
||||
await gradOption.click();
|
||||
|
||||
const addStopBtn = await workspacePage.page.getByRole("button", {
|
||||
const addStopBtn = workspacePage.page.getByRole("button", {
|
||||
name: "Add stop",
|
||||
});
|
||||
await addStopBtn.click();
|
||||
await addStopBtn.click();
|
||||
await addStopBtn.click();
|
||||
|
||||
const removeBtn = await workspacePage.page
|
||||
.getByTestId("colorpicker")
|
||||
const removeBtn = workspacePage.colorpicker
|
||||
.getByRole("button", { name: "Remove color" })
|
||||
.nth(2);
|
||||
await removeBtn.click();
|
||||
await removeBtn.click();
|
||||
|
||||
const inputColor1 = await workspacePage.page.getByPlaceholder("Mixed").nth(1);
|
||||
const inputColor1 = workspacePage.colorpicker
|
||||
.getByPlaceholder("Mixed")
|
||||
.nth(1);
|
||||
await inputColor1.fill("fabada");
|
||||
|
||||
const inputOpacity1 = await workspacePage.page
|
||||
.getByTestId("colorpicker")
|
||||
.getByPlaceholder("--")
|
||||
.nth(1);
|
||||
const inputOpacity1 = workspacePage.colorpicker.getByPlaceholder("--").nth(1);
|
||||
await inputOpacity1.fill("100");
|
||||
|
||||
const inputColor2 = await workspacePage.page.getByPlaceholder("Mixed").nth(2);
|
||||
const inputColor2 = workspacePage.colorpicker
|
||||
.getByPlaceholder("Mixed")
|
||||
.nth(1);
|
||||
await inputColor2.fill("red");
|
||||
|
||||
const inputOpacity2 = await workspacePage.page
|
||||
.getByTestId("colorpicker")
|
||||
.getByPlaceholder("--")
|
||||
.nth(2);
|
||||
const inputOpacity2 = workspacePage.colorpicker.getByPlaceholder("--").nth(1);
|
||||
await inputOpacity2.fill("100");
|
||||
|
||||
const inputOpacityGlobal = await workspacePage.page
|
||||
const inputOpacityGlobal = workspacePage.page
|
||||
.locator("div")
|
||||
.filter({ hasText: /^FillLinear gradient%$/ })
|
||||
.getByPlaceholder("--");
|
||||
|
@ -110,60 +106,75 @@ test("Create a RADIAL gradient", async ({ page }) => {
|
|||
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");
|
||||
const select = workspacePage.page.getByText("Solid");
|
||||
await select.click();
|
||||
|
||||
const gradOption = await workspacePage.page.getByText("Gradient");
|
||||
const gradOption = workspacePage.page.getByText("Gradient");
|
||||
await gradOption.click();
|
||||
|
||||
const gradTypeOptions = await workspacePage.page
|
||||
.getByTestId("colorpicker")
|
||||
const gradTypeOptions = workspacePage.colorpicker
|
||||
.locator("div")
|
||||
.filter({ hasText: "Linear" })
|
||||
.nth(3);
|
||||
await gradTypeOptions.click();
|
||||
|
||||
const gradRadialOption = await workspacePage.page
|
||||
const gradRadialOption = workspacePage.page
|
||||
.locator("li")
|
||||
.filter({ hasText: "Radial" });
|
||||
await gradRadialOption.click();
|
||||
|
||||
const addStopBtn = await workspacePage.page.getByRole("button", {
|
||||
const addStopBtn = workspacePage.page.getByRole("button", {
|
||||
name: "Add stop",
|
||||
});
|
||||
await addStopBtn.click();
|
||||
await addStopBtn.click();
|
||||
await addStopBtn.click();
|
||||
|
||||
const removeBtn = await workspacePage.page
|
||||
.getByTestId("colorpicker")
|
||||
const removeBtn = workspacePage.colorpicker
|
||||
.getByRole("button", { name: "Remove color" })
|
||||
.nth(2);
|
||||
await removeBtn.click();
|
||||
await removeBtn.click();
|
||||
|
||||
const inputColor1 = await workspacePage.page.getByPlaceholder("Mixed").nth(1);
|
||||
const inputColor1 = workspacePage.page.getByPlaceholder("Mixed").nth(1);
|
||||
await inputColor1.fill("fabada");
|
||||
|
||||
const inputOpacity1 = await workspacePage.page
|
||||
.getByTestId("colorpicker")
|
||||
.getByPlaceholder("--")
|
||||
.nth(1);
|
||||
const inputOpacity1 = workspacePage.colorpicker.getByPlaceholder("--").nth(1);
|
||||
await inputOpacity1.fill("100");
|
||||
|
||||
const inputColor2 = await workspacePage.page.getByPlaceholder("Mixed").nth(2);
|
||||
const inputColor2 = workspacePage.page.getByPlaceholder("Mixed").nth(2);
|
||||
await inputColor2.fill("red");
|
||||
|
||||
const inputOpacity2 = await workspacePage.page
|
||||
.getByTestId("colorpicker")
|
||||
.getByPlaceholder("--")
|
||||
.nth(2);
|
||||
const inputOpacity2 = workspacePage.colorpicker.getByPlaceholder("--").nth(1);
|
||||
await inputOpacity2.fill("100");
|
||||
});
|
||||
|
||||
test("Gradient stops limit", async ({ page }) => {
|
||||
const workspacePage = new WorkspacePage(page);
|
||||
await workspacePage.mockConfigFlags(["enable-binary-fills"]);
|
||||
await workspacePage.setupEmptyFile(page);
|
||||
await workspacePage.mockRPC(
|
||||
"get-file-fragment?file-id=*&fragment-id=*",
|
||||
"workspace/get-file-fragment-gradient-limits.json",
|
||||
);
|
||||
|
||||
await workspacePage.goToWorkspace();
|
||||
await workspacePage.clickLeafLayer("Rectangle");
|
||||
|
||||
const swatch = workspacePage.page.getByRole("button", {
|
||||
name: "Linear gradient",
|
||||
});
|
||||
await swatch.click();
|
||||
|
||||
await expect(workspacePage.colorpicker).toBeVisible();
|
||||
|
||||
await expect(
|
||||
workspacePage.colorpicker.getByRole("button", { name: "Add stop" }),
|
||||
).toBeDisabled();
|
||||
});
|
||||
|
||||
// Fix for https://tree.taiga.io/project/penpot/issue/9900
|
||||
test("Bug 9900 - Color picker has no inputs for HSV values", async ({
|
||||
page,
|
||||
|
@ -203,7 +214,6 @@ test("Bug 10089 - Cannot change alpha", async ({ page }) => {
|
|||
await workspacePage.clickLeafLayer("Rectangle");
|
||||
|
||||
const swatch = workspacePage.page.getByRole("button", { name: "#B1B2B5" });
|
||||
const swatchBox = await swatch.boundingBox();
|
||||
await swatch.click();
|
||||
|
||||
const alpha = workspacePage.page.getByLabel("A", { exact: true });
|
||||
|
|
|
@ -13,8 +13,9 @@
|
|||
[app.common.schema :as sm]
|
||||
[app.common.text :as txt]
|
||||
[app.common.types.color :as ctc]
|
||||
[app.common.types.shape :refer [check-stroke]]
|
||||
[app.common.types.shape :as shp]
|
||||
[app.common.types.shape.shadow :refer [check-shadow]]
|
||||
[app.config :as cfg]
|
||||
[app.main.broadcast :as mbc]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.helpers :as dsh]
|
||||
|
@ -24,6 +25,7 @@
|
|||
[app.main.data.workspace.shapes :as dwsh]
|
||||
[app.main.data.workspace.texts :as dwt]
|
||||
[app.main.data.workspace.undo :as dwu]
|
||||
[app.main.features :as features]
|
||||
[app.util.storage :as storage]
|
||||
[beicon.v2.core :as rx]
|
||||
[cuerdas.core :as str]
|
||||
|
@ -421,7 +423,7 @@
|
|||
[ids stroke]
|
||||
|
||||
(assert
|
||||
(check-stroke stroke)
|
||||
(shp/check-stroke stroke)
|
||||
"expected a valid stroke struct")
|
||||
|
||||
(assert
|
||||
|
@ -821,39 +823,43 @@
|
|||
(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)))
|
||||
(let [cap-stops? (or (features/active-feature? state "render-wasm/v1") (contains? cfg/flags :binary-fills))
|
||||
can-add-stop? (or (not cap-stops?) (< (count stops) shp/MAX-GRADIENT-STOPS))]
|
||||
(if can-add-stop?
|
||||
(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)
|
||||
;; 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))
|
||||
{from-offset :offset} (get stops index)
|
||||
{to-offset :offset} (get stops (inc index))
|
||||
|
||||
half-point-offset
|
||||
(+ from-offset (/ (- to-offset from-offset) 2))
|
||||
half-point-offset
|
||||
(+ from-offset (/ (- to-offset from-offset) 2))
|
||||
|
||||
new-stop (-> (cc/interpolate-gradient stops half-point-offset)
|
||||
(split-color-components))
|
||||
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)))))))))
|
||||
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))))
|
||||
state)))))))
|
||||
|
||||
(defn update-colorpicker-add-stop
|
||||
[offset]
|
||||
|
@ -863,15 +869,18 @@
|
|||
(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))))))))
|
||||
cap-stops? (or (features/active-feature? state "render-wasm/v1") (contains? cfg/flags :binary-fills))
|
||||
can-add-stop? (or (not cap-stops?) (< (count stops) shp/MAX-GRADIENT-STOPS))]
|
||||
(if can-add-stop? (let [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)))
|
||||
state)))))))
|
||||
|
||||
(defn update-colorpicker-stops
|
||||
[stops]
|
||||
|
@ -881,7 +890,8 @@
|
|||
(update state :colorpicker
|
||||
(fn [state]
|
||||
(let [stop (or (:editing-stop state) 0)
|
||||
stops (mapv split-color-components stops)]
|
||||
cap-stops? (or (features/active-feature? state "render-wasm/v1") (contains? cfg/flags :binary-fills))
|
||||
stops (mapv split-color-components (if cap-stops? (take shp/MAX-GRADIENT-STOPS stops) stops))]
|
||||
(-> state
|
||||
(assoc :current-color (get stops stop))
|
||||
(assoc :stops stops))))))))
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.types.shape :as shp]
|
||||
[app.config :as cfg]
|
||||
[app.main.data.event :as-alias ev]
|
||||
[app.main.data.modal :as modal]
|
||||
|
@ -20,6 +21,7 @@
|
|||
[app.main.data.workspace.libraries :as dwl]
|
||||
[app.main.data.workspace.media :as dwm]
|
||||
[app.main.data.workspace.undo :as dwu]
|
||||
[app.main.features :as features]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.file-uploader :refer [file-uploader]]
|
||||
|
@ -336,6 +338,8 @@
|
|||
(fn [value]
|
||||
(st/emit! (dc/update-colorpicker-gradient-opacity (/ value 100)))))
|
||||
|
||||
cap-stops? (or (features/use-feature "render-wasm/v1") (contains? cfg/flags :binary-fills))
|
||||
|
||||
tabs
|
||||
#js [#js {:aria-label (tr "workspace.libraries.colors.rgba")
|
||||
:icon ic/rgba
|
||||
|
@ -435,7 +439,7 @@
|
|||
(when (= selected-mode :gradient)
|
||||
[:> gradients*
|
||||
{:type (:type state)
|
||||
:stops (:stops state)
|
||||
:stops (if cap-stops? (vec (take shp/MAX-GRADIENT-STOPS (:stops state))) (:stops state))
|
||||
:editing-stop (:editing-stop state)
|
||||
:on-stop-edit-start handle-stop-edit-start
|
||||
:on-stop-edit-finish handle-stop-edit-finish
|
||||
|
|
|
@ -11,6 +11,9 @@
|
|||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.math :as mth]
|
||||
[app.common.types.shape :as shp]
|
||||
[app.config :as cfg]
|
||||
[app.main.features :as features]
|
||||
[app.main.ui.components.numeric-input :refer [numeric-input*]]
|
||||
[app.main.ui.components.reorder-handler :refer [reorder-handler]]
|
||||
[app.main.ui.components.select :refer [select]]
|
||||
|
@ -283,7 +286,9 @@
|
|||
(mf/deps on-reverse-stops)
|
||||
(fn []
|
||||
(when on-reverse-stops
|
||||
(on-reverse-stops))))]
|
||||
(on-reverse-stops))))
|
||||
cap-stops? (or (features/use-feature "render-wasm/v1") (contains? cfg/flags :binary-fills))
|
||||
add-stop-disabled? (when cap-stops? (>= (count stops) shp/MAX-GRADIENT-STOPS))]
|
||||
|
||||
[:div {:class (stl/css :gradient-panel)}
|
||||
[:div {:class (stl/css :gradient-preview)}
|
||||
|
@ -294,9 +299,10 @@
|
|||
: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)) "%")}}]]
|
||||
(when (not add-stop-disabled?)
|
||||
[: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)]
|
||||
|
@ -339,6 +345,7 @@
|
|||
:icon "switch"}]
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-label "Add stop"
|
||||
:disabled add-stop-disabled?
|
||||
:on-click handle-add-stop
|
||||
:icon "add"}]]]
|
||||
|
||||
|
|
|
@ -15,7 +15,10 @@
|
|||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.geom.shapes.points :as gsp]
|
||||
[app.common.math :as mth]
|
||||
[app.common.types.shape :as shp]
|
||||
[app.config :as cfg]
|
||||
[app.main.data.workspace.colors :as dc]
|
||||
[app.main.features :as features]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.workspace.viewport.viewport-ref :as uwvv]
|
||||
|
@ -131,6 +134,9 @@
|
|||
|
||||
handler-state (mf/use-state {:display? false :offset 0 :hover nil})
|
||||
|
||||
cap-stops? (or (features/use-feature "render-wasm/v1") (contains? cfg/flags :binary-fills))
|
||||
can-add-stop? (if cap-stops? (< (count stops) shp/MAX-GRADIENT-STOPS) true)
|
||||
|
||||
endpoint-on-pointer-down
|
||||
(fn [position event]
|
||||
(dom/stop-propagation event)
|
||||
|
@ -164,7 +170,8 @@
|
|||
points-on-pointer-enter
|
||||
(mf/use-fn
|
||||
(fn []
|
||||
(swap! handler-state assoc :display? true)))
|
||||
(when can-add-stop?
|
||||
(swap! handler-state assoc :display? true))))
|
||||
|
||||
points-on-pointer-leave
|
||||
(mf/use-fn
|
||||
|
@ -177,17 +184,17 @@
|
|||
(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)))))
|
||||
(when can-add-stop?
|
||||
(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
|
||||
|
@ -354,7 +361,7 @@
|
|||
:cx (:x width-p)
|
||||
:cy (:y width-p)
|
||||
:r (/ gradient-width-handler-radius-handler zoom)
|
||||
:fill "transpgarent"
|
||||
:fill "transparent"
|
||||
: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)
|
||||
|
@ -518,7 +525,10 @@
|
|||
shape (mf/deref shape-ref)
|
||||
state (mf/deref refs/colorpicker)
|
||||
gradient (:gradient state)
|
||||
stops (:stops state)
|
||||
cap-stops? (or (features/use-feature "render-wasm/v1") (contains? cfg/flags :binary-fills))
|
||||
stops (if cap-stops?
|
||||
(vec (take shp/MAX-GRADIENT-STOPS (:stops state)))
|
||||
(:stops state))
|
||||
editing-stop (:editing-stop state)]
|
||||
|
||||
(when (and (some? gradient) (= id (:shape-id gradient)))
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
(ns app.render-wasm.serializers.fills
|
||||
(:require
|
||||
[app.common.types.shape :as shp]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.render-wasm.serializers.color :as clr]))
|
||||
|
||||
(def ^:private GRADIENT-STOP-SIZE 8)
|
||||
(def ^:private GRADIENT-BASE-SIZE 28)
|
||||
;; TODO: Define in shape model
|
||||
(def ^:private MAX-GRADIENT-STOPS 16)
|
||||
|
||||
(def GRADIENT-BYTE-SIZE
|
||||
(+ GRADIENT-BASE-SIZE (* MAX-GRADIENT-STOPS GRADIENT-STOP-SIZE)))
|
||||
|
||||
(def GRADIENT-BYTE-SIZE 156)
|
||||
(def SOLID-BYTE-SIZE 4)
|
||||
(def IMAGE-BYTE-SIZE 28)
|
||||
|
||||
|
@ -48,7 +45,7 @@
|
|||
end-x (:end-x gradient)
|
||||
end-y (:end-y gradient)
|
||||
width (or (:width gradient) 0)
|
||||
stops (take MAX-GRADIENT-STOPS (:stops gradient))
|
||||
stops (take shp/MAX-GRADIENT-STOPS (:stops gradient))
|
||||
type (if (= (:type gradient) :linear) 0x01 0x02)]
|
||||
(.setUint8 dview offset type true)
|
||||
(.setFloat32 dview (+ offset 4) start-x true)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue