diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..b7d6511 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,100 @@ +on: [pull_request, workflow_dispatch] + +name: CI + +env: + # --cfg=web_sys_unstable_apis is required to enable the web_sys clipboard API which egui_web uses + # https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Clipboard.html + # https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html + RUSTFLAGS: -D warnings --cfg=web_sys_unstable_apis + RUSTDOCFLAGS: -D warnings + +jobs: + check: + name: Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - run: cargo check --all-features + + check_wasm: + name: Check wasm32 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + targets: wasm32-unknown-unknown + - run: cargo check --all-features --lib --target wasm32-unknown-unknown + + test: + name: Test Suite + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - run: sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libssl-dev + - run: cargo test --lib + + fmt: + name: Rustfmt + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - run: cargo fmt --all -- --check + + clippy: + name: Clippy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: clippy + - run: cargo clippy -- -D warnings + + trunk: + name: trunk + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: 1.76.0 + target: wasm32-unknown-unknown + override: true + - name: Download and install Trunk binary + run: wget -qO- https://github.com/thedodd/trunk/releases/latest/download/trunk-x86_64-unknown-linux-gnu.tar.gz | tar -xzf- + - name: Build + run: ./trunk build frontend/index.html + + build: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + TARGET: x86_64-unknown-linux-musl + - os: ubuntu-latest + TARGET: x86_64-unknown-linux-gnu + - os: windows-latest + TARGET: x86_64-pc-windows-msvc + + steps: + - name: Building ${{ matrix.TARGET }} + run: echo "${{ matrix.TARGET }}" + + - uses: actions/checkout@master + - uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.TARGET }} + + - run: cargo build --release --target=${{ matrix.TARGET }} + + diff --git a/Cargo.lock b/Cargo.lock index eb8fa43..2f8ef99 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,52 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "ab_glyph" +version = "0.2.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e53b0a3d5760cd2ba9b787ae0c6440ad18ee294ff71b05e3381c900a7d16cfd" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" + +[[package]] +name = "accesskit" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74a4b14f3d99c1255dcba8f45621ab1a2e7540a0009652d33989005a4d0bfc6b" +dependencies = [ + "enumn", + "serde", +] + [[package]] name = "adler" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "serde", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.2" @@ -17,30 +57,188 @@ dependencies = [ "memchr", ] +[[package]] +name = "aligned-vec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" + +[[package]] +name = "android-activity" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee91c0c2905bae44f84bfa4e044536541df26b7703fd0888deeb9060fcc44289" +dependencies = [ + "android-properties", + "bitflags 2.5.0", + "cc", + "cesu8", + "jni", + "jni-sys", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "num_enum", + "thiserror", +] + +[[package]] +name = "android-properties" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" + [[package]] name = "anes" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" +[[package]] +name = "anstream" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + [[package]] name = "anstyle" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +[[package]] +name = "anstyle-parse" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + [[package]] name = "anyhow" version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" + +[[package]] +name = "arboard" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb4009533e8ff8f1450a5bcbc30f4242a1d34442221f72314bea1f5dc9c7f89" +dependencies = [ + "clipboard-win", + "log", + "objc2 0.5.1", + "objc2-app-kit", + "objc2-foundation", + "parking_lot", + "x11rb", +] + +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "as-raw-xcb-connection" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "av1-grain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876c75a42f6364451a033496a14c44bffe41f5f4a8236f697391f11024e596d2" +dependencies = [ + "arrayvec", +] + [[package]] name = "base64" version = "0.21.7" @@ -48,13 +246,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] -name = "bincode" -version = "1.3.3" +name = "base64" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bit_field" @@ -68,6 +263,55 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +dependencies = [ + "serde", +] + +[[package]] +name = "bitstream-io" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c12d1856e42f0d817a835fe55853957c85c8c8a470114029143d3f12671446e" + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43ff7d91d3c1d568065b06c899777d1e48dcf76103a672a0adbc238a7f247f1e" +dependencies = [ + "objc2 0.5.1", +] + +[[package]] +name = "built" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6a6c0b39c38fd754ac338b00a88066436389c0f029da5d37d1e01091d9b7c17" + [[package]] name = "bumpalo" version = "3.15.3" @@ -79,6 +323,20 @@ name = "bytemuck" version = "1.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2ef034f05691a48569bd920a96c81b9d91bbad1ab5ac7c4616c1f6ef36cb79f" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4da9a32f3fed317401fa3c862968128267c3106685286e15d5aaa3d7389c2f60" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "byteorder" @@ -86,18 +344,98 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "bytes" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" + +[[package]] +name = "calloop" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fba7adb4dd5aa98e5553510223000e7148f621165ec5f9acd7113f6ca4995298" +dependencies = [ + "bitflags 2.5.0", + "log", + "polling", + "rustix", + "slab", + "thiserror", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f0ea9b9476c7fad82841a8dbb380e2eae480c21910feba80725b46931ed8f02" +dependencies = [ + "calloop", + "rustix", + "wayland-backend", + "wayland-client", +] + [[package]] name = "cast" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" +[[package]] +name = "cc" +version = "1.0.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" +dependencies = [ + "jobserver", + "libc", + "once_cell", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "cgl" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" +dependencies = [ + "libc", +] + [[package]] name = "ciborium" version = "0.2.2" @@ -150,12 +488,46 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +[[package]] +name = "clipboard-win" +version = "5.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79f4473f5144e20d9aceaf2972478f06ddf687831eafeeb434fbaf0acc4144ad" +dependencies = [ + "error-code", +] + [[package]] name = "color_quant" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" +[[package]] +name = "colorchoice" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "console_error_panic_hook" version = "0.1.7" @@ -166,6 +538,46 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "libc", +] + [[package]] name = "crc32fast" version = "1.4.0" @@ -187,7 +599,7 @@ dependencies = [ "clap", "criterion-plot", "is-terminal", - "itertools", + "itertools 0.10.5", "num-traits", "once_cell", "oorandom", @@ -208,7 +620,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", - "itertools", + "itertools 0.10.5", ] [[package]] @@ -243,120 +655,181 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] -name = "either" -version = "1.10.0" +name = "cursor-icon" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" [[package]] -name = "equivalent" -version = "1.0.1" +name = "directories" +version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" +dependencies = [ + "dirs-sys", +] [[package]] -name = "exr" -version = "1.72.0" +name = "dirs-sys" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "887d93f60543e9a9362ef8a21beedd0a833c5d9610e18c67abe15a5963dcb1a4" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" dependencies = [ - "bit_field", - "flume", - "half", - "lebe", - "miniz_oxide", - "rayon-core", - "smallvec", - "zune-inflate", + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", ] [[package]] -name = "fdeflate" -version = "0.3.4" +name = "dispatch" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "simd-adler32", + "libloading", ] [[package]] -name = "flate2" -version = "1.0.28" +name = "document-features" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +checksum = "ef5282ad69563b5fc40319526ba27e0e7363d552a896f0297d54f767717f9b95" dependencies = [ - "crc32fast", - "miniz_oxide", + "litrs", ] [[package]] -name = "flume" -version = "0.11.0" +name = "downcast-rs" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "ecolor" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e6b451ff1143f6de0f33fc7f1b68fecfd2c7de06e104de96c4514de3f5396f8" dependencies = [ - "spin", + "bytemuck", + "emath", + "serde", ] [[package]] -name = "frontend" -version = "0.1.0" +name = "eframe" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6490ef800b2e41ee129b1f32f9ac15f713233fe3bc18e241a1afe1e4fb6811e0" dependencies = [ - "base64", - "console_error_panic_hook", - "gloo", + "ahash", + "bytemuck", + "directories", + "document-features", + "egui", + "egui-winit", + "egui_glow", + "glow", + "glutin", + "glutin-winit", "image", + "js-sys", "log", - "optimize", + "objc2 0.5.1", + "objc2-app-kit", + "objc2-foundation", + "parking_lot", + "percent-encoding", + "raw-window-handle 0.5.2", + "raw-window-handle 0.6.1", + "ron", "serde", - "serde_json", + "static_assertions", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-logger", "web-sys", + "web-time", + "winapi", + "winit", ] [[package]] -name = "futures" -version = "0.3.30" +name = "egui" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "20c97e70a2768de630f161bb5392cbd3874fcf72868f14df0e002e82e06cb798" dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", + "accesskit", + "ahash", + "emath", + "epaint", + "log", + "nohash-hasher", + "ron", + "serde", ] [[package]] -name = "futures-channel" -version = "0.3.30" +name = "egui-winit" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "fac4e066af341bf92559f60dbdf2020b2a03c963415349af5f3f8d79ff7a4926" dependencies = [ - "futures-core", - "futures-sink", + "ahash", + "arboard", + "egui", + "log", + "raw-window-handle 0.6.1", + "serde", + "smithay-clipboard", + "web-time", + "webbrowser", + "winit", ] [[package]] -name = "futures-core" -version = "0.3.30" +name = "egui_glow" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "4e2bdc8b38cfa17cc712c4ae079e30c71c00cd4c2763c9e16dc7860a02769103" +dependencies = [ + "ahash", + "bytemuck", + "egui", + "glow", + "log", + "memoffset", + "wasm-bindgen", + "web-sys", +] [[package]] -name = "futures-io" -version = "0.3.30" +name = "either" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" + +[[package]] +name = "emath" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "0a6a21708405ea88f63d8309650b4d77431f4bc28fb9d8e6f77d3963b51249e6" +dependencies = [ + "bytemuck", + "serde", +] [[package]] -name = "futures-macro" -version = "0.3.30" +name = "enumn" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "6fd000fd6988e73bbe993ea3db9b1aa64906ab88766d654973924340c8cddb42" dependencies = [ "proc-macro2", "quote", @@ -364,268 +837,576 @@ dependencies = [ ] [[package]] -name = "futures-sink" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" - -[[package]] -name = "futures-task" -version = "0.3.30" +name = "env_filter" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +dependencies = [ + "log", + "regex", +] [[package]] -name = "futures-util" -version = "0.3.30" +name = "env_logger" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", ] [[package]] -name = "gif" -version = "0.13.1" +name = "epaint" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" +checksum = "3f0dcc0a0771e7500e94cd1cb797bd13c9f23b9409bdc3c824e2cbc562b7fa01" dependencies = [ - "color_quant", - "weezl", + "ab_glyph", + "ahash", + "bytemuck", + "ecolor", + "emath", + "log", + "nohash-hasher", + "parking_lot", + "serde", ] [[package]] -name = "gloo" -version = "0.11.0" +name = "equivalent" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d15282ece24eaf4bd338d73ef580c6714c8615155c4190c781290ee3fa0fd372" -dependencies = [ - "gloo-dialogs", - "gloo-file", - "gloo-storage", - "gloo-timers", - "gloo-worker", -] +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] -name = "gloo-dialogs" -version = "0.2.0" +name = "errno" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4748e10122b01435750ff530095b1217cf6546173459448b83913ebe7815df" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ - "wasm-bindgen", - "web-sys", + "libc", + "windows-sys 0.52.0", ] [[package]] -name = "gloo-events" -version = "0.2.0" +name = "error-code" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27c26fb45f7c385ba980f5fa87ac677e363949e065a083722697ef1b2cc91e41" -dependencies = [ - "wasm-bindgen", - "web-sys", -] +checksum = "a0474425d51df81997e2f90a21591180b38eccf27292d755f3e30750225c175b" [[package]] -name = "gloo-file" -version = "0.3.0" +name = "exr" +version = "1.72.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97563d71863fb2824b2e974e754a81d19c4a7ec47b09ced8a0e6656b6d54bd1f" +checksum = "887d93f60543e9a9362ef8a21beedd0a833c5d9610e18c67abe15a5963dcb1a4" dependencies = [ - "futures-channel", - "gloo-events", - "js-sys", - "wasm-bindgen", - "web-sys", + "bit_field", + "flume", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", ] [[package]] -name = "gloo-storage" -version = "0.3.0" +name = "fdeflate" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc8031e8c92758af912f9bc08fbbadd3c6f3cfcbf6b64cdf3d6a81f0139277a" +checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" dependencies = [ - "gloo-utils", - "js-sys", - "serde", - "serde_json", - "thiserror", - "wasm-bindgen", - "web-sys", + "simd-adler32", ] [[package]] -name = "gloo-timers" -version = "0.3.0" +name = "flate2" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", + "crc32fast", + "miniz_oxide", ] [[package]] -name = "gloo-utils" -version = "0.2.0" +name = "flume" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" dependencies = [ - "js-sys", - "serde", - "serde_json", - "wasm-bindgen", - "web-sys", + "spin", ] [[package]] -name = "gloo-worker" +name = "foreign-types" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "085f262d7604911c8150162529cefab3782e91adb20202e8658f7275d2aefe5d" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" dependencies = [ - "bincode", - "futures", - "gloo-utils", - "gloo-worker-macros", - "js-sys", - "pinned", - "serde", - "thiserror", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", + "foreign-types-macros", + "foreign-types-shared", ] [[package]] -name = "gloo-worker-macros" -version = "0.1.0" +name = "foreign-types-macros" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "956caa58d4857bc9941749d55e4bd3000032d8212762586fa5705632967140e7" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ - "proc-macro-crate", "proc-macro2", "quote", "syn", ] [[package]] -name = "half" -version = "2.4.0" +name = "foreign-types-shared" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5eceaaeec696539ddaf7b333340f1af35a5aa87ae3e4f3ead0532f72affab2e" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ - "cfg-if", - "crunchy", + "percent-encoding", ] [[package]] -name = "hashbrown" -version = "0.14.3" +name = "frontend" +version = "0.1.0" +dependencies = [ + "base64 0.22.1", + "console_error_panic_hook", + "eframe", + "env_logger", + "graphics", + "image", + "log", + "nalgebra", + "optimize", + "serde", + "serde_json", + "wasm-bindgen-futures", + "wasm-logger", +] + +[[package]] +name = "gethostname" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" +dependencies = [ + "libc", + "windows-targets 0.48.5", +] [[package]] -name = "hermit-abi" -version = "0.3.9" +name = "getrandom" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] [[package]] -name = "image" -version = "0.24.9" +name = "gif" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" dependencies = [ - "bytemuck", - "byteorder", "color_quant", - "exr", - "gif", - "jpeg-decoder", - "num-traits", - "png", - "qoi", - "tiff", + "weezl", ] [[package]] -name = "indexmap" -version = "2.2.3" +name = "gl_generator" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" dependencies = [ - "equivalent", - "hashbrown", + "khronos_api", + "log", + "xml-rs", ] [[package]] -name = "is-terminal" -version = "0.4.12" +name = "glow" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +checksum = "bd348e04c43b32574f2de31c8bb397d96c9fcfa1371bd4ca6d8bdc464ab121b1" dependencies = [ - "hermit-abi", - "libc", - "windows-sys", + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", ] [[package]] -name = "itertools" -version = "0.10.5" +name = "glutin" +version = "0.31.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +checksum = "18fcd4ae4e86d991ad1300b8f57166e5be0c95ef1f63f3f5b827f8a164548746" dependencies = [ - "either", + "bitflags 2.5.0", + "cfg_aliases", + "cgl", + "core-foundation", + "dispatch", + "glutin_egl_sys", + "glutin_glx_sys", + "glutin_wgl_sys", + "icrate", + "libloading", + "objc2 0.4.1", + "once_cell", + "raw-window-handle 0.5.2", + "wayland-sys", + "windows-sys 0.48.0", + "x11-dl", ] [[package]] -name = "itoa" -version = "1.0.10" +name = "glutin-winit" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "1ebcdfba24f73b8412c5181e56f092b5eff16671c514ce896b258a0a64bd7735" +dependencies = [ + "cfg_aliases", + "glutin", + "raw-window-handle 0.5.2", + "winit", +] [[package]] -name = "jpeg-decoder" -version = "0.3.1" +name = "glutin_egl_sys" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" +checksum = "77cc5623f5309ef433c3dd4ca1223195347fe62c413da8e2fdd0eb76db2d9bcd" dependencies = [ - "rayon", + "gl_generator", + "windows-sys 0.48.0", ] [[package]] -name = "js-sys" -version = "0.3.68" +name = "glutin_glx_sys" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" +checksum = "a165fd686c10dcc2d45380b35796e577eacfd43d4660ee741ec8ebe2201b3b4f" dependencies = [ - "wasm-bindgen", + "gl_generator", + "x11-dl", ] [[package]] -name = "lebe" -version = "0.5.2" +name = "glutin_wgl_sys" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" +checksum = "6c8098adac955faa2d31079b65dc48841251f69efd3ac25477903fc424362ead" +dependencies = [ + "gl_generator", +] [[package]] -name = "libc" +name = "graphics" +version = "0.1.0" +dependencies = [ + "eframe", + "nalgebra", +] + +[[package]] +name = "half" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5eceaaeec696539ddaf7b333340f1af35a5aa87ae3e4f3ead0532f72affab2e" +dependencies = [ + "cfg-if", + "crunchy", +] + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[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 = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "image" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd54d660e773627692c524beaad361aca785a4f9f5730ce91f42aabe5bce3d11" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "exr", + "gif", + "image-webp", + "num-traits", + "png", + "qoi", + "ravif", + "rayon", + "rgb", + "tiff", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "image-webp" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d730b085583c4d789dfd07fdcf185be59501666a90c97c40162b37e4fdad272d" +dependencies = [ + "byteorder-lite", + "thiserror", +] + +[[package]] +name = "imgref" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126" + +[[package]] +name = "indexmap" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "is-terminal" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +dependencies = [ + "libc", +] + +[[package]] +name = "jpeg-decoder" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" + +[[package]] +name = "js-sys" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + +[[package]] +name = "lebe" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" + +[[package]] +name = "libc" version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +[[package]] +name = "libfuzzer-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" +dependencies = [ + "arbitrary", + "cc", + "once_cell", +] + +[[package]] +name = "libloading" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" +dependencies = [ + "cfg-if", + "windows-targets 0.52.4", +] + +[[package]] +name = "libredox" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607" +dependencies = [ + "bitflags 2.5.0", + "libc", + "redox_syscall 0.4.1", +] + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.5.0", + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + +[[package]] +name = "litrs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" + [[package]] name = "lock_api" version = "0.4.11" @@ -638,9 +1419,38 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + +[[package]] +name = "matrixmultiply" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7574c1cf36da4798ab73da5b215bbf444f50718207754cb522201d78d1cd0ff2" +dependencies = [ + "autocfg", + "rawpointer", +] + +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] [[package]] name = "memchr" @@ -648,6 +1458,30 @@ version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +[[package]] +name = "memmap2" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" +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 = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.7.2" @@ -658,6 +1492,144 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "nalgebra" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c4b5f057b303842cf3262c27e465f4c303572e7f6b0648f60e16248ac3397f4" +dependencies = [ + "approx", + "matrixmultiply", + "nalgebra-macros", + "num-complex", + "num-rational", + "num-traits", + "serde", + "simba", + "typenum", +] + +[[package]] +name = "nalgebra-macros" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "254a5372af8fc138e36684761d3c0cdb758a4410e938babcff1c860ce14ddbfc" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ndk" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" +dependencies = [ + "bitflags 2.5.0", + "jni-sys", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle 0.5.2", + "raw-window-handle 0.6.1", + "thiserror", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.5.0+25.2.9519653" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + +[[package]] +name = "num-bigint" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", + "serde", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.18" @@ -667,6 +1639,98 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_enum" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "objc-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da284c198fb9b7b0603f8635185e85fbd5b64ee154b1ed406d489077de2d6d60" + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4b25e1034d0e636cd84707ccdaa9f81243d399196b8a773946dcffec0401659" +dependencies = [ + "objc-sys", + "objc2-encode 4.0.1", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb79768a710a9a1798848179edb186d1af7e8a8679f369e4b8d201dd2a034047" +dependencies = [ + "block2 0.5.0", + "objc2 0.5.1", + "objc2-core-data", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e092bc42eaf30a08844e6a076938c60751225ec81431ab89f5d1ccd9f958d6c" +dependencies = [ + "block2 0.5.0", + "objc2 0.5.1", + "objc2-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.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88658da63e4cc2c8adb1262902cd6af51094df0488b760d6fd27194269c0950a" + +[[package]] +name = "objc2-foundation" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfaefe14254871ea16c7d88968c0ff14ba554712a20d76421eec52f0a7fb8904" +dependencies = [ + "block2 0.5.0", + "objc2 0.5.1", +] + [[package]] name = "once_cell" version = "1.19.0" @@ -689,6 +1753,65 @@ dependencies = [ "serde", ] +[[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.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f0d54bde9774d3a51dcf281a5def240c71996bc6ca05d2c847ec8b2b216166" +dependencies = [ + "libredox 0.0.2", +] + +[[package]] +name = "owned_ttf_parser" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b41438d2fc63c46c74a2203bf5ccd82c41ba04347b2fcf5754f230b167067d5" +dependencies = [ + "ttf-parser", +] + +[[package]] +name = "parking_lot" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.1", + "smallvec", + "windows-targets 0.52.4", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + [[package]] name = "pin-project-lite" version = "0.2.13" @@ -696,99 +1819,241 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] -name = "pin-utils" -version = "0.1.0" +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "plotters" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" + +[[package]] +name = "plotters-svg" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "png" +version = "0.17.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "polling" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645493cf344456ef24219d02a768cf1fb92ddf8c92161679ae3d91b91a637be3" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "tracing", + "windows-sys 0.52.0", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[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-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "profiling" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "quick-error" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] -name = "pinned" -version = "0.1.0" +name = "quick-xml" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a829027bd95e54cfe13e3e258a1ae7b645960553fb82b75ff852c29688ee595b" +checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" dependencies = [ - "futures", - "rustversion", - "thiserror", + "memchr", ] [[package]] -name = "plotters" -version = "0.3.5" +name = "quote" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ - "num-traits", - "plotters-backend", - "plotters-svg", - "wasm-bindgen", - "web-sys", + "proc-macro2", ] [[package]] -name = "plotters-backend" -version = "0.3.5" +name = "rand" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] [[package]] -name = "plotters-svg" -version = "0.3.5" +name = "rand_chacha" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ - "plotters-backend", + "ppv-lite86", + "rand_core", ] [[package]] -name = "png" -version = "0.17.13" +name = "rand_core" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "bitflags", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide", + "getrandom", ] [[package]] -name = "proc-macro-crate" -version = "1.3.1" +name = "rav1e" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" dependencies = [ + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools 0.12.1", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", "once_cell", - "toml_edit", + "paste", + "profiling", + "rand", + "rand_chacha", + "simd_helpers", + "system-deps", + "thiserror", + "v_frame", + "wasm-bindgen", ] [[package]] -name = "proc-macro2" -version = "1.0.78" +name = "ravif" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "bc13288f5ab39e6d7c9d501759712e6969fcc9734220846fc9ed26cae2cc4234" dependencies = [ - "unicode-ident", + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", ] [[package]] -name = "qoi" -version = "0.4.1" +name = "raw-window-handle" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" -dependencies = [ - "bytemuck", -] +checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" [[package]] -name = "quote" -version = "1.0.35" +name = "raw-window-handle" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" -dependencies = [ - "proc-macro2", -] +checksum = "8cc3bcbdb1ddfc11e700e62968e6b4cc9c75bb466464ad28fb61c5b2c964418b" + +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" [[package]] name = "rayon" @@ -810,6 +2075,44 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +dependencies = [ + "bitflags 2.5.0", +] + +[[package]] +name = "redox_users" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" +dependencies = [ + "getrandom", + "libredox 0.1.3", + "thiserror", +] + [[package]] name = "regex" version = "1.10.3" @@ -840,10 +2143,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] -name = "rustversion" -version = "1.0.14" +name = "rgb" +version = "0.8.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05aaa8004b64fd573fc9d002f4e632d51ad4f026c2b5ba95fcb6c2f32c2c47d8" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "ron" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" +dependencies = [ + "base64 0.21.7", + "bitflags 2.5.0", + "serde", + "serde_derive", +] + +[[package]] +name = "rustix" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags 2.5.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] [[package]] name = "ryu" @@ -851,6 +2182,15 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +[[package]] +name = "safe_arch" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f398075ce1e6a179b46f51bd88d0598b92b00d3551f1a2d4ac49e771b56ac354" +dependencies = [ + "bytemuck", +] + [[package]] name = "same-file" version = "1.0.6" @@ -860,6 +2200,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" @@ -897,12 +2243,43 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" +dependencies = [ + "serde", +] + +[[package]] +name = "simba" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a386a501cd104797982c15ae17aafe8b9261315b5d07e3ec803f2ea26be0fa" +dependencies = [ + "approx", + "num-complex", + "num-traits", + "paste", + "wide", +] + [[package]] name = "simd-adler32" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + [[package]] name = "slab" version = "0.4.9" @@ -912,12 +2289,66 @@ dependencies = [ "autocfg", ] +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "version_check", +] + [[package]] name = "smallvec" version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +[[package]] +name = "smithay-client-toolkit" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "922fd3eeab3bd820d76537ce8f582b1cf951eceb5475c28500c7457d9d17f53a" +dependencies = [ + "bitflags 2.5.0", + "calloop", + "calloop-wayland-source", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix", + "thiserror", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", +] + +[[package]] +name = "smithay-clipboard" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c091e7354ea8059d6ad99eace06dd13ddeedbb0ac72d40a9a6e7ff790525882d" +dependencies = [ + "libc", + "smithay-client-toolkit", + "wayland-backend", +] + +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] + [[package]] name = "spin" version = "0.9.8" @@ -927,6 +2358,12 @@ dependencies = [ "lock_api", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "syn" version = "2.0.51" @@ -938,6 +2375,25 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "target-lexicon" +version = "0.12.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" + [[package]] name = "thiserror" version = "1.0.57" @@ -973,34 +2429,166 @@ dependencies = [ name = "tinytemplate" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "toml" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.22.14", +] + +[[package]] +name = "toml_datetime" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow 0.6.13", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" + +[[package]] +name = "ttf-parser" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ - "serde", - "serde_json", + "form_urlencoded", + "idna", + "percent-encoding", ] [[package]] -name = "toml_datetime" -version = "0.6.5" +name = "utf8parse" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] -name = "toml_edit" -version = "0.19.15" +name = "v_frame" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b" dependencies = [ - "indexmap", - "toml_datetime", - "winnow", + "aligned-vec", + "num-traits", + "wasm-bindgen", ] [[package]] -name = "unicode-ident" -version = "1.0.12" +name = "version-compare" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "walkdir" @@ -1012,6 +2600,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "wasm-bindgen" version = "0.2.91" @@ -1089,6 +2683,115 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wayland-backend" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d50fa61ce90d76474c87f5fc002828d81b32677340112b4ef08079a9d459a40" +dependencies = [ + "cc", + "downcast-rs", + "rustix", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82fb96ee935c2cea6668ccb470fb7771f6215d1691746c2d896b447a00ad3f1f" +dependencies = [ + "bitflags 2.5.0", + "rustix", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-csd-frame" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" +dependencies = [ + "bitflags 2.5.0", + "cursor-icon", + "wayland-backend", +] + +[[package]] +name = "wayland-cursor" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71ce5fa868dd13d11a0d04c5e2e65726d0897be8de247c0c5a65886e283231ba" +dependencies = [ + "rustix", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" +dependencies = [ + "bitflags 2.5.0", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-plasma" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23803551115ff9ea9bce586860c5c5a971e360825a0309264102a9495a5ff479" +dependencies = [ + "bitflags 2.5.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" +dependencies = [ + "bitflags 2.5.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b3a62929287001986fb58c789dce9b67604a397c15c611ad9f747300b6c283" +dependencies = [ + "proc-macro2", + "quick-xml", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15a0c8eaff5216d07f226cb7a549159267f3467b289d9a2e52fd3ef5aae2b7af" +dependencies = [ + "dlib", + "log", + "once_cell", + "pkg-config", +] + [[package]] name = "web-sys" version = "0.3.68" @@ -1099,12 +2802,50 @@ 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 = "webbrowser" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "425ba64c1e13b1c6e8c5d2541c8fac10022ca584f33da781db01b5756aef1f4e" +dependencies = [ + "block2 0.5.0", + "core-foundation", + "home", + "jni", + "log", + "ndk-context", + "objc2 0.5.1", + "objc2-foundation", + "url", + "web-sys", +] + [[package]] name = "weezl" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" +[[package]] +name = "wide" +version = "0.7.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1134eff459f1063780b94cc78b704e2212cac12abd554e4268f5b8f9dfcc1883" +dependencies = [ + "bytemuck", + "safe_arch", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1136,13 +2877,61 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.4", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] @@ -1151,57 +2940,189 @@ version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +[[package]] +name = "winit" +version = "0.29.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d59ad965a635657faf09c8f062badd885748428933dad8e8bdd64064d92e5ca" +dependencies = [ + "ahash", + "android-activity", + "atomic-waker", + "bitflags 2.5.0", + "bytemuck", + "calloop", + "cfg_aliases", + "core-foundation", + "core-graphics", + "cursor-icon", + "icrate", + "js-sys", + "libc", + "log", + "memmap2", + "ndk", + "ndk-sys", + "objc2 0.4.1", + "once_cell", + "orbclient", + "percent-encoding", + "raw-window-handle 0.5.2", + "raw-window-handle 0.6.1", + "redox_syscall 0.3.5", + "rustix", + "smithay-client-toolkit", + "smol_str", + "unicode-segmentation", + "wasm-bindgen", + "wasm-bindgen-futures", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-protocols-plasma", + "web-sys", + "web-time", + "windows-sys 0.48.0", + "x11-dl", + "x11rb", + "xkbcommon-dl", +] + [[package]] name = "winnow" version = "0.5.40" @@ -1211,6 +3132,104 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1" +dependencies = [ + "memchr", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "x11rb" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" +dependencies = [ + "as-raw-xcb-connection", + "gethostname", + "libc", + "libloading", + "once_cell", + "rustix", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" + +[[package]] +name = "xcursor" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a0ccd7b4a5345edfcd0c3535718a4e9ff7798ffc536bb5b5a0e26ff84732911" + +[[package]] +name = "xkbcommon-dl" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" +dependencies = [ + "bitflags 2.5.0", + "dlib", + "log", + "once_cell", + "xkeysym", +] + +[[package]] +name = "xkeysym" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "054a8e68b76250b253f671d1268cb7f1ae089ec35e195b2efb2a4e9a836d0621" + +[[package]] +name = "xml-rs" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791978798f0597cfc70478424c2b4fdc2b7a8024aaff78497ef00f24ef674193" + +[[package]] +name = "zerocopy" +version = "0.7.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + [[package]] name = "zune-inflate" version = "0.2.54" @@ -1219,3 +3238,12 @@ checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" dependencies = [ "simd-adler32", ] + +[[package]] +name = "zune-jpeg" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec866b44a2a1fd6133d363f073ca1b179f438f99e7e5bfb1e33f7181facfe448" +dependencies = [ + "zune-core", +] diff --git a/Cargo.toml b/Cargo.toml index 44e2e14..7ce54d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,4 +4,18 @@ resolver = "2" members = [ "optimize", "frontend", -] \ No newline at end of file + "graphics", +] + +default-members = ["frontend"] + +[workspace.dependencies] +nalgebra = { version = "0.33", features = ["serde-serialize"] } + +eframe = { version = "0.28", default-features = false, features = [ + # "accesskit", # Make egui comptaible with screen readers. NOTE: adds a lot of dependencies. + "default_fonts", # Embed the default egui fonts. + "glow", # Use the glow rendering backend. Alternative: "wgpu". + "persistence", # Enable restoring app state when restarting the app. +] } + diff --git a/frontend/.cargo/config.toml b/frontend/.cargo/config.toml new file mode 100644 index 0000000..bac83fe --- /dev/null +++ b/frontend/.cargo/config.toml @@ -0,0 +1,6 @@ +# clipboard api is still unstable, so web-sys requires the below flag to be passed for copy (ctrl + c) to work +# https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html +# check status at https://developer.mozilla.org/en-US/docs/Web/API/Clipboard#browser_compatibility +# we don't use `[build]` because of rust analyzer's build cache invalidation https://github.com/emilk/eframe_template/issues/93 +[target.wasm32-unknown-unknown] +rustflags = ["--cfg=web_sys_unstable_apis"] \ No newline at end of file diff --git a/frontend/Cargo.toml b/frontend/Cargo.toml index 5981caa..79ff42a 100644 --- a/frontend/Cargo.toml +++ b/frontend/Cargo.toml @@ -6,41 +6,39 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # [lib] -# crate-type = ["cdylib"] [dependencies] -image = "0.24.7" -wasm-bindgen = "0.2.89" -log = "0.4.6" -wasm-logger = "0.2.0" -wasm-bindgen-futures = "0.4.41" +eframe = { workspace = true } +graphics = { path = "../graphics" } +nalgebra = {workspace = true} +log = "0.4" + +# You only need serde if you want app persistence: +serde = { version = "1", features = ["derive"] } +serde_json = "1.0" + +image = "0.25" optimize = { path= "../optimize" } + +base64 = "0.22" + + +# native: +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +env_logger = "0.11" + +# web: +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen-futures = "0.4" console_error_panic_hook = "0.1.7" -gloo = {version= "0.11.0", default-features = false, features = ["storage", "dialogs", "file", "futures"]} +wasm-logger = "0.2.0" + + +[profile.release] +opt-level = 2 # fast and small wasm + +# Optimize all dependencies even in debug builds: +[profile.dev.package."*"] +opt-level = 2 -serde = {version = "1.0", features = ["derive"]} -serde_json = "1.0" -base64 = "0.21.7" - -[dependencies.web-sys] -version = "0.3.4" -features = [ - 'Document', - 'Element', - 'HtmlElement', - 'Node', - 'Window', - 'CanvasRenderingContext2d', - 'HtmlCanvasElement', - 'ImageData', - 'ImageBitmap', - 'MouseEvent', - 'WheelEvent', - 'KeyboardEvent', - 'HtmlPreElement', - 'HtmlSelectElement', - 'CssStyleDeclaration', - 'HtmlDivElement', - 'HtmlInputElement', -] diff --git a/frontend/assets/favicon.ico b/frontend/assets/favicon.ico new file mode 100755 index 0000000..61ad031 Binary files /dev/null and b/frontend/assets/favicon.ico differ diff --git a/frontend/assets/icon-1024.png b/frontend/assets/icon-1024.png new file mode 100644 index 0000000..1b5868a Binary files /dev/null and b/frontend/assets/icon-1024.png differ diff --git a/frontend/assets/icon-256.png b/frontend/assets/icon-256.png new file mode 100644 index 0000000..ae72287 Binary files /dev/null and b/frontend/assets/icon-256.png differ diff --git a/frontend/assets/icon_ios_touch_192.png b/frontend/assets/icon_ios_touch_192.png new file mode 100644 index 0000000..8472802 Binary files /dev/null and b/frontend/assets/icon_ios_touch_192.png differ diff --git a/frontend/assets/manifest.json b/frontend/assets/manifest.json new file mode 100644 index 0000000..2a137fb --- /dev/null +++ b/frontend/assets/manifest.json @@ -0,0 +1,28 @@ +{ + "name": "egui Template PWA", + "short_name": "egui-template-pwa", + "icons": [ + { + "src": "./icon-256.png", + "sizes": "256x256", + "type": "image/png" + }, + { + "src": "./maskable_icon_x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "any maskable" + }, + { + "src": "./icon-1024.png", + "sizes": "1024x1024", + "type": "image/png" + } + ], + "lang": "en-US", + "id": "/index.html", + "start_url": "./index.html", + "display": "standalone", + "background_color": "white", + "theme_color": "white" +} diff --git a/frontend/assets/maskable_icon_x512.png b/frontend/assets/maskable_icon_x512.png new file mode 100644 index 0000000..db8df3e Binary files /dev/null and b/frontend/assets/maskable_icon_x512.png differ diff --git a/frontend/assets/sw.js b/frontend/assets/sw.js new file mode 100644 index 0000000..7ecd229 --- /dev/null +++ b/frontend/assets/sw.js @@ -0,0 +1,25 @@ +var cacheName = 'egui-template-pwa'; +var filesToCache = [ + './', + './index.html', + './eframe_template.js', + './eframe_template_bg.wasm', +]; + +/* Start the service worker and cache all of the app's content */ +self.addEventListener('install', function (e) { + e.waitUntil( + caches.open(cacheName).then(function (cache) { + return cache.addAll(filesToCache); + }) + ); +}); + +/* Serve cached content when offline */ +self.addEventListener('fetch', function (e) { + e.respondWith( + caches.match(e.request).then(function (response) { + return response || fetch(e.request); + }) + ); +}); diff --git a/frontend/index.html b/frontend/index.html index 0c1b003..113d4cf 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1,139 +1,141 @@ + + + + - - - Path Finder + + eframe template + + + + + + + + + + + + + + + + + + + + + + + -
-
-
- - - - -
- Edit - -
- Presets - -
-
-
- Step 1: Choose background - - - - -
- -
-
- Step 2: Decide Grid Size - -
- - - - -
- -
-
- -
- -
-
- Step 3: Edit Cells - -
- - - - -
- - - -
- Target: - - -
- - -
- -
- - - - - -
- - -
-
- - - -
-
- - -
- - -
- -
- -
-
- - - - - - - -
-
-

Click and drag while holding Ctrl to pan.

-

In path finding mode: Click to select start, Shift-Click to select goal.

-
- -
- -

Version local

-
-
-
- -
-
-

-			
-
-
- + + + + + + + + + diff --git a/frontend/src/app/mod.rs b/frontend/src/app.old/mod.rs similarity index 100% rename from frontend/src/app/mod.rs rename to frontend/src/app.old/mod.rs diff --git a/frontend/src/app/ui/camera.rs b/frontend/src/app.old/ui/camera.rs similarity index 100% rename from frontend/src/app/ui/camera.rs rename to frontend/src/app.old/ui/camera.rs diff --git a/frontend/src/app/ui/mod.rs b/frontend/src/app.old/ui/mod.rs similarity index 100% rename from frontend/src/app/ui/mod.rs rename to frontend/src/app.old/ui/mod.rs diff --git a/frontend/src/app.rs b/frontend/src/app.rs new file mode 100644 index 0000000..5b1c375 --- /dev/null +++ b/frontend/src/app.rs @@ -0,0 +1,1072 @@ +use eframe::egui; +use std::{sync::Arc, time::Duration}; + +use eframe::{egui_glow, glow}; +use egui::{mutex::Mutex, Pos2, Vec2}; +use graphics::{ + camera::Camera, + primitiverenderer::Color, + primitiverenderer_texture::{PrimitiveRendererTexture, RenderTexture}, + shaperenderer::ShapeRenderer, +}; +use image::{DynamicImage, GenericImageView}; +use nalgebra::Point2; +use optimize::{ + find::{MapStorage, MapTrait, PathFinder, PathFinderState, Visited}, + grid::{Cell, Direction, GridMap, Point}, + util::parse_img, +}; + +type Reference = as MapTrait>::Reference; + +pub struct App { + /// Behind an `Arc>` so we can pass it to [`egui::PaintCallback`] and paint later. + world_renderer: Arc>, + + state: State, + background: Option, + output_cell: String, + output_pathfinder: String, + pathfinder: Option< + PathFinder< + as MapTrait>::Reference, + CmpCtx, + usize, + as MapTrait>::Storage< + Visited< + as MapTrait>::Cost, + as MapTrait>::Reference, + >, + >, + GridMap, + >, + >, + mouse_select_state: Option)>>, +} + +type CmpCtx = (); + +/// We derive Deserialize/Serialize so we can persist app state on shutdown. +#[derive(serde::Deserialize, serde::Serialize)] +#[serde(default)] // if we add new fields, give them default values when deserializing old state +struct State { + label: String, + value: f32, + map: GridMap, + draw_grid_lines: bool, + draw_pathfind_debug: bool, + is_editing: bool, + start: Option, + goal: Option, + auto_step: bool, + + edit_state: EditState, + + // store whatever the cameare was looking at + last_camera_position: Option<(nalgebra::Vector2, f32)>, + + background_alpha: f32, + foreground_alpha: f32, +} + +#[derive(serde::Deserialize, serde::Serialize)] +struct EditState { + edit_selection: Option>, + + // stuff for selecting rectangles + selection_start: Option, + selection_end: Option, + + /// The number of pixels per cell in the image, used for auto-detecting the grid size + pixels_per_cell: usize, +} + +#[derive(serde::Deserialize, serde::Serialize)] +struct Selection { + start: R, + end: R, +} + +impl Default for State { + fn default() -> Self { + Self { + value: 0.0, + label: "Hello, world!".to_owned(), + map: GridMap::new(10, 10, 1), + draw_grid_lines: true, + draw_pathfind_debug: true, + is_editing: false, + start: None, + goal: None, + auto_step: true, + edit_state: EditState { + edit_selection: None, + selection_start: None, + selection_end: None, + pixels_per_cell: 1, + }, + last_camera_position: None, + background_alpha: 1.0, + foreground_alpha: 1.0, + } + } +} + +struct Background { + image_data: DynamicImage, + // image: ColorImage, + // texture_handle: egui::TextureHandle, + file_name: String, + scale: f32, +} + +impl App { + /// Called once before the first frame. + pub fn new(cc: &eframe::CreationContext<'_>) -> Self { + // This is also where you can customize the look and feel of egui using + // `cc.egui_ctx.set_visuals` and `cc.egui_ctx.set_fonts`. + + // Load previous app state (if any). + // Note that you must enable the `persistence` feature for this to work. + let state: State = if let Some(storage) = cc.storage { + eframe::get_value(storage, eframe::APP_KEY).unwrap_or_default() + } else { + Default::default() + }; + + let gl = cc + .gl + .as_ref() + .expect("You need to run eframe with the glow backend"); + + let mut world_renderer = WorldRenderer::new(gl); + + if let Some((pos, zoom)) = state.last_camera_position { + world_renderer.camera.set_position(pos); + world_renderer.camera.set_zoom(zoom); + } + + App { + state, + world_renderer: Arc::new(Mutex::new(world_renderer)), + background: None, + output_cell: Default::default(), + pathfinder: None, + output_pathfinder: Default::default(), + mouse_select_state: None, + } + } + + fn set_background(&mut self, image_data: &[u8], file_name: String) { + let image = image::load_from_memory(image_data).unwrap(); + + self.world_renderer.lock().texture = Texture::New(image.clone()); + + self.background = Some(Background { + image_data: image, + file_name, + scale: 1.0, + }); + } + fn set_background_image(&mut self, image: DynamicImage, file_name: String) { + self.world_renderer.lock().texture = Texture::New(image.clone()); + + self.background = Some(Background { + image_data: image, + file_name, + scale: 1.0, + }); + } + + fn draw_neighbors(&self, point: &Point, sr: &mut ShapeRenderer, color: Color) { + if !self.state.map.is_valid(*point) { + return; + } + + sr.begin(graphics::primitiverenderer::PrimitiveType::Line); + for (neighbor, _) in self.state.map.neighbors_of(*point) { + sr.line( + point.col as f32 + 0.5, + point.row as f32 + 0.5, + neighbor.col as f32 + 0.5, + neighbor.row as f32 + 0.5, + color, + ); + } + sr.end(); + + sr.begin(graphics::primitiverenderer::PrimitiveType::Filled); + let padding = 0.3; + for (neighbor, _) in self.state.map.neighbors_of(*point) { + sr.rect( + neighbor.col as f32 + padding, + neighbor.row as f32 + padding, + 1.0 - 2.0 * padding, + 1.0 - 2.0 * padding, + color, + ); + } + sr.end(); + } + + fn mouse_world_to_point_valid(&self, x: f32, y: f32) -> Option { + if x < 0.0 || y < 0.0 { + return None; + } + let point = Point { + row: y as usize, + col: x as usize, + }; + if self.state.map.is_valid(point) { + Some(point) + } else { + None + } + } + fn on_map_change(&mut self) { + // make sure all selections etc are within bounds + if let Some(selection) = &mut self.state.edit_state.edit_selection { + selection.start.row = selection.start.row.min(self.state.map.rows - 1); + selection.start.col = selection.start.col.min(self.state.map.columns - 1); + selection.end.row = selection.end.row.min(self.state.map.rows - 1); + selection.end.col = selection.end.col.min(self.state.map.columns - 1); + } + + if let Some(start) = &mut self.state.start { + start.row = start.row.min(self.state.map.rows - 1); + start.col = start.col.min(self.state.map.columns - 1); + } + + if let Some(goal) = &mut self.state.goal { + goal.row = goal.row.min(self.state.map.rows - 1); + goal.col = goal.col.min(self.state.map.columns - 1); + } + + // also need to reset the pathfinder + if let (Some(start), Some(goal)) = (self.state.start, self.state.goal) { + self.pathfinder = Some(PathFinder::new( + start, + goal, + self.state.map.create_storage::>(), + (), + )); + } + } +} +fn preview_files_being_dropped(ctx: &egui::Context) { + use egui::*; + use std::fmt::Write as _; + + if !ctx.input(|i| i.raw.hovered_files.is_empty()) { + let text = ctx.input(|i| { + let mut text = "Dropping files:\n".to_owned(); + for file in &i.raw.hovered_files { + if let Some(path) = &file.path { + write!(text, "\n{}", path.display()).ok(); + } else if !file.mime.is_empty() { + write!(text, "\n{}", file.mime).ok(); + } else { + text += "\n???"; + } + } + text + }); + + let painter = + ctx.layer_painter(LayerId::new(Order::Foreground, Id::new("file_drop_target"))); + + let screen_rect = ctx.screen_rect(); + painter.rect_filled(screen_rect, 0.0, Color32::from_black_alpha(192)); + painter.text( + screen_rect.center(), + Align2::CENTER_CENTER, + text, + TextStyle::Heading.resolve(&ctx.style()), + Color32::WHITE, + ); + } +} + +impl eframe::App for App { + /// Called by the frame work to save state before shutdown. + fn save(&mut self, storage: &mut dyn eframe::Storage) { + eframe::set_value(storage, eframe::APP_KEY, &self.state); + } + + /// Called each time the UI needs repainting, which may be many times per second. + fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { + // Put your widgets into a `SidePanel`, `TopBottomPanel`, `CentralPanel`, `Window` or `Area`. + // For inspiration and more examples, go to https://emilk.github.io/egui + + egui::TopBottomPanel::top("top_panel").show(ctx, |ui| { + // The top panel is often a good place for a menu bar: + + egui::menu::bar(ui, |ui| { + // NOTE: no File->Quit on web pages! + let is_web = cfg!(target_arch = "wasm32"); + if !is_web { + ui.menu_button("File", |ui| { + if ui.button("Quit").clicked() { + ctx.send_viewport_cmd(egui::ViewportCommand::Close); + } + }); + ui.add_space(16.0); + } + + egui::widgets::global_dark_light_mode_buttons(ui); + }); + }); + + egui::SidePanel::left("side_panel").show(ctx, |ui| { + // The central panel the region left after adding TopPanel's and SidePanel's + + let mouse_pos = self.world_renderer.lock().last_mouse_pos; + ui.label(format!("Mouse: [{:.2}, {:.2}]", mouse_pos.x, mouse_pos.y)); + + ui.checkbox(&mut self.state.is_editing, "Edit Mode"); + if self.state.is_editing { + ui.separator(); + ui.label("Step 1: Select Background"); + preview_files_being_dropped(ctx); + + let file_name = if let Some(b) = &self.background { + &b.file_name + } else { + "None" + }; + + ui.label(format!("Selected: {file_name}")); + + ui.label("Drop file to select another background"); + if let Some((image, name)) = ctx.input(|i| { + let d = i.raw.dropped_files.first()?; + + // handle the differences between web and native + if let Some(data) = &d.bytes { + image::load_from_memory(data) + .inspect_err(|e| log::error!("Error loading image: {e}")) + .ok() + .map(|i| (i, d.name.clone())) + } else if let Some(path) = &d.path { + image::open(path) + .inspect_err(|e| log::error!("Error loading image: {e}")) + .ok() + .map(|i| (i, d.name.clone())) + } else { + None + } + }) { + self.set_background_image(image, name); + } + + ui.separator(); + ui.label("Step 2: Decide Grid Size"); + + let mut changed = false; + if let Some(b) = &mut self.background { + ui.horizontal(|ui| { + ui.label("Pixels per cell: "); + ui.add( + egui::widgets::DragValue::new( + &mut self.state.edit_state.pixels_per_cell, + ) + .range(1..=100), + ); + if ui.button("Auto Scale").clicked() { + let ppc = self.state.edit_state.pixels_per_cell as f32; + let rows = b.image_data.height() as f32 / ppc; + let cols = b.image_data.width() as f32 / ppc; + self.state.map.resize(cols as usize, rows as usize); + b.scale = 1.0 / ppc; + changed = true; + } + }); + } + if changed { + self.on_map_change(); + } + + ui.separator(); + ui.label("Step 3: Edit Cells"); + + if ui + .add_enabled( + self.background.is_some(), + egui::widgets::Button::new("Auto Fill Map"), + ) + .clicked() + { + self.mouse_select_state = Some(Box::new(|s, p| { + if let Some(background) = &s.background { + // get the pixel at the center of that cell + let x = (p.x / background.scale) as u32; + let y = (p.y / background.scale) as u32; + + let (width, height) = background.image_data.dimensions(); + if x < width && y < height { + let color = background.image_data.get_pixel(x, height - y - 1); + log::info!("Selected color: {:?}", color); + + // generate a map based on the selected color + fill_map_from_image( + &mut s.state.map, + &background.image_data, + background.scale as f64, + &color, + ); + } else { + log::info!("Selected color is out of bounds"); + } + + s.on_map_change(); + } + })); + } + + // TODO: cell editing here + + ui.separator(); + } + + if ui.button("Load Preset").clicked() { + self.set_background( + include_bytes!("../../data/maze-03_6_threshold.png"), + "maze".to_string(), + ); + + if let Some(background) = &self.background { + let mut map = parse_img(&background.image_data).unwrap(); + + let start = Point { row: 14, col: 0 }; + let goal = Point { row: 44, col: 51 }; + + map.cells[10][10] = Cell::OneWay { + cost: 1, + direction: Direction::Right, + target: Some(goal), + }; + + let finder = PathFinder::new( + start, + goal, + map.create_storage::>(), + (), + ); + + self.state.map = map; + self.state.goal = Some(goal); + self.state.start = Some(start); + self.pathfinder = Some(finder); + + self.on_map_change(); + } + } + ui.checkbox(&mut self.state.draw_grid_lines, "Draw grid lines"); + ui.checkbox(&mut self.state.draw_pathfind_debug, "Draw Pathfind Debug"); + ui.add( + egui::widgets::Slider::new(&mut self.state.background_alpha, 0.0..=1.0) + .text("Background Alpha"), + ); + ui.add( + egui::widgets::Slider::new(&mut self.state.foreground_alpha, 0.0..=1.0) + .text("Foreground Alpha"), + ); + + // Another option to select the opacity with a single slider only! + let mut value = self.state.foreground_alpha; + if ui + .add( + egui::widgets::Slider::new(&mut value, 0.0..=1.0) + .text("Background <-> Foreground"), + ) + .changed() + { + self.state.foreground_alpha = value; + self.state.background_alpha = 1.0 - value; + } + + if let Some(pathfinder) = &mut self.pathfinder { + ui.label("Pathfinder"); + ui.horizontal(|ui| { + if ui.button("Reset").clicked() { + if let (Some(start), Some(goal)) = (self.state.start, self.state.goal) { + *pathfinder = PathFinder::new( + start, + goal, + self.state.map.create_storage::>(), + (), + ); + } + } + if ui.button("Step").clicked() { + pathfinder.step(&self.state.map); + } + + if ui.button("Finish").clicked() { + loop { + match pathfinder.step(&self.state.map) { + PathFinderState::Computing => {} + _s => break, + } + } + } + }); + ui.checkbox(&mut self.state.auto_step, "Auto Step"); + if self.state.auto_step { + pathfinder.step(&self.state.map); + ctx.request_repaint_after(Duration::from_millis(20)); + } + ui.label(&self.output_cell); + } + + ui.label(&self.output_pathfinder); + ui.label("In path finding mode: Click to select start, Shift-Click to select goal."); + + ui.with_layout(egui::Layout::bottom_up(egui::Align::LEFT), |ui| { + powered_by_egui_and_eframe(ui); + egui::warn_if_debug_build(ui); + }) + }); + egui::CentralPanel::default().show(ctx, |ui| { + // The central panel the region left after adding TopPanel's and SidePanel's + + // Explicit scope for MutexGuard lifetime. + let last_mouse_pos = { + let mut world = self.world_renderer.lock(); + + // store the last camera position for the next time the page is reloaded + self.state.last_camera_position = + Some((world.camera.get_position().clone(), world.camera.get_zoom())); + + // draws the background image + if let Some(background) = &self.background { + world + .pr_texture + .begin(graphics::primitiverenderer::PrimitiveType::Filled); + let x = 0.0; + let y = 0.0; + let width = background.image_data.width() as f32 * background.scale; + let height = background.image_data.height() as f32 * background.scale; + let color = Color::rgba(1.0, 1.0, 1.0, self.state.background_alpha); + + // add the vertices for the image quad, and flip the y axis so that the image is correctly drawn + world.pr_texture.xyzc(x, y, 0.0, color, 0.0, 1.0); + world.pr_texture.xyzc(x + width, y, 0.0, color, 1.0, 1.0); + world + .pr_texture + .xyzc(x + width, y + height, 0.0, color, 1.0, 0.0); + world + .pr_texture + .xyzc(x + width, y + height, 0.0, color, 1.0, 0.0); + world.pr_texture.xyzc(x, y + height, 0.0, color, 0.0, 0.0); + world.pr_texture.xyzc(x, y, 0.0, color, 0.0, 1.0); + + world.pr_texture.end(); + } + + // draw the grid + world + .sr + .begin(graphics::primitiverenderer::PrimitiveType::Filled); + for row in 0..self.state.map.rows { + for col in 0..self.state.map.columns { + let cell = self.state.map.cells[row][col]; + + let color = match cell { + Cell::Invalid => Color::BLACK, + Cell::Valid { cost: 1 } => Color::WHITE, + Cell::Valid { .. } => Color::rgba_u8(255, 255, 0, 255), + // TODO: draw these as arrows! + Cell::OneWay { target: None, .. } => Color::rgba_u8(0, 255, 255, 255), + Cell::OneWay { + target: Some(_), .. + } => Color::rgba_u8(255, 0, 255, 255), + }; + + world.sr.rect(col as f32, row as f32, 1.0, 1.0, color); + } + } + // mark start and goal cells + if let Some(goal) = &self.state.goal { + world + .sr + .rect(goal.col as f32, goal.row as f32, 1.0, 1.0, Color::RED); + } + + if let Some(start) = &self.state.start { + world + .sr + .rect(start.col as f32, start.row as f32, 1.0, 1.0, Color::GREEN); + } + + // draw the selection rectangle + if self.state.is_editing { + if let Some(selection) = &self.state.edit_state.edit_selection { + let color = Color::rgba_u8(0, 255, 0, 128); + let Selection { start, end } = selection; + world.sr.rect( + start.col as f32, + start.row as f32, + end.col as f32 - start.col as f32 + 1.0, + end.row as f32 - start.row as f32 + 1.0, + color, + ); + } + } + + world.sr.end(); + + if self.state.is_editing && self.mouse_select_state.is_some() { + world + .sr + .begin(graphics::primitiverenderer::PrimitiveType::Line); + let color = Color::rgba_u8(255, 0, 0, 255); + let x = world.last_mouse_pos.x; + let y = world.last_mouse_pos.y; + let width = 10.0; + world.sr.line(x - width, y, x + width, y, color); + world.sr.line(x, y - width, x, y + width, color); + world.sr.end(); + } + + // get the cell the user is hovering over + if let Some(point) = + self.mouse_world_to_point_valid(world.last_mouse_pos.x, world.last_mouse_pos.y) + { + world + .sr + .begin(graphics::primitiverenderer::PrimitiveType::Filled); + world + .sr + .rect(point.col as f32, point.row as f32, 1.0, 1.0, Color::GREEN); + world.sr.end(); + + // draw lines to the neighbors of the currently hovered cell + self.draw_neighbors(&point, &mut world.sr, Color::GREEN); + } + + // draw the pathfinder debug information + if let Some(pathfinder) = &self.pathfinder { + let visited = pathfinder.get_visited(); + + if self.state.draw_pathfind_debug { + let margin = 0.15; + world + .sr + .begin(graphics::primitiverenderer::PrimitiveType::Filled); + for row in 0..self.state.map.rows { + for col in 0..self.state.map.columns { + let p = Point { row, col }; + let v = visited.get(p); + + if let Some(f) = *v { + let color = Color::rgba( + (f.cost as f32 / 255.0).min(1.0), + 0.0, + 0.0, + 0.8, + ); + + world.sr.rect( + col as f32 + margin, + row as f32 + margin, + 1.0 - 2.0 * margin, + 1.0 - 2.0 * margin, + color, + ); + } + } + } + world.sr.end(); + } + + match pathfinder.state() { + PathFinderState::Computing => {} + PathFinderState::NoPathFound => { + self.output_pathfinder = "No path found".to_string(); + } + PathFinderState::PathFound(pr) => { + let color = Color::GREEN; + + world + .sr + .begin(graphics::primitiverenderer::PrimitiveType::Line); + // the width is set relative to the size of one cell + if let Some(start) = pr.path.first() { + world.sr.line( + start.col as f32 + 0.5, + start.row as f32 + 0.5, + pr.start.col as f32 + 0.5, + pr.start.row as f32 + 0.5, + color, + ); + } + for p in pr.path.windows(2) { + world.sr.line( + p[0].col as f32 + 0.5, + p[0].row as f32 + 0.5, + p[1].col as f32 + 0.5, + p[1].row as f32 + 0.5, + color, + ); + } + if let Some(end) = pr.path.last() { + world.sr.line( + end.col as f32 + 0.5, + end.row as f32 + 0.5, + pr.goal.col as f32 + 0.5, + pr.goal.row as f32 + 0.5, + color, + ); + } + world.sr.end(); + } + } + + // get the cell the user is hovering + if let Some(point) = self + .mouse_world_to_point_valid(world.last_mouse_pos.x, world.last_mouse_pos.y) + { + let v = visited.get(point); + self.output_cell = format!( + "Cell @{}:{}\n{:#?}\n\n{:#?}", + point.row, point.col, self.state.map.cells[point.row][point.col], v + ); + } + } + + if self.state.draw_grid_lines { + world + .sr + .begin(graphics::primitiverenderer::PrimitiveType::Line); + for row in 0..=self.state.map.rows { + world.sr.line( + 0.0, + row as f32, + self.state.map.columns as f32, + row as f32, + Color::rgba_u8(0, 0, 0, 255), + ); + } + for col in 0..=self.state.map.columns { + world.sr.line( + col as f32, + 0.0, + col as f32, + self.state.map.rows as f32, + Color::rgba_u8(0, 0, 0, 255), + ); + } + + world.sr.end(); + } + world.last_mouse_pos + }; + // do logic based on mouse input + let (mouse_clicked, mouse_pressed, mouse_down, mouse_released) = ui.input(|r| { + ( + r.pointer.primary_clicked(), + r.pointer.primary_pressed(), + r.pointer.primary_down(), + r.pointer.primary_released(), + ) + }); + let modifiers = ui.input(|r| r.modifiers); + + if self.mouse_select_state.is_some() { + if ui.input(|i| i.key_pressed(egui::Key::Escape)) { + self.mouse_select_state = None; + } + if ui.ui_contains_pointer() && mouse_clicked { + if let Some(callback) = self.mouse_select_state.take() { + callback(self, last_mouse_pos); + } + } + } + + let mut start_goal_changed = false; + if let Some(point) = self.mouse_world_to_point_valid(last_mouse_pos.x, last_mouse_pos.y) + { + if !self.state.is_editing { + if mouse_clicked && !modifiers.shift { + self.state.start = Some(point); + start_goal_changed = true; + } else if mouse_clicked && modifiers.shift { + self.state.goal = Some(point); + start_goal_changed = true; + } + } else if !modifiers.shift { + if mouse_clicked && self.mouse_select_state.is_some() { + } else if mouse_pressed { + // initialize region selection + self.state.edit_state.selection_start = Some(point); + self.state.edit_state.selection_end = Some(point); + self.state.edit_state.edit_selection = Some(Selection { + start: point, + end: point, + }); + } else if mouse_released { + self.state.edit_state.selection_start = None; + self.state.edit_state.selection_end = None; + + if let Some(selection) = &self.state.edit_state.edit_selection { + // TODO: load values from the selection here into the editor + let cell = + self.state.map.cells[selection.start.row][selection.start.col]; + log::debug!("Selected region first cell: {:#?}", cell); + } + } else if mouse_down { + // update region selection + if let Some(start) = self.state.edit_state.selection_start { + self.state.edit_state.selection_end = Some(point); + let (start, end) = ( + Point { + row: start.row.min(point.row), + col: start.col.min(point.col), + }, + Point { + row: start.row.max(point.row), + col: start.col.max(point.col), + }, + ); + + self.state.edit_state.edit_selection = Some(Selection { start, end }); + } + } + } + } + + // need to reinitialize the pathfinder if the start or goal has changed + if start_goal_changed { + if let (Some(start), Some(goal)) = (self.state.start, self.state.goal) { + let finder = PathFinder::new( + start, + goal, + self.state.map.create_storage::>(), + (), + ); + + self.pathfinder = Some(finder); + } else { + self.pathfinder = None; + } + } + + self.custom_painting(ui); + }); + } + fn on_exit(&mut self, gl: Option<&glow::Context>) { + if let Some(gl) = gl { + self.world_renderer.lock().destroy(gl); + } + } +} + +fn powered_by_egui_and_eframe(ui: &mut egui::Ui) { + ui.horizontal(|ui| { + ui.spacing_mut().item_spacing.x = 0.0; + ui.label("Powered by "); + ui.hyperlink_to("egui", "https://github.com/emilk/egui"); + ui.label(" and "); + ui.hyperlink_to( + "eframe", + "https://github.com/emilk/egui/tree/master/crates/eframe", + ); + ui.label("."); + }); +} +impl App { + fn custom_painting(&mut self, ui: &mut egui::Ui) { + let (rect, response) = ui.allocate_exact_size( + ui.available_size(), //egui::Vec2::splat(300.0) + egui::Sense::drag(), + ); + + let zoom_factor = if ui.rect_contains_pointer(rect) { + // combine the zoom_delta and the scroll amount to support multitouch gestures as well as normal scroll zoom + + let (scroll_delta, zoom_delta) = ui + .ctx() + .input(|i| (i.smooth_scroll_delta.y, i.zoom_delta())); + + 1.0 / (zoom_delta + 0.1 * scroll_delta / 50.0) + } else { + 1.0 + }; + + let pos = if ui.rect_contains_pointer(rect) { + let mut pos = ui.ctx().pointer_hover_pos().unwrap_or_default(); + // adjust for the position of the allocated space + pos.x -= rect.left(); + pos.y -= rect.top(); + Some(pos) + } else { + None + }; + + let mut drag_delta = response.drag_delta(); + drag_delta.y *= -1.0; + + if self.state.is_editing && !ui.input(|i| i.modifiers.shift) { + drag_delta = Vec2::ZERO; + } + + let size = rect.size(); + + let global_alpha = self.state.foreground_alpha; + // Clone locals so we can move them into the paint callback: + let world_renderer = self.world_renderer.clone(); + + let callback = egui::PaintCallback { + rect, + callback: std::sync::Arc::new(egui_glow::CallbackFn::new(move |_info, painter| { + world_renderer.lock().paint( + painter.gl(), + pos, + size, + drag_delta, + zoom_factor, + global_alpha, + ); + })), + }; + ui.painter().add(callback); + } +} + +pub struct WorldRenderer { + pub sr: ShapeRenderer, + pub pr_texture: PrimitiveRendererTexture, + camera: Camera, + pub last_mouse_pos: Point2, + pub texture: Texture, +} +pub enum Texture { + None, + New(DynamicImage), + Existing(RenderTexture), +} + +impl WorldRenderer { + fn new(gl: &glow::Context) -> Self { + // use glow::HasContext as _; + + Self { + sr: ShapeRenderer::new(gl), + pr_texture: PrimitiveRendererTexture::new(gl, 1000), + camera: Camera::new(), + last_mouse_pos: Point2::new(0.0, 0.0), + texture: Texture::None, + } + } + + fn destroy(&mut self, gl: &glow::Context) { + self.sr.destroy(gl); + } + + // fn as_world_object(&mut self) -> WorldObj<'_> { + // WorldObj { + // sr: &mut self.sr, + // last_mouse_pos: self.last_mouse_pos, + // } + // } + + fn paint( + &mut self, + gl: &glow::Context, + pos: Option, + size: Vec2, + pan: Vec2, + zoom_factor: f32, + global_alpha: f32, + ) { + // first update the camera with any zoom and resize change + self.camera.resize(size); + self.camera.pan(pan); + self.camera.zoom(zoom_factor); + self.camera.update(); + + // set the correct MVP matrix for the shape renderer + let mvp = self.camera.get_mvp(); + self.sr.set_mvp(mvp); + self.pr_texture.set_mvp(mvp); + + self.sr.set_global_alpha(global_alpha); + + // enable blending for transparency + unsafe { + use eframe::glow::HasContext as _; + gl.blend_func(glow::SRC_ALPHA, glow::ONE_MINUS_SRC_ALPHA); + } + // unproject mouse position to + if let Some(pos) = pos { + self.last_mouse_pos = self.camera.unproject(pos); + } + + // if there is a new texture to use, load it + if let Texture::New(image) = &self.texture { + // TODO: destroy the old one using opengl to avoid memory leaks! + + let image_buffer = image.to_rgba8(); + let pixels = image_buffer.as_flat_samples(); + + let texture_id = self.pr_texture.create_texture( + gl, + &pixels.as_slice(), + image.width(), + image.height(), + ); + + log::info!("Generated with Texture ID "); + + self.texture = Texture::Existing(texture_id); + } + + // TODO: draw image and shapes with opacity + + // do the actual drawing of already cached vertices + if let Texture::Existing(texture_id) = &self.texture { + self.pr_texture.flush(gl, texture_id); + } + self.sr.flush(gl); + } +} + +/// Fills a map based on the pixels on an image and a selected color for valid cells +fn fill_map_from_image( + map: &mut GridMap, + image: &DynamicImage, + image_scale: f64, + color: &image::Rgba, +) { + let image_height = image.height(); + for row in 0..map.rows { + for col in 0..map.columns { + // find the pixel at the center of the cell + let (x, y) = (col as f64 + 0.5, row as f64 + 0.5); + let (x, y) = (x / image_scale, y / image_scale); + let (x, y) = (x as u32, image_height - y as u32 - 1); + let pixel = image.get_pixel(x, y); + + let diff = pixel_difference_norm(&pixel, color); + + if diff < 10.0 { + map.cells[row][col] = Cell::Valid { cost: 1 }; + } else { + map.cells[row][col] = Cell::Invalid; + } + } + } +} + +fn pixel_difference_norm(a: &image::Rgba, b: &image::Rgba) -> f64 { + let a = a.0; + let b = b.0; + let diff = [ + (a[0] as f64 - b[0] as f64).abs(), + (a[1] as f64 - b[1] as f64).abs(), + (a[2] as f64 - b[2] as f64).abs(), + ]; + let diff = (diff[0].powi(2) + diff[1].powi(2) + diff[2].powi(2)).sqrt(); + diff +} diff --git a/frontend/src/lib.rs b/frontend/src/lib.rs new file mode 100644 index 0000000..27de78f --- /dev/null +++ b/frontend/src/lib.rs @@ -0,0 +1,4 @@ +#![warn(clippy::all, rust_2018_idioms)] + +mod app; +pub use app::App; diff --git a/frontend/src/main.rs b/frontend/src/main.rs index 63cffd1..c230fd1 100644 --- a/frontend/src/main.rs +++ b/frontend/src/main.rs @@ -1,372 +1,45 @@ -use std::{cell::RefCell, collections::VecDeque, rc::Rc}; - -use app::AppImpl; -use context::{CellSelector, Context, ContextImpl, Input}; -use event::{ButtonId, InputChange, InputId}; -use log::debug; -use wasm_bindgen::prelude::*; -use web_sys::{CanvasRenderingContext2d, Document, HtmlCanvasElement, HtmlElement}; - -use crate::event::Event; - -mod app; -mod context; -mod event; - -/// The main entry point for the application -pub trait App { - #[allow(async_fn_in_trait)] - async fn render(&mut self, ctx: &Context, rendering_ctx: &CanvasRenderingContext2d); -} - -fn register_onclick () + 'static>(id: &str, callback: T) { - let closure_btn_clone = Closure::::new(callback); - get_element_by_id::(id) - .set_onclick(Some(closure_btn_clone.as_ref().unchecked_ref())); - - // See comments https://rustwasm.github.io/wasm-bindgen/examples/closures.html - closure_btn_clone.forget(); -} - -/// register a change event on an element (e.g. any input element) -fn register_change_event () + 'static>(id: &str, mut callback: T) { - let closure = Closure::::new(move |event: web_sys::Event| { - let element = event.current_target().unwrap().dyn_into::().unwrap(); - callback(&element); - }); - get_element_by_id::(id) - .add_event_listener_with_callback("change", closure.as_ref().unchecked_ref()) - .unwrap(); - - closure.forget(); -} -fn register_canvas_event () + 'static>( - canvas: &HtmlCanvasElement, - event: &str, - callback: T, -) { - let closure = Closure::::new(callback); - - canvas - .add_event_listener_with_callback(event, closure.as_ref().unchecked_ref()) - .unwrap(); - - closure.forget(); -} -fn register_canvas_scroll () + 'static>( - canvas: &HtmlCanvasElement, - callback: T, -) { - let closure = Closure::::new(callback); - - canvas - .add_event_listener_with_callback("wheel", closure.as_ref().unchecked_ref()) - .unwrap(); - - closure.forget(); -} - -fn window() -> web_sys::Window { - web_sys::window().expect("no global `window` exists") -} - -fn document() -> Document { - window() - .document() - .expect("should have a document on window") -} - -fn get_element_by_id(id: &str) -> T { - document() - .get_element_by_id(id) - .expect(&format!("should have {} on the page", id)) - .dyn_into::() - .expect(&format!( - "{} should be an `{}`", - id, - std::any::type_name::() - )) +#![warn(clippy::all, rust_2018_idioms)] +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release + +// When compiling natively: +#[cfg(not(target_arch = "wasm32"))] +fn main() -> eframe::Result<()> { + env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`). + + let native_options = eframe::NativeOptions { + viewport: eframe::egui::ViewportBuilder::default() + .with_inner_size([400.0, 300.0]) + .with_min_inner_size([300.0, 220.0]) + .with_icon( + // NOTE: Adding an icon is optional + eframe::icon_data::from_png_bytes(&include_bytes!("../assets/icon-256.png")[..]) + .expect("Failed to load icon"), + ), + ..Default::default() + }; + eframe::run_native( + "eframe template", + native_options, + Box::new(|cc| Ok(Box::new(frontend::App::new(cc)))), + ) } +// When compiling to web using trunk: +#[cfg(target_arch = "wasm32")] fn main() { - wasm_logger::init(wasm_logger::Config::default()); - console_error_panic_hook::set_once(); - - // if we are in CI, set the hash and the url - if let Some(hash) = option_env!("GITHUB_SHA") { - let url = format!("https://github.com/antbern/mappath/tree/{hash}"); - - document() - .get_element_by_id("github-link") - .unwrap() - .set_attribute("href", &url) - .unwrap(); - document() - .get_element_by_id("commit-hash") - .unwrap() - .set_inner_html(&hash[0..7]); - } - - // setup an event handler for window.onresize and scale the canvas based on it's - // clientWidth/clientHeight - { - let closure = move || { - let canvas = get_element_by_id::("canvas"); - let width = canvas.client_width(); - let height = canvas.client_height(); - canvas.set_width(width as u32); - canvas.set_height(height as u32); - debug!("resized canvas to {}x{}", width, height); - }; - // call it once to set the initial size - closure(); - - // then hand it over to the event handler - let closure = Closure::::new(closure); - window().set_onresize(Some(closure.as_ref().unchecked_ref())); - closure.forget(); - } - - let canvas = get_element_by_id::("canvas"); - let rendering_context = canvas - .get_context("2d") - .unwrap() - .unwrap() - .dyn_into::() - .unwrap(); - - let output = get_element_by_id::("output"); - - // setup the context for the app to interact with the world - let context = Context::new(ContextImpl { - document: document(), - cell_selector: CellSelector { - radio_invalid: get_element_by_id("cell-invalid"), - radio_valid: get_element_by_id("cell-normal"), - input_valid_cost: get_element_by_id("input-normal-cost"), - radio_oneway: get_element_by_id("cell-oneway"), - select_oneway: get_element_by_id("select-oneway"), - span_oneway_target: get_element_by_id("span-oneway-target"), - }, - output, - input: Input::default(), - events: VecDeque::new(), - repaint_requested: false, + // Redirect `log` message to `console.log` and friends: + eframe::WebLogger::init(log::LevelFilter::Debug).ok(); + + let web_options = eframe::WebOptions::default(); + + wasm_bindgen_futures::spawn_local(async { + eframe::WebRunner::new() + .start( + "the_canvas_id", // hardcode it + web_options, + Box::new(|cc| Ok(Box::new(frontend::App::new(cc)))), + ) + .await + .expect("failed to start eframe"); }); - - // create cells for storing the closure that redraws the canvas - let redraw: Rc>>> = Rc::new(RefCell::new(None)); - - let request_repaint = { - let redraw = redraw.clone(); - move || { - debug!("request_repaint called, registering animation frame"); - window() - .request_animation_frame( - redraw - .borrow() - .as_ref() - .expect("should convert ok") - .as_ref() - .unchecked_ref(), - ) - .expect("should register `requestAnimationFrame` OK"); - - // request_animation_frame(redraw.borrow().as_ref().unwrap()); - } - }; - - // create input and register mouse event handlers - { - let context = context.clone(); - let request_repaint = request_repaint.clone(); - register_canvas_event(&canvas, "mouseenter", move |event: web_sys::MouseEvent| { - context.push_event(Event::MouseEnter(event.into())); - request_repaint(); - }); - } - { - let context = context.clone(); - let request_repaint = request_repaint.clone(); - register_canvas_event(&canvas, "mousemove", move |event: web_sys::MouseEvent| { - context.push_event(Event::MouseMove(event.into())); - request_repaint(); - }); - } - { - let context = context.clone(); - let request_repaint = request_repaint.clone(); - register_canvas_event(&canvas, "mouseleave", move |event: web_sys::MouseEvent| { - context.push_event(Event::MouseLeave(event.into())); - request_repaint(); - }); - } - { - let context = context.clone(); - let request_repaint = request_repaint.clone(); - register_canvas_event(&canvas, "mousedown", move |event: web_sys::MouseEvent| { - if let Some(_button) = event::MouseButton::from_web_button(event.button()) { - context.push_event(Event::MousePressed(event.into())); - request_repaint(); - } - }); - } - { - let context = context.clone(); - let request_repaint = request_repaint.clone(); - register_canvas_event(&canvas, "mouseup", move |event: web_sys::MouseEvent| { - if let Some(_button) = event::MouseButton::from_web_button(event.button()) { - context.push_event(Event::MouseReleased(event.into())); - request_repaint(); - } - }); - } - { - let context = context.clone(); - let request_repaint = request_repaint.clone(); - register_canvas_event(&canvas, "click", move |event: web_sys::MouseEvent| { - if let Some(_button) = event::MouseButton::from_web_button(event.button()) { - context.push_event(Event::MouseClicked(event.into())); - request_repaint(); - } - }); - } - { - let context = context.clone(); - let request_repaint = request_repaint.clone(); - register_canvas_scroll(&canvas, move |event: web_sys::WheelEvent| { - context.push_event(Event::MouseWheel { - x: event.offset_x(), - y: event.offset_y(), - delta_x: event.delta_x(), - delta_y: event.delta_y(), - }); - event.prevent_default(); - request_repaint(); - }); - } - - for button in ButtonId::iterate() { - let context = context.clone(); - let request_repaint = request_repaint.clone(); - register_onclick(button.id_str(), move || { - context.push_event(Event::ButtonPressed(button)); - request_repaint(); - }); - } - // setup change events for all inputs - { - for input in InputId::iterate() { - match input { - InputId::Select(id) => { - let context = context.clone(); - let request_repaint = request_repaint.clone(); - - register_change_event( - id.id_str(), - move |select: &web_sys::HtmlSelectElement| { - context.push_event(Event::InputChanged(InputChange::Select { - id, - value: select.value(), - })); - request_repaint(); - }, - ); - } - _ => { - let context = context.clone(); - let request_repaint = request_repaint.clone(); - - register_change_event( - input.id_str(), - move |event: &web_sys::HtmlInputElement| { - context.push_event(Event::InputChanged(match input { - InputId::Number(id) => InputChange::Number { - id, - value: event.value().parse().unwrap(), - }, - InputId::Checkbox(id) => InputChange::Checkbox { - id, - value: event.checked(), - }, - InputId::Select(_) => unreachable!(), - })); - request_repaint(); - }, - ); - } - } - } - } - // setup key press handler for button shortcuts - { - let context = context.clone(); - let request_repaint = request_repaint.clone(); - let closure = Closure::::new( - move |event: web_sys::KeyboardEvent| { - if let Some(button) = event::ButtonId::from_key_code(&event.key()) { - context.push_event(Event::ButtonPressed(button)); - request_repaint(); - } - }, - ); - window() - .add_event_listener_with_callback("keydown", closure.as_ref().unchecked_ref()) - .unwrap(); - closure.forget(); - } - // { - // let context = context.clone(); - // let request_repaint = request_repaint.clone(); - // register_change_event( - // "select-mode-radio", - // move |select: &web_sys::HtmlSelectElement| { - // context.push_event(Event::SelectChanged(SelectId::Mode, select.value())); - // request_repaint(); - // }, - // ); - // } - // - - // initialize the app and put it in a refcell - let app = Rc::new(RefCell::new(None)); - - // spawn the constructor to setup the app - wasm_bindgen_futures::spawn_local({ - let app = app.clone(); - let context = context.clone(); - async move { - *app.borrow_mut() = Some(AppImpl::new(&context).await); - } - }); - - // create a closure that will be called by the browser's animation frame - *redraw.borrow_mut() = Some(Closure::::new({ - let context = context.clone(); - let request_repaint = request_repaint.clone(); - let rendering_context = Rc::new(rendering_context); - - move || { - // we need to clone everything so that the block sent to spawn_local is 'static - let context = context.clone(); - let request_repaint = request_repaint.clone(); - let rendering_context = rendering_context.clone(); - let app = app.clone(); - wasm_bindgen_futures::spawn_local(async move { - if let Some(app) = app.borrow_mut().as_mut() { - debug!("redraw"); - - app.render(&context, &rendering_context).await; - - // if the app requested to be repainted, schedule another call - if context.is_repaint_requested() { - debug!("repaint requested"); - request_repaint(); - } - } - }); - } - })); - // initial call to the animation frame function - request_repaint(); } diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml new file mode 100644 index 0000000..055a9e8 --- /dev/null +++ b/graphics/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "graphics" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +# local dependencies + +# inherited dependencies +eframe = {workspace = true} +nalgebra = {workspace = true} + +# special dependencies for this crate diff --git a/graphics/src/camera.rs b/graphics/src/camera.rs new file mode 100644 index 0000000..da74180 --- /dev/null +++ b/graphics/src/camera.rs @@ -0,0 +1,156 @@ +use eframe::egui; +use nalgebra::{Isometry3, Matrix4, Orthographic3, Point2, Vector2, Vector3}; + +pub struct Camera { + position: Vector2, + zoom: f32, + viewport_width: f32, + viewport_height: f32, + has_changed: bool, + current_screen_size: egui::Vec2, + // matrices for the Camera projection + combined: Matrix4, +} + +impl Camera { + pub fn new() -> Self { + Camera { + position: Vector2::new(0.0, 0.0), + zoom: 1.0, + viewport_width: 1.0, + viewport_height: 1.0, + has_changed: true, + current_screen_size: egui::Vec2::new(1.0, 1.0), + + combined: Matrix4::zeros(), + } + } + + pub fn pan(&mut self, screen_change: egui::Vec2) { + if screen_change.x == 0.0 && screen_change.y == 0.0 { + return; + } + + let viewport_change = Vector2::new( + screen_change.x / self.current_screen_size.x * self.viewport_width * self.zoom, + screen_change.y / self.current_screen_size.y * self.viewport_height * self.zoom, + ); + + self.position += viewport_change; + self.has_changed = true; + } + + pub fn resize(&mut self, new_size: egui::Vec2) { + // only do something if the screen size has actually changed + if new_size == self.current_screen_size { + return; + } + + self.current_screen_size = new_size; + + // recalculate the size of the viewport here + self.viewport_width = 10.0; + self.viewport_height = + self.viewport_width * self.current_screen_size.y / self.current_screen_size.x; + + self.has_changed = true; + } + + pub fn zoom(&mut self, factor: f32) { + if factor == 1.0 { + return; + } + + self.zoom *= factor; + + // clamp zoom + if self.zoom < 0.1 { + self.zoom = 0.1; + } + + self.has_changed = true; + } + + pub fn unproject(&self, screen_coord: egui::Pos2) -> Point2 { + // let r = self + // .combined + // .try_inverse() + // .unwrap() + // .transform_point(&Point3::new(screen_coord.x, screen_coord.y, 0.0)); + + // dbg!(r); + // r.xy() + + let mut v = Vector2::new( + screen_coord.x / self.current_screen_size.x * self.viewport_width * self.zoom, + (self.current_screen_size.y - screen_coord.y - 1.0) / self.current_screen_size.y + * self.viewport_height + * self.zoom, + ); + + // adjust for the viewport size + v -= Vector2::new( + self.viewport_width * self.zoom / 2.0, + self.viewport_height * self.zoom / 2.0, + ); + + // adjust for the fact that the center of the screen is at "position" + v -= self.position; + + Point2::new(v.x, v.y) + } + + pub fn update(&mut self) { + if !self.has_changed { + return; + } + self.has_changed = true; + + // recreate the projection matrix + let projection = Orthographic3::new( + self.zoom * -self.viewport_width / 2.0, + self.zoom * self.viewport_width / 2.0, + self.zoom * -self.viewport_height / 2.0, + self.zoom * self.viewport_height / 2.0, + -1.0, + 1.0, + ); + + // recreate the view matrix containing the camera translation + let view = Isometry3::new( + Vector3::new(self.position.x, self.position.y, 0.0), + nalgebra::zero(), + ); + + // calculate the combined transformation + self.combined = projection.as_matrix() * view.to_homogeneous(); + } + + pub fn get_mvp(&self) -> &Matrix4 { + &self.combined + } + + pub fn get_position(&self) -> &Vector2 { + &self.position + } + + pub fn set_position(&mut self, position: Vector2) { + self.position = position; + self.has_changed = true; + } + + pub fn get_zoom(&self) -> f32 { + self.zoom + } + + pub fn set_zoom(&mut self, zoom: f32) { + self.zoom = zoom; + self.has_changed = true; + } +} + +impl Default for Camera { + fn default() -> Self { + Self::new() + } +} diff --git a/graphics/src/gl.rs b/graphics/src/gl.rs new file mode 100644 index 0000000..8eb99a0 --- /dev/null +++ b/graphics/src/gl.rs @@ -0,0 +1,182 @@ +use eframe::glow; + +pub struct VertexBufferLayout { + elements: Vec, + stride: u32, +} + +#[derive(Clone, Copy)] +struct LayoutElement { + gl_type: GLType, + count: u32, + normalized: bool, +} + +#[derive(Clone, Copy)] +#[repr(u32)] +pub enum GLType { + Float = glow::FLOAT, + UnsignedInt = glow::UNSIGNED_INT, + UnsignedByte = glow::UNSIGNED_BYTE, +} + +impl GLType { + fn size(&self) -> u32 { + match *self { + GLType::Float => 4, + GLType::UnsignedInt => 4, + GLType::UnsignedByte => 1, + } + } +} + +impl VertexBufferLayout { + pub fn new() -> Self { + Self { + elements: Vec::new(), + stride: 0, + } + } + + pub fn push(&mut self, gl_type: GLType, count: u32) -> &mut Self { + assert!((1..=4).contains(&count), "count must be 1,2,3 or 4"); + self.elements.push(LayoutElement { + gl_type, + count, + normalized: match gl_type { + GLType::Float => false, + GLType::UnsignedInt => false, + GLType::UnsignedByte => true, + }, + }); + self.stride += (gl_type.size()) * count; + + self + } + + pub fn get_stride(&self) -> u32 { + self.stride + } +} + +pub struct VertexArray { + va: glow::VertexArray, +} + +impl VertexArray { + pub fn new(gl: &glow::Context) -> Self { + use glow::HasContext as _; + + let vertex_array = unsafe { + gl.create_vertex_array() + .expect("Cannot create vertex array") + }; + + Self { va: vertex_array } + } + /// Adds a buffer to this VertexArray together with the specified layout + pub fn add_buffer( + &mut self, + gl: &glow::Context, + buffer: &mut VertexBuffer, + layout: &VertexBufferLayout, + ) { + use glow::HasContext as _; + self.bind(gl); + buffer.bind(gl); + + // attach the layout (this binds the layout and VBO to the VAO) + let mut offset = 0u32; + + for (i, e) in layout.elements.iter().enumerate() { + unsafe { + gl.enable_vertex_attrib_array(i as u32); + gl.vertex_attrib_pointer_f32( + i as u32, + e.count as i32, + e.gl_type as u32, + e.normalized, + layout.get_stride() as i32, + offset as i32, + ); + + offset += e.count * e.gl_type.size(); + } + } + } + + pub fn bind(&self, gl: &glow::Context) { + use glow::HasContext as _; + unsafe { + gl.bind_vertex_array(Some(self.va)); + } + } + + pub fn unbind(&self, gl: &glow::Context) { + use glow::HasContext as _; + unsafe { + gl.bind_vertex_array(None); + } + } + + pub fn destroy(&self, gl: &glow::Context) { + use glow::HasContext as _; + unsafe { gl.delete_vertex_array(self.va) } + } +} + +pub struct VertexBuffer { + id: glow::Buffer, + is_bound: bool, +} +impl VertexBuffer { + pub fn new(gl: &glow::Context) -> Self { + use glow::HasContext as _; + + let buffer = unsafe { gl.create_buffer().expect("Cannot create vertex buffer") }; + + Self { + id: buffer, + is_bound: false, + } + } + + pub fn set_vertices(&mut self, gl: &glow::Context, vertices: &[f32]) { + use glow::HasContext as _; + + if !self.is_bound { + self.bind(gl); + } + + // reinterpret the data as pure bytes + let data = unsafe { + std::slice::from_raw_parts( + vertices.as_ptr() as *const u8, + std::mem::size_of_val(vertices), + ) + }; + // upload the data + unsafe { gl.buffer_data_u8_slice(glow::ARRAY_BUFFER, data, glow::DYNAMIC_DRAW) } + } + + pub fn bind(&mut self, gl: &glow::Context) { + use glow::HasContext as _; + unsafe { + gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.id)); + } + self.is_bound = true; + } + + pub fn unbind(&mut self, gl: &glow::Context) { + use glow::HasContext as _; + unsafe { + gl.bind_buffer(glow::ARRAY_BUFFER, None); + } + self.is_bound = false; + } + + pub fn destroy(&self, gl: &glow::Context) { + use glow::HasContext as _; + unsafe { gl.delete_buffer(self.id) } + } +} diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs new file mode 100644 index 0000000..f4a4265 --- /dev/null +++ b/graphics/src/lib.rs @@ -0,0 +1,9 @@ +#![allow(unused)] + +pub mod camera; + +mod gl; +pub mod primitiverenderer; +pub mod primitiverenderer_texture; +pub mod shader; +pub mod shaperenderer; diff --git a/graphics/src/primitiverenderer.rs b/graphics/src/primitiverenderer.rs new file mode 100644 index 0000000..ee4352a --- /dev/null +++ b/graphics/src/primitiverenderer.rs @@ -0,0 +1,366 @@ +use super::{gl, shader}; +use eframe::glow; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[repr(u32)] +pub enum PrimitiveType { + Point = glow::POINTS, + Line = glow::LINES, + Filled = glow::TRIANGLES, +} + +pub struct PrimitiveRenderer { + program: shader::Program, + vertex_array: gl::VertexArray, + vertex_buffer: gl::VertexBuffer, + + proj_model_view: nalgebra::Matrix4, + global_alpha: f32, + vertices: Vec, + max_vertices: usize, + vertex_count: usize, + index: usize, + active_drawcall: Option, + draw_calls: Vec, +} + +#[derive(Clone, Copy, Debug)] +pub(crate) struct DrawCall { + pub(crate) pt: PrimitiveType, + pub(crate) start_index: usize, + pub(crate) vertex_count: usize, +} + +/* /// Test for using a "RenderGuard" to make sure state of the renderer is correctly managed +pub struct RenderGuard<'a> { + pr: &'a mut PrimitiveRenderer, + pt: PrimitiveType, + start_index: usize, +} + +impl RenderGuard<'_> { + pub fn end(self) { + // the end of this method will drop self + + // self.pr.draw_calls.push(DrawCall { + // pt: self.pt, + // start_index: self.start_index, + // vertex_count: self.pr.vertex_count - self.start_index, + // }); + + // // TODO: remove + // self.pr.active_drawcall = None; + } +} + +impl Vertex3C for RenderGuard<'_> { + fn xyzc(&mut self, x: f32, y: f32, z: f32, color: Color) { + self.pr.xyzc(x, y, z, color); + } +} + +impl Drop for RenderGuard<'_> { + fn drop(&mut self) { + self.pr.draw_calls.push(DrawCall { + pt: self.pt, + start_index: self.start_index, + vertex_count: self.pr.vertex_count - self.start_index, + }); + + // TODO: remove + self.pr.active_drawcall = None; + } +} + */ + +pub trait Vertex3C { + /// Adds a vertex at a 3D position with a specific color + fn xyzc(&mut self, x: f32, y: f32, z: f32, color: Color); + + #[inline] + fn xyz(&mut self, x: f32, y: f32, z: f32) { + self.xyzc(x, y, z, Color::BLACK); + } + + #[inline] + fn v3(&mut self, v: nalgebra::Vector3) { + self.v3c(v, Color::BLACK); + } + #[inline] + fn v3c(&mut self, v: nalgebra::Vector3, color: Color) { + self.xyzc(v.x, v.y, v.z, color); + } +} + +pub trait Vertex2C { + /// Adds a vertex at a 2D position with a specific color + fn xyc(&mut self, x: f32, y: f32, color: Color); + + #[inline] + fn xy(&mut self, x: f32, y: f32) { + self.xyc(x, y, Color::BLACK); + } + + #[inline] + fn v2(&mut self, v: nalgebra::Vector2) { + self.v2c(v, Color::BLACK); + } + #[inline] + fn v2c(&mut self, v: nalgebra::Vector2, color: Color) { + self.xyc(v.x, v.y, color); + } +} + +/// Automatically implement Vertex2C for any Vertex3C by setting z=0.0 +impl Vertex2C for T { + fn xyc(&mut self, x: f32, y: f32, color: Color) { + self.xyzc(x, y, 0.0, color); + } +} + +impl PrimitiveRenderer { + pub fn new(gl: &glow::Context, max_vertices: u32) -> Self { + //load our shader + let shader = shader::Program::new( + gl, + r#" + layout(location = 0) in vec4 position; + layout(location = 1) in vec4 color; + + uniform mat4 u_projModelView; + uniform float u_globalAlpha; + + out vec4 v_Color; + void main(){ + // output the final vertex position + gl_Position = u_projModelView * position; + + v_Color = vec4(color.xyz, color.w * u_globalAlpha); + } + "#, + r#" + precision mediump float; + layout(location = 0) out vec4 color; + + in vec4 v_Color; + void main(){ + color = v_Color; + } + "#, + ); + + shader.bind(gl); + + // create the layout description for the program above + let mut layout = gl::VertexBufferLayout::new(); + layout.push(gl::GLType::Float, 3); + layout.push(gl::GLType::UnsignedByte, 4); + let layout = layout; + + let mut vb = gl::VertexBuffer::new(gl); + + // allocate storage for our vertices (3 position + 1 color) floats + let vertices = vec![0f32; max_vertices as usize * 4]; + + // create vertex array and combine our vertex buffer with the layout + let mut va = gl::VertexArray::new(gl); + va.add_buffer(gl, &mut vb, &layout); + + Self { + program: shader, + vertex_array: va, + vertex_buffer: vb, + vertices, + max_vertices: max_vertices as usize, + proj_model_view: nalgebra::Matrix4::identity(), + global_alpha: 1.0, + vertex_count: 0, + index: 0, + active_drawcall: None, + draw_calls: Vec::new(), + } + } + + pub fn set_mvp(&mut self, mvp: &nalgebra::Matrix4) { + self.proj_model_view.copy_from(mvp); + } + pub fn set_global_alpha(&mut self, alpha: f32) { + self.global_alpha = alpha; + } + + pub fn begin(&mut self, primitive_type: PrimitiveType) { + assert!( + self.active_drawcall.is_none(), + "begin cannot be called twice in a row" + ); + + self.active_drawcall = Some(DrawCall { + pt: primitive_type, + start_index: self.vertex_count, + vertex_count: 0, + }); + } + + /* + pub fn begin2(&mut self, primitive_type: PrimitiveType) -> RenderGuard<'_> { + // todo: remove me later + self.active_drawcall = Some(DrawCall { + pt: primitive_type, + start_index: self.vertex_count, + vertex_count: 0, + }); + + RenderGuard { + start_index: self.vertex_count, + pr: self, + pt: primitive_type, + } + }*/ + + pub fn end(&mut self) { + // mark the current position in the buffer + if let Some(mut dc) = self.active_drawcall { + dc.vertex_count = self.vertex_count - dc.start_index; + self.draw_calls.push(dc); + } else { + panic!("end() cannot be called before a call to begin() was made"); + } + + self.active_drawcall = None; + } + + // TODO: add function for ensuring space for X more vertices. That could actually take in the GL context and perform a `draw` if necessary... + + pub fn flush(&mut self, gl: &glow::Context) { + use glow::HasContext as _; + + assert!( + self.active_drawcall.is_none(), + "end() must be called before draw()" + ); + + // println!( + // "Flushing {} vertices in {} draw calls => {:.2} vertices / call. Cap = {} ~= {} MB", + // self.vertex_count, + // self.draw_calls.len(), + // self.vertex_count as f32 / self.draw_calls.len() as f32, + // self.vertices.capacity(), + // (self.vertices.capacity() * std::mem::size_of::()) / 1024 / 1024 + // ); + // use the shader + self.program.bind(gl); + self.program + .set_uniform_matrix_4_f32(gl, "u_projModelView", self.proj_model_view); + self.program + .set_uniform_1_f32(gl, "u_globalAlpha", self.global_alpha); + + // upload all our data + self.vertex_buffer.bind(gl); + self.vertex_buffer + .set_vertices(gl, &self.vertices[..self.index]); + + // do the actual drawing using multiple draw calls + self.vertex_array.bind(gl); + + // TODO: go through and "optimize" the drawcalls if possible, i.e. by combining "adjacent" calls with the same primitive type + + for dc in self.draw_calls.iter() { + unsafe { + gl.draw_arrays(dc.pt as u32, dc.start_index as i32, dc.vertex_count as i32); + } + } + + // reset state + self.vertex_count = 0; + self.index = 0; + self.draw_calls.clear(); + } + + pub fn destroy(&self, gl: &glow::Context) { + self.vertex_array.destroy(gl); + self.vertex_buffer.destroy(gl); + self.program.destroy(gl); + } +} + +impl Vertex3C for PrimitiveRenderer { + fn xyzc(&mut self, x: f32, y: f32, z: f32, color: Color) { + assert!( + self.active_drawcall.is_some(), + "must call begin() before vertex" + ); + + // if the buffer is full, do a "flush" + if self.vertex_count >= self.max_vertices - 1 { + panic!("no more space for vertices"); + } + + // SAFETY: we keep track and make sure we have enough space using index and vertex_count variables + #[allow(clippy::identity_op)] + unsafe { + *self.vertices.get_unchecked_mut(self.index + 0) = x; + *self.vertices.get_unchecked_mut(self.index + 1) = y; + *self.vertices.get_unchecked_mut(self.index + 2) = z; + *self.vertices.get_unchecked_mut(self.index + 3) = color.bits; + } + // self.vertices[self.index + 0] = x; + // self.vertices[self.index + 1] = y; + // self.vertices[self.index + 2] = z; + // self.vertices[self.index + 3] = color.bits; + + self.index += 4; // 3 position + 1 u32 for color + self.vertex_count += 1; + } +} + +/// An RGBA color. +/// Internally, the color is packed into 4 bytes, one for each of RGBA, instead of as 4 floats to save memory +#[derive(Clone, Copy)] +pub struct Color { + pub(crate) bits: f32, +} + +impl Color { + pub const BLACK: Color = Color::rgba_u8(0x00, 0x00, 0x00, 0xff); + pub const WHITE: Color = Color::rgba_u8(0xff, 0xff, 0xff, 0xff); + pub const RED: Color = Color::rgba_u8(0xff, 0x00, 0x00, 0xff); + pub const GREEN: Color = Color::rgba_u8(0x00, 0xff, 0x00, 0xff); + pub const BLUE: Color = Color::rgba_u8(0x00, 0x00, 0xff, 0xff); + + pub fn rgb(r: f32, g: f32, b: f32) -> Self { + Self::rgba(r, g, b, 1.0) + } + pub fn rgba(r: f32, g: f32, b: f32, a: f32) -> Self { + Self::rgba_u8( + (255.0 * r) as u8, + (255.0 * g) as u8, + (255.0 * b) as u8, + (255.0 * a) as u8, + ) + } + + pub const fn rgba_u8(r: u8, g: u8, b: u8, a: u8) -> Self { + let colori = ((a as u32) << 24) | ((b as u32) << 16) | ((g as u32) << 8) | (r as u32); + + // bits: f32::from_bits(colori), // (not const yet...) + Self { + bits: unsafe { core::mem::transmute::(colori) }, + } + } + + pub fn grayscale(gray: f32) -> Self { + Self::rgb(gray, gray, gray) + } +} + +impl From<[f32; 3]> for Color { + fn from(value: [f32; 3]) -> Self { + Color::rgb(value[0], value[1], value[2]) + } +} + +impl From<[f32; 4]> for Color { + fn from(value: [f32; 4]) -> Self { + Color::rgba(value[0], value[1], value[2], value[3]) + } +} diff --git a/graphics/src/primitiverenderer_texture.rs b/graphics/src/primitiverenderer_texture.rs new file mode 100644 index 0000000..7a16b46 --- /dev/null +++ b/graphics/src/primitiverenderer_texture.rs @@ -0,0 +1,339 @@ +use crate::primitiverenderer::{Color, DrawCall, PrimitiveType}; + +use super::{gl, shader}; +use eframe::glow; + +pub struct PrimitiveRendererTexture { + program: shader::Program, + vertex_array: gl::VertexArray, + vertex_buffer: gl::VertexBuffer, + + proj_model_view: nalgebra::Matrix4, + vertices: Vec, + max_vertices: usize, + vertex_count: usize, + index: usize, + active_drawcall: Option, + draw_calls: Vec, +} + +/* /// Test for using a "RenderGuard" to make sure state of the renderer is correctly managed +pub struct RenderGuard<'a> { + pr: &'a mut PrimitiveRenderer, + pt: PrimitiveType, + start_index: usize, +} + +impl RenderGuard<'_> { + pub fn end(self) { + // the end of this method will drop self + + // self.pr.draw_calls.push(DrawCall { + // pt: self.pt, + // start_index: self.start_index, + // vertex_count: self.pr.vertex_count - self.start_index, + // }); + + // // TODO: remove + // self.pr.active_drawcall = None; + } +} + +impl Vertex3C for RenderGuard<'_> { + fn xyzc(&mut self, x: f32, y: f32, z: f32, color: Color) { + self.pr.xyzc(x, y, z, color); + } +} + +impl Drop for RenderGuard<'_> { + fn drop(&mut self) { + self.pr.draw_calls.push(DrawCall { + pt: self.pt, + start_index: self.start_index, + vertex_count: self.pr.vertex_count - self.start_index, + }); + + // TODO: remove + self.pr.active_drawcall = None; + } +} + */ + +pub trait Vertex3C { + /// Adds a vertex at a 3D position with a specific color + fn xyzc(&mut self, x: f32, y: f32, z: f32, color: Color); + + #[inline] + fn xyz(&mut self, x: f32, y: f32, z: f32) { + self.xyzc(x, y, z, Color::BLACK); + } + + #[inline] + fn v3(&mut self, v: nalgebra::Vector3) { + self.v3c(v, Color::BLACK); + } + #[inline] + fn v3c(&mut self, v: nalgebra::Vector3, color: Color) { + self.xyzc(v.x, v.y, v.z, color); + } +} + +pub trait Vertex2C { + /// Adds a vertex at a 2D position with a specific color + fn xyc(&mut self, x: f32, y: f32, color: Color); + + #[inline] + fn xy(&mut self, x: f32, y: f32) { + self.xyc(x, y, Color::BLACK); + } + + #[inline] + fn v2(&mut self, v: nalgebra::Vector2) { + self.v2c(v, Color::BLACK); + } + #[inline] + fn v2c(&mut self, v: nalgebra::Vector2, color: Color) { + self.xyc(v.x, v.y, color); + } +} + +/// Automatically implement Vertex2C for any Vertex3C by setting z=0.0 +impl Vertex2C for T { + fn xyc(&mut self, x: f32, y: f32, color: Color) { + self.xyzc(x, y, 0.0, color); + } +} + +pub struct RenderTexture { + id: ::Texture, +} + +impl PrimitiveRendererTexture { + pub fn new(gl: &glow::Context, max_vertices: u32) -> Self { + //load our shader + let shader = shader::Program::new( + gl, + r#" + layout(location = 0) in vec4 position; + layout(location = 1) in vec4 color; + layout(location = 2) in vec2 aTexCoord; + + uniform mat4 u_projModelView; + + out vec4 v_Color; + out vec2 TexCoord; + + void main(){ + // output the final vertex position + gl_Position = u_projModelView * position; + + v_Color = color; + TexCoord = aTexCoord; + } + "#, + r#" + precision mediump float; + layout(location = 0) out vec4 color; + + in vec4 v_Color; + in vec2 TexCoord; + + uniform sampler2D ourTexture; + + void main(){ + color = texture(ourTexture, TexCoord) * v_Color; + // color = v_Color; + } + "#, + ); + + shader.bind(gl); + + // create the layout description for the program above + let mut layout = gl::VertexBufferLayout::new(); + layout.push(gl::GLType::Float, 3); + layout.push(gl::GLType::UnsignedByte, 4); + layout.push(gl::GLType::Float, 2); + let layout = layout; + + let mut vb = gl::VertexBuffer::new(gl); + + // allocate storage for our vertices (3 position + 1 color + 2 texture coord) floats + let vertices = vec![0f32; max_vertices as usize * (4 + 2)]; + + // create vertex array and combine our vertex buffer with the layout + let mut va = gl::VertexArray::new(gl); + va.add_buffer(gl, &mut vb, &layout); + + Self { + program: shader, + vertex_array: va, + vertex_buffer: vb, + vertices, + max_vertices: max_vertices as usize, + proj_model_view: nalgebra::Matrix4::identity(), + vertex_count: 0, + index: 0, + active_drawcall: None, + draw_calls: Vec::new(), + } + } + + pub fn set_mvp(&mut self, mvp: &nalgebra::Matrix4) { + self.proj_model_view.copy_from(mvp); + } + + pub fn begin(&mut self, primitive_type: PrimitiveType) { + assert!( + self.active_drawcall.is_none(), + "begin cannot be called twice in a row" + ); + + self.active_drawcall = Some(DrawCall { + pt: primitive_type, + start_index: self.vertex_count, + vertex_count: 0, + }); + } + + pub fn end(&mut self) { + // mark the current position in the buffer + if let Some(mut dc) = self.active_drawcall { + dc.vertex_count = self.vertex_count - dc.start_index; + self.draw_calls.push(dc); + } else { + panic!("end() cannot be called before a call to begin() was made"); + } + + self.active_drawcall = None; + } + + pub fn create_texture( + &self, + gl: &glow::Context, + image_data: &[u8], + width: u32, + height: u32, + ) -> RenderTexture { + use glow::HasContext as _; + + unsafe { + let texture_id = gl.create_texture().expect("cannot create texture"); + gl.bind_texture(glow::TEXTURE_2D, Some(texture_id)); + gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_S, glow::REPEAT as i32); + gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_T, glow::REPEAT as i32); + gl.tex_parameter_i32( + glow::TEXTURE_2D, + glow::TEXTURE_MIN_FILTER, + glow::LINEAR as i32, + ); + gl.tex_parameter_i32( + glow::TEXTURE_2D, + glow::TEXTURE_MAG_FILTER, + glow::LINEAR as i32, + ); + + gl.tex_image_2d( + glow::TEXTURE_2D, + 0, + glow::RGBA8 as i32, + width as i32, + height as i32, + 0, + glow::RGBA, + glow::UNSIGNED_BYTE, + Some(image_data), + ); + eframe::egui_glow::check_for_gl_error!(&gl, "tex_image_2d"); + + RenderTexture { id: texture_id } + } + } + + // TODO: add function for ensuring space for X more vertices. That could actually take in the GL context and perform a `draw` if necessary... + + pub fn flush(&mut self, gl: &glow::Context, texture: &RenderTexture) { + use glow::HasContext as _; + + assert!( + self.active_drawcall.is_none(), + "end() must be called before draw()" + ); + + // println!( + // "Flushing {} vertices in {} draw calls => {:.2} vertices / call. Cap = {} ~= {} MB", + // self.vertex_count, + // self.draw_calls.len(), + // self.vertex_count as f32 / self.draw_calls.len() as f32, + // self.vertices.capacity(), + // (self.vertices.capacity() * std::mem::size_of::()) / 1024 / 1024 + // ); + + // use the shader + self.program.bind(gl); + self.program + .set_uniform_matrix_4_f32(gl, "u_projModelView", self.proj_model_view); + + // bind the texture + unsafe { + // gl.active_texture(glow::TEXTURE0); + gl.bind_texture(glow::TEXTURE_2D, Some(texture.id)); + } + + // upload all our data + self.vertex_buffer.bind(gl); + self.vertex_buffer + .set_vertices(gl, &self.vertices[..self.index]); + + // do the actual drawing using multiple draw calls + self.vertex_array.bind(gl); + + // TODO: go through and "optimize" the drawcalls if possible, i.e. by combining "adjacent" calls with the same primitive type + + for dc in self.draw_calls.iter() { + // !("Drawing {} vertices", dc.vertex_count); + unsafe { + gl.draw_arrays(dc.pt as u32, dc.start_index as i32, dc.vertex_count as i32); + } + } + + // reset state + self.vertex_count = 0; + self.index = 0; + self.draw_calls.clear(); + } + + pub fn destroy(&self, gl: &glow::Context) { + self.vertex_array.destroy(gl); + self.vertex_buffer.destroy(gl); + self.program.destroy(gl); + } +} + +impl PrimitiveRendererTexture { + pub fn xyzc(&mut self, x: f32, y: f32, z: f32, color: Color, texture_x: f32, texture_y: f32) { + assert!( + self.active_drawcall.is_some(), + "must call begin() before vertex" + ); + + // if the buffer is full, do a "flush" + if self.vertex_count >= self.max_vertices - 1 { + panic!("no more space for vertices"); + } + + // SAFETY: we keep track and make sure we have enough space using index and vertex_count variables + #[allow(clippy::identity_op)] + unsafe { + *self.vertices.get_unchecked_mut(self.index + 0) = x; + *self.vertices.get_unchecked_mut(self.index + 1) = y; + *self.vertices.get_unchecked_mut(self.index + 2) = z; + *self.vertices.get_unchecked_mut(self.index + 3) = color.bits; + *self.vertices.get_unchecked_mut(self.index + 4) = texture_x; + *self.vertices.get_unchecked_mut(self.index + 5) = texture_y; + } + + self.index += 4 + 2; // 3 position + 1 u32 for color + 2 for texture coord + self.vertex_count += 1; + } +} diff --git a/graphics/src/shader.rs b/graphics/src/shader.rs new file mode 100644 index 0000000..d206775 --- /dev/null +++ b/graphics/src/shader.rs @@ -0,0 +1,107 @@ +use eframe::glow; + +/// Builds upon `glow::Program` to easily construct a new shader program in a safe way +pub struct Program { + program: glow::Program, +} + +impl Program { + /// Create a new shader program from the vertex and fragment shader source + /// Panics if the shader could not be compiled. + pub fn new( + gl: &glow::Context, + vertex_shader_source: &str, + fragment_shader_source: &str, + ) -> Self { + use glow::HasContext as _; + + let shader_version = if cfg!(target_arch = "wasm32") { + "#version 300 es" + } else { + "#version 330" + }; + + unsafe { + let program = gl.create_program().expect("Cannot create program"); + + let shader_sources = [ + (glow::VERTEX_SHADER, vertex_shader_source), + (glow::FRAGMENT_SHADER, fragment_shader_source), + ]; + + let shaders: Vec<_> = shader_sources + .iter() + .map(|(shader_type, shader_source)| { + let source = format!("{}\n{}", shader_version, shader_source); + let shader = gl + .create_shader(*shader_type) + .expect("Cannot create shader"); + gl.shader_source(shader, &source); + gl.compile_shader(shader); + assert!( + gl.get_shader_compile_status(shader), + "Failed to compile shader of type {shader_type}: {}, source: {}", + gl.get_shader_info_log(shader), + &source + ); + gl.attach_shader(program, shader); + shader + }) + .collect(); + + gl.link_program(program); + if !gl.get_program_link_status(program) { + panic!("{}", gl.get_program_info_log(program)); + } + + for shader in shaders { + gl.detach_shader(program, shader); + gl.delete_shader(shader); + } + + Self { program } + } + } + + pub fn set_uniform_matrix_4_f32( + &self, + gl: &glow::Context, + name: &str, + value: nalgebra::Matrix4, + ) { + use glow::HasContext as _; + unsafe { + gl.uniform_matrix_4_f32_slice( + gl.get_uniform_location(self.program, name).as_ref(), + false, + value.as_slice(), + ); + } + } + pub fn set_uniform_1_f32(&self, gl: &glow::Context, name: &str, value: f32) { + use glow::HasContext as _; + unsafe { + gl.uniform_1_f32(gl.get_uniform_location(self.program, name).as_ref(), value); + } + } + pub fn destroy(&self, gl: &glow::Context) { + use glow::HasContext as _; + unsafe { + gl.delete_program(self.program); + } + } + + pub fn bind(&self, gl: &glow::Context) { + use glow::HasContext as _; + unsafe { + gl.use_program(Some(self.program)); + } + } + + pub fn unbind(&self, gl: &glow::Context) { + use glow::HasContext as _; + unsafe { + gl.use_program(None); + } + } +} diff --git a/graphics/src/shaperenderer.rs b/graphics/src/shaperenderer.rs new file mode 100644 index 0000000..28849fe --- /dev/null +++ b/graphics/src/shaperenderer.rs @@ -0,0 +1,271 @@ +use eframe::glow; +use std::f32::consts::PI; + +use nalgebra::{Matrix2, Vector2}; + +use crate::primitiverenderer::Color; + +use super::primitiverenderer::{PrimitiveRenderer, PrimitiveType, Vertex2C}; + +pub struct ShapeRenderer { + pr: PrimitiveRenderer, + current_shape_type: Option, +} + +// TODO: this could build on some trait for adding vertices that the primitive renderer implements + +impl ShapeRenderer { + pub fn new(gl: &glow::Context) -> Self { + Self { + pr: PrimitiveRenderer::new(gl, 1000000), + current_shape_type: None, + } + } + + pub fn set_mvp(&mut self, mvp: &nalgebra::Matrix4) { + self.pr.set_mvp(mvp); + } + + pub fn set_global_alpha(&mut self, alpha: f32) { + self.pr.set_global_alpha(alpha); + } + + pub fn begin(&mut self, pt: PrimitiveType) { + self.current_shape_type = Some(pt); + self.pr.begin(pt); + } + + pub fn end(&mut self) { + self.pr.end(); + self.current_shape_type = None; + } + + pub fn flush(&mut self, gl: &glow::Context) { + self.pr.flush(gl); + } + + fn check(&mut self, desired_type: PrimitiveType, other: PrimitiveType, _n_vertices: usize) { + if let Some(pt) = self.current_shape_type { + // do we need to "restart" ? + if pt != desired_type && pt != other { + self.end(); + self.begin(desired_type); + } + // else if (renderer.getVertexCount() + numberOfNewVertices > renderer.getMaxVertices()) { + // ShapeType type = currentShapeType; + // end(); + // begin(type); + // } + } else { + panic!("begin() must be called first"); + } + } + + pub fn line(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, color: Color) { + self.check(PrimitiveType::Line, PrimitiveType::Point, 2); + + self.pr.xyc(x1, y1, color); + self.pr.xyc(x2, y2, color); + } + + pub fn rect(&mut self, x: f32, y: f32, width: f32, height: f32, color: Color) { + self.check( + PrimitiveType::Line, + PrimitiveType::Filled, + 8, /*not really always 8 though */ + ); + + match self.current_shape_type { + Some(PrimitiveType::Line) => { + self.pr.xyc(x, y, color); + self.pr.xyc(x + width, y, color); + self.pr.xyc(x + width, y, color); + self.pr.xyc(x + width, y + height, color); + self.pr.xyc(x + width, y + height, color); + self.pr.xyc(x, y + height, color); + self.pr.xyc(x, y + height, color); + self.pr.xyc(x, y, color); + } + Some(PrimitiveType::Filled) => { + self.pr.xyc(x, y, color); + self.pr.xyc(x + width, y, color); + self.pr.xyc(x + width, y + height, color); + self.pr.xyc(x + width, y + height, color); + self.pr.xyc(x, y + height, color); + self.pr.xyc(x, y, color); + } + _ => {} + } + } + + pub fn circle(&mut self, x: f32, y: f32, radius: f32, color: Color) { + // calculate the number of segments needed for a "good" circle + let number_of_segments = 1.max((4.0 * 12.0 * radius.cbrt()) as usize); + self._circle(x, y, radius, color, number_of_segments); + } + + fn _circle(&mut self, x: f32, y: f32, radius: f32, color: Color, number_of_segments: usize) { + // the angle between each circle segment + let angle_per_segment = 2.0 * std::f32::consts::PI / number_of_segments as f32; + + // precompute sin and cos + let (s, c) = angle_per_segment.sin_cos(); + + // starting point + let mut px: f32 = radius; + let mut py: f32 = 0.0; + + match self.current_shape_type { + Some(PrimitiveType::Line) => { + // check(ShapeType.LINE, null, numberOfSegments * 2 + 2); + + // place one vertex for each segment + for _ in 0..number_of_segments { + self.pr.xyc(x + px, y + py, color); + + // rotate point using the "rotation matrix" multiplication to get to the next + (px, py) = (c * px - s * py, s * px + c * py); + + self.pr.xyc(x + px, y + py, color); + } + } + Some(PrimitiveType::Filled) => { + // check(ShapeType.LINE, ShapeType.FILLED, numberOfSegments * 3 + 3); + + // place one vertex for each segment + for _ in 0..number_of_segments { + self.pr.xyc(x, y, color); + self.pr.xyc(x + px, y + py, color); + + // rotate point using the "rotation matrix" multiplication to get to the next + (px, py) = (c * px - s * py, s * px + c * py); + + self.pr.xyc(x + px, y + py, color); + } + } + _ => {} + } + } + + pub fn arrow(&mut self, x: f32, y: f32, angle_rad: f32, radius: f32, color: Color) { + // pre compute sin and cos for the rotation + let (s, c) = angle_rad.sin_cos(); + + // pre-computed sine and cosine values for the "back-wing" of the arrow + let (a_sin, a_cos) = 45f32.sin_cos(); + + // Used Wolfram Alpha for the following trigonometric identities for the corner points: + // cos(t+pi-a) = -sin(a)sin(t)-cos(a)cos(t) + // sin(t+pi-a) = sin(a)cos(t)-cos(a)sin(t) + // cos(t+pi+a) = sin(a)sin(t)-cos(a)cos(t) + // sin(t+pi+a) = sin(a)-cos(t)-cos(a)sin(t) + + // pre compute the factors above for the position of the corner points + let left_cos = -a_sin * s - a_cos * c; + let left_sin = a_sin * c - a_cos * s; + let right_cos = a_sin * s - a_cos * c; + let right_sin = a_sin * -c - a_cos * s; + + match self.current_shape_type { + Some(PrimitiveType::Filled) => { + // check(ShapeType.FILLED, null, 3 * 2); + + // front + self.pr.xyc(x + c * radius, y + s * radius, color); + + // back left + self.pr + .xyc(x + left_cos * radius, y + left_sin * radius, color); + + // back middle + self.pr + .xyc(x - c * (radius / 3.0), y - s * (radius / 3.0), color); + + // back middle (again, starting a new triangle) + self.pr + .xyc(x - c * (radius / 3.0), y - s * (radius / 3.0), color); + + // back right + self.pr + .xyc(x + right_cos * radius, y + right_sin * radius, color); + + // front + self.pr.xyc(x + c * radius, y + s * radius, color); + } + Some(PrimitiveType::Line) => { + // check(ShapeType.LINE, ShapeType.POINT, 4 * 2); + + // front + self.pr.xyc(x + c * radius, y + s * radius, color); + + // back left + self.pr + .xyc(x + left_cos * radius, y + left_sin * radius, color); + + // back left (again, starting a new line) + self.pr + .xyc(x + left_cos * radius, y + left_sin * radius, color); + + // back middle + self.pr + .xyc(x - c * (radius / 3.0), y - s * (radius / 3.0), color); + + // back middle (again, starting a new line) + self.pr + .xyc(x - c * (radius / 3.0), y - s * (radius / 3.0), color); + + // back right + self.pr + .xyc(x + right_cos * radius, y + right_sin * radius, color); + + // back right (again, starting a new line) + self.pr + .xyc(x + right_cos * radius, y + right_sin * radius, color); + + // front + self.pr.xyc(x + c * radius, y + s * radius, color); + } + _ => {} + } + } + + /// Use the information in the Gaussian2D component to draw the correct ellipse around the uncertainty as well as a center piece + pub fn gaussian2d(&mut self, mean: &Vector2, covariance: &Matrix2, p: f32) { + self.begin(PrimitiveType::Filled); + self.circle(mean.x, mean.y, 0.01, Color::BLUE); + self.end(); + + // Matlab reference (Source: https://www.xarg.org/2018/04/how-to-plot-a-covariance-error-ellipse/) + // s = -2 * log(1 - p); + // [V, D] = eig(Sigma * s); + // t = linspace(0, 2 * pi); + // a = (V * sqrt(D)) * [cos(t(:))'; sin(t(:))']; + // plot(a(1, :) + mu(1), a(2, :) + mu(2)); + + // update the ellipse radii + + let s = -2.0 * (1.0 - p).ln(); + + let eigen = (covariance * s).symmetric_eigen(); + + let d = Matrix2::from_diagonal(&eigen.eigenvalues.map(|v| v.sqrt())); + let v = eigen.eigenvectors; + + self.begin(PrimitiveType::Line); + let steps = 25; + for i in 0..steps { + let angle = i as f32 * PI * 2.0 / steps as f32; + let start = mean + (v * d) * Vector2::new(angle.cos(), angle.sin()); + + let angle = ((i + 1) % steps) as f32 * PI * 2.0 / steps as f32; + let end = mean + (v * d) * Vector2::new(angle.cos(), angle.sin()); + self.line(start.x, start.y, end.x, end.y, Color::BLACK); + } + + self.end(); + } + + pub fn destroy(&self, gl: &glow::Context) { + self.pr.destroy(gl); + } +} diff --git a/optimize/Cargo.toml b/optimize/Cargo.toml index 5203d0f..71f8449 100644 --- a/optimize/Cargo.toml +++ b/optimize/Cargo.toml @@ -13,7 +13,7 @@ path = "bin/main.rs" [dependencies] anyhow = "1.0.77" -image = "0.24.7" +image = "0.25" serde = { version = "1.0", features = ["derive"] } [dev-dependencies]