diff --git a/Cargo.lock b/Cargo.lock index 02fefa7..6c048c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -112,6 +112,15 @@ dependencies = [ "winit", ] +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + [[package]] name = "adler2" version = "2.0.1" @@ -462,6 +471,27 @@ dependencies = [ "arrayvec", ] +[[package]] +name = "backtrace" +version = "0.3.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bit-set" version = "0.8.0" @@ -739,7 +769,7 @@ dependencies = [ "bitflags 1.3.2", "core-foundation 0.9.4", "core-graphics-types", - "foreign-types", + "foreign-types 0.5.0", "libc", ] @@ -1067,6 +1097,15 @@ dependencies = [ "serde", ] +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "endi" version = "1.1.0" @@ -1253,12 +1292,27 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foldhash" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared 0.1.1", +] + [[package]] name = "foreign-types" version = "0.5.0" @@ -1266,7 +1320,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" dependencies = [ "foreign-types-macros", - "foreign-types-shared", + "foreign-types-shared 0.3.1", ] [[package]] @@ -1280,6 +1334,12 @@ dependencies = [ "syn", ] +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "foreign-types-shared" version = "0.3.1" @@ -1295,6 +1355,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + [[package]] name = "futures-core" version = "0.3.31" @@ -1331,6 +1401,12 @@ dependencies = [ "syn", ] +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + [[package]] name = "futures-task" version = "0.3.31" @@ -1344,8 +1420,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", + "futures-io", "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", "slab", @@ -1394,6 +1473,12 @@ dependencies = [ "weezl", ] +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + [[package]] name = "gl_generator" version = "0.14.0" @@ -1483,6 +1568,25 @@ dependencies = [ "gl_generator", ] +[[package]] +name = "h2" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "half" version = "2.6.0" @@ -1527,6 +1631,124 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2 0.6.0", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + [[package]] name = "iana-time-zone" version = "0.1.63" @@ -1718,6 +1940,33 @@ dependencies = [ "syn", ] +[[package]] +name = "io-uring" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "libc", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "itertools" version = "0.12.1" @@ -1953,6 +2202,17 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "mio" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", +] + [[package]] name = "naga" version = "25.0.1" @@ -1977,6 +2237,23 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "ndk" version = "0.9.0" @@ -2392,12 +2669,65 @@ dependencies = [ "objc2-foundation 0.2.2", ] +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "openssl" +version = "0.10.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "foreign-types 0.3.2", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "orbclient" version = "0.3.48" @@ -2839,12 +3169,74 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" +[[package]] +name = "reqwest" +version = "0.12.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "rgb" version = "0.8.51" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a457e416a0f90d246a4c3288bd7a25b2304ca727f253f95be383dd17af56be8f" +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" + [[package]] name = "rustc-hash" version = "1.1.0" @@ -2883,6 +3275,39 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "rustls" +version = "0.23.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.21" @@ -2904,6 +3329,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -2929,6 +3363,29 @@ dependencies = [ "tiny-skia", ] +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.9.1", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "serde" version = "1.0.219" @@ -2981,6 +3438,18 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "shlex" version = "1.3.0" @@ -3083,6 +3552,26 @@ dependencies = [ "serde", ] +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "somewhatusefultool" version = "0.1.0" @@ -3095,6 +3584,7 @@ dependencies = [ "egui_extras", "egui_file", "image", + "reqwest", "serde", "serde_json", "thiserror 2.0.12", @@ -3142,6 +3632,12 @@ dependencies = [ "syn", ] +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "2.0.104" @@ -3153,6 +3649,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + [[package]] name = "synstructure" version = "0.13.2" @@ -3164,6 +3669,27 @@ dependencies = [ "syn", ] +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.9.1", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "system-deps" version = "6.2.2" @@ -3291,6 +3817,56 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tokio" +version = "1.46.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" +dependencies = [ + "backtrace", + "bytes", + "io-uring", + "libc", + "mio", + "pin-project-lite", + "slab", + "socket2 0.5.10", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml" version = "0.8.23" @@ -3325,6 +3901,51 @@ dependencies = [ "winnow", ] +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags 2.9.1", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + [[package]] name = "tracing" version = "0.1.41" @@ -3356,6 +3977,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "ttf-parser" version = "0.25.1" @@ -3406,6 +4033,12 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.4" @@ -3445,6 +4078,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version-compare" version = "0.2.0" @@ -3467,6 +4106,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -3916,6 +4564,17 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-registry" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + [[package]] name = "windows-result" version = "0.3.4" @@ -4522,6 +5181,12 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + [[package]] name = "zerotrie" version = "0.2.2" diff --git a/Cargo.toml b/Cargo.toml index 2a47beb..f605e1f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ thiserror = "2.0.12" egui_commonmark = { version = "0.21.1", features = ["embedded_image"] } walkdir = "2.5.0" uuid = { version = "1.17.0", features = ["v4"] } +reqwest = { version = "0.12.22", features = ["blocking", "json"] } [target.x86_64-pc-windows-gnu] diff --git a/project/assets/characters/nucleus.png b/project/assets/characters/nucleus.png new file mode 100644 index 0000000..517edd5 Binary files /dev/null and b/project/assets/characters/nucleus.png differ diff --git a/project/assets/characters/tayles.png b/project/assets/characters/tayles.png new file mode 100644 index 0000000..9b0ad7e Binary files /dev/null and b/project/assets/characters/tayles.png differ diff --git a/project/assets/characters/the chancellor.jpg b/project/assets/characters/the chancellor.jpg new file mode 100644 index 0000000..5d07a5c Binary files /dev/null and b/project/assets/characters/the chancellor.jpg differ diff --git a/project/assets/the prophet.png b/project/assets/characters/the order.png similarity index 100% rename from project/assets/the prophet.png rename to project/assets/characters/the order.png diff --git a/project/assets/characters/zxq5.png b/project/assets/characters/zxq5.png new file mode 100644 index 0000000..fec1bb4 Binary files /dev/null and b/project/assets/characters/zxq5.png differ diff --git a/project/context.json b/project/context.json new file mode 100644 index 0000000..552aae1 --- /dev/null +++ b/project/context.json @@ -0,0 +1,6 @@ +{ + "date": "2025-07-17", + "project_name": "New Project", + "project_author": "Your Name", + "project_description": "Description of your project" +} \ No newline at end of file diff --git a/project/documents/83592caa-f97d-427e-9d6a-50a586c30e6e.json b/project/documents/83592caa-f97d-427e-9d6a-50a586c30e6e.json new file mode 100644 index 0000000..ac8f1a5 --- /dev/null +++ b/project/documents/83592caa-f97d-427e-9d6a-50a586c30e6e.json @@ -0,0 +1,8 @@ +{ + "title": "test", + "id": "83592caa-f97d-427e-9d6a-50a586c30e6e", + "description": "ee", + "tags": [], + "content": "# Test project\n\n- this project is a test to ensure that this tool can be integrated with AI models correctly\n- I’m testing various prompts and parameters to evaluate its capabilities. The initial focus is on simple tasks like list generation, text summarization, and question answering. More complex scenarios involving code generation and creative writing will follow in subsequent phases. A key aspect of this test project involves documenting all interactions – both the prompts used and the AI’s responses – for later analysis. This allows us to identify patterns, biases, and areas where the tool can be improved. ", + "parent": null +} \ No newline at end of file diff --git a/project/objects/161227ef-ba29-41a7-b40a-ed4ac550a8ea.json b/project/objects/161227ef-ba29-41a7-b40a-ed4ac550a8ea.json new file mode 100644 index 0000000..d51c1de --- /dev/null +++ b/project/objects/161227ef-ba29-41a7-b40a-ed4ac550a8ea.json @@ -0,0 +1,23 @@ +{ + "id": "161227ef-ba29-41a7-b40a-ed4ac550a8ea", + "template_id": "69cf7e1d-96a1-4d2a-9f08-bd3386d4bc69", + "name": "nucleus", + "fields": { + "age": { + "Number": 0.0 + }, + "parent": { + "Link": "" + }, + "dob": { + "Date": "1970-01-01" + }, + "pfp": { + "Image": "characters/nucleus.png" + }, + "description": { + "MultiLine": "an AI" + } + }, + "tags": [] +} \ No newline at end of file diff --git a/project/objects/3ce0e977-9f65-4f4c-a036-67f3d5c25fdc.json b/project/objects/3ce0e977-9f65-4f4c-a036-67f3d5c25fdc.json new file mode 100644 index 0000000..a1d263b --- /dev/null +++ b/project/objects/3ce0e977-9f65-4f4c-a036-67f3d5c25fdc.json @@ -0,0 +1,23 @@ +{ + "id": "3ce0e977-9f65-4f4c-a036-67f3d5c25fdc", + "template_id": "69cf7e1d-96a1-4d2a-9f08-bd3386d4bc69", + "name": "ZXQ5", + "fields": { + "dob": { + "Date": "1970-01-01" + }, + "description": { + "MultiLine": "yes" + }, + "age": { + "Number": 19.1 + }, + "parent": { + "Link": "" + }, + "pfp": { + "Image": "characters/zxq5.png" + } + }, + "tags": [] +} \ No newline at end of file diff --git a/project/objects/57429207-5fc1-4bab-a524-c550773c3d45.json b/project/objects/57429207-5fc1-4bab-a524-c550773c3d45.json new file mode 100644 index 0000000..01f1e9a --- /dev/null +++ b/project/objects/57429207-5fc1-4bab-a524-c550773c3d45.json @@ -0,0 +1,23 @@ +{ + "id": "57429207-5fc1-4bab-a524-c550773c3d45", + "template_id": "69cf7e1d-96a1-4d2a-9f08-bd3386d4bc69", + "name": "Tayles", + "fields": { + "pfp": { + "Image": "characters/tayles.png" + }, + "parent": { + "Link": "" + }, + "description": { + "MultiLine": "trainspotter" + }, + "age": { + "Number": 17.5 + }, + "dob": { + "Date": "1970-01-01" + } + }, + "tags": [] +} \ No newline at end of file diff --git a/project/objects/8d76fdcd-0c3e-41a9-abc4-66fe21c0cb73.json b/project/objects/8d76fdcd-0c3e-41a9-abc4-66fe21c0cb73.json deleted file mode 100644 index 4757219..0000000 --- a/project/objects/8d76fdcd-0c3e-41a9-abc4-66fe21c0cb73.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "id": "8d76fdcd-0c3e-41a9-abc4-66fe21c0cb73", - "template_id": "d1223e6b-ade0-405a-8c3b-657c743a21cc", - "name": "Prophet", - "fields": { - "Age": { - "value": "" - }, - "Appearance": { - "value": "" - }, - "Species": { - "value": "" - }, - "DOB": { - "value": "" - }, - "PFP": { - "value": "./project/assets/the prophet.png" - } - }, - "tags": [] -} \ No newline at end of file diff --git a/project/objects/be24e58f-3f79-4c5a-9224-9037eea5f51f.json b/project/objects/be24e58f-3f79-4c5a-9224-9037eea5f51f.json new file mode 100644 index 0000000..14810cb --- /dev/null +++ b/project/objects/be24e58f-3f79-4c5a-9224-9037eea5f51f.json @@ -0,0 +1,25 @@ +{ + "id": "be24e58f-3f79-4c5a-9224-9037eea5f51f", + "template_id": "69cf7e1d-96a1-4d2a-9f08-bd3386d4bc69", + "name": "The Order", + "fields": { + "pfp": { + "Image": "characters/the order.png" + }, + "description": { + "MultiLine": "yes" + }, + "dob": { + "Date": "1970-01-29" + }, + "parent": { + "Link": "" + }, + "age": { + "Number": 20.6 + } + }, + "tags": [ + "bbeddabd-914c-4648-8262-bf14bfcf8fff" + ] +} \ No newline at end of file diff --git a/project/objects/deeaee92-bdec-4eb3-bb3b-ee760fc83d45.json b/project/objects/deeaee92-bdec-4eb3-bb3b-ee760fc83d45.json new file mode 100644 index 0000000..66d86f7 --- /dev/null +++ b/project/objects/deeaee92-bdec-4eb3-bb3b-ee760fc83d45.json @@ -0,0 +1,25 @@ +{ + "id": "deeaee92-bdec-4eb3-bb3b-ee760fc83d45", + "template_id": "69cf7e1d-96a1-4d2a-9f08-bd3386d4bc69", + "name": "The Chancellor", + "fields": { + "age": { + "Number": 37.0 + }, + "parent": { + "Link": "" + }, + "dob": { + "Date": "1970-01-01" + }, + "description": { + "MultiLine": "a tall ahh american" + }, + "pfp": { + "Image": "characters/the chancellor.jpg" + } + }, + "tags": [ + "bbeddabd-914c-4648-8262-bf14bfcf8fff" + ] +} \ No newline at end of file diff --git a/project/tags/bbeddabd-914c-4648-8262-bf14bfcf8fff.json b/project/tags/bbeddabd-914c-4648-8262-bf14bfcf8fff.json new file mode 100644 index 0000000..3fbb1e9 --- /dev/null +++ b/project/tags/bbeddabd-914c-4648-8262-bf14bfcf8fff.json @@ -0,0 +1,11 @@ +{ + "id": "bbeddabd-914c-4648-8262-bf14bfcf8fff", + "name": "American", + "description": "an american smh", + "color": [ + 0, + 32, + 207, + 255 + ] +} \ No newline at end of file diff --git a/project/templates/353649f9-e1f3-46d9-b723-8e56b510b2cc.json b/project/templates/353649f9-e1f3-46d9-b723-8e56b510b2cc.json new file mode 100644 index 0000000..0dd630b --- /dev/null +++ b/project/templates/353649f9-e1f3-46d9-b723-8e56b510b2cc.json @@ -0,0 +1,32 @@ +{ + "name": "Species", + "id": "353649f9-e1f3-46d9-b723-8e56b510b2cc", + "description": "A classification system for living or digital entities.", + "fields": [ + { + "name": "Diverged from", + "field_type": { + "Link": { + "template_id": null + } + }, + "required": false, + "on_preview": false, + "description": "did this diverge from another documented species?" + }, + { + "name": "Appearance / Features", + "field_type": "MultiLine", + "required": true, + "on_preview": true, + "description": "anatomy etc." + }, + { + "name": "behaviour", + "field_type": "MultiLine", + "required": true, + "on_preview": true, + "description": "aggressive, collaborative, etc.." + } + ] +} \ No newline at end of file diff --git a/project/templates/69cf7e1d-96a1-4d2a-9f08-bd3386d4bc69.json b/project/templates/69cf7e1d-96a1-4d2a-9f08-bd3386d4bc69.json new file mode 100644 index 0000000..18f8aaf --- /dev/null +++ b/project/templates/69cf7e1d-96a1-4d2a-9f08-bd3386d4bc69.json @@ -0,0 +1,46 @@ +{ + "name": "Character", + "id": "69cf7e1d-96a1-4d2a-9f08-bd3386d4bc69", + "description": "a character", + "fields": [ + { + "name": "description", + "field_type": "MultiLine", + "required": true, + "on_preview": true, + "description": "yes" + }, + { + "name": "age", + "field_type": "Number", + "required": true, + "on_preview": false, + "description": "yes" + }, + { + "name": "dob", + "field_type": "Date", + "required": true, + "on_preview": false, + "description": "yes" + }, + { + "name": "parent", + "field_type": { + "Link": { + "template_id": null + } + }, + "required": true, + "on_preview": false, + "description": "yes" + }, + { + "name": "pfp", + "field_type": "Image", + "required": true, + "on_preview": true, + "description": "yes" + } + ] +} \ No newline at end of file diff --git a/project/templates/d1223e6b-ade0-405a-8c3b-657c743a21cc.json b/project/templates/d1223e6b-ade0-405a-8c3b-657c743a21cc.json deleted file mode 100644 index c404cfe..0000000 --- a/project/templates/d1223e6b-ade0-405a-8c3b-657c743a21cc.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "Character", - "id": "d1223e6b-ade0-405a-8c3b-657c743a21cc", - "description": "desc", - "fields": [ - { - "name": "Appearance", - "field_type": "MultiLine", - "required": true, - "description": "app" - }, - { - "name": "Age", - "field_type": "Number", - "required": true, - "description": "age" - }, - { - "name": "DOB", - "field_type": "Date", - "required": false, - "description": "dob" - }, - { - "name": "PFP", - "field_type": "Image", - "required": true, - "description": "" - }, - { - "name": "Species", - "field_type": "Link", - "required": false, - "description": "" - } - ] -} \ No newline at end of file diff --git a/src/editors/asset_editor.rs b/src/editors/asset_editor.rs index d6ce031..2d16f4b 100644 --- a/src/editors/asset_editor.rs +++ b/src/editors/asset_editor.rs @@ -4,37 +4,51 @@ use crate::{PROJECT_FOLDER, util}; #[derive(Debug, Clone)] pub struct Asset { + pub new_name: String, pub name: String, - pub old_name: String, pub saved: bool, } impl Asset { pub fn open(name: String) -> Self { Self { - old_name: name.clone(), + new_name: name.clone(), name, - saved: false, + saved: true, } } pub fn save(&mut self) { - let old_path = Self::path(&self.old_name); - let new_path = Self::path(&self.name); + let old_path = Self::path(&self.name); + let new_path = Self::path(&self.new_name); + + println!("old_path: {old_path:?}"); + println!("new_path: {new_path:?}"); // move from src dir to name path - std::fs::rename(&old_path, &new_path).unwrap(); + if let Err(err) = std::fs::rename(&old_path, &new_path) { + match err.kind() { + std::io::ErrorKind::NotFound => { + let dir = new_path.parent().unwrap(); + if !dir.exists() { + std::fs::create_dir_all(dir).unwrap(); + } + std::fs::rename(&old_path, &new_path).unwrap(); + } + _ => panic!("Failed to rename file: {err}"), + } + } self.saved = true; - self.old_name = self.name.clone(); + self.name = self.new_name.clone(); } pub fn path(name: &str) -> std::path::PathBuf { - PROJECT_FOLDER.join("assets").join(format!("{name}.png")) + PROJECT_FOLDER.join("assets").join(name) } pub fn ui(&mut self, ui: &mut egui::Ui) { ui.vertical(|ui| { - util::saved_status(ui, self.saved, &self.name, &self.name); + util::saved_status(ui, self.saved, &self.name, &self.new_name); if ui.input(|i| i.key_pressed(egui::Key::S) && i.modifiers.ctrl) || ui.button("Save").clicked() @@ -46,7 +60,7 @@ impl Asset { ui.horizontal(|ui| { ui.strong("Filename:"); - if TextEdit::singleline(&mut self.name) + if TextEdit::singleline(&mut self.new_name) .desired_width(f32::INFINITY) .frame(false) .show(ui) diff --git a/src/editors/content_editor.rs b/src/editors/content_editor.rs index 9d9ffb2..3b853d0 100644 --- a/src/editors/content_editor.rs +++ b/src/editors/content_editor.rs @@ -1,8 +1,8 @@ -use egui::TextEdit; +use egui::{TextEdit, text}; use egui_commonmark::{CommonMarkCache, CommonMarkViewer}; use serde::{self, Deserialize, Serialize}; -use crate::{PROJECT_FOLDER, editors::tags::Tag, util}; +use crate::{PROJECT_FOLDER, editors::tags::Tag, llm_integration::content_llm::ai_enabled, util}; pub struct MainEditor { pub content: ContentSection, @@ -95,7 +95,7 @@ impl MainEditor { Self { content: ContentSection::new(), show_editor: false, // Start with editor hidden - show_preview: true, + show_preview: false, preview_cache: CommonMarkCache::default(), } } @@ -104,7 +104,7 @@ impl MainEditor { Self { content, show_editor: true, - show_preview: true, + show_preview: false, preview_cache: CommonMarkCache::default(), } } @@ -264,7 +264,7 @@ impl MainEditor { } fn editor_ui(&mut self, ui: &mut egui::Ui) { - egui::ScrollArea::both() + let response = egui::ScrollArea::both() .auto_shrink([false, false]) .id_salt("editor_scroll") .show(ui, |ui| { @@ -290,14 +290,39 @@ impl MainEditor { .hint_text("Type here...") .desired_width(max_width as f32); - if ui + let mut ctx_menu = false; + let response = ui .add_sized( egui::vec2(max_width as f32 - 30.0, ui.available_height()), text_edit, ) - .changed() - { - self.content.saved = false; + .on_hover_text("Right click to open context menu") + .context_menu(|ui| { + ctx_menu = true; + + ui.menu_button("AI Actions", |ui| { + ui.add_enabled_ui(ai_enabled(), |ui| { + if ui.button("Summarise").clicked() { + println!("Summarise"); + } + + if ui.button("Continue").clicked() { + let content = self.content.content.clone(); + let response = + crate::llm_integration::content_llm::continue_content( + &content, "", 1024, + ) + .unwrap(); + self.content.content.push_str(&response); + } + }); + }); + }); + + if let Some(response) = response { + if response.response.changed() || ctx_menu { + self.content.saved = false; + } } }); }); diff --git a/src/editors/context_editor.rs b/src/editors/context_editor.rs new file mode 100644 index 0000000..512307c --- /dev/null +++ b/src/editors/context_editor.rs @@ -0,0 +1,78 @@ +use std::io::Read; + +use chrono::NaiveDate; +use egui_extras::DatePickerButton; +use serde::{Deserialize, Serialize}; + +use crate::PROJECT_FOLDER; + +#[derive(Serialize, Deserialize)] +pub struct ProjectContext { + date: NaiveDate, + project_name: String, + project_author: String, + project_description: String, +} + +impl ProjectContext { + pub fn new() -> Self { + Self::default() + } + + pub fn load() -> Self { + let path = PROJECT_FOLDER.join("context.json"); + if let Ok(mut file) = std::fs::File::open(path) { + let mut contents = String::new(); + file.read_to_string(&mut contents).unwrap(); + if let Ok(proj) = serde_json::from_str(&contents) { + return proj; + } + } + Self::default() + } + + pub fn save(&self) { + let path = PROJECT_FOLDER.join("context.json"); + let content = serde_json::to_string_pretty(self).unwrap(); + std::fs::write(path, content).unwrap(); + } + + pub fn ui(&mut self, ui: &mut egui::Ui) { + // table + egui::Grid::new("context_editor") + .striped(true) + .num_columns(2) + .show(ui, |ui| { + ui.label("Project Name"); + ui.text_edit_singleline(&mut self.project_name); + + ui.end_row(); + + ui.label("Project Author"); + ui.text_edit_singleline(&mut self.project_author); + + ui.end_row(); + + ui.label("Project Description"); + ui.text_edit_singleline(&mut self.project_description); + + ui.end_row(); + + ui.label("Date"); + ui.add(DatePickerButton::new(&mut self.date)); + + ui.end_row(); + }); + } +} + +impl Default for ProjectContext { + fn default() -> Self { + Self { + date: chrono::Local::now().naive_local().into(), + project_name: "New Project".to_string(), + project_author: "Your Name".to_string(), + project_description: "Description of your project".to_string(), + } + } +} diff --git a/src/editors/mod.rs b/src/editors/mod.rs index 49d0d61..c0d9b9f 100644 --- a/src/editors/mod.rs +++ b/src/editors/mod.rs @@ -1,5 +1,6 @@ pub mod asset_editor; pub mod content_editor; +pub mod context_editor; pub mod note_editor; pub mod object_editor; pub mod tags; diff --git a/src/editors/object_editor.rs b/src/editors/object_editor.rs index ef9343c..79c10f7 100644 --- a/src/editors/object_editor.rs +++ b/src/editors/object_editor.rs @@ -1,23 +1,22 @@ use core::f32; -use std::path::Path; - -use chrono::NaiveDate; -use egui::{CollapsingHeader, Response, RichText, Sense, TextEdit, Ui, UiBuilder, vec2}; +use egui::{CollapsingHeader, RichText, Sense, TextEdit, Ui, UiBuilder, vec2}; use serde::{Deserialize, Serialize}; use crate::{ PROJECT_FOLDER, RightPanelContent, editors::{ tags::Tag, - template_editor::{FieldDefinition, FieldType, FieldValue, Template}, + template_editor::{FieldValue, Template}, }, util, }; +pub type ObjectId = String; + #[derive(Debug, Serialize, Deserialize)] pub struct ObjectInstance { // template info - pub id: String, + pub id: ObjectId, pub template_id: String, // instance info @@ -67,7 +66,7 @@ impl ObjectInstance { let mut fields = std::collections::HashMap::new(); for field in &template.fields { - fields.insert(field.name.clone(), FieldValue::default()); + fields.insert(field.name.clone(), FieldValue::from_type(&field.field_type)); } Self { @@ -106,7 +105,7 @@ impl ObjectInstance { ui: &mut Ui, template: &Template, right_panel: &mut Option, - objects: &mut Vec, + objects: &mut [ObjectInstance], ) { let _ = right_panel; if ui.input(|i| i.key_pressed(egui::Key::S) && i.modifiers.ctrl) { @@ -193,13 +192,7 @@ impl ObjectInstance { ui.separator(); - Self::render_field( - field_def, - field_value, - ui, - &mut self.saved, - objects, - ); + Self::render_field(field_value, ui, &mut self.saved, objects); ui.separator(); }); @@ -210,27 +203,25 @@ impl ObjectInstance { } fn render_field( - field_def: &FieldDefinition, field_value: &mut FieldValue, ui: &mut egui::Ui, saved: &mut bool, - objects: &mut Vec, + objects: &mut [ObjectInstance], ) { - match field_def.field_type { - FieldType::SingleLine => { - if TextEdit::singleline(&mut field_value.value) + match field_value { + FieldValue::SingleLine(value) => { + if TextEdit::singleline(value) .desired_width(f32::INFINITY) .frame(false) .show(ui) .response .changed() { - field_value.modified = true; *saved = false; } } - FieldType::MultiLine => { - if TextEdit::multiline(&mut field_value.value) + FieldValue::MultiLine(value) => { + if TextEdit::multiline(value) .desired_width(f32::INFINITY) .desired_rows(5) .frame(false) @@ -238,51 +229,39 @@ impl ObjectInstance { .response .changed() { - field_value.modified = true; *saved = false; } } - FieldType::Date => { - let date_str = &field_value.value; - let mut date = NaiveDate::parse_from_str(date_str, "%Y-%m-%d") - .unwrap_or_else(|_| chrono::Local::now().date_naive()); - - let response = ui.add(egui_extras::DatePickerButton::new(&mut date)); - + FieldValue::Date(value) => { + let response = ui.add(egui_extras::DatePickerButton::new(value)); if response.changed() { - field_value.value = date.format("%Y-%m-%d").to_string(); - field_value.modified = true; *saved = false; } } - FieldType::Number => { - let mut num = field_value.value.parse::().unwrap_or(0.0); - let response = ui.add(egui::DragValue::new(&mut num).speed(0.1)); - + FieldValue::Number(value) => { + let response = ui.add(egui::DragValue::new(value).speed(0.1)); if response.changed() { - field_value.value = num.to_string(); - field_value.modified = true; *saved = false; } } - FieldType::Image => { + FieldValue::Image(value) => { ui.scope_builder(UiBuilder::new().sense(Sense::HOVER), |ui| { let id = ui.make_persistent_id("is_hovered"); - let should_show = field_value.value.is_empty() + let should_show = value.is_empty() || ui.response().hovered() - || ui.memory(|mem| mem.data.get_temp(id).unwrap_or(false)); + || ui.memory(|mem| mem.data.get_temp(id).unwrap_or(false)) + || !PROJECT_FOLDER.join("assets").join(&value).exists(); // Simple path input for now if should_show { - let response = TextEdit::singleline(&mut field_value.value) - .hint_text("Path to image") + let response = TextEdit::singleline(value) + .hint_text("Asset name (ignore file extension)") .desired_width(f32::INFINITY) .frame(false) .show(ui) .response; if response.changed() { - field_value.modified = true; *saved = false; } @@ -292,10 +271,10 @@ impl ObjectInstance { } // If we have a valid path, try to display a preview - if !field_value.value.is_empty() { - if let Ok(bytes) = std::fs::read(&field_value.value) { - let path = PROJECT_FOLDER.join(&field_value.value); + if !value.is_empty() { + let path = PROJECT_FOLDER.join("assets").join(&value); + if let Ok(bytes) = std::fs::read(&path) { let image_source = egui::ImageSource::Bytes { uri: std::borrow::Cow::Owned(path.to_str().unwrap().to_string()), bytes: bytes.into(), @@ -307,10 +286,12 @@ impl ObjectInstance { } }); } - FieldType::Link => ObjectInstance::selector_ui(field_value, objects, ui, saved), - FieldType::Links => { - if ui.text_edit_singleline(&mut field_value.value).changed() { - field_value.modified = true; + FieldValue::Link(template_id) => { + ObjectInstance::selector_ui(template_id, objects, ui, saved) + } + FieldValue::Links(_template_ids) => { + let mut value = String::new(); + if ui.text_edit_singleline(&mut value).changed() { *saved = false; } } @@ -318,13 +299,13 @@ impl ObjectInstance { } fn selector_ui( - field_value: &mut FieldValue, - objects: &mut Vec, + selected: &mut ObjectId, + objects: &mut [ObjectInstance], ui: &mut egui::Ui, saved: &mut bool, ) { - if !field_value.value.is_empty() { - if let Ok(object) = ObjectInstance::load(&field_value.value) { + if !selected.is_empty() { + if let Ok(object) = ObjectInstance::load(selected) { ui.strong(&object.name); } } @@ -334,7 +315,7 @@ impl ObjectInstance { let ctx = ui.ctx(); let mut object_selection: usize = - ctx.memory_mut(|mem| *mem.data.get_temp_mut_or_default::(id)); + ctx.memory(|mem| mem.data.get_temp::(id).unwrap_or(0)); if objects.is_empty() { ui.label("No objects available"); @@ -348,15 +329,18 @@ impl ObjectInstance { }); } + let ctx = ui.ctx(); + ctx.memory_mut(|mem| { + *mem.data.get_temp_mut_or_default::(id) = object_selection; + }); + if ui.button("Set").clicked() && object_selection < objects.len() { - field_value.value = objects[object_selection].id.clone(); - field_value.modified = true; + *selected = objects[object_selection].id.clone(); *saved = false; } if ui.button("Remove").clicked() { - field_value.value.clear(); - field_value.modified = true; + *selected = String::new(); *saved = false; } }); diff --git a/src/editors/template_editor.rs b/src/editors/template_editor.rs index eacfc89..e6fa029 100644 --- a/src/editors/template_editor.rs +++ b/src/editors/template_editor.rs @@ -1,5 +1,6 @@ use core::fmt; +use chrono::NaiveDate; use egui::ScrollArea; use serde::{Deserialize, Serialize}; @@ -16,7 +17,7 @@ pub enum FieldType { MultiLine, Date, Number, - Link, + Link { template_id: Option }, Links, } @@ -34,17 +35,54 @@ impl FieldType { FieldType::MultiLine, FieldType::Date, FieldType::Number, - FieldType::Link, + FieldType::Link { template_id: None }, FieldType::Links, ] } } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum FieldValue { + Image(String), + SingleLine(String), + MultiLine(String), + Date(NaiveDate), + Number(f64), + Link(String), + Links(Vec), +} + +impl FieldValue { + pub fn from_type(_type: &FieldType) -> Self { + match _type { + FieldType::Image => Self::Image(String::new()), + FieldType::SingleLine => Self::SingleLine(String::new()), + FieldType::MultiLine => Self::MultiLine(String::new()), + FieldType::Date => Self::Date(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()), + FieldType::Number => Self::Number(0.0), + FieldType::Link { template_id: None } => Self::Link(String::new()), + FieldType::Link { + template_id: Some(template_id), + } => Self::Link(template_id.clone()), + FieldType::Links => Self::Links(Vec::new()), + } + } +} + +impl Default for FieldValue { + fn default() -> Self { + Self::SingleLine(String::new()) + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct FieldDefinition { pub name: String, pub field_type: FieldType, pub required: bool, + + #[serde(default)] + pub on_preview: bool, pub description: Option, } @@ -73,6 +111,9 @@ pub struct Template { #[serde(skip)] pub new_field_description: String, + + #[serde(skip)] + pub new_field_on_preview: bool, } impl fmt::Debug for Template { @@ -105,6 +146,7 @@ impl Clone for Template { new_field_type: FieldType::default(), new_field_required: false, new_field_description: "".to_string(), + new_field_on_preview: false, } } } @@ -123,6 +165,7 @@ impl Default for Template { new_field_type: FieldType::default(), new_field_required: false, new_field_description: "".to_string(), + new_field_on_preview: false, } } } @@ -325,6 +368,12 @@ impl Template { } ui.end_row(); + ui.label("On Preview:"); + if ui.checkbox(&mut field.on_preview, "").clicked() { + self.saved = false; + } + ui.end_row(); + ui.label("Description:"); if ui .text_edit_singleline( @@ -377,6 +426,10 @@ impl Template { ui.checkbox(&mut self.new_field_required, ""); ui.end_row(); + ui.label("On Preview:"); + ui.checkbox(&mut self.new_field_on_preview, ""); + ui.end_row(); + ui.label("Description:"); ui.text_edit_singleline(&mut self.new_field_description); ui.end_row(); @@ -385,6 +438,7 @@ impl Template { self.fields.push(FieldDefinition { name: self.new_field_name.clone(), field_type: self.new_field_type.clone(), + on_preview: self.new_field_on_preview, required: self.new_field_required, description: if self.new_field_description.is_empty() { None @@ -404,10 +458,3 @@ impl Template { }); } } - -#[derive(Debug, Clone, Serialize, Deserialize, Default)] -pub struct FieldValue { - pub value: String, - #[serde(skip)] - pub modified: bool, -} diff --git a/src/explorer.rs b/src/explorer.rs index cb2f656..34eb8ef 100644 --- a/src/explorer.rs +++ b/src/explorer.rs @@ -1,3 +1,5 @@ +use walkdir::{DirEntry, WalkDir}; + use crate::{ PROJECT_FOLDER, RightPanelContent, content_editor::MainEditor, @@ -14,7 +16,6 @@ pub struct Explorer { notes: Vec, documents: Vec, tags: Vec, - assets: Vec, } impl Explorer { @@ -25,7 +26,6 @@ impl Explorer { notes: Vec::new(), documents: Vec::new(), tags: Vec::new(), - assets: Vec::new(), } } @@ -43,161 +43,122 @@ impl Explorer { self.load_objects().expect("Failed to load objects"); self.load_notes().expect("Failed to load notes"); self.load_documents().expect("Failed to load documents"); - self.load_assets().expect("Failed to load assets"); + self.load_tags().expect("Failed to load tags"); ui.vertical(|ui| { - egui::collapsing_header::CollapsingState::load_with_default_open( - ui.ctx(), - ui.make_persistent_id("templates"), - true, - ) - .show_header(ui, |ui| { - ui.horizontal(|ui| { - ui.label("Templates"); - if ui.button("+").clicked() { - *to_load = Some(RightPanelContent::template(Some(Template::default()))); - } - }); - }) - .body(|ui| { - for template in &self.templates { - let id = ui.make_persistent_id(template.name.clone()); - egui::collapsing_header::CollapsingState::load_with_default_open( - ui.ctx(), - id, - true, - ) - .show_header(ui, |ui| { - // load the template - if ui.selectable_label(false, template.name.clone()).clicked() { - *to_load = Some(RightPanelContent::template(Some(template.clone()))); - } + self.render_templates(ui, to_load); + self.render_notes(ui, to_load); + self.render_doc_root(ui, load_doc); + self.render_tags(ui, to_load); + self.render_assets(ui, to_load); + }); + } - // create a new object based on this template - if ui.button("+").clicked() { - *to_load = Some(RightPanelContent::object(Some(ObjectInstance::new( - template, - )))); - } - }) - .body(|ui| { - for object in &self.objects { - if object.template_id == template.id { - ui.horizontal(|ui| { - ui.add_space(10.0); - - // load the object - if ui.selectable_label(false, &object.name).clicked() { - *to_load = - Some(RightPanelContent::object(Some(object.clone()))); - } - }); - } - } - }); + fn render_templates(&mut self, ui: &mut egui::Ui, to_load: &mut Option) { + egui::collapsing_header::CollapsingState::load_with_default_open( + ui.ctx(), + ui.make_persistent_id("templates"), + true, + ) + .show_header(ui, |ui| { + ui.horizontal(|ui| { + ui.label("Templates"); + if ui.button("+").clicked() { + *to_load = Some(RightPanelContent::template(Some(Template::default()))); } }); + }) + .body(|ui| { + for template in &self.templates { + let id = ui.make_persistent_id(template.name.clone()); + egui::collapsing_header::CollapsingState::load_with_default_open( + ui.ctx(), + id, + true, + ) + .show_header(ui, |ui| { + // load the template + if ui.selectable_label(false, template.name.clone()).clicked() { + *to_load = Some(RightPanelContent::template(Some(template.clone()))); + } - egui::collapsing_header::CollapsingState::load_with_default_open( - ui.ctx(), - ui.make_persistent_id("notes"), - true, - ) - .show_header(ui, |ui| { - ui.horizontal(|ui| { - ui.label("Notes"); + // create a new object based on this template if ui.button("+").clicked() { - *to_load = Some(RightPanelContent::note(Some(Note::default()))); + *to_load = Some(RightPanelContent::object(Some(ObjectInstance::new( + template, + )))); + } + }) + .body(|ui| { + for object in &self.objects { + if object.template_id == template.id { + ui.horizontal(|ui| { + ui.add_space(10.0); + + // load the object + if ui.selectable_label(false, &object.name).clicked() { + *to_load = + Some(RightPanelContent::object(Some(object.clone()))); + } + }); + } } }); - }) - .body(|ui| { - for note in &self.notes { - ui.horizontal(|ui| { - ui.add_space(10.0); + } + }); + } - // load the note - if ui.selectable_label(false, ¬e.name).clicked() { - *to_load = Some(RightPanelContent::note(Some(note.clone()))); - } - }); + fn render_notes(&mut self, ui: &mut egui::Ui, to_load: &mut Option) { + egui::collapsing_header::CollapsingState::load_with_default_open( + ui.ctx(), + ui.make_persistent_id("notes"), + true, + ) + .show_header(ui, |ui| { + ui.horizontal(|ui| { + ui.label("Notes"); + if ui.button("+").clicked() { + *to_load = Some(RightPanelContent::note(Some(Note::default()))); } }); - - egui::collapsing_header::CollapsingState::load_with_default_open( - ui.ctx(), - ui.make_persistent_id("projects"), - true, - ) - .show_header(ui, |ui| { + }) + .body(|ui| { + for note in &self.notes { ui.horizontal(|ui| { - ui.label("Projects"); - if ui.button("+").clicked() { - *load_doc = Some(MainEditor::open(ContentSection::new())); + ui.add_space(10.0); + + // load the note + if ui.selectable_label(false, ¬e.name).clicked() { + *to_load = Some(RightPanelContent::note(Some(note.clone()))); } }); - }) - .body(|ui| { - // Convert MainEditor vec to ContentSection vec - let content_sections: Vec = self - .documents - .iter() - .map(|doc| doc.content.clone()) - .collect(); + } + }); + } - Self::render_document_tree(ui, &content_sections, None, load_doc); - }); - - self.tags = Tag::load_all(); - - egui::collapsing_header::CollapsingState::load_with_default_open( - ui.ctx(), - ui.make_persistent_id("tags"), - true, - ) - .show_header(ui, |ui| { - ui.horizontal(|ui| { - ui.label("Tags"); - if ui.button("+").clicked() { - *to_load = Some(RightPanelContent::Tag(Tag::default())); - } - }); - }) - .body(|ui| { - for tag in &mut self.tags { - ui.horizontal(|ui| { - ui.add_space(10.0); - - // load the tag - if tag.list_ui(ui).clicked() { - *to_load = Some(RightPanelContent::Tag(tag.clone())); - } - }); + fn render_doc_root(&self, ui: &mut egui::Ui, load_doc: &mut Option) { + egui::collapsing_header::CollapsingState::load_with_default_open( + ui.ctx(), + ui.make_persistent_id("projects"), + true, + ) + .show_header(ui, |ui| { + ui.horizontal(|ui| { + ui.label("Projects"); + if ui.button("+").clicked() { + *load_doc = Some(MainEditor::open(ContentSection::new())); } }); + }) + .body(|ui| { + // Convert MainEditor vec to ContentSection vec + let content_sections: Vec = self + .documents + .iter() + .map(|doc| doc.content.clone()) + .collect(); - egui::collapsing_header::CollapsingState::load_with_default_open( - ui.ctx(), - ui.make_persistent_id("assets"), - true, - ) - .show_header(ui, |ui| { - ui.horizontal(|ui| { - ui.label("Assets"); - }); - }) - .body(|ui| { - for asset in &mut self.assets { - ui.horizontal(|ui| { - ui.add_space(10.0); - - // load the asset - if ui.selectable_label(false, &asset.name).clicked() { - *to_load = Some(RightPanelContent::Asset(Box::new(asset.clone()))); - } - }); - } - }); + Self::render_doc_branch(ui, &content_sections, None, load_doc); }); } @@ -209,7 +170,7 @@ impl Explorer { /// /// `load_doc` is a mutable reference to a `MainEditor`. When a document is clicked, it /// is loaded into the `MainEditor` and returned as `Some`. - fn render_document_tree( + fn render_doc_branch( ui: &mut egui::Ui, documents: &[ContentSection], parent_id: Option<&str>, @@ -243,15 +204,135 @@ impl Explorer { }) .body(|ui| { // recursive call to render the next level of documents - Self::render_document_tree(ui, documents, Some(&doc.id), load_doc); + Self::render_doc_branch(ui, documents, Some(&doc.id), load_doc); }); } } + fn render_tags(&mut self, ui: &mut egui::Ui, to_load: &mut Option) { + egui::collapsing_header::CollapsingState::load_with_default_open( + ui.ctx(), + ui.make_persistent_id("tags"), + true, + ) + .show_header(ui, |ui| { + ui.horizontal(|ui| { + ui.label("Tags"); + if ui.button("+").clicked() { + *to_load = Some(RightPanelContent::Tag(Tag::default())); + } + }); + }) + .body(|ui| { + for tag in &mut self.tags { + ui.horizontal(|ui| { + ui.add_space(10.0); + + // load the tag + if tag.list_ui(ui).clicked() { + *to_load = Some(RightPanelContent::Tag(tag.clone())); + } + }); + } + }); + } + + fn render_assets(&mut self, ui: &mut egui::Ui, to_load: &mut Option) { + egui::collapsing_header::CollapsingState::load_with_default_open( + ui.ctx(), + ui.make_persistent_id("assets"), + true, + ) + .show_header(ui, |ui| { + ui.horizontal(|ui| { + ui.label("Assets"); + }); + }) + .body(|ui| { + let mut entries: Vec<_> = WalkDir::new(PROJECT_FOLDER.join("assets")) + .min_depth(1) + .max_depth(1) // Only immediate children + .sort_by(|a, b| { + // Directories first, then files + let a_is_dir = a.file_type().is_dir(); + let b_is_dir = b.file_type().is_dir(); + if a_is_dir == b_is_dir { + a.file_name().cmp(b.file_name()) + } else if a_is_dir { + std::cmp::Ordering::Less + } else { + std::cmp::Ordering::Greater + } + }) + .into_iter() + .filter_map(Result::ok) + .collect(); + + for entry in entries { + self.render_entry(ui, to_load, &entry); + } + }); + } + + fn render_entry( + &mut self, + ui: &mut egui::Ui, + to_load: &mut Option, + entry: &DirEntry, + ) { + let file_type = entry.file_type(); + let is_dir = file_type.is_dir(); + let file_name = entry.file_name().to_string_lossy(); + let path = entry.path(); + + if is_dir { + let entries: Vec<_> = WalkDir::new(path) + .min_depth(1) + .max_depth(1) + .sort_by(|a, b| a.file_name().cmp(b.file_name())) + .into_iter() + .filter_map(Result::ok) + .collect(); + + egui::collapsing_header::CollapsingState::load_with_default_open( + ui.ctx(), + ui.make_persistent_id(&file_name), + false, + ) + .show_header(ui, |ui| { + ui.horizontal(|ui| { + ui.label(file_name); + let clicked = ui.button("+").on_hover_text("Add new item").clicked(); + }); + }) + .body(|ui| { + // recursive call to render the next level of documents + for entry in entries { + self.render_entry(ui, to_load, &entry); + } + }); + } else { + // Handle file + if ui + .selectable_label(false, format!("📄 {file_name}")) + .clicked() + { + // use asset::load to get the file at the path + let asset_path = path.strip_prefix(PROJECT_FOLDER.join("assets")).unwrap(); + let asset = Asset::open(asset_path.to_string_lossy().to_string()); + *to_load = Some(RightPanelContent::Asset(Box::new(asset))); + } + } + } + // load templates from the templates folder fn load_templates(&mut self) -> std::io::Result<()> { + let templates_folder = PROJECT_FOLDER.join("templates"); + if !templates_folder.exists() { + std::fs::create_dir_all(&templates_folder)?; + } let mut templates = Vec::new(); - for entry in std::fs::read_dir(PROJECT_FOLDER.join("templates")).unwrap() { + for entry in std::fs::read_dir(&templates_folder).unwrap() { let path = entry.unwrap().path(); match Template::load(path.file_stem().unwrap().to_str().unwrap()) { Ok(t) => templates.push(t), @@ -265,8 +346,12 @@ impl Explorer { // load objects from the objects folder fn load_objects(&mut self) -> std::io::Result<()> { + let objects_folder = PROJECT_FOLDER.join("objects"); + if !objects_folder.exists() { + std::fs::create_dir_all(&objects_folder)?; + } let mut objects = Vec::new(); - for entry in std::fs::read_dir(PROJECT_FOLDER.join("objects")).unwrap() { + for entry in std::fs::read_dir(&objects_folder).unwrap() { let path = entry.unwrap().path(); match ObjectInstance::load(path.file_stem().unwrap().to_str().unwrap()) { Ok(o) => objects.push(o), @@ -280,9 +365,13 @@ impl Explorer { // load notes from the notes folder fn load_notes(&mut self) -> std::io::Result<()> { + let notes_folder = PROJECT_FOLDER.join("notes"); + if !notes_folder.exists() { + std::fs::create_dir_all(¬es_folder)?; + } let mut notes = Vec::new(); - for entry in std::fs::read_dir(PROJECT_FOLDER.join("notes")).unwrap() { + for entry in std::fs::read_dir(¬es_folder).unwrap() { let path = entry.unwrap().path(); match Note::load(path.file_stem().unwrap().to_str().unwrap()) { Ok(note) => notes.push(note), @@ -297,9 +386,13 @@ impl Explorer { // load documents from the documents folder fn load_documents(&mut self) -> std::io::Result<()> { + let documents_folder = PROJECT_FOLDER.join("documents"); + if !documents_folder.exists() { + std::fs::create_dir_all(&documents_folder)?; + } let mut documents = Vec::new(); - for entry in std::fs::read_dir(PROJECT_FOLDER.join("documents")).unwrap() { + for entry in std::fs::read_dir(&documents_folder).unwrap() { let path = entry.unwrap().path(); match ContentSection::load(path.file_stem().unwrap().to_str().unwrap()) { Ok(document) => documents.push(MainEditor::open(document)), @@ -312,17 +405,8 @@ impl Explorer { Ok(()) } - fn load_assets(&mut self) -> std::io::Result<()> { - let mut assets = Vec::new(); - for entry in std::fs::read_dir(PROJECT_FOLDER.join("assets")).unwrap() { - let path = entry.unwrap().path(); - assets.push(Asset::open( - path.file_stem().unwrap().to_str().unwrap().to_string(), - )); - } - - self.assets = assets; - + fn load_tags(&mut self) -> std::io::Result<()> { + self.tags = Tag::load_all(); Ok(()) } } diff --git a/src/llm_integration/content_llm.rs b/src/llm_integration/content_llm.rs new file mode 100644 index 0000000..523c13c --- /dev/null +++ b/src/llm_integration/content_llm.rs @@ -0,0 +1,79 @@ +use serde::{Deserialize, Serialize}; + +pub fn continue_content( + context: &str, + instruction: &str, + max_tokens: usize, +) -> Result> { + let client = reqwest::blocking::Client::new(); + + let messages = vec![ + Message { + role: "system".to_string(), + content: " + Please generate content that is a direct continuation of the given text. + Your response should be a logical next step in the content and should not repeat any of the text from the instruction or the content. + Do not generate any text that is not a direct continuation of the content. + if extra instructions are provided, follow them exactly, otherwise continue the text in a logical way. + ".to_string(), + }, + Message { + role: "user".to_string(), + content: context.to_string(), + }, + Message { + role: "user".to_string(), + content: format!("Instructions: {instruction}"), + }, + ]; + + let request = ChatRequest { + messages, + temperature: 0.7, + }; + + let response = client + .post("http://localhost:1234/v1/chat/completions") + .json(&request) + .send()?; + + if !response.status().is_success() { + return Err(format!("Request failed: {}", response.text()?).into()); + } + + let response: ChatResponse = response.json()?; + + if let Some(choice) = response.choices.into_iter().next() { + Ok(choice.message.content) + } else { + Err("No response from model".into()) + } +} + +pub fn ai_enabled() -> bool { + let client = reqwest::blocking::Client::new(); + client.get("http://localhost:1234/v1/models").send().is_ok() +} + +// Simple request structure +#[derive(Serialize)] +struct ChatRequest { + messages: Vec, + temperature: f32, +} + +#[derive(Serialize, Deserialize, Debug)] +struct Message { + role: String, + content: String, +} + +#[derive(Deserialize, Debug)] +struct ChatResponse { + choices: Vec, +} + +#[derive(Deserialize, Debug)] +struct Choice { + message: Message, +} diff --git a/src/llm_integration/mod.rs b/src/llm_integration/mod.rs new file mode 100644 index 0000000..b1b3be3 --- /dev/null +++ b/src/llm_integration/mod.rs @@ -0,0 +1 @@ +pub mod content_llm; diff --git a/src/main.rs b/src/main.rs index 6178b78..9457cbd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,14 +4,15 @@ use egui::ScrollArea; mod editors; mod explorer; +mod llm_integration; mod scene; mod util; use crate::{ editors::{ - asset_editor::Asset, content_editor, note_editor, object_editor::ObjectInstance, tags::Tag, - template_editor::Template, + asset_editor::Asset, content_editor, context_editor::ProjectContext, note_editor, + object_editor::ObjectInstance, tags::Tag, template_editor::Template, }, explorer::Explorer, }; @@ -39,6 +40,7 @@ pub struct Interface { editor: content_editor::MainEditor, scene: scene::EditorScene, explorer: Explorer, + project: ProjectContext, } impl eframe::App for Interface { @@ -84,6 +86,7 @@ impl Interface { editor: content_editor::MainEditor::new(), scene: scene::EditorScene::new(), explorer: Explorer::new(), + project: ProjectContext::load(), } } @@ -183,28 +186,31 @@ impl Interface { }); } + // render main content area fn render_main_content(&mut self, ctx: &egui::Context) { self.editor.ui(ctx); - self.scene.ui(ctx); + self.scene.ui(ctx, &mut self.explorer.objects()); } + // configure appearance of UI elements fn configure_appearance(&self, ctx: &egui::Context) { + // configure appearance of UI elements let mut visuals = egui::Visuals::dark(); visuals.window_fill = egui::Color32::from_rgb(20, 20, 20); visuals.panel_fill = egui::Color32::from_rgb(20, 20, 20); visuals.widgets.inactive.fg_stroke = - egui::Stroke::from((2.0, egui::Color32::from_rgb(255, 255, 255))); + egui::Stroke::from((1.0, egui::Color32::from_rgb(255, 255, 255))); visuals.widgets.inactive.bg_stroke = - egui::Stroke::from((2.0, egui::Color32::from_rgb(60, 60, 60))); + egui::Stroke::from((1.0, egui::Color32::from_rgb(60, 60, 60))); visuals.widgets.inactive.corner_radius = egui::CornerRadius::from(4); visuals.widgets.inactive.bg_fill = egui::Color32::from_rgb(20, 20, 20); visuals.widgets.inactive.weak_bg_fill = egui::Color32::from_rgb(20, 20, 20); - visuals.widgets.inactive.expansion = 2.0; + visuals.widgets.inactive.expansion = 1.0; ctx.set_visuals(visuals); + // setup fonts. let mut fonts = egui::FontDefinitions::default(); - fonts.font_data.insert( "JetBrains Mono Nerd Font".to_string(), std::sync::Arc::new(egui::FontData::from_static(include_bytes!( @@ -233,3 +239,9 @@ impl Default for Interface { Self::new() } } + +impl Drop for Interface { + fn drop(&mut self) { + self.project.save(); + } +} diff --git a/src/scene.rs b/src/scene.rs index 9a89469..5e289b8 100644 --- a/src/scene.rs +++ b/src/scene.rs @@ -1,3 +1,13 @@ +use egui::{RichText, vec2}; + +use crate::{ + PROJECT_FOLDER, + editors::{ + object_editor::ObjectInstance, + template_editor::{FieldType, FieldValue, Template}, + }, +}; + pub struct EditorScene { rect: egui::Rect, } @@ -9,17 +19,127 @@ impl EditorScene { } } - pub fn ui(&mut self, ctx: &egui::Context) { + pub fn ui(&mut self, ctx: &egui::Context, objects: &mut [ObjectInstance]) { egui::CentralPanel::default() .frame(egui::Frame::NONE) .show(ctx, |ui| { egui::Scene::default() .zoom_range(0.1..=10.0) .show(ui, &mut self.rect, |ui| { - egui::Resize::default().auto_sized().show(ui, |ui| { - ui.group(|ui| { - ui.label("Scene"); - }); + ui.horizontal_wrapped(|ui| { + ui.set_max_width(5000.0); + // Group objects by their template_id + use std::collections::HashMap; + let mut objects_by_template: HashMap> = + HashMap::new(); + + for obj in objects { + objects_by_template + .entry(obj.template_id.clone()) + .or_default() + .push(obj); + } + + // For each template with objects, create cards + for (template_id, template_objects) in objects_by_template { + // Try to load the template to get field definitions + if let Ok(mut template) = Template::load(&template_id) { + for obj in template_objects { + // Create a card for each object + egui::Frame::group(ui.style()) + .fill(egui::Color32::from_rgba_premultiplied( + 30, 30, 30, 200, + )) + .corner_radius(4.0) + .show(ui, |ui| { + + ui.vertical(|ui| { + ui.set_max_width(512.0); + ui.set_min_width(512.0); + + // Object name as header + ui.heading(RichText::new(&obj.name).strong()); + + // Show fields with on_preview = true + template.fields.sort_by_key(|field| field.field_type != FieldType::Image); + for field_def in &template.fields { + if field_def.on_preview { + if let Some(field_value) = + obj.fields.get(&field_def.name) + { + ui.separator(); + + match field_value { + FieldValue::SingleLine( + text, + ) => { + ui.strong(&field_def.name); + ui.label(text); + } + FieldValue::MultiLine( + text, + ) => { + ui.strong(&field_def.name); + ui.label(text); + } + FieldValue::Number(n) => { + ui.strong(&field_def.name); + ui.label(n.to_string()); + } + FieldValue::Date(date) => { + ui.strong(&field_def.name); + ui.label( + date.format( + "%Y-%m-%d", + ) + .to_string(), + ); + } + FieldValue::Image(value) => { + if !value.is_empty() { + let path = PROJECT_FOLDER.join("assets").join(value); + + if let Ok(bytes) = std::fs::read(&path) { + let image_source = egui::ImageSource::Bytes { + uri: std::borrow::Cow::Owned(path.to_str().unwrap().to_string()), + bytes: bytes.into(), + }; + ui.add( + egui::Image::new(image_source).fit_to_exact_size(vec2(512.0, 512.0)), + ); + } + } + } + FieldValue::Link( + target_id, + ) => { + ui.strong(&field_def.name); + ui.label(format!( + "→ {target_id}" + )); + } + FieldValue::Links( + links, + ) => { + ui.strong(&field_def.name); + ui.label(format!( + "{} links", + links.len() + )); + } + } + + } + } + } + }); + }); + + // Add some spacing between cards + ui.add_space(8.0); + } + } + } }); }); });