diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index 0451d4226e..93bda7e01d 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -9,6 +9,7 @@ on: pull_request: paths: - website/** + workflow_dispatch: {} env: CARGO_TERM_COLOR: always INDEX_HTML_HEAD_INCLUSION: @@ -80,8 +81,32 @@ jobs: - name: 🚚 Move `website/other/dist` contents to `website/public` run: | + mkdir -p website/public mv website/other/dist/* website/public + - name: 💿 Obtain cache of auto-generated code docs artifacts + id: cache-website-code-docs + uses: actions/cache/restore@v3 + with: + path: artifacts + key: website-code-docs + + - name: 📁 Fallback in case auto-generated code docs artifacts weren't cached + if: steps.cache-website-code-docs.outputs.cache-hit != 'true' + run: | + echo "🦀 Initial system version of Rust:" + rustc --version + rustup update stable + echo "🦀 Latest updated version of Rust:" + rustc --version + cargo test --package graphite-editor --lib -- messages::message::test::generate_message_tree + mkdir artifacts + mv hierarchical_message_system_tree.txt artifacts/hierarchical_message_system_tree.txt + + - name: 🚚 Move `artifacts` contents to `website/public` + run: | + mv artifacts/* website/public + - name: 📤 Publish to Cloudflare Pages id: cloudflare uses: cloudflare/pages-action@1 diff --git a/Cargo.lock b/Cargo.lock index 5487e27d28..71611c5faa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -290,9 +290,9 @@ dependencies = [ [[package]] name = "avif-serialize" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19135c0c7a60bfee564dbe44ab5ce0557c6bf3884e5291a50be76a15640c4fbd" +checksum = "2ea8ef51aced2b9191c08197f55450d830876d9933f8f48a429b354f1d496b42" dependencies = [ "arrayvec", ] @@ -666,7 +666,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02260d489095346e5cafd04dea8e8cb54d1d74fcd759022a9b72986ebe9a1257" dependencies = [ "serde", - "toml", + "toml 0.8.23", ] [[package]] @@ -677,9 +677,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.27" +version = "1.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" +checksum = "5c1599538de2394445747c8cf7935946e3cc27e9625f889d979bfb2aaf569362" dependencies = [ "jobserver", "libc", @@ -775,9 +775,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.40" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" +checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9" dependencies = [ "clap_builder", "clap_derive", @@ -785,9 +785,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.40" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" +checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d" dependencies = [ "anstream", "anstyle", @@ -797,9 +797,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.40" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" +checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -1016,9 +1016,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] @@ -1401,14 +1401,14 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "embed-resource" -version = "3.0.4" +version = "3.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0963f530273dc3022ab2bdc3fcd6d488e850256f2284a82b7413cb9481ee85dd" +checksum = "4c6d81016d6c977deefb2ef8d8290da019e27cc26167e102185da528e6c0ab38" dependencies = [ "cc", "memchr", "rustc_version", - "toml", + "toml 0.9.2", "vswhom", "winreg", ] @@ -2751,9 +2751,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" +checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df" dependencies = [ "base64 0.22.1", "bytes", @@ -3100,6 +3100,17 @@ dependencies = [ "wgpu-executor", ] +[[package]] +name = "io-uring" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "libc", +] + [[package]] name = "ipnet" version = "2.11.0" @@ -3388,9 +3399,9 @@ checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "libfuzzer-sys" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf78f52d400cf2d84a3a973a78a592b4adc535739e0a5597a0da6f0c357adc75" +checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404" dependencies = [ "arbitrary", "cc", @@ -3586,9 +3597,9 @@ checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "memmap2" -version = "0.9.5" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" +checksum = "483758ad303d734cec05e5c12b41d7e93e6a6390c5e9dae6bdeb7c1259012d28" dependencies = [ "libc", ] @@ -4364,7 +4375,7 @@ checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" [[package]] name = "peniko" version = "0.2.0" -source = "git+https://github.com/linebender/peniko.git?rev=d114c62#d114c6292dbcfb03e7360692198be423168a0edd" +source = "git+https://github.com/mTvare6/peniko.git?branch=luminance-clip#cd9aa45fe5a5c3070f18311cf33aad9cc83e5863" dependencies = [ "color 0.1.0", "kurbo", @@ -4612,13 +4623,13 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "plist" -version = "1.7.2" +version = "1.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d77244ce2d584cd84f6a15f86195b8c9b2a0dfbfd817c09e0464244091a58ed" +checksum = "3af6b589e163c5a788fab00ce0c0366f6efbb9959c2f9874b224936af7fce7e1" dependencies = [ "base64 0.22.1", "indexmap 2.10.0", - "quick-xml", + "quick-xml 0.38.0", "serde", "time", ] @@ -4674,7 +4685,7 @@ dependencies = [ "concurrent-queue", "hermit-abi", "pin-project-lite", - "rustix 1.0.7", + "rustix 1.0.8", "tracing", "windows-sys 0.59.0", ] @@ -4897,6 +4908,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "quick-xml" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8927b0664f5c5a98265138b7e3f90aa19a6b21353182469ace36d4ac527b7b1b" +dependencies = [ + "memchr", +] + [[package]] name = "quinn" version = "0.11.8" @@ -5348,9 +5368,9 @@ dependencies = [ [[package]] name = "rgb" -version = "0.8.50" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" +checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce" dependencies = [ "bytemuck", ] @@ -5429,22 +5449,22 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ "bitflags 2.9.1", "errno", "libc", "linux-raw-sys 0.9.4", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "rustls" -version = "0.23.28" +version = "0.23.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643" +checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1" dependencies = [ "once_cell", "ring", @@ -5466,9 +5486,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.3" +version = "0.103.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" dependencies = [ "ring", "rustls-pki-types", @@ -5552,9 +5572,9 @@ dependencies = [ [[package]] name = "schemars" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1375ba8ef45a6f15d83fa8748f1079428295d403d6ea991d09ab100155fbc06d" +checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" dependencies = [ "dyn-clone", "ref-cast", @@ -5744,6 +5764,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -5768,7 +5797,7 @@ dependencies = [ "indexmap 1.9.3", "indexmap 2.10.0", "schemars 0.9.0", - "schemars 1.0.3", + "schemars 1.0.4", "serde", "serde_derive", "serde_json", @@ -5833,9 +5862,9 @@ dependencies = [ [[package]] name = "shared_child" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2778001df1384cf20b6dc5a5a90f48da35539885edaaefd887f8d744e939c0b" +checksum = "1e362d9935bc50f019969e2f9ecd66786612daae13e8f277be7bfb66e8bed3f7" dependencies = [ "libc", "sigchld", @@ -5850,9 +5879,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "sigchld" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1219ef50fc0fdb04fcc243e6aa27f855553434ffafe4fa26554efb78b5b4bf89" +checksum = "47106eded3c154e70176fc83df9737335c94ce22f821c32d17ed1db1f83badb1" dependencies = [ "libc", "os_pipe", @@ -6275,7 +6304,7 @@ dependencies = [ "cfg-expr", "heck 0.5.0", "pkg-config", - "toml", + "toml 0.8.23", "version-compare", ] @@ -6403,7 +6432,7 @@ dependencies = [ "serde_json", "tauri-utils", "tauri-winres", - "toml", + "toml 0.8.23", "walkdir", ] @@ -6461,7 +6490,7 @@ dependencies = [ "serde", "serde_json", "tauri-utils", - "toml", + "toml 0.8.23", "walkdir", ] @@ -6483,7 +6512,7 @@ dependencies = [ "tauri-plugin", "tauri-utils", "thiserror 2.0.12", - "toml", + "toml 0.8.23", "url", ] @@ -6612,7 +6641,7 @@ dependencies = [ "serde_with", "swift-rs", "thiserror 2.0.12", - "toml", + "toml 0.8.23", "url", "urlpattern", "uuid", @@ -6627,7 +6656,7 @@ checksum = "e8d321dbc6f998d825ab3f0d62673e810c861aac2d0de2cc2c395328f1d113b4" dependencies = [ "embed-resource", "indexmap 2.10.0", - "toml", + "toml 0.8.23", ] [[package]] @@ -6639,7 +6668,7 @@ dependencies = [ "fastrand", "getrandom 0.3.3", "once_cell", - "rustix 1.0.7", + "rustix 1.0.8", "windows-sys 0.59.0", ] @@ -6817,15 +6846,17 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.45.1" +version = "1.46.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" +checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" dependencies = [ "backtrace", "bytes", + "io-uring", "libc", "mio", "pin-project-lite", + "slab", "socket2", "tokio-macros", "windows-sys 0.52.0", @@ -6882,11 +6913,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", - "serde_spanned", - "toml_datetime", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", "toml_edit 0.22.27", ] +[[package]] +name = "toml" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed0aee96c12fa71097902e0bb061a5e1ebd766a6636bb605ba401c45c1650eac" +dependencies = [ + "indexmap 2.10.0", + "serde", + "serde_spanned 1.0.0", + "toml_datetime 0.7.0", + "toml_parser", + "toml_writer", + "winnow 0.7.12", +] + [[package]] name = "toml_datetime" version = "0.6.11" @@ -6896,6 +6942,15 @@ dependencies = [ "serde", ] +[[package]] +name = "toml_datetime" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" +dependencies = [ + "serde", +] + [[package]] name = "toml_edit" version = "0.19.15" @@ -6903,7 +6958,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ "indexmap 2.10.0", - "toml_datetime", + "toml_datetime 0.6.11", "winnow 0.5.40", ] @@ -6914,7 +6969,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" dependencies = [ "indexmap 2.10.0", - "toml_datetime", + "toml_datetime 0.6.11", "winnow 0.5.40", ] @@ -6926,10 +6981,19 @@ checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap 2.10.0", "serde", - "serde_spanned", - "toml_datetime", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", "toml_write", - "winnow 0.7.11", + "winnow 0.7.12", +] + +[[package]] +name = "toml_parser" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97200572db069e74c512a14117b296ba0a80a30123fbbb5aa1f4a348f639ca30" +dependencies = [ + "winnow 0.7.12", ] [[package]] @@ -6938,6 +7002,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" +[[package]] +name = "toml_writer" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" + [[package]] name = "tower" version = "0.5.2" @@ -7273,7 +7343,7 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "vello" version = "0.3.0" -source = "git+https://github.com/linebender/vello.git?rev=3275ec8#3275ec85d831180be81820de06cca29a97a757f5" +source = "git+https://github.com/mTvare6/vello.git?branch=luminance-clip#cbe3d204cdbe3dd45716ffac6109da1390ec9536" dependencies = [ "bytemuck", "futures-intrusive", @@ -7291,7 +7361,7 @@ dependencies = [ [[package]] name = "vello_encoding" version = "0.3.0" -source = "git+https://github.com/linebender/vello.git?rev=3275ec8#3275ec85d831180be81820de06cca29a97a757f5" +source = "git+https://github.com/mTvare6/vello.git?branch=luminance-clip#cbe3d204cdbe3dd45716ffac6109da1390ec9536" dependencies = [ "bytemuck", "guillotiere", @@ -7303,7 +7373,7 @@ dependencies = [ [[package]] name = "vello_shaders" version = "0.3.0" -source = "git+https://github.com/linebender/vello.git?rev=3275ec8#3275ec85d831180be81820de06cca29a97a757f5" +source = "git+https://github.com/mTvare6/vello.git?branch=luminance-clip#cbe3d204cdbe3dd45716ffac6109da1390ec9536" dependencies = [ "bytemuck", "naga", @@ -7560,7 +7630,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "896fdafd5d28145fce7958917d69f2fd44469b1d4e861cb5961bcbeebc6d1484" dependencies = [ "proc-macro2", - "quick-xml", + "quick-xml 0.37.5", "quote", ] @@ -8413,9 +8483,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" dependencies = [ "memchr", ] @@ -8564,9 +8634,9 @@ checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" [[package]] name = "xml-rs" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62ce76d9b56901b19a74f19431b0d8b3bc7ca4ad685a746dfd78ca8f4fc6bda" +checksum = "6fd8403733700263c6eb89f192880191f1b83e332f7a20371ddcf421c4a337c7" [[package]] name = "xmlwriter" @@ -8713,9 +8783,9 @@ dependencies = [ [[package]] name = "zune-jpeg" -version = "0.4.18" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7384255a918371b5af158218d131530f694de9ad3815ebdd0453a940485cb0fa" +checksum = "2c9e525af0a6a658e031e95f14b7f889976b74a11ba0eca5a5fc9ac8a1c43a6a" dependencies = [ "zune-core", ] diff --git a/Cargo.toml b/Cargo.toml index 35c5e7040d..0adcac31ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -114,7 +114,7 @@ web-sys = { version = "=0.3.77", features = [ winit = "0.29" url = "2.5" tokio = { version = "1.29", features = ["fs", "macros", "io-std", "rt"] } -vello = { git = "https://github.com/linebender/vello.git", rev = "3275ec8" } # TODO switch back to stable when a release is made +vello = { git = "https://github.com/mTvare6/vello.git", branch = "luminance-clip" } # TODO switch back to stable when a release is made resvg = "0.44" usvg = "0.44" rand = { version = "0.9", default-features = false, features = ["std_rng"] } diff --git a/editor/src/messages/portfolio/spreadsheet/spreadsheet_message_handler.rs b/editor/src/messages/portfolio/spreadsheet/spreadsheet_message_handler.rs index 72c44c5975..684b7d1d76 100644 --- a/editor/src/messages/portfolio/spreadsheet/spreadsheet_message_handler.rs +++ b/editor/src/messages/portfolio/spreadsheet/spreadsheet_message_handler.rs @@ -300,6 +300,8 @@ impl InstanceLayout for Instances { TextButton::new(instance.instance.identifier()) .on_update(move |_| SpreadsheetMessage::PushToInstancePath { index }.into()) .widget_holder(), + // Add the mask information here. We'll simply display "Yes" or "No". + TextLabel::new(if instance.mask.is_some() { "Yes" } else { "No" }.to_string()).widget_holder(), TextLabel::new(format!( "Location: ({} px, {} px) — Rotation: {rotation:2}° — Scale: ({}x, {}x)", round(translation.x), @@ -314,7 +316,8 @@ impl InstanceLayout for Instances { }) .collect::>(); - rows.insert(0, column_headings(&["", "instance", "transform", "alpha_blending", "source_node_id"])); + // Add "mask" to the column headings + rows.insert(0, column_headings(&["", "instance", "mask", "transform", "alpha_blending", "source_node_id"])); let instances = vec![TextLabel::new("Instances:").widget_holder()]; vec![LayoutGroup::Row { widgets: instances }, LayoutGroup::Table { rows }] diff --git a/node-graph/gcore/src/graphic_element.rs b/node-graph/gcore/src/graphic_element.rs index 131c6c04b0..3fe51c0530 100644 --- a/node-graph/gcore/src/graphic_element.rs +++ b/node-graph/gcore/src/graphic_element.rs @@ -41,6 +41,7 @@ pub fn migrate_graphic_group<'de, D: serde::Deserializer<'de>>(deserializer: D) for (graphic_element, source_node_id) in old.elements { graphic_group_table.push(Instance { instance: graphic_element, + mask: None, transform: old.transform, alpha_blending: old.alpha_blending, source_node_id, @@ -56,6 +57,7 @@ pub fn migrate_graphic_group<'de, D: serde::Deserializer<'de>>(deserializer: D) for (graphic_element, source_node_id) in &instance.instance.elements { graphic_group_table.push(Instance { instance: graphic_element.clone(), + mask: None, transform: *instance.transform, alpha_blending: *instance.alpha_blending, source_node_id: *source_node_id, @@ -302,6 +304,7 @@ pub fn migrate_artboard_group<'de, D: serde::Deserializer<'de>>(deserializer: D) for (artboard, source_node_id) in artboard_group.artboards { table.push(Instance { instance: artboard, + mask: None, transform: DAffine2::IDENTITY, alpha_blending: AlphaBlending::default(), source_node_id, @@ -335,6 +338,7 @@ async fn layer( stack.push(Instance { instance: element, + mask: None, transform: DAffine2::IDENTITY, alpha_blending: AlphaBlending::default(), source_node_id, @@ -395,6 +399,7 @@ async fn flatten_group(_: impl Ctx, group: GraphicGroupTable, fully_flatten: boo _ => { output_group_table.push(Instance { instance: current_element, + mask: None, transform: *current_instance.transform, alpha_blending: *current_instance.alpha_blending, source_node_id: reference, @@ -433,6 +438,7 @@ async fn flatten_vector(_: impl Ctx, group: GraphicGroupTable) -> VectorDataTabl for current_element in vector_instance.instance_ref_iter() { output_group_table.push(Instance { instance: current_element.instance.clone(), + mask: None, transform: *current_instance.transform * *current_element.transform, alpha_blending: AlphaBlending { blend_mode: current_element.alpha_blending.blend_mode, @@ -497,6 +503,7 @@ async fn append_artboard(_ctx: impl Ctx, mut artboards: ArtboardGroupTable, artb artboards.push(Instance { instance: artboard, + mask: None, transform: DAffine2::IDENTITY, alpha_blending: AlphaBlending::default(), source_node_id: encapsulating_node_id, diff --git a/node-graph/gcore/src/instances.rs b/node-graph/gcore/src/instances.rs index 5b5be2a311..dcafa83f42 100644 --- a/node-graph/gcore/src/instances.rs +++ b/node-graph/gcore/src/instances.rs @@ -1,13 +1,17 @@ -use crate::AlphaBlending; use crate::uuid::NodeId; +use crate::{AlphaBlending, GraphicElement}; use dyn_any::StaticType; use glam::DAffine2; use std::hash::Hash; +pub type Mask = Option; + #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] pub struct Instances { #[serde(alias = "instances")] instance: Vec, + #[serde(default = "one_mask_default")] + mask: Vec, #[serde(default = "one_daffine2_default")] transform: Vec, #[serde(default = "one_alpha_blending_default")] @@ -20,6 +24,7 @@ impl Instances { pub fn new(instance: T) -> Self { Self { instance: vec![instance], + mask: vec![None], transform: vec![DAffine2::IDENTITY], alpha_blending: vec![AlphaBlending::default()], source_node_id: vec![None], @@ -28,6 +33,7 @@ impl Instances { pub fn push(&mut self, instance: Instance) { self.instance.push(instance.instance); + self.mask.push(instance.mask); self.transform.push(instance.transform); self.alpha_blending.push(instance.alpha_blending); self.source_node_id.push(instance.source_node_id); @@ -43,11 +49,13 @@ impl Instances { pub fn instance_iter(self) -> impl DoubleEndedIterator> { self.instance .into_iter() + .zip(self.mask) .zip(self.transform) .zip(self.alpha_blending) .zip(self.source_node_id) - .map(|(((instance, transform), alpha_blending), source_node_id)| Instance { + .map(|((((instance, mask), transform), alpha_blending), source_node_id)| Instance { instance, + mask, transform, alpha_blending, source_node_id, @@ -57,11 +65,13 @@ impl Instances { pub fn instance_ref_iter(&self) -> impl DoubleEndedIterator> + Clone { self.instance .iter() + .zip(self.mask.iter()) .zip(self.transform.iter()) .zip(self.alpha_blending.iter()) .zip(self.source_node_id.iter()) - .map(|(((instance, transform), alpha_blending), source_node_id)| InstanceRef { + .map(|((((instance, mask), transform), alpha_blending), source_node_id)| InstanceRef { instance, + mask, transform, alpha_blending, source_node_id, @@ -71,11 +81,13 @@ impl Instances { pub fn instance_mut_iter(&mut self) -> impl DoubleEndedIterator> { self.instance .iter_mut() + .zip(self.mask.iter_mut()) .zip(self.transform.iter_mut()) .zip(self.alpha_blending.iter_mut()) .zip(self.source_node_id.iter_mut()) - .map(|(((instance, transform), alpha_blending), source_node_id)| InstanceMut { + .map(|((((instance, mask), transform), alpha_blending), source_node_id)| InstanceMut { instance, + mask, transform, alpha_blending, source_node_id, @@ -89,6 +101,7 @@ impl Instances { Some(InstanceRef { instance: &self.instance[index], + mask: &self.mask[index], transform: &self.transform[index], alpha_blending: &self.alpha_blending[index], source_node_id: &self.source_node_id[index], @@ -102,6 +115,7 @@ impl Instances { Some(InstanceMut { instance: &mut self.instance[index], + mask: &mut self.mask[index], transform: &mut self.transform[index], alpha_blending: &mut self.alpha_blending[index], source_node_id: &mut self.source_node_id[index], @@ -121,6 +135,7 @@ impl Default for Instances { fn default() -> Self { Self { instance: Vec::new(), + mask: Vec::new(), transform: Vec::new(), alpha_blending: Vec::new(), source_node_id: Vec::new(), @@ -146,6 +161,9 @@ unsafe impl StaticType for Instances { type Static = Instances; } +fn one_mask_default() -> Vec { + vec![None] +} fn one_daffine2_default() -> Vec { vec![DAffine2::IDENTITY] } @@ -159,6 +177,7 @@ fn one_source_node_id_default() -> Vec> { #[derive(Copy, Clone, Debug, PartialEq)] pub struct InstanceRef<'a, T> { pub instance: &'a T, + pub mask: &'a Mask, pub transform: &'a DAffine2, pub alpha_blending: &'a AlphaBlending, pub source_node_id: &'a Option, @@ -171,6 +190,7 @@ impl InstanceRef<'_, T> { { Instance { instance: self.instance.clone(), + mask: self.mask.clone(), transform: *self.transform, alpha_blending: *self.alpha_blending, source_node_id: *self.source_node_id, @@ -181,14 +201,16 @@ impl InstanceRef<'_, T> { #[derive(Debug)] pub struct InstanceMut<'a, T> { pub instance: &'a mut T, + pub mask: &'a mut Mask, pub transform: &'a mut DAffine2, pub alpha_blending: &'a mut AlphaBlending, pub source_node_id: &'a mut Option, } -#[derive(Copy, Clone, Default, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[derive(Clone, Default, Debug, PartialEq, serde::Serialize, serde::Deserialize)] pub struct Instance { pub instance: T, + pub mask: Mask, pub transform: DAffine2, pub alpha_blending: AlphaBlending, pub source_node_id: Option, @@ -201,6 +223,7 @@ impl Instance { { Instance { instance: self.instance.into(), + mask: self.mask, transform: self.transform, alpha_blending: self.alpha_blending, source_node_id: self.source_node_id, @@ -210,6 +233,7 @@ impl Instance { pub fn to_instance_ref(&self) -> InstanceRef<'_, T> { InstanceRef { instance: &self.instance, + mask: &self.mask, transform: &self.transform, alpha_blending: &self.alpha_blending, source_node_id: &self.source_node_id, @@ -219,6 +243,7 @@ impl Instance { pub fn to_instance_mut(&mut self) -> InstanceMut<'_, T> { InstanceMut { instance: &mut self.instance, + mask: &mut self.mask, transform: &mut self.transform, alpha_blending: &mut self.alpha_blending, source_node_id: &mut self.source_node_id, @@ -228,6 +253,7 @@ impl Instance { pub fn to_table(self) -> Instances { Instances { instance: vec![self.instance], + mask: vec![self.mask], transform: vec![self.transform], alpha_blending: vec![self.alpha_blending], source_node_id: vec![self.source_node_id], diff --git a/node-graph/gcore/src/raster/image.rs b/node-graph/gcore/src/raster/image.rs index e93fe60ba2..6e0232825b 100644 --- a/node-graph/gcore/src/raster/image.rs +++ b/node-graph/gcore/src/raster/image.rs @@ -390,6 +390,7 @@ pub fn migrate_image_frame_instance<'de, D: serde::Deserializer<'de>>(deserializ }, FormatVersions::OldImageFrame(image_frame_with_transform_and_blending) => Instance { instance: Raster::new_cpu(image_frame_with_transform_and_blending.image), + mask: None, transform: image_frame_with_transform_and_blending.transform, alpha_blending: image_frame_with_transform_and_blending.alpha_blending, source_node_id: None, @@ -458,6 +459,7 @@ impl From> for Image { // for image_frame_instance in image_frame_table.instance_iter() { // result_table.push(Instance { // instance: image_frame_instance.instance, +// mask: image_frame_instance.mask, // transform: image_frame_instance.transform, // alpha_blending: image_frame_instance.alpha_blending, // source_node_id: image_frame_instance.source_node_id, diff --git a/node-graph/gcore/src/vector/vector_nodes.rs b/node-graph/gcore/src/vector/vector_nodes.rs index b3b47e48e7..f8515810a8 100644 --- a/node-graph/gcore/src/vector/vector_nodes.rs +++ b/node-graph/gcore/src/vector/vector_nodes.rs @@ -444,6 +444,7 @@ async fn round_corners( for source in source.instance_ref_iter() { let source_transform = *source.transform; let source_transform_inverse = source_transform.inverse(); + let source_mask = source.mask; let source = source.instance; let upstream_graphic_group = source.upstream_graphic_group.clone(); @@ -536,6 +537,7 @@ async fn round_corners( result_table.push(Instance { instance: result, + mask: source_mask.clone(), transform: source_transform, alpha_blending: Default::default(), source_node_id: None, @@ -679,6 +681,7 @@ async fn auto_tangents( for source in source.instance_ref_iter() { let transform = *source.transform; + let mask = source.mask.clone(); let alpha_blending = *source.alpha_blending; let source_node_id = *source.source_node_id; let source = source.instance; @@ -777,6 +780,7 @@ async fn auto_tangents( result_table.push(Instance { instance: result, + mask, transform, alpha_blending, source_node_id, diff --git a/node-graph/gpath-bool/src/lib.rs b/node-graph/gpath-bool/src/lib.rs index e94963298b..0441162d28 100644 --- a/node-graph/gpath-bool/src/lib.rs +++ b/node-graph/gpath-bool/src/lib.rs @@ -198,6 +198,7 @@ fn difference<'a>(vector_data: impl DoubleEndedIterator( _: impl Ctx, /// The image to be masked. - image: RasterDataTable, + #[implementations( + VectorDataTable, + VectorDataTable, + VectorDataTable, + RasterDataTable, + RasterDataTable, + RasterDataTable, + GraphicGroupTable, + GraphicGroupTable, + GraphicGroupTable + )] + mut image: Instances, /// The stencil to be used for masking. #[expose] - stencil: RasterDataTable, -) -> RasterDataTable { + #[implementations( + VectorDataTable, + RasterDataTable, + GraphicGroupTable, + VectorDataTable, + RasterDataTable, + GraphicGroupTable, + VectorDataTable, + RasterDataTable, + GraphicGroupTable + )] + stencil: Instances, +) -> Instances +where + Instances: Into + Clone, +{ + for instance in image.instance_mut_iter() { + *instance.mask = Some(stencil.clone().into()); + } + image +} + +// TODO: Use as in-place raster modifier +fn _mask_lambda(image: RasterDataTable, stencil: RasterDataTable) -> RasterDataTable { // TODO: Support multiple stencil instances let Some(stencil_instance) = stencil.instance_iter().next() else { // No stencil provided so we return the original image diff --git a/node-graph/gsvg-renderer/src/renderer.rs b/node-graph/gsvg-renderer/src/renderer.rs index 70f7c18206..ecb42905ae 100644 --- a/node-graph/gsvg-renderer/src/renderer.rs +++ b/node-graph/gsvg-renderer/src/renderer.rs @@ -271,6 +271,10 @@ impl GraphicElementRendered for GraphicGroupTable { attributes.push(mask_type.to_attribute(), selector); } + + if let Some(mask) = instance.mask { + attributes.push_mask(mask, render_params.for_clipper()); + } }, |render| { instance.instance.render_svg(render, render_params); @@ -315,6 +319,20 @@ impl GraphicElementRendered for GraphicGroupTable { } } + let mut masked = false; + if let Some(mask) = instance.mask { + let bounds = mask.bounding_box(transform, true); + + if let Some(bounds) = bounds { + masked = true; + let rect = vello::kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y); + + scene.push_layer(peniko::Mix::Normal, 1., kurbo::Affine::IDENTITY, &rect); + mask.render_to_vello(scene, transform, context, &render_params.for_clipper()); + scene.push_layer(peniko::BlendMode::new(peniko::Mix::LuminanceClip, peniko::Compose::SrcIn), 1., kurbo::Affine::IDENTITY, &rect); + } + } + let next_clips = iter.peek().is_some_and(|next_instance| next_instance.instance.had_clip_enabled()); if next_clips && mask_instance_state.is_none() { mask_instance_state = Some((instance.instance, transform)); @@ -349,6 +367,11 @@ impl GraphicElementRendered for GraphicGroupTable { instance.instance.render_to_vello(scene, transform, context, render_params); } + if masked { + scene.pop_layer(); + scene.pop_layer(); + } + if layer { scene.pop_layer(); } @@ -451,6 +474,7 @@ impl GraphicElementRendered for VectorDataTable { vector_row.push(Instance { instance: fill_instance, + mask: None, alpha_blending: *instance.alpha_blending, transform: *instance.transform, source_node_id: None, @@ -498,6 +522,11 @@ impl GraphicElementRendered for VectorDataTable { let selector = format!("url(#{id})"); attributes.push(mask_type.to_attribute(), selector); } + + if let Some(mask) = instance.mask { + attributes.push_mask(mask, render_params.for_clipper()); + } + attributes.push_val(fill_and_stroke); let factor = if render_params.for_mask { 1. } else { instance.alpha_blending.fill }; @@ -554,6 +583,20 @@ impl GraphicElementRendered for VectorDataTable { ); } + let mut masked = false; + if let Some(mask) = instance.mask { + let bounds = mask.bounding_box(element_transform, true); + + if let Some(bounds) = bounds { + masked = true; + let rect = vello::kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y); + + scene.push_layer(peniko::Mix::Normal, 1., kurbo::Affine::IDENTITY, &rect); + mask.render_to_vello(scene, element_transform, _context, &render_params.for_clipper()); + scene.push_layer(peniko::BlendMode::new(peniko::Mix::LuminanceClip, peniko::Compose::SrcIn), 1., kurbo::Affine::IDENTITY, &rect); + } + } + let can_draw_aligned_stroke = instance.instance.style.stroke().is_some_and(|stroke| stroke.has_renderable_stroke() && stroke.align.is_not_centered()) && instance.instance.stroke_bezier_paths().all(|path| path.closed()); @@ -571,6 +614,7 @@ impl GraphicElementRendered for VectorDataTable { vector_data.push(Instance { instance: fill_instance, + mask: None, alpha_blending: *instance.alpha_blending, transform: *instance.transform, source_node_id: None, @@ -714,6 +758,11 @@ impl GraphicElementRendered for VectorDataTable { scene.pop_layer(); } + if masked { + scene.pop_layer(); + scene.pop_layer(); + } + // If we pushed a layer for opacity or a blend mode, we need to pop it if layer { scene.pop_layer(); @@ -952,29 +1001,40 @@ impl GraphicElementRendered for RasterDataTable { base64::engine::general_purpose::STANDARD.encode_string(output, &mut base64_string); base64_string }); - render.leaf_tag("image", |attributes| { - attributes.push("width", 1.to_string()); - attributes.push("height", 1.to_string()); - attributes.push("preserveAspectRatio", "none"); - attributes.push("href", base64_string); - let matrix = format_transform_matrix(transform); - if !matrix.is_empty() { - attributes.push("transform", matrix); - } - let factor = if render_params.for_mask { 1. } else { instance.alpha_blending.fill }; - let opacity = instance.alpha_blending.opacity * factor; - if opacity < 1. { - attributes.push("opacity", opacity.to_string()); - } - if instance.alpha_blending.blend_mode != BlendMode::default() { - attributes.push("style", instance.alpha_blending.blend_mode.render()); - } - }); + + let render_image = |render: &mut SvgRender| { + render.leaf_tag("image", |attributes| { + attributes.push("width", 1.to_string()); + attributes.push("height", 1.to_string()); + attributes.push("preserveAspectRatio", "none"); + attributes.push("href", base64_string); + let matrix = format_transform_matrix(transform); + if !matrix.is_empty() { + attributes.push("transform", matrix); + } + let factor = if render_params.for_mask { 1. } else { instance.alpha_blending.fill }; + let opacity = instance.alpha_blending.opacity * factor; + if opacity < 1. { + attributes.push("opacity", opacity.to_string()); + } + if instance.alpha_blending.blend_mode != BlendMode::default() { + attributes.push("style", instance.alpha_blending.blend_mode.render()); + } + }) + }; + + // Unlike path and g elements, the mask attribute of image element is broken and + // behaves differently across different browsers. And so we use g to have it work correctly. + if let Some(mask) = instance.mask { + render.parent_tag("g", |attributes| attributes.push_mask(mask, render_params.for_clipper()), render_image); + } else { + render_image(render) + } } } #[cfg(feature = "vello")] - fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, _: &mut RenderContext, _render_params: &RenderParams) { + fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, _context: &mut RenderContext, render_params: &RenderParams) { use vello::peniko; for instance in self.instance_ref_iter() { @@ -985,7 +1045,26 @@ impl GraphicElementRendered for RasterDataTable { let image = peniko::Image::new(image.to_flat_u8().0.into(), peniko::Format::Rgba8, image.width, image.height).with_extend(peniko::Extend::Repeat); let transform = transform * *instance.transform * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64)); + let mut masked = false; + if let Some(mask) = instance.mask { + let bounds = mask.bounding_box(transform, true); + + if let Some(bounds) = bounds { + masked = true; + let rect = vello::kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y); + + scene.push_layer(peniko::Mix::Normal, 1., kurbo::Affine::IDENTITY, &rect); + mask.render_to_vello(scene, transform, _context, &render_params.for_clipper()); + scene.push_layer(peniko::BlendMode::new(peniko::Mix::LuminanceClip, peniko::Compose::SrcIn), 1., kurbo::Affine::IDENTITY, &rect); + } + } + scene.draw_image(&image, kurbo::Affine::new(transform.to_cols_array())); + + if masked { + scene.pop_layer(); + scene.pop_layer(); + } } } @@ -1268,4 +1347,17 @@ impl SvgRenderAttrs<'_> { pub fn push_val(&mut self, value: impl Into) { self.0.svg.push(value.into()); } + fn push_mask(&mut self, mask: &GraphicElement, render_params: RenderParams) { + let uuid = generate_uuid(); + let mut svg = SvgRender::new(); + mask.render_svg(&mut svg, &render_params); + + let id = format!("mask-{}", uuid); + write!(&mut self.0.svg_defs, r##"{}"##, svg.svg_defs).unwrap(); + write!(&mut self.0.svg_defs, r##"{}"##, svg.svg.to_svg_string()).unwrap(); + + let selector = format!("url(#{id})"); + + self.push("mask", selector); + } }