mirror of
https://github.com/penpot/penpot.git
synced 2026-02-23 10:17:35 -05:00
Compare commits
1 Commits
staging-re
...
superalex-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
782adec4e6 |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"~:file-id": "~u3a4d7ec7-c391-8146-8007-9a05c41da6b9",
|
||||
"~:id": "~u3a4d7ec7-c391-8146-8007-9dd6c998fbc4",
|
||||
"~:created-at": "~m1771846681191",
|
||||
"~:modified-at": "~m1771846681191",
|
||||
"~:type": "fragment",
|
||||
"~:backend": "db",
|
||||
"~:data": {
|
||||
"~:id": "~u95b23c15-79f9-81ba-8007-99d81b5290dd",
|
||||
"~:name": "Page 1",
|
||||
"~:objects": {
|
||||
"~#penpot/objects-map/v2": {
|
||||
"~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]],[\"^:\",[\"^ \",\"~:x\",0.01,\"~:y\",0.0]],[\"^:\",[\"^ \",\"~:x\",0.01,\"~:y\",0.01]],[\"^:\",[\"^ \",\"~:x\",0.0,\"~:y\",0.01]]],\"~:r2\",0,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^3\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:page-id\",\"~u95b23c15-79f9-81ba-8007-99d81b5290dd\",\"~: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,\"^6\",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,\"^I\",0.01,\"~:flip-y\",null,\"~:shapes\",[\"~ucfb31a9c-83c2-806f-8007-9dbf43043bdf\"]]]",
|
||||
"~ucfb31a9c-83c2-806f-8007-9dbf43043be0": "[\"~#shape\",[\"^ \",\"~:y\",-218.99999605032087,\"~:hide-fill-on-export\",false,\"~:layout-gap-type\",\"~:multiple\",\"~:layout-padding\",[\"^ \",\"~:p1\",5,\"~:p2\",5,\"~:p3\",5,\"~:p4\",5],\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:layout-wrap-type\",\"~:nowrap\",\"~:layout\",\"~:flex\",\"~:hide-in-viewer\",true,\"~:name\",\"Container\",\"~:layout-align-items\",\"~:start\",\"~:width\",431.99994866329087,\"~:layout-padding-type\",\"~:simple\",\"~:type\",\"~:frame\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",608.9999813066789,\"~:y\",-218.99999605032087]],[\"^J\",[\"^ \",\"~:x\",1040.9999299699698,\"~:y\",-218.99999605032087]],[\"^J\",[\"^ \",\"~:x\",1040.9999299699698,\"~:y\",-177.00001533586985]],[\"^J\",[\"^ \",\"~:x\",608.9999813066789,\"~:y\",-177.00001533586985]]],\"~:show-content\",true,\"~:layout-item-h-sizing\",\"~:fill\",\"~:proportion-lock\",false,\"~:layout-gap\",[\"^ \",\"~:row-gap\",4,\"~:column-gap\",4],\"~:transform-inverse\",[\"^:\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:page-id\",\"~u95b23c15-79f9-81ba-8007-99d81b5290dd\",\"~:layout-item-v-sizing\",\"~:auto\",\"~:layout-justify-content\",\"^C\",\"~:constraints-v\",\"~:top\",\"~:constraints-h\",\"~:left\",\"~:id\",\"~ucfb31a9c-83c2-806f-8007-9dbf43043be0\",\"~:parent-id\",\"~ucfb31a9c-83c2-806f-8007-9dbf43043bdf\",\"~:layout-flex-dir\",\"~:column\",\"~:layout-align-content\",\"~:stretch\",\"~:frame-id\",\"~ucfb31a9c-83c2-806f-8007-9dbf43043bdf\",\"~:strokes\",[],\"~:x\",608.9999813066788,\"~:proportion\",1,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",608.9999813066788,\"~:y\",-218.99999605032087,\"^D\",431.99994866329087,\"~:height\",41.99998071445103,\"~:x1\",608.9999813066788,\"~:y1\",-218.99999605032087,\"~:x2\",1040.9999299699698,\"~:y2\",-177.00001533586985]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#ffc0cb\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^1:\",41.99998071445103,\"~:flip-y\",null,\"~:shapes\",[\"~ucfb31a9c-83c2-806f-8007-9dbf43043be2\",\"~ucfb31a9c-83c2-806f-8007-9dbf43043be3\"]]]",
|
||||
"~ucfb31a9c-83c2-806f-8007-9dbf43043be2": "[\"~#shape\",[\"^ \",\"~:y\",-178.00000568505413,\"~: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\",\"show / hide me\",\"~:width\",99.98206911702209,\"~:type\",\"~:rect\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",614.0000002576337,\"~:y\",-178.00000568505413]],[\"^:\",[\"^ \",\"~:x\",713.9820693746558,\"~:y\",-178.00000568505413]],[\"^:\",[\"^ \",\"~:x\",713.9820693746558,\"~:y\",-148.0000135081636]],[\"^:\",[\"^ \",\"~:x\",614.0000002576337,\"~:y\",-148.0000135081636]]],\"~:r2\",0,\"~:layout-item-h-sizing\",\"~:fix\",\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:layout-item-v-sizing\",\"^=\",\"~:r3\",0,\"~:constraints-v\",\"~:top\",\"~:constraints-h\",\"~:left\",\"~:r1\",0,\"~:hidden\",true,\"~:id\",\"~ucfb31a9c-83c2-806f-8007-9dbf43043be2\",\"~:parent-id\",\"~ucfb31a9c-83c2-806f-8007-9dbf43043be0\",\"~:frame-id\",\"~ucfb31a9c-83c2-806f-8007-9dbf43043be0\",\"~:strokes\",[],\"~:x\",614.0000002576337,\"~:proportion\",1,\"~:r4\",0,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",614.0000002576337,\"~:y\",-178.00000568505413,\"^6\",99.98206911702209,\"~:height\",29.999992176890544,\"~:x1\",614.0000002576337,\"~:y1\",-178.00000568505413,\"~:x2\",713.9820693746558,\"~:y2\",-148.0000135081636]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#B1B2B5\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^P\",29.999992176890544,\"~:flip-y\",null]]",
|
||||
"~ucfb31a9c-83c2-806f-8007-9dbf43043be3": "[\"~#shape\",[\"^ \",\"~:y\",-213.99999587313152,\"~:hide-fill-on-export\",false,\"~:rx\",8,\"~: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\",true,\"~:name\",\"Full width\",\"~:width\",422.00001200500014,\"~:type\",\"~:frame\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",613.9999939062393,\"~:y\",-213.99999587313152]],[\"^<\",[\"^ \",\"~:x\",1036.0000059112394,\"~:y\",-213.99999587313152]],[\"^<\",[\"^ \",\"~:x\",1036.0000059112394,\"~:y\",-182.00001303926604]],[\"^<\",[\"^ \",\"~:x\",613.9999939062393,\"~:y\",-182.00001303926604]]],\"~:r2\",8,\"~:show-content\",true,\"~:layout-item-h-sizing\",\"~:fix\",\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^4\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:page-id\",\"~u95b23c15-79f9-81ba-8007-99d81b5290dd\",\"~:layout-item-v-sizing\",\"^@\",\"~:r3\",8,\"~:r1\",8,\"~:id\",\"~ucfb31a9c-83c2-806f-8007-9dbf43043be3\",\"~:parent-id\",\"~ucfb31a9c-83c2-806f-8007-9dbf43043be0\",\"~:frame-id\",\"~ucfb31a9c-83c2-806f-8007-9dbf43043be0\",\"~:strokes\",[],\"~:x\",613.9999939062393,\"~:proportion\",1,\"~:r4\",8,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",613.9999939062393,\"~:y\",-213.99999587313152,\"^8\",422.00001200500014,\"~:height\",31.999982833865488,\"~:x1\",613.9999939062393,\"~:y1\",-213.99999587313152,\"~:x2\",1036.0000059112394,\"~:y2\",-182.00001303926604]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#212426\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"~:ry\",8,\"^O\",31.999982833865488,\"~:flip-y\",null,\"~:shapes\",[]]]",
|
||||
"~ucfb31a9c-83c2-806f-8007-9dbf43043bdf": "[\"~#shape\",[\"^ \",\"~:y\",-228.99999763039506,\"~:hide-fill-on-export\",false,\"~:layout-gap-type\",\"~:multiple\",\"~:layout-padding\",[\"^ \",\"~:p1\",10,\"~:p2\",10,\"~:p3\",10,\"~:p4\",10],\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:layout-wrap-type\",\"~:nowrap\",\"~:layout\",\"~:flex\",\"~:hide-in-viewer\",true,\"~:name\",\"Parent\",\"~:layout-align-items\",\"~:start\",\"~:width\",451.999905143128,\"~:layout-padding-type\",\"~:simple\",\"~:type\",\"~:frame\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",599.0000149607649,\"~:y\",-228.99999763039506]],[\"^J\",[\"^ \",\"~:x\",1050.999920103893,\"~:y\",-228.99999763039506]],[\"^J\",[\"^ \",\"~:x\",1050.999920103893,\"~:y\",-167.0000160450801]],[\"^J\",[\"^ \",\"~:x\",599.0000149607649,\"~:y\",-167.0000160450801]]],\"~:r2\",0,\"~:show-content\",true,\"~:layout-item-h-sizing\",\"~:fix\",\"~:proportion-lock\",false,\"~:layout-gap\",[\"^ \",\"~:row-gap\",10,\"~:column-gap\",8],\"~:transform-inverse\",[\"^:\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:page-id\",\"~u95b23c15-79f9-81ba-8007-99d81b5290dd\",\"~:layout-item-v-sizing\",\"~:auto\",\"~:r3\",0,\"~:layout-justify-content\",\"^C\",\"~:r1\",0,\"~:id\",\"~ucfb31a9c-83c2-806f-8007-9dbf43043bdf\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:layout-flex-dir\",\"~:column\",\"~:layout-align-content\",\"~:stretch\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",599.0000149607649,\"~:proportion\",1,\"~:r4\",0,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",599.0000149607649,\"~:y\",-228.99999763039506,\"^D\",451.999905143128,\"~:height\",61.99998158531497,\"~:x1\",599.0000149607649,\"~:y1\",-228.99999763039506,\"~:x2\",1050.999920103893,\"~:y2\",-167.0000160450801]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#000000\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^1:\",61.99998158531497,\"~:flip-y\",null,\"~:shapes\",[\"~ucfb31a9c-83c2-806f-8007-9dbf43043be0\"]]]"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
{
|
||||
"~:features": {
|
||||
"~#set": [
|
||||
"fdata/path-data",
|
||||
"design-tokens/v1",
|
||||
"variants/v1",
|
||||
"layout/grid",
|
||||
"fdata/pointer-map",
|
||||
"fdata/objects-map",
|
||||
"components/v2",
|
||||
"fdata/shape-data-type"
|
||||
]
|
||||
},
|
||||
"~:team-id": "~ud715d0a5-a44e-8056-8005-a79999e18b64",
|
||||
"~:permissions": {
|
||||
"~:type": "~:membership",
|
||||
"~:is-owner": true,
|
||||
"~:is-admin": true,
|
||||
"~:can-edit": true,
|
||||
"~:can-read": true,
|
||||
"~:is-logged": true
|
||||
},
|
||||
"~:has-media-trimmed": false,
|
||||
"~:comment-thread-seqn": 0,
|
||||
"~:name": "test-bug-flex",
|
||||
"~:revn": 114,
|
||||
"~:modified-at": "~m1771846681183",
|
||||
"~:vern": 0,
|
||||
"~:id": "~u3a4d7ec7-c391-8146-8007-9a05c41da6b9",
|
||||
"~:is-shared": false,
|
||||
"~:migrations": {
|
||||
"~#ordered-set": [
|
||||
"legacy-2",
|
||||
"legacy-3",
|
||||
"legacy-5",
|
||||
"legacy-6",
|
||||
"legacy-7",
|
||||
"legacy-8",
|
||||
"legacy-9",
|
||||
"legacy-10",
|
||||
"legacy-11",
|
||||
"legacy-12",
|
||||
"legacy-13",
|
||||
"legacy-14",
|
||||
"legacy-16",
|
||||
"legacy-17",
|
||||
"legacy-18",
|
||||
"legacy-19",
|
||||
"legacy-25",
|
||||
"legacy-26",
|
||||
"legacy-27",
|
||||
"legacy-28",
|
||||
"legacy-29",
|
||||
"legacy-31",
|
||||
"legacy-32",
|
||||
"legacy-33",
|
||||
"legacy-34",
|
||||
"legacy-36",
|
||||
"legacy-37",
|
||||
"legacy-38",
|
||||
"legacy-39",
|
||||
"legacy-40",
|
||||
"legacy-41",
|
||||
"legacy-42",
|
||||
"legacy-43",
|
||||
"legacy-44",
|
||||
"legacy-45",
|
||||
"legacy-46",
|
||||
"legacy-47",
|
||||
"legacy-48",
|
||||
"legacy-49",
|
||||
"legacy-50",
|
||||
"legacy-51",
|
||||
"legacy-52",
|
||||
"legacy-53",
|
||||
"legacy-54",
|
||||
"legacy-55",
|
||||
"legacy-56",
|
||||
"legacy-57",
|
||||
"legacy-59",
|
||||
"legacy-62",
|
||||
"legacy-65",
|
||||
"legacy-66",
|
||||
"legacy-67",
|
||||
"0001-remove-tokens-from-groups",
|
||||
"0002-normalize-bool-content-v2",
|
||||
"0002-clean-shape-interactions",
|
||||
"0003-fix-root-shape",
|
||||
"0003-convert-path-content-v2",
|
||||
"0005-deprecate-image-type",
|
||||
"0006-fix-old-texts-fills",
|
||||
"0008-fix-library-colors-v4",
|
||||
"0009-clean-library-colors",
|
||||
"0009-add-partial-text-touched-flags",
|
||||
"0010-fix-swap-slots-pointing-non-existent-shapes",
|
||||
"0011-fix-invalid-text-touched-flags",
|
||||
"0012-fix-position-data",
|
||||
"0013-fix-component-path",
|
||||
"0013-clear-invalid-strokes-and-fills",
|
||||
"0014-fix-tokens-lib-duplicate-ids",
|
||||
"0014-clear-components-nil-objects",
|
||||
"0015-fix-text-attrs-blank-strings",
|
||||
"0015-clean-shadow-color",
|
||||
"0016-copy-fills-from-position-data-to-text-node"
|
||||
]
|
||||
},
|
||||
"~:version": 67,
|
||||
"~:project-id": "~u76eab896-accf-81a5-8007-2b264ebe7817",
|
||||
"~:created-at": "~m1771590560885",
|
||||
"~:backend": "legacy-db",
|
||||
"~:data": {
|
||||
"~:pages": [
|
||||
"~u95b23c15-79f9-81ba-8007-99d81b5290dd"
|
||||
],
|
||||
"~:pages-index": {
|
||||
"~u95b23c15-79f9-81ba-8007-99d81b5290dd": {
|
||||
"~#penpot/pointer": [
|
||||
"~u3a4d7ec7-c391-8146-8007-9dd6c998fbc4",
|
||||
{
|
||||
"~:created-at": "~m1771846681187"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"~:id": "~u3a4d7ec7-c391-8146-8007-9a05c41da6b9",
|
||||
"~:options": {
|
||||
"~:components-v2": true,
|
||||
"~:base-font-size": "16px"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -243,46 +243,6 @@ test("Renders a file with a closed path shape with multiple segments using strok
|
||||
await expect(workspace.canvas).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test("Renders solid shadows after select all and zoom to selected", async ({
|
||||
page,
|
||||
}) => {
|
||||
const workspace = new WasmWorkspacePage(page);
|
||||
await workspace.setupEmptyFile();
|
||||
await workspace.mockGetFile("render-wasm/get-solid-shadows.json");
|
||||
|
||||
await workspace.goToWorkspace({
|
||||
id: "93113137-fe66-80fb-8007-99ca9fd96841",
|
||||
pageId: "93113137-fe66-80fb-8007-99ca9fd96842",
|
||||
});
|
||||
await workspace.waitForFirstRender();
|
||||
|
||||
await workspace.viewport.click();
|
||||
await page.keyboard.press("ControlOrMeta+A");
|
||||
const previousRenderCount = await workspace.getRenderCount();
|
||||
await page.keyboard.press("f");
|
||||
await workspace.waitForNextRender(previousRenderCount);
|
||||
|
||||
await workspace.hideUI();
|
||||
await expect(workspace.canvas).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test("Renders strokes with solid shadows", async ({
|
||||
page,
|
||||
}) => {
|
||||
const workspace = new WasmWorkspacePage(page);
|
||||
await workspace.setupEmptyFile();
|
||||
await workspace.mockGetFile("render-wasm/get-solid-strokes-shadows.json");
|
||||
|
||||
await workspace.goToWorkspace({
|
||||
id: "93113137-fe66-80fb-8007-99cfd5cbf361",
|
||||
pageId: "93113137-fe66-80fb-8007-99cfd5cbf362",
|
||||
});
|
||||
await workspace.waitForFirstRender();
|
||||
|
||||
await workspace.hideUI();
|
||||
await expect(workspace.canvas).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test("Renders a file with paths and svg attrs", async ({ page }) => {
|
||||
const workspace = new WasmWorkspacePage(page);
|
||||
await workspace.setupEmptyFile();
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 63 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 260 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 114 KiB |
@@ -55,31 +55,3 @@ test("BUG 13382 - Fix problem with flex layout", async ({ page }) => {
|
||||
await expect(workspacePage.rightSidebar.getByTitle("Height").getByRole("textbox")).toHaveValue("340");
|
||||
|
||||
});
|
||||
|
||||
test("BUG 13468 - Fix problem with flex propagation", async ({ page }) => {
|
||||
const workspacePage = new WasmWorkspacePage(page);
|
||||
await workspacePage.setupEmptyFile();
|
||||
await workspacePage.mockGetFile("workspace/get-file-13468.json");
|
||||
|
||||
await workspacePage.mockRPC(
|
||||
"get-file-fragment?file-id=*&fragment-id=*",
|
||||
"workspace/get-file-13468-fragment.json",
|
||||
);
|
||||
|
||||
await workspacePage.mockRPC("update-file?id=*", "workspace/update-file-empty.json");
|
||||
|
||||
await workspacePage.goToWorkspace({
|
||||
fileId: "3a4d7ec7-c391-8146-8007-9a05c41da6b9",
|
||||
pageId: "95b23c15-79f9-81ba-8007-99d81b5290dd",
|
||||
});
|
||||
0
|
||||
await workspacePage.clickToggableLayer("Parent");
|
||||
await workspacePage.clickToggableLayer("Container");
|
||||
|
||||
await workspacePage.sidebar.getByRole('button', { name: 'Show' }).click();
|
||||
|
||||
await workspacePage.clickLeafLayer("Container");
|
||||
await expect(workspacePage.rightSidebar.getByTitle("Height").getByRole("textbox")).toHaveValue("76");
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -642,7 +642,7 @@ impl RenderState {
|
||||
apply_to_current_surface: bool,
|
||||
offset: Option<(f32, f32)>,
|
||||
parent_shadows: Option<Vec<skia_safe::Paint>>,
|
||||
spread: Option<f32>,
|
||||
outset: Option<f32>,
|
||||
) {
|
||||
let surface_ids = fills_surface_id as u32
|
||||
| strokes_surface_id as u32
|
||||
@@ -701,14 +701,7 @@ impl RenderState {
|
||||
canvas.translate(translation);
|
||||
});
|
||||
|
||||
fills::render(
|
||||
self,
|
||||
shape,
|
||||
&shape.fills,
|
||||
antialias,
|
||||
SurfaceId::Current,
|
||||
None,
|
||||
);
|
||||
fills::render(self, shape, &shape.fills, antialias, SurfaceId::Current, outset);
|
||||
|
||||
// Pass strokes in natural order; stroke merging handles top-most ordering internally.
|
||||
let visible_strokes: Vec<&Stroke> = shape.visible_strokes().collect();
|
||||
@@ -718,7 +711,7 @@ impl RenderState {
|
||||
&visible_strokes,
|
||||
Some(SurfaceId::Current),
|
||||
antialias,
|
||||
spread,
|
||||
outset,
|
||||
);
|
||||
|
||||
self.surfaces.apply_mut(SurfaceId::Current as u32, |s| {
|
||||
@@ -1070,7 +1063,7 @@ impl RenderState {
|
||||
&fills_to_render,
|
||||
antialias,
|
||||
fills_surface_id,
|
||||
spread,
|
||||
outset,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
@@ -1080,7 +1073,7 @@ impl RenderState {
|
||||
&shape.fills,
|
||||
antialias,
|
||||
fills_surface_id,
|
||||
spread,
|
||||
outset,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1096,7 +1089,7 @@ impl RenderState {
|
||||
&visible_strokes,
|
||||
Some(strokes_surface_id),
|
||||
antialias,
|
||||
spread,
|
||||
outset,
|
||||
);
|
||||
if !fast_mode {
|
||||
for stroke in &visible_strokes {
|
||||
@@ -1651,54 +1644,12 @@ impl RenderState {
|
||||
return;
|
||||
}
|
||||
|
||||
// blur=0 at high zoom: draw directly on DropShadows with geometric spread (no filter).
|
||||
if scale > 1.0 && shadow.blur <= 0.0 {
|
||||
let drop_canvas = self.surfaces.canvas(SurfaceId::DropShadows);
|
||||
drop_canvas.save();
|
||||
drop_canvas.scale((scale, scale));
|
||||
drop_canvas.translate(translation);
|
||||
|
||||
self.with_nested_blurs_suppressed(|state| {
|
||||
state.render_shape(
|
||||
&plain_shape,
|
||||
clip_bounds,
|
||||
SurfaceId::DropShadows,
|
||||
SurfaceId::DropShadows,
|
||||
SurfaceId::DropShadows,
|
||||
SurfaceId::DropShadows,
|
||||
false,
|
||||
Some(shadow.offset),
|
||||
None,
|
||||
Some(shadow.spread),
|
||||
);
|
||||
});
|
||||
|
||||
self.surfaces.canvas(SurfaceId::DropShadows).restore();
|
||||
return;
|
||||
}
|
||||
|
||||
// Create filter with blur only (no offset, no spread - handled geometrically)
|
||||
let blur_only_filter = if transformed_shadow.blur > 0.0 {
|
||||
Some(skia::image_filters::blur(
|
||||
(transformed_shadow.blur, transformed_shadow.blur),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut shadow_paint = skia::Paint::default();
|
||||
if let Some(blur_filter) = blur_only_filter {
|
||||
shadow_paint.set_image_filter(blur_filter);
|
||||
}
|
||||
shadow_paint.set_blend_mode(skia::BlendMode::SrcOver);
|
||||
|
||||
let layer_rec = skia::canvas::SaveLayerRec::default().paint(&shadow_paint);
|
||||
|
||||
// Low zoom path: use blur filter but apply offset and spread geometrically
|
||||
if use_low_zoom_path {
|
||||
let mut shadow_paint = skia::Paint::default();
|
||||
shadow_paint.set_image_filter(drop_filter);
|
||||
shadow_paint.set_blend_mode(skia::BlendMode::SrcOver);
|
||||
|
||||
let layer_rec = skia::canvas::SaveLayerRec::default().paint(&shadow_paint);
|
||||
let drop_canvas = self.surfaces.canvas(SurfaceId::DropShadows);
|
||||
drop_canvas.save_layer(&layer_rec);
|
||||
drop_canvas.scale((scale, scale));
|
||||
@@ -1713,9 +1664,9 @@ impl RenderState {
|
||||
SurfaceId::DropShadows,
|
||||
SurfaceId::DropShadows,
|
||||
false,
|
||||
Some(shadow.offset), // Offset is geometric
|
||||
Some(shadow.offset),
|
||||
None,
|
||||
None,
|
||||
Some(shadow.spread), // Spread is geometric
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1725,7 +1676,7 @@ impl RenderState {
|
||||
|
||||
// Adaptive downscale for large blur values (lossless GPU optimization).
|
||||
// Bounds above were computed from the original sigma so filter surface coverage is correct.
|
||||
// Maximum downscale is 1/BLUR_DOWNSCALE_THRESHOLD (i.e. 8x): beyond that the
|
||||
// Maximum downscale is 1/BLUR_DOWNSCALE_THRESHOLD (i.e. 8×): beyond that the
|
||||
// filter surface becomes too small and quality degrades noticeably.
|
||||
const MIN_BLUR_DOWNSCALE: f32 = 1.0 / BLUR_DOWNSCALE_THRESHOLD;
|
||||
let blur_downscale = if shadow.blur > BLUR_DOWNSCALE_THRESHOLD {
|
||||
@@ -1734,18 +1685,23 @@ impl RenderState {
|
||||
1.0
|
||||
};
|
||||
|
||||
// High zoom with blur: use render_into_filter_surface to ensure blur has enough space
|
||||
// Apply spread geometrically to avoid dilate filter rounding issues
|
||||
let filter_result = filters::render_into_filter_surface(
|
||||
self,
|
||||
bounds,
|
||||
blur_downscale,
|
||||
|state, temp_surface| {
|
||||
let canvas = state.surfaces.canvas(temp_surface);
|
||||
canvas.save_layer(&layer_rec);
|
||||
{
|
||||
let canvas = state.surfaces.canvas(temp_surface);
|
||||
|
||||
let mut shadow_paint = skia::Paint::default();
|
||||
shadow_paint.set_image_filter(drop_filter);
|
||||
shadow_paint.set_blend_mode(skia::BlendMode::SrcOver);
|
||||
|
||||
let layer_rec = skia::canvas::SaveLayerRec::default().paint(&shadow_paint);
|
||||
canvas.save_layer(&layer_rec);
|
||||
}
|
||||
|
||||
state.with_nested_blurs_suppressed(|state| {
|
||||
// Apply offset and spread geometrically
|
||||
state.render_shape(
|
||||
&plain_shape,
|
||||
clip_bounds,
|
||||
@@ -1754,13 +1710,16 @@ impl RenderState {
|
||||
temp_surface,
|
||||
temp_surface,
|
||||
false,
|
||||
Some(shadow.offset), // Offset is geometric
|
||||
Some(shadow.offset),
|
||||
None,
|
||||
None,
|
||||
Some(shadow.spread), // Spread is geometric
|
||||
);
|
||||
});
|
||||
|
||||
state.surfaces.canvas(temp_surface).restore();
|
||||
{
|
||||
let canvas = state.surfaces.canvas(temp_surface);
|
||||
canvas.restore();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -2,7 +2,15 @@ use skia_safe::{self as skia, Paint, RRect};
|
||||
|
||||
use super::{filters, RenderState, SurfaceId};
|
||||
use crate::render::get_source_rect;
|
||||
use crate::shapes::{merge_fills, Fill, Frame, ImageFill, Rect, Shape, Type};
|
||||
use crate::shapes::{merge_fills, Fill, Frame, ImageFill, Rect, Shape, StrokeKind, Type};
|
||||
|
||||
/// True when the shape has at least one visible inner stroke.
|
||||
fn has_inner_stroke(shape: &Shape) -> bool {
|
||||
let is_open = shape.is_open();
|
||||
shape
|
||||
.visible_strokes()
|
||||
.any(|s| s.render_kind(is_open) == StrokeKind::Inner)
|
||||
}
|
||||
|
||||
fn draw_image_fill(
|
||||
render_state: &mut RenderState,
|
||||
@@ -97,7 +105,7 @@ pub fn render(
|
||||
fills: &[Fill],
|
||||
antialias: bool,
|
||||
surface_id: SurfaceId,
|
||||
spread: Option<f32>,
|
||||
outset: Option<f32>,
|
||||
) {
|
||||
if fills.is_empty() {
|
||||
return;
|
||||
@@ -107,8 +115,14 @@ pub fn render(
|
||||
// and sampling options that get_fill_shader (used by merge_fills) lacks.
|
||||
let has_image_fills = fills.iter().any(|f| matches!(f, Fill::Image(_)));
|
||||
if has_image_fills {
|
||||
let scale = render_state.get_scale().max(1e-6);
|
||||
let inset = if has_inner_stroke(shape) {
|
||||
Some(1.0 / scale)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
for fill in fills.iter().rev() {
|
||||
render_single_fill(render_state, shape, fill, antialias, surface_id, spread);
|
||||
render_single_fill(render_state, shape, fill, antialias, surface_id, outset, inset);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -116,6 +130,13 @@ pub fn render(
|
||||
let mut paint = merge_fills(fills, shape.selrect);
|
||||
paint.set_anti_alias(antialias);
|
||||
|
||||
let scale = render_state.get_scale().max(1e-6);
|
||||
let inset = if has_inner_stroke(shape) {
|
||||
Some(1.0 / scale)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(image_filter) = shape.image_filter(1.) {
|
||||
let bounds = image_filter.compute_fast_bounds(shape.selrect);
|
||||
if filters::render_with_filter_surface(
|
||||
@@ -125,7 +146,7 @@ pub fn render(
|
||||
|state, temp_surface| {
|
||||
let mut filtered_paint = paint.clone();
|
||||
filtered_paint.set_image_filter(image_filter.clone());
|
||||
draw_fill_to_surface(state, shape, temp_surface, &filtered_paint, spread);
|
||||
draw_fill_to_surface(state, shape, temp_surface, &filtered_paint, outset, inset);
|
||||
},
|
||||
) {
|
||||
return;
|
||||
@@ -134,33 +155,35 @@ pub fn render(
|
||||
}
|
||||
}
|
||||
|
||||
draw_fill_to_surface(render_state, shape, surface_id, &paint, spread);
|
||||
draw_fill_to_surface(render_state, shape, surface_id, &paint, outset, inset);
|
||||
}
|
||||
|
||||
/// Draws a single paint (with a merged shader) to the appropriate surface
|
||||
/// based on the shape type.
|
||||
/// When `inset` is Some(eps), the fill is inset by eps (e.g. to avoid seam with inner strokes).
|
||||
fn draw_fill_to_surface(
|
||||
render_state: &mut RenderState,
|
||||
shape: &Shape,
|
||||
surface_id: SurfaceId,
|
||||
paint: &Paint,
|
||||
spread: Option<f32>,
|
||||
outset: Option<f32>,
|
||||
inset: Option<f32>,
|
||||
) {
|
||||
match &shape.shape_type {
|
||||
Type::Rect(_) | Type::Frame(_) => {
|
||||
render_state
|
||||
.surfaces
|
||||
.draw_rect_to(surface_id, shape, paint, spread);
|
||||
.draw_rect_to(surface_id, shape, paint, outset, inset);
|
||||
}
|
||||
Type::Circle => {
|
||||
render_state
|
||||
.surfaces
|
||||
.draw_circle_to(surface_id, shape, paint, spread);
|
||||
.draw_circle_to(surface_id, shape, paint, outset, inset);
|
||||
}
|
||||
Type::Path(_) | Type::Bool(_) => {
|
||||
render_state
|
||||
.surfaces
|
||||
.draw_path_to(surface_id, shape, paint, spread);
|
||||
.draw_path_to(surface_id, shape, paint, outset, inset);
|
||||
}
|
||||
Type::Group(_) => {}
|
||||
_ => unreachable!("This shape should not have fills"),
|
||||
@@ -173,7 +196,8 @@ fn render_single_fill(
|
||||
fill: &Fill,
|
||||
antialias: bool,
|
||||
surface_id: SurfaceId,
|
||||
spread: Option<f32>,
|
||||
outset: Option<f32>,
|
||||
inset: Option<f32>,
|
||||
) {
|
||||
let mut paint = fill.to_paint(&shape.selrect, antialias);
|
||||
if let Some(image_filter) = shape.image_filter(1.) {
|
||||
@@ -192,7 +216,8 @@ fn render_single_fill(
|
||||
antialias,
|
||||
temp_surface,
|
||||
&filtered_paint,
|
||||
spread,
|
||||
outset,
|
||||
inset,
|
||||
);
|
||||
},
|
||||
) {
|
||||
@@ -209,7 +234,8 @@ fn render_single_fill(
|
||||
antialias,
|
||||
surface_id,
|
||||
&paint,
|
||||
spread,
|
||||
outset,
|
||||
inset,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -220,7 +246,8 @@ fn draw_single_fill_to_surface(
|
||||
antialias: bool,
|
||||
surface_id: SurfaceId,
|
||||
paint: &Paint,
|
||||
spread: Option<f32>,
|
||||
outset: Option<f32>,
|
||||
inset: Option<f32>,
|
||||
) {
|
||||
match (fill, &shape.shape_type) {
|
||||
(Fill::Image(image_fill), _) => {
|
||||
@@ -236,17 +263,17 @@ fn draw_single_fill_to_surface(
|
||||
(_, Type::Rect(_) | Type::Frame(_)) => {
|
||||
render_state
|
||||
.surfaces
|
||||
.draw_rect_to(surface_id, shape, paint, spread);
|
||||
.draw_rect_to(surface_id, shape, paint, outset, inset);
|
||||
}
|
||||
(_, Type::Circle) => {
|
||||
render_state
|
||||
.surfaces
|
||||
.draw_circle_to(surface_id, shape, paint, spread);
|
||||
.draw_circle_to(surface_id, shape, paint, outset, inset);
|
||||
}
|
||||
(_, Type::Path(_)) | (_, Type::Bool(_)) => {
|
||||
render_state
|
||||
.surfaces
|
||||
.draw_path_to(surface_id, shape, paint, spread);
|
||||
.draw_path_to(surface_id, shape, paint, outset, inset);
|
||||
}
|
||||
(_, Type::Group(_)) => {
|
||||
// Groups can have fills but they propagate them to their children
|
||||
|
||||
@@ -47,7 +47,7 @@ pub fn render_stroke_inner_shadows(
|
||||
Some(surface_id),
|
||||
filter.as_ref(),
|
||||
antialias,
|
||||
None, // Inner shadows don't use spread
|
||||
None,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -109,17 +109,17 @@ fn render_shadow_paint(
|
||||
Type::Rect(_) | Type::Frame(_) => {
|
||||
render_state
|
||||
.surfaces
|
||||
.draw_rect_to(surface_id, shape, paint, None);
|
||||
.draw_rect_to(surface_id, shape, paint, None, None);
|
||||
}
|
||||
Type::Circle => {
|
||||
render_state
|
||||
.surfaces
|
||||
.draw_circle_to(surface_id, shape, paint, None);
|
||||
.draw_circle_to(surface_id, shape, paint, None, None);
|
||||
}
|
||||
Type::Path(_) | Type::Bool(_) => {
|
||||
render_state
|
||||
.surfaces
|
||||
.draw_path_to(surface_id, shape, paint, None);
|
||||
.draw_path_to(surface_id, shape, paint, None, None);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -526,7 +526,7 @@ pub fn render(
|
||||
strokes: &[&Stroke],
|
||||
surface_id: Option<SurfaceId>,
|
||||
antialias: bool,
|
||||
spread: Option<f32>,
|
||||
outset: Option<f32>,
|
||||
) {
|
||||
if strokes.is_empty() {
|
||||
return;
|
||||
@@ -541,8 +541,8 @@ pub fn render(
|
||||
// edges semi-transparent and revealing strokes underneath.
|
||||
if let Some(image_filter) = shape.image_filter(1.) {
|
||||
let mut content_bounds = shape.selrect;
|
||||
// Expand for spread if provided
|
||||
if let Some(s) = spread.filter(|&s| s > 0.0) {
|
||||
// Expand for outset if provided
|
||||
if let Some(s) = outset.filter(|&s| s > 0.0) {
|
||||
content_bounds.outset((s, s));
|
||||
}
|
||||
let max_margin = strokes
|
||||
@@ -588,7 +588,7 @@ pub fn render(
|
||||
antialias,
|
||||
true,
|
||||
true,
|
||||
spread,
|
||||
outset,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -608,7 +608,7 @@ pub fn render(
|
||||
surface_id,
|
||||
None,
|
||||
antialias,
|
||||
spread,
|
||||
outset,
|
||||
);
|
||||
}
|
||||
return;
|
||||
@@ -621,7 +621,7 @@ pub fn render(
|
||||
surface_id,
|
||||
antialias,
|
||||
false,
|
||||
spread,
|
||||
outset,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -642,7 +642,7 @@ fn render_merged(
|
||||
surface_id: Option<SurfaceId>,
|
||||
antialias: bool,
|
||||
bypass_filter: bool,
|
||||
spread: Option<f32>,
|
||||
outset: Option<f32>,
|
||||
) {
|
||||
let representative = *strokes
|
||||
.last()
|
||||
@@ -658,8 +658,8 @@ fn render_merged(
|
||||
if !bypass_filter {
|
||||
if let Some(image_filter) = blur_filter.clone() {
|
||||
let mut content_bounds = shape.selrect;
|
||||
// Expand for spread if provided
|
||||
if let Some(s) = spread.filter(|&s| s > 0.0) {
|
||||
// Expand for outset if provided
|
||||
if let Some(s) = outset.filter(|&s| s > 0.0) {
|
||||
content_bounds.outset((s, s));
|
||||
}
|
||||
let stroke_margin = representative.bounds_width(shape.is_open());
|
||||
@@ -694,7 +694,7 @@ fn render_merged(
|
||||
Some(temp_surface),
|
||||
antialias,
|
||||
true,
|
||||
spread,
|
||||
outset,
|
||||
);
|
||||
|
||||
state.surfaces.apply_mut(temp_surface as u32, |surface| {
|
||||
@@ -711,8 +711,8 @@ fn render_merged(
|
||||
// via SrcOver), matching the non-merged path where strokes[0] is drawn last (on top).
|
||||
let fills: Vec<Fill> = strokes.iter().map(|s| s.fill.clone()).collect();
|
||||
|
||||
// Expand selrect if spread is provided
|
||||
let selrect = if let Some(s) = spread.filter(|&s| s > 0.0) {
|
||||
// Expand selrect if outset is provided
|
||||
let selrect = if let Some(s) = outset.filter(|&s| s > 0.0) {
|
||||
let mut r = shape.selrect;
|
||||
r.outset((s, s));
|
||||
r
|
||||
@@ -724,6 +724,7 @@ fn render_merged(
|
||||
let scale = render_state.get_scale();
|
||||
let target_surface = surface_id.unwrap_or(SurfaceId::Strokes);
|
||||
let canvas = render_state.surfaces.canvas_and_mark_dirty(target_surface);
|
||||
let selrect = shape.selrect;
|
||||
let svg_attrs = shape.svg_attrs.as_ref();
|
||||
let path_transform = shape.to_path_transform();
|
||||
|
||||
@@ -790,7 +791,7 @@ pub fn render_single(
|
||||
surface_id: Option<SurfaceId>,
|
||||
shadow: Option<&ImageFilter>,
|
||||
antialias: bool,
|
||||
spread: Option<f32>,
|
||||
outset: Option<f32>,
|
||||
) {
|
||||
render_single_internal(
|
||||
render_state,
|
||||
@@ -801,7 +802,7 @@ pub fn render_single(
|
||||
antialias,
|
||||
false,
|
||||
false,
|
||||
spread,
|
||||
outset,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -815,13 +816,13 @@ fn render_single_internal(
|
||||
antialias: bool,
|
||||
bypass_filter: bool,
|
||||
skip_blur: bool,
|
||||
spread: Option<f32>,
|
||||
outset: Option<f32>,
|
||||
) {
|
||||
if !bypass_filter {
|
||||
if let Some(image_filter) = shape.image_filter(1.) {
|
||||
let mut content_bounds = shape.selrect;
|
||||
// Expand for spread if provided
|
||||
if let Some(s) = spread.filter(|&s| s > 0.0) {
|
||||
// Expand for outset if provided
|
||||
if let Some(s) = outset.filter(|&s| s > 0.0) {
|
||||
content_bounds.outset((s, s));
|
||||
}
|
||||
let stroke_margin = stroke.bounds_width(shape.is_open());
|
||||
@@ -849,7 +850,7 @@ fn render_single_internal(
|
||||
antialias,
|
||||
true,
|
||||
true,
|
||||
spread,
|
||||
outset,
|
||||
);
|
||||
},
|
||||
) {
|
||||
@@ -920,18 +921,18 @@ fn render_single_internal(
|
||||
let is_open = path.is_open();
|
||||
let mut paint =
|
||||
stroke.to_stroked_paint(is_open, &selrect, svg_attrs, antialias);
|
||||
// Apply spread by increasing stroke width
|
||||
if let Some(s) = spread.filter(|&s| s > 0.0) {
|
||||
// Apply outset by increasing stroke width
|
||||
if let Some(s) = outset.filter(|&s| s > 0.0) {
|
||||
let current_width = paint.stroke_width();
|
||||
// Path stroke kinds are built differently:
|
||||
// - Center uses the stroke width directly.
|
||||
// - Inner/Outer use a doubled width plus clipping/clearing logic.
|
||||
// Compensate spread so visual growth is comparable across kinds.
|
||||
let spread_growth = match stroke.render_kind(is_open) {
|
||||
// Compensate outset so visual growth is comparable across kinds.
|
||||
let outset_growth = match stroke.render_kind(is_open) {
|
||||
StrokeKind::Center => s * 2.0,
|
||||
StrokeKind::Inner | StrokeKind::Outer => s * 4.0,
|
||||
};
|
||||
paint.set_stroke_width(current_width + spread_growth);
|
||||
paint.set_stroke_width(current_width + outset_growth);
|
||||
}
|
||||
draw_stroke_on_path(
|
||||
canvas,
|
||||
|
||||
@@ -74,7 +74,7 @@ impl Surfaces {
|
||||
let margins = skia::ISize::new(extra_tile_dims.width / 4, extra_tile_dims.height / 4);
|
||||
|
||||
let target = gpu_state.create_target_surface(width, height);
|
||||
let filter = gpu_state.create_surface_with_isize("filter".to_string(), extra_tile_dims);
|
||||
let filter = gpu_state.create_surface_with_dimensions("filter".to_string(), width, height);
|
||||
let cache = gpu_state.create_surface_with_dimensions("cache".to_string(), width, height);
|
||||
let current = gpu_state.create_surface_with_isize("current".to_string(), extra_tile_dims);
|
||||
let drop_shadows =
|
||||
@@ -360,16 +360,30 @@ impl Surfaces {
|
||||
id: SurfaceId,
|
||||
shape: &Shape,
|
||||
paint: &Paint,
|
||||
spread: Option<f32>,
|
||||
outset: Option<f32>,
|
||||
inset: Option<f32>,
|
||||
) {
|
||||
let rect = if let Some(s) = spread.filter(|&s| s > 0.0) {
|
||||
let mut rect = if let Some(s) = outset.filter(|&s| s > 0.0) {
|
||||
let mut r = shape.selrect;
|
||||
r.outset((s, s));
|
||||
r
|
||||
} else {
|
||||
shape.selrect
|
||||
};
|
||||
if let Some(eps) = inset.filter(|&e| e > 0.0) {
|
||||
rect.inset((eps, eps));
|
||||
}
|
||||
if let Some(corners) = shape.shape_type.corners() {
|
||||
let corners = if let Some(eps) = inset.filter(|&e| e > 0.0) {
|
||||
let mut c = corners;
|
||||
for r in c.iter_mut() {
|
||||
r.x = (r.x - eps).max(0.0);
|
||||
r.y = (r.y - eps).max(0.0);
|
||||
}
|
||||
c
|
||||
} else {
|
||||
corners
|
||||
};
|
||||
let rrect = RRect::new_rect_radii(rect, &corners);
|
||||
self.canvas_and_mark_dirty(id).draw_rrect(rrect, paint);
|
||||
} else {
|
||||
@@ -382,15 +396,19 @@ impl Surfaces {
|
||||
id: SurfaceId,
|
||||
shape: &Shape,
|
||||
paint: &Paint,
|
||||
spread: Option<f32>,
|
||||
outset: Option<f32>,
|
||||
inset: Option<f32>,
|
||||
) {
|
||||
let rect = if let Some(s) = spread.filter(|&s| s > 0.0) {
|
||||
let mut rect = if let Some(s) = outset.filter(|&s| s > 0.0) {
|
||||
let mut r = shape.selrect;
|
||||
r.outset((s, s));
|
||||
r
|
||||
} else {
|
||||
shape.selrect
|
||||
};
|
||||
if let Some(eps) = inset.filter(|&e| e > 0.0) {
|
||||
rect.inset((eps, eps));
|
||||
}
|
||||
self.canvas_and_mark_dirty(id).draw_oval(rect, paint);
|
||||
}
|
||||
|
||||
@@ -399,17 +417,28 @@ impl Surfaces {
|
||||
id: SurfaceId,
|
||||
shape: &Shape,
|
||||
paint: &Paint,
|
||||
spread: Option<f32>,
|
||||
outset: Option<f32>,
|
||||
inset: Option<f32>,
|
||||
) {
|
||||
if let Some(path) = shape.get_skia_path() {
|
||||
let canvas = self.canvas_and_mark_dirty(id);
|
||||
if let Some(s) = spread.filter(|&s| s > 0.0) {
|
||||
if let Some(s) = outset.filter(|&s| s > 0.0) {
|
||||
// Draw path as a thick stroke to get outset (expanded) silhouette
|
||||
let mut stroke_paint = paint.clone();
|
||||
stroke_paint.set_style(skia::PaintStyle::Stroke);
|
||||
stroke_paint.set_stroke_width(s * 2.0);
|
||||
canvas.draw_path(&path, &stroke_paint);
|
||||
} else {
|
||||
canvas.draw_path(&path, paint);
|
||||
// Inset: avoid seam with inner strokes by clearing a thin border from the fill
|
||||
if let Some(eps) = inset.filter(|&e| e > 0.0) {
|
||||
let mut clear_paint = skia::Paint::default();
|
||||
clear_paint.set_style(skia::PaintStyle::Stroke);
|
||||
clear_paint.set_stroke_width(eps * 2.0);
|
||||
clear_paint.set_blend_mode(skia::BlendMode::Clear);
|
||||
clear_paint.set_anti_alias(paint.is_anti_alias());
|
||||
canvas.draw_path(&path, &clear_paint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,7 +300,20 @@ fn propagate_reflow(
|
||||
Type::Frame(Frame {
|
||||
layout: Some(_), ..
|
||||
}) => {
|
||||
layout_reflows.insert(*id);
|
||||
let mut skip_reflow = false;
|
||||
if shape.is_layout_horizontal_fill() || shape.is_layout_vertical_fill() {
|
||||
if let Some(parent_id) = shape.parent_id {
|
||||
if parent_id != Uuid::nil() && !reflown.contains(&parent_id) {
|
||||
// If this is a fill layout but the parent has not been reflown yet
|
||||
// we wait for the next iteration for reflow
|
||||
skip_reflow = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !skip_reflow {
|
||||
layout_reflows.insert(*id);
|
||||
}
|
||||
}
|
||||
Type::Group(Group { masked: true }) => {
|
||||
let children_ids = shape.children_ids(true);
|
||||
|
||||
Reference in New Issue
Block a user