diff --git a/.nix/flake.lock b/.nix/flake.lock index a102f87f43..dc9b853784 100644 --- a/.nix/flake.lock +++ b/.nix/flake.lock @@ -34,27 +34,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1748190013, - "narHash": "sha256-R5HJFflOfsP5FBtk+zE8FpL8uqE7n62jqOsADvVshhE=", + "lastModified": 1754214453, + "narHash": "sha256-Q/I2xJn/j1wpkGhWkQnm20nShYnG7TI99foDBpXm1SY=", "owner": "nixos", "repo": "nixpkgs", - "rev": "62b852f6c6742134ade1abdd2a21685fd617a291", - "type": "github" - }, - "original": { - "owner": "nixos", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "nixpkgs-unstable": { - "locked": { - "lastModified": 1748190013, - "narHash": "sha256-R5HJFflOfsP5FBtk+zE8FpL8uqE7n62jqOsADvVshhE=", - "owner": "nixos", - "repo": "nixpkgs", - "rev": "62b852f6c6742134ade1abdd2a21685fd617a291", + "rev": "5b09dc45f24cf32316283e62aec81ffee3c3e376", "type": "github" }, "original": { @@ -69,7 +53,6 @@ "flake-compat": "flake-compat", "flake-utils": "flake-utils", "nixpkgs": "nixpkgs", - "nixpkgs-unstable": "nixpkgs-unstable", "rust-overlay": "rust-overlay" } }, diff --git a/.nix/flake.nix b/.nix/flake.nix index 7b1b1da371..d8fb88ed54 100644 --- a/.nix/flake.nix +++ b/.nix/flake.nix @@ -16,7 +16,6 @@ inputs = { nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; - nixpkgs-unstable.url = "github:nixos/nixpkgs/nixos-unstable"; rust-overlay = { url = "github:oxalica/rust-overlay"; inputs.nixpkgs.follows = "nixpkgs"; @@ -27,17 +26,14 @@ flake-compat.url = "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz"; }; - outputs = { nixpkgs, nixpkgs-unstable, rust-overlay, flake-utils, ... }: + outputs = { nixpkgs, rust-overlay, flake-utils, ... }: flake-utils.lib.eachDefaultSystem (system: let overlays = [ (import rust-overlay) ]; pkgs = import nixpkgs { inherit system overlays; }; - 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" ]; @@ -75,10 +71,8 @@ buildInputs = with pkgs; [ # System libraries wayland - wayland.dev openssl vulkan-loader - mesa libraw libGL ]; @@ -90,11 +84,10 @@ pkgs.nodePackages.npm pkgs.binaryen pkgs.wasm-bindgen-cli - pkgs-unstable.wasm-pack + pkgs.wasm-pack pkgs.pkg-config pkgs.git - pkgs.gobject-introspection - pkgs-unstable.cargo-about + pkgs.cargo-about # Linker pkgs.mold diff --git a/Cargo.lock b/Cargo.lock index 7fee0af4f6..10ddcda817 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -224,6 +224,181 @@ dependencies = [ "libloading", ] +[[package]] +name = "ashpd" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cbdf310d77fd3aaee6ea2093db7011dc2d35d2eb3481e5607f1f8d942ed99df" +dependencies = [ + "async-fs", + "async-net", + "enumflags2", + "futures-channel", + "futures-util", + "rand 0.9.1", + "raw-window-handle", + "serde", + "serde_repr", + "url", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "zbus", +] + +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb812ffb58524bdd10860d7d974e2f01cc0950c2438a74ee5ec2e2280c6c4ffa" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-fs" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f7e37c0ed80b2a977691c47dae8625cfb21e205827106c64f7c588766b2e50" +dependencies = [ + "async-lock", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-io" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19634d6336019ef220f09fd31168ce5c184b295cbf80345437cc36094ef223ca" +dependencies = [ + "async-lock", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix 1.0.7", + "slab", + "windows-sys 0.60.2", +] + +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-net" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" +dependencies = [ + "async-io", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-process" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65daa13722ad51e6ab1a1b9c01299142bc75135b337923cfa10e79bbbd669f00" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix 1.0.7", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "async-signal" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f567af260ef69e1d52c2b560ce0ea230763e6fbb9214a85d768760a920e3e3c1" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix 1.0.7", + "signal-hook-registry", + "slab", + "windows-sys 0.60.2", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -382,6 +557,28 @@ 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 = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + [[package]] name = "built" version = "0.7.7" @@ -965,6 +1162,18 @@ 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", + "block2 0.6.1", + "libc", + "objc2 0.6.1", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -1068,6 +1277,33 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "endi" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" + +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "env_filter" version = "0.1.3" @@ -1136,6 +1372,27 @@ dependencies = [ "num-traits", ] +[[package]] +name = "event-listener" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + [[package]] name = "exr" version = "1.73.0" @@ -1406,6 +1663,19 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +[[package]] +name = "futures-lite" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-macro" version = "0.3.31" @@ -1844,6 +2114,7 @@ dependencies = [ "graphene-std", "graphite-editor", "include_dir", + "rfd", "ron", "thiserror 2.0.12", "tracing", @@ -1987,6 +2258,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" @@ -2776,6 +3053,15 @@ 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.31.0" @@ -2927,6 +3213,19 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset", +] + [[package]] name = "node-macro" version = "0.0.0" @@ -3097,7 +3396,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" dependencies = [ "bitflags 2.9.1", - "block2", + "block2 0.5.1", "libc", "objc2 0.5.2", "objc2-core-data", @@ -3106,6 +3405,18 @@ dependencies = [ "objc2-quartz-core", ] +[[package]] +name = "objc2-app-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" +dependencies = [ + "bitflags 2.9.1", + "block2 0.6.1", + "objc2 0.6.1", + "objc2-foundation 0.3.1", +] + [[package]] name = "objc2-cloud-kit" version = "0.2.2" @@ -3113,7 +3424,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" dependencies = [ "bitflags 2.9.1", - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-core-location", "objc2-foundation 0.2.2", @@ -3125,7 +3436,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" dependencies = [ - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", ] @@ -3137,7 +3448,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ "bitflags 2.9.1", - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", ] @@ -3149,6 +3460,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" dependencies = [ "bitflags 2.9.1", + "dispatch2", + "objc2 0.6.1", ] [[package]] @@ -3157,7 +3470,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" dependencies = [ - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", "objc2-metal", @@ -3169,7 +3482,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" dependencies = [ - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-contacts", "objc2-foundation 0.2.2", @@ -3198,7 +3511,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ "bitflags 2.9.1", - "block2", + "block2 0.5.1", "dispatch", "libc", "objc2 0.5.2", @@ -3212,6 +3525,7 @@ checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" dependencies = [ "bitflags 2.9.1", "objc2 0.6.1", + "objc2-core-foundation", ] [[package]] @@ -3220,9 +3534,9 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" dependencies = [ - "block2", + "block2 0.5.1", "objc2 0.5.2", - "objc2-app-kit", + "objc2-app-kit 0.2.2", "objc2-foundation 0.2.2", ] @@ -3233,7 +3547,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ "bitflags 2.9.1", - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", ] @@ -3245,7 +3559,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ "bitflags 2.9.1", - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", "objc2-metal", @@ -3268,7 +3582,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" dependencies = [ "bitflags 2.9.1", - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-cloud-kit", "objc2-core-data", @@ -3288,7 +3602,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" dependencies = [ - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", ] @@ -3300,7 +3614,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" dependencies = [ "bitflags 2.9.1", - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-core-location", "objc2-foundation 0.2.2", @@ -3401,6 +3715,16 @@ dependencies = [ "num-traits", ] +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + [[package]] name = "overload" version = "0.1.1" @@ -3416,6 +3740,12 @@ dependencies = [ "ttf-parser 0.25.1", ] +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.4" @@ -3593,6 +3923,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + [[package]] name = "pkg-config" version = "0.3.32" @@ -3655,6 +3996,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "pollster" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" + [[package]] name = "portable-atomic" version = "1.11.1" @@ -4205,6 +4552,30 @@ dependencies = [ "zune-jpeg", ] +[[package]] +name = "rfd" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef2bee61e6cffa4635c72d7d81a84294e28f0930db0ddcb0f66d10244674ebed" +dependencies = [ + "ashpd", + "block2 0.6.1", + "dispatch2", + "js-sys", + "log", + "objc2 0.6.1", + "objc2-app-kit 0.3.1", + "objc2-core-foundation", + "objc2-foundation 0.3.1", + "pollster", + "raw-window-handle", + "urlencoding", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-sys 0.59.0", +] + [[package]] name = "rgb" version = "0.8.51" @@ -4480,6 +4851,17 @@ dependencies = [ "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" @@ -4533,6 +4915,15 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[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" @@ -5302,6 +5693,17 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" +[[package]] +name = "uds_windows" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +dependencies = [ + "memoffset", + "tempfile", + "winapi", +] + [[package]] name = "unicode-bidi" version = "0.3.18" @@ -5411,8 +5813,15 @@ dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "usvg" version = "0.44.0" @@ -6413,7 +6822,7 @@ dependencies = [ "android-activity", "atomic-waker", "bitflags 2.9.1", - "block2", + "block2 0.5.1", "bytemuck", "calloop", "cfg_aliases", @@ -6427,7 +6836,7 @@ dependencies = [ "memmap2", "ndk", "objc2 0.5.2", - "objc2-app-kit", + "objc2-app-kit 0.2.2", "objc2-foundation 0.2.2", "objc2-ui-kit", "orbclient", @@ -6601,6 +7010,66 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zbus" +version = "5.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb4f9a464286d42851d18a605f7193b8febaf5b0919d71c6399b7b26e5b0aad" +dependencies = [ + "async-broadcast", + "async-executor", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-lite", + "hex", + "nix", + "ordered-stream", + "serde", + "serde_repr", + "tracing", + "uds_windows", + "windows-sys 0.59.0", + "winnow", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "5.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef9859f68ee0c4ee2e8cde84737c78e3f4c54f946f2a38645d0d4c7a95327659" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.104", + "zbus_names", + "zvariant", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97" +dependencies = [ + "serde", + "static_assertions", + "winnow", + "zvariant", +] + [[package]] name = "zeno" version = "0.3.3" @@ -6710,3 +7179,45 @@ checksum = "2c9e525af0a6a658e031e95f14b7f889976b74a11ba0eca5a5fc9ac8a1c43a6a" dependencies = [ "zune-core", ] + +[[package]] +name = "zvariant" +version = "5.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91b3680bb339216abd84714172b5138a4edac677e641ef17e1d8cb1b3ca6e6f" +dependencies = [ + "endi", + "enumflags2", + "serde", + "url", + "winnow", + "zvariant_derive", + "zvariant_utils", +] + +[[package]] +name = "zvariant_derive" +version = "5.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a8c68501be459a8dbfffbe5d792acdd23b4959940fc87785fb013b32edbc208" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.104", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16edfee43e5d7b553b77872d99bc36afdda75c223ca7ad5e3fbecd82ca5fc34" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "static_assertions", + "syn 2.0.104", + "winnow", +] diff --git a/Cargo.toml b/Cargo.toml index 3a9485b7e3..89fee5170c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -163,6 +163,7 @@ cef = "138.5.0" include_dir = "0.7.4" tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } tracing = "0.1.41" +rfd = "0.15.4" [profile.dev] opt-level = 1 diff --git a/desktop/Cargo.toml b/desktop/Cargo.toml index 8b46d717ee..f6a1ce861e 100644 --- a/desktop/Cargo.toml +++ b/desktop/Cargo.toml @@ -37,3 +37,4 @@ bytemuck = { workspace = true } glam = { workspace = true } vello = { workspace = true } derivative = { workspace = true } +rfd = { workspace = true } diff --git a/desktop/src/app.rs b/desktop/src/app.rs index 53876e12a9..9dc16085e5 100644 --- a/desktop/src/app.rs +++ b/desktop/src/app.rs @@ -1,5 +1,7 @@ use crate::CustomEvent; use crate::WindowSize; +use crate::dialogs::dialog_open_graphite_file; +use crate::dialogs::dialog_save_graphite_file; use crate::render::GraphicsState; use crate::render::WgpuContext; use graph_craft::wasm_application_io::WasmApplicationIo; @@ -7,6 +9,7 @@ use graphite_editor::application::Editor; use graphite_editor::messages::prelude::*; use std::sync::Arc; use std::sync::mpsc::Sender; +use std::thread; use std::time::Duration; use std::time::Instant; use winit::application::ApplicationHandler; @@ -15,23 +18,25 @@ use winit::event::StartCause; use winit::event::WindowEvent; use winit::event_loop::ActiveEventLoop; use winit::event_loop::ControlFlow; +use winit::event_loop::EventLoopProxy; use winit::window::Window; use winit::window::WindowId; use crate::cef; pub(crate) struct WinitApp { - pub(crate) cef_context: cef::Context, - pub(crate) window: Option>, + cef_context: cef::Context, + window: Option>, cef_schedule: Option, window_size_sender: Sender, graphics_state: Option, wgpu_context: WgpuContext, - pub(crate) editor: Editor, + event_loop_proxy: EventLoopProxy, + editor: Editor, } impl WinitApp { - pub(crate) fn new(cef_context: cef::Context, window_size_sender: Sender, wgpu_context: WgpuContext) -> Self { + pub(crate) fn new(cef_context: cef::Context, window_size_sender: Sender, wgpu_context: WgpuContext, event_loop_proxy: EventLoopProxy) -> Self { Self { cef_context, window: None, @@ -39,6 +44,7 @@ impl WinitApp { graphics_state: None, window_size_sender, wgpu_context, + event_loop_proxy, editor: Editor::new(), } } @@ -57,6 +63,49 @@ impl WinitApp { } } + for _ in responses.extract_if(.., |m| matches!(m, FrontendMessage::TriggerOpenDocument)) { + let event_loop_proxy = self.event_loop_proxy.clone(); + let _ = thread::spawn(move || { + let path = futures::executor::block_on(dialog_open_graphite_file()); + if let Some(path) = path { + let content = std::fs::read_to_string(&path).unwrap_or_else(|_| { + tracing::error!("Failed to read file: {}", path.display()); + String::new() + }); + let message = PortfolioMessage::OpenDocumentFile { + document_name: path.file_name().and_then(|s| s.to_str()).unwrap_or("unknown").to_string(), + document_serialized_content: content, + }; + let _ = event_loop_proxy.send_event(CustomEvent::DispatchMessage(message.into())); + } + }); + } + + for message in responses.extract_if(.., |m| matches!(m, FrontendMessage::TriggerSaveDocument { .. })) { + let FrontendMessage::TriggerSaveDocument { document_id, name, path, document } = message else { + unreachable!() + }; + if let Some(path) = path { + let _ = std::fs::write(&path, document); + } else { + let event_loop_proxy = self.event_loop_proxy.clone(); + let _ = thread::spawn(move || { + let path = futures::executor::block_on(dialog_save_graphite_file(name)); + if let Some(path) = path { + if let Err(e) = std::fs::write(&path, document) { + tracing::error!("Failed to save file: {}: {}", path.display(), e); + } else { + let message = Message::Portfolio(PortfolioMessage::DocumentPassMessage { + document_id, + message: DocumentMessage::SavedDocument { path: Some(path) }, + }); + let _ = event_loop_proxy.send_event(CustomEvent::DispatchMessage(message)); + } + } + }); + } + } + if responses.is_empty() { return; } @@ -132,7 +181,10 @@ impl ApplicationHandler for WinitApp { self.cef_schedule = Some(instant); } } - CustomEvent::MessageReceived { message } => { + CustomEvent::DispatchMessage(message) => { + self.dispatch_message(message); + } + CustomEvent::MessageReceived(message) => { if let Message::InputPreprocessor(_) = &message { if let Some(window) = &self.window { window.request_redraw(); @@ -155,7 +207,7 @@ impl ApplicationHandler for WinitApp { self.dispatch_message(message); } - CustomEvent::NodeGraphRan { texture } => { + CustomEvent::NodeGraphRan(texture) => { if let Some(texture) = texture && let Some(graphics_state) = &mut self.graphics_state { diff --git a/desktop/src/cef.rs b/desktop/src/cef.rs index 7d3e68e328..af1e7ec73d 100644 --- a/desktop/src/cef.rs +++ b/desktop/src/cef.rs @@ -124,7 +124,7 @@ impl CefEventHandler for CefHandler { let str = std::str::from_utf8(message).unwrap(); match ron::from_str(str) { Ok(message) => { - let _ = self.event_loop_proxy.send_event(CustomEvent::MessageReceived { message }); + let _ = self.event_loop_proxy.send_event(CustomEvent::MessageReceived(message)); } Err(e) => { tracing::error!("Failed to deserialize message {:?}", e) diff --git a/desktop/src/dialogs.rs b/desktop/src/dialogs.rs new file mode 100644 index 0000000000..a1b2f6dbd3 --- /dev/null +++ b/desktop/src/dialogs.rs @@ -0,0 +1,22 @@ +use std::path::PathBuf; + +use rfd::AsyncFileDialog; + +pub(crate) async fn dialog_open_graphite_file() -> Option { + AsyncFileDialog::new() + .add_filter("Graphite", &["graphite"]) + .set_title("Open Graphite Document") + .pick_file() + .await + .map(|f| f.path().to_path_buf()) +} + +pub(crate) async fn dialog_save_graphite_file(name: String) -> Option { + AsyncFileDialog::new() + .add_filter("Graphite", &["graphite"]) + .set_title("Save Graphite Document") + .set_file_name(name) + .save_file() + .await + .map(|f| f.path().to_path_buf()) +} diff --git a/desktop/src/main.rs b/desktop/src/main.rs index fcaab73d9c..41cf45ced7 100644 --- a/desktop/src/main.rs +++ b/desktop/src/main.rs @@ -17,12 +17,15 @@ use app::WinitApp; mod dirs; +mod dialogs; + #[derive(Debug)] pub(crate) enum CustomEvent { UiUpdate(wgpu::Texture), ScheduleBrowserWork(Instant), - MessageReceived { message: Message }, - NodeGraphRan { texture: Option }, + DispatchMessage(Message), + MessageReceived(Message), + NodeGraphRan(Option), } fn main() { @@ -63,9 +66,7 @@ fn main() { let last_render = Instant::now(); let (has_run, texture) = futures::executor::block_on(graphite_editor::node_graph_executor::run_node_graph()); if has_run { - let _ = rendering_loop_proxy.send_event(CustomEvent::NodeGraphRan { - texture: texture.map(|t| (*t.texture).clone()), - }); + let _ = rendering_loop_proxy.send_event(CustomEvent::NodeGraphRan(texture.map(|t| (*t.texture).clone()))); } let frame_time = Duration::from_secs_f32((target_fps as f32).recip()); let sleep = last_render + frame_time - Instant::now(); @@ -73,7 +74,7 @@ fn main() { } }); - let mut winit_app = WinitApp::new(cef_context, window_size_sender, wgpu_context); + let mut winit_app = WinitApp::new(cef_context, window_size_sender, wgpu_context, event_loop.create_proxy()); event_loop.run_app(&mut winit_app).unwrap(); } diff --git a/editor/src/messages/defer/defer_message_handler.rs b/editor/src/messages/defer/defer_message_handler.rs index 693c2de99e..762b392988 100644 --- a/editor/src/messages/defer/defer_message_handler.rs +++ b/editor/src/messages/defer/defer_message_handler.rs @@ -25,8 +25,8 @@ impl MessageHandler for DeferMessageHandler { return; } // Find the index of the last message we can process - let num_elements_to_remove = self.after_graph_run.binary_search_by_key(&(execution_id + 1), |x| x.0).unwrap_or_else(|pos| pos - 1); - let elements = self.after_graph_run.drain(0..=num_elements_to_remove); + let split = self.after_graph_run.partition_point(|&(id, _)| id <= execution_id); + let elements = self.after_graph_run.drain(..split); for (_, message) in elements.rev() { responses.add_front(message); } diff --git a/editor/src/messages/frontend/frontend_message.rs b/editor/src/messages/frontend/frontend_message.rs index c31c6e2c0d..b342396f74 100644 --- a/editor/src/messages/frontend/frontend_message.rs +++ b/editor/src/messages/frontend/frontend_message.rs @@ -12,6 +12,7 @@ use graph_craft::document::NodeId; use graphene_std::raster::Image; use graphene_std::raster::color::Color; use graphene_std::text::{Font, TextAlign}; +use std::path::PathBuf; #[cfg(not(target_family = "wasm"))] use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; @@ -63,6 +64,12 @@ pub enum FrontendMessage { #[serde(rename = "commitDate")] commit_date: String, }, + TriggerSaveDocument { + document_id: DocumentId, + name: String, + path: Option, + document: String, + }, TriggerDownloadImage { svg: String, name: String, diff --git a/editor/src/messages/portfolio/document/document_message.rs b/editor/src/messages/portfolio/document/document_message.rs index ca67d9b0ed..89db87c983 100644 --- a/editor/src/messages/portfolio/document/document_message.rs +++ b/editor/src/messages/portfolio/document/document_message.rs @@ -1,3 +1,5 @@ +use std::path::PathBuf; + use super::utility_types::misc::{GroupFolderType, SnappingState}; use crate::messages::input_mapper::utility_types::input_keyboard::Key; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; @@ -105,6 +107,9 @@ pub enum DocumentMessage { RenderRulers, RenderScrollbars, SaveDocument, + SavedDocument { + path: Option, + }, SelectParentLayer, SelectAllLayers, SelectedLayersLower, diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 1d7f174dc3..9076d0354e 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -37,6 +37,7 @@ use graphene_std::table::Table; use graphene_std::vector::PointId; use graphene_std::vector::click_target::{ClickTarget, ClickTargetType}; use graphene_std::vector::style::ViewMode; +use std::path::PathBuf; use std::time::Duration; #[derive(ExtractField)] @@ -115,6 +116,9 @@ pub struct DocumentMessageHandler { /// Stack of document network snapshots for future history states. #[serde(skip)] document_redo_history: VecDeque, + /// The path of the to the document file. + #[serde(skip)] + path: Option, /// Hash of the document snapshot that was most recently saved to disk by the user. #[serde(skip)] saved_hash: Option, @@ -162,6 +166,7 @@ impl Default for DocumentMessageHandler { selection_network_path: Vec::new(), document_undo_history: VecDeque::new(), document_redo_history: VecDeque::new(), + path: None, saved_hash: None, auto_saved_hash: None, layer_range_selection_reference: None, @@ -996,11 +1001,16 @@ impl MessageHandler> for DocumentMes true => self.name.clone(), false => self.name.clone() + FILE_SAVE_SUFFIX, }; - responses.add(FrontendMessage::TriggerDownloadTextFile { - document: self.serialize_document(), + responses.add(FrontendMessage::TriggerSaveDocument { + document_id, name, + path: self.path.clone(), + document: self.serialize_document(), }) } + DocumentMessage::SavedDocument { path } => { + self.path = path; + } DocumentMessage::SelectParentLayer => { let selected_nodes = self.network_interface.selected_nodes(); let selected_layers = selected_nodes.selected_layers(self.metadata()); diff --git a/frontend/src/messages.ts b/frontend/src/messages.ts index 0fe4066cf0..3920c497b4 100644 --- a/frontend/src/messages.ts +++ b/frontend/src/messages.ts @@ -791,6 +791,16 @@ export class TriggerImport extends JsMessage {} export class TriggerPaste extends JsMessage {} +export class TriggerSaveDocument extends JsMessage { + readonly documentId!: bigint; + + readonly name!: string; + + readonly path!: string | undefined; + + readonly document!: string; +} + export class TriggerDownloadImage extends JsMessage { readonly svg!: string; @@ -1647,6 +1657,7 @@ export const messageMakers: Record = { DisplayRemoveEditableTextbox, SendUIMetadata, TriggerAboutGraphiteLocalizedCommitDate, + TriggerSaveDocument, TriggerDownloadImage, TriggerDownloadTextFile, TriggerFetchAndOpenDocument, diff --git a/frontend/src/state-providers/portfolio.ts b/frontend/src/state-providers/portfolio.ts index aaf5711401..43a5650603 100644 --- a/frontend/src/state-providers/portfolio.ts +++ b/frontend/src/state-providers/portfolio.ts @@ -6,6 +6,7 @@ import { type Editor } from "@graphite/editor"; import { type FrontendDocumentDetails, TriggerFetchAndOpenDocument, + TriggerSaveDocument, TriggerDownloadImage, TriggerDownloadTextFile, TriggerImport, @@ -84,6 +85,9 @@ export function createPortfolioState(editor: Editor) { const imageData = await extractPixelData(new Blob([data.content.data], { type: data.type })); editor.handle.pasteImage(data.filename, new Uint8Array(imageData.data), imageData.width, imageData.height); }); + editor.subscriptions.subscribeJsMessage(TriggerSaveDocument, (triggerSaveDocument) => { + downloadFileText(triggerSaveDocument.name, triggerSaveDocument.document); + }); editor.subscriptions.subscribeJsMessage(TriggerDownloadTextFile, (triggerFileDownload) => { downloadFileText(triggerFileDownload.name, triggerFileDownload.document); });