diff --git a/.nix/flake.lock b/.nix/flake.lock index 5c11b23de1..ae901cc0eb 100644 --- a/.nix/flake.lock +++ b/.nix/flake.lock @@ -65,11 +65,11 @@ ] }, "locked": { - "lastModified": 1748399823, - "narHash": "sha256-kahD8D5hOXOsGbNdoLLnqCL887cjHkx98Izc37nDjlA=", + "lastModified": 1753238793, + "narHash": "sha256-jmQeEpgX+++MEgrcikcwoSiI7vDZWLP0gci7XiWb9uQ=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "d68a69dc71bc19beb3479800392112c2f6218159", + "rev": "0ad7ab4ca8e83febf147197e65c006dff60623ab", "type": "github" }, "original": { diff --git a/.nix/flake.nix b/.nix/flake.nix index fc0cb6cbe4..12192fd2b1 100644 --- a/.nix/flake.nix +++ b/.nix/flake.nix @@ -36,34 +36,50 @@ pkgs-unstable = import nixpkgs-unstable { inherit system overlays; }; - + rustc-wasm = pkgs.rust-bin.stable.latest.default.override { targets = [ "wasm32-unknown-unknown" ]; extensions = [ "rust-src" "rust-analyzer" "clippy" "cargo" ]; }; + libcef = pkgs.libcef.overrideAttrs (finalAttrs: previousAttrs: { + version = "138.0.26"; + gitRevision = "84f2d27"; + chromiumVersion = "138.0.7204.158"; + srcHash = "sha256-d9jQJX7rgdoHfROD3zmOdMSesRdKE3slB5ZV+U2wlbQ="; + + __intentionallyOverridingVersion = true; + + postInstall = '' + strip $out/lib/* + ''; + }); + + libcefPath = pkgs.runCommand "libcef-path" {} '' + mkdir -p $out + + ln -s ${libcef}/include $out/include + find ${libcef}/lib -type f -name "*" -exec ln -s {} $out/ \; + find ${libcef}/libexec -type f -name "*" -exec ln -s {} $out/ \; + cp -r ${libcef}/share/cef/* $out/ + + echo '${builtins.toJSON { + type = "minimal"; + name = builtins.baseNameOf libcef.src.url; + sha1 = ""; + }}' > $out/archive.json + ''; + # Shared build inputs - system libraries that need to be in LD_LIBRARY_PATH buildInputs = with pkgs; [ # System libraries + wayland + wayland.dev openssl vulkan-loader mesa libraw - - - # Tauri dependencies: keep in sync with https://v2.tauri.app/start/prerequisites/#system-dependencies (under the NixOS tab) - at-spi2-atk - atkmm - cairo - gdk-pixbuf - glib - gtk3 - harfbuzz - librsvg - libsoup_3 - pango - webkitgtk_4_1 - openssl + libGL ]; # Development tools that don't need to be in LD_LIBRARY_PATH @@ -88,12 +104,11 @@ cargo-watch cargo-nextest cargo-expand - + # Profiling tools gnuplot samply cargo-flamegraph - ]; in { @@ -101,10 +116,9 @@ devShells.default = pkgs.mkShell { packages = buildInputs ++ buildTools ++ devTools; - LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath buildInputs; - GIO_MODULE_DIR="${pkgs.glib-networking}/lib/gio/modules/"; + LD_LIBRARY_PATH = "${pkgs.lib.makeLibraryPath buildInputs}:${libcefPath}"; + CEF_PATH = libcefPath; XDG_DATA_DIRS="${pkgs.gsettings-desktop-schemas}/share/gsettings-schemas/${pkgs.gsettings-desktop-schemas.name}:${pkgs.gtk3}/share/gsettings-schemas/${pkgs.gtk3.name}:$XDG_DATA_DIRS"; - shellHook = '' alias cargo='mold --run cargo' diff --git a/Cargo.lock b/Cargo.lock index 5487e27d28..2d33ed756e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -70,21 +70,6 @@ dependencies = [ "equator", ] -[[package]] -name = "alloc-no-stdlib" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" - -[[package]] -name = "alloc-stdlib" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" -dependencies = [ - "alloc-no-stdlib", -] - [[package]] name = "allocator-api2" version = "0.2.21" @@ -93,9 +78,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "android-activity" -version = "0.5.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee91c0c2905bae44f84bfa4e044536541df26b7703fd0888deeb9060fcc44289" +checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" dependencies = [ "android-properties", "bitflags 2.9.1", @@ -105,9 +90,9 @@ dependencies = [ "jni-sys", "libc", "log", - "ndk 0.8.0", + "ndk", "ndk-context", - "ndk-sys 0.5.0+25.2.9519653", + "ndk-sys 0.6.0+11769913", "num_enum", "thiserror 1.0.69", ] @@ -236,30 +221,7 @@ version = "0.38.0+1.3.281" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" dependencies = [ - "libloading 0.8.8", -] - -[[package]] -name = "atk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" -dependencies = [ - "atk-sys", - "glib", - "libc", -] - -[[package]] -name = "atk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", + "libloading", ] [[package]] @@ -290,67 +252,13 @@ 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", ] -[[package]] -name = "axum" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" -dependencies = [ - "axum-core", - "bytes", - "form_urlencoded", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-util", - "itoa", - "matchit", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "rustversion", - "serde", - "serde_json", - "serde_path_to_error", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tower", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "axum-core" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "http-body-util", - "mime", - "pin-project-lite", - "rustversion", - "sync_wrapper", - "tower-layer", - "tower-service", - "tracing", -] - [[package]] name = "backtrace" version = "0.3.75" @@ -465,25 +373,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "block-sys" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae85a0696e7ea3b835a453750bf002770776609115e6d25c6d2ff28a8200f7e7" -dependencies = [ - "objc-sys", -] - -[[package]] -name = "block2" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15b55663a85f33501257357e6421bb33e769d5c9ffb5ba0921c975a123e35e68" -dependencies = [ - "block-sys", - "objc2 0.4.1", -] - [[package]] name = "block2" version = "0.5.1" @@ -493,36 +382,6 @@ dependencies = [ "objc2 0.5.2", ] -[[package]] -name = "block2" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "340d2f0bdb2a43c1d3cd40513185b2bd7def0aa1052f956455114bc98f82dcf2" -dependencies = [ - "objc2 0.6.1", -] - -[[package]] -name = "brotli" -version = "8.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9991eea70ea4f293524138648e41ee89b0b2b12ddef3b255effa43c8056e0e0d" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", -] - -[[package]] -name = "brotli-decompressor" -version = "5.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", -] - [[package]] name = "built" version = "0.7.7" @@ -572,40 +431,21 @@ name = "bytes" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" -dependencies = [ - "serde", -] [[package]] -name = "cairo-rs" -version = "0.18.5" +name = "bzip2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" +checksum = "bea8dcd42434048e4f7a304411d9273a411f647446c1234a65ce0554923f4cff" dependencies = [ - "bitflags 2.9.1", - "cairo-sys-rs", - "glib", - "libc", - "once_cell", - "thiserror 1.0.69", -] - -[[package]] -name = "cairo-sys-rs" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" -dependencies = [ - "glib-sys", - "libc", - "system-deps", + "libbz2-rs-sys", ] [[package]] name = "calloop" -version = "0.12.4" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fba7adb4dd5aa98e5553510223000e7148f621165ec5f9acd7113f6ca4995298" +checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" dependencies = [ "bitflags 2.9.1", "log", @@ -617,9 +457,9 @@ dependencies = [ [[package]] name = "calloop-wayland-source" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f0ea9b9476c7fad82841a8dbb380e2eae480c21910feba80725b46931ed8f02" +checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" dependencies = [ "calloop", "rustix 0.38.44", @@ -627,48 +467,6 @@ dependencies = [ "wayland-client", ] -[[package]] -name = "camino" -version = "1.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0da45bc31171d8d6960122e222a67740df867c1dd53b4d51caa297084c185cab" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo-platform" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo_metadata" -version = "0.19.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" -dependencies = [ - "camino", - "cargo-platform", - "semver", - "serde", - "serde_json", - "thiserror 2.0.12", -] - -[[package]] -name = "cargo_toml" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02260d489095346e5cafd04dea8e8cb54d1d74fcd759022a9b72986ebe9a1257" -dependencies = [ - "serde", - "toml", -] - [[package]] name = "cast" version = "0.3.0" @@ -677,32 +475,43 @@ 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", "shlex", ] +[[package]] +name = "cef" +version = "138.5.0+138.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bfa138b538b29584b02e990a631ef44261d9a70d35a77ce4ef624410046fe91" +dependencies = [ + "cef-dll-sys", + "windows-sys 0.60.2", +] + +[[package]] +name = "cef-dll-sys" +version = "138.5.0+138.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "658450855d0ef25af50420b95fbdfacb5df17c7eb20a1615ec995470c26ed643" +dependencies = [ + "anyhow", + "cmake", + "download-cef", + "serde_json", +] + [[package]] name = "cesu8" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" -[[package]] -name = "cfb" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" -dependencies = [ - "byteorder", - "fnv", - "uuid", -] - [[package]] name = "cfg-expr" version = "0.15.8" @@ -719,12 +528,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" -[[package]] -name = "cfg_aliases" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" - [[package]] name = "cfg_aliases" version = "0.2.1" @@ -741,7 +544,6 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits", - "serde", "wasm-bindgen", "windows-link", ] @@ -775,9 +577,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 +587,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,11 +599,11 @@ 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", + "heck", "proc-macro2", "quote", "syn 2.0.104", @@ -814,21 +616,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] -name = "codespan-reporting" -version = "0.11.1" +name = "cmake" +version = "0.1.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" dependencies = [ - "termcolor", - "unicode-width", + "cc", ] [[package]] -name = "color" -version = "0.1.0" -source = "git+https://github.com/linebender/color.git?rev=a4fa61aff6c3f292b729dc409e7832e5f0166e4a#a4fa61aff6c3f292b729dc409e7832e5f0166e4a" +name = "codespan-reporting" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81" dependencies = [ "serde", + "termcolor", + "unicode-width 0.1.14", ] [[package]] @@ -879,10 +683,17 @@ dependencies = [ ] [[package]] -name = "convert_case" -version = "0.4.0" +name = "console" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +checksum = "2e09ced7ebbccb63b4c65413d821f2e00ce54c5ca4514ddc6b3c892fdbcbc69d" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "unicode-width 0.2.1", + "windows-sys 0.60.2", +] [[package]] name = "convert_case" @@ -913,8 +724,8 @@ dependencies = [ "cookie", "document-features", "idna", + "indexmap", "log", - "publicsuffix", "serde", "serde_derive", "serde_json", @@ -932,16 +743,6 @@ dependencies = [ "libc", ] -[[package]] -name = "core-foundation" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -955,21 +756,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" dependencies = [ "bitflags 1.3.2", - "core-foundation 0.9.4", - "core-graphics-types 0.1.3", - "foreign-types 0.5.0", - "libc", -] - -[[package]] -name = "core-graphics" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" -dependencies = [ - "bitflags 2.9.1", - "core-foundation 0.10.1", - "core-graphics-types 0.2.0", + "core-foundation", + "core-graphics-types", "foreign-types 0.5.0", "libc", ] @@ -981,18 +769,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" dependencies = [ "bitflags 1.3.2", - "core-foundation 0.9.4", - "libc", -] - -[[package]] -name = "core-graphics-types" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" -dependencies = [ - "bitflags 2.9.1", - "core-foundation 0.10.1", + "core-foundation", "libc", ] @@ -1016,9 +793,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", ] @@ -1059,15 +836,6 @@ dependencies = [ "itertools 0.10.5", ] -[[package]] -name = "crossbeam-channel" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "crossbeam-deque" version = "0.8.6" @@ -1109,33 +877,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "cssparser" -version = "0.29.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93d03419cb5950ccfd3daf3ff1c7a36ace64609a1a8746d493df1ca0afde0fa" -dependencies = [ - "cssparser-macros", - "dtoa-short", - "itoa", - "matches", - "phf 0.10.1", - "proc-macro2", - "quote", - "smallvec", - "syn 1.0.109", -] - -[[package]] -name = "cssparser-macros" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" -dependencies = [ - "quote", - "syn 2.0.104", -] - [[package]] name = "ctor" version = "0.2.9" @@ -1151,40 +892,8 @@ name = "cursor-icon" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" - -[[package]] -name = "darling" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.104", -] - -[[package]] -name = "darling_macro" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" -dependencies = [ - "darling_core", - "quote", - "syn 2.0.104", + "serde", ] [[package]] @@ -1200,7 +909,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ "powerfmt", - "serde", ] [[package]] @@ -1214,19 +922,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "derive_more" -version = "0.99.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" -dependencies = [ - "convert_case 0.4.0", - "proc-macro2", - "quote", - "rustc_version", - "syn 2.0.104", -] - [[package]] name = "diff" version = "0.1.13" @@ -1243,43 +938,12 @@ dependencies = [ "crypto-common", ] -[[package]] -name = "dirs" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys 0.60.2", -] - [[package]] name = "dispatch" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" -[[package]] -name = "dispatch2" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" -dependencies = [ - "bitflags 2.9.1", - "objc2 0.6.1", -] - [[package]] name = "displaydoc" version = "0.2.5" @@ -1297,30 +961,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "libloading 0.8.8", -] - -[[package]] -name = "dlopen2" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1297103d2bbaea85724fcee6294c2d50b1081f9ad47d0f6f6f61eda65315a6" -dependencies = [ - "dlopen2_derive", - "libc", - "once_cell", - "winapi", -] - -[[package]] -name = "dlopen2_derive" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "788160fb30de9cdd857af31c6a2675904b16ece8fc2737b2c7127ba368c9d0f4" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", + "libloading", ] [[package]] @@ -1338,6 +979,25 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" +[[package]] +name = "download-cef" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eecc63fbfbe85b1767a8eeb4118408ee4452280c069b4c73301cf2efda4db0d6" +dependencies = [ + "bzip2", + "clap", + "indicatif", + "regex", + "semver", + "serde", + "serde_json", + "sha1_smol", + "tar", + "thiserror 2.0.12", + "ureq", +] + [[package]] name = "dpi" version = "0.1.2" @@ -1347,27 +1007,6 @@ dependencies = [ "serde", ] -[[package]] -name = "dtoa" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04" - -[[package]] -name = "dtoa-short" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" -dependencies = [ - "dtoa", -] - -[[package]] -name = "dunce" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" - [[package]] name = "dyn-any" version = "0.3.1" @@ -1387,12 +1026,6 @@ dependencies = [ "syn 2.0.104", ] -[[package]] -name = "dyn-clone" -version = "1.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" - [[package]] name = "either" version = "1.15.0" @@ -1400,24 +1033,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] -name = "embed-resource" -version = "3.0.4" +name = "encode_unicode" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0963f530273dc3022ab2bdc3fcd6d488e850256f2284a82b7413cb9481ee85dd" -dependencies = [ - "cc", - "memchr", - "rustc_version", - "toml", - "vswhom", - "winreg", -] - -[[package]] -name = "embed_plist" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "encoding_rs" @@ -1477,16 +1096,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" -[[package]] -name = "erased-serde" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7" -dependencies = [ - "serde", - "typeid", -] - [[package]] name = "errno" version = "0.3.13" @@ -1556,21 +1165,17 @@ dependencies = [ ] [[package]] -name = "field-offset" -version = "0.3.6" +name = "filetime" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" dependencies = [ - "memoffset", - "rustc_version", + "cfg-if", + "libc", + "libredox", + "windows-sys 0.59.0", ] -[[package]] -name = "fixedbitset" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" - [[package]] name = "fixedbitset" version = "0.5.7" @@ -1605,15 +1210,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" -[[package]] -name = "font-types" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa6a5e5a77b5f3f7f9e32879f484aa5b3632ddfbe568a16266c904a6f32cdaf" -dependencies = [ - "bytemuck", -] - [[package]] name = "font-types" version = "0.9.0" @@ -1664,18 +1260,18 @@ checksum = "39f97079e1293b8c1e9fb03a2875d328bd2ee8f3b95ce62959c0acc04049c708" dependencies = [ "bytemuck", "fontconfig-cache-parser", - "hashbrown 0.15.4", + "hashbrown", "icu_locid", "memmap2", "objc2 0.6.1", "objc2-core-foundation", "objc2-core-text", "objc2-foundation 0.3.1", - "peniko 0.4.0", + "peniko", "read-fonts 0.29.3", "roxmltree", "smallvec", - "windows 0.58.0", + "windows", "windows-core 0.58.0", ] @@ -1730,16 +1326,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "futf" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" -dependencies = [ - "mac", - "new_debug_unreachable", -] - [[package]] name = "futures" version = "0.3.31" @@ -1840,114 +1426,6 @@ dependencies = [ "slab", ] -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - -[[package]] -name = "gdk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" -dependencies = [ - "cairo-rs", - "gdk-pixbuf", - "gdk-sys", - "gio", - "glib", - "libc", - "pango", -] - -[[package]] -name = "gdk-pixbuf" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" -dependencies = [ - "gdk-pixbuf-sys", - "gio", - "glib", - "libc", - "once_cell", -] - -[[package]] -name = "gdk-pixbuf-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" -dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "gdk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" -dependencies = [ - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "pkg-config", - "system-deps", -] - -[[package]] -name = "gdkwayland-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" -dependencies = [ - "gdk-sys", - "glib-sys", - "gobject-sys", - "libc", - "pkg-config", - "system-deps", -] - -[[package]] -name = "gdkx11" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe" -dependencies = [ - "gdk", - "gdkx11-sys", - "gio", - "glib", - "libc", - "x11", -] - -[[package]] -name = "gdkx11-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" -dependencies = [ - "gdk-sys", - "glib-sys", - "libc", - "system-deps", - "x11", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -1968,17 +1446,6 @@ dependencies = [ "windows-targets 0.48.5", ] -[[package]] -name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", -] - [[package]] name = "getrandom" version = "0.2.16" @@ -2022,38 +1489,6 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" -[[package]] -name = "gio" -version = "0.18.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "gio-sys", - "glib", - "libc", - "once_cell", - "pin-project-lite", - "smallvec", - "thiserror 1.0.69", -] - -[[package]] -name = "gio-sys" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", - "winapi", -] - [[package]] name = "gl_generator" version = "0.14.0" @@ -2074,53 +1509,6 @@ dependencies = [ "serde", ] -[[package]] -name = "glib" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" -dependencies = [ - "bitflags 2.9.1", - "futures-channel", - "futures-core", - "futures-executor", - "futures-task", - "futures-util", - "gio-sys", - "glib-macros", - "glib-sys", - "gobject-sys", - "libc", - "memchr", - "once_cell", - "smallvec", - "thiserror 1.0.69", -] - -[[package]] -name = "glib-macros" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" -dependencies = [ - "heck 0.4.1", - "proc-macro-crate 2.0.0", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "glib-sys" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" -dependencies = [ - "libc", - "system-deps", -] - [[package]] name = "glob" version = "0.3.2" @@ -2129,9 +1517,9 @@ checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "glow" -version = "0.14.2" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51fa363f025f5c111e03f13eda21162faeacb6911fe8caa0c0349f9cf0c4483" +checksum = "c5e5ea60d70410161c8bf5da3fdfeaa1c72ed2c15f8bbb9d19fe3a4fad085f08" dependencies = [ "js-sys", "slotmap", @@ -2148,17 +1536,6 @@ dependencies = [ "gl_generator", ] -[[package]] -name = "gobject-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" -dependencies = [ - "glib-sys", - "libc", - "system-deps", -] - [[package]] name = "gpu-alloc" version = "0.6.0" @@ -2187,7 +1564,7 @@ dependencies = [ "log", "presser", "thiserror 1.0.69", - "windows 0.58.0", + "windows", ] [[package]] @@ -2198,7 +1575,7 @@ checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" dependencies = [ "bitflags 2.9.1", "gpu-descriptor-types", - "hashbrown 0.15.4", + "hashbrown", ] [[package]] @@ -2419,18 +1796,17 @@ dependencies = [ name = "graphite-desktop" version = "0.1.0" dependencies = [ - "axum", - "chrono", - "fern", - "futures", + "base64 0.22.1", + "bytemuck", + "cef", "graphite-editor", - "log", - "ron", - "tauri", - "tauri-build", - "tauri-plugin-http", - "tauri-plugin-shell", - "tokio", + "include_dir", + "pollster", + "thiserror 2.0.12", + "tracing", + "tracing-subscriber", + "wgpu", + "winit", ] [[package]] @@ -2497,58 +1873,6 @@ dependencies = [ "wgpu", ] -[[package]] -name = "gtk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" -dependencies = [ - "atk", - "cairo-rs", - "field-offset", - "futures-channel", - "gdk", - "gdk-pixbuf", - "gio", - "glib", - "gtk-sys", - "gtk3-macros", - "libc", - "pango", - "pkg-config", -] - -[[package]] -name = "gtk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" -dependencies = [ - "atk-sys", - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gdk-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "system-deps", -] - -[[package]] -name = "gtk3-macros" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" -dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.104", -] - [[package]] name = "guillotiere" version = "0.6.2" @@ -2571,7 +1895,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.10.0", + "indexmap", "slab", "tokio", "tokio-util", @@ -2587,15 +1911,10 @@ dependencies = [ "bytemuck", "cfg-if", "crunchy", + "num-traits", "serde", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "hashbrown" version = "0.15.4" @@ -2607,12 +1926,6 @@ dependencies = [ "foldhash", ] -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - [[package]] name = "heck" version = "0.5.0" @@ -2625,30 +1938,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - [[package]] name = "hexf-parse" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" -[[package]] -name = "html5ever" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c" -dependencies = [ - "log", - "mac", - "markup5ever", - "match_token", -] - [[package]] name = "http" version = "1.3.1" @@ -2689,12 +1984,6 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - [[package]] name = "hyper" version = "1.6.0" @@ -2708,7 +1997,6 @@ dependencies = [ "http", "http-body", "httparse", - "httpdate", "itoa", "pin-project-lite", "smallvec", @@ -2730,7 +2018,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots", + "webpki-roots 1.0.1", ] [[package]] @@ -2751,9 +2039,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", @@ -2833,27 +2121,6 @@ dependencies = [ "cc", ] -[[package]] -name = "ico" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc50b891e4acf8fe0e71ef88ec43ad82ee07b3810ad09de10f1d01f072ed4b98" -dependencies = [ - "byteorder", - "png", -] - -[[package]] -name = "icrate" -version = "0.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d3aaff8a54577104bafdf686ff18565c3b6903ca5782a2026ef06e2c7aa319" -dependencies = [ - "block2 0.3.0", - "dispatch", - "objc2 0.4.1", -] - [[package]] name = "icu_collections" version = "2.0.0" @@ -2952,12 +2219,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - [[package]] name = "idna" version = "1.0.3" @@ -3035,14 +2296,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408" [[package]] -name = "indexmap" -version = "1.9.3" +name = "include_dir" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", + "include_dir_macros", +] + +[[package]] +name = "include_dir_macros" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" +dependencies = [ + "proc-macro2", + "quote", ] [[package]] @@ -3052,8 +2321,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", - "hashbrown 0.15.4", - "serde", + "hashbrown", +] + +[[package]] +name = "indicatif" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a646d946d06bedbbc4cac4c218acf4bbf2d87757a784857025f4d447e4e1cd" +dependencies = [ + "console", + "portable-atomic", + "unicode-width 0.2.1", + "unit-prefix", + "web-time", ] [[package]] @@ -3062,15 +2343,6 @@ version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" -[[package]] -name = "infer" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" -dependencies = [ - "cfb", -] - [[package]] name = "interpolate_name" version = "0.2.4" @@ -3100,6 +2372,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" @@ -3116,15 +2399,6 @@ dependencies = [ "serde", ] -[[package]] -name = "is-docker" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" -dependencies = [ - "once_cell", -] - [[package]] name = "is-terminal" version = "0.4.16" @@ -3136,16 +2410,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "is-wsl" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" -dependencies = [ - "is-docker", - "once_cell", -] - [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -3176,29 +2440,6 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" -[[package]] -name = "javascriptcore-rs" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" -dependencies = [ - "bitflags 1.3.2", - "glib", - "javascriptcore-rs-sys", -] - -[[package]] -name = "javascriptcore-rs-sys" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - [[package]] name = "jiff" version = "0.2.15" @@ -3271,39 +2512,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "json-patch" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" -dependencies = [ - "jsonptr", - "serde", - "serde_json", - "thiserror 1.0.69", -] - -[[package]] -name = "jsonptr" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" -dependencies = [ - "serde", - "serde_json", -] - -[[package]] -name = "keyboard-types" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" -dependencies = [ - "bitflags 2.9.1", - "serde", - "unicode-segmentation", -] - [[package]] name = "khronos-egl" version = "6.0.0" @@ -3311,7 +2519,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" dependencies = [ "libc", - "libloading 0.8.8", + "libloading", "pkg-config", ] @@ -3321,18 +2529,6 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" -[[package]] -name = "kuchikiki" -version = "0.8.8-speedreader" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2" -dependencies = [ - "cssparser", - "html5ever", - "indexmap 2.10.0", - "selectors", -] - [[package]] name = "kurbo" version = "0.11.2" @@ -3357,28 +2553,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] -name = "libappindicator" -version = "0.9.0" +name = "libbz2-rs-sys" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" -dependencies = [ - "glib", - "gtk", - "gtk-sys", - "libappindicator-sys", - "log", -] - -[[package]] -name = "libappindicator-sys" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" -dependencies = [ - "gtk-sys", - "libloading 0.7.4", - "once_cell", -] +checksum = "775bf80d5878ab7c2b1080b5351a48b2f737d9f6f8b383574eebcc22be0dfccb" [[package]] name = "libc" @@ -3388,24 +2566,14 @@ 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", ] -[[package]] -name = "libloading" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" -dependencies = [ - "cfg-if", - "winapi", -] - [[package]] name = "libloading" version = "0.8.8" @@ -3494,12 +2662,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" -[[package]] -name = "mac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" - [[package]] name = "malloc_buf" version = "0.0.6" @@ -3510,42 +2672,14 @@ dependencies = [ ] [[package]] -name = "markup5ever" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18" -dependencies = [ - "log", - "phf 0.11.3", - "phf_codegen 0.11.3", - "string_cache", - "string_cache_codegen", - "tendril", -] - -[[package]] -name = "match_token" +name = "matchers" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", + "regex-automata 0.1.10", ] -[[package]] -name = "matches" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" - -[[package]] -name = "matchit" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" - [[package]] name = "math-parser" version = "0.0.0" @@ -3586,31 +2720,22 @@ 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", ] -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - [[package]] name = "metal" -version = "0.29.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21" +checksum = "f569fb946490b5743ad69813cb19629130ce9374034abe31614a36402d18f99e" dependencies = [ "bitflags 2.9.1", "block", - "core-graphics-types 0.1.3", + "core-graphics-types", "foreign-types 0.5.0", "log", "objc", @@ -3650,47 +2775,30 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "muda" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58b89bf91c19bf036347f1ab85a81c560f08c0667c8601bece664d860a600988" -dependencies = [ - "crossbeam-channel", - "dpi", - "gtk", - "keyboard-types", - "objc2 0.6.1", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation 0.3.1", - "once_cell", - "png", - "serde", - "thiserror 2.0.12", - "windows-sys 0.59.0", -] - [[package]] name = "naga" -version = "23.1.0" +version = "25.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "364f94bc34f61332abebe8cad6f6cd82a5b65cff22c828d05d0968911462ca4f" +checksum = "2b977c445f26e49757f9aca3631c3b8b836942cb278d69a92e7b80d3b24da632" dependencies = [ "arrayvec", "bit-set", "bitflags 2.9.1", - "cfg_aliases 0.1.1", + "cfg_aliases", "codespan-reporting", + "half", + "hashbrown", "hexf-parse", - "indexmap 2.10.0", + "indexmap", "log", - "petgraph 0.6.5", + "num-traits", + "once_cell", + "petgraph 0.8.2", "rustc-hash 1.1.0", "spirv", - "termcolor", - "thiserror 1.0.69", - "unicode-xid", + "strum", + "thiserror 2.0.12", + "unicode-ident", ] [[package]] @@ -3725,21 +2833,6 @@ dependencies = [ "rawpointer", ] -[[package]] -name = "ndk" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" -dependencies = [ - "bitflags 2.9.1", - "jni-sys", - "log", - "ndk-sys 0.5.0+25.2.9519653", - "num_enum", - "raw-window-handle", - "thiserror 1.0.69", -] - [[package]] name = "ndk" version = "0.9.0" @@ -3789,22 +2882,16 @@ checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" name = "node-macro" version = "0.0.0" dependencies = [ - "convert_case 0.7.1", + "convert_case", "graphene-core", "indoc", - "proc-macro-crate 3.3.0", + "proc-macro-crate", "proc-macro-error2", "proc-macro2", "quote", "syn 2.0.104", ] -[[package]] -name = "nodrop" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" - [[package]] name = "nom" version = "7.1.3" @@ -3821,6 +2908,16 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -3884,6 +2981,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -3902,7 +3000,7 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" dependencies = [ - "proc-macro-crate 3.3.0", + "proc-macro-crate", "proc-macro2", "quote", "syn 2.0.104", @@ -3923,16 +3021,6 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" -[[package]] -name = "objc2" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "559c5a40fdd30eb5e344fbceacf7595a81e242529fb4e21cf5f43fb4f11ff98d" -dependencies = [ - "objc-sys", - "objc2-encode 3.0.0", -] - [[package]] name = "objc2" version = "0.5.2" @@ -3940,7 +3028,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" dependencies = [ "objc-sys", - "objc2-encode 4.1.0", + "objc2-encode", ] [[package]] @@ -3949,49 +3037,59 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88c6597e14493ab2e44ce58f2fdecf095a51f12ca57bec060a11c57332520551" dependencies = [ - "objc2-encode 4.1.0", - "objc2-exception-helper", + "objc2-encode", ] [[package]] name = "objc2-app-kit" -version = "0.3.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" dependencies = [ "bitflags 2.9.1", - "block2 0.6.1", + "block2", "libc", - "objc2 0.6.1", - "objc2-cloud-kit", + "objc2 0.5.2", "objc2-core-data", - "objc2-core-foundation", - "objc2-core-graphics", "objc2-core-image", - "objc2-foundation 0.3.1", - "objc2-quartz-core 0.3.1", + "objc2-foundation 0.2.2", + "objc2-quartz-core", ] [[package]] name = "objc2-cloud-kit" -version = "0.3.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17614fdcd9b411e6ff1117dfb1d0150f908ba83a7df81b1f118005fe0a8ea15d" +checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" dependencies = [ "bitflags 2.9.1", - "objc2 0.6.1", - "objc2-foundation 0.3.1", + "block2", + "objc2 0.5.2", + "objc2-core-location", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-contacts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" +dependencies = [ + "block2", + "objc2 0.5.2", + "objc2-foundation 0.2.2", ] [[package]] name = "objc2-core-data" -version = "0.3.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291fbbf7d29287518e8686417cf7239c74700fd4b607623140a7d4a3c834329d" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ "bitflags 2.9.1", - "objc2 0.6.1", - "objc2-foundation 0.3.1", + "block2", + "objc2 0.5.2", + "objc2-foundation 0.2.2", ] [[package]] @@ -4001,31 +3099,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" dependencies = [ "bitflags 2.9.1", - "dispatch2", - "objc2 0.6.1", -] - -[[package]] -name = "objc2-core-graphics" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4" -dependencies = [ - "bitflags 2.9.1", - "dispatch2", - "objc2 0.6.1", - "objc2-core-foundation", - "objc2-io-surface", ] [[package]] name = "objc2-core-image" -version = "0.3.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79b3dc0cc4386b6ccf21c157591b34a7f44c8e75b064f85502901ab2188c007e" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" dependencies = [ - "objc2 0.6.1", - "objc2-foundation 0.3.1", + "block2", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal", +] + +[[package]] +name = "objc2-core-location" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" +dependencies = [ + "block2", + "objc2 0.5.2", + "objc2-contacts", + "objc2-foundation 0.2.2", ] [[package]] @@ -4038,27 +3135,12 @@ dependencies = [ "objc2-core-foundation", ] -[[package]] -name = "objc2-encode" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d079845b37af429bfe5dfa76e6d087d788031045b25cfc6fd898486fd9847666" - [[package]] name = "objc2-encode" version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" -[[package]] -name = "objc2-exception-helper" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" -dependencies = [ - "cc", -] - [[package]] name = "objc2-foundation" version = "0.2.2" @@ -4066,7 +3148,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ "bitflags 2.9.1", - "block2 0.5.1", + "block2", + "dispatch", "libc", "objc2 0.5.2", ] @@ -4078,21 +3161,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" dependencies = [ "bitflags 2.9.1", - "block2 0.6.1", - "libc", "objc2 0.6.1", - "objc2-core-foundation", ] [[package]] -name = "objc2-io-surface" -version = "0.3.1" +name = "objc2-link-presentation" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c" +checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" dependencies = [ - "bitflags 2.9.1", - "objc2 0.6.1", - "objc2-core-foundation", + "block2", + "objc2 0.5.2", + "objc2-app-kit", + "objc2-foundation 0.2.2", ] [[package]] @@ -4102,7 +3183,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ "bitflags 2.9.1", - "block2 0.5.1", + "block2", "objc2 0.5.2", "objc2-foundation 0.2.2", ] @@ -4114,47 +3195,65 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ "bitflags 2.9.1", - "block2 0.5.1", + "block2", "objc2 0.5.2", "objc2-foundation 0.2.2", "objc2-metal", ] [[package]] -name = "objc2-quartz-core" -version = "0.3.1" +name = "objc2-symbols" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ffb6a0cd5f182dc964334388560b12a57f7b74b3e2dec5e2722aa2dfb2ccd5" +checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" dependencies = [ - "bitflags 2.9.1", - "objc2 0.6.1", - "objc2-foundation 0.3.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", ] [[package]] name = "objc2-ui-kit" -version = "0.3.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25b1312ad7bc8a0e92adae17aa10f90aae1fb618832f9b993b022b591027daed" +checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" dependencies = [ "bitflags 2.9.1", - "objc2 0.6.1", - "objc2-core-foundation", - "objc2-foundation 0.3.1", + "block2", + "objc2 0.5.2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-image", + "objc2-core-location", + "objc2-foundation 0.2.2", + "objc2-link-presentation", + "objc2-quartz-core", + "objc2-symbols", + "objc2-uniform-type-identifiers", + "objc2-user-notifications", ] [[package]] -name = "objc2-web-kit" -version = "0.3.1" +name = "objc2-uniform-type-identifiers" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91672909de8b1ce1c2252e95bbee8c1649c9ad9d14b9248b3d7b4c47903c47ad" +checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" +dependencies = [ + "block2", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" dependencies = [ "bitflags 2.9.1", - "block2 0.6.1", - "objc2 0.6.1", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation 0.3.1", + "block2", + "objc2 0.5.2", + "objc2-core-location", + "objc2-foundation 0.2.2", ] [[package]] @@ -4184,18 +3283,6 @@ version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" -[[package]] -name = "open" -version = "5.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2483562e62ea94312f3576a7aca397306df7990b8d89033e18766744377ef95" -dependencies = [ - "dunce", - "is-wsl", - "libc", - "pathdiff", -] - [[package]] name = "openssl" version = "0.10.73" @@ -4240,12 +3327,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - [[package]] name = "orbclient" version = "0.3.48" @@ -4256,15 +3337,20 @@ dependencies = [ ] [[package]] -name = "os_pipe" -version = "1.2.2" +name = "ordered-float" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db335f4760b14ead6290116f2427bf33a14d4f0617d49f78a246de10c1831224" +checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" dependencies = [ - "libc", - "windows-sys 0.59.0", + "num-traits", ] +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "owned_ttf_parser" version = "0.25.0" @@ -4274,31 +3360,6 @@ dependencies = [ "ttf-parser 0.25.1", ] -[[package]] -name = "pango" -version = "0.18.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" -dependencies = [ - "gio", - "glib", - "libc", - "once_cell", - "pango-sys", -] - -[[package]] -name = "pango-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - [[package]] name = "parking_lot" version = "0.12.4" @@ -4329,8 +3390,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13e57638545cf2ba4c3e72cc5715e53b1880b829cc3dbefda3d1700c58efe723" dependencies = [ "fontique", - "hashbrown 0.15.4", - "peniko 0.4.0", + "hashbrown", + "peniko", "skrifa 0.31.3", "swash", ] @@ -4355,29 +3416,13 @@ dependencies = [ "svg", ] -[[package]] -name = "pathdiff" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" - -[[package]] -name = "peniko" -version = "0.2.0" -source = "git+https://github.com/linebender/peniko.git?rev=d114c62#d114c6292dbcfb03e7360692198be423168a0edd" -dependencies = [ - "color 0.1.0", - "kurbo", - "smallvec", -] - [[package]] name = "peniko" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f9529efd019889b2a205193c14ffb6e2839b54ed9d2720674f10f4b04d87ac9" dependencies = [ - "color 0.3.1", + "color", "kurbo", "smallvec", ] @@ -4432,158 +3477,26 @@ dependencies = [ "sha2", ] -[[package]] -name = "petgraph" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" -dependencies = [ - "fixedbitset 0.4.2", - "indexmap 2.10.0", -] - [[package]] name = "petgraph" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" dependencies = [ - "fixedbitset 0.5.7", - "indexmap 2.10.0", + "fixedbitset", + "indexmap", ] [[package]] -name = "phf" -version = "0.8.0" +name = "petgraph" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +checksum = "54acf3a685220b533e437e264e4d932cfbdc4cc7ec0cd232ed73c08d03b8a7ca" dependencies = [ - "phf_shared 0.8.0", -] - -[[package]] -name = "phf" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" -dependencies = [ - "phf_macros 0.10.0", - "phf_shared 0.10.0", - "proc-macro-hack", -] - -[[package]] -name = "phf" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" -dependencies = [ - "phf_macros 0.11.3", - "phf_shared 0.11.3", -] - -[[package]] -name = "phf_codegen" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" -dependencies = [ - "phf_generator 0.8.0", - "phf_shared 0.8.0", -] - -[[package]] -name = "phf_codegen" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", -] - -[[package]] -name = "phf_generator" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" -dependencies = [ - "phf_shared 0.8.0", - "rand 0.7.3", -] - -[[package]] -name = "phf_generator" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" -dependencies = [ - "phf_shared 0.10.0", - "rand 0.8.5", -] - -[[package]] -name = "phf_generator" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" -dependencies = [ - "phf_shared 0.11.3", - "rand 0.8.5", -] - -[[package]] -name = "phf_macros" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" -dependencies = [ - "phf_generator 0.10.0", - "phf_shared 0.10.0", - "proc-macro-hack", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "phf_macros" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "phf_shared" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" -dependencies = [ - "siphasher 0.3.11", -] - -[[package]] -name = "phf_shared" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" -dependencies = [ - "siphasher 0.3.11", -] - -[[package]] -name = "phf_shared" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" -dependencies = [ - "siphasher 1.0.1", + "fixedbitset", + "hashbrown", + "indexmap", + "serde", ] [[package]] @@ -4592,6 +3505,26 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -4610,19 +3543,6 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" -[[package]] -name = "plist" -version = "1.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d77244ce2d584cd84f6a15f86195b8c9b2a0dfbfd817c09e0464244091a58ed" -dependencies = [ - "base64 0.22.1", - "indexmap 2.10.0", - "quick-xml", - "serde", - "time", -] - [[package]] name = "plotters" version = "0.3.7" @@ -4679,6 +3599,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "pollster" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" + [[package]] name = "portable-atomic" version = "1.11.1" @@ -4718,12 +3644,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "precomputed-hash" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" - [[package]] name = "preprocessor" version = "0.1.0" @@ -4749,32 +3669,13 @@ dependencies = [ "yansi", ] -[[package]] -name = "proc-macro-crate" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" -dependencies = [ - "once_cell", - "toml_edit 0.19.15", -] - -[[package]] -name = "proc-macro-crate" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8" -dependencies = [ - "toml_edit 0.20.7", -] - [[package]] name = "proc-macro-crate" version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" dependencies = [ - "toml_edit 0.22.27", + "toml_edit", ] [[package]] @@ -4823,12 +3724,6 @@ dependencies = [ "syn 2.0.104", ] -[[package]] -name = "proc-macro-hack" -version = "0.5.20+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" - [[package]] name = "proc-macro2" version = "1.0.95" @@ -4857,22 +3752,6 @@ dependencies = [ "syn 2.0.104", ] -[[package]] -name = "psl-types" -version = "2.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" - -[[package]] -name = "publicsuffix" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42ea446cab60335f76979ec15e12619a2165b5ae2c12166bef27d283a9fadf" -dependencies = [ - "idna", - "psl-types", -] - [[package]] name = "qoi" version = "0.4.1" @@ -4904,7 +3783,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" dependencies = [ "bytes", - "cfg_aliases 0.2.1", + "cfg_aliases", "pin-project-lite", "quinn-proto", "quinn-udp", @@ -4914,7 +3793,7 @@ dependencies = [ "thiserror 2.0.12", "tokio", "tracing", - "web-time 1.1.0", + "web-time", ] [[package]] @@ -4935,7 +3814,7 @@ dependencies = [ "thiserror 2.0.12", "tinyvec", "tracing", - "web-time 1.1.0", + "web-time", ] [[package]] @@ -4944,7 +3823,7 @@ version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970" dependencies = [ - "cfg_aliases 0.2.1", + "cfg_aliases", "libc", "once_cell", "socket2", @@ -4967,20 +3846,6 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", - "rand_pcg", -] - [[package]] name = "rand" version = "0.8.5" @@ -5002,16 +3867,6 @@ dependencies = [ "rand_core 0.9.3", ] -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", -] - [[package]] name = "rand_chacha" version = "0.3.1" @@ -5032,15 +3887,6 @@ dependencies = [ "rand_core 0.9.3", ] -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", -] - [[package]] name = "rand_core" version = "0.6.4" @@ -5059,24 +3905,6 @@ dependencies = [ "getrandom 0.3.3", ] -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "rand_pcg" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" -dependencies = [ - "rand_core 0.5.1", -] - [[package]] name = "range-alloc" version = "0.1.4" @@ -5165,16 +3993,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "read-fonts" -version = "0.25.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f9e8a4f503e5c8750e4cd3b32a4e090035c46374b305a15c70bad833dca05f" -dependencies = [ - "bytemuck", - "font-types 0.8.4", -] - [[package]] name = "read-fonts" version = "0.29.3" @@ -5182,7 +4000,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04ca636dac446b5664bd16c069c00a9621806895b8bb02c2dc68542b23b8f25d" dependencies = [ "bytemuck", - "font-types 0.9.0", + "font-types", ] [[package]] @@ -5192,14 +4010,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "192735ef611aac958468e670cb98432c925426f3cb71521fda202130f7388d91" dependencies = [ "bytemuck", - "font-types 0.9.0", + "font-types", ] [[package]] name = "redox_syscall" -version = "0.3.5" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ "bitflags 1.3.2", ] @@ -5213,37 +4031,6 @@ dependencies = [ "bitflags 2.9.1", ] -[[package]] -name = "redox_users" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" -dependencies = [ - "getrandom 0.2.16", - "libredox", - "thiserror 2.0.12", -] - -[[package]] -name = "ref-cast" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" -dependencies = [ - "ref-cast-impl", -] - -[[package]] -name = "ref-cast-impl" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - [[package]] name = "regex" version = "1.11.1" @@ -5252,8 +4039,17 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata", - "regex-syntax", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", ] [[package]] @@ -5264,9 +4060,15 @@ checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.8.5", ] +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "regex-syntax" version = "0.8.5" @@ -5287,8 +4089,6 @@ checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" dependencies = [ "base64 0.22.1", "bytes", - "cookie", - "cookie_store", "encoding_rs", "futures-channel", "futures-core", @@ -5317,16 +4117,14 @@ dependencies = [ "tokio", "tokio-native-tls", "tokio-rustls", - "tokio-util", "tower", "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams", "web-sys", - "webpki-roots", + "webpki-roots 1.0.1", ] [[package]] @@ -5348,9 +4146,9 @@ dependencies = [ [[package]] name = "rgb" -version = "0.8.50" +version = "0.8.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" +checksum = "a457e416a0f90d246a4c3288bd7a25b2304ca727f253f95be383dd17af56be8f" dependencies = [ "bytemuck", ] @@ -5405,15 +4203,6 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - [[package]] name = "rustix" version = "0.38.44" @@ -5442,10 +4231,11 @@ dependencies = [ [[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 = [ + "log", "once_cell", "ring", "rustls-pki-types", @@ -5454,21 +4244,30 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "rustls-pki-types" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" dependencies = [ - "web-time 1.1.0", + "web-time", "zeroize", ] [[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", @@ -5523,57 +4322,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "schemars" -version = "0.8.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" -dependencies = [ - "dyn-clone", - "indexmap 1.9.3", - "schemars_derive", - "serde", - "serde_json", - "url", - "uuid", -] - -[[package]] -name = "schemars" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" -dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", -] - -[[package]] -name = "schemars" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1375ba8ef45a6f15d83fa8748f1079428295d403d6ea991d09ab100155fbc06d" -dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", -] - -[[package]] -name = "schemars_derive" -version = "0.8.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn 2.0.104", -] - [[package]] name = "scoped-tls" version = "1.0.1" @@ -5588,9 +4336,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sctk-adwaita" -version = "0.8.3" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70b31447ca297092c5a9916fc3b955203157b37c19ca8edde4f52e9843e602c7" +checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec" dependencies = [ "ab_glyph", "log", @@ -5606,7 +4354,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags 2.9.1", - "core-foundation 0.9.4", + "core-foundation", "core-foundation-sys", "libc", "security-framework-sys", @@ -5622,32 +4370,11 @@ dependencies = [ "libc", ] -[[package]] -name = "selectors" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c37578180969d00692904465fb7f6b3d50b9a2b952b87c23d0e2e5cb5013416" -dependencies = [ - "bitflags 1.3.2", - "cssparser", - "derive_more", - "fxhash", - "log", - "phf 0.8.0", - "phf_codegen 0.8.0", - "precomputed-hash", - "servo_arc", - "smallvec", -] - [[package]] name = "semver" version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" -dependencies = [ - "serde", -] [[package]] name = "serde" @@ -5658,17 +4385,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde-untagged" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "299d9c19d7d466db4ab10addd5703e4c615dec2a5a16dbbafe191045e87ee66e" -dependencies = [ - "erased-serde", - "serde", - "typeid", -] - [[package]] name = "serde-wasm-bindgen" version = "0.6.5" @@ -5691,17 +4407,6 @@ dependencies = [ "syn 2.0.104", ] -[[package]] -name = "serde_derive_internals" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - [[package]] name = "serde_json" version = "1.0.140" @@ -5714,27 +4419,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_path_to_error" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" -dependencies = [ - "itoa", - "serde", -] - -[[package]] -name = "serde_repr" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - [[package]] name = "serde_spanned" version = "0.6.9" @@ -5757,68 +4441,10 @@ dependencies = [ ] [[package]] -name = "serde_with" -version = "3.14.0" +name = "sha1_smol" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2c45cd61fefa9db6f254525d46e392b852e0e61d9a1fd36e5bd183450a556d5" -dependencies = [ - "base64 0.22.1", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.10.0", - "schemars 0.9.0", - "schemars 1.0.3", - "serde", - "serde_derive", - "serde_json", - "serde_with_macros", - "time", -] - -[[package]] -name = "serde_with_macros" -version = "3.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de90945e6565ce0d9a25098082ed4ee4002e047cb59892c318d66821e14bb30f" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "serialize-to-javascript" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9823f2d3b6a81d98228151fdeaf848206a7855a7a042bbf9bf870449a66cafb" -dependencies = [ - "serde", - "serde_json", - "serialize-to-javascript-impl", -] - -[[package]] -name = "serialize-to-javascript-impl" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74064874e9f6a15f04c1f3cb627902d0e6b410abbf36668afa873c61889f1763" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "servo_arc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52aa42f8fdf0fed91e5ce7f23d8138441002fa31dca008acf47e6fd4721f741" -dependencies = [ - "nodrop", - "stable_deref_trait", -] +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" [[package]] name = "sha2" @@ -5832,14 +4458,12 @@ dependencies = [ ] [[package]] -name = "shared_child" -version = "1.1.0" +name = "sharded-slab" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2778001df1384cf20b6dc5a5a90f48da35539885edaaefd887f8d744e939c0b" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ - "libc", - "sigchld", - "windows-sys 0.60.2", + "lazy_static", ] [[package]] @@ -5848,36 +4472,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" -[[package]] -name = "sigchld" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1219ef50fc0fdb04fcc243e6aa27f855553434ffafe4fa26554efb78b5b4bf89" -dependencies = [ - "libc", - "os_pipe", - "signal-hook", -] - -[[package]] -name = "signal-hook" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" -dependencies = [ - "libc", - "signal-hook-registry", -] - -[[package]] -name = "signal-hook-registry" -version = "1.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" -dependencies = [ - "libc", -] - [[package]] name = "simd-adler32" version = "0.3.7" @@ -5902,28 +4496,12 @@ dependencies = [ "log", ] -[[package]] -name = "siphasher" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" - [[package]] name = "siphasher" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" -[[package]] -name = "skrifa" -version = "0.26.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cc1aa86c26dbb1b63875a7180aa0819709b33348eb5b1491e4321fae388179d" -dependencies = [ - "bytemuck", - "read-fonts 0.25.3", -] - [[package]] name = "skrifa" version = "0.31.3" @@ -5970,9 +4548,9 @@ dependencies = [ [[package]] name = "smithay-client-toolkit" -version = "0.18.1" +version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "922fd3eeab3bd820d76537ce8f582b1cf951eceb5475c28500c7457d9d17f53a" +checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" dependencies = [ "bitflags 2.9.1", "calloop", @@ -6013,51 +4591,14 @@ dependencies = [ ] [[package]] -name = "softbuffer" -version = "0.4.6" +name = "socks" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18051cdd562e792cad055119e0cdb2cfc137e44e3987532e0f9659a77931bb08" +checksum = "f0c3dbbd9ae980613c6dd8e28a9407b50509d3803b57624d5dfe8315218cd58b" dependencies = [ - "bytemuck", - "cfg_aliases 0.2.1", - "core-graphics 0.24.0", - "foreign-types 0.5.0", - "js-sys", - "log", - "objc2 0.5.2", - "objc2-foundation 0.2.2", - "objc2-quartz-core 0.2.2", - "raw-window-handle", - "redox_syscall 0.5.13", - "wasm-bindgen", - "web-sys", - "windows-sys 0.59.0", -] - -[[package]] -name = "soup3" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" -dependencies = [ - "futures-channel", - "gio", - "glib", + "byteorder", "libc", - "soup3-sys", -] - -[[package]] -name = "soup3-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" -dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps", + "winapi", ] [[package]] @@ -6122,37 +4663,34 @@ dependencies = [ "float-cmp", ] -[[package]] -name = "string_cache" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" -dependencies = [ - "new_debug_unreachable", - "parking_lot", - "phf_shared 0.11.3", - "precomputed-hash", - "serde", -] - -[[package]] -name = "string_cache_codegen" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", - "proc-macro2", - "quote", -] - [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.104", +] + [[package]] name = "subtle" version = "2.6.1" @@ -6178,7 +4716,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68c7541fff44b35860c1a7a47a7cadf3e4a304c457b58f9870d9706ece028afc" dependencies = [ "kurbo", - "siphasher 1.0.1", + "siphasher", ] [[package]] @@ -6192,17 +4730,6 @@ dependencies = [ "zeno", ] -[[package]] -name = "swift-rs" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7" -dependencies = [ - "base64 0.21.7", - "serde", - "serde_json", -] - [[package]] name = "syn" version = "1.0.109" @@ -6252,7 +4779,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ "bitflags 2.9.1", - "core-foundation 0.9.4", + "core-foundation", "system-configuration-sys", ] @@ -6273,60 +4800,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" dependencies = [ "cfg-expr", - "heck 0.5.0", + "heck", "pkg-config", "toml", "version-compare", ] [[package]] -name = "tao" -version = "0.34.0" +name = "tar" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49c380ca75a231b87b6c9dd86948f035012e7171d1a7c40a9c2890489a7ffd8a" +checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a" dependencies = [ - "bitflags 2.9.1", - "core-foundation 0.10.1", - "core-graphics 0.24.0", - "crossbeam-channel", - "dispatch", - "dlopen2", - "dpi", - "gdkwayland-sys", - "gdkx11-sys", - "gtk", - "jni", - "lazy_static", + "filetime", "libc", - "log", - "ndk 0.9.0", - "ndk-context", - "ndk-sys 0.6.0+11769913", - "objc2 0.6.1", - "objc2-app-kit", - "objc2-foundation 0.3.1", - "once_cell", - "parking_lot", - "raw-window-handle", - "scopeguard", - "tao-macros", - "unicode-segmentation", - "url", - "windows 0.61.3", - "windows-core 0.61.2", - "windows-version", - "x11-dl", -] - -[[package]] -name = "tao-macros" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", + "xattr", ] [[package]] @@ -6335,301 +4823,6 @@ version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" -[[package]] -name = "tauri" -version = "2.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "124e129c9c0faa6bec792c5948c89e86c90094133b0b9044df0ce5f0a8efaa0d" -dependencies = [ - "anyhow", - "bytes", - "dirs", - "dunce", - "embed_plist", - "getrandom 0.3.3", - "glob", - "gtk", - "heck 0.5.0", - "http", - "jni", - "libc", - "log", - "mime", - "muda", - "objc2 0.6.1", - "objc2-app-kit", - "objc2-foundation 0.3.1", - "objc2-ui-kit", - "percent-encoding", - "plist", - "raw-window-handle", - "reqwest", - "serde", - "serde_json", - "serde_repr", - "serialize-to-javascript", - "swift-rs", - "tauri-build", - "tauri-macros", - "tauri-runtime", - "tauri-runtime-wry", - "tauri-utils", - "thiserror 2.0.12", - "tokio", - "tray-icon", - "url", - "urlpattern", - "webkit2gtk", - "webview2-com", - "window-vibrancy", - "windows 0.61.3", -] - -[[package]] -name = "tauri-build" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f025c389d3adb83114bec704da973142e82fc6ec799c7c750c5e21cefaec83" -dependencies = [ - "anyhow", - "cargo_toml", - "dirs", - "glob", - "heck 0.5.0", - "json-patch", - "schemars 0.8.22", - "semver", - "serde", - "serde_json", - "tauri-utils", - "tauri-winres", - "toml", - "walkdir", -] - -[[package]] -name = "tauri-codegen" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5df493a1075a241065bc865ed5ef8d0fbc1e76c7afdc0bf0eccfaa7d4f0e406" -dependencies = [ - "base64 0.22.1", - "brotli", - "ico", - "json-patch", - "plist", - "png", - "proc-macro2", - "quote", - "semver", - "serde", - "serde_json", - "sha2", - "syn 2.0.104", - "tauri-utils", - "thiserror 2.0.12", - "time", - "url", - "uuid", - "walkdir", -] - -[[package]] -name = "tauri-macros" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f237fbea5866fa5f2a60a21bea807a2d6e0379db070d89c3a10ac0f2d4649bbc" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.104", - "tauri-codegen", - "tauri-utils", -] - -[[package]] -name = "tauri-plugin" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d9a0bd00bf1930ad1a604d08b0eb6b2a9c1822686d65d7f4731a7723b8901d3" -dependencies = [ - "anyhow", - "glob", - "plist", - "schemars 0.8.22", - "serde", - "serde_json", - "tauri-utils", - "toml", - "walkdir", -] - -[[package]] -name = "tauri-plugin-fs" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c341290d31991dbca38b31d412c73dfbdb070bb11536784f19dd2211d13b778f" -dependencies = [ - "anyhow", - "dunce", - "glob", - "percent-encoding", - "schemars 0.8.22", - "serde", - "serde_json", - "serde_repr", - "tauri", - "tauri-plugin", - "tauri-utils", - "thiserror 2.0.12", - "toml", - "url", -] - -[[package]] -name = "tauri-plugin-http" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c1a38da944b357ffa23bafd563b1579f18e6fbd118fcd84769406d35dcc5c7" -dependencies = [ - "bytes", - "cookie_store", - "data-url", - "http", - "regex", - "reqwest", - "schemars 0.8.22", - "serde", - "serde_json", - "tauri", - "tauri-plugin", - "tauri-plugin-fs", - "thiserror 2.0.12", - "tokio", - "url", - "urlpattern", -] - -[[package]] -name = "tauri-plugin-shell" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b9ffadec5c3523f11e8273465cacb3d86ea7652a28e6e2a2e9b5c182f791d25" -dependencies = [ - "encoding_rs", - "log", - "open", - "os_pipe", - "regex", - "schemars 0.8.22", - "serde", - "serde_json", - "shared_child", - "tauri", - "tauri-plugin", - "thiserror 2.0.12", - "tokio", -] - -[[package]] -name = "tauri-runtime" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e7bb73d1bceac06c20b3f755b2c8a2cb13b20b50083084a8cf3700daf397ba4" -dependencies = [ - "cookie", - "dpi", - "gtk", - "http", - "jni", - "objc2 0.6.1", - "objc2-ui-kit", - "raw-window-handle", - "serde", - "serde_json", - "tauri-utils", - "thiserror 2.0.12", - "url", - "windows 0.61.3", -] - -[[package]] -name = "tauri-runtime-wry" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "902b5aa9035e16f342eb64f8bf06ccdc2808e411a2525ed1d07672fa4e780bad" -dependencies = [ - "gtk", - "http", - "jni", - "log", - "objc2 0.6.1", - "objc2-app-kit", - "objc2-foundation 0.3.1", - "once_cell", - "percent-encoding", - "raw-window-handle", - "softbuffer", - "tao", - "tauri-runtime", - "tauri-utils", - "url", - "webkit2gtk", - "webview2-com", - "windows 0.61.3", - "wry", -] - -[[package]] -name = "tauri-utils" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41743bbbeb96c3a100d234e5a0b60a46d5aa068f266160862c7afdbf828ca02e" -dependencies = [ - "anyhow", - "brotli", - "cargo_metadata", - "ctor", - "dunce", - "glob", - "html5ever", - "http", - "infer", - "json-patch", - "kuchikiki", - "log", - "memchr", - "phf 0.11.3", - "proc-macro2", - "quote", - "regex", - "schemars 0.8.22", - "semver", - "serde", - "serde-untagged", - "serde_json", - "serde_with", - "swift-rs", - "thiserror 2.0.12", - "toml", - "url", - "urlpattern", - "uuid", - "walkdir", -] - -[[package]] -name = "tauri-winres" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8d321dbc6f998d825ab3f0d62673e810c861aac2d0de2cc2c395328f1d113b4" -dependencies = [ - "embed-resource", - "indexmap 2.10.0", - "toml", -] - [[package]] name = "tempfile" version = "3.20.0" @@ -6643,17 +4836,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "tendril" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" -dependencies = [ - "futf", - "mac", - "utf-8", -] - [[package]] name = "termcolor" version = "1.4.1" @@ -6703,6 +4885,15 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + [[package]] name = "tiff" version = "0.9.1" @@ -6817,15 +5008,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", @@ -6884,7 +5077,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.27", + "toml_edit", ] [[package]] @@ -6896,48 +5089,19 @@ dependencies = [ "serde", ] -[[package]] -name = "toml_edit" -version = "0.19.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" -dependencies = [ - "indexmap 2.10.0", - "toml_datetime", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.20.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" -dependencies = [ - "indexmap 2.10.0", - "toml_datetime", - "winnow 0.5.40", -] - [[package]] name = "toml_edit" version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.10.0", + "indexmap", "serde", "serde_spanned", "toml_datetime", - "toml_write", - "winnow 0.7.11", + "winnow", ] -[[package]] -name = "toml_write" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" - [[package]] name = "tower" version = "0.5.2" @@ -6951,7 +5115,6 @@ dependencies = [ "tokio", "tower-layer", "tower-service", - "tracing", ] [[package]] @@ -6990,11 +5153,22 @@ version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ - "log", "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "tracing-core" version = "0.1.34" @@ -7002,28 +5176,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", + "valuable", ] [[package]] -name = "tray-icon" -version = "0.21.0" +name = "tracing-log" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2da75ec677957aa21f6e0b361df0daab972f13a5bee3606de0638fd4ee1c666a" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" dependencies = [ - "crossbeam-channel", - "dirs", - "libappindicator", - "muda", - "objc2 0.6.1", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-foundation 0.3.1", + "log", "once_cell", - "png", - "serde", - "thiserror 2.0.12", - "windows-sys 0.59.0", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", ] [[package]] @@ -7047,12 +5229,6 @@ version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" -[[package]] -name = "typeid" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" - [[package]] name = "typenum" version = "1.18.0" @@ -7065,47 +5241,6 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" -[[package]] -name = "unic-char-property" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" -dependencies = [ - "unic-char-range", -] - -[[package]] -name = "unic-char-range" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" - -[[package]] -name = "unic-common" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" - -[[package]] -name = "unic-ucd-ident" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" -dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", -] - -[[package]] -name = "unic-ucd-version" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" -dependencies = [ - "unic-common", -] - [[package]] name = "unicode-bidi" version = "0.3.18" @@ -7161,10 +5296,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] -name = "unicode-xid" -version = "0.2.6" +name = "unicode-width" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" + +[[package]] +name = "unit-prefix" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "323402cff2dd658f39ca17c789b502021b3f18707c91cdf22e3838e1b4023817" [[package]] name = "untrusted" @@ -7172,6 +5313,40 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "ureq" +version = "3.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f0fde9bc91026e381155f8c67cb354bcd35260b2f4a29bcc84639f762760c39" +dependencies = [ + "base64 0.22.1", + "cookie_store", + "flate2", + "log", + "percent-encoding", + "rustls", + "rustls-pemfile", + "rustls-pki-types", + "serde", + "serde_json", + "socks", + "ureq-proto", + "utf-8", + "webpki-roots 0.26.11", +] + +[[package]] +name = "ureq-proto" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59db78ad1923f2b1be62b6da81fe80b173605ca0d57f85da2e005382adf693f7" +dependencies = [ + "base64 0.22.1", + "http", + "httparse", + "log", +] + [[package]] name = "url" version = "2.5.4" @@ -7181,19 +5356,6 @@ dependencies = [ "form_urlencoded", "idna", "percent-encoding", - "serde", -] - -[[package]] -name = "urlpattern" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" -dependencies = [ - "regex", - "serde", - "unic-ucd-ident", - "url", ] [[package]] @@ -7213,7 +5375,7 @@ dependencies = [ "roxmltree", "rustybuzz", "simplecss", - "siphasher 1.0.1", + "siphasher", "strict-num", "svgtypes", "tiny-skia-path", @@ -7241,18 +5403,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" -[[package]] -name = "uuid" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" -dependencies = [ - "getrandom 0.3.3", - "js-sys", - "serde", - "wasm-bindgen", -] - [[package]] name = "v_frame" version = "0.3.9" @@ -7264,6 +5414,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "vcpkg" version = "0.2.15" @@ -7272,15 +5428,15 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "vello" -version = "0.3.0" -source = "git+https://github.com/linebender/vello.git?rev=3275ec8#3275ec85d831180be81820de06cca29a97a757f5" +version = "0.5.0" +source = "git+https://github.com/linebender/vello.git#87cc5bee6d3a34d15017dbbb58634ddc7f33ff9b" dependencies = [ "bytemuck", "futures-intrusive", "log", - "peniko 0.2.0", + "peniko", "png", - "skrifa 0.26.6", + "skrifa 0.31.3", "static_assertions", "thiserror 2.0.12", "vello_encoding", @@ -7290,22 +5446,23 @@ dependencies = [ [[package]] name = "vello_encoding" -version = "0.3.0" -source = "git+https://github.com/linebender/vello.git?rev=3275ec8#3275ec85d831180be81820de06cca29a97a757f5" +version = "0.5.0" +source = "git+https://github.com/linebender/vello.git#87cc5bee6d3a34d15017dbbb58634ddc7f33ff9b" dependencies = [ "bytemuck", "guillotiere", - "peniko 0.2.0", - "skrifa 0.26.6", + "peniko", + "skrifa 0.31.3", "smallvec", ] [[package]] name = "vello_shaders" -version = "0.3.0" -source = "git+https://github.com/linebender/vello.git?rev=3275ec8#3275ec85d831180be81820de06cca29a97a757f5" +version = "0.5.0" +source = "git+https://github.com/linebender/vello.git#87cc5bee6d3a34d15017dbbb58634ddc7f33ff9b" dependencies = [ "bytemuck", + "log", "naga", "thiserror 2.0.12", "vello_encoding", @@ -7323,26 +5480,6 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" -[[package]] -name = "vswhom" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" -dependencies = [ - "libc", - "vswhom-sys", -] - -[[package]] -name = "vswhom-sys" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" -dependencies = [ - "cc", - "libc", -] - [[package]] name = "walkdir" version = "2.5.0" @@ -7362,12 +5499,6 @@ dependencies = [ "try-lock", ] -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -7454,19 +5585,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-streams" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "wayland-backend" version = "0.3.10" @@ -7517,9 +5635,9 @@ dependencies = [ [[package]] name = "wayland-protocols" -version = "0.31.2" +version = "0.32.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" +checksum = "779075454e1e9a521794fed15886323ea0feda3f8b0fc1390f5398141310422a" dependencies = [ "bitflags 2.9.1", "wayland-backend", @@ -7529,9 +5647,9 @@ dependencies = [ [[package]] name = "wayland-protocols-plasma" -version = "0.2.0" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23803551115ff9ea9bce586860c5c5a971e360825a0309264102a9495a5ff479" +checksum = "4fd38cdad69b56ace413c6bcc1fbf5acc5e2ef4af9d5f8f1f9570c0c83eae175" dependencies = [ "bitflags 2.9.1", "wayland-backend", @@ -7542,9 +5660,9 @@ dependencies = [ [[package]] name = "wayland-protocols-wlr" -version = "0.2.0" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" +checksum = "1cb6cdc73399c0e06504c437fe3cf886f25568dd5454473d565085b36d6a8bbf" dependencies = [ "bitflags 2.9.1", "wayland-backend", @@ -7586,16 +5704,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "web-time" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa30049b1c872b72c89866d458eae9f20380ab280ffd1b1e18df2d3e2d98cfe0" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - [[package]] name = "web-time" version = "1.1.0" @@ -7607,47 +5715,12 @@ dependencies = [ ] [[package]] -name = "webkit2gtk" -version = "2.0.1" +name = "webpki-roots" +version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76b1bc1e54c581da1e9f179d0b38512ba358fb1af2d634a1affe42e37172361a" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "bitflags 1.3.2", - "cairo-rs", - "gdk", - "gdk-sys", - "gio", - "gio-sys", - "glib", - "glib-sys", - "gobject-sys", - "gtk", - "gtk-sys", - "javascriptcore-rs", - "libc", - "once_cell", - "soup3", - "webkit2gtk-sys", -] - -[[package]] -name = "webkit2gtk-sys" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62daa38afc514d1f8f12b8693d30d5993ff77ced33ce30cd04deebc267a6d57c" -dependencies = [ - "bitflags 1.3.2", - "cairo-sys-rs", - "gdk-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "gtk-sys", - "javascriptcore-rs-sys", - "libc", - "pkg-config", - "soup3-sys", - "system-deps", + "webpki-roots 1.0.1", ] [[package]] @@ -7659,42 +5732,6 @@ dependencies = [ "rustls-pki-types", ] -[[package]] -name = "webview2-com" -version = "0.38.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4ba622a989277ef3886dd5afb3e280e3dd6d974b766118950a08f8f678ad6a4" -dependencies = [ - "webview2-com-macros", - "webview2-com-sys", - "windows 0.61.3", - "windows-core 0.61.2", - "windows-implement 0.60.0", - "windows-interface 0.59.1", -] - -[[package]] -name = "webview2-com-macros" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d228f15bba3b9d56dde8bddbee66fa24545bd17b48d5128ccf4a8742b18e431" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "webview2-com-sys" -version = "0.38.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36695906a1b53a3bf5c4289621efedac12b73eeb0b89e7e1a89b517302d5d75c" -dependencies = [ - "thiserror 2.0.12", - "windows 0.61.3", - "windows-core 0.61.2", -] - [[package]] name = "weezl" version = "0.1.10" @@ -7703,17 +5740,20 @@ checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" [[package]] name = "wgpu" -version = "23.0.1" +version = "25.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80f70000db37c469ea9d67defdc13024ddf9a5f1b89cb2941b812ad7cde1735a" +checksum = "ec8fb398f119472be4d80bc3647339f56eb63b2a331f6a3d16e25d8144197dd9" dependencies = [ "arrayvec", - "cfg_aliases 0.1.1", + "bitflags 2.9.1", + "cfg_aliases", "document-features", + "hashbrown", "js-sys", "log", "naga", "parking_lot", + "portable-atomic", "profiling", "raw-window-handle", "smallvec", @@ -7728,30 +5768,63 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "23.0.1" +version = "25.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d63c3c478de8e7e01786479919c8769f62a22eec16788d8c2ac77ce2c132778a" +checksum = "f7b882196f8368511d613c6aeec80655160db6646aebddf8328879a88d54e500" dependencies = [ "arrayvec", + "bit-set", "bit-vec", "bitflags 2.9.1", "bytemuck", - "cfg_aliases 0.1.1", + "cfg_aliases", "document-features", - "indexmap 2.10.0", + "hashbrown", + "indexmap", "log", "naga", "once_cell", "parking_lot", + "portable-atomic", "profiling", "raw-window-handle", "rustc-hash 1.1.0", "smallvec", - "thiserror 1.0.69", + "thiserror 2.0.12", + "wgpu-core-deps-apple", + "wgpu-core-deps-emscripten", + "wgpu-core-deps-windows-linux-android", "wgpu-hal", "wgpu-types", ] +[[package]] +name = "wgpu-core-deps-apple" +version = "25.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfd488b3239b6b7b185c3b045c39ca6bf8af34467a4c5de4e0b1a564135d093d" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-emscripten" +version = "25.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f09ad7aceb3818e52539acc679f049d3475775586f3f4e311c30165cf2c00445" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-windows-linux-android" +version = "25.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cba5fb5f7f9c98baa7c889d444f63ace25574833df56f5b817985f641af58e46" +dependencies = [ + "wgpu-hal", +] + [[package]] name = "wgpu-executor" version = "0.1.0" @@ -7772,9 +5845,9 @@ dependencies = [ [[package]] name = "wgpu-hal" -version = "23.0.1" +version = "25.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89364b8a0b211adc7b16aeaf1bd5ad4a919c1154b44c9ce27838213ba05fd821" +checksum = "f968767fe4d3d33747bbd1473ccd55bf0f6451f55d733b5597e67b5deab4ad17" dependencies = [ "android_system_properties", "arrayvec", @@ -7783,46 +5856,51 @@ dependencies = [ "bitflags 2.9.1", "block", "bytemuck", - "cfg_aliases 0.1.1", - "core-graphics-types 0.1.3", + "cfg-if", + "cfg_aliases", + "core-graphics-types", "glow", "glutin_wgl_sys", "gpu-alloc", "gpu-allocator", "gpu-descriptor", + "hashbrown", "js-sys", "khronos-egl", "libc", - "libloading 0.8.8", + "libloading", "log", "metal", "naga", "ndk-sys 0.5.0+25.2.9519653", "objc", - "once_cell", + "ordered-float", "parking_lot", + "portable-atomic", "profiling", "range-alloc", "raw-window-handle", "renderdoc-sys", - "rustc-hash 1.1.0", "smallvec", - "thiserror 1.0.69", + "thiserror 2.0.12", "wasm-bindgen", "web-sys", "wgpu-types", - "windows 0.58.0", + "windows", "windows-core 0.58.0", ] [[package]] name = "wgpu-types" -version = "23.0.0" +version = "25.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "610f6ff27778148c31093f3b03abc4840f9636d58d597ca2f5977433acfe0068" +checksum = "2aa49460c2a8ee8edba3fca54325540d904dd85b2e086ada762767e17d06e8bc" dependencies = [ "bitflags 2.9.1", + "bytemuck", "js-sys", + "log", + "thiserror 2.0.12", "web-sys", ] @@ -7857,21 +5935,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "window-vibrancy" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" -dependencies = [ - "objc2 0.6.1", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation 0.3.1", - "raw-window-handle", - "windows-sys 0.59.0", - "windows-version", -] - [[package]] name = "windows" version = "0.58.0" @@ -7882,28 +5945,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows" -version = "0.61.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" -dependencies = [ - "windows-collections", - "windows-core 0.61.2", - "windows-future", - "windows-link", - "windows-numerics", -] - -[[package]] -name = "windows-collections" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" -dependencies = [ - "windows-core 0.61.2", -] - [[package]] name = "windows-core" version = "0.58.0" @@ -7930,17 +5971,6 @@ dependencies = [ "windows-strings 0.4.2", ] -[[package]] -name = "windows-future" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" -dependencies = [ - "windows-core 0.61.2", - "windows-link", - "windows-threading", -] - [[package]] name = "windows-implement" version = "0.58.0" @@ -7991,16 +6021,6 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" -[[package]] -name = "windows-numerics" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" -dependencies = [ - "windows-core 0.61.2", - "windows-link", -] - [[package]] name = "windows-registry" version = "0.5.3" @@ -8058,15 +6078,6 @@ dependencies = [ "windows-targets 0.42.2", ] -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.52.0" @@ -8156,24 +6167,6 @@ dependencies = [ "windows_x86_64_msvc 0.53.0", ] -[[package]] -name = "windows-threading" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-version" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e04a5c6627e310a23ad2358483286c7df260c964eb2d003d8efd6d0f4e79265c" -dependencies = [ - "windows-link", -] - [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -8356,37 +6349,42 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winit" -version = "0.29.15" +version = "0.30.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d59ad965a635657faf09c8f062badd885748428933dad8e8bdd64064d92e5ca" +checksum = "a4409c10174df8779dc29a4788cac85ed84024ccbc1743b776b21a520ee1aaf4" dependencies = [ "ahash", "android-activity", "atomic-waker", "bitflags 2.9.1", + "block2", "bytemuck", "calloop", - "cfg_aliases 0.1.1", - "core-foundation 0.9.4", - "core-graphics 0.23.2", + "cfg_aliases", + "concurrent-queue", + "core-foundation", + "core-graphics", "cursor-icon", - "icrate", + "dpi", "js-sys", "libc", - "log", "memmap2", - "ndk 0.8.0", - "ndk-sys 0.5.0+25.2.9519653", - "objc2 0.4.1", - "once_cell", + "ndk", + "objc2 0.5.2", + "objc2-app-kit", + "objc2-foundation 0.2.2", + "objc2-ui-kit", "orbclient", "percent-encoding", + "pin-project", "raw-window-handle", - "redox_syscall 0.3.5", + "redox_syscall 0.4.1", "rustix 0.38.44", "sctk-adwaita", + "serde", "smithay-client-toolkit", "smol_str", + "tracing", "unicode-segmentation", "wasm-bindgen", "wasm-bindgen-futures", @@ -8395,8 +6393,8 @@ dependencies = [ "wayland-protocols", "wayland-protocols-plasma", "web-sys", - "web-time 0.2.4", - "windows-sys 0.48.0", + "web-time", + "windows-sys 0.52.0", "x11-dl", "x11rb", "xkbcommon-dl", @@ -8404,32 +6402,13 @@ dependencies = [ [[package]] name = "winnow" -version = "0.5.40" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" dependencies = [ "memchr", ] -[[package]] -name = "winnow" -version = "0.7.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" -dependencies = [ - "memchr", -] - -[[package]] -name = "winreg" -version = "0.55.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" -dependencies = [ - "cfg-if", - "windows-sys 0.59.0", -] - [[package]] name = "wit-bindgen-rt" version = "0.39.0" @@ -8451,60 +6430,6 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" -[[package]] -name = "wry" -version = "0.52.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12a714d9ba7075aae04a6e50229d6109e3d584774b99a6a8c60de1698ca111b9" -dependencies = [ - "base64 0.22.1", - "block2 0.6.1", - "cookie", - "crossbeam-channel", - "dpi", - "dunce", - "gdkx11", - "gtk", - "html5ever", - "http", - "javascriptcore-rs", - "jni", - "kuchikiki", - "libc", - "ndk 0.9.0", - "objc2 0.6.1", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation 0.3.1", - "objc2-ui-kit", - "objc2-web-kit", - "once_cell", - "percent-encoding", - "raw-window-handle", - "sha2", - "soup3", - "tao-macros", - "thiserror 2.0.12", - "url", - "webkit2gtk", - "webkit2gtk-sys", - "webview2-com", - "windows 0.61.3", - "windows-core 0.61.2", - "windows-version", - "x11-dl", -] - -[[package]] -name = "x11" -version = "2.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" -dependencies = [ - "libc", - "pkg-config", -] - [[package]] name = "x11-dl" version = "2.21.0" @@ -8525,7 +6450,7 @@ dependencies = [ "as-raw-xcb-connection", "gethostname", "libc", - "libloading 0.8.8", + "libloading", "once_cell", "rustix 0.38.44", "x11rb-protocol", @@ -8537,6 +6462,16 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" +[[package]] +name = "xattr" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af3a19837351dc82ba89f8a125e22a3c475f05aba604acc023d62b2739ae2909" +dependencies = [ + "libc", + "rustix 1.0.7", +] + [[package]] name = "xcursor" version = "0.3.10" @@ -8564,9 +6499,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 +6648,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..cf2a1ec0f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,9 @@ [workspace] members = [ "editor", + "desktop", "proc-macros", "frontend/wasm", - "frontend/src-tauri", "node-graph/gapplication-io", "node-graph/gbrush", "node-graph/gcore", @@ -83,7 +83,7 @@ axum = "0.8" chrono = "0.4" ron = "0.8" fastnoise-lite = "1.1" -wgpu = { version = "23", features = [ +wgpu = { version = "25.0.2", features = [ # We don't have wgpu on multiple threads (yet) https://github.com/gfx-rs/wgpu/blob/trunk/CHANGELOG.md#wgpu-types-now-send-sync-on-wasm "fragile-send-sync-non-atomic-wasm", "spirv", @@ -111,10 +111,10 @@ web-sys = { version = "=0.3.77", features = [ "HtmlImageElement", "ImageBitmapRenderingContext", ] } -winit = "0.29" +winit = { version = "0.30", features = ["wayland", "rwh_06"] } 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/linebender/vello.git" } # 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/about.toml b/about.toml index 796e061fd4..e1ead699bf 100644 --- a/about.toml +++ b/about.toml @@ -16,6 +16,7 @@ accepted = [ "Unicode-DFS-2016", "Zlib", "NCSA", + "bzip2-1.0.6", ] workarounds = ["ring"] ignore-build-dependencies = true diff --git a/deny.toml b/deny.toml index 610902ac35..9f89b12283 100644 --- a/deny.toml +++ b/deny.toml @@ -28,10 +28,6 @@ targets = [ #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, ] -# Tauri produces too many nonsense warnings. -exclude = ["tauri", "tauri-build"] - - # This section is considered when running `cargo deny check advisories` # More documentation for the advisories section can be found here: # https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html @@ -85,6 +81,7 @@ allow = [ "Unicode-DFS-2016", "Zlib", "NCSA", + "bzip2-1.0.6", ] # The confidence threshold for detecting a license from license text. # The higher the value, the more closely the license text must be to the diff --git a/frontend/src-tauri/.gitignore b/desktop/.gitignore similarity index 73% rename from frontend/src-tauri/.gitignore rename to desktop/.gitignore index 3419525157..aba21e242c 100644 --- a/frontend/src-tauri/.gitignore +++ b/desktop/.gitignore @@ -1,5 +1,3 @@ # Generated by Cargo # will have compiled files and executables /target/ -# Generated by tauri -gen/ diff --git a/desktop/Cargo.toml b/desktop/Cargo.toml new file mode 100644 index 0000000000..fb4b2899a0 --- /dev/null +++ b/desktop/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "graphite-desktop" +version = "0.1.0" +description = "Graphite Desktop" +authors = ["Graphite Authors "] +license = "Apache-2.0" +repository = "" +edition = "2024" +rust-version = "1.87" + +[features] +default = ["gpu"] +gpu = ["graphite-editor/gpu"] + +[dependencies] +# Local dependencies +graphite-editor = { path = "../editor", features = [ + "gpu", + "ron", + "vello", + "decouple-execution", +] } +wgpu = { workspace = true } +winit = { workspace = true, features = ["serde"] } +base64.workspace = true +thiserror.workspace = true +pollster = "0.3" +cef = "138.5.0" +tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } +tracing = "0.1.41" +bytemuck = { version = "1.23.1", features = ["derive"] } +include_dir = "0.7.4" diff --git a/desktop/build.rs b/desktop/build.rs new file mode 100644 index 0000000000..6602dee825 --- /dev/null +++ b/desktop/build.rs @@ -0,0 +1,10 @@ +use std::fs::metadata; + +fn main() { + let frontend_dir = format!("{}/../frontend/dist", env!("CARGO_MANIFEST_DIR")); + metadata(&frontend_dir).expect("Failed to find frontend directory. Please build the frontend first."); + metadata(format!("{}/index.html", &frontend_dir)).expect("Failed to find index.html in frontend directory."); + + println!("cargo:rerun-if-changed=."); + println!("cargo:rerun-if-changed=../frontend/dist"); +} diff --git a/desktop/src/app.rs b/desktop/src/app.rs new file mode 100644 index 0000000000..fb447aa923 --- /dev/null +++ b/desktop/src/app.rs @@ -0,0 +1,151 @@ +use crate::CustomEvent; +use crate::WindowState; +use crate::WindowStateHandle; +use crate::render::GraphicsState; +use std::sync::Arc; +use std::time::Duration; +use std::time::Instant; +use winit::application::ApplicationHandler; +use winit::event::StartCause; +use winit::event::WindowEvent; +use winit::event_loop::ActiveEventLoop; +use winit::event_loop::ControlFlow; +use winit::window::Window; +use winit::window::WindowId; + +use crate::cef; + +pub(crate) struct WinitApp { + pub(crate) window_state: WindowStateHandle, + pub(crate) cef_context: cef::Context, + pub(crate) window: Option>, + cef_schedule: Option, +} + +impl WinitApp { + pub(crate) fn new(window_state: WindowStateHandle, cef_context: cef::Context) -> Self { + Self { + window_state, + cef_context, + window: None, + cef_schedule: Some(Instant::now()), + } + } +} + +impl ApplicationHandler for WinitApp { + fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { + let timeout = Instant::now() + Duration::from_millis(10); + let wait_until = timeout.min(self.cef_schedule.unwrap_or(timeout)); + event_loop.set_control_flow(ControlFlow::WaitUntil(wait_until)); + } + + fn new_events(&mut self, _event_loop: &ActiveEventLoop, _cause: StartCause) { + if let Some(schedule) = self.cef_schedule + && schedule < Instant::now() + { + self.cef_schedule = None; + self.cef_context.work(); + } + } + + fn resumed(&mut self, event_loop: &ActiveEventLoop) { + self.window_state + .with(|s| { + if let WindowState { width: Some(w), height: Some(h), .. } = s { + let window = Arc::new( + event_loop + .create_window( + Window::default_attributes() + .with_title("CEF Offscreen Rendering") + .with_inner_size(winit::dpi::LogicalSize::new(*w as u32, *h as u32)), + ) + .unwrap(), + ); + let graphics_state = pollster::block_on(GraphicsState::new(window.clone())); + + self.window = Some(window.clone()); + s.graphics_state = Some(graphics_state); + + tracing::info!("Winit window created and ready"); + } + }) + .unwrap(); + } + + fn user_event(&mut self, _: &ActiveEventLoop, event: CustomEvent) { + match event { + CustomEvent::UiUpdate => { + if let Some(window) = &self.window { + window.request_redraw(); + } + } + CustomEvent::ScheduleBrowserWork(instant) => { + self.cef_schedule = Some(instant); + } + } + } + + fn window_event(&mut self, event_loop: &ActiveEventLoop, _window_id: WindowId, event: WindowEvent) { + let Some(event) = self.cef_context.handle_window_event(event) else { return }; + + match event { + WindowEvent::CloseRequested => { + tracing::info!("The close button was pressed; stopping"); + event_loop.exit(); + } + WindowEvent::Resized(physical_size) => { + self.window_state + .with(|s| { + let width = physical_size.width as usize; + let height = physical_size.height as usize; + s.width = Some(width); + s.height = Some(height); + if let Some(graphics_state) = &mut s.graphics_state { + graphics_state.resize(width, height); + } + }) + .unwrap(); + self.cef_context.notify_of_resize(); + } + + WindowEvent::RedrawRequested => { + self.cef_context.work(); + + self.window_state + .with(|s| { + if let WindowState { + width: Some(width), + height: Some(height), + graphics_state: Some(graphics_state), + ui_frame_buffer: ui_fb, + .. + } = s + { + if let Some(fb) = &*ui_fb { + graphics_state.update_texture(fb); + if fb.width() != *width && fb.height() != *height { + graphics_state.resize(*width, *height); + } + } else if let Some(window) = &self.window { + window.request_redraw(); + } + + match graphics_state.render() { + Ok(_) => {} + Err(wgpu::SurfaceError::Lost) => { + graphics_state.resize(*width, *height); + } + Err(wgpu::SurfaceError::OutOfMemory) => { + event_loop.exit(); + } + Err(e) => tracing::error!("{:?}", e), + } + } + }) + .unwrap(); + } + _ => {} + } + } +} diff --git a/desktop/src/cef.rs b/desktop/src/cef.rs new file mode 100644 index 0000000000..eaa2170354 --- /dev/null +++ b/desktop/src/cef.rs @@ -0,0 +1,29 @@ +use crate::FrameBuffer; +use std::time::Instant; + +mod context; +mod input; +mod internal; +mod scheme_handler; + +pub(crate) use context::{Context, InitError, Initialized, Setup, SetupError}; + +pub(crate) trait CefEventHandler: Clone { + fn window_size(&self) -> WindowSize; + fn draw(&self, frame_buffer: FrameBuffer) -> bool; + /// Scheudule the main event loop to run the cef event loop after the timeout + /// [`_cef_browser_process_handler_t::on_schedule_message_pump_work`] for more documentation. + fn schedule_cef_message_loop_work(&self, scheduled_time: Instant); +} + +#[derive(Clone)] +pub(crate) struct WindowSize { + pub(crate) width: usize, + pub(crate) height: usize, +} + +impl WindowSize { + pub(crate) fn new(width: usize, height: usize) -> Self { + Self { width, height } + } +} diff --git a/desktop/src/cef/context.rs b/desktop/src/cef/context.rs new file mode 100644 index 0000000000..a19172e189 --- /dev/null +++ b/desktop/src/cef/context.rs @@ -0,0 +1,146 @@ +use cef::sys::CEF_API_VERSION_LAST; +use cef::{App, BrowserSettings, Client, DictionaryValue, ImplBrowser, ImplBrowserHost, ImplCommandLine, RenderHandler, RequestContext, WindowInfo, browser_host_create_browser_sync, initialize}; +use cef::{Browser, CefString, Settings, api_hash, args::Args, execute_process}; +use thiserror::Error; +use winit::event::WindowEvent; + +use super::input::InputState; +use super::scheme_handler::{FRONTEND_DOMAIN, GRAPHITE_SCHEME}; +use super::{CefEventHandler, input}; + +use super::internal::{AppImpl, ClientImpl, NonBrowserAppImpl, RenderHandlerImpl}; + +pub(crate) struct Setup {} +pub(crate) struct Initialized {} +pub(crate) trait ContextState {} +impl ContextState for Setup {} +impl ContextState for Initialized {} + +pub(crate) struct Context { + args: Args, + pub(crate) browser: Option, + pub(crate) input_state: InputState, + marker: std::marker::PhantomData, +} + +impl Context { + pub(crate) fn new() -> Result, SetupError> { + #[cfg(target_os = "macos")] + let _loader = { + let loader = library_loader::LibraryLoader::new(&std::env::current_exe().unwrap(), false); + assert!(loader.load()); + loader + }; + let _ = api_hash(CEF_API_VERSION_LAST, 0); + + let args = Args::new(); + let cmd = args.as_cmd_line().unwrap(); + let switch = CefString::from("type"); + let is_browser_process = cmd.has_switch(Some(&switch)) != 1; + + if !is_browser_process { + let process_type = CefString::from(&cmd.switch_value(Some(&switch))); + let mut app = NonBrowserAppImpl::app(); + let ret = execute_process(Some(args.as_main_args()), Some(&mut app), std::ptr::null_mut()); + if ret >= 0 { + return Err(SetupError::SubprocessFailed(process_type.to_string())); + } else { + return Err(SetupError::Subprocess); + } + } + + Ok(Context { + args, + browser: None, + input_state: InputState::default(), + marker: std::marker::PhantomData::, + }) + } + + pub(crate) fn init(self, event_handler: impl CefEventHandler) -> Result, InitError> { + let settings = Settings { + windowless_rendering_enabled: 1, + multi_threaded_message_loop: 0, + external_message_pump: 1, + ..Default::default() + }; + + // Attention! Wrapping this in an extra App is necessary, otherwise the program still compiles but segfaults + let mut cef_app = App::new(AppImpl::new(event_handler.clone())); + + let result = initialize(Some(self.args.as_main_args()), Some(&settings), Some(&mut cef_app), std::ptr::null_mut()); + if result != 1 { + return Err(InitError::InitializationFailed); + } + + let render_handler = RenderHandlerImpl::new(event_handler.clone()); + let mut client = Client::new(ClientImpl::new(RenderHandler::new(render_handler))); + + let url = CefString::from(format!("{GRAPHITE_SCHEME}://{FRONTEND_DOMAIN}/").as_str()); + + let window_info = WindowInfo { + windowless_rendering_enabled: 1, + ..Default::default() + }; + + let settings = BrowserSettings { + windowless_frame_rate: 60, + background_color: 0x0, + ..Default::default() + }; + + let browser = browser_host_create_browser_sync( + Some(&window_info), + Some(&mut client), + Some(&url), + Some(&settings), + Option::<&mut DictionaryValue>::None, + Option::<&mut RequestContext>::None, + ); + + Ok(Context { + args: self.args.clone(), + browser, + input_state: self.input_state.clone(), + marker: std::marker::PhantomData::, + }) + } +} + +impl Context { + pub(crate) fn work(&mut self) { + cef::do_message_loop_work(); + } + + pub(crate) fn handle_window_event(&mut self, event: WindowEvent) -> Option { + input::handle_window_event(self, event) + } + + pub(crate) fn notify_of_resize(&self) { + if let Some(browser) = &self.browser { + browser.host().unwrap().was_resized(); + } + } +} + +impl Drop for Context { + fn drop(&mut self) { + if self.browser.is_some() { + cef::shutdown(); + } + } +} + +#[derive(Error, Debug)] +pub(crate) enum SetupError { + #[error("this is the sub process should exit immediately")] + Subprocess, + #[error("subprocess returned non zero exit code")] + SubprocessFailed(String), +} + +#[derive(Error, Debug)] +pub(crate) enum InitError { + #[error("initialization failed")] + InitializationFailed, +} diff --git a/desktop/src/cef/input.rs b/desktop/src/cef/input.rs new file mode 100644 index 0000000000..103838a188 --- /dev/null +++ b/desktop/src/cef/input.rs @@ -0,0 +1,277 @@ +use cef::sys::{cef_event_flags_t, cef_key_event_type_t, cef_mouse_button_type_t}; +use cef::{ImplBrowser, ImplBrowserHost, KeyEvent, KeyEventType, MouseEvent}; +use winit::dpi::PhysicalPosition; +use winit::event::{ElementState, MouseButton, MouseScrollDelta, WindowEvent}; + +use super::context::{Context, Initialized}; + +mod keymap; +use keymap::{ToDomBits, ToVKBits}; + +pub(crate) fn handle_window_event(context: &mut Context, event: WindowEvent) -> Option { + match event { + WindowEvent::CursorMoved { position, .. } => { + if let Some(browser) = &context.browser { + if let Some(host) = browser.host() { + host.set_focus(1); + } + + context.input_state.update_mouse_position(&position); + let mouse_event: MouseEvent = (&context.input_state).into(); + browser.host().unwrap().send_mouse_move_event(Some(&mouse_event), 0); + } + } + WindowEvent::MouseInput { state, button, .. } => { + if let Some(browser) = &context.browser { + if let Some(host) = browser.host() { + host.set_focus(1); + + let mouse_up = match state { + ElementState::Pressed => 0, + ElementState::Released => 1, + }; + + let cef_button = match button { + MouseButton::Left => Some(cef::MouseButtonType::from(cef_mouse_button_type_t::MBT_LEFT)), + MouseButton::Right => Some(cef::MouseButtonType::from(cef_mouse_button_type_t::MBT_RIGHT)), + MouseButton::Middle => Some(cef::MouseButtonType::from(cef_mouse_button_type_t::MBT_MIDDLE)), + MouseButton::Forward => None, //TODO: Handle Forward button + MouseButton::Back => None, //TODO: Handle Back button + _ => None, + }; + + let mut mouse_state = context.input_state.mouse_state.clone(); + match button { + MouseButton::Left => { + mouse_state.left = match state { + ElementState::Pressed => true, + ElementState::Released => false, + } + } + MouseButton::Right => { + mouse_state.right = match state { + ElementState::Pressed => true, + ElementState::Released => false, + } + } + MouseButton::Middle => { + mouse_state.middle = match state { + ElementState::Pressed => true, + ElementState::Released => false, + } + } + _ => {} + }; + context.input_state.update_mouse_state(mouse_state); + + let mouse_event: MouseEvent = (&context.input_state).into(); + + if let Some(button) = cef_button { + host.send_mouse_click_event( + Some(&mouse_event), + button, + mouse_up, + 1, // click count + ); + } + } + } + } + WindowEvent::MouseWheel { delta, phase: _, device_id: _, .. } => { + if let Some(browser) = &context.browser { + if let Some(host) = browser.host() { + let mouse_event = (&context.input_state).into(); + let line_width = 40; //feels about right, TODO: replace with correct value + let line_height = 30; //feels about right, TODO: replace with correct value + let (delta_x, delta_y) = match delta { + MouseScrollDelta::LineDelta(x, y) => (x * line_width as f32, y * line_height as f32), + MouseScrollDelta::PixelDelta(physical_position) => (physical_position.x as f32, physical_position.y as f32), + }; + host.send_mouse_wheel_event(Some(&mouse_event), delta_x as i32, delta_y as i32); + } + } + } + WindowEvent::ModifiersChanged(modifiers) => { + context.input_state.update_modifiers(&modifiers.state()); + } + WindowEvent::KeyboardInput { device_id: _, event, is_synthetic: _ } => { + if let Some(browser) = &context.browser { + if let Some(host) = browser.host() { + host.set_focus(1); + + let (named_key, character) = match &event.logical_key { + winit::keyboard::Key::Named(named_key) => ( + Some(named_key), + match named_key { + winit::keyboard::NamedKey::Space => Some(' '), + winit::keyboard::NamedKey::Enter => Some('\u{000d}'), + _ => None, + }, + ), + winit::keyboard::Key::Character(str) => { + let char = str.chars().next().unwrap_or('\0'); + (None, Some(char)) + } + _ => return None, + }; + + let mut key_event = KeyEvent { + size: size_of::(), + focus_on_editable_field: 1, + modifiers: context.input_state.cef_modifiers(&event.location, event.repeat).raw(), + is_system_key: 0, + ..Default::default() + }; + + if let Some(named_key) = named_key { + key_event.native_key_code = named_key.to_dom_bits(); + key_event.windows_key_code = named_key.to_vk_bits(); + } else if let Some(char) = character { + key_event.native_key_code = char.to_dom_bits(); + key_event.windows_key_code = char.to_vk_bits(); + } + + match event.state { + ElementState::Pressed => { + key_event.type_ = KeyEventType::from(cef_key_event_type_t::KEYEVENT_RAWKEYDOWN); + host.send_key_event(Some(&key_event)); + + if let Some(char) = character { + let mut buf = [0; 2]; + char.encode_utf16(&mut buf); + key_event.character = buf[0]; + let mut buf = [0; 2]; + char.to_lowercase().next().unwrap().encode_utf16(&mut buf); + key_event.unmodified_character = buf[0]; + + key_event.type_ = KeyEventType::from(cef_key_event_type_t::KEYEVENT_CHAR); + host.send_key_event(Some(&key_event)); + } + } + ElementState::Released => { + key_event.type_ = KeyEventType::from(cef_key_event_type_t::KEYEVENT_KEYUP); + host.send_key_event(Some(&key_event)); + } + }; + } + } + } + e => return Some(e), + } + None +} + +#[derive(Default, Clone)] +pub(crate) struct MouseState { + left: bool, + right: bool, + middle: bool, +} + +#[derive(Default, Clone, Debug)] +pub(crate) struct MousePosition { + x: usize, + y: usize, +} + +impl From<&PhysicalPosition> for MousePosition { + fn from(position: &PhysicalPosition) -> Self { + Self { + x: position.x as usize, + y: position.y as usize, + } + } +} + +#[derive(Default, Clone)] +pub(crate) struct InputState { + modifiers: winit::keyboard::ModifiersState, + mouse_position: MousePosition, + mouse_state: MouseState, +} + +impl InputState { + fn update_modifiers(&mut self, modifiers: &winit::keyboard::ModifiersState) { + self.modifiers = *modifiers; + } + + fn update_mouse_position(&mut self, position: &PhysicalPosition) { + self.mouse_position = position.into(); + } + + fn update_mouse_state(&mut self, state: MouseState) { + self.mouse_state = state; + } + + fn cef_modifiers(&self, location: &winit::keyboard::KeyLocation, is_repeat: bool) -> CefModifiers { + CefModifiers::new(self, location, is_repeat) + } + + fn cef_modifiers_mouse_event(&self) -> CefModifiers { + self.cef_modifiers(&winit::keyboard::KeyLocation::Standard, false) + } +} + +impl From for CefModifiers { + fn from(val: InputState) -> Self { + CefModifiers::new(&val, &winit::keyboard::KeyLocation::Standard, false) + } +} + +impl From<&InputState> for MouseEvent { + fn from(val: &InputState) -> Self { + MouseEvent { + x: val.mouse_position.x as i32, + y: val.mouse_position.y as i32, + modifiers: val.cef_modifiers_mouse_event().raw(), + } + } +} + +struct CefModifiers(u32); + +impl CefModifiers { + fn new(input_state: &InputState, location: &winit::keyboard::KeyLocation, is_repeat: bool) -> Self { + let mut inner = 0; + + if input_state.modifiers.shift_key() { + inner |= cef_event_flags_t::EVENTFLAG_SHIFT_DOWN as u32; + } + if input_state.modifiers.control_key() { + inner |= cef_event_flags_t::EVENTFLAG_CONTROL_DOWN as u32; + } + if input_state.modifiers.alt_key() { + inner |= cef_event_flags_t::EVENTFLAG_ALT_DOWN as u32; + } + if input_state.modifiers.super_key() { + inner |= cef_event_flags_t::EVENTFLAG_COMMAND_DOWN as u32; + } + + if input_state.mouse_state.left { + inner |= cef_event_flags_t::EVENTFLAG_LEFT_MOUSE_BUTTON as u32; + } + if input_state.mouse_state.right { + inner |= cef_event_flags_t::EVENTFLAG_RIGHT_MOUSE_BUTTON as u32; + } + if input_state.mouse_state.middle { + inner |= cef_event_flags_t::EVENTFLAG_MIDDLE_MOUSE_BUTTON as u32; + } + + if is_repeat { + inner |= cef_event_flags_t::EVENTFLAG_IS_REPEAT as u32; + } + + inner |= match location { + winit::keyboard::KeyLocation::Left => cef_event_flags_t::EVENTFLAG_IS_LEFT as u32, + winit::keyboard::KeyLocation::Right => cef_event_flags_t::EVENTFLAG_IS_RIGHT as u32, + winit::keyboard::KeyLocation::Numpad => cef_event_flags_t::EVENTFLAG_IS_KEY_PAD as u32, + winit::keyboard::KeyLocation::Standard => 0, + }; + + Self(inner) + } + + fn raw(&self) -> u32 { + self.0 + } +} diff --git a/desktop/src/cef/input/keymap.rs b/desktop/src/cef/input/keymap.rs new file mode 100644 index 0000000000..042b5c9f91 --- /dev/null +++ b/desktop/src/cef/input/keymap.rs @@ -0,0 +1,386 @@ +macro_rules! map_enum { + ($target:expr, $enum:ident, $( ($code:expr, $variant:ident), )+ ) => { + match $target { + $( + $enum::$variant => $code, + )+ + _ => 0, + } + }; +} + +macro_rules! map { + ($target:expr, $( ($code:expr, $variant:literal), )+ ) => { + match $target { + $( + $variant => $code, + )+ + _ => 0, + } + }; +} + +// Windows Virtual keyboard binary representation +pub(crate) trait ToVKBits { + fn to_vk_bits(&self) -> i32; +} + +impl ToVKBits for winit::keyboard::NamedKey { + fn to_vk_bits(&self) -> i32 { + use winit::keyboard::NamedKey; + map_enum!( + self, + NamedKey, + (0x12, Alt), + (0xA5, AltGraph), + (0x14, CapsLock), + (0x11, Control), + (0x90, NumLock), + (0x91, ScrollLock), + (0x10, Shift), + (0x5B, Meta), + (0x5C, Super), + (0x0D, Enter), + (0x09, Tab), + (0x20, Space), + (0x28, ArrowDown), + (0x25, ArrowLeft), + (0x27, ArrowRight), + (0x26, ArrowUp), + (0x23, End), + (0x24, Home), + (0x22, PageDown), + (0x21, PageUp), + (0x08, Backspace), + (0x0C, Clear), + (0xF7, CrSel), + (0x2E, Delete), + (0xF9, EraseEof), + (0xF8, ExSel), + (0x2D, Insert), + (0x1E, Accept), + (0xF6, Attn), + (0x03, Cancel), + (0x5D, ContextMenu), + (0x1B, Escape), + (0x2B, Execute), + (0x2F, Help), + (0x13, Pause), + (0xFA, Play), + (0x5D, Props), + (0x29, Select), + (0xFB, ZoomIn), + (0xFB, ZoomOut), + (0x2C, PrintScreen), + (0x5F, Standby), + (0x1C, Convert), + (0x18, FinalMode), + (0x1F, ModeChange), + (0x1D, NonConvert), + (0xE5, Process), + (0x15, HangulMode), + (0x19, HanjaMode), + (0x17, JunjaMode), + (0x15, KanaMode), + (0x19, KanjiMode), + (0xB0, MediaFastForward), + (0xB3, MediaPause), + (0xB3, MediaPlay), + (0xB3, MediaPlayPause), + (0xB1, MediaRewind), + (0xB2, MediaStop), + (0xB0, MediaTrackNext), + (0xB1, MediaTrackPrevious), + (0x2A, Print), + (0xAE, AudioVolumeDown), + (0xAF, AudioVolumeUp), + (0xAD, AudioVolumeMute), + (0xB6, LaunchApplication1), + (0xB7, LaunchApplication2), + (0xB4, LaunchMail), + (0xB5, LaunchMediaPlayer), + (0xB5, LaunchMusicPlayer), + (0xA6, BrowserBack), + (0xAB, BrowserFavorites), + (0xA7, BrowserForward), + (0xAC, BrowserHome), + (0xA8, BrowserRefresh), + (0xAA, BrowserSearch), + (0xA9, BrowserStop), + (0xFB, ZoomToggle), + (0x70, F1), + (0x71, F2), + (0x72, F3), + (0x73, F4), + (0x74, F5), + (0x75, F6), + (0x76, F7), + (0x77, F8), + (0x78, F9), + (0x79, F10), + (0x7A, F11), + (0x7B, F12), + (0x7C, F13), + (0x7D, F14), + (0x7E, F15), + (0x7F, F16), + (0x80, F17), + (0x81, F18), + (0x82, F19), + (0x83, F20), + (0x84, F21), + (0x85, F22), + (0x86, F23), + (0x87, F24), + ) + } +} + +impl ToVKBits for char { + fn to_vk_bits(&self) -> i32 { + map!( + self, + (0x0041, 'a'), + (0x0042, 'b'), + (0x0043, 'c'), + (0x0044, 'd'), + (0x0045, 'e'), + (0x0046, 'f'), + (0x0047, 'g'), + (0x0048, 'h'), + (0x0049, 'i'), + (0x004a, 'j'), + (0x004b, 'k'), + (0x004c, 'l'), + (0x004d, 'm'), + (0x004e, 'n'), + (0x004f, 'o'), + (0x0050, 'p'), + (0x0051, 'q'), + (0x0052, 'r'), + (0x0053, 's'), + (0x0054, 't'), + (0x0055, 'u'), + (0x0056, 'v'), + (0x0057, 'w'), + (0x0058, 'x'), + (0x0059, 'y'), + (0x005a, 'z'), + (0x0041, 'A'), + (0x0042, 'B'), + (0x0043, 'C'), + (0x0044, 'D'), + (0x0045, 'E'), + (0x0046, 'F'), + (0x0047, 'G'), + (0x0048, 'H'), + (0x0049, 'I'), + (0x004a, 'J'), + (0x004b, 'K'), + (0x004c, 'L'), + (0x004d, 'M'), + (0x004e, 'N'), + (0x004f, 'O'), + (0x0050, 'P'), + (0x0051, 'Q'), + (0x0052, 'R'), + (0x0053, 'S'), + (0x0054, 'T'), + (0x0055, 'U'), + (0x0056, 'V'), + (0x0057, 'W'), + (0x0058, 'X'), + (0x0059, 'Y'), + (0x005a, 'Z'), + (0x0031, '1'), + (0x0032, '2'), + (0x0032, '3'), + (0x0033, '4'), + (0x0034, '5'), + (0x0035, '6'), + (0x0036, '7'), + (0x0037, '8'), + (0x0039, '9'), + (0x0030, '0'), + ) + } +} + +// Chromium dom key binary representation +pub(crate) trait ToDomBits { + fn to_dom_bits(&self) -> i32; +} + +impl ToDomBits for winit::keyboard::NamedKey { + fn to_dom_bits(&self) -> i32 { + use winit::keyboard::NamedKey; + map_enum!( + self, + NamedKey, + (0x0000, Hyper), + (0x0085, Super), + (0x0025, Control), + (0x0032, Shift), + (0x0040, Alt), + (0x0000, Fn), + (0x0000, FnLock), + (0x0024, Enter), + (0x0009, Escape), + (0x0016, Backspace), + (0x0017, Tab), + (0x0041, Space), + (0x0042, CapsLock), + (0x0043, F1), + (0x0044, F2), + (0x0045, F3), + (0x0046, F4), + (0x0047, F5), + (0x0048, F6), + (0x0049, F7), + (0x004a, F8), + (0x004b, F9), + (0x004c, F10), + (0x005f, F11), + (0x0060, F12), + (0x006b, PrintScreen), + (0x004e, ScrollLock), + (0x007f, Pause), + (0x0076, Insert), + (0x006e, Home), + (0x0070, PageUp), + (0x0077, Delete), + (0x0073, End), + (0x0075, PageDown), + (0x0072, ArrowRight), + (0x0071, ArrowLeft), + (0x0074, ArrowDown), + (0x006f, ArrowUp), + (0x004d, NumLock), + (0x0087, ContextMenu), + (0x007c, Power), + (0x00bf, F13), + (0x00c0, F14), + (0x00c1, F15), + (0x00c2, F16), + (0x00c3, F17), + (0x00c4, F18), + (0x00c5, F19), + (0x00c6, F20), + (0x00c7, F21), + (0x00c8, F22), + (0x00c9, F23), + (0x00ca, F24), + (0x008e, Open), + (0x0092, Help), + (0x008c, Select), + (0x0089, Again), + (0x008b, Undo), + (0x0091, Cut), + (0x008d, Copy), + (0x008f, Paste), + (0x0090, Find), + (0x0079, AudioVolumeMute), + (0x007b, AudioVolumeUp), + (0x007a, AudioVolumeDown), + (0x0065, KanaMode), + (0x0064, Convert), + (0x0066, NonConvert), + (0x0000, Props), + (0x00e9, BrightnessUp), + (0x00e8, BrightnessDown), + (0x00d7, MediaPlay), + (0x00d1, MediaPause), + (0x00af, MediaRecord), + (0x00d8, MediaFastForward), + (0x00b0, MediaRewind), + (0x00ab, MediaTrackNext), + (0x00ad, MediaTrackPrevious), + (0x00ae, MediaStop), + (0x00a9, Eject), + (0x00ac, MediaPlayPause), + (0x00a3, LaunchMail), + (0x024d, LaunchScreenSaver), + (0x00e1, BrowserSearch), + (0x00b4, BrowserHome), + (0x00a6, BrowserBack), + (0x00a7, BrowserForward), + (0x0088, BrowserStop), + (0x00b5, BrowserRefresh), + (0x00a4, BrowserFavorites), + (0x017c, ZoomToggle), + (0x00f0, MailReply), + (0x00f1, MailForward), + (0x00ef, MailSend), + ) + } +} + +impl ToDomBits for char { + fn to_dom_bits(&self) -> i32 { + map!( + self, + (0x0026, 'a'), + (0x0038, 'b'), + (0x0036, 'c'), + (0x0028, 'd'), + (0x001a, 'e'), + (0x0029, 'f'), + (0x002a, 'g'), + (0x002b, 'h'), + (0x001f, 'i'), + (0x002c, 'j'), + (0x002d, 'k'), + (0x002e, 'l'), + (0x003a, 'm'), + (0x0039, 'n'), + (0x0020, 'o'), + (0x0021, 'p'), + (0x0018, 'q'), + (0x001b, 'r'), + (0x0027, 's'), + (0x001c, 't'), + (0x001e, 'u'), + (0x0037, 'v'), + (0x0019, 'w'), + (0x0035, 'x'), + (0x001d, 'y'), + (0x0034, 'z'), + (0x0026, 'A'), + (0x0038, 'B'), + (0x0036, 'C'), + (0x0028, 'D'), + (0x001a, 'E'), + (0x0029, 'F'), + (0x002a, 'G'), + (0x002b, 'H'), + (0x001f, 'I'), + (0x002c, 'J'), + (0x002d, 'K'), + (0x002e, 'L'), + (0x003a, 'M'), + (0x0039, 'N'), + (0x0020, 'O'), + (0x0021, 'P'), + (0x0018, 'Q'), + (0x001b, 'R'), + (0x0027, 'S'), + (0x001c, 'T'), + (0x001e, 'U'), + (0x0037, 'V'), + (0x0019, 'W'), + (0x0035, 'X'), + (0x001d, 'Y'), + (0x0034, 'Z'), + (0x000a, '1'), + (0x000b, '2'), + (0x000c, '3'), + (0x000d, '4'), + (0x000e, '5'), + (0x000f, '6'), + (0x0010, '7'), + (0x0011, '8'), + (0x0012, '9'), + (0x0013, '0'), + ) + } +} diff --git a/desktop/src/cef/internal.rs b/desktop/src/cef/internal.rs new file mode 100644 index 0000000000..8473884fd3 --- /dev/null +++ b/desktop/src/cef/internal.rs @@ -0,0 +1,10 @@ +mod app; +mod browser_process_handler; +mod client; +mod non_browser_app; +mod render_handler; + +pub(crate) use app::AppImpl; +pub(crate) use client::ClientImpl; +pub(crate) use non_browser_app::NonBrowserAppImpl; +pub(crate) use render_handler::RenderHandlerImpl; diff --git a/desktop/src/cef/internal/app.rs b/desktop/src/cef/internal/app.rs new file mode 100644 index 0000000000..effed9d492 --- /dev/null +++ b/desktop/src/cef/internal/app.rs @@ -0,0 +1,61 @@ +use cef::rc::{Rc, RcImpl}; +use cef::sys::{_cef_app_t, cef_base_ref_counted_t}; +use cef::{BrowserProcessHandler, ImplApp, SchemeRegistrar, WrapApp}; + +use crate::cef::CefEventHandler; +use crate::cef::scheme_handler::GraphiteSchemeHandlerFactory; + +use super::browser_process_handler::BrowserProcessHandlerImpl; + +pub(crate) struct AppImpl { + object: *mut RcImpl<_cef_app_t, Self>, + event_handler: H, +} +impl AppImpl { + pub(crate) fn new(event_handler: H) -> Self { + Self { + object: std::ptr::null_mut(), + event_handler, + } + } +} + +impl ImplApp for AppImpl { + fn browser_process_handler(&self) -> Option { + Some(BrowserProcessHandler::new(BrowserProcessHandlerImpl::new(self.event_handler.clone()))) + } + + fn on_register_custom_schemes(&self, registrar: Option<&mut SchemeRegistrar>) { + GraphiteSchemeHandlerFactory::register_schemes(registrar); + } + + fn get_raw(&self) -> *mut _cef_app_t { + self.object.cast() + } +} + +impl Clone for AppImpl { + fn clone(&self) -> Self { + unsafe { + let rc_impl = &mut *self.object; + rc_impl.interface.add_ref(); + } + Self { + object: self.object, + event_handler: self.event_handler.clone(), + } + } +} +impl Rc for AppImpl { + fn as_base(&self) -> &cef_base_ref_counted_t { + unsafe { + let base = &*self.object; + std::mem::transmute(&base.cef_object) + } + } +} +impl WrapApp for AppImpl { + fn wrap_rc(&mut self, object: *mut RcImpl<_cef_app_t, Self>) { + self.object = object; + } +} diff --git a/desktop/src/cef/internal/browser_process_handler.rs b/desktop/src/cef/internal/browser_process_handler.rs new file mode 100644 index 0000000000..553540ba3a --- /dev/null +++ b/desktop/src/cef/internal/browser_process_handler.rs @@ -0,0 +1,61 @@ +use std::time::{Duration, Instant}; + +use cef::rc::{Rc, RcImpl}; +use cef::sys::{_cef_browser_process_handler_t, cef_base_ref_counted_t, cef_browser_process_handler_t}; +use cef::{CefString, ImplBrowserProcessHandler, SchemeHandlerFactory, WrapBrowserProcessHandler}; + +use crate::cef::CefEventHandler; +use crate::cef::scheme_handler::{GRAPHITE_SCHEME, GraphiteSchemeHandlerFactory}; + +pub(crate) struct BrowserProcessHandlerImpl { + object: *mut RcImpl, + event_handler: H, +} +impl BrowserProcessHandlerImpl { + pub(crate) fn new(event_handler: H) -> Self { + Self { + object: std::ptr::null_mut(), + event_handler, + } + } +} + +impl ImplBrowserProcessHandler for BrowserProcessHandlerImpl { + fn on_context_initialized(&self) { + cef::register_scheme_handler_factory(Some(&CefString::from(GRAPHITE_SCHEME)), None, Some(&mut SchemeHandlerFactory::new(GraphiteSchemeHandlerFactory::new()))); + } + + fn get_raw(&self) -> *mut _cef_browser_process_handler_t { + self.object.cast() + } + + fn on_schedule_message_pump_work(&self, delay_ms: i64) { + self.event_handler.schedule_cef_message_loop_work(Instant::now() + Duration::from_millis(delay_ms as u64)); + } +} + +impl Clone for BrowserProcessHandlerImpl { + fn clone(&self) -> Self { + unsafe { + let rc_impl = &mut *self.object; + rc_impl.interface.add_ref(); + } + Self { + object: self.object, + event_handler: self.event_handler.clone(), + } + } +} +impl Rc for BrowserProcessHandlerImpl { + fn as_base(&self) -> &cef_base_ref_counted_t { + unsafe { + let base = &*self.object; + std::mem::transmute(&base.cef_object) + } + } +} +impl WrapBrowserProcessHandler for BrowserProcessHandlerImpl { + fn wrap_rc(&mut self, object: *mut RcImpl<_cef_browser_process_handler_t, Self>) { + self.object = object; + } +} diff --git a/desktop/src/cef/internal/client.rs b/desktop/src/cef/internal/client.rs new file mode 100644 index 0000000000..543f9c590d --- /dev/null +++ b/desktop/src/cef/internal/client.rs @@ -0,0 +1,52 @@ +use cef::rc::{Rc, RcImpl}; +use cef::sys::{_cef_client_t, cef_base_ref_counted_t}; +use cef::{ImplClient, RenderHandler, WrapClient}; + +pub(crate) struct ClientImpl { + object: *mut RcImpl<_cef_client_t, Self>, + render_handler: RenderHandler, +} +impl ClientImpl { + pub(crate) fn new(render_handler: RenderHandler) -> Self { + Self { + object: std::ptr::null_mut(), + render_handler, + } + } +} + +impl ImplClient for ClientImpl { + fn render_handler(&self) -> Option { + Some(self.render_handler.clone()) + } + + fn get_raw(&self) -> *mut _cef_client_t { + self.object.cast() + } +} + +impl Clone for ClientImpl { + fn clone(&self) -> Self { + unsafe { + let rc_impl = &mut *self.object; + rc_impl.interface.add_ref(); + } + Self { + object: self.object, + render_handler: self.render_handler.clone(), + } + } +} +impl Rc for ClientImpl { + fn as_base(&self) -> &cef_base_ref_counted_t { + unsafe { + let base = &*self.object; + std::mem::transmute(&base.cef_object) + } + } +} +impl WrapClient for ClientImpl { + fn wrap_rc(&mut self, object: *mut RcImpl<_cef_client_t, Self>) { + self.object = object; + } +} diff --git a/desktop/src/cef/internal/non_browser_app.rs b/desktop/src/cef/internal/non_browser_app.rs new file mode 100644 index 0000000000..04007d729d --- /dev/null +++ b/desktop/src/cef/internal/non_browser_app.rs @@ -0,0 +1,47 @@ +use cef::rc::{Rc, RcImpl}; +use cef::sys::{_cef_app_t, cef_base_ref_counted_t}; +use cef::{App, ImplApp, SchemeRegistrar, WrapApp}; + +use crate::cef::scheme_handler::GraphiteSchemeHandlerFactory; + +pub(crate) struct NonBrowserAppImpl { + object: *mut RcImpl<_cef_app_t, Self>, +} +impl NonBrowserAppImpl { + pub(crate) fn app() -> App { + App::new(Self { object: std::ptr::null_mut() }) + } +} + +impl ImplApp for NonBrowserAppImpl { + fn on_register_custom_schemes(&self, registrar: Option<&mut SchemeRegistrar>) { + GraphiteSchemeHandlerFactory::register_schemes(registrar); + } + + fn get_raw(&self) -> *mut _cef_app_t { + self.object.cast() + } +} + +impl Clone for NonBrowserAppImpl { + fn clone(&self) -> Self { + unsafe { + let rc_impl = &mut *self.object; + rc_impl.interface.add_ref(); + } + Self { object: self.object } + } +} +impl Rc for NonBrowserAppImpl { + fn as_base(&self) -> &cef_base_ref_counted_t { + unsafe { + let base = &*self.object; + std::mem::transmute(&base.cef_object) + } + } +} +impl WrapApp for NonBrowserAppImpl { + fn wrap_rc(&mut self, object: *mut RcImpl<_cef_app_t, Self>) { + self.object = object; + } +} diff --git a/desktop/src/cef/internal/render_handler.rs b/desktop/src/cef/internal/render_handler.rs new file mode 100644 index 0000000000..cc4394daf7 --- /dev/null +++ b/desktop/src/cef/internal/render_handler.rs @@ -0,0 +1,84 @@ +use cef::rc::{Rc, RcImpl}; +use cef::sys::{_cef_render_handler_t, cef_base_ref_counted_t}; +use cef::{Browser, ImplBrowser, ImplBrowserHost, ImplRenderHandler, PaintElementType, Rect, WrapRenderHandler}; + +use crate::FrameBuffer; +use crate::cef::CefEventHandler; + +pub(crate) struct RenderHandlerImpl { + object: *mut RcImpl<_cef_render_handler_t, Self>, + event_handler: H, +} +impl RenderHandlerImpl { + pub(crate) fn new(event_handler: H) -> Self { + Self { + object: std::ptr::null_mut(), + event_handler, + } + } +} +impl ImplRenderHandler for RenderHandlerImpl { + fn view_rect(&self, _browser: Option<&mut Browser>, rect: Option<&mut Rect>) { + if let Some(rect) = rect { + let view = self.event_handler.window_size(); + *rect = Rect { + x: 0, + y: 0, + width: view.width as i32, + height: view.height as i32, + }; + } + } + + fn on_paint( + &self, + browser: Option<&mut Browser>, + _type_: PaintElementType, + _dirty_rect_count: usize, + _dirty_rects: Option<&Rect>, + buffer: *const u8, + width: ::std::os::raw::c_int, + height: ::std::os::raw::c_int, + ) { + let buffer_size = (width * height * 4) as usize; + let buffer_slice = unsafe { std::slice::from_raw_parts(buffer, buffer_size) }; + let frame_buffer = FrameBuffer::new(buffer_slice.to_vec(), width as usize, height as usize).expect("Failed to create frame buffer"); + + let draw_successful = self.event_handler.draw(frame_buffer); + if !draw_successful { + if let Some(browser) = browser { + browser.host().unwrap().was_resized(); + } + } + } + + fn get_raw(&self) -> *mut _cef_render_handler_t { + self.object.cast() + } +} + +impl Clone for RenderHandlerImpl { + fn clone(&self) -> Self { + unsafe { + let rc_impl = &mut *self.object; + rc_impl.interface.add_ref(); + } + Self { + object: self.object, + event_handler: self.event_handler.clone(), + } + } +} +impl Rc for RenderHandlerImpl { + fn as_base(&self) -> &cef_base_ref_counted_t { + unsafe { + let base = &*self.object; + std::mem::transmute(&base.cef_object) + } + } +} +impl WrapRenderHandler for RenderHandlerImpl { + fn wrap_rc(&mut self, object: *mut RcImpl<_cef_render_handler_t, Self>) { + self.object = object; + } +} diff --git a/desktop/src/cef/scheme_handler.rs b/desktop/src/cef/scheme_handler.rs new file mode 100644 index 0000000000..0cbb8bb541 --- /dev/null +++ b/desktop/src/cef/scheme_handler.rs @@ -0,0 +1,223 @@ +use std::cell::RefCell; +use std::ffi::c_int; +use std::ops::DerefMut; +use std::slice::Iter; + +use cef::rc::{Rc, RcImpl}; +use cef::sys::{_cef_resource_handler_t, _cef_scheme_handler_factory_t, cef_base_ref_counted_t, cef_scheme_options_t}; +use cef::{ + Browser, Callback, CefString, Frame, ImplRequest, ImplResourceHandler, ImplResponse, ImplSchemeHandlerFactory, ImplSchemeRegistrar, Request, ResourceHandler, ResourceReadCallback, Response, + SchemeRegistrar, WrapResourceHandler, WrapSchemeHandlerFactory, +}; +use include_dir::{Dir, include_dir}; + +pub(crate) const GRAPHITE_SCHEME: &str = "graphite-static"; +pub(crate) const FRONTEND_DOMAIN: &str = "frontend"; + +pub(crate) struct GraphiteSchemeHandlerFactory { + object: *mut RcImpl<_cef_scheme_handler_factory_t, Self>, +} +impl GraphiteSchemeHandlerFactory { + pub(crate) fn new() -> Self { + Self { object: std::ptr::null_mut() } + } + + pub(crate) fn register_schemes(registrar: Option<&mut SchemeRegistrar>) { + if let Some(registrar) = registrar { + let mut scheme_options = 0; + scheme_options |= cef_scheme_options_t::CEF_SCHEME_OPTION_STANDARD as i32; + scheme_options |= cef_scheme_options_t::CEF_SCHEME_OPTION_FETCH_ENABLED as i32; + scheme_options |= cef_scheme_options_t::CEF_SCHEME_OPTION_SECURE as i32; + scheme_options |= cef_scheme_options_t::CEF_SCHEME_OPTION_CORS_ENABLED as i32; + registrar.add_custom_scheme(Some(&CefString::from(GRAPHITE_SCHEME)), scheme_options); + } + } +} +impl ImplSchemeHandlerFactory for GraphiteSchemeHandlerFactory { + fn create(&self, _browser: Option<&mut Browser>, _frame: Option<&mut Frame>, scheme_name: Option<&CefString>, request: Option<&mut Request>) -> Option { + if let Some(scheme_name) = scheme_name { + if scheme_name.to_string() != GRAPHITE_SCHEME { + return None; + } + if let Some(request) = request { + let url = CefString::from(&request.url()).to_string(); + let path = url.strip_prefix(&format!("{GRAPHITE_SCHEME}://")).unwrap(); + let domain = path.split('/').next().unwrap_or(""); + let path = path.strip_prefix(domain).unwrap_or(""); + let path = path.trim_start_matches('/'); + return match domain { + FRONTEND_DOMAIN => { + if path.is_empty() { + Some(ResourceHandler::new(GraphiteFrontendResourceHandler::new("index.html"))) + } else { + Some(ResourceHandler::new(GraphiteFrontendResourceHandler::new(path))) + } + } + _ => None, + }; + } + return None; + } + None + } + fn get_raw(&self) -> *mut _cef_scheme_handler_factory_t { + self.object.cast() + } +} + +static FRONTEND: Dir = include_dir!("$CARGO_MANIFEST_DIR/../frontend/dist"); + +struct GraphiteFrontendResourceHandler<'a> { + object: *mut RcImpl<_cef_resource_handler_t, Self>, + data: Option>>, + mimetype: Option, +} +impl<'a> GraphiteFrontendResourceHandler<'a> { + pub fn new(path: &str) -> Self { + let file = FRONTEND.get_file(path); + let data = if let Some(file) = file { + Some(RefCell::new(file.contents().iter())) + } else { + tracing::error!("Failed to find asset at path: {}", path); + None + }; + let mimetype = if let Some(file) = file { + let ext = file.path().extension().and_then(|s| s.to_str()).unwrap_or(""); + + // We know what file types will be in the assets this should be fine + match ext { + "html" => Some("text/html".to_string()), + "css" => Some("text/css".to_string()), + "wasm" => Some("application/wasm".to_string()), + "js" => Some("application/javascript".to_string()), + "png" => Some("image/png".to_string()), + "jpg" | "jpeg" => Some("image/jpeg".to_string()), + "svg" => Some("image/svg+xml".to_string()), + "xml" => Some("application/xml".to_string()), + "json" => Some("application/json".to_string()), + "ico" => Some("image/x-icon".to_string()), + "woff" => Some("font/woff".to_string()), + "woff2" => Some("font/woff2".to_string()), + "ttf" => Some("font/ttf".to_string()), + "otf" => Some("font/otf".to_string()), + "webmanifest" => Some("application/manifest+json".to_string()), + "graphite" => Some("application/graphite+json".to_string()), + _ => None, + } + } else { + None + }; + Self { + object: std::ptr::null_mut(), + data, + mimetype, + } + } +} +impl<'a> ImplResourceHandler for GraphiteFrontendResourceHandler<'a> { + fn open(&self, _request: Option<&mut Request>, handle_request: Option<&mut c_int>, _callback: Option<&mut Callback>) -> c_int { + if let Some(handle_request) = handle_request { + *handle_request = 1; + } + 1 + } + + fn response_headers(&self, response: Option<&mut Response>, response_length: Option<&mut i64>, _redirect_url: Option<&mut CefString>) { + if let Some(response_length) = response_length { + *response_length = -1; // Indicating that the length is unknown + } + if let Some(response) = response { + if self.data.is_some() { + if let Some(mimetype) = &self.mimetype { + let cef_mime = CefString::from(mimetype.as_str()); + response.set_mime_type(Some(&cef_mime)); + } else { + response.set_mime_type(None); + } + response.set_status(200); + } else { + response.set_status(404); + response.set_mime_type(Some(&CefString::from("text/plain"))); + } + } + } + + fn read(&self, data_out: *mut u8, bytes_to_read: c_int, bytes_read: Option<&mut c_int>, _callback: Option<&mut ResourceReadCallback>) -> c_int { + let mut read = 0; + + let out = unsafe { std::slice::from_raw_parts_mut(data_out, bytes_to_read as usize) }; + if let Some(data) = &self.data { + let mut data = data.borrow_mut(); + + for (out, &data) in out.iter_mut().zip(data.deref_mut()) { + *out = data; + read += 1; + } + } + + if let Some(bytes_read) = bytes_read { + *bytes_read = read; + } + + if read > 0 { + 1 // Indicating that data was read + } else { + 0 // Indicating no data was read + } + } + + fn get_raw(&self) -> *mut _cef_resource_handler_t { + self.object.cast() + } +} + +impl WrapSchemeHandlerFactory for GraphiteSchemeHandlerFactory { + fn wrap_rc(&mut self, object: *mut RcImpl<_cef_scheme_handler_factory_t, Self>) { + self.object = object; + } +} +impl<'a> WrapResourceHandler for GraphiteFrontendResourceHandler<'a> { + fn wrap_rc(&mut self, object: *mut RcImpl<_cef_resource_handler_t, Self>) { + self.object = object; + } +} + +impl Clone for GraphiteSchemeHandlerFactory { + fn clone(&self) -> Self { + unsafe { + let rc_impl = &mut *self.object; + rc_impl.interface.add_ref(); + } + Self { object: self.object } + } +} +impl<'a> Clone for GraphiteFrontendResourceHandler<'a> { + fn clone(&self) -> Self { + unsafe { + let rc_impl = &mut *self.object; + rc_impl.interface.add_ref(); + } + Self { + object: self.object, + data: self.data.clone(), + mimetype: self.mimetype.clone(), + } + } +} + +impl Rc for GraphiteSchemeHandlerFactory { + fn as_base(&self) -> &cef_base_ref_counted_t { + unsafe { + let base = &*self.object; + std::mem::transmute(&base.cef_object) + } + } +} +impl<'a> Rc for GraphiteFrontendResourceHandler<'a> { + fn as_base(&self) -> &cef_base_ref_counted_t { + unsafe { + let base = &*self.object; + std::mem::transmute(&base.cef_object) + } + } +} diff --git a/desktop/src/main.rs b/desktop/src/main.rs new file mode 100644 index 0000000000..824f469d0a --- /dev/null +++ b/desktop/src/main.rs @@ -0,0 +1,175 @@ +use std::fmt::Debug; +use std::process::exit; +use std::sync::{Arc, Mutex, MutexGuard, PoisonError}; +use std::time::Instant; + +use tracing_subscriber::EnvFilter; +use winit::event_loop::{EventLoop, EventLoopProxy}; + +mod cef; +use cef::Setup; + +mod render; +use render::{FrameBuffer, GraphicsState}; + +mod app; +use app::WinitApp; + +#[derive(Debug)] +pub(crate) enum CustomEvent { + UiUpdate, + ScheduleBrowserWork(Instant), +} + +#[derive(Debug)] +pub(crate) struct WindowState { + width: Option, + height: Option, + ui_frame_buffer: Option, + _viewport_frame_buffer: Option, + graphics_state: Option, + event_loop_proxy: Option>, +} + +impl WindowState { + fn new() -> Self { + Self { + width: None, + height: None, + ui_frame_buffer: None, + _viewport_frame_buffer: None, + graphics_state: None, + event_loop_proxy: None, + } + } + + fn handle(self) -> WindowStateHandle { + WindowStateHandle { inner: Arc::new(Mutex::new(self)) } + } +} + +pub(crate) struct WindowStateHandle { + inner: Arc>, +} + +impl WindowStateHandle { + fn with<'a, P>(&self, p: P) -> Result<(), PoisonError>> + where + P: FnOnce(&mut WindowState), + { + match self.inner.lock() { + Ok(mut guard) => { + p(&mut guard); + Ok(()) + } + Err(_) => todo!("not error handling yet"), + } + } +} + +impl Clone for WindowStateHandle { + fn clone(&self) -> Self { + Self { inner: self.inner.clone() } + } +} + +#[derive(Clone)] +struct CefHandler { + window_state: WindowStateHandle, +} + +impl CefHandler { + fn new(window_state: WindowStateHandle) -> Self { + Self { window_state } + } +} + +impl cef::CefEventHandler for CefHandler { + fn window_size(&self) -> cef::WindowSize { + let mut w = 1; + let mut h = 1; + + self.window_state + .with(|s| { + if let WindowState { + width: Some(width), + height: Some(height), + .. + } = s + { + w = *width; + h = *height; + } + }) + .unwrap(); + + cef::WindowSize::new(w, h) + } + + fn draw(&self, frame_buffer: FrameBuffer) -> bool { + let mut correct_size = true; + self.window_state + .with(|s| { + if let Some(event_loop_proxy) = &s.event_loop_proxy { + let _ = event_loop_proxy.send_event(CustomEvent::UiUpdate); + } + if frame_buffer.width() != s.width.unwrap_or(1) || frame_buffer.height() != s.height.unwrap_or(1) { + correct_size = false; + } else { + s.ui_frame_buffer = Some(frame_buffer); + } + }) + .unwrap(); + + correct_size + } + + fn schedule_cef_message_loop_work(&self, scheduled_time: std::time::Instant) { + self.window_state + .with(|s| { + let Some(event_loop_proxy) = &mut s.event_loop_proxy else { return }; + let _ = event_loop_proxy.send_event(CustomEvent::ScheduleBrowserWork(scheduled_time)); + }) + .unwrap(); + } +} + +fn main() { + tracing_subscriber::fmt().with_env_filter(EnvFilter::from_default_env()).init(); + + let cef_context = match cef::Context::::new() { + Ok(c) => c, + Err(cef::SetupError::Subprocess) => exit(0), + Err(cef::SetupError::SubprocessFailed(t)) => { + tracing::error!("Subprocess of type {t} failed"); + exit(1); + } + }; + + let window_state = WindowState::new().handle(); + + window_state + .with(|s| { + s.width = Some(1200); + s.height = Some(800); + }) + .unwrap(); + + let event_loop = EventLoop::::with_user_event().build().unwrap(); + + window_state.with(|s| s.event_loop_proxy = Some(event_loop.create_proxy())).unwrap(); + + let cef_context = match cef_context.init(CefHandler::new(window_state.clone())) { + Ok(c) => c, + Err(cef::InitError::InitializationFailed) => { + tracing::error!("Cef initialization failed"); + exit(1); + } + }; + + tracing::info!("Cef initialized successfully"); + + let mut winit_app = WinitApp::new(window_state, cef_context); + + event_loop.run_app(&mut winit_app).unwrap(); +} diff --git a/desktop/src/render.rs b/desktop/src/render.rs new file mode 100644 index 0000000000..e6f07b46bc --- /dev/null +++ b/desktop/src/render.rs @@ -0,0 +1,336 @@ +use std::sync::Arc; + +use thiserror::Error; +use winit::window::Window; + +pub(crate) struct FrameBuffer { + buffer: Vec, + width: usize, + height: usize, +} +impl std::fmt::Debug for FrameBuffer { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("WindowState") + .field("width", &self.width) + .field("height", &self.height) + .field("len", &self.buffer.len()) + .finish() + } +} + +#[derive(Error, Debug)] +pub(crate) enum FrameBufferError { + #[error("Invalid buffer size {buffer_size}, expected {expected_size} for width {width} multiplied with height {height} multiplied by 4 channels")] + InvalidSize { buffer_size: usize, expected_size: usize, width: usize, height: usize }, +} + +impl FrameBuffer { + pub(crate) fn new(buffer: Vec, width: usize, height: usize) -> Result { + let fb = Self { buffer, width, height }; + fb.validate_size()?; + Ok(fb) + } + + pub(crate) fn buffer(&self) -> &[u8] { + &self.buffer + } + + pub(crate) fn width(&self) -> usize { + self.width + } + + pub(crate) fn height(&self) -> usize { + self.height + } + + fn validate_size(&self) -> Result<(), FrameBufferError> { + if self.buffer.len() != self.width * self.height * 4 { + Err(FrameBufferError::InvalidSize { + buffer_size: self.buffer.len(), + expected_size: self.width * self.height * 4, + width: self.width, + height: self.height, + }) + } else { + Ok(()) + } + } +} + +#[derive(Debug)] +pub(crate) struct GraphicsState { + surface: wgpu::Surface<'static>, + device: wgpu::Device, + queue: wgpu::Queue, + config: wgpu::SurfaceConfiguration, + texture: Option, + bind_group: Option, + render_pipeline: wgpu::RenderPipeline, + sampler: wgpu::Sampler, +} + +impl GraphicsState { + pub(crate) async fn new(window: Arc) -> Self { + let size = window.inner_size(); + + let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { + backends: wgpu::Backends::PRIMARY, + ..Default::default() + }); + + let surface = instance.create_surface(window).unwrap(); + + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::default(), + compatible_surface: Some(&surface), + force_fallback_adapter: false, + }) + .await + .unwrap(); + + let (device, queue) = adapter + .request_device(&wgpu::DeviceDescriptor { + required_features: wgpu::Features::empty(), + required_limits: wgpu::Limits::default(), + label: None, + memory_hints: Default::default(), + ..Default::default() + }) + .await + .unwrap(); + + let surface_caps = surface.get_capabilities(&adapter); + let surface_format = surface_caps.formats.iter().find(|f| f.is_srgb()).copied().unwrap_or(surface_caps.formats[0]); + + let config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: surface_format, + width: size.width, + height: size.height, + present_mode: surface_caps.present_modes[0], + alpha_mode: surface_caps.alpha_modes[0], + view_formats: vec![], + desired_maximum_frame_latency: 2, + }; + + surface.configure(&device, &config); + + // Create shader module + let shader = device.create_shader_module(wgpu::include_wgsl!("render/fullscreen_texture.wgsl")); + + // Create sampler + let sampler = device.create_sampler(&wgpu::SamplerDescriptor { + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Nearest, + mipmap_filter: wgpu::FilterMode::Nearest, + ..Default::default() + }); + + let texture_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + multisampled: false, + view_dimension: wgpu::TextureViewDimension::D2, + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + ], + label: Some("texture_bind_group_layout"), + }); + + let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Render Pipeline Layout"), + bind_group_layouts: &[&texture_bind_group_layout], + push_constant_ranges: &[], + }); + + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Render Pipeline"), + layout: Some(&render_pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: Some("vs_main"), + buffers: &[], + compilation_options: Default::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: Some("fs_main"), + targets: &[Some(wgpu::ColorTargetState { + format: config.format, + blend: Some(wgpu::BlendState::REPLACE), + write_mask: wgpu::ColorWrites::ALL, + })], + compilation_options: Default::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: Some(wgpu::Face::Back), + polygon_mode: wgpu::PolygonMode::Fill, + unclipped_depth: false, + conservative: false, + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview: None, + cache: None, + }); + + let mut graphics_state = Self { + surface, + device, + queue, + config, + texture: None, + bind_group: None, + render_pipeline, + sampler, + }; + + // Initialize with a test pattern so we always have something to render + let width = 800; + let height = 600; + let initial_data = vec![34u8; width * height * 4]; // Gray texture #222222FF + + let fb = FrameBuffer::new(initial_data, width, height) + .map_err(|e| { + panic!("Failed to create initial FrameBuffer: {}", e); + }) + .unwrap(); + + graphics_state.update_texture(&fb); + + graphics_state + } + + pub(crate) fn resize(&mut self, width: usize, height: usize) { + if width > 0 && height > 0 && (self.config.width != width as u32 || self.config.height != height as u32) { + self.config.width = width as u32; + self.config.height = height as u32; + self.surface.configure(&self.device, &self.config); + } + } + + pub(crate) fn update_texture(&mut self, frame_buffer: &FrameBuffer) { + let data = frame_buffer.buffer(); + let width = frame_buffer.width() as u32; + let height = frame_buffer.height() as u32; + + if width > 0 && height > 0 && (self.config.width != width || self.config.height != height) { + self.config.width = width; + self.config.height = height; + self.surface.configure(&self.device, &self.config); + } + + let texture = self.device.create_texture(&wgpu::TextureDescriptor { + label: Some("CEF Texture"), + size: wgpu::Extent3d { + width, + height, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Bgra8UnormSrgb, + usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + view_formats: &[], + }); + + self.queue.write_texture( + wgpu::TexelCopyTextureInfo { + texture: &texture, + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + aspect: wgpu::TextureAspect::All, + }, + data, + wgpu::TexelCopyBufferLayout { + offset: 0, + bytes_per_row: Some(4 * width), + rows_per_image: Some(height), + }, + wgpu::Extent3d { + width, + height, + depth_or_array_layers: 1, + }, + ); + + let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default()); + + let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &self.render_pipeline.get_bind_group_layout(0), + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&texture_view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&self.sampler), + }, + ], + label: Some("texture_bind_group"), + }); + + self.texture = Some(texture); + self.bind_group = Some(bind_group); + } + + pub(crate) fn render(&mut self) -> Result<(), wgpu::SurfaceError> { + let output = self.surface.get_current_texture()?; + let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default()); + + let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("Render Encoder") }); + + { + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("Render Pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.01, g: 0.01, b: 0.01, a: 1.0 }), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + occlusion_query_set: None, + timestamp_writes: None, + }); + + render_pass.set_pipeline(&self.render_pipeline); + if let Some(bind_group) = &self.bind_group { + render_pass.set_bind_group(0, bind_group, &[]); + render_pass.draw(0..6, 0..1); // Draw 3 vertices for fullscreen triangle + } else { + tracing::warn!("No bind group available - showing clear color only"); + } + } + self.queue.submit(std::iter::once(encoder.finish())); + output.present(); + + Ok(()) + } +} diff --git a/desktop/src/render/fullscreen_texture.wgsl b/desktop/src/render/fullscreen_texture.wgsl new file mode 100644 index 0000000000..216d820063 --- /dev/null +++ b/desktop/src/render/fullscreen_texture.wgsl @@ -0,0 +1,36 @@ +struct VertexOutput { + @builtin(position) clip_position: vec4, + @location(0) tex_coords: vec2, +} + +@vertex +fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput { + var out: VertexOutput; + + let pos = array( + // 1st triangle + vec2f( -1.0, -1.0), // center + vec2f( 1.0, -1.0), // right, center + vec2f( -1.0, 1.0), // center, top + + // 2nd triangle + vec2f( -1.0, 1.0), // center, top + vec2f( 1.0, -1.0), // right, center + vec2f( 1.0, 1.0), // right, top + ); + let xy = pos[vertex_index]; + out.clip_position = vec4f(xy , 0.0, 1.0); + let coords = (xy / 2. + 0.5); + out.tex_coords = vec2f(coords.x, 1. - coords.y); + return out; +} + +@group(0) @binding(0) +var t_diffuse: texture_2d; +@group(0) @binding(1) +var s_diffuse: sampler; + +@fragment +fn fs_main(in: VertexOutput) -> @location(0) vec4 { + return textureSample(t_diffuse, s_diffuse, in.tex_coords); +} diff --git a/editor/Cargo.toml b/editor/Cargo.toml index 1f6af599ca..a39aa5ef2e 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -2,7 +2,7 @@ name = "graphite-editor" publish = false version = "0.0.0" -rust-version = "1.85" +rust-version = "1.88" authors = ["Graphite Authors "] edition = "2024" readme = "../README.md" diff --git a/editor/src/messages/input_mapper/input_mapper_message.rs b/editor/src/messages/input_mapper/input_mapper_message.rs index b09d09e309..bfcda26c73 100644 --- a/editor/src/messages/input_mapper/input_mapper_message.rs +++ b/editor/src/messages/input_mapper/input_mapper_message.rs @@ -19,5 +19,6 @@ pub enum InputMapperMessage { // Messages PointerMove, + PointerShake, WheelScroll, } diff --git a/editor/src/messages/input_mapper/input_mappings.rs b/editor/src/messages/input_mapper/input_mappings.rs index 02dafca170..71fb27a01c 100644 --- a/editor/src/messages/input_mapper/input_mappings.rs +++ b/editor/src/messages/input_mapper/input_mappings.rs @@ -54,14 +54,15 @@ pub fn input_mappings() -> Mapping { entry!(KeyDown(KeyZ); modifiers=[Accel, MouseLeft], action_dispatch=DocumentMessage::Noop), // // NodeGraphMessage - entry!(KeyDown(MouseLeft); action_dispatch=NodeGraphMessage::PointerDown {shift_click: false, control_click: false, alt_click: false, right_click: false}), - entry!(KeyDown(MouseLeft); modifiers=[Shift], action_dispatch=NodeGraphMessage::PointerDown {shift_click: true, control_click: false, alt_click: false, right_click: false}), - entry!(KeyDown(MouseLeft); modifiers=[Accel], action_dispatch=NodeGraphMessage::PointerDown {shift_click: false, control_click: true, alt_click: false, right_click: false}), - entry!(KeyDown(MouseLeft); modifiers=[Shift, Accel], action_dispatch=NodeGraphMessage::PointerDown {shift_click: true, control_click: true, alt_click: false, right_click: false}), - entry!(KeyDown(MouseLeft); modifiers=[Alt], action_dispatch=NodeGraphMessage::PointerDown {shift_click: false, control_click: false, alt_click: true, right_click: false}), - entry!(KeyDown(MouseRight); action_dispatch=NodeGraphMessage::PointerDown {shift_click: false, control_click: false, alt_click: false, right_click: true}), + entry!(KeyDown(MouseLeft); action_dispatch=NodeGraphMessage::PointerDown { shift_click: false, control_click: false, alt_click: false, right_click: false }), + entry!(KeyDown(MouseLeft); modifiers=[Shift], action_dispatch=NodeGraphMessage::PointerDown { shift_click: true, control_click: false, alt_click: false, right_click: false }), + entry!(KeyDown(MouseLeft); modifiers=[Accel], action_dispatch=NodeGraphMessage::PointerDown { shift_click: false, control_click: true, alt_click: false, right_click: false }), + entry!(KeyDown(MouseLeft); modifiers=[Shift, Accel], action_dispatch=NodeGraphMessage::PointerDown { shift_click: true, control_click: true, alt_click: false, right_click: false }), + entry!(KeyDown(MouseLeft); modifiers=[Alt], action_dispatch=NodeGraphMessage::PointerDown { shift_click: false, control_click: false, alt_click: true, right_click: false }), + entry!(KeyDown(MouseRight); action_dispatch=NodeGraphMessage::PointerDown { shift_click: false, control_click: false, alt_click: false, right_click: true }), entry!(DoubleClick(MouseButton::Left); action_dispatch=NodeGraphMessage::EnterNestedNetwork), - entry!(PointerMove; refresh_keys=[Shift], action_dispatch=NodeGraphMessage::PointerMove {shift: Shift}), + entry!(PointerMove; refresh_keys=[Shift], action_dispatch=NodeGraphMessage::PointerMove { shift: Shift }), + entry!(PointerShake; action_dispatch=NodeGraphMessage::ShakeNode), entry!(KeyUp(MouseLeft); action_dispatch=NodeGraphMessage::PointerUp), entry!(KeyDown(Delete); modifiers=[Accel], action_dispatch=NodeGraphMessage::DeleteSelectedNodes { delete_children: false }), entry!(KeyDown(Backspace); modifiers=[Accel], action_dispatch=NodeGraphMessage::DeleteSelectedNodes { delete_children: false }), @@ -417,7 +418,7 @@ pub fn input_mappings() -> Mapping { entry!(KeyDown(Tab); modifiers=[Control], action_dispatch=PortfolioMessage::NextDocument), entry!(KeyDown(Tab); modifiers=[Control, Shift], action_dispatch=PortfolioMessage::PrevDocument), entry!(KeyDown(KeyW); modifiers=[Accel], action_dispatch=PortfolioMessage::CloseActiveDocumentWithConfirmation), - entry!(KeyDown(KeyW); modifiers=[Accel,Alt], action_dispatch=PortfolioMessage::CloseAllDocumentsWithConfirmation), + entry!(KeyDown(KeyW); modifiers=[Accel, Alt], action_dispatch=PortfolioMessage::CloseAllDocumentsWithConfirmation), entry!(KeyDown(KeyO); modifiers=[Accel], action_dispatch=PortfolioMessage::OpenDocument), entry!(KeyDown(KeyI); modifiers=[Accel], action_dispatch=PortfolioMessage::Import), entry!(KeyDown(KeyX); modifiers=[Accel], action_dispatch=PortfolioMessage::Cut { clipboard: Clipboard::Device }), @@ -440,7 +441,7 @@ pub fn input_mappings() -> Mapping { entry!(KeyDown(Space); modifiers=[Shift], action_dispatch=AnimationMessage::ToggleLivePreview), entry!(KeyDown(Home); modifiers=[Shift], action_dispatch=AnimationMessage::RestartAnimation), ]; - let (mut key_up, mut key_down, mut key_up_no_repeat, mut key_down_no_repeat, mut double_click, mut wheel_scroll, mut pointer_move) = mappings; + let (mut key_up, mut key_down, mut key_up_no_repeat, mut key_down_no_repeat, mut double_click, mut wheel_scroll, mut pointer_move, mut pointer_shake) = mappings; let sort = |list: &mut KeyMappingEntries| list.0.sort_by(|a, b| b.modifiers.count_ones().cmp(&a.modifiers.count_ones())); // Sort the sublists of `key_up`, `key_down`, `key_up_no_repeat`, and `key_down_no_repeat` @@ -457,6 +458,8 @@ pub fn input_mappings() -> Mapping { sort(&mut wheel_scroll); // Sort `pointer_move` sort(&mut pointer_move); + // Sort `pointer_shake` + sort(&mut pointer_shake); Mapping { key_up, @@ -466,6 +469,7 @@ pub fn input_mappings() -> Mapping { double_click, wheel_scroll, pointer_move, + pointer_shake, } } diff --git a/editor/src/messages/input_mapper/utility_types/macros.rs b/editor/src/messages/input_mapper/utility_types/macros.rs index 3fabbbeb2d..11ad50dace 100644 --- a/editor/src/messages/input_mapper/utility_types/macros.rs +++ b/editor/src/messages/input_mapper/utility_types/macros.rs @@ -90,6 +90,7 @@ macro_rules! mapping { let mut double_click = KeyMappingEntries::mouse_buttons_arrays(); let mut wheel_scroll = KeyMappingEntries::new(); let mut pointer_move = KeyMappingEntries::new(); + let mut pointer_shake = KeyMappingEntries::new(); $( // Each of the many entry slices, one specified per action @@ -104,6 +105,7 @@ macro_rules! mapping { InputMapperMessage::DoubleClick(key) => &mut double_click[key as usize], InputMapperMessage::WheelScroll => &mut wheel_scroll, InputMapperMessage::PointerMove => &mut pointer_move, + InputMapperMessage::PointerShake => &mut pointer_shake, }; // Push each entry to the corresponding `KeyMappingEntries` list for its input type corresponding_list.push(entry.clone()); @@ -111,7 +113,7 @@ macro_rules! mapping { } )* - (key_up, key_down, key_up_no_repeat, key_down_no_repeat, double_click, wheel_scroll, pointer_move) + (key_up, key_down, key_up_no_repeat, key_down_no_repeat, double_click, wheel_scroll, pointer_move, pointer_shake) }}; } diff --git a/editor/src/messages/input_mapper/utility_types/misc.rs b/editor/src/messages/input_mapper/utility_types/misc.rs index 0c9a220529..7733d6a27f 100644 --- a/editor/src/messages/input_mapper/utility_types/misc.rs +++ b/editor/src/messages/input_mapper/utility_types/misc.rs @@ -14,6 +14,7 @@ pub struct Mapping { pub double_click: [KeyMappingEntries; NUMBER_OF_MOUSE_BUTTONS], pub wheel_scroll: KeyMappingEntries, pub pointer_move: KeyMappingEntries, + pub pointer_shake: KeyMappingEntries, } impl Default for Mapping { @@ -47,6 +48,7 @@ impl Mapping { InputMapperMessage::DoubleClick(key) => &self.double_click[*key as usize], InputMapperMessage::WheelScroll => &self.wheel_scroll, InputMapperMessage::PointerMove => &self.pointer_move, + InputMapperMessage::PointerShake => &self.pointer_shake, } } @@ -59,6 +61,7 @@ impl Mapping { InputMapperMessage::DoubleClick(key) => &mut self.double_click[*key as usize], InputMapperMessage::WheelScroll => &mut self.wheel_scroll, InputMapperMessage::PointerMove => &mut self.pointer_move, + InputMapperMessage::PointerShake => &mut self.pointer_shake, } } } diff --git a/editor/src/messages/input_preprocessor/input_preprocessor_message.rs b/editor/src/messages/input_preprocessor/input_preprocessor_message.rs index 53347542fc..dcfbac728a 100644 --- a/editor/src/messages/input_preprocessor/input_preprocessor_message.rs +++ b/editor/src/messages/input_preprocessor/input_preprocessor_message.rs @@ -12,6 +12,7 @@ pub enum InputPreprocessorMessage { PointerDown { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys }, PointerMove { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys }, PointerUp { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys }, + PointerShake { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys }, CurrentTime { timestamp: u64 }, WheelScroll { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys }, } diff --git a/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs b/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs index 16f9dca9cf..1a8962d974 100644 --- a/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs +++ b/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs @@ -97,6 +97,14 @@ impl MessageHandler f self.translate_mouse_event(mouse_state, false, responses); } + InputPreprocessorMessage::PointerShake { editor_mouse_state, modifier_keys } => { + self.update_states_of_modifier_keys(modifier_keys, keyboard_platform, responses); + + let mouse_state = editor_mouse_state.to_mouse_state(&self.viewport_bounds); + self.mouse.position = mouse_state.position; + + responses.add(InputMapperMessage::PointerShake); + } InputPreprocessorMessage::CurrentTime { timestamp } => { responses.add(AnimationMessage::SetTime { time: timestamp as f64 }); self.time = timestamp; diff --git a/editor/src/messages/layout/utility_types/widgets/input_widgets.rs b/editor/src/messages/layout/utility_types/widgets/input_widgets.rs index 893b301cd8..983e7804fd 100644 --- a/editor/src/messages/layout/utility_types/widgets/input_widgets.rs +++ b/editor/src/messages/layout/utility_types/widgets/input_widgets.rs @@ -332,6 +332,9 @@ pub enum NumberInputMode { pub struct NodeCatalog { pub disabled: bool, + #[serde(rename = "initialSearchTerm")] + pub intial_search: String, + // Callbacks #[serde(skip)] #[derivative(Debug = "ignore", PartialEq = "ignore")] diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 91303f8d97..422d5bb111 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -182,8 +182,6 @@ impl MessageHandler> for DocumentMes device_pixel_ratio, } = context; - let selected_nodes_bounding_box_viewport = self.network_interface.selected_nodes_bounding_box_viewport(&self.breadcrumb_network_path); - let selected_visible_layers_bounding_box_viewport = self.selected_visible_layers_bounding_box_viewport(); match message { // Sub-messages DocumentMessage::Navigation(message) => { @@ -191,11 +189,6 @@ impl MessageHandler> for DocumentMes network_interface: &mut self.network_interface, breadcrumb_network_path: &self.breadcrumb_network_path, ipp, - selection_bounds: if self.graph_view_overlay_open { - selected_nodes_bounding_box_viewport - } else { - selected_visible_layers_bounding_box_viewport - }, document_ptz: &mut self.document_ptz, graph_view_overlay_open: self.graph_view_overlay_open, preferences, @@ -259,7 +252,7 @@ impl MessageHandler> for DocumentMes AlignAxis::X => DVec2::X, AlignAxis::Y => DVec2::Y, }; - let Some(combined_box) = self.selected_visible_layers_bounding_box_viewport() else { + let Some(combined_box) = self.network_interface.selected_layers_artwork_bounding_box_viewport() else { return; }; @@ -486,7 +479,7 @@ impl MessageHandler> for DocumentMes FlipAxis::X => DVec2::new(-1., 1.), FlipAxis::Y => DVec2::new(1., -1.), }; - if let Some([min, max]) = self.selected_visible_and_unlock_layers_bounding_box_viewport() { + if let Some([min, max]) = self.network_interface.selected_unlocked_layers_bounding_box_viewport() { let center = (max + min) / 2.; let bbox_trans = DAffine2::from_translation(-center); let mut added_transaction = false; @@ -506,7 +499,7 @@ impl MessageHandler> for DocumentMes } DocumentMessage::RotateSelectedLayers { degrees } => { // Get the bounding box of selected layers in viewport space - if let Some([min, max]) = self.selected_visible_and_unlock_layers_bounding_box_viewport() { + if let Some([min, max]) = self.network_interface.selected_unlocked_layers_bounding_box_viewport() { // Calculate the center of the bounding box to use as rotation pivot let center = (max + min) / 2.; // Transform that moves pivot point to origin @@ -1063,13 +1056,13 @@ impl MessageHandler> for DocumentMes self.selected_layers_reorder(relative_index_offset, responses); } DocumentMessage::ClipLayer { id } => { - let layer = LayerNodeIdentifier::new(id, &self.network_interface, &[]); + let layer = LayerNodeIdentifier::new(id, &self.network_interface); responses.add(DocumentMessage::AddTransaction); responses.add(GraphOperationMessage::ClipModeToggle { layer }); } DocumentMessage::SelectLayer { id, ctrl, shift } => { - let layer = LayerNodeIdentifier::new(id, &self.network_interface, &[]); + let layer = LayerNodeIdentifier::new(id, &self.network_interface); let mut nodes = vec![]; @@ -1266,7 +1259,7 @@ impl MessageHandler> for DocumentMes responses.add(OverlaysMessage::Draw); } DocumentMessage::ToggleLayerExpansion { id, recursive } => { - let layer = LayerNodeIdentifier::new(id, &self.network_interface, &[]); + let layer = LayerNodeIdentifier::new(id, &self.network_interface); let metadata = self.metadata(); let is_collapsed = self.collapsed.0.contains(&layer); @@ -1323,7 +1316,7 @@ impl MessageHandler> for DocumentMes self.network_interface.document_network().nodes.contains_key(node_id)) .filter_map(|(node_id, click_targets)| { self.network_interface.is_layer(&node_id, &[]).then(|| { - let layer = LayerNodeIdentifier::new(node_id, &self.network_interface, &[]); + let layer = LayerNodeIdentifier::new(node_id, &self.network_interface); (layer, click_targets) }) }) @@ -1708,31 +1701,6 @@ impl DocumentMessageHandler { .last() } - /// Get the combined bounding box of the click targets of the selected visible layers in viewport space - pub fn selected_visible_layers_bounding_box_viewport(&self) -> Option<[DVec2; 2]> { - self.network_interface - .selected_nodes() - .selected_visible_layers(&self.network_interface) - .filter_map(|layer| self.metadata().bounding_box_viewport(layer)) - .reduce(graphene_std::renderer::Quad::combine_bounds) - } - - pub fn selected_visible_and_unlock_layers_bounding_box_viewport(&self) -> Option<[DVec2; 2]> { - self.network_interface - .selected_nodes() - .selected_visible_and_unlocked_layers(&self.network_interface) - .filter_map(|layer| self.metadata().bounding_box_viewport(layer)) - .reduce(graphene_std::renderer::Quad::combine_bounds) - } - - pub fn selected_visible_and_unlock_layers_bounding_box_document(&self) -> Option<[DVec2; 2]> { - self.network_interface - .selected_nodes() - .selected_visible_and_unlocked_layers(&self.network_interface) - .map(|layer| self.metadata().nonzero_bounding_box(layer)) - .reduce(graphene_std::renderer::Quad::combine_bounds) - } - pub fn document_network(&self) -> &NodeNetwork { self.network_interface.document_network() } @@ -2741,7 +2709,22 @@ impl DocumentMessageHandler { .tooltip("Add an operation to the end of this layer's chain of nodes") .disabled(!has_selection || has_multiple_selection) .popover_layout({ - let node_chooser = NodeCatalog::new() + // Showing only compatible types + let compatible_type = selected_layer.and_then(|layer| { + let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &self.network_interface); + let node_type = graph_layer.horizontal_layer_flow().nth(1); + if let Some(node_id) = node_type { + let (output_type, _) = self.network_interface.output_type(&node_id, 0, &self.selection_network_path); + Some(format!("type:{}", output_type.nested_type())) + } else { + None + } + }); + + let mut node_chooser = NodeCatalog::new(); + node_chooser.intial_search = compatible_type.unwrap_or("".to_string()); + + let node_chooser = node_chooser .on_update(move |node_type| { if let Some(layer) = selected_layer { NodeGraphMessage::CreateNodeInLayerWithTransaction { diff --git a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs index c7f6fc12cb..b7ea613323 100644 --- a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs +++ b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs @@ -119,7 +119,7 @@ impl MessageHandler> for let primary_input = artboard.inputs.first().expect("Artboard should have a primary input").clone(); if let NodeInput::Node { node_id, .. } = &primary_input { if network_interface.is_layer(node_id, &[]) && !network_interface.is_artboard(node_id, &[]) { - network_interface.move_layer_to_stack(LayerNodeIdentifier::new(*node_id, network_interface, &[]), artboard_layer, 0, &[]); + network_interface.move_layer_to_stack(LayerNodeIdentifier::new(*node_id, network_interface), artboard_layer, 0, &[]); } else { network_interface.disconnect_input(&InputConnector::node(artboard_layer.to_node(), 0), &[]); network_interface.set_input(&InputConnector::node(id, 0), primary_input, &[]); diff --git a/editor/src/messages/portfolio/document/graph_operation/utility_types.rs b/editor/src/messages/portfolio/document/graph_operation/utility_types.rs index 1f6bce9e9e..85cce0c5c8 100644 --- a/editor/src/messages/portfolio/document/graph_operation/utility_types.rs +++ b/editor/src/messages/portfolio/document/graph_operation/utility_types.rs @@ -124,7 +124,7 @@ impl<'a> ModifyInputsContext<'a> { pub fn create_layer(&mut self, new_id: NodeId) -> LayerNodeIdentifier { let new_merge_node = resolve_document_node_type("Merge").expect("Merge node").default_node_template(); self.network_interface.insert_node(new_id, new_merge_node, &[]); - LayerNodeIdentifier::new(new_id, self.network_interface, &[]) + LayerNodeIdentifier::new(new_id, self.network_interface) } /// Creates an artboard as the primary export for the document network @@ -138,7 +138,7 @@ impl<'a> ModifyInputsContext<'a> { Some(NodeInput::value(TaggedValue::Bool(artboard.clip), false)), ]); self.network_interface.insert_node(new_id, artboard_node_template, &[]); - LayerNodeIdentifier::new(new_id, self.network_interface, &[]) + LayerNodeIdentifier::new(new_id, self.network_interface) } pub fn insert_boolean_data(&mut self, operation: graphene_std::path_bool::BooleanOperation, layer: LayerNodeIdentifier) { @@ -236,7 +236,7 @@ impl<'a> ModifyInputsContext<'a> { self.layer_node.or_else(|| { let export_node = self.network_interface.document_network().exports.first().and_then(|export| export.as_node())?; if self.network_interface.is_layer(&export_node, &[]) { - Some(LayerNodeIdentifier::new(export_node, self.network_interface, &[])) + Some(LayerNodeIdentifier::new(export_node, self.network_interface)) } else { None } diff --git a/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs b/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs index c1128f0080..e01e1195e1 100644 --- a/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs +++ b/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs @@ -18,7 +18,6 @@ pub struct NavigationMessageContext<'a> { pub network_interface: &'a mut NodeNetworkInterface, pub breadcrumb_network_path: &'a [NodeId], pub ipp: &'a InputPreprocessorMessageHandler, - pub selection_bounds: Option<[DVec2; 2]>, pub document_ptz: &'a mut PTZ, pub graph_view_overlay_open: bool, pub preferences: &'a PreferencesMessageHandler, @@ -39,7 +38,6 @@ impl MessageHandler> for Navigat network_interface, breadcrumb_network_path, ipp, - selection_bounds, document_ptz, graph_view_overlay_open, preferences, @@ -386,9 +384,16 @@ impl MessageHandler> for Navigat responses.add(DocumentMessage::PTZUpdate); responses.add(NodeGraphMessage::SetGridAlignedEdges); } + // Fully zooms in on the selected NavigationMessage::FitViewportToSelection => { + let selection_bounds = if graph_view_overlay_open { + network_interface.selected_nodes_bounding_box_viewport(breadcrumb_network_path) + } else { + network_interface.selected_layers_artwork_bounding_box_viewport() + }; + if let Some(bounds) = selection_bounds { - let Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else { + let Some(ptz) = get_ptz(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else { log::error!("Could not get node graph PTZ in FitViewportToSelection"); return; }; diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs index a730f47cad..bd6c77d467 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs @@ -16,6 +16,7 @@ pub enum NodeGraphMessage { nodes: Vec<(NodeId, NodeTemplate)>, new_ids: HashMap, }, + AddPathNode, AddImport, AddExport, Init, @@ -81,6 +82,9 @@ pub enum NodeGraphMessage { node_id: NodeId, parent: LayerNodeIdentifier, }, + SetChainPosition { + node_id: NodeId, + }, PasteNodes { serialized_nodes: String, }, @@ -97,6 +101,7 @@ pub enum NodeGraphMessage { PointerOutsideViewport { shift: Key, }, + ShakeNode, RemoveImport { import_index: usize, }, diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs index d7391227c9..8263edbace 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs @@ -10,20 +10,24 @@ use crate::messages::portfolio::document::node_graph::utility_types::{ContextMen use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::portfolio::document::utility_types::misc::GroupFolderType; use crate::messages::portfolio::document::utility_types::network_interface::{ - self, InputConnector, NodeNetworkInterface, NodeTemplate, NodeTypePersistentMetadata, OutputConnector, Previewing, TypeSource, + self, FlowType, InputConnector, NodeNetworkInterface, NodeTemplate, NodeTypePersistentMetadata, OutputConnector, Previewing, TypeSource, }; use crate::messages::portfolio::document::utility_types::nodes::{CollapsedLayers, LayerPanelEntry}; use crate::messages::portfolio::document::utility_types::wires::{GraphWireStyle, WirePath, WirePathUpdate, build_vector_wire}; use crate::messages::prelude::*; use crate::messages::tool::common_functionality::auto_panning::AutoPanning; -use crate::messages::tool::common_functionality::graph_modification_utils::get_clip_mode; +use crate::messages::tool::common_functionality::graph_modification_utils::{self, get_clip_mode}; use crate::messages::tool::tool_messages::tool_prelude::{Key, MouseMotion}; use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; +use bezier_rs::Subpath; use glam::{DAffine2, DVec2, IVec2}; +use graph_craft::document::value::TaggedValue; use graph_craft::document::{DocumentNodeImplementation, NodeId, NodeInput}; use graph_craft::proto::GraphErrors; use graphene_std::math::math_ext::QuadExt; +use graphene_std::vector::misc::subpath_to_kurbo_bezpath; use graphene_std::*; +use kurbo::{Line, Point}; use renderer::Quad; use std::cmp::Ordering; @@ -55,6 +59,8 @@ pub struct NodeGraphMessageHandler { /// If dragging the selected nodes, this stores the starting position both in viewport and node graph coordinates, /// plus a flag indicating if it has been dragged since the mousedown began. pub drag_start: Option<(DragStart, bool)>, + // Store the selected chain nodes on drag start so they can be reconnected if shaken + pub drag_start_chain_nodes: Vec, /// If dragging the background to create a box selection, this stores its starting point in node graph coordinates, /// plus a flag indicating if it has been dragged since the mousedown began. box_selection_start: Option<(DVec2, bool)>, @@ -119,6 +125,38 @@ impl<'a> MessageHandler> for NodeG responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![new_layer_id] }); } + NodeGraphMessage::AddPathNode => { + let selected_nodes = network_interface.selected_nodes(); + let mut selected_layers = selected_nodes.selected_layers(network_interface.document_metadata()); + let first_layer = selected_layers.next(); + let second_layer = selected_layers.next(); + let has_single_selection = first_layer.is_some() && second_layer.is_none(); + + let compatible_type = first_layer.and_then(|layer| { + let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &network_interface); + graph_layer.horizontal_layer_flow().nth(1).and_then(|node_id| { + let (output_type, _) = network_interface.output_type(&node_id, 0, &[]); + Some(format!("type:{}", output_type.nested_type())) + }) + }); + + let is_compatible = compatible_type.as_deref() == Some("type:Instances"); + + if first_layer.is_some() && has_single_selection && is_compatible { + if let Some(layer) = first_layer { + let node_type = "Path".to_string(); + let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &network_interface); + let is_modifiable = matches!(graph_layer.find_input("Path", 1), Some(TaggedValue::VectorModification(_))); + if !is_modifiable { + responses.add(NodeGraphMessage::CreateNodeInLayerWithTransaction { + node_type: node_type.clone(), + layer: LayerNodeIdentifier::new_unchecked(layer.to_node()), + }); + responses.add(BroadcastEvent::SelectionChanged); + } + } + } + } NodeGraphMessage::AddImport => { network_interface.add_import(graph_craft::document::value::TaggedValue::None, true, -1, "", "", breadcrumb_network_path); responses.add(NodeGraphMessage::SendGraph); @@ -568,6 +606,9 @@ impl<'a> MessageHandler> for NodeG NodeGraphMessage::MoveNodeToChainStart { node_id, parent } => { network_interface.move_node_to_chain_start(&node_id, parent, selection_network_path); } + NodeGraphMessage::SetChainPosition { node_id } => { + network_interface.set_chain_position(&node_id, selection_network_path); + } NodeGraphMessage::PasteNodes { serialized_nodes } => { let data = match serde_json::from_str::>(&serialized_nodes) { Ok(d) => d, @@ -821,6 +862,20 @@ impl<'a> MessageHandler> for NodeG }; self.drag_start = Some((drag_start, false)); + let selected_chain_nodes = updated_selected + .iter() + .filter(|node_id| network_interface.is_chain(node_id, selection_network_path)) + .copied() + .collect::>(); + self.drag_start_chain_nodes = selected_chain_nodes + .iter() + .flat_map(|selected| { + network_interface + .upstream_flow_back_from_nodes(vec![*selected], selection_network_path, FlowType::PrimaryFlow) + .skip(1) + .filter(|node_id| network_interface.is_chain(node_id, selection_network_path)) + }) + .collect::>(); self.begin_dragging = true; self.node_has_moved_in_drag = false; self.update_node_graph_hints(responses); @@ -1188,10 +1243,39 @@ impl<'a> MessageHandler> for NodeG { return None; } + log::debug!("preferences.graph_wire_style: {:?}", preferences.graph_wire_style); let (wire, is_stack) = network_interface.vector_wire_from_input(&input, preferences.graph_wire_style, selection_network_path)?; - wire.rectangle_intersections_exist(bounding_box[0], bounding_box[1]).then_some((input, is_stack)) + + let bbox_rect = kurbo::Rect::new(bounding_box[0].x, bounding_box[0].y, bounding_box[1].x, bounding_box[1].y); + + let p1 = DVec2::new(bbox_rect.x0, bbox_rect.y0); + let p2 = DVec2::new(bbox_rect.x1, bbox_rect.y0); + let p3 = DVec2::new(bbox_rect.x1, bbox_rect.y1); + let p4 = DVec2::new(bbox_rect.x0, bbox_rect.y1); + let ps = [p1, p2, p3, p4]; + + let inside = wire.is_inside_subpath(&Subpath::from_anchors_linear(ps, true), None, None); + + let wire = subpath_to_kurbo_bezpath(wire); + + let intersect = wire.segments().any(|segment| { + let rect = kurbo::Rect::new(bounding_box[0].x, bounding_box[0].y, bounding_box[1].x, bounding_box[1].y); + + let top_line = Line::new(Point::new(rect.x0, rect.y0), Point::new(rect.x1, rect.y0)); + let bottom_line = Line::new(Point::new(rect.x0, rect.y1), Point::new(rect.x1, rect.y1)); + let left_line = Line::new(Point::new(rect.x0, rect.y0), Point::new(rect.x0, rect.y1)); + let right_line = Line::new(Point::new(rect.x1, rect.y0), Point::new(rect.x1, rect.y1)); + + !segment.intersect_line(top_line).is_empty() + || !segment.intersect_line(bottom_line).is_empty() + || !segment.intersect_line(left_line).is_empty() + || !segment.intersect_line(right_line).is_empty() + }); + + (intersect || inside).then_some((input, is_stack)) }) .collect::>(); + // Prioritize vertical thick lines and cancel if there are multiple potential wires let mut node_wires = Vec::new(); let mut stack_wires = Vec::new(); @@ -1270,6 +1354,135 @@ impl<'a> MessageHandler> for NodeG self.auto_panning.stop(&messages, responses); } } + NodeGraphMessage::ShakeNode => { + let Some(drag_start) = &self.drag_start else { + log::error!("Drag start should be initialized when shaking a node"); + return; + }; + + let Some(network_metadata) = network_interface.network_metadata(selection_network_path) else { + return; + }; + + let viewport_location = ipp.mouse.position; + let point = network_metadata + .persistent_metadata + .navigation_metadata + .node_graph_to_viewport + .inverse() + .transform_point2(viewport_location); + + // Collect the distance to move the shaken nodes after the undo + let graph_delta = IVec2::new(((point.x - drag_start.0.start_x) / 24.).round() as i32, ((point.y - drag_start.0.start_y) / 24.).round() as i32); + + // Undo to the state of the graph before shaking + responses.add(DocumentMessage::AbortTransaction); + + // Add a history step to abort to the state before shaking if right clicked + responses.add(DocumentMessage::StartTransaction); + + let Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(selection_network_path) else { + log::error!("Could not get selected nodes in ShakeNode"); + return; + }; + + let mut all_selected_nodes = selected_nodes.0.iter().copied().collect::>(); + for selected_layer in selected_nodes + .0 + .iter() + .filter(|selected_node| network_interface.is_layer(selected_node, selection_network_path)) + .copied() + .collect::>() + { + for sole_dependent in network_interface.upstream_nodes_below_layer(&selected_layer, selection_network_path) { + all_selected_nodes.insert(sole_dependent); + } + } + + for selected_node in &all_selected_nodes { + // Handle inputs of selected node + for input_index in 0..network_interface.number_of_inputs(selected_node, selection_network_path) { + let input_connector = InputConnector::node(*selected_node, input_index); + // Only disconnect inputs to non selected nodes + if network_interface + .upstream_output_connector(&input_connector, selection_network_path) + .and_then(|connector| connector.node_id()) + .is_some_and(|node_id| !all_selected_nodes.contains(&node_id)) + { + responses.add(NodeGraphMessage::DisconnectInput { input_connector }); + } + } + + let number_of_outputs = network_interface.number_of_outputs(selected_node, selection_network_path); + let first_deselected_upstream_node = network_interface + .upstream_flow_back_from_nodes(vec![*selected_node], selection_network_path, FlowType::PrimaryFlow) + .find(|upstream_node| !all_selected_nodes.contains(upstream_node)); + let Some(outward_wires) = network_interface.outward_wires(selection_network_path) else { + log::error!("Could not get output wires in shake input"); + continue; + }; + + // Disconnect output wires to non selected nodes + for output_index in 0..number_of_outputs { + let output_connector = OutputConnector::node(*selected_node, output_index); + if let Some(downstream_connections) = outward_wires.get(&output_connector) { + for &input_connector in downstream_connections { + if input_connector.node_id().is_some_and(|downstream_node| !all_selected_nodes.contains(&downstream_node)) { + responses.add(NodeGraphMessage::DisconnectInput { input_connector }); + } + } + } + } + + // Handle reconnection + // Find first non selected upstream node by primary flow + if let Some(first_deselected_upstream_node) = first_deselected_upstream_node { + let Some(downstream_connections_to_first_output) = outward_wires.get(&OutputConnector::node(*selected_node, 0)).cloned() else { + log::error!("Could not get downstream_connections_to_first_output in shake node"); + return; + }; + // Reconnect only if all downstream outputs are not selected + if !downstream_connections_to_first_output + .iter() + .any(|connector| connector.node_id().is_some_and(|node_id| all_selected_nodes.contains(&node_id))) + { + // Find what output on the deselected upstream node to reconnect to + for output_index in 0..network_interface.number_of_outputs(&first_deselected_upstream_node, selection_network_path) { + let output_connector = &OutputConnector::node(first_deselected_upstream_node, output_index); + let Some(outward_wires) = network_interface.outward_wires(selection_network_path) else { + log::error!("Could not get output wires in shake input"); + continue; + }; + if let Some(inputs) = outward_wires.get(output_connector) { + // This can only run once + if inputs.iter().any(|input_connector| { + input_connector + .node_id() + .is_some_and(|upstream_node| all_selected_nodes.contains(&upstream_node) && input_connector.input_index() == 0) + }) { + // Output index is the output of the deselected upstream node to reconnect to + for downstream_connections_to_first_output in &downstream_connections_to_first_output { + responses.add(NodeGraphMessage::CreateWire { + output_connector: OutputConnector::node(first_deselected_upstream_node, output_index), + input_connector: *downstream_connections_to_first_output, + }); + } + } + } + + // Set all chain nodes back to chain position + // TODO: Fix + // for chain_node_to_reset in std::mem::take(&mut self.drag_start_chain_nodes) { + // responses.add(NodeGraphMessage::SetChainPosition { node_id: chain_node_to_reset }); + // } + } + } + } + } + responses.add(NodeGraphMessage::ShiftSelectedNodesByAmount { graph_delta, rubber_band: false }); + responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(NodeGraphMessage::SendGraph); + } NodeGraphMessage::RemoveImport { import_index: usize } => { network_interface.remove_import(usize, selection_network_path); responses.add(NodeGraphMessage::SendGraph); @@ -1354,6 +1567,11 @@ impl<'a> MessageHandler> for NodeG if node_bbox[1].x >= document_bbox[0].x && node_bbox[0].x <= document_bbox[1].x && node_bbox[1].y >= document_bbox[0].y && node_bbox[0].y <= document_bbox[1].y { nodes.push(*node_id); } + for error in &self.node_graph_errors { + if error.node_path.contains(node_id) { + nodes.push(*node_id); + } + } } responses.add(FrontendMessage::UpdateVisibleNodes { nodes }); @@ -1785,6 +2003,12 @@ impl NodeGraphMessageHandler { )); } + if self.drag_start.is_some() { + common.extend(actions!(NodeGraphMessageDiscriminant; + ShakeNode, + )); + } + common } @@ -1824,26 +2048,57 @@ impl NodeGraphMessageHandler { let selection_all_locked = network_interface.selected_nodes().selected_unlocked_layers(network_interface).count() == 0; let selection_all_visible = selected_nodes.selected_nodes().all(|node_id| network_interface.is_visible(node_id, breadcrumb_network_path)); + let mut selected_layers = selected_nodes.selected_layers(network_interface.document_metadata()); + let selected_layer = selected_layers.next(); + let has_multiple_selection = selected_layers.next().is_some(); + let mut widgets = vec![ PopoverButton::new() .icon(Some("Node".to_string())) .tooltip("New Node (Right Click)") .popover_layout({ - let node_chooser = NodeCatalog::new() - .on_update(move |node_type| { - let node_id = NodeId::new(); + // Showing only compatible types + let compatible_type = match (selection_includes_layers, has_multiple_selection, selected_layer) { + (true, false, Some(layer)) => { + let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, network_interface); + let node_type = graph_layer.horizontal_layer_flow().nth(1); + if let Some(node_id) = node_type { + let (output_type, _) = network_interface.output_type(&node_id, 0, &[]); + Some(format!("type:{}", output_type.nested_type())) + } else { + None + } + } + _ => None, + }; - Message::Batched { - messages: Box::new([ - NodeGraphMessage::CreateNodeFromContextMenu { - node_id: Some(node_id), - node_type: node_type.clone(), - xy: None, - add_transaction: true, - } - .into(), - NodeGraphMessage::SelectedNodesSet { nodes: vec![node_id] }.into(), - ]), + let single_layer_selected = selection_includes_layers && !has_multiple_selection; + + let mut node_chooser = NodeCatalog::new(); + node_chooser.intial_search = compatible_type.unwrap_or("".to_string()); + + let node_chooser = node_chooser + .on_update(move |node_type| { + if let (true, Some(layer)) = (single_layer_selected, selected_layer) { + NodeGraphMessage::CreateNodeInLayerWithTransaction { + node_type: node_type.clone(), + layer: LayerNodeIdentifier::new_unchecked(layer.to_node()), + } + .into() + } else { + let node_id = NodeId::new(); + Message::Batched { + messages: Box::new([ + NodeGraphMessage::CreateNodeFromContextMenu { + node_id: Some(node_id), + node_type: node_type.clone(), + xy: None, + add_transaction: true, + } + .into(), + NodeGraphMessage::SelectedNodesSet { nodes: vec![node_id] }.into(), + ]), + } } }) .widget_holder(); @@ -2115,7 +2370,22 @@ impl NodeGraphMessageHandler { .icon(Some("Node".to_string())) .tooltip("Add an operation to the end of this layer's chain of nodes") .popover_layout({ - let node_chooser = NodeCatalog::new() + let layer_identifier = LayerNodeIdentifier::new(layer, &context.network_interface); + let compatible_type = { + let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer_identifier, &context.network_interface); + let node_type = graph_layer.horizontal_layer_flow().nth(1); + if let Some(node_id) = node_type { + let (output_type, _) = context.network_interface.output_type(&node_id, 0, &[]); + Some(format!("type:{}", output_type.nested_type())) + } else { + None + } + }; + + let mut node_chooser = NodeCatalog::new(); + node_chooser.intial_search = compatible_type.unwrap_or("".to_string()); + + let node_chooser = node_chooser .on_update(move |node_type| { NodeGraphMessage::CreateNodeInLayerWithTransaction { node_type: node_type.clone(), @@ -2366,19 +2636,19 @@ impl NodeGraphMessageHandler { let mut ancestors_of_selected = HashSet::new(); let mut descendants_of_selected = HashSet::new(); for selected_layer in &selected_layers { - for ancestor in LayerNodeIdentifier::new(*selected_layer, network_interface, &[]).ancestors(network_interface.document_metadata()) { + for ancestor in LayerNodeIdentifier::new(*selected_layer, network_interface).ancestors(network_interface.document_metadata()) { if ancestor != LayerNodeIdentifier::ROOT_PARENT && ancestor.to_node() != *selected_layer { ancestors_of_selected.insert(ancestor.to_node()); } } - for descendant in LayerNodeIdentifier::new(*selected_layer, network_interface, &[]).descendants(network_interface.document_metadata()) { + for descendant in LayerNodeIdentifier::new(*selected_layer, network_interface).descendants(network_interface.document_metadata()) { descendants_of_selected.insert(descendant.to_node()); } } for (&node_id, node_metadata) in &network_interface.document_network_metadata().persistent_metadata.node_metadata { if node_metadata.persistent_metadata.is_layer() { - let layer = LayerNodeIdentifier::new(node_id, network_interface, &[]); + let layer = LayerNodeIdentifier::new(node_id, network_interface); let children_allowed = // The layer has other layers as children along the secondary input's horizontal flow @@ -2559,6 +2829,7 @@ impl Default for NodeGraphMessageHandler { node_has_moved_in_drag: false, shift_without_push: false, box_selection_start: None, + drag_start_chain_nodes: Vec::new(), selection_before_pointer_down: Vec::new(), disconnecting: None, initial_disconnecting: false, diff --git a/editor/src/messages/portfolio/document/utility_types/document_metadata.rs b/editor/src/messages/portfolio/document/utility_types/document_metadata.rs index 0b6f65bc87..600d4958f6 100644 --- a/editor/src/messages/portfolio/document/utility_types/document_metadata.rs +++ b/editor/src/messages/portfolio/document/utility_types/document_metadata.rs @@ -250,12 +250,8 @@ impl LayerNodeIdentifier { /// Construct a [`LayerNodeIdentifier`], debug asserting that it is a layer node. This should only be used in the document network since the structure is not loaded in nested networks. #[track_caller] - pub fn new(node_id: NodeId, network_interface: &NodeNetworkInterface, network_path: &[NodeId]) -> Self { - debug_assert!( - network_interface.is_layer(&node_id, network_path), - "Layer identifier constructed from non-layer node {node_id}: {:#?}", - network_interface.nested_network(network_path).unwrap().nodes.get(&node_id) - ); + pub fn new(node_id: NodeId, network_interface: &NodeNetworkInterface) -> Self { + debug_assert!(network_interface.is_layer(&node_id, &[]), "Layer identifier constructed from non-layer node {node_id}",); Self::new_unchecked(node_id) } diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface.rs b/editor/src/messages/portfolio/document/utility_types/network_interface.rs index 4a4258a742..290247f621 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -203,12 +203,12 @@ impl NodeNetworkInterface { } /// Returns the first downstream layer(inclusive) from a node. If the node is a layer, it will return itself. - pub fn downstream_layer(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> Option { + pub fn downstream_layer_for_chain_node(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> Option { let mut id = *node_id; while !self.is_layer(&id, network_path) { id = self.outward_wires(network_path)?.get(&OutputConnector::node(id, 0))?.first()?.node_id()?; } - Some(LayerNodeIdentifier::new(id, self, network_path)) + Some(id) } /// Returns all downstream layers (inclusive) from a node. If the node is a layer, it will return itself. @@ -388,8 +388,8 @@ impl NodeNetworkInterface { } // If a chain node does not have a selected downstream layer, then set the position to absolute - let downstream_layer = self.downstream_layer(node_id, network_path); - if downstream_layer.is_none_or(|downstream_layer| new_ids.keys().all(|key| *key != downstream_layer.to_node())) { + let downstream_layer = self.downstream_layer_for_chain_node(node_id, network_path); + if downstream_layer.is_none_or(|downstream_layer| new_ids.keys().all(|key| *key != downstream_layer)) { let Some(position) = self.position(node_id, network_path) else { log::error!("Could not get position in create_node_template"); return None; @@ -1244,7 +1244,7 @@ impl NodeNetworkInterface { .as_ref() .is_some_and(|reference| reference == "Artboard" && self.connected_to_output(node_id, &[]) && self.is_layer(node_id, &[])) { - Some(LayerNodeIdentifier::new(*node_id, self, &[])) + Some(LayerNodeIdentifier::new(*node_id, self)) } else { None } @@ -3025,7 +3025,7 @@ impl NodeNetworkInterface { // Helper functions for mutable getters impl NodeNetworkInterface { - pub fn upstream_chain_nodes(&mut self, network_path: &[NodeId]) -> Vec { + pub fn upstream_chain_nodes(&self, network_path: &[NodeId]) -> Vec { let Some(selected_nodes) = self.selected_nodes_in_nested_network(network_path) else { log::error!("Could not get selected nodes in upstream_chain_nodes"); return Vec::new(); @@ -3156,7 +3156,7 @@ impl NodeNetworkInterface { self.document_metadata.document_to_viewport = transform; } - pub fn is_eligible_to_be_layer(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> bool { + pub fn is_eligible_to_be_layer(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool { let Some(node) = self.document_node(node_id, network_path) else { log::error!("Could not get node {node_id} in is_eligible_to_be_layer"); return false; @@ -3362,6 +3362,24 @@ impl NodeNetworkInterface { .map(|[a, b]| [node_graph_to_viewport.transform_point2(a), node_graph_to_viewport.transform_point2(b)]) } + pub fn selected_layers_artwork_bounding_box_viewport(&self) -> Option<[DVec2; 2]> { + self.selected_nodes() + .0 + .iter() + .filter(|node| self.is_layer(&node, &[])) + .filter_map(|layer| self.document_metadata.bounding_box_viewport(LayerNodeIdentifier::new(*layer, self))) + .reduce(Quad::combine_bounds) + } + + pub fn selected_unlocked_layers_bounding_box_viewport(&self) -> Option<[DVec2; 2]> { + self.selected_nodes() + .0 + .iter() + .filter(|node| self.is_layer(&node, &[]) && !self.is_layer(&node, &[])) + .filter_map(|layer| self.document_metadata.bounding_box_viewport(LayerNodeIdentifier::new(*layer, self))) + .reduce(Quad::combine_bounds) + } + /// Get the combined bounding box of the click targets of the selected nodes in the node graph in layer space pub fn selected_nodes_bounding_box(&mut self, network_path: &[NodeId]) -> Option<[DVec2; 2]> { let Some(selected_nodes) = self.selected_nodes_in_nested_network(network_path) else { @@ -3451,7 +3469,7 @@ impl NodeNetworkInterface { let Some(first_root_layer) = self .upstream_flow_back_from_nodes(vec![root_node.node_id], &[], FlowType::PrimaryFlow) - .find_map(|node_id| if self.is_layer(&node_id, &[]) { Some(LayerNodeIdentifier::new(node_id, self, &[])) } else { None }) + .find_map(|node_id| if self.is_layer(&node_id, &[]) { Some(LayerNodeIdentifier::new(node_id, self)) } else { None }) else { return; }; @@ -3467,7 +3485,7 @@ impl NodeNetworkInterface { if horizontal_root_node_id == first_root_layer.to_node() { for current_node_id in horizontal_flow_iter { if self.is_layer(¤t_node_id, &[]) { - let current_layer_node = LayerNodeIdentifier::new(current_node_id, self, &[]); + let current_layer_node = LayerNodeIdentifier::new(current_node_id, self); if !self.document_metadata.structure.contains_key(¤t_layer_node) { if current_node_id == first_root_layer.to_node() { awaiting_primary_flow.push((current_node_id, LayerNodeIdentifier::ROOT_PARENT)); @@ -3484,7 +3502,7 @@ impl NodeNetworkInterface { // Skip the horizontal_root_node_id node for current_node_id in horizontal_flow_iter.skip(1) { if self.is_layer(¤t_node_id, &[]) { - let current_layer_node = LayerNodeIdentifier::new(current_node_id, self, &[]); + let current_layer_node = LayerNodeIdentifier::new(current_node_id, self); if !self.document_metadata.structure.contains_key(¤t_layer_node) { awaiting_primary_flow.push((current_node_id, parent_layer_node)); children.push((parent_layer_node, current_layer_node)); @@ -3505,7 +3523,7 @@ impl NodeNetworkInterface { for current_node_id in primary_flow_iter.skip(1) { if self.is_layer(¤t_node_id, &[]) { // Create a new layer for the top of each stack, and add it as a child to the previous parent - let current_layer_node = LayerNodeIdentifier::new(current_node_id, self, &[]); + let current_layer_node = LayerNodeIdentifier::new(current_node_id, self); if !self.document_metadata.structure.contains_key(¤t_layer_node) { children.push(current_layer_node); @@ -3568,7 +3586,7 @@ impl NodeNetworkInterface { } stack.extend(self_network_metadata.persistent_metadata.node_metadata.keys().map(|node_id| { - let mut current_path = path.clone(); + let mut current_path: Vec = path.clone(); current_path.push(*node_id); current_path })); @@ -5085,12 +5103,45 @@ impl NodeNetworkInterface { else { log::error!("Could not set chain position for layer node {node_id}"); } + // let previous_upstream_node = self.upstream_output_connector(&InputConnector::node(*node_id, 0), network_path).and_then(|output| output.node_id()); + // let Some(previous_upstream_node_position) = previous_upstream_node.and_then(|upstream| self.position_from_downstream_node(&upstream, network_path)) else { + // log::error!("Could not get previous_upstream_node_position"); + // return; + // }; self.unload_upstream_node_click_targets(vec![*node_id], network_path); // Reload click target of the layer which encapsulate the chain - if let Some(downstream_layer) = self.downstream_layer(node_id, network_path) { - self.unload_node_click_targets(&downstream_layer.to_node(), network_path); + if let Some(downstream_layer) = self.downstream_layer_for_chain_node(node_id, network_path) { + self.unload_node_click_targets(&downstream_layer, network_path); } self.unload_all_nodes_bounding_box(network_path); + + // let Some(new_upstream_node_position) = previous_upstream_node.and_then(|upstream| self.position_from_downstream_node(&upstream, network_path)) else { + // log::error!("Could not get new_upstream_node_position"); + // return; + // }; + // if let Some(previous_upstream_node) = { + // let x_delta = new_upstream_node_position.x - previous_upstream_node_position.x; + // // Upstream node got shifted to left, so shift all upstream absolute sole dependents + // if x_delta != 0 { + // let upstream_absolute_nodes = SelectedNodes( + // self.upstream_flow_back_from_nodes(vec![previous_upstream_node], network_path, FlowType::UpstreamFlow) + // .into_iter() + // .filter(|node_id| self.is_absolute(node_id, network_path)) + // .collect::>(), + // ); + // let old_selected_nodes = std::mem::replace(self.selected_nodes_mut(network_path).unwrap(), upstream_absolute_nodes); + // if x_delta < 0 { + // for _ in 0..x_delta.abs() { + // self.shift_selected_nodes(Direction::Left, false, network_path); + // } + // } else { + // for _ in 0..x_delta.abs() { + // self.shift_selected_nodes(Direction::Right, false, network_path); + // } + // } + // let _ = std::mem::replace(self.selected_nodes_mut(network_path).unwrap(), old_selected_nodes); + // } + // } } fn valid_upstream_chain_nodes(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> Vec { @@ -5205,7 +5256,7 @@ impl NodeNetworkInterface { /// node_id is the first chain node, not the layer fn set_upstream_chain_to_absolute(&mut self, node_id: &NodeId, network_path: &[NodeId]) { - let Some(downstream_layer) = self.downstream_layer(node_id, network_path) else { + let Some(downstream_layer) = self.downstream_layer_for_chain_node(node_id, network_path) else { log::error!("Could not get downstream layer in set_upstream_chain_to_absolute"); return; }; @@ -5218,7 +5269,7 @@ impl NodeNetworkInterface { if self.is_chain(upstream_id, network_path) { self.set_absolute_position(upstream_id, previous_position, network_path); // Reload click target of the layer which used to encapsulate the chain - self.unload_node_click_targets(&downstream_layer.to_node(), network_path); + self.unload_node_click_targets(&downstream_layer, network_path); } // If there is an upstream layer then stop breaking the chain else { @@ -5297,8 +5348,8 @@ impl NodeNetworkInterface { // Deselect chain nodes upstream from a selected layer if self.is_chain(selected_node, network_path) && self - .downstream_layer(selected_node, network_path) - .is_some_and(|downstream_layer| node_ids.contains(&downstream_layer.to_node())) + .downstream_layer_for_chain_node(selected_node, network_path) + .is_some_and(|downstream_layer| node_ids.contains(&downstream_layer)) { node_ids.remove(selected_node); } @@ -5947,31 +5998,6 @@ impl NodeNetworkInterface { self.create_wire(&OutputConnector::node(*node_id, 0), &InputConnector::node(parent.to_node(), 1), network_path); self.set_chain_position(node_id, network_path); } else { - // TODO: Implement a more robust horizontal shift system when inserting a node into a chain. - // This should be done by breaking the chain and shifting the sole dependents for each node upstream of the insertion. - // Before inserting the node, shift the layer right 7 units so that all sole dependents are also shifted - // let input_connector = InputConnector::node(parent.to_node(), 0); - // let old_upstream = self.upstream_output_connector(&input_connector, network_path); - // This also needs to disconnect from the downstream layer - // self.disconnect_input(&input_connector, network_path); - // let Some(selected_nodes) = self.selected_nodes_mut(network_path) else { - // log::error!("Could not get selected nodes in move_layer_to_stack"); - // return; - // }; - // let old_selected_nodes = selected_nodes.replace_with(vec![parent.to_node()]); - - // for _ in 0..7 { - // self.shift_selected_nodes(Direction::Left, false, network_path); - // } - // // Grip drag it back to the right - // for _ in 0..7 { - // self.shift_selected_nodes(Direction::Right, true, network_path); - // } - // let _ = self.selected_nodes_mut(network_path).unwrap().replace_with(old_selected_nodes); - // if let Some(old_upstream) = old_upstream { - // self.create_wire(&old_upstream, &input_connector, network_path); - // } - // Insert the node in the gap and set the upstream to a chain self.insert_node_between(node_id, &InputConnector::node(parent.to_node(), 1), 0, network_path); self.force_set_upstream_to_chain(node_id, network_path); @@ -6778,13 +6804,6 @@ impl From for DocumentNodePersisten } } -#[derive(serde::Serialize, serde::Deserialize)] -enum NodePersistentMetadataVersions { - DocumentNodePersistentMetadataPropertiesRow(DocumentNodePersistentMetadataPropertiesRow), - NodePersistentMetadataInputNames(DocumentNodePersistentMetadataInputNames), - NodePersistentMetadata(DocumentNodePersistentMetadata), -} - fn deserialize_node_persistent_metadata<'de, D>(deserializer: D) -> Result where D: serde::Deserializer<'de>, diff --git a/editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs b/editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs index 50fb7b8ec0..d7f1c72d7d 100644 --- a/editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs +++ b/editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs @@ -19,6 +19,7 @@ pub struct MenuBarMessageHandler { pub spreadsheet_view_open: bool, pub message_logging_verbosity: MessageLoggingVerbosity, pub reset_node_definitions_on_open: bool, + pub single_path_node_compatible_layer_selected: bool, } #[message_handler_data] @@ -45,6 +46,7 @@ impl LayoutHolder for MenuBarMessageHandler { let message_logging_verbosity_names = self.message_logging_verbosity == MessageLoggingVerbosity::Names; let message_logging_verbosity_contents = self.message_logging_verbosity == MessageLoggingVerbosity::Contents; let reset_node_definitions_on_open = self.reset_node_definitions_on_open; + let single_path_node_compatible_layer_selected = self.single_path_node_compatible_layer_selected; let menu_bar_entries = vec![ MenuBarEntry { @@ -418,9 +420,8 @@ impl LayoutHolder for MenuBarMessageHandler { disabled: no_active_document || !has_selected_layers, children: MenuBarEntryChildren(vec![{ let list = ::list(); - list.into_iter() - .map(|i| i.into_iter()) - .flatten() + list.iter() + .flat_map(|i| i.iter()) .map(move |(operation, info)| MenuBarEntry { label: info.label.to_string(), icon: info.icon.as_ref().map(|i| i.to_string()), @@ -436,6 +437,14 @@ impl LayoutHolder for MenuBarMessageHandler { ..MenuBarEntry::default() }, ], + vec![MenuBarEntry { + label: "Make Path Editable".into(), + icon: Some("NodeShape".into()), + shortcut: None, + action: MenuBarEntry::create_action(|_| NodeGraphMessage::AddPathNode.into()), + disabled: !single_path_node_compatible_layer_selected, + ..MenuBarEntry::default() + }], ]), ), MenuBarEntry::new_root( diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index daa81274a3..71de86b90f 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -18,10 +18,12 @@ use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes; use crate::messages::portfolio::document_migration::*; use crate::messages::preferences::SelectionMode; use crate::messages::prelude::*; +use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::utility_types::{HintData, HintGroup, ToolType}; use crate::node_graph_executor::{ExportConfig, NodeGraphExecutor}; use glam::{DAffine2, DVec2}; use graph_craft::document::NodeId; +use graph_craft::document::value::TaggedValue; use graphene_std::renderer::Quad; use graphene_std::text::Font; use std::vec; @@ -78,6 +80,7 @@ impl MessageHandler> for Portfolio self.menu_bar_message_handler.has_selected_nodes = false; self.menu_bar_message_handler.has_selected_layers = false; self.menu_bar_message_handler.has_selection_history = (false, false); + self.menu_bar_message_handler.single_path_node_compatible_layer_selected = false; self.menu_bar_message_handler.spreadsheet_view_open = self.spreadsheet.spreadsheet_view_open; self.menu_bar_message_handler.message_logging_verbosity = message_logging_verbosity; self.menu_bar_message_handler.reset_node_definitions_on_open = reset_node_definitions_on_open; @@ -95,6 +98,30 @@ impl MessageHandler> for Portfolio let metadata = &document.network_interface.document_network_metadata().persistent_metadata; (!metadata.selection_undo_history.is_empty(), !metadata.selection_redo_history.is_empty()) }; + self.menu_bar_message_handler.single_path_node_compatible_layer_selected = { + let selected_nodes = document.network_interface.selected_nodes(); + let mut selected_layers = selected_nodes.selected_layers(document.metadata()); + let first_layer = selected_layers.next(); + let second_layer = selected_layers.next(); + let has_single_selection = first_layer.is_some() && second_layer.is_none(); + + let compatible_type = first_layer.and_then(|layer| { + let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &document.network_interface); + graph_layer.horizontal_layer_flow().nth(1).and_then(|node_id| { + let (output_type, _) = document.network_interface.output_type(&node_id, 0, &[]); + Some(format!("type:{}", output_type.nested_type())) + }) + }); + + let is_compatible = compatible_type.as_deref() == Some("type:Instances"); + + let is_modifiable = first_layer.map_or(false, |layer| { + let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &document.network_interface); + matches!(graph_layer.find_input("Path", 1), Some(TaggedValue::VectorModification(_))) + }); + + first_layer.is_some() && has_single_selection && is_compatible && !is_modifiable + } } self.menu_bar_message_handler.process_message(message, responses, ()); @@ -762,6 +789,8 @@ impl MessageHandler> for Portfolio responses.add(DocumentMessage::GraphViewOverlay { open: node_graph_open }); if node_graph_open { responses.add(NodeGraphMessage::UpdateGraphBarRight); + responses.add(NodeGraphMessage::UnloadWires); + responses.add(NodeGraphMessage::SendWires) } else { responses.add(PortfolioMessage::UpdateDocumentWidgets); } diff --git a/editor/src/messages/tool/common_functionality/shape_editor.rs b/editor/src/messages/tool/common_functionality/shape_editor.rs index f98e50f079..34fda01a34 100644 --- a/editor/src/messages/tool/common_functionality/shape_editor.rs +++ b/editor/src/messages/tool/common_functionality/shape_editor.rs @@ -1000,7 +1000,7 @@ impl ShapeState { } else { // Push both in and out handles into the correct position for ((handle, sign), other_anchor) in handles.iter().zip([1., -1.]).zip(&anchor_positions) { - let Some(anchor_vector) = other_anchor.map(|position| (position - anchor_position)) else { + let Some(anchor_vector) = other_anchor.map(|position| position - anchor_position) else { continue; }; diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index a8b10afaa9..f5ebc33189 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -11,6 +11,7 @@ use crate::messages::portfolio::document::utility_types::network_interface::Node use crate::messages::portfolio::document::utility_types::transformation::Axis; use crate::messages::preferences::SelectionMode; use crate::messages::tool::common_functionality::auto_panning::AutoPanning; +use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::common_functionality::pivot::{PivotGizmo, PivotGizmoType, PivotToolSource, pin_pivot_widget, pivot_gizmo_type_widget, pivot_reference_point_widget}; use crate::messages::tool::common_functionality::shape_editor::{ ClosestSegment, ManipulatorAngle, OpposingHandleLengths, SelectedLayerState, SelectedPointsInfo, SelectionChange, SelectionShape, SelectionShapeType, ShapeState, @@ -18,6 +19,7 @@ use crate::messages::tool::common_functionality::shape_editor::{ use crate::messages::tool::common_functionality::snapping::{SnapCache, SnapCandidatePoint, SnapConstraint, SnapData, SnapManager}; use crate::messages::tool::common_functionality::utility_functions::{calculate_segment_angle, find_two_param_best_approximate}; use bezier_rs::{Bezier, BezierHandles, TValue}; +use graph_craft::document::value::TaggedValue; use graphene_std::renderer::Quad; use graphene_std::transform::ReferencePoint; use graphene_std::vector::click_target::ClickTargetType; @@ -264,6 +266,14 @@ impl LayoutHolder for PathTool { .selected_index(Some(self.options.path_overlay_mode as u32)) .widget_holder(); + // Works only if a single layer is selected and its type is vectordata + let path_node_button = TextButton::new("Make Path Editable") + .icon(Some("NodeShape".into())) + .tooltip("Make Path Editable") + .on_update(|_| NodeGraphMessage::AddPathNode.into()) + .disabled(!self.tool_data.single_path_node_compatible_layer_selected) + .widget_holder(); + let [_checkbox, _dropdown] = { let pivot_gizmo_type_widget = pivot_gizmo_type_widget(self.tool_data.pivot_gizmo.state, PivotToolSource::Path); [pivot_gizmo_type_widget[0].clone(), pivot_gizmo_type_widget[2].clone()] @@ -294,6 +304,7 @@ impl LayoutHolder for PathTool { unrelated_seperator.clone(), path_overlay_mode_widget, unrelated_seperator.clone(), + path_node_button, // checkbox.clone(), // related_seperator.clone(), // dropdown.clone(), @@ -522,6 +533,7 @@ struct PathToolData { drill_through_cycle_count: usize, hovered_layers: Vec, ghost_outline: Vec<(Vec, DAffine2)>, + single_path_node_compatible_layer_selected: bool, } impl PathToolData { @@ -2383,6 +2395,31 @@ impl Fsm for PathToolFsmState { point_select_state: shape_editor.get_dragging_state(&document.network_interface), colinear, }; + + tool_data.single_path_node_compatible_layer_selected = { + let selected_nodes = document.network_interface.selected_nodes(); + let mut selected_layers = selected_nodes.selected_layers(document.metadata()); + let first_layer = selected_layers.next(); + let second_layer = selected_layers.next(); + let has_single_selection = first_layer.is_some() && second_layer.is_none(); + + let compatible_type = first_layer.and_then(|layer| { + let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &document.network_interface); + graph_layer.horizontal_layer_flow().nth(1).and_then(|node_id| { + let (output_type, _) = document.network_interface.output_type(&node_id, 0, &[]); + Some(format!("type:{}", output_type.nested_type())) + }) + }); + + let is_compatible = compatible_type.as_deref() == Some("type:Instances"); + + let is_modifiable = first_layer.map_or(false, |layer| { + let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &document.network_interface); + matches!(graph_layer.find_input("Path", 1), Some(TaggedValue::VectorModification(_))) + }); + + first_layer.is_some() && has_single_selection && is_compatible && !is_modifiable + }; tool_data.update_selection_status(shape_editor, document); self } diff --git a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs index c89373f956..203c84646f 100644 --- a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs +++ b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs @@ -1323,7 +1323,7 @@ mod test_transform_layer { let document = editor.active_document_mut(); let group_children = document.network_interface.downstream_layers(&group_layer.to_node(), &[]); if !group_children.is_empty() { - Some(LayerNodeIdentifier::new(group_children[0], &document.network_interface, &[])) + Some(LayerNodeIdentifier::new(group_children[0], &document.network_interface)) } else { None } diff --git a/frontend/package.json b/frontend/package.json index ea18e0fba9..07fc0460b0 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -19,8 +19,6 @@ "lint-fix": "eslint . --fix && tsc --noEmit", "---------- INTERNAL ----------": "", "setup": "node package-installer.js", - "tauri:dev": "vite", - "tauri:build": "wasm-pack build ./wasm --target=web --features=tauri", "wasm:build-dev": "wasm-pack build ./wasm --dev --target=web", "wasm:build-profiling": "wasm-pack build ./wasm --profiling --target=web", "wasm:build-production": "wasm-pack build ./wasm --release --target=web", diff --git a/frontend/src-tauri/Cargo.toml b/frontend/src-tauri/Cargo.toml deleted file mode 100644 index c2095e3d87..0000000000 --- a/frontend/src-tauri/Cargo.toml +++ /dev/null @@ -1,45 +0,0 @@ -[package] -name = "graphite-desktop" -version = "0.1.0" -description = "Graphite Desktop" -authors = ["Graphite Authors "] -license = "Apache-2.0" -repository = "" -default-run = "graphite-desktop" -edition = "2021" -rust-version = "1.79" - -[features] -# By default Tauri runs in production mode when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL -default = ["custom-protocol", "gpu"] -# This feature is used for production builds where `devPath` points to the filesystem -# DO NOT remove this -custom-protocol = ["tauri/custom-protocol"] -gpu = ["graphite-editor/gpu"] - -[dependencies] -# Local dependencies -graphite-editor = { path = "../../editor", features = [ - "gpu", - "ron", - "vello", - "decouple-execution", -] } - -# Workspace dependencies -axum = { workspace = true } -chrono = { workspace = true } -tokio = { workspace = true } -ron = { workspace = true } -log = { workspace = true } -fern = { workspace = true } -futures = { workspace = true } - -# Required dependencies -tauri = { version = "2", features = ["devtools", "wry"] } -tauri-plugin-shell = "2" -tauri-plugin-http = "2" - -[build-dependencies] -# Required dependencies -tauri-build = { version = "2", features = [] } diff --git a/frontend/src-tauri/build.rs b/frontend/src-tauri/build.rs deleted file mode 100644 index 59fdfbea60..0000000000 --- a/frontend/src-tauri/build.rs +++ /dev/null @@ -1,9 +0,0 @@ -use std::fs; -use std::path::PathBuf; - -fn main() { - // Directory required for compilation, but not tracked by git if empty. - let dist_dir: PathBuf = ["..", "dist"].iter().collect(); - fs::create_dir_all(dist_dir).unwrap(); - tauri_build::build() -} diff --git a/frontend/src-tauri/capabilities/desktop.json b/frontend/src-tauri/capabilities/desktop.json deleted file mode 100644 index 938d5a3367..0000000000 --- a/frontend/src-tauri/capabilities/desktop.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "identifier": "desktop-capability", - "platforms": ["macOS", "windows", "linux"], - "windows": ["main"], - "permissions": ["http:default"] -} diff --git a/frontend/src-tauri/capabilities/migrated.json b/frontend/src-tauri/capabilities/migrated.json deleted file mode 100644 index 4e47d1a91a..0000000000 --- a/frontend/src-tauri/capabilities/migrated.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "identifier": "migrated", - "description": "permissions that were migrated from v1", - "local": true, - "windows": ["main"], - "permissions": [ - "core:default", - "core:window:allow-create", - "core:window:allow-center", - "core:window:allow-request-user-attention", - "core:window:allow-set-resizable", - "core:window:allow-set-maximizable", - "core:window:allow-set-minimizable", - "core:window:allow-set-closable", - "core:window:allow-set-title", - "core:window:allow-maximize", - "core:window:allow-unmaximize", - "core:window:allow-minimize", - "core:window:allow-unminimize", - "core:window:allow-show", - "core:window:allow-hide", - "core:window:allow-close", - "core:window:allow-set-decorations", - "core:window:allow-set-always-on-top", - "core:window:allow-set-content-protected", - "core:window:allow-set-size", - "core:window:allow-set-min-size", - "core:window:allow-set-max-size", - "core:window:allow-set-position", - "core:window:allow-set-fullscreen", - "core:window:allow-set-focus", - "core:window:allow-set-icon", - "core:window:allow-set-skip-taskbar", - "core:window:allow-set-cursor-grab", - "core:window:allow-set-cursor-visible", - "core:window:allow-set-cursor-icon", - "core:window:allow-set-cursor-position", - "core:window:allow-set-ignore-cursor-events", - "core:window:allow-start-dragging", - "core:webview:allow-print", - "shell:allow-execute", - "shell:allow-open", - "http:default", - "core:app:allow-app-show", - "core:app:allow-app-hide", - "shell:default", - "http:default" - ] -} diff --git a/frontend/src-tauri/icons/128x128.png b/frontend/src-tauri/icons/128x128.png deleted file mode 100644 index f7bd3fdad7..0000000000 Binary files a/frontend/src-tauri/icons/128x128.png and /dev/null differ diff --git a/frontend/src-tauri/icons/128x128@2x.png b/frontend/src-tauri/icons/128x128@2x.png deleted file mode 100644 index 56b991cd81..0000000000 Binary files a/frontend/src-tauri/icons/128x128@2x.png and /dev/null differ diff --git a/frontend/src-tauri/icons/32x32.png b/frontend/src-tauri/icons/32x32.png deleted file mode 100644 index 53d1df4aeb..0000000000 Binary files a/frontend/src-tauri/icons/32x32.png and /dev/null differ diff --git a/frontend/src-tauri/icons/Square107x107Logo.png b/frontend/src-tauri/icons/Square107x107Logo.png deleted file mode 100644 index 5a93106185..0000000000 Binary files a/frontend/src-tauri/icons/Square107x107Logo.png and /dev/null differ diff --git a/frontend/src-tauri/icons/Square142x142Logo.png b/frontend/src-tauri/icons/Square142x142Logo.png deleted file mode 100644 index 5d4be439c8..0000000000 Binary files a/frontend/src-tauri/icons/Square142x142Logo.png and /dev/null differ diff --git a/frontend/src-tauri/icons/Square150x150Logo.png b/frontend/src-tauri/icons/Square150x150Logo.png deleted file mode 100644 index d1f9dbc536..0000000000 Binary files a/frontend/src-tauri/icons/Square150x150Logo.png and /dev/null differ diff --git a/frontend/src-tauri/icons/Square284x284Logo.png b/frontend/src-tauri/icons/Square284x284Logo.png deleted file mode 100644 index e4bbc7a36e..0000000000 Binary files a/frontend/src-tauri/icons/Square284x284Logo.png and /dev/null differ diff --git a/frontend/src-tauri/icons/Square30x30Logo.png b/frontend/src-tauri/icons/Square30x30Logo.png deleted file mode 100644 index cc7bb9c9ca..0000000000 Binary files a/frontend/src-tauri/icons/Square30x30Logo.png and /dev/null differ diff --git a/frontend/src-tauri/icons/Square310x310Logo.png b/frontend/src-tauri/icons/Square310x310Logo.png deleted file mode 100644 index d4dd3fcae3..0000000000 Binary files a/frontend/src-tauri/icons/Square310x310Logo.png and /dev/null differ diff --git a/frontend/src-tauri/icons/Square44x44Logo.png b/frontend/src-tauri/icons/Square44x44Logo.png deleted file mode 100644 index 3edfca75e1..0000000000 Binary files a/frontend/src-tauri/icons/Square44x44Logo.png and /dev/null differ diff --git a/frontend/src-tauri/icons/Square71x71Logo.png b/frontend/src-tauri/icons/Square71x71Logo.png deleted file mode 100644 index a189c3e7f9..0000000000 Binary files a/frontend/src-tauri/icons/Square71x71Logo.png and /dev/null differ diff --git a/frontend/src-tauri/icons/Square89x89Logo.png b/frontend/src-tauri/icons/Square89x89Logo.png deleted file mode 100644 index 454158e565..0000000000 Binary files a/frontend/src-tauri/icons/Square89x89Logo.png and /dev/null differ diff --git a/frontend/src-tauri/icons/StoreLogo.png b/frontend/src-tauri/icons/StoreLogo.png deleted file mode 100644 index f2a2283e50..0000000000 Binary files a/frontend/src-tauri/icons/StoreLogo.png and /dev/null differ diff --git a/frontend/src-tauri/icons/icon.icns b/frontend/src-tauri/icons/icon.icns deleted file mode 100644 index a05bb43549..0000000000 Binary files a/frontend/src-tauri/icons/icon.icns and /dev/null differ diff --git a/frontend/src-tauri/icons/icon.ico b/frontend/src-tauri/icons/icon.ico deleted file mode 100644 index e45b58dd9d..0000000000 Binary files a/frontend/src-tauri/icons/icon.ico and /dev/null differ diff --git a/frontend/src-tauri/icons/icon.png b/frontend/src-tauri/icons/icon.png deleted file mode 100644 index 05483f325f..0000000000 Binary files a/frontend/src-tauri/icons/icon.png and /dev/null differ diff --git a/frontend/src-tauri/src/main.rs b/frontend/src-tauri/src/main.rs deleted file mode 100644 index 64c78a79be..0000000000 --- a/frontend/src-tauri/src/main.rs +++ /dev/null @@ -1,84 +0,0 @@ -#![cfg_attr(all(not(debug_assertions), target_os = "windows"), windows_subsystem = "windows")] - -use axum::routing::get; -use axum::Router; -use fern::colors::{Color, ColoredLevelConfig}; -use graphite_editor::node_graph_executor::*; -use std::sync::Mutex; - -static NODE_RUNTIME_IO: Mutex> = const { Mutex::new(None) }; - -#[tokio::main] -async fn main() { - println!("Starting server..."); - - let colors = ColoredLevelConfig::new().debug(Color::Magenta).info(Color::Green).error(Color::Red); - - fern::Dispatch::new() - .chain(std::io::stdout()) - .level(log::LevelFilter::Trace) - .level_for("naga", log::LevelFilter::Error) - .level_for("wgpu-hal", log::LevelFilter::Error) - .level_for("wgpu_hal", log::LevelFilter::Error) - .level_for("wgpu_core", log::LevelFilter::Error) - .format(move |out, message, record| { - out.finish(format_args!( - "[{}]{} {} {}", - // This will color the log level only, not the whole line. Just a touch. - colors.color(record.level()), - chrono::Utc::now().format("[%Y-%m-%d %H:%M:%S]"), - message, - record.module_path().unwrap_or("") - )) - }) - .apply() - .unwrap(); - - std::thread::spawn(|| loop { - futures::executor::block_on(graphite_editor::node_graph_executor::run_node_graph()); - std::thread::sleep(std::time::Duration::from_millis(16)) - }); - graphite_editor::application::set_uuid_seed(0); - - let mut runtime_lock = NODE_RUNTIME_IO.lock().unwrap(); - *runtime_lock = Some(NodeRuntimeIO::new()); - drop(runtime_lock); - - let app = Router::new().route("/", get(|| async { "Hello, World!" })); - - // Run it with hyper on localhost:3000 - tauri::async_runtime::spawn(async { - let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); - axum::serve(listener, app).await.unwrap(); - }); - - tauri::Builder::default() - .plugin(tauri_plugin_http::init()) - .plugin(tauri_plugin_shell::init()) - .invoke_handler(tauri::generate_handler![poll_node_graph, runtime_message]) - .setup(|_app| { - use tauri::Manager; - _app.get_webview_window("main").unwrap().open_devtools(); - Ok(()) - }) - .run(tauri::generate_context!()) - .expect("error while running tauri application"); -} -#[tauri::command] -fn poll_node_graph() -> String { - let vec: Vec<_> = NODE_RUNTIME_IO.lock().as_mut().unwrap().as_mut().unwrap().receive().collect(); - ron::to_string(&vec).unwrap() -} - -#[tauri::command] -fn runtime_message(message: String) -> Result<(), String> { - let message = match ron::from_str(&message) { - Ok(message) => message, - Err(e) => { - log::error!("Failed to deserialize message: {}\nwith error: {}", message, e); - return Err("Failed to deserialize message".into()); - } - }; - let response = NODE_RUNTIME_IO.lock().as_ref().unwrap().as_ref().unwrap().send(message); - response -} diff --git a/frontend/src-tauri/tauri.conf.json b/frontend/src-tauri/tauri.conf.json deleted file mode 100644 index b7614dff01..0000000000 --- a/frontend/src-tauri/tauri.conf.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "$schema": "../node_modules/@tauri-apps/cli/schema.json", - "build": { - "beforeBuildCommand": "npm run tauri:build", - "beforeDevCommand": "npm run tauri:dev", - "frontendDist": "../dist", - "devUrl": "http://127.0.0.1:8080/" - }, - "bundle": { - "active": true, - "category": "DeveloperTool", - "copyright": "", - "targets": "all", - "externalBin": [], - "icon": ["icons/32x32.png", "icons/128x128.png", "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico"], - "windows": { - "certificateThumbprint": null, - "digestAlgorithm": "sha256", - "timestampUrl": "" - }, - "longDescription": "", - "macOS": { - "entitlements": null, - "exceptionDomain": "", - "frameworks": [], - "providerShortName": null, - "signingIdentity": null - }, - "resources": [], - "shortDescription": "", - "linux": { - "deb": { - "depends": ["librustc_codegen_spirv"] - } - } - }, - "productName": "Graphite", - "mainBinaryName": "Graphite", - "version": "0.1.0", - "identifier": "rs.graphite.editor", - "plugins": {}, - "app": { - "withGlobalTauri": true, - "windows": [ - { - "decorations": false, - "fullscreen": false, - "height": 1080, - "resizable": true, - "title": "Graphite", - "width": 1920, - "useHttpsScheme": true - } - ], - "security": { - "csp": null - } - } -} diff --git a/frontend/src/io-managers/input.ts b/frontend/src/io-managers/input.ts index bb7b035ceb..aee4091c8b 100644 --- a/frontend/src/io-managers/input.ts +++ b/frontend/src/io-managers/input.ts @@ -36,6 +36,8 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli let textToolInteractiveInputElement = undefined as undefined | HTMLDivElement; let canvasFocused = true; let inPointerLock = false; + const shakeSamples: { x: number; y: number; time: number }[] = []; + let lastShakeTime = 0; // Event listeners @@ -159,6 +161,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli if (!viewportPointerInteractionOngoing && (inFloatingMenu || inGraphOverlay)) return; const modifiers = makeKeyboardModifiersBitfield(e); + if (detectShake(e)) editor.handle.onMouseShake(e.clientX, e.clientY, e.buttons, modifiers); editor.handle.onMouseMove(e.clientX, e.clientY, e.buttons, modifiers); } @@ -331,6 +334,71 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli }); } + function detectShake(e: PointerEvent | MouseEvent): boolean { + const SENSITIVITY_DIRECTION_CHANGES = 3; + const SENSITIVITY_DISTANCE_TO_DISPLACEMENT_RATIO = 0.1; + const DETECTION_WINDOW_MS = 500; + const DEBOUNCE_MS = 1000; + + // Add the current mouse position and time to our list of samples + const now = Date.now(); + shakeSamples.push({ x: e.clientX, y: e.clientY, time: now }); + + // Remove samples that are older than our time window + while (shakeSamples.length > 0 && now - shakeSamples[0].time > DETECTION_WINDOW_MS) { + shakeSamples.shift(); + } + + // We can't be shaking if it's too early in terms of samples or debounce time + if (shakeSamples.length <= 3 || now - lastShakeTime <= DEBOUNCE_MS) return false; + + // Calculate the total distance traveled + let totalDistanceSquared = 0; + for (let i = 1; i < shakeSamples.length; i += 1) { + const p1 = shakeSamples[i - 1]; + const p2 = shakeSamples[i]; + totalDistanceSquared += (p2.x - p1.x) ** 2 + (p2.y - p1.y) ** 2; + } + + // Count the number of times the mouse changes direction significantly, and the average position of the mouse + let directionChanges = 0; + const averagePoint = { x: 0, y: 0 }; + let averagePointCount = 0; + for (let i = 0; i < shakeSamples.length - 2; i += 1) { + const p1 = shakeSamples[i]; + const p2 = shakeSamples[i + 1]; + const p3 = shakeSamples[i + 2]; + + const vector1 = { x: p2.x - p1.x, y: p2.y - p1.y }; + const vector2 = { x: p3.x - p2.x, y: p3.y - p2.y }; + + // Check if the dot product is negative, which indicates the angle between vectors is > 90 degrees + if (vector1.x * vector2.x + vector1.y * vector2.y < 0) directionChanges += 1; + + averagePoint.x += p2.x; + averagePoint.y += p2.y; + averagePointCount += 1; + } + if (averagePointCount > 0) { + averagePoint.x /= averagePointCount; + averagePoint.y /= averagePointCount; + } + + // Calculate the displacement (the distance between the first and last mouse positions) + const lastPoint = shakeSamples[shakeSamples.length - 1]; + const displacementSquared = (lastPoint.x - averagePoint.x) ** 2 + (lastPoint.y - averagePoint.y) ** 2; + + // A shake is detected if the mouse has traveled a lot but not moved far, and has changed direction enough times + if (SENSITIVITY_DISTANCE_TO_DISPLACEMENT_RATIO * totalDistanceSquared >= displacementSquared && directionChanges >= SENSITIVITY_DIRECTION_CHANGES) { + lastShakeTime = now; + shakeSamples.length = 0; + + return true; + } + + return false; + } + // Frontend message subscriptions editor.subscriptions.subscribeJsMessage(TriggerPaste, async () => { diff --git a/frontend/wasm/Cargo.toml b/frontend/wasm/Cargo.toml index 2843820124..13e45bd07e 100644 --- a/frontend/wasm/Cargo.toml +++ b/frontend/wasm/Cargo.toml @@ -2,7 +2,7 @@ name = "graphite-wasm" publish = false version = "0.0.0" -rust-version = "1.85" +rust-version = "1.88" authors = ["Graphite Authors "] edition = "2024" readme = "../../README.md" @@ -13,7 +13,7 @@ license = "Apache-2.0" [features] default = ["gpu"] gpu = ["editor/gpu"] -tauri = [ "editor/tauri"] +tauri = ["editor/tauri"] [lib] crate-type = ["cdylib", "rlib"] diff --git a/frontend/wasm/src/editor_api.rs b/frontend/wasm/src/editor_api.rs index c8056e4efc..57c5899e0b 100644 --- a/frontend/wasm/src/editor_api.rs +++ b/frontend/wasm/src/editor_api.rs @@ -384,6 +384,17 @@ impl EditorHandle { self.dispatch(message); } + /// Mouse shaken + #[wasm_bindgen(js_name = onMouseShake)] + pub fn on_mouse_shake(&self, x: f64, y: f64, mouse_keys: u8, modifiers: u8) { + let editor_mouse_state = EditorMouseState::from_keys_and_editor_position(mouse_keys, (x, y).into()); + + let modifier_keys = ModifierKeys::from_bits(modifiers).expect("Invalid modifier keys"); + + let message = InputPreprocessorMessage::PointerShake { editor_mouse_state, modifier_keys }; + self.dispatch(message); + } + /// Mouse double clicked #[wasm_bindgen(js_name = onDoubleClick)] pub fn on_double_click(&self, x: f64, y: f64, mouse_keys: u8, modifiers: u8) { diff --git a/libraries/bezier-rs/src/poisson_disk.rs b/libraries/bezier-rs/src/poisson_disk.rs index cd2d8d45f0..42cb904a8a 100644 --- a/libraries/bezier-rs/src/poisson_disk.rs +++ b/libraries/bezier-rs/src/poisson_disk.rs @@ -169,7 +169,7 @@ where A::Item: Clone, B::Item: Clone, { - a.flat_map(move |i| (b.clone().map(move |j| (i.clone(), j)))) + a.flat_map(move |i| b.clone().map(move |j| (i.clone(), j))) } /// A square (represented by its top left corner position and width/height of `square_size`) that is currently a candidate for targetting by the dart throwing process. diff --git a/libraries/path-bool/src/path_boolean.rs b/libraries/path-bool/src/path_boolean.rs index c426b0db88..fa858e9360 100644 --- a/libraries/path-bool/src/path_boolean.rs +++ b/libraries/path-bool/src/path_boolean.rs @@ -1086,7 +1086,7 @@ fn compute_dual(minor_graph: &MinorGraph) -> Result { let outer_face_key = if count != 1 { #[cfg(feature = "logging")] eprintln!("Found multiple outer faces: {areas:?}, falling back to area calculation"); - let (key, _) = *areas.iter().max_by_key(|(_, area)| ((area.abs() * 1000.) as u64)).unwrap(); + let (key, _) = *areas.iter().max_by_key(|(_, area)| (area.abs() * 1000.) as u64).unwrap(); *key } else { *windings diff --git a/node-graph/gcore/src/color/color.rs b/node-graph/gcore/src/color/color.rs index 69edc39e87..0ca55220d6 100644 --- a/node-graph/gcore/src/color/color.rs +++ b/node-graph/gcore/src/color/color.rs @@ -835,6 +835,20 @@ impl Color { [(gamma.red * 255.) as u8, (gamma.green * 255.) as u8, (gamma.blue * 255.) as u8, (gamma.alpha * 255.) as u8] } + /// Return the all RGB components as a u8 slice, first component is red, followed by green, followed by blue. Use this if the [`Color`] is in linear space. + /// + /// # Examples + /// ``` + /// use graphene_core::color::Color; + /// let color = Color::from_rgbaf32(0.114, 0.103, 0.98, 0.97).unwrap(); + /// // TODO: Add test + /// ``` + #[inline(always)] + pub fn to_rgb8_srgb(&self) -> [u8; 3] { + let gamma = self.to_gamma_srgb(); + [(gamma.red * 255.) as u8, (gamma.green * 255.) as u8, (gamma.blue * 255.) as u8] + } + // https://www.niwa.nu/2013/05/math-behind-colorspace-conversions-rgb-hsl/ /// Convert a [Color] to a hue, saturation, lightness and alpha (all between 0 and 1) /// diff --git a/node-graph/gcore/src/instances.rs b/node-graph/gcore/src/instances.rs index ca76745e03..4f0442edda 100644 --- a/node-graph/gcore/src/instances.rs +++ b/node-graph/gcore/src/instances.rs @@ -27,6 +27,24 @@ impl Instances { } } + pub fn new_instance(instance: Instance) -> Self { + Self { + instance: vec![instance.instance], + transform: vec![instance.transform], + alpha_blending: vec![instance.alpha_blending], + source_node_id: vec![instance.source_node_id], + } + } + + pub fn with_capacity(capacity: usize) -> Self { + Self { + instance: Vec::with_capacity(capacity), + transform: Vec::with_capacity(capacity), + alpha_blending: Vec::with_capacity(capacity), + source_node_id: Vec::with_capacity(capacity), + } + } + pub fn push(&mut self, instance: Instance) { self.instance.push(instance.instance); self.transform.push(instance.transform); @@ -161,6 +179,18 @@ unsafe impl StaticType for Instances { type Static = Instances; } +impl FromIterator> for Instances { + fn from_iter>>(iter: I) -> Self { + let iter = iter.into_iter(); + let (lower, _) = iter.size_hint(); + let mut instances = Self::with_capacity(lower); + for instance in iter { + instances.push(instance); + } + instances + } +} + fn one_daffine2_default() -> Vec { vec![DAffine2::IDENTITY] } diff --git a/node-graph/gcore/src/logic.rs b/node-graph/gcore/src/logic.rs index 9be5e7c60b..1c48970b7a 100644 --- a/node-graph/gcore/src/logic.rs +++ b/node-graph/gcore/src/logic.rs @@ -10,10 +10,18 @@ use crate::{Context, Ctx}; use glam::{DAffine2, DVec2}; #[node_macro::node(category("Text"))] -fn to_string(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, VectorDataTable, DAffine2)] value: T) -> String { +fn to_string(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, DAffine2, VectorDataTable)] value: T) -> String { format!("{:?}", value) } +#[node_macro::node(category("Text"))] +fn serialize( + _: impl Ctx, + #[implementations(String, bool, f64, u32, u64, DVec2, DAffine2, Color, Option, GraphicGroupTable, VectorDataTable, RasterDataTable)] value: T, +) -> String { + serde_json::to_string(&value).unwrap_or_else(|_| "Serialization Error".to_string()) +} + #[node_macro::node(category("Text"))] fn string_concatenate(_: impl Ctx, #[implementations(String)] first: String, second: TextArea) -> String { first.clone() + &second diff --git a/node-graph/gcore/src/text/to_path.rs b/node-graph/gcore/src/text/to_path.rs index b96e03ffb5..880a05fe8e 100644 --- a/node-graph/gcore/src/text/to_path.rs +++ b/node-graph/gcore/src/text/to_path.rs @@ -50,16 +50,14 @@ impl PathBuilder { } if per_glyph_instances { - if !self.glyph_subpaths.is_empty() { - self.vector_table.push(Instance { - instance: VectorData::from_subpaths(core::mem::take(&mut self.glyph_subpaths), false), - transform: DAffine2::from_translation(glyph_offset), - ..Default::default() - }) - } - } else if !self.glyph_subpaths.is_empty() { - for subpath in self.glyph_subpaths.iter() { - // Unwrapping here is ok, since the check above guarantees there is at least one `VectorData` + self.vector_table.push(Instance { + instance: VectorData::from_subpaths(core::mem::take(&mut self.glyph_subpaths), false), + transform: DAffine2::from_translation(glyph_offset), + ..Default::default() + }); + } else { + for subpath in self.glyph_subpaths.drain(..) { + // Unwrapping here is ok because `self.vector_table` is initialized with a single `VectorData` self.vector_table.get_mut(0).unwrap().instance.append_subpath(subpath, false); } } @@ -128,18 +126,26 @@ fn render_glyph_run(glyph_run: &GlyphRun<'_, ()>, path_builder: &mut PathBuilder // User-requested tilt applied around baseline to avoid vertical displacement // Translation ensures rotation point is at the baseline, not origin - let skew = DAffine2::from_translation(DVec2::new(0., run_y as f64)) - * DAffine2::from_cols_array(&[1., 0., -tilt.to_radians().tan(), 1., 0., 0.]) - * DAffine2::from_translation(DVec2::new(0., -run_y as f64)); + let skew = if per_glyph_instances { + DAffine2::from_cols_array(&[1., 0., -tilt.to_radians().tan(), 1., 0., 0.]) + } else { + DAffine2::from_translation(DVec2::new(0., run_y as f64)) + * DAffine2::from_cols_array(&[1., 0., -tilt.to_radians().tan(), 1., 0., 0.]) + * DAffine2::from_translation(DVec2::new(0., -run_y as f64)) + }; let synthesis = run.synthesis(); // Font synthesis (e.g., synthetic italic) applied separately from user transforms // This preserves the distinction between font styling and user transformations let style_skew = synthesis.skew().map(|angle| { - DAffine2::from_translation(DVec2::new(0., run_y as f64)) - * DAffine2::from_cols_array(&[1., 0., -angle.to_radians().tan() as f64, 1., 0., 0.]) - * DAffine2::from_translation(DVec2::new(0., -run_y as f64)) + if per_glyph_instances { + DAffine2::from_cols_array(&[1., 0., -angle.to_radians().tan() as f64, 1., 0., 0.]) + } else { + DAffine2::from_translation(DVec2::new(0., run_y as f64)) + * DAffine2::from_cols_array(&[1., 0., -angle.to_radians().tan() as f64, 1., 0., 0.]) + * DAffine2::from_translation(DVec2::new(0., -run_y as f64)) + } }); let font = run.font(); diff --git a/node-graph/gcore/src/vector/algorithms/poisson_disk.rs b/node-graph/gcore/src/vector/algorithms/poisson_disk.rs index da42a486de..7a3445f10b 100644 --- a/node-graph/gcore/src/vector/algorithms/poisson_disk.rs +++ b/node-graph/gcore/src/vector/algorithms/poisson_disk.rs @@ -182,7 +182,7 @@ where A::Item: Clone, B::Item: Clone, { - a.flat_map(move |i| (b.clone().map(move |j| (i.clone(), j)))) + a.flat_map(move |i| b.clone().map(move |j| (i.clone(), j))) } /// A square (represented by its top left corner position and width/height of `square_size`) that is currently a candidate for targetting by the dart throwing process. diff --git a/node-graph/gcore/src/vector/misc.rs b/node-graph/gcore/src/vector/misc.rs index 4a3ffd2d9a..bdf8a18da6 100644 --- a/node-graph/gcore/src/vector/misc.rs +++ b/node-graph/gcore/src/vector/misc.rs @@ -1,7 +1,9 @@ -use bezier_rs::BezierHandles; +use bezier_rs::{BezierHandles, ManipulatorGroup, Subpath}; use dyn_any::DynAny; use glam::DVec2; -use kurbo::{CubicBez, Line, PathSeg, Point, QuadBez}; +use kurbo::{BezPath, CubicBez, Line, PathSeg, Point, QuadBez}; + +use super::PointId; /// Represents different ways of calculating the centroid. #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, specta::Type, node_macro::ChoiceType)] @@ -138,3 +140,39 @@ pub fn handles_to_segment(start: DVec2, handles: BezierHandles, end: DVec2) -> P } } } + +pub fn subpath_to_kurbo_bezpath(subpath: Subpath) -> BezPath { + let maniputor_groups = subpath.manipulator_groups(); + let closed = subpath.closed(); + bezpath_from_manipulator_groups(maniputor_groups, closed) +} + +pub fn bezpath_from_manipulator_groups(manipulator_groups: &[ManipulatorGroup], closed: bool) -> BezPath { + let mut bezpath = kurbo::BezPath::new(); + let mut out_handle; + + let Some(first) = manipulator_groups.first() else { return bezpath }; + bezpath.move_to(dvec2_to_point(first.anchor)); + out_handle = first.out_handle; + + for manipulator in manipulator_groups.iter().skip(1) { + match (out_handle, manipulator.in_handle) { + (Some(handle_start), Some(handle_end)) => bezpath.curve_to(dvec2_to_point(handle_start), dvec2_to_point(handle_end), dvec2_to_point(manipulator.anchor)), + (None, None) => bezpath.line_to(dvec2_to_point(manipulator.anchor)), + (None, Some(handle)) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(manipulator.anchor)), + (Some(handle), None) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(manipulator.anchor)), + } + out_handle = manipulator.out_handle; + } + + if closed { + match (out_handle, first.in_handle) { + (Some(handle_start), Some(handle_end)) => bezpath.curve_to(dvec2_to_point(handle_start), dvec2_to_point(handle_end), dvec2_to_point(first.anchor)), + (None, None) => bezpath.line_to(dvec2_to_point(first.anchor)), + (None, Some(handle)) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(first.anchor)), + (Some(handle), None) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(first.anchor)), + } + bezpath.close_path(); + } + bezpath +} diff --git a/node-graph/gcore/src/vector/vector_data.rs b/node-graph/gcore/src/vector/vector_data.rs index 3523784545..04a929ed23 100644 --- a/node-graph/gcore/src/vector/vector_data.rs +++ b/node-graph/gcore/src/vector/vector_data.rs @@ -337,7 +337,7 @@ impl VectorData { /// Returns the number of linear segments connected to the given point. pub fn connected_linear_segments(&self, point_id: PointId) -> usize { self.segment_bezier_iter() - .filter(|(_, bez, start, end)| ((*start == point_id || *end == point_id) && matches!(bez.handles, BezierHandles::Linear))) + .filter(|(_, bez, start, end)| (*start == point_id || *end == point_id) && matches!(bez.handles, BezierHandles::Linear)) .count() } diff --git a/node-graph/gcore/src/vector/vector_nodes.rs b/node-graph/gcore/src/vector/vector_nodes.rs index 93e732938b..ca2349feed 100644 --- a/node-graph/gcore/src/vector/vector_nodes.rs +++ b/node-graph/gcore/src/vector/vector_nodes.rs @@ -443,110 +443,109 @@ async fn round_corners( #[default(5.)] min_angle_threshold: Angle, ) -> VectorDataTable { - let mut result_table = VectorDataTable::default(); + source + .instance_ref_iter() + .map(|source| { + let source_transform = *source.transform; + let source_transform_inverse = source_transform.inverse(); + let source = source.instance; - for source in source.instance_ref_iter() { - let source_transform = *source.transform; - let source_transform_inverse = source_transform.inverse(); - let source = source.instance; + let upstream_graphic_group = source.upstream_graphic_group.clone(); - let upstream_graphic_group = source.upstream_graphic_group.clone(); + // Flip the roundness to help with user intuition + let roundness = 1. - roundness; + // Convert 0-100 to 0-0.5 + let edge_length_limit = edge_length_limit * 0.005; - // Flip the roundness to help with user intuition - let roundness = 1. - roundness; - // Convert 0-100 to 0-0.5 - let edge_length_limit = edge_length_limit * 0.005; + let mut result = VectorData { + style: source.style.clone(), + ..Default::default() + }; - let mut result = VectorData { - style: source.style.clone(), - ..Default::default() - }; + // Grab the initial point ID as a stable starting point + let mut initial_point_id = source.point_domain.ids().first().copied().unwrap_or(PointId::generate()); - // Grab the initial point ID as a stable starting point - let mut initial_point_id = source.point_domain.ids().first().copied().unwrap_or(PointId::generate()); + for mut subpath in source.stroke_bezier_paths() { + subpath.apply_transform(source_transform); - for mut subpath in source.stroke_bezier_paths() { - subpath.apply_transform(source_transform); - - // End if not enough points for corner rounding - if subpath.manipulator_groups().len() < 3 { - result.append_subpath(subpath, false); - continue; - } - - let groups = subpath.manipulator_groups(); - let mut new_groups = Vec::new(); - let is_closed = subpath.closed(); - - for i in 0..groups.len() { - // Skip first and last points for open paths - if !is_closed && (i == 0 || i == groups.len() - 1) { - new_groups.push(groups[i]); + // End if not enough points for corner rounding + if subpath.manipulator_groups().len() < 3 { + result.append_subpath(subpath, false); continue; } - // Not the prettiest, but it makes the rest of the logic more readable - let prev_idx = if i == 0 { if is_closed { groups.len() - 1 } else { 0 } } else { i - 1 }; - let curr_idx = i; - let next_idx = if i == groups.len() - 1 { if is_closed { 0 } else { i } } else { i + 1 }; + let groups = subpath.manipulator_groups(); + let mut new_groups = Vec::new(); + let is_closed = subpath.closed(); - let prev = groups[prev_idx].anchor; - let curr = groups[curr_idx].anchor; - let next = groups[next_idx].anchor; + for i in 0..groups.len() { + // Skip first and last points for open paths + if !is_closed && (i == 0 || i == groups.len() - 1) { + new_groups.push(groups[i]); + continue; + } - let dir1 = (curr - prev).normalize_or(DVec2::X); - let dir2 = (next - curr).normalize_or(DVec2::X); + // Not the prettiest, but it makes the rest of the logic more readable + let prev_idx = if i == 0 { if is_closed { groups.len() - 1 } else { 0 } } else { i - 1 }; + let curr_idx = i; + let next_idx = if i == groups.len() - 1 { if is_closed { 0 } else { i } } else { i + 1 }; - let theta = PI - dir1.angle_to(dir2).abs(); + let prev = groups[prev_idx].anchor; + let curr = groups[curr_idx].anchor; + let next = groups[next_idx].anchor; - // Skip near-straight corners - if theta > PI - min_angle_threshold.to_radians() { - new_groups.push(groups[curr_idx]); - continue; + let dir1 = (curr - prev).normalize_or(DVec2::X); + let dir2 = (next - curr).normalize_or(DVec2::X); + + let theta = PI - dir1.angle_to(dir2).abs(); + + // Skip near-straight corners + if theta > PI - min_angle_threshold.to_radians() { + new_groups.push(groups[curr_idx]); + continue; + } + + // Calculate L, with limits to avoid extreme values + let distance_along_edge = radius / (theta / 2.).sin(); + let distance_along_edge = distance_along_edge.min(edge_length_limit * (curr - prev).length().min((next - curr).length())).max(0.01); + + // Find points on each edge at distance L from corner + let p1 = curr - dir1 * distance_along_edge; + let p2 = curr + dir2 * distance_along_edge; + + // Add first point (coming into the rounded corner) + new_groups.push(ManipulatorGroup { + anchor: p1, + in_handle: None, + out_handle: Some(curr - dir1 * distance_along_edge * roundness), + id: initial_point_id.next_id(), + }); + + // Add second point (coming out of the rounded corner) + new_groups.push(ManipulatorGroup { + anchor: p2, + in_handle: Some(curr + dir2 * distance_along_edge * roundness), + out_handle: None, + id: initial_point_id.next_id(), + }); } - // Calculate L, with limits to avoid extreme values - let distance_along_edge = radius / (theta / 2.).sin(); - let distance_along_edge = distance_along_edge.min(edge_length_limit * (curr - prev).length().min((next - curr).length())).max(0.01); - - // Find points on each edge at distance L from corner - let p1 = curr - dir1 * distance_along_edge; - let p2 = curr + dir2 * distance_along_edge; - - // Add first point (coming into the rounded corner) - new_groups.push(ManipulatorGroup { - anchor: p1, - in_handle: None, - out_handle: Some(curr - dir1 * distance_along_edge * roundness), - id: initial_point_id.next_id(), - }); - - // Add second point (coming out of the rounded corner) - new_groups.push(ManipulatorGroup { - anchor: p2, - in_handle: Some(curr + dir2 * distance_along_edge * roundness), - out_handle: None, - id: initial_point_id.next_id(), - }); + // One subpath for each shape + let mut rounded_subpath = Subpath::new(new_groups, is_closed); + rounded_subpath.apply_transform(source_transform_inverse); + result.append_subpath(rounded_subpath, false); } - // One subpath for each shape - let mut rounded_subpath = Subpath::new(new_groups, is_closed); - rounded_subpath.apply_transform(source_transform_inverse); - result.append_subpath(rounded_subpath, false); - } + result.upstream_graphic_group = upstream_graphic_group; - result.upstream_graphic_group = upstream_graphic_group; - - result_table.push(Instance { - instance: result, - transform: source_transform, - alpha_blending: Default::default(), - source_node_id: None, - }); - } - - result_table + Instance { + instance: result, + transform: source_transform, + alpha_blending: Default::default(), + source_node_id: None, + } + }) + .collect() } #[node_macro::node(name("Merge by Distance"), category("Vector: Modifier"), path(graphene_core::vector))] @@ -558,24 +557,22 @@ pub fn merge_by_distance( distance: PixelLength, algorithm: MergeByDistanceAlgorithm, ) -> VectorDataTable { - let mut result_table = VectorDataTable::default(); - match algorithm { - MergeByDistanceAlgorithm::Spatial => { - for mut vector_data_instance in vector_data.instance_iter() { + MergeByDistanceAlgorithm::Spatial => vector_data + .instance_iter() + .map(|mut vector_data_instance| { vector_data_instance.instance.merge_by_distance_spatial(vector_data_instance.transform, distance); - result_table.push(vector_data_instance); - } - } - MergeByDistanceAlgorithm::Topological => { - for mut vector_data_instance in vector_data.instance_iter() { + vector_data_instance + }) + .collect(), + MergeByDistanceAlgorithm::Topological => vector_data + .instance_iter() + .map(|mut vector_data_instance| { vector_data_instance.instance.merge_by_distance_topological(distance); - result_table.push(vector_data_instance); - } - } + vector_data_instance + }) + .collect(), } - - result_table } #[node_macro::node(category("Vector: Modifier"), path(graphene_core::vector))] @@ -584,75 +581,74 @@ async fn box_warp(_: impl Ctx, vector_data: VectorDataTable, #[expose] rectangle return vector_data; }; - let mut result_table = VectorDataTable::default(); + vector_data + .instance_iter() + .map(|mut vector_data_instance| { + let vector_data_transform = vector_data_instance.transform; + let vector_data = vector_data_instance.instance; - for mut vector_data_instance in vector_data.instance_iter() { - let vector_data_transform = vector_data_instance.transform; - let vector_data = vector_data_instance.instance; + // Get the bounding box of the source vector data + let source_bbox = vector_data.bounding_box_with_transform(vector_data_transform).unwrap_or([DVec2::ZERO, DVec2::ONE]); - // Get the bounding box of the source vector data - let source_bbox = vector_data.bounding_box_with_transform(vector_data_transform).unwrap_or([DVec2::ZERO, DVec2::ONE]); + // Extract first 4 points from target shape to form the quadrilateral + // Apply the target's transform to get points in world space + let target_points: Vec = target.point_domain.positions().iter().map(|&p| target_transform.transform_point2(p)).take(4).collect(); - // Extract first 4 points from target shape to form the quadrilateral - // Apply the target's transform to get points in world space - let target_points: Vec = target.point_domain.positions().iter().map(|&p| target_transform.transform_point2(p)).take(4).collect(); + // If we have fewer than 4 points, use the corners of the source bounding box + // This handles the degenerative case + let dst_corners = if target_points.len() >= 4 { + [target_points[0], target_points[1], target_points[2], target_points[3]] + } else { + warn!("Target shape has fewer than 4 points. Using source bounding box instead."); + [ + source_bbox[0], + DVec2::new(source_bbox[1].x, source_bbox[0].y), + source_bbox[1], + DVec2::new(source_bbox[0].x, source_bbox[1].y), + ] + }; - // If we have fewer than 4 points, use the corners of the source bounding box - // This handles the degenerative case - let dst_corners = if target_points.len() >= 4 { - [target_points[0], target_points[1], target_points[2], target_points[3]] - } else { - warn!("Target shape has fewer than 4 points. Using source bounding box instead."); - [ - source_bbox[0], - DVec2::new(source_bbox[1].x, source_bbox[0].y), - source_bbox[1], - DVec2::new(source_bbox[0].x, source_bbox[1].y), - ] - }; + // Apply the warp + let mut result = vector_data.clone(); - // Apply the warp - let mut result = vector_data.clone(); + // Precompute source bounding box size for normalization + let source_size = source_bbox[1] - source_bbox[0]; - // Precompute source bounding box size for normalization - let source_size = source_bbox[1] - source_bbox[0]; - - // Transform points - for (_, position) in result.point_domain.positions_mut() { - // Get the point in world space - let world_pos = vector_data_transform.transform_point2(*position); - - // Normalize coordinates within the source bounding box - let t = ((world_pos - source_bbox[0]) / source_size).clamp(DVec2::ZERO, DVec2::ONE); - - // Apply bilinear interpolation - *position = bilinear_interpolate(t, &dst_corners); - } - - // Transform handles in bezier curves - for (_, handles, _, _) in result.handles_mut() { - *handles = handles.apply_transformation(|pos| { - // Get the handle in world space - let world_pos = vector_data_transform.transform_point2(pos); + // Transform points + for (_, position) in result.point_domain.positions_mut() { + // Get the point in world space + let world_pos = vector_data_transform.transform_point2(*position); // Normalize coordinates within the source bounding box let t = ((world_pos - source_bbox[0]) / source_size).clamp(DVec2::ZERO, DVec2::ONE); // Apply bilinear interpolation - bilinear_interpolate(t, &dst_corners) - }); - } + *position = bilinear_interpolate(t, &dst_corners); + } - result.style.set_stroke_transform(DAffine2::IDENTITY); + // Transform handles in bezier curves + for (_, handles, _, _) in result.handles_mut() { + *handles = handles.apply_transformation(|pos| { + // Get the handle in world space + let world_pos = vector_data_transform.transform_point2(pos); - // Add this to the table and reset the transform since we've applied it directly to the points - vector_data_instance.instance = result; - vector_data_instance.transform = DAffine2::IDENTITY; - vector_data_instance.source_node_id = None; - result_table.push(vector_data_instance); - } + // Normalize coordinates within the source bounding box + let t = ((world_pos - source_bbox[0]) / source_size).clamp(DVec2::ZERO, DVec2::ONE); - result_table + // Apply bilinear interpolation + bilinear_interpolate(t, &dst_corners) + }); + } + + result.style.set_stroke_transform(DAffine2::IDENTITY); + + // Add this to the table and reset the transform since we've applied it directly to the points + vector_data_instance.instance = result; + vector_data_instance.transform = DAffine2::IDENTITY; + vector_data_instance.source_node_id = None; + vector_data_instance + }) + .collect() } // Interpolate within a quadrilateral using normalized coordinates (0-1) @@ -679,115 +675,114 @@ async fn auto_tangents( #[default(true)] preserve_existing: bool, ) -> VectorDataTable { - let mut result_table = VectorDataTable::default(); + source + .instance_ref_iter() + .map(|source| { + let transform = *source.transform; + let alpha_blending = *source.alpha_blending; + let source_node_id = *source.source_node_id; + let source = source.instance; - for source in source.instance_ref_iter() { - let transform = *source.transform; - let alpha_blending = *source.alpha_blending; - let source_node_id = *source.source_node_id; - let source = source.instance; + let mut result = VectorData { + style: source.style.clone(), + ..Default::default() + }; - let mut result = VectorData { - style: source.style.clone(), - ..Default::default() - }; + for mut subpath in source.stroke_bezier_paths() { + subpath.apply_transform(transform); - for mut subpath in source.stroke_bezier_paths() { - subpath.apply_transform(transform); + let groups = subpath.manipulator_groups(); + if groups.len() < 2 { + // Not enough points for softening or handle removal + result.append_subpath(subpath, true); + continue; + } - let groups = subpath.manipulator_groups(); - if groups.len() < 2 { - // Not enough points for softening or handle removal - result.append_subpath(subpath, true); - continue; - } + let mut new_groups = Vec::with_capacity(groups.len()); + let is_closed = subpath.closed(); - let mut new_groups = Vec::with_capacity(groups.len()); - let is_closed = subpath.closed(); + for i in 0..groups.len() { + let curr = &groups[i]; - for i in 0..groups.len() { - let curr = &groups[i]; + if preserve_existing { + // Check if this point has handles that are meaningfully different from the anchor + let has_handles = (curr.in_handle.is_some() && !curr.in_handle.unwrap().abs_diff_eq(curr.anchor, 1e-5)) + || (curr.out_handle.is_some() && !curr.out_handle.unwrap().abs_diff_eq(curr.anchor, 1e-5)); - if preserve_existing { - // Check if this point has handles that are meaningfully different from the anchor - let has_handles = (curr.in_handle.is_some() && !curr.in_handle.unwrap().abs_diff_eq(curr.anchor, 1e-5)) - || (curr.out_handle.is_some() && !curr.out_handle.unwrap().abs_diff_eq(curr.anchor, 1e-5)); + // If the point already has handles, or if it's an endpoint of an open path, keep it as is. + if has_handles || (!is_closed && (i == 0 || i == groups.len() - 1)) { + new_groups.push(*curr); + continue; + } + } - // If the point already has handles, or if it's an endpoint of an open path, keep it as is. - if has_handles || (!is_closed && (i == 0 || i == groups.len() - 1)) { + // If spread is 0, remove handles for this point, making it a sharp corner. + if spread == 0. { + new_groups.push(ManipulatorGroup { + anchor: curr.anchor, + in_handle: None, + out_handle: None, + id: curr.id, + }); + continue; + } + + // Get previous and next points for auto-tangent calculation + let prev_idx = if i == 0 { if is_closed { groups.len() - 1 } else { i } } else { i - 1 }; + let next_idx = if i == groups.len() - 1 { if is_closed { 0 } else { i } } else { i + 1 }; + + let prev = groups[prev_idx].anchor; + let curr_pos = curr.anchor; + let next = groups[next_idx].anchor; + + // Calculate directions from current point to adjacent points + let dir_prev = (prev - curr_pos).normalize_or_zero(); + let dir_next = (next - curr_pos).normalize_or_zero(); + + // Check if we have valid directions (e.g., points are not coincident) + if dir_prev.length_squared() < 1e-5 || dir_next.length_squared() < 1e-5 { + // Fallback: keep the original manipulator group (which has no active handles here) new_groups.push(*curr); continue; } - } - // If spread is 0, remove handles for this point, making it a sharp corner. - if spread == 0. { + // Calculate handle direction (colinear, pointing along the line from prev to next) + // Original logic: (dir_prev - dir_next) is equivalent to (prev - curr) - (next - curr) = prev - next + // The handle_dir will be along the line connecting prev and next, or perpendicular if they are coincident. + let mut handle_dir = (dir_prev - dir_next).try_normalize().unwrap_or_else(|| dir_prev.perp()); + + // Ensure consistent orientation of the handle_dir + // This makes the `+ handle_dir` for in_handle and `- handle_dir` for out_handle consistent + if dir_prev.dot(handle_dir) < 0. { + handle_dir = -handle_dir; + } + + // Calculate handle lengths: 1/3 of distance to adjacent points, scaled by spread + let in_length = (curr_pos - prev).length() / 3. * spread; + let out_length = (next - curr_pos).length() / 3. * spread; + + // Create new manipulator group with calculated auto-tangents new_groups.push(ManipulatorGroup { - anchor: curr.anchor, - in_handle: None, - out_handle: None, + anchor: curr_pos, + in_handle: Some(curr_pos + handle_dir * in_length), + out_handle: Some(curr_pos - handle_dir * out_length), id: curr.id, }); - continue; } - // Get previous and next points for auto-tangent calculation - let prev_idx = if i == 0 { if is_closed { groups.len() - 1 } else { i } } else { i - 1 }; - let next_idx = if i == groups.len() - 1 { if is_closed { 0 } else { i } } else { i + 1 }; - - let prev = groups[prev_idx].anchor; - let curr_pos = curr.anchor; - let next = groups[next_idx].anchor; - - // Calculate directions from current point to adjacent points - let dir_prev = (prev - curr_pos).normalize_or_zero(); - let dir_next = (next - curr_pos).normalize_or_zero(); - - // Check if we have valid directions (e.g., points are not coincident) - if dir_prev.length_squared() < 1e-5 || dir_next.length_squared() < 1e-5 { - // Fallback: keep the original manipulator group (which has no active handles here) - new_groups.push(*curr); - continue; - } - - // Calculate handle direction (colinear, pointing along the line from prev to next) - // Original logic: (dir_prev - dir_next) is equivalent to (prev - curr) - (next - curr) = prev - next - // The handle_dir will be along the line connecting prev and next, or perpendicular if they are coincident. - let mut handle_dir = (dir_prev - dir_next).try_normalize().unwrap_or_else(|| dir_prev.perp()); - - // Ensure consistent orientation of the handle_dir - // This makes the `+ handle_dir` for in_handle and `- handle_dir` for out_handle consistent - if dir_prev.dot(handle_dir) < 0. { - handle_dir = -handle_dir; - } - - // Calculate handle lengths: 1/3 of distance to adjacent points, scaled by spread - let in_length = (curr_pos - prev).length() / 3. * spread; - let out_length = (next - curr_pos).length() / 3. * spread; - - // Create new manipulator group with calculated auto-tangents - new_groups.push(ManipulatorGroup { - anchor: curr_pos, - in_handle: Some(curr_pos + handle_dir * in_length), - out_handle: Some(curr_pos - handle_dir * out_length), - id: curr.id, - }); + let mut softened_subpath = Subpath::new(new_groups, is_closed); + softened_subpath.apply_transform(transform.inverse()); + result.append_subpath(softened_subpath, true); } - let mut softened_subpath = Subpath::new(new_groups, is_closed); - softened_subpath.apply_transform(transform.inverse()); - result.append_subpath(softened_subpath, true); - } - - result_table.push(Instance { - instance: result, - transform, - alpha_blending, - source_node_id, - }); - } - - result_table + Instance { + instance: result, + transform, + alpha_blending, + source_node_id, + } + }) + .collect() } // TODO: Fix issues and reenable @@ -904,29 +899,28 @@ async fn auto_tangents( #[node_macro::node(category("Vector: Modifier"), path(graphene_core::vector))] async fn bounding_box(_: impl Ctx, vector_data: VectorDataTable) -> VectorDataTable { - let mut result_table = VectorDataTable::default(); + vector_data + .instance_iter() + .map(|mut vector_data_instance| { + let vector_data = vector_data_instance.instance; - for mut vector_data_instance in vector_data.instance_iter() { - let vector_data = vector_data_instance.instance; + let mut result = vector_data + .bounding_box_rect() + .map(|bbox| { + let mut vector_data = VectorData::default(); + vector_data.append_bezpath(bbox.to_path(DEFAULT_ACCURACY)); + vector_data + }) + .unwrap_or_default(); - let mut result = vector_data - .bounding_box_rect() - .map(|bbox| { - let mut vector_data = VectorData::default(); - vector_data.append_bezpath(bbox.to_path(DEFAULT_ACCURACY)); - vector_data - }) - .unwrap_or_default(); + result.style = vector_data.style.clone(); + result.style.set_stroke_transform(DAffine2::IDENTITY); - result.style = vector_data.style.clone(); - result.style.set_stroke_transform(DAffine2::IDENTITY); - - vector_data_instance.instance = result; - vector_data_instance.source_node_id = None; - result_table.push(vector_data_instance); - } - - result_table + vector_data_instance.instance = result; + vector_data_instance.source_node_id = None; + vector_data_instance + }) + .collect() } #[node_macro::node(category("Vector: Measure"), path(graphene_core::vector))] @@ -944,17 +938,13 @@ async fn dimensions(_: impl Ctx, vector_data: VectorDataTable) -> DVec2 { /// This is useful in conjunction with nodes that repeat it, followed by the "Points to Polyline" node to string together a path of the points. #[node_macro::node(category("Vector"), name("Coordinate to Point"), path(graphene_core::vector))] async fn position_to_point(_: impl Ctx, coordinate: DVec2) -> VectorDataTable { - let mut result_table = VectorDataTable::default(); - let mut point_domain = PointDomain::new(); point_domain.push(PointId::generate(), coordinate); - result_table.push(Instance { + VectorDataTable::new_instance(Instance { instance: VectorData { point_domain, ..Default::default() }, ..Default::default() - }); - - result_table + }) } /// Creates a polyline from a series of vector points, replacing any existing segments and regions that may already exist. @@ -988,102 +978,100 @@ async fn points_to_polyline(_: impl Ctx, mut points: VectorDataTable, #[default( #[node_macro::node(category("Vector: Modifier"), path(graphene_core::vector), properties("offset_path_properties"))] async fn offset_path(_: impl Ctx, vector_data: VectorDataTable, distance: f64, join: StrokeJoin, #[default(4.)] miter_limit: f64) -> VectorDataTable { - let mut result_table = VectorDataTable::default(); + vector_data + .instance_iter() + .map(|mut vector_data_instance| { + let vector_data_transform = vector_data_instance.transform; + let vector_data = vector_data_instance.instance; - for mut vector_data_instance in vector_data.instance_iter() { - let vector_data_transform = vector_data_instance.transform; - let vector_data = vector_data_instance.instance; + let subpaths = vector_data.stroke_bezier_paths(); + let mut result = VectorData { + style: vector_data.style.clone(), + ..Default::default() + }; + result.style.set_stroke_transform(DAffine2::IDENTITY); - let subpaths = vector_data.stroke_bezier_paths(); - let mut result = VectorData { - style: vector_data.style.clone(), - ..Default::default() - }; - result.style.set_stroke_transform(DAffine2::IDENTITY); + // Perform operation on all subpaths in this shape. + for mut subpath in subpaths { + subpath.apply_transform(vector_data_transform); - // Perform operation on all subpaths in this shape. - for mut subpath in subpaths { - subpath.apply_transform(vector_data_transform); + // Taking the existing stroke data and passing it to Bezier-rs to generate new paths. + let mut subpath_out = offset_subpath( + &subpath, + -distance, + match join { + StrokeJoin::Miter => Join::Miter(Some(miter_limit)), + StrokeJoin::Bevel => Join::Bevel, + StrokeJoin::Round => Join::Round, + }, + ); - // Taking the existing stroke data and passing it to Bezier-rs to generate new paths. - let mut subpath_out = offset_subpath( - &subpath, - -distance, - match join { - StrokeJoin::Miter => Join::Miter(Some(miter_limit)), - StrokeJoin::Bevel => Join::Bevel, - StrokeJoin::Round => Join::Round, - }, - ); + subpath_out.apply_transform(vector_data_transform.inverse()); - subpath_out.apply_transform(vector_data_transform.inverse()); + // One closed subpath, open path. + result.append_subpath(subpath_out, false); + } - // One closed subpath, open path. - result.append_subpath(subpath_out, false); - } - - vector_data_instance.instance = result; - vector_data_instance.source_node_id = None; - result_table.push(vector_data_instance); - } - - result_table + vector_data_instance.instance = result; + vector_data_instance.source_node_id = None; + vector_data_instance + }) + .collect() } #[node_macro::node(category("Vector: Modifier"), path(graphene_core::vector))] async fn solidify_stroke(_: impl Ctx, vector_data: VectorDataTable) -> VectorDataTable { - let mut result_table = VectorDataTable::default(); + vector_data + .instance_iter() + .map(|mut vector_data_instance| { + let vector_data = vector_data_instance.instance; - for mut vector_data_instance in vector_data.instance_iter() { - let vector_data = vector_data_instance.instance; + let stroke = vector_data.style.stroke().clone().unwrap_or_default(); + let bezpaths = vector_data.stroke_bezpath_iter(); + let mut result = VectorData::default(); - let stroke = vector_data.style.stroke().clone().unwrap_or_default(); - let bezpaths = vector_data.stroke_bezpath_iter(); - let mut result = VectorData::default(); + // Taking the existing stroke data and passing it to kurbo::stroke to generate new fill paths. + let join = match stroke.join { + StrokeJoin::Miter => kurbo::Join::Miter, + StrokeJoin::Bevel => kurbo::Join::Bevel, + StrokeJoin::Round => kurbo::Join::Round, + }; + let cap = match stroke.cap { + StrokeCap::Butt => kurbo::Cap::Butt, + StrokeCap::Round => kurbo::Cap::Round, + StrokeCap::Square => kurbo::Cap::Square, + }; + let dash_offset = stroke.dash_offset; + let dash_pattern = stroke.dash_lengths; + let miter_limit = stroke.join_miter_limit; - // Taking the existing stroke data and passing it to kurbo::stroke to generate new fill paths. - let join = match stroke.join { - StrokeJoin::Miter => kurbo::Join::Miter, - StrokeJoin::Bevel => kurbo::Join::Bevel, - StrokeJoin::Round => kurbo::Join::Round, - }; - let cap = match stroke.cap { - StrokeCap::Butt => kurbo::Cap::Butt, - StrokeCap::Round => kurbo::Cap::Round, - StrokeCap::Square => kurbo::Cap::Square, - }; - let dash_offset = stroke.dash_offset; - let dash_pattern = stroke.dash_lengths; - let miter_limit = stroke.join_miter_limit; + let stroke_style = kurbo::Stroke::new(stroke.weight) + .with_caps(cap) + .with_join(join) + .with_dashes(dash_offset, dash_pattern) + .with_miter_limit(miter_limit); - let stroke_style = kurbo::Stroke::new(stroke.weight) - .with_caps(cap) - .with_join(join) - .with_dashes(dash_offset, dash_pattern) - .with_miter_limit(miter_limit); + let stroke_options = kurbo::StrokeOpts::default(); - let stroke_options = kurbo::StrokeOpts::default(); + // 0.25 is balanced between performace and accuracy of the curve. + const STROKE_TOLERANCE: f64 = 0.25; - // 0.25 is balanced between performace and accuracy of the curve. - const STROKE_TOLERANCE: f64 = 0.25; + for path in bezpaths { + let solidified = kurbo::stroke(path, &stroke_style, &stroke_options, STROKE_TOLERANCE); + result.append_bezpath(solidified); + } - for path in bezpaths { - let solidified = kurbo::stroke(path, &stroke_style, &stroke_options, STROKE_TOLERANCE); - result.append_bezpath(solidified); - } + // We set our fill to our stroke's color, then clear our stroke. + if let Some(stroke) = vector_data.style.stroke() { + result.style.set_fill(Fill::solid_or_none(stroke.color)); + result.style.set_stroke(Stroke::default()); + } - // We set our fill to our stroke's color, then clear our stroke. - if let Some(stroke) = vector_data.style.stroke() { - result.style.set_fill(Fill::solid_or_none(stroke.color)); - result.style.set_stroke(Stroke::default()); - } - - vector_data_instance.instance = result; - vector_data_instance.source_node_id = None; - result_table.push(vector_data_instance); - } - - result_table + vector_data_instance.instance = result; + vector_data_instance.source_node_id = None; + vector_data_instance + }) + .collect() } #[node_macro::node(category("Vector"), path(graphene_core::vector))] @@ -1154,60 +1142,59 @@ async fn sample_polyline( adaptive_spacing: bool, subpath_segment_lengths: Vec, ) -> VectorDataTable { - let mut result_table = VectorDataTable::default(); - - for mut vector_data_instance in vector_data.instance_iter() { - let mut result = VectorData { - point_domain: Default::default(), - segment_domain: Default::default(), - region_domain: Default::default(), - colinear_manipulators: Default::default(), - style: std::mem::take(&mut vector_data_instance.instance.style), - upstream_graphic_group: std::mem::take(&mut vector_data_instance.instance.upstream_graphic_group), - }; - // Transfer the stroke transform from the input vector data to the result. - result.style.set_stroke_transform(vector_data_instance.transform); - - // Using `stroke_bezpath_iter` so that the `subpath_segment_lengths` is aligned to the segments of each bezpath. - // So we can index into `subpath_segment_lengths` to get the length of the segments. - // NOTE: `subpath_segment_lengths` has precalulated lengths with transformation applied. - let bezpaths = vector_data_instance.instance.stroke_bezpath_iter(); - - // Keeps track of the index of the first segment of the next bezpath in order to get lengths of all segments. - let mut next_segment_index = 0; - - for mut bezpath in bezpaths { - // Apply the tranformation to the current bezpath to calculate points after transformation. - bezpath.apply_affine(Affine::new(vector_data_instance.transform.to_cols_array())); - - let segment_count = bezpath.segments().count(); - - // For the current bezpath we get its segment's length by calculating the start index and end index. - let current_bezpath_segments_length = &subpath_segment_lengths[next_segment_index..next_segment_index + segment_count]; - - // Increment the segment index by the number of segments in the current bezpath to calculate the next bezpath segment's length. - next_segment_index += segment_count; - - let amount = match spacing { - PointSpacingType::Separation => separation, - PointSpacingType::Quantity => quantity as f64, - }; - let Some(mut sample_bezpath) = sample_polyline_on_bezpath(bezpath, spacing, amount, start_offset, stop_offset, adaptive_spacing, current_bezpath_segments_length) else { - continue; + vector_data + .instance_iter() + .map(|mut vector_data_instance| { + let mut result = VectorData { + point_domain: Default::default(), + segment_domain: Default::default(), + region_domain: Default::default(), + colinear_manipulators: Default::default(), + style: std::mem::take(&mut vector_data_instance.instance.style), + upstream_graphic_group: std::mem::take(&mut vector_data_instance.instance.upstream_graphic_group), }; + // Transfer the stroke transform from the input vector data to the result. + result.style.set_stroke_transform(vector_data_instance.transform); - // Reverse the transformation applied to the bezpath as the `result` already has the transformation set. - sample_bezpath.apply_affine(Affine::new(vector_data_instance.transform.to_cols_array()).inverse()); + // Using `stroke_bezpath_iter` so that the `subpath_segment_lengths` is aligned to the segments of each bezpath. + // So we can index into `subpath_segment_lengths` to get the length of the segments. + // NOTE: `subpath_segment_lengths` has precalulated lengths with transformation applied. + let bezpaths = vector_data_instance.instance.stroke_bezpath_iter(); - // Append the bezpath (subpath) that connects generated points by lines. - result.append_bezpath(sample_bezpath); - } + // Keeps track of the index of the first segment of the next bezpath in order to get lengths of all segments. + let mut next_segment_index = 0; - vector_data_instance.instance = result; - result_table.push(vector_data_instance); - } + for mut bezpath in bezpaths { + // Apply the tranformation to the current bezpath to calculate points after transformation. + bezpath.apply_affine(Affine::new(vector_data_instance.transform.to_cols_array())); - result_table + let segment_count = bezpath.segments().count(); + + // For the current bezpath we get its segment's length by calculating the start index and end index. + let current_bezpath_segments_length = &subpath_segment_lengths[next_segment_index..next_segment_index + segment_count]; + + // Increment the segment index by the number of segments in the current bezpath to calculate the next bezpath segment's length. + next_segment_index += segment_count; + + let amount = match spacing { + PointSpacingType::Separation => separation, + PointSpacingType::Quantity => quantity as f64, + }; + let Some(mut sample_bezpath) = sample_polyline_on_bezpath(bezpath, spacing, amount, start_offset, stop_offset, adaptive_spacing, current_bezpath_segments_length) else { + continue; + }; + + // Reverse the transformation applied to the bezpath as the `result` already has the transformation set. + sample_bezpath.apply_affine(Affine::new(vector_data_instance.transform.to_cols_array()).inverse()); + + // Append the bezpath (subpath) that connects generated points by lines. + result.append_bezpath(sample_bezpath); + } + + vector_data_instance.instance = result; + vector_data_instance + }) + .collect() } /// Splits a path at a given progress from 0 to 1 along the path, creating two new subpaths from the original one (if the path is initially open) or one open subpath (if the path is initially closed). @@ -1404,42 +1391,40 @@ async fn poisson_disk_points( ) -> VectorDataTable { let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into()); - let mut result_table = VectorDataTable::default(); + vector_data + .instance_iter() + .map(|mut vector_data_instance| { + let mut result = VectorData::default(); - for mut vector_data_instance in vector_data.instance_iter() { - let mut result = VectorData::default(); + let path_with_bounding_boxes: Vec<_> = vector_data_instance + .instance + .stroke_bezpath_iter() + .map(|mut bezpath| { + // TODO: apply transform to points instead of modifying the paths + bezpath.close_path(); + let bbox = bezpath.bounding_box(); + (bezpath, bbox) + }) + .collect(); - let path_with_bounding_boxes: Vec<_> = vector_data_instance - .instance - .stroke_bezpath_iter() - .map(|mut bezpath| { - // TODO: apply transform to points instead of modifying the paths - bezpath.close_path(); - let bbox = bezpath.bounding_box(); - (bezpath, bbox) - }) - .collect(); + for (i, (subpath, _)) in path_with_bounding_boxes.iter().enumerate() { + if subpath.segments().count() < 2 { + continue; + } - for (i, (subpath, _)) in path_with_bounding_boxes.iter().enumerate() { - if subpath.segments().count() < 2 { - continue; + for point in bezpath_algorithms::poisson_disk_points(i, &path_with_bounding_boxes, separation_disk_diameter, || rng.random::()) { + result.point_domain.push(PointId::generate(), point); + } } - for point in bezpath_algorithms::poisson_disk_points(i, &path_with_bounding_boxes, separation_disk_diameter, || rng.random::()) { - result.point_domain.push(PointId::generate(), point); - } - } + // Transfer the style from the input vector data to the result. + result.style = vector_data_instance.instance.style.clone(); + result.style.set_stroke_transform(DAffine2::IDENTITY); - // Transfer the style from the input vector data to the result. - result.style = vector_data_instance.instance.style.clone(); - result.style.set_stroke_transform(DAffine2::IDENTITY); - - vector_data_instance.instance = result; - - result_table.push(vector_data_instance); - } - - result_table + vector_data_instance.instance = result; + vector_data_instance + }) + .collect() } #[node_macro::node(category(""), path(graphene_core::vector))] @@ -1462,48 +1447,47 @@ async fn subpath_segment_lengths(_: impl Ctx, vector_data: VectorDataTable) -> V #[node_macro::node(name("Spline"), category("Vector: Modifier"), path(graphene_core::vector))] async fn spline(_: impl Ctx, vector_data: VectorDataTable) -> VectorDataTable { - let mut result_table = VectorDataTable::default(); - - for mut vector_data_instance in vector_data.instance_iter() { - // Exit early if there are no points to generate splines from. - if vector_data_instance.instance.point_domain.positions().is_empty() { - continue; - } - - let mut segment_domain = SegmentDomain::default(); - for (manipulator_groups, closed) in vector_data_instance.instance.stroke_manipulator_groups() { - let positions = manipulator_groups.iter().map(|group| group.anchor).collect::>(); - let closed = closed && positions.len() > 2; - - // Compute control point handles for Bezier spline. - let first_handles = if closed { - solve_spline_first_handle_closed(&positions) - } else { - solve_spline_first_handle_open(&positions) - }; - - let stroke_id = StrokeId::ZERO; - - // Create segments with computed Bezier handles and add them to vector data. - for i in 0..(positions.len() - if closed { 0 } else { 1 }) { - let next_index = (i + 1) % positions.len(); - - let start_index = vector_data_instance.instance.point_domain.resolve_id(manipulator_groups[i].id).unwrap(); - let end_index = vector_data_instance.instance.point_domain.resolve_id(manipulator_groups[next_index].id).unwrap(); - - let handle_start = first_handles[i]; - let handle_end = positions[next_index] * 2. - first_handles[next_index]; - let handles = bezier_rs::BezierHandles::Cubic { handle_start, handle_end }; - - segment_domain.push(SegmentId::generate(), start_index, end_index, handles, stroke_id); + vector_data + .instance_iter() + .filter_map(|mut vector_data_instance| { + // Exit early if there are no points to generate splines from. + if vector_data_instance.instance.point_domain.positions().is_empty() { + return None; } - } - vector_data_instance.instance.segment_domain = segment_domain; - result_table.push(vector_data_instance); - } + let mut segment_domain = SegmentDomain::default(); + for (manipulator_groups, closed) in vector_data_instance.instance.stroke_manipulator_groups() { + let positions = manipulator_groups.iter().map(|group| group.anchor).collect::>(); + let closed = closed && positions.len() > 2; - result_table + // Compute control point handles for Bezier spline. + let first_handles = if closed { + solve_spline_first_handle_closed(&positions) + } else { + solve_spline_first_handle_open(&positions) + }; + + let stroke_id = StrokeId::ZERO; + + // Create segments with computed Bezier handles and add them to vector data. + for i in 0..(positions.len() - if closed { 0 } else { 1 }) { + let next_index = (i + 1) % positions.len(); + + let start_index = vector_data_instance.instance.point_domain.resolve_id(manipulator_groups[i].id).unwrap(); + let end_index = vector_data_instance.instance.point_domain.resolve_id(manipulator_groups[next_index].id).unwrap(); + + let handle_start = first_handles[i]; + let handle_end = positions[next_index] * 2. - first_handles[next_index]; + let handles = bezier_rs::BezierHandles::Cubic { handle_start, handle_end }; + + segment_domain.push(SegmentId::generate(), start_index, end_index, handles, stroke_id); + } + } + + vector_data_instance.instance.segment_domain = segment_domain; + Some(vector_data_instance) + }) + .collect() } #[node_macro::node(category("Vector: Modifier"), path(graphene_core::vector))] @@ -1515,55 +1499,58 @@ async fn jitter_points( amount: f64, seed: SeedValue, ) -> VectorDataTable { - let mut result_table = VectorDataTable::default(); + vector_data + .instance_iter() + .map(|mut vector_data_instance| { + let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into()); - for mut vector_data_instance in vector_data.instance_iter() { - let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into()); + let vector_data_transform = vector_data_instance.transform; + let inverse_transform = if vector_data_transform.matrix2.determinant() != 0. { + vector_data_transform.inverse() + } else { + Default::default() + }; - let vector_data_transform = vector_data_instance.transform; - let inverse_transform = (vector_data_transform.matrix2.determinant() != 0.).then(|| vector_data_transform.inverse()).unwrap_or_default(); + let deltas = (0..vector_data_instance.instance.point_domain.positions().len()) + .map(|_| { + let angle = rng.random::() * TAU; - let deltas = (0..vector_data_instance.instance.point_domain.positions().len()) - .map(|_| { - let angle = rng.random::() * TAU; + inverse_transform.transform_vector2(DVec2::from_angle(angle) * rng.random::() * amount) + }) + .collect::>(); + let mut already_applied = vec![false; vector_data_instance.instance.point_domain.positions().len()]; - inverse_transform.transform_vector2(DVec2::from_angle(angle) * rng.random::() * amount) - }) - .collect::>(); - let mut already_applied = vec![false; vector_data_instance.instance.point_domain.positions().len()]; + for (handles, start, end) in vector_data_instance.instance.segment_domain.handles_and_points_mut() { + let start_delta = deltas[*start]; + let end_delta = deltas[*end]; - for (handles, start, end) in vector_data_instance.instance.segment_domain.handles_and_points_mut() { - let start_delta = deltas[*start]; - let end_delta = deltas[*end]; - - if !already_applied[*start] { - let start_position = vector_data_instance.instance.point_domain.positions()[*start]; - vector_data_instance.instance.point_domain.set_position(*start, start_position + start_delta); - already_applied[*start] = true; - } - if !already_applied[*end] { - let end_position = vector_data_instance.instance.point_domain.positions()[*end]; - vector_data_instance.instance.point_domain.set_position(*end, end_position + end_delta); - already_applied[*end] = true; - } - - match handles { - bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => { - *handle_start += start_delta; - *handle_end += end_delta; + if !already_applied[*start] { + let start_position = vector_data_instance.instance.point_domain.positions()[*start]; + vector_data_instance.instance.point_domain.set_position(*start, start_position + start_delta); + already_applied[*start] = true; } - bezier_rs::BezierHandles::Quadratic { handle } => { - *handle = vector_data_instance.transform.transform_point2(*handle) + (start_delta + end_delta) / 2.; + if !already_applied[*end] { + let end_position = vector_data_instance.instance.point_domain.positions()[*end]; + vector_data_instance.instance.point_domain.set_position(*end, end_position + end_delta); + already_applied[*end] = true; + } + + match handles { + bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => { + *handle_start += start_delta; + *handle_end += end_delta; + } + bezier_rs::BezierHandles::Quadratic { handle } => { + *handle = vector_data_instance.transform.transform_point2(*handle) + (start_delta + end_delta) / 2.; + } + bezier_rs::BezierHandles::Linear => {} } - bezier_rs::BezierHandles::Linear => {} } - } - vector_data_instance.instance.style.set_stroke_transform(DAffine2::IDENTITY); - result_table.push(vector_data_instance); - } - - result_table + vector_data_instance.instance.style.set_stroke_transform(DAffine2::IDENTITY); + vector_data_instance + }) + .collect() } #[node_macro::node(category("Vector: Modifier"), path(graphene_core::vector))] @@ -1613,125 +1600,125 @@ async fn morph(_: impl Ctx, source: VectorDataTable, #[expose] target: VectorDat let time = time.clamp(0., 1.); - let mut result_table = VectorDataTable::default(); + source + .instance_iter() + .zip(target.instance_iter()) + .map(|(source_instance, target_instance)| { + let mut vector_data_instance = VectorData::default(); - for (source_instance, target_instance) in source.instance_iter().zip(target.instance_iter()) { - let mut vector_data_instance = VectorData::default(); + // Lerp styles + let vector_data_alpha_blending = source_instance.alpha_blending.lerp(&target_instance.alpha_blending, time as f32); + vector_data_instance.style = source_instance.instance.style.lerp(&target_instance.instance.style, time); - // Lerp styles - let vector_data_alpha_blending = source_instance.alpha_blending.lerp(&target_instance.alpha_blending, time as f32); - vector_data_instance.style = source_instance.instance.style.lerp(&target_instance.instance.style, time); + // Before and after transforms + let source_transform = source_instance.transform; + let target_transform = target_instance.transform; - // Before and after transforms - let source_transform = source_instance.transform; - let target_transform = target_instance.transform; + // Before and after paths + let source_bezpaths = source_instance.instance.stroke_bezpath_iter(); + let target_bezpaths = target_instance.instance.stroke_bezpath_iter(); - // Before and after paths - let source_bezpaths = source_instance.instance.stroke_bezpath_iter(); - let target_bezpaths = target_instance.instance.stroke_bezpath_iter(); + for (mut source_bezpath, mut target_bezpath) in source_bezpaths.zip(target_bezpaths) { + if source_bezpath.elements().is_empty() || target_bezpath.elements().is_empty() { + continue; + } - for (mut source_bezpath, mut target_bezpath) in source_bezpaths.zip(target_bezpaths) { - if source_bezpath.elements().is_empty() || target_bezpath.elements().is_empty() { - continue; - } + source_bezpath.apply_affine(Affine::new(source_transform.to_cols_array())); + target_bezpath.apply_affine(Affine::new(target_transform.to_cols_array())); - source_bezpath.apply_affine(Affine::new(source_transform.to_cols_array())); - target_bezpath.apply_affine(Affine::new(target_transform.to_cols_array())); + let target_segment_len = target_bezpath.segments().count(); + let source_segment_len = source_bezpath.segments().count(); - let target_segment_len = target_bezpath.segments().count(); - let source_segment_len = source_bezpath.segments().count(); + // Insert new segments to align the number of segments in sorce_bezpath and target_bezpath. + make_new_segments(&mut source_bezpath, target_segment_len.max(source_segment_len) - source_segment_len); + make_new_segments(&mut target_bezpath, source_segment_len.max(target_segment_len) - target_segment_len); - // Insert new segments to align the number of segments in sorce_bezpath and target_bezpath. - make_new_segments(&mut source_bezpath, target_segment_len.max(source_segment_len) - source_segment_len); - make_new_segments(&mut target_bezpath, source_segment_len.max(target_segment_len) - target_segment_len); + let source_segments = source_bezpath.segments().collect::>(); + let target_segments = target_bezpath.segments().collect::>(); - let source_segments = source_bezpath.segments().collect::>(); - let target_segments = target_bezpath.segments().collect::>(); - - // Interpolate anchors and handles - for (i, (source_element, target_element)) in source_bezpath.elements_mut().iter_mut().zip(target_bezpath.elements_mut().iter_mut()).enumerate() { - match source_element { - PathEl::MoveTo(point) => *point = point.lerp(target_element.end_point().unwrap(), time), - PathEl::ClosePath => {} - elm => { - let mut source_segment = source_segments.get(i - 1).unwrap().to_cubic(); - let target_segment = target_segments.get(i - 1).unwrap().to_cubic(); - source_segment.p0 = source_segment.p0.lerp(target_segment.p0, time); - source_segment.p1 = source_segment.p1.lerp(target_segment.p1, time); - source_segment.p2 = source_segment.p2.lerp(target_segment.p2, time); - source_segment.p3 = source_segment.p3.lerp(target_segment.p3, time); - *elm = PathSeg::Cubic(source_segment).as_path_el(); + // Interpolate anchors and handles + for (i, (source_element, target_element)) in source_bezpath.elements_mut().iter_mut().zip(target_bezpath.elements_mut().iter_mut()).enumerate() { + match source_element { + PathEl::MoveTo(point) => *point = point.lerp(target_element.end_point().unwrap(), time), + PathEl::ClosePath => {} + elm => { + let mut source_segment = source_segments.get(i - 1).unwrap().to_cubic(); + let target_segment = target_segments.get(i - 1).unwrap().to_cubic(); + source_segment.p0 = source_segment.p0.lerp(target_segment.p0, time); + source_segment.p1 = source_segment.p1.lerp(target_segment.p1, time); + source_segment.p2 = source_segment.p2.lerp(target_segment.p2, time); + source_segment.p3 = source_segment.p3.lerp(target_segment.p3, time); + *elm = PathSeg::Cubic(source_segment).as_path_el(); + } } } + + vector_data_instance.append_bezpath(source_bezpath.clone()); } - vector_data_instance.append_bezpath(source_bezpath.clone()); - } + // Deal with unmatched extra paths by collapsing them + let source_paths_count = source_instance.instance.stroke_bezpath_iter().count(); + let target_paths_count = target_instance.instance.stroke_bezpath_iter().count(); + let source_paths = source_instance.instance.stroke_bezpath_iter().skip(target_paths_count); + let target_paths = target_instance.instance.stroke_bezpath_iter().skip(source_paths_count); - // Deal with unmatched extra paths by collapsing them - let source_paths_count = source_instance.instance.stroke_bezpath_iter().count(); - let target_paths_count = target_instance.instance.stroke_bezpath_iter().count(); - let source_paths = source_instance.instance.stroke_bezpath_iter().skip(target_paths_count); - let target_paths = target_instance.instance.stroke_bezpath_iter().skip(source_paths_count); + for mut source_path in source_paths { + source_path.apply_affine(Affine::new(source_transform.to_cols_array())); - for mut source_path in source_paths { - source_path.apply_affine(Affine::new(source_transform.to_cols_array())); + // Skip if the path has no segments else get the point at the end of the path. + let Some(end) = source_path.segments().last().map(|element| element.end()) else { continue }; - // Skip if the path has no segments else get the point at the end of the path. - let Some(end) = source_path.segments().last().map(|element| element.end()) else { continue }; - - for element in source_path.elements_mut() { - match element { - PathEl::MoveTo(point) => *point = point.lerp(end, time), - PathEl::LineTo(point) => *point = point.lerp(end, time), - PathEl::QuadTo(point, point1) => { - *point = point.lerp(end, time); - *point1 = point1.lerp(end, time); + for element in source_path.elements_mut() { + match element { + PathEl::MoveTo(point) => *point = point.lerp(end, time), + PathEl::LineTo(point) => *point = point.lerp(end, time), + PathEl::QuadTo(point, point1) => { + *point = point.lerp(end, time); + *point1 = point1.lerp(end, time); + } + PathEl::CurveTo(point, point1, point2) => { + *point = point.lerp(end, time); + *point1 = point1.lerp(end, time); + *point2 = point2.lerp(end, time); + } + PathEl::ClosePath => {} } - PathEl::CurveTo(point, point1, point2) => { - *point = point.lerp(end, time); - *point1 = point1.lerp(end, time); - *point2 = point2.lerp(end, time); - } - PathEl::ClosePath => {} } + vector_data_instance.append_bezpath(source_path); } - vector_data_instance.append_bezpath(source_path); - } - for mut target_path in target_paths { - target_path.apply_affine(Affine::new(source_transform.to_cols_array())); + for mut target_path in target_paths { + target_path.apply_affine(Affine::new(source_transform.to_cols_array())); - // Skip if the path has no segments else get the point at the start of the path. - let Some(start) = target_path.segments().next().map(|element| element.start()) else { continue }; + // Skip if the path has no segments else get the point at the start of the path. + let Some(start) = target_path.segments().next().map(|element| element.start()) else { continue }; - for element in target_path.elements_mut() { - match element { - PathEl::MoveTo(point) => *point = start.lerp(*point, time), - PathEl::LineTo(point) => *point = start.lerp(*point, time), - PathEl::QuadTo(point, point1) => { - *point = start.lerp(*point, time); - *point1 = start.lerp(*point1, time); + for element in target_path.elements_mut() { + match element { + PathEl::MoveTo(point) => *point = start.lerp(*point, time), + PathEl::LineTo(point) => *point = start.lerp(*point, time), + PathEl::QuadTo(point, point1) => { + *point = start.lerp(*point, time); + *point1 = start.lerp(*point1, time); + } + PathEl::CurveTo(point, point1, point2) => { + *point = start.lerp(*point, time); + *point1 = start.lerp(*point1, time); + *point2 = start.lerp(*point2, time); + } + PathEl::ClosePath => {} } - PathEl::CurveTo(point, point1, point2) => { - *point = start.lerp(*point, time); - *point1 = start.lerp(*point1, time); - *point2 = start.lerp(*point2, time); - } - PathEl::ClosePath => {} } + vector_data_instance.append_bezpath(target_path); } - vector_data_instance.append_bezpath(target_path); - } - result_table.push(Instance { - instance: vector_data_instance, - alpha_blending: vector_data_alpha_blending, - ..Default::default() - }); - } - - result_table + Instance { + instance: vector_data_instance, + alpha_blending: vector_data_alpha_blending, + ..Default::default() + } + }) + .collect() } fn bevel_algorithm(mut vector_data: VectorData, vector_data_transform: DAffine2, distance: f64) -> VectorData { @@ -1928,7 +1915,11 @@ fn bevel_algorithm(mut vector_data: VectorData, vector_data_transform: DAffine2, next_bezier = handles_to_segment(start, BezierHandles::Linear, end); } - let inverse_transform = (vector_data_transform.matrix2.determinant() != 0.).then(|| vector_data_transform.inverse()).unwrap_or_default(); + let inverse_transform = if vector_data_transform.matrix2.determinant() != 0. { + vector_data_transform.inverse() + } else { + Default::default() + }; if index == 0 && next_index == 1 { first_original_length = bezier.perimeter(DEFAULT_ACCURACY); @@ -2010,28 +2001,24 @@ fn bevel_algorithm(mut vector_data: VectorData, vector_data_transform: DAffine2, #[node_macro::node(category("Vector: Modifier"), path(graphene_core::vector))] fn bevel(_: impl Ctx, source: VectorDataTable, #[default(10.)] distance: Length) -> VectorDataTable { - let mut result_table = VectorDataTable::default(); - - for source_instance in source.instance_iter() { - result_table.push(Instance { + source + .instance_iter() + .map(|source_instance| Instance { instance: bevel_algorithm(source_instance.instance, source_instance.transform, distance), ..source_instance - }); - } - - result_table + }) + .collect() } #[node_macro::node(category("Vector: Modifier"), path(graphene_core::vector))] fn close_path(_: impl Ctx, source: VectorDataTable) -> VectorDataTable { - let mut result_table = VectorDataTable::default(); - - for mut source_instance in source.instance_iter() { - source_instance.instance.close_subpaths(); - result_table.push(source_instance); - } - - result_table + source + .instance_iter() + .map(|mut source_instance| { + source_instance.instance.close_subpaths(); + source_instance + }) + .collect() } #[node_macro::node(category("Vector: Measure"), path(graphene_core::vector))] @@ -2162,14 +2149,6 @@ mod test { } } - fn vector_node_from_instances(data: Vec>) -> VectorDataTable { - let mut vector_data_table = VectorDataTable::default(); - for instance in data { - vector_data_table.push(instance); - } - vector_data_table - } - #[tokio::test] async fn repeat() { let direction = DVec2::X * 1.5; @@ -2306,9 +2285,9 @@ mod test { let bezpath = Rect::new(100., 100., 201., 201.).to_path(DEFAULT_ACCURACY); let transform = DAffine2::from_scale(DVec2::new(2., 2.)); let instance = create_vector_data_instance(bezpath, transform); - let instances = (0..5).map(|_| instance.clone()).collect::>>(); + let instances = (0..5).map(|_| instance.clone()).collect::>(); - let length = super::path_length(Footprint::default(), vector_node_from_instances(instances)).await; + let length = super::path_length(Footprint::default(), instances).await; // 101 (each rectangle edge length) * 4 (rectangle perimeter) * 2 (scale) * 5 (number of rows) assert_eq!(length, 101. * 4. * 2. * 5.); diff --git a/node-graph/gpath-bool/src/lib.rs b/node-graph/gpath-bool/src/lib.rs index e94963298b..5a1b08b630 100644 --- a/node-graph/gpath-bool/src/lib.rs +++ b/node-graph/gpath-bool/src/lib.rs @@ -80,8 +80,7 @@ fn union<'a>(vector_data: impl DoubleEndedIterator(vector_data: impl DoubleEndedIterator(vector_data: impl Iterator>) -> VectorDataTable { let mut vector_data = vector_data.into_iter(); - let mut result_vector_data_table = VectorDataTable::default(); - result_vector_data_table.push(vector_data.next().map(|x| x.to_instance_cloned()).unwrap_or_default()); + let mut result_vector_data_table = VectorDataTable::new_instance(vector_data.next().map(|x| x.to_instance_cloned()).unwrap_or_default()); let mut first_instance = result_vector_data_table.instance_mut_iter().next().expect("Expected the one instance we just pushed"); let mut next_vector_data = vector_data.next(); @@ -145,8 +143,7 @@ fn subtract<'a>(vector_data: impl Iterator>) fn intersect<'a>(vector_data: impl DoubleEndedIterator>) -> VectorDataTable { let mut vector_data = vector_data.rev(); - let mut result_vector_data_table = VectorDataTable::default(); - result_vector_data_table.push(vector_data.next().map(|x| x.to_instance_cloned()).unwrap_or_default()); + let mut result_vector_data_table = VectorDataTable::new_instance(vector_data.next().map(|x| x.to_instance_cloned()).unwrap_or_default()); let mut first_instance = result_vector_data_table.instance_mut_iter().next().expect("Expected the one instance we just pushed"); let default = Instance::default(); @@ -225,71 +222,67 @@ fn difference<'a>(vector_data: impl DoubleEndedIterator VectorDataTable { - let mut result_table = VectorDataTable::default(); + graphic_group_table + .instance_ref_iter() + .flat_map(|element| { + match element.instance.clone() { + GraphicElement::VectorData(vector_data) => { + // Apply the parent group's transform to each element of vector data + vector_data + .instance_iter() + .map(|mut sub_vector_data| { + sub_vector_data.transform = *element.transform * sub_vector_data.transform; - for element in graphic_group_table.instance_ref_iter() { - match element.instance.clone() { - GraphicElement::VectorData(vector_data) => { - // Apply the parent group's transform to each element of vector data - for mut sub_vector_data in vector_data.instance_iter() { - sub_vector_data.transform = *element.transform * sub_vector_data.transform; + sub_vector_data + }) + .collect::>() + } + GraphicElement::RasterDataCPU(image) => { + let make_instance = |transform| { + // Convert the image frame into a rectangular subpath with the image's transform + let mut subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE); + subpath.apply_transform(transform); - result_table.push(sub_vector_data); + // Create a vector data table row from the rectangular subpath, with a default black fill + let mut instance = VectorData::from_subpath(subpath); + instance.style.set_fill(Fill::Solid(Color::BLACK)); + + Instance { instance, ..Default::default() } + }; + + // Apply the parent group's transform to each element of raster data + image.instance_ref_iter().map(|instance| make_instance(*element.transform * *instance.transform)).collect::>() + } + GraphicElement::RasterDataGPU(image) => { + let make_instance = |transform| { + // Convert the image frame into a rectangular subpath with the image's transform + let mut subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE); + subpath.apply_transform(transform); + + // Create a vector data table row from the rectangular subpath, with a default black fill + let mut instance = VectorData::from_subpath(subpath); + instance.style.set_fill(Fill::Solid(Color::BLACK)); + + Instance { instance, ..Default::default() } + }; + + // Apply the parent group's transform to each element of raster data + image.instance_ref_iter().map(|instance| make_instance(*element.transform * *instance.transform)).collect::>() + } + GraphicElement::GraphicGroup(mut graphic_group) => { + // Apply the parent group's transform to each element of inner group + for sub_element in graphic_group.instance_mut_iter() { + *sub_element.transform = *element.transform * *sub_element.transform; + } + + // Recursively flatten the inner group into vector data + let unioned = boolean_operation_on_vector_data_table(flatten_vector_data(&graphic_group).instance_ref_iter(), BooleanOperation::Union); + + unioned.instance_iter().collect::>() } } - GraphicElement::RasterDataCPU(image) => { - let make_instance = |transform| { - // Convert the image frame into a rectangular subpath with the image's transform - let mut subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE); - subpath.apply_transform(transform); - - // Create a vector data table row from the rectangular subpath, with a default black fill - let mut instance = VectorData::from_subpath(subpath); - instance.style.set_fill(Fill::Solid(Color::BLACK)); - - Instance { instance, ..Default::default() } - }; - - // Apply the parent group's transform to each element of raster data - for instance in image.instance_ref_iter() { - result_table.push(make_instance(*element.transform * *instance.transform)); - } - } - GraphicElement::RasterDataGPU(image) => { - let make_instance = |transform| { - // Convert the image frame into a rectangular subpath with the image's transform - let mut subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE); - subpath.apply_transform(transform); - - // Create a vector data table row from the rectangular subpath, with a default black fill - let mut instance = VectorData::from_subpath(subpath); - instance.style.set_fill(Fill::Solid(Color::BLACK)); - - Instance { instance, ..Default::default() } - }; - - // Apply the parent group's transform to each element of raster data - for instance in image.instance_ref_iter() { - result_table.push(make_instance(*element.transform * *instance.transform)); - } - } - GraphicElement::GraphicGroup(mut graphic_group) => { - // Apply the parent group's transform to each element of inner group - for sub_element in graphic_group.instance_mut_iter() { - *sub_element.transform = *element.transform * *sub_element.transform; - } - - // Recursively flatten the inner group into vector data - let unioned = boolean_operation_on_vector_data_table(flatten_vector_data(&graphic_group).instance_ref_iter(), BooleanOperation::Union); - - for element in unioned.instance_iter() { - result_table.push(element); - } - } - } - } - - result_table + }) + .collect() } fn to_path(vector: &VectorData, transform: DAffine2) -> Vec { diff --git a/node-graph/graph-craft/src/wasm_application_io.rs b/node-graph/graph-craft/src/wasm_application_io.rs index 1d11744aa4..732135b4c8 100644 --- a/node-graph/graph-craft/src/wasm_application_io.rs +++ b/node-graph/graph-craft/src/wasm_application_io.rs @@ -217,26 +217,30 @@ impl ApplicationIo for WasmApplicationIo { } #[cfg(not(target_arch = "wasm32"))] fn create_window(&self) -> SurfaceHandle { - log::trace!("Spawning window"); + todo!("winit api changed, calling create_window on EventLoop is deprecated"); - #[cfg(all(not(test), target_os = "linux", feature = "wayland"))] - use winit::platform::wayland::EventLoopBuilderExtWayland; + // log::trace!("Spawning window"); - #[cfg(all(not(test), target_os = "linux", feature = "wayland"))] - let event_loop = winit::event_loop::EventLoopBuilder::new().with_any_thread(true).build().unwrap(); - #[cfg(not(all(not(test), target_os = "linux", feature = "wayland")))] - let event_loop = winit::event_loop::EventLoop::new().unwrap(); + // #[cfg(all(not(test), target_os = "linux", feature = "wayland"))] + // use winit::platform::wayland::EventLoopBuilderExtWayland; - let window = winit::window::WindowBuilder::new() - .with_title("Graphite") - .with_inner_size(winit::dpi::PhysicalSize::new(800, 600)) - .build(&event_loop) - .unwrap(); + // #[cfg(all(not(test), target_os = "linux", feature = "wayland"))] + // let event_loop = winit::event_loop::EventLoopBuilder::new().with_any_thread(true).build().unwrap(); + // #[cfg(not(all(not(test), target_os = "linux", feature = "wayland")))] + // let event_loop = winit::event_loop::EventLoop::new().unwrap(); - SurfaceHandle { - window_id: SurfaceId(window.id().into()), - surface: Arc::new(window), - } + // let window = event_loop + // .create_window( + // winit::window::WindowAttributes::default() + // .with_title("Graphite") + // .with_inner_size(winit::dpi::PhysicalSize::new(800, 600)), + // ) + // .unwrap(); + + // SurfaceHandle { + // window_id: SurfaceId(window.id().into()), + // surface: Arc::new(window), + // } } #[cfg(target_arch = "wasm32")] diff --git a/node-graph/graphene-cli/src/main.rs b/node-graph/graphene-cli/src/main.rs index af535b7363..049878cc3b 100644 --- a/node-graph/graphene-cli/src/main.rs +++ b/node-graph/graphene-cli/src/main.rs @@ -111,7 +111,7 @@ async fn main() -> Result<(), Box> { std::thread::spawn(move || { loop { std::thread::sleep(std::time::Duration::from_nanos(10)); - device.poll(wgpu::Maintain::Poll); + device.poll(wgpu::PollType::Poll).unwrap(); } }); let executor = create_executor(proto_graph)?; @@ -123,7 +123,7 @@ async fn main() -> Result<(), Box> { println!("{:?}", result); break; } - std::thread::sleep(std::time::Duration::from_millis(16)); + tokio::time::sleep(std::time::Duration::from_millis(16)).await; } } } diff --git a/node-graph/graster-nodes/src/adjustments.rs b/node-graph/graster-nodes/src/adjustments.rs index 297fd1b39a..eb516a723f 100644 --- a/node-graph/graster-nodes/src/adjustments.rs +++ b/node-graph/graster-nodes/src/adjustments.rs @@ -65,6 +65,26 @@ fn luminance>( input } +#[node_macro::node(category("Raster"))] +fn gamma_correction>( + _: impl Ctx, + #[implementations( + Color, + RasterDataTable, + GradientStops, + )] + mut input: T, + #[default(2.2)] + #[range((0.01, 10.))] + #[hard_min(0.0001)] + gamma: f64, + inverse: bool, +) -> T { + let exponent = if inverse { 1. / gamma } else { gamma }; + input.adjust(|color| color.gamma(exponent as f32)); + input +} + #[node_macro::node(category("Raster: Channels"))] fn extract_channel>( _: impl Ctx, diff --git a/node-graph/graster-nodes/src/dehaze.rs b/node-graph/graster-nodes/src/dehaze.rs index 268ccee09d..6c6246b964 100644 --- a/node-graph/graster-nodes/src/dehaze.rs +++ b/node-graph/graster-nodes/src/dehaze.rs @@ -8,34 +8,33 @@ use std::cmp::{max, min}; #[node_macro::node(category("Raster: Filter"))] async fn dehaze(_: impl Ctx, image_frame: RasterDataTable, strength: Percentage) -> RasterDataTable { - let mut result_table = RasterDataTable::default(); + image_frame + .instance_iter() + .map(|mut image_frame_instance| { + let image = image_frame_instance.instance; + // Prepare the image data for processing + let image_data = bytemuck::cast_vec(image.data.clone()); + let image_buffer = image::Rgba32FImage::from_raw(image.width, image.height, image_data).expect("Failed to convert internal image format into image-rs data type."); + let dynamic_image: DynamicImage = image_buffer.into(); - for mut image_frame_instance in image_frame.instance_iter() { - let image = image_frame_instance.instance; - // Prepare the image data for processing - let image_data = bytemuck::cast_vec(image.data.clone()); - let image_buffer = image::Rgba32FImage::from_raw(image.width, image.height, image_data).expect("Failed to convert internal image format into image-rs data type."); - let dynamic_image: DynamicImage = image_buffer.into(); + // Run the dehaze algorithm + let dehazed_dynamic_image = dehaze_image(dynamic_image, strength / 100.); - // Run the dehaze algorithm - let dehazed_dynamic_image = dehaze_image(dynamic_image, strength / 100.); + // Prepare the image data for returning + let buffer = dehazed_dynamic_image.to_rgba32f().into_raw(); + let color_vec = bytemuck::cast_vec(buffer); + let dehazed_image = Image { + width: image.width, + height: image.height, + data: color_vec, + base64_string: None, + }; - // Prepare the image data for returning - let buffer = dehazed_dynamic_image.to_rgba32f().into_raw(); - let color_vec = bytemuck::cast_vec(buffer); - let dehazed_image = Image { - width: image.width, - height: image.height, - data: color_vec, - base64_string: None, - }; - - image_frame_instance.instance = Raster::new_cpu(dehazed_image); - image_frame_instance.source_node_id = None; - result_table.push(image_frame_instance); - } - - result_table + image_frame_instance.instance = Raster::new_cpu(dehazed_image); + image_frame_instance.source_node_id = None; + image_frame_instance + }) + .collect() } // There is no real point in modifying these values because they do not change the final result all that much. diff --git a/node-graph/graster-nodes/src/filter.rs b/node-graph/graster-nodes/src/filter.rs index 3fd1f0686c..b5c4bbf9b4 100644 --- a/node-graph/graster-nodes/src/filter.rs +++ b/node-graph/graster-nodes/src/filter.rs @@ -20,27 +20,26 @@ async fn blur( /// Opt to incorrectly apply the filter with color calculations in gamma space for compatibility with the results from other software. gamma: bool, ) -> RasterDataTable { - let mut result_table = RasterDataTable::default(); + image_frame + .instance_iter() + .map(|mut image_instance| { + let image = image_instance.instance.clone(); - for mut image_instance in image_frame.instance_iter() { - let image = image_instance.instance.clone(); + // Run blur algorithm + let blurred_image = if radius < 0.1 { + // Minimum blur radius + image.clone() + } else if box_blur { + Raster::new_cpu(box_blur_algorithm(image.into_data(), radius, gamma)) + } else { + Raster::new_cpu(gaussian_blur_algorithm(image.into_data(), radius, gamma)) + }; - // Run blur algorithm - let blurred_image = if radius < 0.1 { - // Minimum blur radius - image.clone() - } else if box_blur { - Raster::new_cpu(box_blur_algorithm(image.into_data(), radius, gamma)) - } else { - Raster::new_cpu(gaussian_blur_algorithm(image.into_data(), radius, gamma)) - }; - - image_instance.instance = blurred_image; - image_instance.source_node_id = None; - result_table.push(image_instance); - } - - result_table + image_instance.instance = blurred_image; + image_instance.source_node_id = None; + image_instance + }) + .collect() } // 1D gaussian kernel diff --git a/node-graph/graster-nodes/src/std_nodes.rs b/node-graph/graster-nodes/src/std_nodes.rs index 9b331424d3..3df24c364c 100644 --- a/node-graph/graster-nodes/src/std_nodes.rs +++ b/node-graph/graster-nodes/src/std_nodes.rs @@ -31,69 +31,68 @@ impl From for Error { #[node_macro::node(category("Debug: Raster"))] pub fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: RasterDataTable) -> RasterDataTable { - let mut result_table = RasterDataTable::default(); + image_frame + .instance_iter() + .filter_map(|mut image_frame_instance| { + let image_frame_transform = image_frame_instance.transform; + let image = image_frame_instance.instance; - for mut image_frame_instance in image_frame.instance_iter() { - let image_frame_transform = image_frame_instance.transform; - let image = image_frame_instance.instance; + // Resize the image using the image crate + let data = bytemuck::cast_vec(image.data.clone()); - // Resize the image using the image crate - let data = bytemuck::cast_vec(image.data.clone()); + let footprint = ctx.footprint(); + let viewport_bounds = footprint.viewport_bounds_in_local_space(); + let image_bounds = Bbox::from_transform(image_frame_transform).to_axis_aligned_bbox(); + let intersection = viewport_bounds.intersect(&image_bounds); + let image_size = DAffine2::from_scale(DVec2::new(image.width as f64, image.height as f64)); + let size = intersection.size(); + let size_px = image_size.transform_vector2(size).as_uvec2(); - let footprint = ctx.footprint(); - let viewport_bounds = footprint.viewport_bounds_in_local_space(); - let image_bounds = Bbox::from_transform(image_frame_transform).to_axis_aligned_bbox(); - let intersection = viewport_bounds.intersect(&image_bounds); - let image_size = DAffine2::from_scale(DVec2::new(image.width as f64, image.height as f64)); - let size = intersection.size(); - let size_px = image_size.transform_vector2(size).as_uvec2(); + // If the image would not be visible, add nothing. + if size.x <= 0. || size.y <= 0. { + return None; + } - // If the image would not be visible, add nothing. - if size.x <= 0. || size.y <= 0. { - continue; - } + let image_buffer = ::image::Rgba32FImage::from_raw(image.width, image.height, data).expect("Failed to convert internal image format into image-rs data type."); - let image_buffer = ::image::Rgba32FImage::from_raw(image.width, image.height, data).expect("Failed to convert internal image format into image-rs data type."); + let dynamic_image: ::image::DynamicImage = image_buffer.into(); + let offset = (intersection.start - image_bounds.start).max(DVec2::ZERO); + let offset_px = image_size.transform_vector2(offset).as_uvec2(); + let cropped = dynamic_image.crop_imm(offset_px.x, offset_px.y, size_px.x, size_px.y); - let dynamic_image: ::image::DynamicImage = image_buffer.into(); - let offset = (intersection.start - image_bounds.start).max(DVec2::ZERO); - let offset_px = image_size.transform_vector2(offset).as_uvec2(); - let cropped = dynamic_image.crop_imm(offset_px.x, offset_px.y, size_px.x, size_px.y); + let viewport_resolution_x = footprint.transform.transform_vector2(DVec2::X * size.x).length(); + let viewport_resolution_y = footprint.transform.transform_vector2(DVec2::Y * size.y).length(); + let mut new_width = size_px.x; + let mut new_height = size_px.y; - let viewport_resolution_x = footprint.transform.transform_vector2(DVec2::X * size.x).length(); - let viewport_resolution_y = footprint.transform.transform_vector2(DVec2::Y * size.y).length(); - let mut new_width = size_px.x; - let mut new_height = size_px.y; + // Only downscale the image for now + let resized = if new_width < image.width || new_height < image.height { + new_width = viewport_resolution_x as u32; + new_height = viewport_resolution_y as u32; + // TODO: choose filter based on quality requirements + cropped.resize_exact(new_width, new_height, ::image::imageops::Triangle) + } else { + cropped + }; + let buffer = resized.to_rgba32f(); + let buffer = buffer.into_raw(); + let vec = bytemuck::cast_vec(buffer); + let image = Image { + width: new_width, + height: new_height, + data: vec, + base64_string: None, + }; + // we need to adjust the offset if we truncate the offset calculation - // Only downscale the image for now - let resized = if new_width < image.width || new_height < image.height { - new_width = viewport_resolution_x as u32; - new_height = viewport_resolution_y as u32; - // TODO: choose filter based on quality requirements - cropped.resize_exact(new_width, new_height, ::image::imageops::Triangle) - } else { - cropped - }; - let buffer = resized.to_rgba32f(); - let buffer = buffer.into_raw(); - let vec = bytemuck::cast_vec(buffer); - let image = Image { - width: new_width, - height: new_height, - data: vec, - base64_string: None, - }; - // we need to adjust the offset if we truncate the offset calculation + let new_transform = image_frame_transform * DAffine2::from_translation(offset) * DAffine2::from_scale(size); - let new_transform = image_frame_transform * DAffine2::from_translation(offset) * DAffine2::from_scale(size); - - image_frame_instance.transform = new_transform; - image_frame_instance.source_node_id = None; - image_frame_instance.instance = Raster::new_cpu(image); - result_table.push(image_frame_instance) - } - - result_table + image_frame_instance.transform = new_transform; + image_frame_instance.source_node_id = None; + image_frame_instance.instance = Raster::new_cpu(image); + Some(image_frame_instance) + }) + .collect() } #[node_macro::node(category("Raster: Channels"))] @@ -105,84 +104,85 @@ pub fn combine_channels( #[expose] blue: RasterDataTable, #[expose] alpha: RasterDataTable, ) -> RasterDataTable { - let mut result_table = RasterDataTable::default(); - let max_len = red.len().max(green.len()).max(blue.len()).max(alpha.len()); let red = red.instance_iter().map(Some).chain(std::iter::repeat(None)).take(max_len); let green = green.instance_iter().map(Some).chain(std::iter::repeat(None)).take(max_len); let blue = blue.instance_iter().map(Some).chain(std::iter::repeat(None)).take(max_len); let alpha = alpha.instance_iter().map(Some).chain(std::iter::repeat(None)).take(max_len); - for (((red, green), blue), alpha) in red.zip(green).zip(blue).zip(alpha) { - // Turn any default zero-sized image instances into None - let red = red.filter(|i| i.instance.width > 0 && i.instance.height > 0); - let green = green.filter(|i| i.instance.width > 0 && i.instance.height > 0); - let blue = blue.filter(|i| i.instance.width > 0 && i.instance.height > 0); - let alpha = alpha.filter(|i| i.instance.width > 0 && i.instance.height > 0); + red.zip(green) + .zip(blue) + .zip(alpha) + .filter_map(|(((red, green), blue), alpha)| { + // Turn any default zero-sized image instances into None + let red = red.filter(|i| i.instance.width > 0 && i.instance.height > 0); + let green = green.filter(|i| i.instance.width > 0 && i.instance.height > 0); + let blue = blue.filter(|i| i.instance.width > 0 && i.instance.height > 0); + let alpha = alpha.filter(|i| i.instance.width > 0 && i.instance.height > 0); - // Get this instance's transform and alpha blending mode from the first non-empty channel - let Some((transform, alpha_blending)) = [&red, &green, &blue, &alpha].iter().find_map(|i| i.as_ref()).map(|i| (i.transform, i.alpha_blending)) else { - continue; - }; + // Get this instance's transform and alpha blending mode from the first non-empty channel + let Some((transform, alpha_blending)) = [&red, &green, &blue, &alpha].iter().find_map(|i| i.as_ref()).map(|i| (i.transform, i.alpha_blending)) else { + return None; + }; - // Get the common width and height of the channels, which must have equal dimensions - let channel_dimensions = [ - red.as_ref().map(|r| (r.instance.width, r.instance.height)), - green.as_ref().map(|g| (g.instance.width, g.instance.height)), - blue.as_ref().map(|b| (b.instance.width, b.instance.height)), - alpha.as_ref().map(|a| (a.instance.width, a.instance.height)), - ]; - if channel_dimensions.iter().all(Option::is_none) - || channel_dimensions - .iter() - .flatten() - .any(|&(x, y)| channel_dimensions.iter().flatten().any(|&(other_x, other_y)| x != other_x || y != other_y)) - { - continue; - } - let Some(&(width, height)) = channel_dimensions.iter().flatten().next() else { continue }; + // Get the common width and height of the channels, which must have equal dimensions + let channel_dimensions = [ + red.as_ref().map(|r| (r.instance.width, r.instance.height)), + green.as_ref().map(|g| (g.instance.width, g.instance.height)), + blue.as_ref().map(|b| (b.instance.width, b.instance.height)), + alpha.as_ref().map(|a| (a.instance.width, a.instance.height)), + ]; + if channel_dimensions.iter().all(Option::is_none) + || channel_dimensions + .iter() + .flatten() + .any(|&(x, y)| channel_dimensions.iter().flatten().any(|&(other_x, other_y)| x != other_x || y != other_y)) + { + return None; + } + let Some(&(width, height)) = channel_dimensions.iter().flatten().next() else { + return None; + }; - // Create a new image for this instance output - let mut image = Image::new(width, height, Color::TRANSPARENT); + // Create a new image for this instance output + let mut image = Image::new(width, height, Color::TRANSPARENT); - // Iterate over all pixels in the image and set the color channels - for y in 0..image.height() { - for x in 0..image.width() { - let image_pixel = image.get_pixel_mut(x, y).unwrap(); + // Iterate over all pixels in the image and set the color channels + for y in 0..image.height() { + for x in 0..image.width() { + let image_pixel = image.get_pixel_mut(x, y).unwrap(); - if let Some(r) = red.as_ref().and_then(|r| r.instance.get_pixel(x, y)) { - image_pixel.set_red(r.l().cast_linear_channel()); - } else { - image_pixel.set_red(Channel::from_linear(0.)); - } - if let Some(g) = green.as_ref().and_then(|g| g.instance.get_pixel(x, y)) { - image_pixel.set_green(g.l().cast_linear_channel()); - } else { - image_pixel.set_green(Channel::from_linear(0.)); - } - if let Some(b) = blue.as_ref().and_then(|b| b.instance.get_pixel(x, y)) { - image_pixel.set_blue(b.l().cast_linear_channel()); - } else { - image_pixel.set_blue(Channel::from_linear(0.)); - } - if let Some(a) = alpha.as_ref().and_then(|a| a.instance.get_pixel(x, y)) { - image_pixel.set_alpha(a.l().cast_linear_channel()); - } else { - image_pixel.set_alpha(Channel::from_linear(1.)); + if let Some(r) = red.as_ref().and_then(|r| r.instance.get_pixel(x, y)) { + image_pixel.set_red(r.l().cast_linear_channel()); + } else { + image_pixel.set_red(Channel::from_linear(0.)); + } + if let Some(g) = green.as_ref().and_then(|g| g.instance.get_pixel(x, y)) { + image_pixel.set_green(g.l().cast_linear_channel()); + } else { + image_pixel.set_green(Channel::from_linear(0.)); + } + if let Some(b) = blue.as_ref().and_then(|b| b.instance.get_pixel(x, y)) { + image_pixel.set_blue(b.l().cast_linear_channel()); + } else { + image_pixel.set_blue(Channel::from_linear(0.)); + } + if let Some(a) = alpha.as_ref().and_then(|a| a.instance.get_pixel(x, y)) { + image_pixel.set_alpha(a.l().cast_linear_channel()); + } else { + image_pixel.set_alpha(Channel::from_linear(1.)); + } } } - } - // Add this instance to the result table - result_table.push(Instance { - instance: Raster::new_cpu(image), - transform, - alpha_blending, - source_node_id: None, - }); - } - - result_table + Some(Instance { + instance: Raster::new_cpu(image), + transform, + alpha_blending, + source_node_id: None, + }) + }) + .collect() } #[node_macro::node(category("Raster"))] @@ -201,91 +201,85 @@ pub fn mask( }; let stencil_size = DVec2::new(stencil_instance.instance.width as f64, stencil_instance.instance.height as f64); - let mut result_table = RasterDataTable::default(); + image + .instance_iter() + .filter_map(|mut image_instance| { + let image_size = DVec2::new(image_instance.instance.width as f64, image_instance.instance.height as f64); + let mask_size = stencil_instance.transform.decompose_scale(); - for mut image_instance in image.instance_iter() { - let image_size = DVec2::new(image_instance.instance.width as f64, image_instance.instance.height as f64); - let mask_size = stencil_instance.transform.decompose_scale(); - - if mask_size == DVec2::ZERO { - continue; - } - - // Transforms a point from the background image to the foreground image - let bg_to_fg = image_instance.transform * DAffine2::from_scale(1. / image_size); - let stencil_transform_inverse = stencil_instance.transform.inverse(); - - for y in 0..image_instance.instance.height { - for x in 0..image_instance.instance.width { - let image_point = DVec2::new(x as f64, y as f64); - let mask_point = bg_to_fg.transform_point2(image_point); - let local_mask_point = stencil_transform_inverse.transform_point2(mask_point); - let mask_point = stencil_instance.transform.transform_point2(local_mask_point.clamp(DVec2::ZERO, DVec2::ONE)); - let mask_point = (DAffine2::from_scale(stencil_size) * stencil_instance.transform.inverse()).transform_point2(mask_point); - - let image_pixel = image_instance.instance.data_mut().get_pixel_mut(x, y).unwrap(); - let mask_pixel = stencil_instance.instance.sample(mask_point); - *image_pixel = image_pixel.multiplied_alpha(mask_pixel.l().cast_linear_channel()); + if mask_size == DVec2::ZERO { + return None; } - } - result_table.push(image_instance); - } + // Transforms a point from the background image to the foreground image + let bg_to_fg = image_instance.transform * DAffine2::from_scale(1. / image_size); + let stencil_transform_inverse = stencil_instance.transform.inverse(); - result_table + for y in 0..image_instance.instance.height { + for x in 0..image_instance.instance.width { + let image_point = DVec2::new(x as f64, y as f64); + let mask_point = bg_to_fg.transform_point2(image_point); + let local_mask_point = stencil_transform_inverse.transform_point2(mask_point); + let mask_point = stencil_instance.transform.transform_point2(local_mask_point.clamp(DVec2::ZERO, DVec2::ONE)); + let mask_point = (DAffine2::from_scale(stencil_size) * stencil_instance.transform.inverse()).transform_point2(mask_point); + + let image_pixel = image_instance.instance.data_mut().get_pixel_mut(x, y).unwrap(); + let mask_pixel = stencil_instance.instance.sample(mask_point); + *image_pixel = image_pixel.multiplied_alpha(mask_pixel.l().cast_linear_channel()); + } + } + + Some(image_instance) + }) + .collect() } #[node_macro::node(category(""))] pub fn extend_image_to_bounds(_: impl Ctx, image: RasterDataTable, bounds: DAffine2) -> RasterDataTable { - let mut result_table = RasterDataTable::default(); - - for mut image_instance in image.instance_iter() { - let image_aabb = Bbox::unit().affine_transform(image_instance.transform).to_axis_aligned_bbox(); - let bounds_aabb = Bbox::unit().affine_transform(bounds.transform()).to_axis_aligned_bbox(); - if image_aabb.contains(bounds_aabb.start) && image_aabb.contains(bounds_aabb.end) { - result_table.push(image_instance); - continue; - } - - let image_data = &image_instance.instance.data; - let (image_width, image_height) = (image_instance.instance.width, image_instance.instance.height); - if image_width == 0 || image_height == 0 { - for image_instance in empty_image((), bounds, Color::TRANSPARENT).instance_iter() { - result_table.push(image_instance); + image + .instance_iter() + .map(|mut image_instance| { + let image_aabb = Bbox::unit().affine_transform(image_instance.transform).to_axis_aligned_bbox(); + let bounds_aabb = Bbox::unit().affine_transform(bounds.transform()).to_axis_aligned_bbox(); + if image_aabb.contains(bounds_aabb.start) && image_aabb.contains(bounds_aabb.end) { + return image_instance; } - continue; - } - let orig_image_scale = DVec2::new(image_width as f64, image_height as f64); - let layer_to_image_space = DAffine2::from_scale(orig_image_scale) * image_instance.transform.inverse(); - let bounds_in_image_space = Bbox::unit().affine_transform(layer_to_image_space * bounds).to_axis_aligned_bbox(); + let image_data = &image_instance.instance.data; + let (image_width, image_height) = (image_instance.instance.width, image_instance.instance.height); + if image_width == 0 || image_height == 0 { + return empty_image((), bounds, Color::TRANSPARENT).instance_iter().next().unwrap(); + } - let new_start = bounds_in_image_space.start.floor().min(DVec2::ZERO); - let new_end = bounds_in_image_space.end.ceil().max(orig_image_scale); - let new_scale = new_end - new_start; + let orig_image_scale = DVec2::new(image_width as f64, image_height as f64); + let layer_to_image_space = DAffine2::from_scale(orig_image_scale) * image_instance.transform.inverse(); + let bounds_in_image_space = Bbox::unit().affine_transform(layer_to_image_space * bounds).to_axis_aligned_bbox(); - // Copy over original image into enlarged image. - let mut new_image = Image::new(new_scale.x as u32, new_scale.y as u32, Color::TRANSPARENT); - let offset_in_new_image = (-new_start).as_uvec2(); - for y in 0..image_height { - let old_start = y * image_width; - let new_start = (y + offset_in_new_image.y) * new_image.width + offset_in_new_image.x; - let old_row = &image_data[old_start as usize..(old_start + image_width) as usize]; - let new_row = &mut new_image.data[new_start as usize..(new_start + image_width) as usize]; - new_row.copy_from_slice(old_row); - } + let new_start = bounds_in_image_space.start.floor().min(DVec2::ZERO); + let new_end = bounds_in_image_space.end.ceil().max(orig_image_scale); + let new_scale = new_end - new_start; - // Compute new transform. - // let layer_to_new_texture_space = (DAffine2::from_scale(1. / new_scale) * DAffine2::from_translation(new_start) * layer_to_image_space).inverse(); - let new_texture_to_layer_space = image_instance.transform * DAffine2::from_scale(1. / orig_image_scale) * DAffine2::from_translation(new_start) * DAffine2::from_scale(new_scale); + // Copy over original image into enlarged image. + let mut new_image = Image::new(new_scale.x as u32, new_scale.y as u32, Color::TRANSPARENT); + let offset_in_new_image = (-new_start).as_uvec2(); + for y in 0..image_height { + let old_start = y * image_width; + let new_start = (y + offset_in_new_image.y) * new_image.width + offset_in_new_image.x; + let old_row = &image_data[old_start as usize..(old_start + image_width) as usize]; + let new_row = &mut new_image.data[new_start as usize..(new_start + image_width) as usize]; + new_row.copy_from_slice(old_row); + } - image_instance.instance = Raster::new_cpu(new_image); - image_instance.transform = new_texture_to_layer_space; - image_instance.source_node_id = None; - result_table.push(image_instance); - } + // Compute new transform. + // let layer_to_new_texture_space = (DAffine2::from_scale(1. / new_scale) * DAffine2::from_translation(new_start) * layer_to_image_space).inverse(); + let new_texture_to_layer_space = image_instance.transform * DAffine2::from_scale(1. / orig_image_scale) * DAffine2::from_translation(new_start) * DAffine2::from_scale(new_scale); - result_table + image_instance.instance = Raster::new_cpu(new_image); + image_instance.transform = new_texture_to_layer_space; + image_instance.source_node_id = None; + image_instance + }) + .collect() } #[node_macro::node(category("Debug: Raster"))] @@ -392,14 +386,11 @@ pub fn noise_pattern( } } - let mut result = RasterDataTable::default(); - result.push(Instance { + return RasterDataTable::new_instance(Instance { instance: Raster::new_cpu(image), transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size), ..Default::default() }); - - return result; } }; noise.set_noise_type(Some(noise_type)); @@ -457,14 +448,11 @@ pub fn noise_pattern( } } - let mut result = RasterDataTable::default(); - result.push(Instance { + RasterDataTable::new_instance(Instance { instance: Raster::new_cpu(image), transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size), ..Default::default() - }); - - result + }) } #[node_macro::node(category("Raster: Pattern"))] @@ -502,20 +490,16 @@ pub fn mandelbrot(ctx: impl ExtractFootprint + Send) -> RasterDataTable { } } - let image = Image { - width, - height, - data, - ..Default::default() - }; - let mut result = RasterDataTable::default(); - result.push(Instance { - instance: Raster::new_cpu(image), + RasterDataTable::new_instance(Instance { + instance: Raster::new_cpu(Image { + width, + height, + data, + ..Default::default() + }), transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size), ..Default::default() - }); - - result + }) } #[inline(always)] diff --git a/node-graph/gstd/src/http.rs b/node-graph/gstd/src/http.rs deleted file mode 100644 index bd3e77ab3a..0000000000 --- a/node-graph/gstd/src/http.rs +++ /dev/null @@ -1,11 +0,0 @@ -use graphene_core::Ctx; - -#[node_macro::node(category("Web Request"))] -async fn get_request(_: impl Ctx, url: String) -> reqwest::Response { - reqwest::get(url).await.unwrap() -} - -#[node_macro::node(category("Web Request"))] -async fn post_request(_: impl Ctx, url: String, body: String) -> reqwest::Response { - reqwest::Client::new().post(url).body(body).send().await.unwrap() -} diff --git a/node-graph/gstd/src/lib.rs b/node-graph/gstd/src/lib.rs index d121234c36..d21d4c9c1a 100644 --- a/node-graph/gstd/src/lib.rs +++ b/node-graph/gstd/src/lib.rs @@ -1,5 +1,4 @@ pub mod any; -pub mod http; pub mod text; #[cfg(feature = "wasm")] pub mod wasm_application_io; diff --git a/node-graph/gstd/src/wasm_application_io.rs b/node-graph/gstd/src/wasm_application_io.rs index ae03edd425..a40309f88e 100644 --- a/node-graph/gstd/src/wasm_application_io.rs +++ b/node-graph/gstd/src/wasm_application_io.rs @@ -59,6 +59,81 @@ async fn create_surface<'a: 'n>(_: impl Ctx, editor: &'a WasmEditorApi) -> Arc String { + #[cfg(target_arch = "wasm32")] + { + if discard_result { + wasm_bindgen_futures::spawn_local(async move { + let _ = reqwest::get(url).await; + }); + return String::new(); + } + } + #[cfg(not(target_arch = "wasm32"))] + { + #[cfg(feature = "tokio")] + if discard_result { + tokio::spawn(async move { + let _ = reqwest::get(url).await; + }); + return String::new(); + } + #[cfg(not(feature = "tokio"))] + if discard_result { + return String::new(); + } + } + + let Ok(response) = reqwest::get(url).await else { return String::new() }; + response.text().await.ok().unwrap_or_default() +} + +#[node_macro::node(category("Web Request"))] +async fn post_request(_: impl Ctx, _primary: (), #[name("URL")] url: String, body: Vec, discard_result: bool) -> String { + #[cfg(target_arch = "wasm32")] + { + if discard_result { + wasm_bindgen_futures::spawn_local(async move { + let _ = reqwest::Client::new().post(url).body(body).header("Content-Type", "application/octet-stream").send().await; + }); + return String::new(); + } + } + #[cfg(not(target_arch = "wasm32"))] + { + #[cfg(feature = "tokio")] + if discard_result { + let url = url.clone(); + let body = body.clone(); + tokio::spawn(async move { + let _ = reqwest::Client::new().post(url).body(body).header("Content-Type", "application/octet-stream").send().await; + }); + return String::new(); + } + #[cfg(not(feature = "tokio"))] + if discard_result { + return String::new(); + } + } + + let Ok(response) = reqwest::Client::new().post(url).body(body).header("Content-Type", "application/octet-stream").send().await else { + return String::new(); + }; + response.text().await.ok().unwrap_or_default() +} + +#[node_macro::node(category("Web Request"), name("String to Bytes"))] +fn string_to_bytes(_: impl Ctx, string: String) -> Vec { + string.into_bytes() +} + +#[node_macro::node(category("Web Request"), name("Image to Bytes"))] +fn image_to_bytes(_: impl Ctx, image: RasterDataTable) -> Vec { + let Some(image) = image.instance_ref_iter().next() else { return vec![] }; + image.instance.data.iter().flat_map(|color| color.to_rgb8_srgb().into_iter()).collect::>() +} + #[node_macro::node(category("Web Request"))] async fn load_resource<'a: 'n>(_: impl Ctx, _primary: (), #[scope("editor-api")] editor: &'a WasmEditorApi, #[name("URL")] url: String) -> Arc<[u8]> { let Some(api) = editor.application_io.as_ref() else { @@ -145,7 +220,7 @@ async fn render_canvas( if !data.contains_artboard() && !render_config.hide_artboards { background = Color::WHITE; } - exec.render_vello_scene(&scene, &surface_handle, footprint.resolution.x, footprint.resolution.y, &context, background) + exec.render_vello_scene(&scene, &surface_handle, footprint.resolution, &context, background) .await .expect("Failed to render Vello scene"); @@ -217,15 +292,12 @@ where let rasterized = context.get_image_data(0., 0., resolution.x as f64, resolution.y as f64).unwrap(); - let mut result = RasterDataTable::default(); let image = Image::from_image_data(&rasterized.data().0, resolution.x as u32, resolution.y as u32); - result.push(Instance { + RasterDataTable::new_instance(Instance { instance: Raster::new_cpu(image), transform: footprint.transform, ..Default::default() - }); - - result + }) } #[node_macro::node(category(""))] diff --git a/node-graph/gsvg-renderer/src/renderer.rs b/node-graph/gsvg-renderer/src/renderer.rs index 216a9b666f..dd3a1ebb08 100644 --- a/node-graph/gsvg-renderer/src/renderer.rs +++ b/node-graph/gsvg-renderer/src/renderer.rs @@ -38,10 +38,10 @@ impl MaskType { } fn write_to_defs(self, svg_defs: &mut String, uuid: u64, svg_string: String) { - let id = format!("mask-{}", uuid); + let id = format!("mask-{uuid}"); match self { - Self::Clip => write!(svg_defs, r##"{}"##, svg_string).unwrap(), - Self::Mask => write!(svg_defs, r##"{}"##, svg_string).unwrap(), + Self::Clip => write!(svg_defs, r##"{svg_string}"##).unwrap(), + Self::Mask => write!(svg_defs, r##"{svg_string}"##).unwrap(), } } } @@ -89,9 +89,9 @@ impl SvgRender { .unwrap_or_default(); let matrix = format_transform_matrix(transform); - let transform = if matrix.is_empty() { String::new() } else { format!(r#" transform="{}""#, matrix) }; + let transform = if matrix.is_empty() { String::new() } else { format!(r#" transform="{matrix}""#) }; - let svg_header = format!(r#"{defs}"#, view_box); + let svg_header = format!(r#"{defs}"#); self.svg.insert(0, svg_header.into()); self.svg.push("".into()); } @@ -145,7 +145,7 @@ impl Default for SvgRender { #[derive(Clone, Debug, Default)] pub struct RenderContext { #[cfg(feature = "vello")] - pub resource_overrides: HashMap>, + pub resource_overrides: HashMap, } /// Static state used whilst rendering @@ -267,7 +267,7 @@ impl GraphicElementRendered for GraphicGroupTable { mask_state = None; } - let id = format!("mask-{}", uuid); + let id = format!("mask-{uuid}"); let selector = format!("url(#{id})"); attributes.push(mask_type.to_attribute(), selector); @@ -444,18 +444,18 @@ impl GraphicElementRendered for VectorDataTable { let can_use_order = !instance.instance.style.fill().is_none() && mask_type == MaskType::Mask; if !can_use_order { let id = format!("alignment-{}", generate_uuid()); - let mut vector_row = VectorDataTable::default(); - let mut fill_instance = instance.instance.clone(); + let mut fill_instance = instance.instance.clone(); fill_instance.style.clear_stroke(); fill_instance.style.set_fill(Fill::solid(Color::BLACK)); - vector_row.push(Instance { + let vector_row = VectorDataTable::new_instance(Instance { instance: fill_instance, alpha_blending: *instance.alpha_blending, transform: *instance.transform, source_node_id: None, }); + push_id = Some((id, mask_type, vector_row)); } } @@ -477,7 +477,7 @@ impl GraphicElementRendered for VectorDataTable { let (x, y) = quad.top_left().into(); let (width, height) = (quad.bottom_right() - quad.top_left()).into(); write!(defs, r##"{}"##, svg.svg_defs).unwrap(); - let rect = format!(r##""##, x, y); + let rect = format!(r##""##); match mask_type { MaskType::Clip => write!(defs, r##"{}"##, svg.svg.to_svg_string()).unwrap(), MaskType::Mask => write!(defs, r##"{}{}"##, rect, svg.svg.to_svg_string()).unwrap(), @@ -564,13 +564,11 @@ impl GraphicElementRendered for VectorDataTable { .stroke() .is_some_and(|stroke| stroke.align == StrokeAlign::Outside && !instance.instance.style.fill().is_none()); if can_draw_aligned_stroke && !reorder_for_outside { - let mut vector_data = VectorDataTable::default(); - let mut fill_instance = instance.instance.clone(); fill_instance.style.clear_stroke(); fill_instance.style.set_fill(Fill::solid(Color::BLACK)); - vector_data.push(Instance { + let vector_data = VectorDataTable::new_instance(Instance { instance: fill_instance, alpha_blending: *instance.alpha_blending, transform: *instance.transform, @@ -639,7 +637,11 @@ impl GraphicElementRendered for VectorDataTable { let bounds = instance.instance.nonzero_bounding_box(); let bound_transform = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]); - let inverse_parent_transform = (parent_transform.matrix2.determinant() != 0.).then(|| parent_transform.inverse()).unwrap_or_default(); + let inverse_parent_transform = if parent_transform.matrix2.determinant() != 0. { + parent_transform.inverse() + } else { + Default::default() + }; let mod_points = inverse_parent_transform * multiplied_transform * bound_transform; let start = mod_points.transform_point2(gradient.start); @@ -666,7 +668,11 @@ impl GraphicElementRendered for VectorDataTable { }); // Vello does `element_transform * brush_transform` internally. We don't want element_transform to have any impact so we need to left multiply by the inverse. // This makes the final internal brush transform equal to `parent_transform`, allowing you to stretch a gradient by transforming the parent folder. - let inverse_element_transform = (element_transform.matrix2.determinant() != 0.).then(|| element_transform.inverse()).unwrap_or_default(); + let inverse_element_transform = if element_transform.matrix2.determinant() != 0. { + element_transform.inverse() + } else { + Default::default() + }; let brush_transform = kurbo::Affine::new((inverse_element_transform * parent_transform).to_cols_array()); scene.fill(peniko::Fill::NonZero, kurbo::Affine::new(element_transform.to_cols_array()), &fill, Some(brush_transform), &path); } @@ -983,7 +989,7 @@ impl GraphicElementRendered for RasterDataTable { if image.data.is_empty() { return; } - let image = peniko::Image::new(image.to_flat_u8().0.into(), peniko::Format::Rgba8, image.width, image.height).with_extend(peniko::Extend::Repeat); + let image = peniko::Image::new(image.to_flat_u8().0.into(), peniko::ImageFormat::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)); scene.draw_image(&image, kurbo::Affine::new(transform.to_cols_array())); @@ -1035,10 +1041,10 @@ impl GraphicElementRendered for RasterDataTable { }; for instance in self.instance_ref_iter() { - let image = peniko::Image::new(vec![].into(), peniko::Format::Rgba8, instance.instance.data().width(), instance.instance.data().height()).with_extend(peniko::Extend::Repeat); + let image = peniko::Image::new(vec![].into(), peniko::ImageFormat::Rgba8, instance.instance.data().width(), instance.instance.data().height()).with_extend(peniko::Extend::Repeat); let id = image.data.id(); - context.resource_overrides.insert(id, instance.instance.data_owned()); + context.resource_overrides.insert(id, instance.instance.data().clone()); render_stuff(image, *instance.transform, *instance.alpha_blending); } diff --git a/node-graph/node-macro/Cargo.toml b/node-graph/node-macro/Cargo.toml index b63ce4927d..ebc1037d92 100644 --- a/node-graph/node-macro/Cargo.toml +++ b/node-graph/node-macro/Cargo.toml @@ -2,7 +2,7 @@ name = "node-macro" publish = false version = "0.0.0" -rust-version = "1.85" +rust-version = "1.88" authors = ["Graphite Authors "] edition = "2024" readme = "../../README.md" @@ -26,4 +26,3 @@ proc-macro-error2 = "2" [dev-dependencies] graphene-core = { workspace = true } - diff --git a/node-graph/wgpu-executor/src/context.rs b/node-graph/wgpu-executor/src/context.rs index 7d0d81859c..a8ffd7b17f 100644 --- a/node-graph/wgpu-executor/src/context.rs +++ b/node-graph/wgpu-executor/src/context.rs @@ -16,7 +16,7 @@ impl Context { backends: wgpu::Backends::all(), ..Default::default() }; - let instance = Instance::new(instance_descriptor); + let instance = Instance::new(&instance_descriptor); let adapter_options = wgpu::RequestAdapterOptions { power_preference: wgpu::PowerPreference::HighPerformance, @@ -24,26 +24,24 @@ impl Context { force_fallback_adapter: false, }; // `request_adapter` instantiates the general connection to the GPU - let adapter = instance.request_adapter(&adapter_options).await?; + let adapter = instance.request_adapter(&adapter_options).await.ok()?; let required_limits = adapter.limits(); // `request_device` instantiates the feature specific connection to the GPU, defining some parameters, // `features` being the available features. let (device, queue) = adapter - .request_device( - &wgpu::DeviceDescriptor { - label: None, - // #[cfg(not(feature = "passthrough"))] - required_features: wgpu::Features::empty(), - // Currently disabled because not all backend support passthrough. - // TODO: reenable only when vulkan adapter is available - // #[cfg(feature = "passthrough")] - // required_features: wgpu::Features::SPIRV_SHADER_PASSTHROUGH, - required_limits, - memory_hints: Default::default(), - }, - None, - ) + .request_device(&wgpu::DeviceDescriptor { + label: None, + // #[cfg(not(feature = "passthrough"))] + required_features: wgpu::Features::empty(), + // Currently disabled because not all backend support passthrough. + // TODO: reenable only when vulkan adapter is available + // #[cfg(feature = "passthrough")] + // required_features: wgpu::Features::SPIRV_SHADER_PASSTHROUGH, + required_limits, + memory_hints: Default::default(), + trace: wgpu::Trace::Off, + }) .await .unwrap(); diff --git a/node-graph/wgpu-executor/src/lib.rs b/node-graph/wgpu-executor/src/lib.rs index d65c24814a..c0113a45c6 100644 --- a/node-graph/wgpu-executor/src/lib.rs +++ b/node-graph/wgpu-executor/src/lib.rs @@ -3,18 +3,20 @@ mod context; use anyhow::Result; pub use context::Context; use dyn_any::StaticType; +use futures::lock::Mutex; use glam::UVec2; -use graphene_application_io::{ApplicationIo, EditorApi, SurfaceHandle}; +use graphene_application_io::{ApplicationIo, EditorApi, SurfaceHandle, SurfaceId}; use graphene_core::{Color, Ctx}; pub use graphene_svg_renderer::RenderContext; use std::sync::Arc; use vello::{AaConfig, AaSupport, RenderParams, Renderer, RendererOptions, Scene}; +use wgpu::util::TextureBlitter; use wgpu::{Origin3d, SurfaceConfiguration, TextureAspect}; #[derive(dyn_any::DynAny)] pub struct WgpuExecutor { pub context: Context, - vello_renderer: futures::lock::Mutex, + vello_renderer: Mutex, } impl std::fmt::Debug for WgpuExecutor { @@ -32,16 +34,17 @@ impl<'a, T: ApplicationIo> From<&'a EditorApi> for & pub type WgpuSurface = Arc>; pub type WgpuWindow = Arc>; -impl graphene_application_io::Size for Surface { - fn size(&self) -> UVec2 { - self.resolution - } -} - pub struct Surface { pub inner: wgpu::Surface<'static>, - resolution: UVec2, + pub target_texture: Mutex>, + pub blitter: TextureBlitter, } + +pub struct TargetTexture { + view: wgpu::TextureView, + size: UVec2, +} + #[cfg(target_arch = "wasm32")] pub type Window = web_sys::HtmlCanvasElement; #[cfg(not(target_arch = "wasm32"))] @@ -51,52 +54,88 @@ unsafe impl StaticType for Surface { type Static = Surface; } +const VELLO_SURFACE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm; + impl WgpuExecutor { - pub async fn render_vello_scene(&self, scene: &Scene, surface: &WgpuSurface, width: u32, height: u32, context: &RenderContext, background: Color) -> Result<()> { - let surface = &surface.surface.inner; - let surface_caps = surface.get_capabilities(&self.context.adapter); - surface.configure( + pub async fn render_vello_scene(&self, scene: &Scene, surface: &WgpuSurface, size: UVec2, context: &RenderContext, background: Color) -> Result<()> { + let mut guard = surface.surface.target_texture.lock().await; + let target_texture = if let Some(target_texture) = &*guard + && target_texture.size == size + { + target_texture + } else { + let texture = self.context.device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: wgpu::Extent3d { + width: size.x, + height: size.y, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING, + format: VELLO_SURFACE_FORMAT, + view_formats: &[], + }); + let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); + *guard = Some(TargetTexture { size, view }); + guard.as_ref().unwrap() + }; + + let surface_inner = &surface.surface.inner; + let surface_caps = surface_inner.get_capabilities(&self.context.adapter); + surface_inner.configure( &self.context.device, &SurfaceConfiguration { usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::STORAGE_BINDING, - format: wgpu::TextureFormat::Rgba8Unorm, - width, - height, + format: VELLO_SURFACE_FORMAT, + width: size.x, + height: size.y, present_mode: surface_caps.present_modes[0], alpha_mode: wgpu::CompositeAlphaMode::Opaque, view_formats: vec![], desired_maximum_frame_latency: 2, }, ); - let surface_texture = surface.get_current_texture()?; let [r, g, b, _] = background.to_rgba8_srgb(); let render_params = RenderParams { // We are using an explicit opaque color here to eliminate the alpha premultiplication step // which would be required to support a transparent webgpu canvas base_color: vello::peniko::Color::from_rgba8(r, g, b, 0xff), - width, - height, + width: size.x, + height: size.y, antialiasing_method: AaConfig::Msaa16, }; { let mut renderer = self.vello_renderer.lock().await; for (id, texture) in context.resource_overrides.iter() { - let texture_view = wgpu::ImageCopyTextureBase { - texture: texture.clone(), + let texture = texture.clone(); + let texture_view = wgpu::TexelCopyTextureInfoBase { + texture, mip_level: 0, origin: Origin3d::ZERO, aspect: TextureAspect::All, }; renderer.override_image( - &vello::peniko::Image::new(vello::peniko::Blob::from_raw_parts(Arc::new(vec![]), *id), vello::peniko::Format::Rgba8, 0, 0), + &vello::peniko::Image::new(vello::peniko::Blob::from_raw_parts(Arc::new(vec![]), *id), vello::peniko::ImageFormat::Rgba8, 0, 0), Some(texture_view), ); } - renderer.render_to_surface(&self.context.device, &self.context.queue, scene, &surface_texture, &render_params).unwrap(); + renderer.render_to_texture(&self.context.device, &self.context.queue, scene, &target_texture.view, &render_params)?; } + let surface_texture = surface_inner.get_current_texture()?; + let mut encoder = self.context.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("Surface Blit") }); + surface.surface.blitter.copy( + &self.context.device, + &mut encoder, + &target_texture.view, + &surface_texture.texture.create_view(&wgpu::TextureViewDescriptor::default()), + ); + self.context.queue.submit([encoder.finish()]); surface_texture.present(); Ok(()) @@ -105,24 +144,23 @@ impl WgpuExecutor { #[cfg(target_arch = "wasm32")] pub fn create_surface(&self, canvas: graphene_application_io::WasmSurfaceHandle) -> Result> { let surface = self.context.instance.create_surface(wgpu::SurfaceTarget::Canvas(canvas.surface))?; - - Ok(SurfaceHandle { - window_id: canvas.window_id, - surface: Surface { - inner: surface, - resolution: UVec2::ZERO, - }, - }) + self.create_surface_inner(surface, canvas.window_id) } #[cfg(not(target_arch = "wasm32"))] pub fn create_surface(&self, window: SurfaceHandle) -> Result> { - let size = window.surface.inner_size(); - let resolution = UVec2::new(size.width, size.height); let surface = self.context.instance.create_surface(wgpu::SurfaceTarget::Window(Box::new(window.surface)))?; + self.create_surface_inner(surface, window.window_id) + } + pub fn create_surface_inner(&self, surface: wgpu::Surface<'static>, window_id: SurfaceId) -> Result> { + let blitter = TextureBlitter::new(&self.context.device, VELLO_SURFACE_FORMAT); Ok(SurfaceHandle { - window_id: window.window_id, - surface: Surface { inner: surface, resolution }, + window_id, + surface: Surface { + inner: surface, + target_texture: Mutex::new(None), + blitter, + }, }) } } @@ -134,7 +172,8 @@ impl WgpuExecutor { let vello_renderer = Renderer::new( &context.device, RendererOptions { - surface_format: Some(wgpu::TextureFormat::Rgba8Unorm), + // surface_format: Some(wgpu::TextureFormat::Rgba8Unorm), + pipeline_cache: None, use_cpu: false, antialiasing_support: AaSupport::all(), num_init_threads: std::num::NonZeroUsize::new(1), diff --git a/proc-macros/Cargo.toml b/proc-macros/Cargo.toml index 51417c6ff7..a19e5b598d 100644 --- a/proc-macros/Cargo.toml +++ b/proc-macros/Cargo.toml @@ -2,7 +2,7 @@ name = "graphite-proc-macros" publish = false version = "0.0.0" -rust-version = "1.85" +rust-version = "1.88" authors = ["Graphite Authors "] edition = "2024" readme = "../README.md"