Compare commits
9 Commits
762d481707
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
122904369b | ||
|
|
4d6e23980e | ||
|
|
165a9fc139 | ||
|
|
89f08ff580 | ||
|
|
7796045b13 | ||
|
|
2335d67f8d | ||
|
|
c595a3d879 | ||
|
|
6eb5da9599 | ||
|
|
772f1c634c |
630
Cargo.lock
generated
630
Cargo.lock
generated
@@ -92,8 +92,8 @@ dependencies = [
|
|||||||
"hashbrown",
|
"hashbrown",
|
||||||
"paste",
|
"paste",
|
||||||
"static_assertions",
|
"static_assertions",
|
||||||
"windows 0.58.0",
|
"windows",
|
||||||
"windows-core 0.58.0",
|
"windows-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -165,6 +165,12 @@ version = "0.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04"
|
checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "android-tzdata"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "android_system_properties"
|
name = "android_system_properties"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
@@ -275,6 +281,25 @@ dependencies = [
|
|||||||
"libloading",
|
"libloading",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ashpd"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6cbdf310d77fd3aaee6ea2093db7011dc2d35d2eb3481e5607f1f8d942ed99df"
|
||||||
|
dependencies = [
|
||||||
|
"async-fs",
|
||||||
|
"async-net",
|
||||||
|
"enumflags2",
|
||||||
|
"futures-channel",
|
||||||
|
"futures-util",
|
||||||
|
"rand 0.9.0",
|
||||||
|
"raw-window-handle",
|
||||||
|
"serde",
|
||||||
|
"serde_repr",
|
||||||
|
"url",
|
||||||
|
"zbus 5.5.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-broadcast"
|
name = "async-broadcast"
|
||||||
version = "0.7.2"
|
version = "0.7.2"
|
||||||
@@ -353,6 +378,17 @@ dependencies = [
|
|||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-net"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7"
|
||||||
|
dependencies = [
|
||||||
|
"async-io",
|
||||||
|
"blocking",
|
||||||
|
"futures-lite",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-process"
|
name = "async-process"
|
||||||
version = "2.3.0"
|
version = "2.3.0"
|
||||||
@@ -481,12 +517,6 @@ version = "1.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "beul"
|
|
||||||
version = "1.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d3c3baedf037b161e69da9200947329f52afa3b6e1cdbe6f344d6aa350fedd2f"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bit-set"
|
name = "bit-set"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
@@ -665,6 +695,29 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chrono"
|
||||||
|
version = "0.4.40"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
|
||||||
|
dependencies = [
|
||||||
|
"android-tzdata",
|
||||||
|
"iana-time-zone",
|
||||||
|
"js-sys",
|
||||||
|
"num-traits",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chrono-humanize"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "799627e6b4d27827a814e837b9d8a504832086081806d45b1afa34dc982b023b"
|
||||||
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clipboard-win"
|
name = "clipboard-win"
|
||||||
version = "5.4.0"
|
version = "5.4.0"
|
||||||
@@ -684,6 +737,12 @@ dependencies = [
|
|||||||
"unicode-width",
|
"unicode-width",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "color_quant"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colorchoice"
|
name = "colorchoice"
|
||||||
version = "1.0.3"
|
version = "1.0.3"
|
||||||
@@ -777,6 +836,15 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-channel"
|
||||||
|
version = "0.5.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-utils"
|
name = "crossbeam-utils"
|
||||||
version = "0.8.21"
|
version = "0.8.21"
|
||||||
@@ -836,6 +904,18 @@ version = "0.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
|
checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dispatch2"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1a0d569e003ff27784e0e14e4a594048698e0c0f0b66cabcb51511be55a7caa0"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.9.0",
|
||||||
|
"block2 0.6.0",
|
||||||
|
"libc",
|
||||||
|
"objc2 0.6.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "displaydoc"
|
name = "displaydoc"
|
||||||
version = "0.2.5"
|
version = "0.2.5"
|
||||||
@@ -939,6 +1019,14 @@ dependencies = [
|
|||||||
"profiling",
|
"profiling",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "egui-modal"
|
||||||
|
version = "0.6.0"
|
||||||
|
dependencies = [
|
||||||
|
"eframe",
|
||||||
|
"egui",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "egui-wgpu"
|
name = "egui-wgpu"
|
||||||
version = "0.31.1"
|
version = "0.31.1"
|
||||||
@@ -979,6 +1067,29 @@ dependencies = [
|
|||||||
"winit",
|
"winit",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "egui_alignments"
|
||||||
|
version = "0.3.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bf881b608a1a6050fa4f283834592bd814667de900acb4612a03b0da22f529b5"
|
||||||
|
dependencies = [
|
||||||
|
"egui",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "egui_extras"
|
||||||
|
version = "0.31.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "624659a2e972a46f4d5f646557906c55f1cd5a0836eddbe610fdf1afba1b4226"
|
||||||
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
|
"egui",
|
||||||
|
"enum-map",
|
||||||
|
"log",
|
||||||
|
"mime_guess2",
|
||||||
|
"profiling",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "egui_glow"
|
name = "egui_glow"
|
||||||
version = "0.31.1"
|
version = "0.31.1"
|
||||||
@@ -1012,6 +1123,27 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf"
|
checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "enum-map"
|
||||||
|
version = "2.7.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6866f3bfdf8207509a033af1a75a7b08abda06bbaaeae6669323fd5a097df2e9"
|
||||||
|
dependencies = [
|
||||||
|
"enum-map-derive",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "enum-map-derive"
|
||||||
|
version = "0.17.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "enumflags2"
|
name = "enumflags2"
|
||||||
version = "0.7.11"
|
version = "0.7.11"
|
||||||
@@ -1307,6 +1439,16 @@ dependencies = [
|
|||||||
"wasi 0.14.2+wasi-0.2.4",
|
"wasi 0.14.2+wasi-0.2.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gif"
|
||||||
|
version = "0.13.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2"
|
||||||
|
dependencies = [
|
||||||
|
"color_quant",
|
||||||
|
"weezl",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gl_generator"
|
name = "gl_generator"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
@@ -1477,6 +1619,30 @@ dependencies = [
|
|||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone"
|
||||||
|
version = "0.1.63"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
|
||||||
|
dependencies = [
|
||||||
|
"android_system_properties",
|
||||||
|
"core-foundation-sys",
|
||||||
|
"iana-time-zone-haiku",
|
||||||
|
"js-sys",
|
||||||
|
"log",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"windows-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone-haiku"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "icu_collections"
|
name = "icu_collections"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
@@ -1624,9 +1790,24 @@ checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"byteorder-lite",
|
"byteorder-lite",
|
||||||
|
"color_quant",
|
||||||
|
"gif",
|
||||||
|
"image-webp",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"png",
|
"png",
|
||||||
"tiff",
|
"tiff",
|
||||||
|
"zune-core",
|
||||||
|
"zune-jpeg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "image-webp"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b77d01e822461baa8409e156015a1d91735549f0f2c17691bd2d996bef238f7f"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder-lite",
|
||||||
|
"quick-error",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1654,6 +1835,12 @@ version = "1.70.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jiff"
|
name = "jiff"
|
||||||
version = "0.2.5"
|
version = "0.2.5"
|
||||||
@@ -1869,6 +2056,24 @@ dependencies = [
|
|||||||
"paste",
|
"paste",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mime"
|
||||||
|
version = "0.3.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mime_guess2"
|
||||||
|
version = "2.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f54028747dfea8e8bf00d3c2d4e83cf023c1accfd5d436335456e9864940cb85"
|
||||||
|
dependencies = [
|
||||||
|
"mime",
|
||||||
|
"phf",
|
||||||
|
"phf_shared",
|
||||||
|
"unicase",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.8.5"
|
version = "0.8.5"
|
||||||
@@ -1879,30 +2084,6 @@ dependencies = [
|
|||||||
"simd-adler32",
|
"simd-adler32",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "mundy"
|
|
||||||
version = "0.1.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a2831eeb1fbfc52f23d3a6bbc4a2f4457b57d016c9c9112cf6a2a4cffc3c4cc6"
|
|
||||||
dependencies = [
|
|
||||||
"async-io",
|
|
||||||
"beul",
|
|
||||||
"cfg-if",
|
|
||||||
"dispatch",
|
|
||||||
"futures-channel",
|
|
||||||
"futures-lite",
|
|
||||||
"objc2 0.6.0",
|
|
||||||
"objc2-app-kit 0.3.0",
|
|
||||||
"objc2-foundation 0.3.0",
|
|
||||||
"pin-project-lite",
|
|
||||||
"slab",
|
|
||||||
"wasm-bindgen",
|
|
||||||
"wasm-bindgen-futures",
|
|
||||||
"web-sys",
|
|
||||||
"windows 0.61.1",
|
|
||||||
"zbus 5.5.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "naga"
|
name = "naga"
|
||||||
version = "24.0.0"
|
version = "24.0.0"
|
||||||
@@ -2057,10 +2238,10 @@ dependencies = [
|
|||||||
"block2 0.5.1",
|
"block2 0.5.1",
|
||||||
"libc",
|
"libc",
|
||||||
"objc2 0.5.2",
|
"objc2 0.5.2",
|
||||||
"objc2-core-data 0.2.2",
|
"objc2-core-data",
|
||||||
"objc2-core-image 0.2.2",
|
"objc2-core-image",
|
||||||
"objc2-foundation 0.2.2",
|
"objc2-foundation 0.2.2",
|
||||||
"objc2-quartz-core 0.2.2",
|
"objc2-quartz-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2071,15 +2252,8 @@ checksum = "5906f93257178e2f7ae069efb89fbd6ee94f0592740b5f8a1512ca498814d0fb"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.0",
|
"bitflags 2.9.0",
|
||||||
"block2 0.6.0",
|
"block2 0.6.0",
|
||||||
"libc",
|
|
||||||
"objc2 0.6.0",
|
"objc2 0.6.0",
|
||||||
"objc2-cloud-kit 0.3.0",
|
|
||||||
"objc2-core-data 0.3.0",
|
|
||||||
"objc2-core-foundation",
|
|
||||||
"objc2-core-graphics",
|
|
||||||
"objc2-core-image 0.3.0",
|
|
||||||
"objc2-foundation 0.3.0",
|
"objc2-foundation 0.3.0",
|
||||||
"objc2-quartz-core 0.3.0",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2095,17 +2269,6 @@ dependencies = [
|
|||||||
"objc2-foundation 0.2.2",
|
"objc2-foundation 0.2.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "objc2-cloud-kit"
|
|
||||||
version = "0.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6c1948a9be5f469deadbd6bcb86ad7ff9e47b4f632380139722f7d9840c0d42c"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 2.9.0",
|
|
||||||
"objc2 0.6.0",
|
|
||||||
"objc2-foundation 0.3.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-contacts"
|
name = "objc2-contacts"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
@@ -2129,17 +2292,6 @@ dependencies = [
|
|||||||
"objc2-foundation 0.2.2",
|
"objc2-foundation 0.2.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "objc2-core-data"
|
|
||||||
version = "0.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1f860f8e841f6d32f754836f51e6bc7777cd7e7053cf18528233f6811d3eceb4"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 2.9.0",
|
|
||||||
"objc2 0.6.0",
|
|
||||||
"objc2-foundation 0.3.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-core-foundation"
|
name = "objc2-core-foundation"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
@@ -2150,18 +2302,6 @@ dependencies = [
|
|||||||
"objc2 0.6.0",
|
"objc2 0.6.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "objc2-core-graphics"
|
|
||||||
version = "0.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f8dca602628b65356b6513290a21a6405b4d4027b8b250f0b98dddbb28b7de02"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 2.9.0",
|
|
||||||
"objc2 0.6.0",
|
|
||||||
"objc2-core-foundation",
|
|
||||||
"objc2-io-surface",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-core-image"
|
name = "objc2-core-image"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
@@ -2174,16 +2314,6 @@ dependencies = [
|
|||||||
"objc2-metal",
|
"objc2-metal",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "objc2-core-image"
|
|
||||||
version = "0.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6ffa6bea72bf42c78b0b34e89c0bafac877d5f80bf91e159a5d96ea7f693ca56"
|
|
||||||
dependencies = [
|
|
||||||
"objc2 0.6.0",
|
|
||||||
"objc2-foundation 0.3.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-core-location"
|
name = "objc2-core-location"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
@@ -2220,19 +2350,6 @@ name = "objc2-foundation"
|
|||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3a21c6c9014b82c39515db5b396f91645182611c97d24637cf56ac01e5f8d998"
|
checksum = "3a21c6c9014b82c39515db5b396f91645182611c97d24637cf56ac01e5f8d998"
|
||||||
dependencies = [
|
|
||||||
"bitflags 2.9.0",
|
|
||||||
"block2 0.6.0",
|
|
||||||
"libc",
|
|
||||||
"objc2 0.6.0",
|
|
||||||
"objc2-core-foundation",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "objc2-io-surface"
|
|
||||||
version = "0.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "161a8b87e32610086e1a7a9e9ec39f84459db7b3a0881c1f16ca5a2605581c19"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.0",
|
"bitflags 2.9.0",
|
||||||
"objc2 0.6.0",
|
"objc2 0.6.0",
|
||||||
@@ -2276,17 +2393,6 @@ dependencies = [
|
|||||||
"objc2-metal",
|
"objc2-metal",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "objc2-quartz-core"
|
|
||||||
version = "0.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6fb3794501bb1bee12f08dcad8c61f2a5875791ad1c6f47faa71a0f033f20071"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 2.9.0",
|
|
||||||
"objc2 0.6.0",
|
|
||||||
"objc2-foundation 0.3.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-symbols"
|
name = "objc2-symbols"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
@@ -2306,13 +2412,13 @@ dependencies = [
|
|||||||
"bitflags 2.9.0",
|
"bitflags 2.9.0",
|
||||||
"block2 0.5.1",
|
"block2 0.5.1",
|
||||||
"objc2 0.5.2",
|
"objc2 0.5.2",
|
||||||
"objc2-cloud-kit 0.2.2",
|
"objc2-cloud-kit",
|
||||||
"objc2-core-data 0.2.2",
|
"objc2-core-data",
|
||||||
"objc2-core-image 0.2.2",
|
"objc2-core-image",
|
||||||
"objc2-core-location",
|
"objc2-core-location",
|
||||||
"objc2-foundation 0.2.2",
|
"objc2-foundation 0.2.2",
|
||||||
"objc2-link-presentation",
|
"objc2-link-presentation",
|
||||||
"objc2-quartz-core 0.2.2",
|
"objc2-quartz-core",
|
||||||
"objc2-symbols",
|
"objc2-symbols",
|
||||||
"objc2-uniform-type-identifiers",
|
"objc2-uniform-type-identifiers",
|
||||||
"objc2-user-notifications",
|
"objc2-user-notifications",
|
||||||
@@ -2477,6 +2583,50 @@ dependencies = [
|
|||||||
"sha2",
|
"sha2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "phf"
|
||||||
|
version = "0.11.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
|
||||||
|
dependencies = [
|
||||||
|
"phf_macros",
|
||||||
|
"phf_shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "phf_generator"
|
||||||
|
version = "0.11.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
|
||||||
|
dependencies = [
|
||||||
|
"phf_shared",
|
||||||
|
"rand 0.8.5",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "phf_macros"
|
||||||
|
version = "0.11.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216"
|
||||||
|
dependencies = [
|
||||||
|
"phf_generator",
|
||||||
|
"phf_shared",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"unicase",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "phf_shared"
|
||||||
|
version = "0.11.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
|
||||||
|
dependencies = [
|
||||||
|
"siphasher",
|
||||||
|
"unicase",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project"
|
name = "pin-project"
|
||||||
version = "1.1.10"
|
version = "1.1.10"
|
||||||
@@ -2554,6 +2704,12 @@ dependencies = [
|
|||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pollster"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "portable-atomic"
|
name = "portable-atomic"
|
||||||
version = "1.11.0"
|
version = "1.11.0"
|
||||||
@@ -2602,6 +2758,12 @@ version = "1.0.16"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d"
|
checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quick-error"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quick-xml"
|
name = "quick-xml"
|
||||||
version = "0.30.0"
|
version = "0.30.0"
|
||||||
@@ -2643,8 +2805,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"rand_chacha",
|
"rand_chacha 0.3.1",
|
||||||
"rand_core",
|
"rand_core 0.6.4",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94"
|
||||||
|
dependencies = [
|
||||||
|
"rand_chacha 0.9.0",
|
||||||
|
"rand_core 0.9.3",
|
||||||
|
"zerocopy 0.8.24",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2654,7 +2827,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ppv-lite86",
|
"ppv-lite86",
|
||||||
"rand_core",
|
"rand_core 0.6.4",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||||
|
dependencies = [
|
||||||
|
"ppv-lite86",
|
||||||
|
"rand_core 0.9.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2666,6 +2849,15 @@ dependencies = [
|
|||||||
"getrandom 0.2.15",
|
"getrandom 0.2.15",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.9.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom 0.3.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "raw-window-handle"
|
name = "raw-window-handle"
|
||||||
version = "0.6.2"
|
version = "0.6.2"
|
||||||
@@ -2740,7 +2932,7 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
"utfx",
|
"utfx",
|
||||||
"windows 0.58.0",
|
"windows",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2749,6 +2941,30 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832"
|
checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rfd"
|
||||||
|
version = "0.15.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "80c844748fdc82aae252ee4594a89b6e7ebef1063de7951545564cbc4e57075d"
|
||||||
|
dependencies = [
|
||||||
|
"ashpd",
|
||||||
|
"block2 0.6.0",
|
||||||
|
"dispatch2",
|
||||||
|
"js-sys",
|
||||||
|
"log",
|
||||||
|
"objc2 0.6.0",
|
||||||
|
"objc2-app-kit 0.3.0",
|
||||||
|
"objc2-core-foundation",
|
||||||
|
"objc2-foundation 0.3.0",
|
||||||
|
"pollster",
|
||||||
|
"raw-window-handle",
|
||||||
|
"urlencoding",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
|
"web-sys",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-hash"
|
name = "rustc-hash"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@@ -2787,6 +3003,12 @@ version = "1.0.20"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
|
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ryu"
|
||||||
|
version = "1.0.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "same-file"
|
name = "same-file"
|
||||||
version = "1.0.6"
|
version = "1.0.6"
|
||||||
@@ -2841,6 +3063,18 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_json"
|
||||||
|
version = "1.0.140"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"memchr",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_repr"
|
name = "serde_repr"
|
||||||
version = "0.1.20"
|
version = "0.1.20"
|
||||||
@@ -2895,6 +3129,12 @@ version = "0.3.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
|
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "siphasher"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slab"
|
name = "slab"
|
||||||
version = "0.4.9"
|
version = "0.4.9"
|
||||||
@@ -3202,14 +3442,26 @@ name = "tundlebool"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"chrono",
|
||||||
|
"chrono-humanize",
|
||||||
|
"crossbeam-channel",
|
||||||
"dirs",
|
"dirs",
|
||||||
"eframe",
|
"eframe",
|
||||||
"egui",
|
"egui",
|
||||||
|
"egui-modal",
|
||||||
|
"egui_alignments",
|
||||||
|
"egui_extras",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
|
"hex",
|
||||||
|
"image",
|
||||||
"keyvalues-parser",
|
"keyvalues-parser",
|
||||||
"mundy",
|
"log",
|
||||||
|
"regex",
|
||||||
"registry",
|
"registry",
|
||||||
|
"rfd",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"sha2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3244,6 +3496,12 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicase"
|
||||||
|
version = "2.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.18"
|
version = "1.0.18"
|
||||||
@@ -3277,8 +3535,15 @@ dependencies = [
|
|||||||
"form_urlencoded",
|
"form_urlencoded",
|
||||||
"idna",
|
"idna",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "urlencoding"
|
||||||
|
version = "2.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "utf16_iter"
|
name = "utf16_iter"
|
||||||
version = "1.0.5"
|
version = "1.0.5"
|
||||||
@@ -3645,7 +3910,7 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
"wgpu-types",
|
"wgpu-types",
|
||||||
"windows 0.58.0",
|
"windows",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3697,68 +3962,23 @@ version = "0.58.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6"
|
checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-core 0.58.0",
|
"windows-core",
|
||||||
"windows-targets 0.52.6",
|
"windows-targets 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows"
|
|
||||||
version = "0.61.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419"
|
|
||||||
dependencies = [
|
|
||||||
"windows-collections",
|
|
||||||
"windows-core 0.61.0",
|
|
||||||
"windows-future",
|
|
||||||
"windows-link",
|
|
||||||
"windows-numerics",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-collections"
|
|
||||||
version = "0.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
|
|
||||||
dependencies = [
|
|
||||||
"windows-core 0.61.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-core"
|
name = "windows-core"
|
||||||
version = "0.58.0"
|
version = "0.58.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99"
|
checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-implement 0.58.0",
|
"windows-implement",
|
||||||
"windows-interface 0.58.0",
|
"windows-interface",
|
||||||
"windows-result 0.2.0",
|
"windows-result",
|
||||||
"windows-strings 0.1.0",
|
"windows-strings",
|
||||||
"windows-targets 0.52.6",
|
"windows-targets 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-core"
|
|
||||||
version = "0.61.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980"
|
|
||||||
dependencies = [
|
|
||||||
"windows-implement 0.60.0",
|
|
||||||
"windows-interface 0.59.1",
|
|
||||||
"windows-link",
|
|
||||||
"windows-result 0.3.2",
|
|
||||||
"windows-strings 0.4.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-future"
|
|
||||||
version = "0.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7a1d6bbefcb7b60acd19828e1bc965da6fcf18a7e39490c5f8be71e54a19ba32"
|
|
||||||
dependencies = [
|
|
||||||
"windows-core 0.61.0",
|
|
||||||
"windows-link",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-implement"
|
name = "windows-implement"
|
||||||
version = "0.58.0"
|
version = "0.58.0"
|
||||||
@@ -3770,17 +3990,6 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-implement"
|
|
||||||
version = "0.60.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-interface"
|
name = "windows-interface"
|
||||||
version = "0.58.0"
|
version = "0.58.0"
|
||||||
@@ -3792,33 +4001,12 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-interface"
|
|
||||||
version = "0.59.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-link"
|
name = "windows-link"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
|
checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-numerics"
|
|
||||||
version = "0.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
|
|
||||||
dependencies = [
|
|
||||||
"windows-core 0.61.0",
|
|
||||||
"windows-link",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-result"
|
name = "windows-result"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@@ -3828,34 +4016,16 @@ dependencies = [
|
|||||||
"windows-targets 0.52.6",
|
"windows-targets 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-result"
|
|
||||||
version = "0.3.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252"
|
|
||||||
dependencies = [
|
|
||||||
"windows-link",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-strings"
|
name = "windows-strings"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
|
checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-result 0.2.0",
|
"windows-result",
|
||||||
"windows-targets 0.52.6",
|
"windows-targets 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-strings"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97"
|
|
||||||
dependencies = [
|
|
||||||
"windows-link",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.45.0"
|
version = "0.45.0"
|
||||||
@@ -4273,7 +4443,7 @@ dependencies = [
|
|||||||
"hex",
|
"hex",
|
||||||
"nix",
|
"nix",
|
||||||
"ordered-stream",
|
"ordered-stream",
|
||||||
"rand",
|
"rand 0.8.5",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_repr",
|
"serde_repr",
|
||||||
"sha1",
|
"sha1",
|
||||||
@@ -4494,6 +4664,21 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zune-core"
|
||||||
|
version = "0.4.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zune-jpeg"
|
||||||
|
version = "0.4.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "99a5bab8d7dedf81405c4bb1f2b83ea057643d9cb28778cea9eecddeedd2e028"
|
||||||
|
dependencies = [
|
||||||
|
"zune-core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zvariant"
|
name = "zvariant"
|
||||||
version = "4.2.0"
|
version = "4.2.0"
|
||||||
@@ -4517,6 +4702,7 @@ dependencies = [
|
|||||||
"enumflags2",
|
"enumflags2",
|
||||||
"serde",
|
"serde",
|
||||||
"static_assertions",
|
"static_assertions",
|
||||||
|
"url",
|
||||||
"winnow",
|
"winnow",
|
||||||
"zvariant_derive 5.4.0",
|
"zvariant_derive 5.4.0",
|
||||||
"zvariant_utils 3.2.0",
|
"zvariant_utils 3.2.0",
|
||||||
|
|||||||
28
Cargo.toml
28
Cargo.toml
@@ -8,8 +8,34 @@ egui = "0.31.1"
|
|||||||
eframe = "0.31.1"
|
eframe = "0.31.1"
|
||||||
env_logger = "0.11.8"
|
env_logger = "0.11.8"
|
||||||
anyhow = "1.0.97"
|
anyhow = "1.0.97"
|
||||||
mundy = "0.1.8"
|
|
||||||
serde = { version = "1.0.219", features = ["derive"] }
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
|
serde_json = "1.0.140"
|
||||||
registry = "1.3.0"
|
registry = "1.3.0"
|
||||||
dirs = "6.0.0"
|
dirs = "6.0.0"
|
||||||
keyvalues-parser = "0.2.0"
|
keyvalues-parser = "0.2.0"
|
||||||
|
crossbeam-channel = "0.5.14"
|
||||||
|
rfd = { version = "0.15.3", features = ["xdg-portal"] }
|
||||||
|
regex = "1.11.1"
|
||||||
|
sha2 = "0.10.8"
|
||||||
|
hex = "0.4.3"
|
||||||
|
egui_alignments = "0.3.4"
|
||||||
|
log = "0.4.27"
|
||||||
|
egui-modal = { path = "./egui-modal" }
|
||||||
|
chrono-humanize = "0.2.3"
|
||||||
|
chrono = "0.4.40"
|
||||||
|
egui_extras = "0.31.1"
|
||||||
|
image = { version = "0.25.6", default-features = false, features = ["gif", "jpeg", "png", "webp", "bmp"] }
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
members = ["egui-modal"]
|
||||||
|
|
||||||
|
[profile.special]
|
||||||
|
inherits = "release"
|
||||||
|
opt-level = 3
|
||||||
|
strip = true
|
||||||
|
lto = "fat"
|
||||||
|
debug-assertions = false
|
||||||
|
overflow-checks = false
|
||||||
|
panic = "abort"
|
||||||
|
incremental = false
|
||||||
|
debug = false
|
||||||
|
|||||||
12
egui-modal/.gitignore
vendored
Normal file
12
egui-modal/.gitignore
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# Generated by Cargo
|
||||||
|
# will have compiled files and executables
|
||||||
|
/target/
|
||||||
|
|
||||||
|
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||||
|
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||||
|
# Cargo.lock
|
||||||
|
|
||||||
|
# These are backup files generated by rustfmt
|
||||||
|
**/*.rs.bk
|
||||||
|
|
||||||
|
Cargo.lock
|
||||||
17
egui-modal/Cargo.toml
Normal file
17
egui-modal/Cargo.toml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
[package]
|
||||||
|
name = "egui-modal"
|
||||||
|
version = "0.6.0"
|
||||||
|
edition = "2021"
|
||||||
|
license = "MIT"
|
||||||
|
description = "a modal library for egui"
|
||||||
|
repository = "https://github.com/n00kii/egui-modal"
|
||||||
|
readme = "README.md"
|
||||||
|
authors = ["n00kii"]
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
egui = { version = "0.31.1", default-features = false }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
eframe = "0.31.1"
|
||||||
21
egui-modal/LICENSE
Normal file
21
egui-modal/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 n00kii
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
5
egui-modal/src/lib.rs
Normal file
5
egui-modal/src/lib.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#![warn(missing_docs)]
|
||||||
|
//! egui-modal
|
||||||
|
//! Modal library for [`egui`]
|
||||||
|
mod modal;
|
||||||
|
pub use modal::*;
|
||||||
629
egui-modal/src/modal.rs
Normal file
629
egui-modal/src/modal.rs
Normal file
@@ -0,0 +1,629 @@
|
|||||||
|
use egui::{
|
||||||
|
emath::{Align, Align2}, epaint::{Color32, Pos2}, Area, Button, Context, CornerRadius, Id, Layout, Response, RichText, Sense, Ui, WidgetText, Window
|
||||||
|
};
|
||||||
|
|
||||||
|
const ERROR_ICON_COLOR: Color32 = Color32::from_rgb(200, 90, 90);
|
||||||
|
const INFO_ICON_COLOR: Color32 = Color32::from_rgb(150, 200, 210);
|
||||||
|
const WARNING_ICON_COLOR: Color32 = Color32::from_rgb(230, 220, 140);
|
||||||
|
const SUCCESS_ICON_COLOR: Color32 = Color32::from_rgb(140, 230, 140);
|
||||||
|
|
||||||
|
const CAUTION_BUTTON_FILL: Color32 = Color32::from_rgb(87, 38, 34);
|
||||||
|
const SUGGESTED_BUTTON_FILL: Color32 = Color32::from_rgb(33, 54, 84);
|
||||||
|
const CAUTION_BUTTON_TEXT_COLOR: Color32 = Color32::from_rgb(242, 148, 148);
|
||||||
|
const SUGGESTED_BUTTON_TEXT_COLOR: Color32 = Color32::from_rgb(141, 182, 242);
|
||||||
|
|
||||||
|
const OVERLAY_COLOR: Color32 = Color32::from_rgba_premultiplied(0, 0, 0, 200);
|
||||||
|
|
||||||
|
/// The different styles a modal button can take.
|
||||||
|
pub enum ModalButtonStyle {
|
||||||
|
/// A normal [`egui`] button
|
||||||
|
None,
|
||||||
|
/// A button highlighted blue
|
||||||
|
Suggested,
|
||||||
|
/// A button highlighted red
|
||||||
|
Caution,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An icon. If used, it will be shown next to the body of
|
||||||
|
/// the modal.
|
||||||
|
#[derive(Clone, Default, PartialEq)]
|
||||||
|
pub enum Icon {
|
||||||
|
#[default]
|
||||||
|
/// An info icon
|
||||||
|
Info,
|
||||||
|
/// A warning icon
|
||||||
|
Warning,
|
||||||
|
/// A success icon
|
||||||
|
Success,
|
||||||
|
/// An error icon
|
||||||
|
Error,
|
||||||
|
/// A custom icon. The first field in the tuple is
|
||||||
|
/// the text of the icon, and the second field is the
|
||||||
|
/// color.
|
||||||
|
Custom((String, Color32)),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Icon {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Icon::Info => write!(f, "ℹ"),
|
||||||
|
Icon::Warning => write!(f, "⚠"),
|
||||||
|
Icon::Success => write!(f, "✔"),
|
||||||
|
Icon::Error => write!(f, "❗"),
|
||||||
|
Icon::Custom((icon_text, _)) => write!(f, "{icon_text}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
struct DialogData {
|
||||||
|
title: Option<String>,
|
||||||
|
body: Option<String>,
|
||||||
|
icon: Option<Icon>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used for constructing and opening a modal dialog. This can be used
|
||||||
|
/// to both set the title/body/icon of the modal and open it as a one-time call
|
||||||
|
/// (as opposed to a continous call in the update loop) at the same time.
|
||||||
|
/// Make sure to call `DialogBuilder::open` to actually open the dialog.
|
||||||
|
#[must_use = "use `DialogBuilder::open`"]
|
||||||
|
pub struct DialogBuilder {
|
||||||
|
data: DialogData,
|
||||||
|
modal_id: Id,
|
||||||
|
ctx: Context,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
enum ModalType {
|
||||||
|
Modal,
|
||||||
|
Dialog(DialogData),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
/// Information about the current state of the modal. (Pretty empty
|
||||||
|
/// right now but may be expanded upon in the future.)
|
||||||
|
struct ModalState {
|
||||||
|
is_open: bool,
|
||||||
|
was_outside_clicked: bool,
|
||||||
|
modal_type: ModalType,
|
||||||
|
last_frame_height: Option<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
/// Contains styling parameters for the modal, like body margin
|
||||||
|
/// and button colors.
|
||||||
|
pub struct ModalStyle {
|
||||||
|
/// The margin around the modal body. Only applies if using
|
||||||
|
/// [`.body()`]
|
||||||
|
pub body_margin: f32,
|
||||||
|
/// The margin around the container of the icon and body. Only
|
||||||
|
/// applies if using [`.frame()`]
|
||||||
|
pub frame_margin: f32,
|
||||||
|
/// The margin around the container of the icon. Only applies
|
||||||
|
/// if using [`.icon()`].
|
||||||
|
pub icon_margin: f32,
|
||||||
|
/// The size of any icons used in the modal
|
||||||
|
pub icon_size: f32,
|
||||||
|
/// The color of the overlay that dims the background
|
||||||
|
pub overlay_color: Color32,
|
||||||
|
|
||||||
|
/// The fill color for the caution button style
|
||||||
|
pub caution_button_fill: Color32,
|
||||||
|
/// The fill color for the suggested button style
|
||||||
|
pub suggested_button_fill: Color32,
|
||||||
|
|
||||||
|
/// The text color for the caution button style
|
||||||
|
pub caution_button_text_color: Color32,
|
||||||
|
/// The text color for the suggested button style
|
||||||
|
pub suggested_button_text_color: Color32,
|
||||||
|
|
||||||
|
/// The text of the acknowledgement button for dialogs
|
||||||
|
pub dialog_ok_text: String,
|
||||||
|
|
||||||
|
/// The color of the info icon
|
||||||
|
pub info_icon_color: Color32,
|
||||||
|
/// The color of the warning icon
|
||||||
|
pub warning_icon_color: Color32,
|
||||||
|
/// The color of the success icon
|
||||||
|
pub success_icon_color: Color32,
|
||||||
|
/// The color of the error icon
|
||||||
|
pub error_icon_color: Color32,
|
||||||
|
|
||||||
|
/// The default width of the modal
|
||||||
|
pub default_width: Option<f32>,
|
||||||
|
/// The default height of the modal
|
||||||
|
pub default_height: Option<f32>,
|
||||||
|
|
||||||
|
/// The alignment of text inside the body
|
||||||
|
pub body_alignment: Align,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModalState {
|
||||||
|
fn load(ctx: &Context, id: Id) -> Self {
|
||||||
|
ctx.data_mut(|d| d.get_temp(id).unwrap_or_default())
|
||||||
|
}
|
||||||
|
fn save(self, ctx: &Context, id: Id) {
|
||||||
|
ctx.data_mut(|d| d.insert_temp(id, self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ModalState {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
was_outside_clicked: false,
|
||||||
|
is_open: false,
|
||||||
|
modal_type: ModalType::Modal,
|
||||||
|
last_frame_height: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ModalStyle {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
body_margin: 5.,
|
||||||
|
icon_margin: 7.,
|
||||||
|
frame_margin: 2.,
|
||||||
|
icon_size: 30.,
|
||||||
|
overlay_color: OVERLAY_COLOR,
|
||||||
|
|
||||||
|
caution_button_fill: CAUTION_BUTTON_FILL,
|
||||||
|
suggested_button_fill: SUGGESTED_BUTTON_FILL,
|
||||||
|
|
||||||
|
caution_button_text_color: CAUTION_BUTTON_TEXT_COLOR,
|
||||||
|
suggested_button_text_color: SUGGESTED_BUTTON_TEXT_COLOR,
|
||||||
|
|
||||||
|
dialog_ok_text: "ok".to_string(),
|
||||||
|
|
||||||
|
info_icon_color: INFO_ICON_COLOR,
|
||||||
|
warning_icon_color: WARNING_ICON_COLOR,
|
||||||
|
success_icon_color: SUCCESS_ICON_COLOR,
|
||||||
|
error_icon_color: ERROR_ICON_COLOR,
|
||||||
|
|
||||||
|
default_height: None,
|
||||||
|
default_width: None,
|
||||||
|
|
||||||
|
body_alignment: Align::Min,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A [`Modal`] is created using [`Modal::new()`]. Make sure to use a `let` binding when
|
||||||
|
/// using [`Modal::new()`] to ensure you can call things like [`Modal::open()`] later on.
|
||||||
|
/// ```
|
||||||
|
/// let modal = Modal::new(ctx, "my_modal");
|
||||||
|
/// modal.show(|ui| {
|
||||||
|
/// ui.label("Hello world!")
|
||||||
|
/// });
|
||||||
|
/// if ui.button("modal").clicked() {
|
||||||
|
/// modal.open();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
/// Helper functions are also available to use that help apply margins based on the modal's
|
||||||
|
/// [`ModalStyle`]. They are not necessary to use, but may help reduce boilerplate.
|
||||||
|
/// ```
|
||||||
|
/// let other_modal = Modal::new(ctx, "another_modal");
|
||||||
|
/// other_modal.show(|ui| {
|
||||||
|
/// other_modal.frame(ui, |ui| {
|
||||||
|
/// other_modal.body(ui, "Hello again, world!");
|
||||||
|
/// });
|
||||||
|
/// other_modal.buttons(ui, |ui| {
|
||||||
|
/// other_modal.button(ui, "Close");
|
||||||
|
/// });
|
||||||
|
/// });
|
||||||
|
/// if ui.button("open the other modal").clicked() {
|
||||||
|
/// other_modal.open();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub struct Modal {
|
||||||
|
close_on_outside_click: bool,
|
||||||
|
style: ModalStyle,
|
||||||
|
ctx: Context,
|
||||||
|
id: Id,
|
||||||
|
window_id: Id,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ui_with_margin<R>(ui: &mut Ui, margin: f32, add_contents: impl FnOnce(&mut Ui) -> R) {
|
||||||
|
egui::Frame::NONE
|
||||||
|
.inner_margin(margin)
|
||||||
|
.show(ui, |ui| add_contents(ui));
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Modal {
|
||||||
|
/// Creates a new [`Modal`]. Can use constructor functions like [`Modal::with_style`]
|
||||||
|
/// to modify upon creation.
|
||||||
|
pub fn new(ctx: &Context, id_source: impl std::fmt::Display) -> Self {
|
||||||
|
let self_id = Id::new(id_source.to_string());
|
||||||
|
Self {
|
||||||
|
window_id: self_id.with("window"),
|
||||||
|
id: self_id,
|
||||||
|
style: ModalStyle::default(),
|
||||||
|
ctx: ctx.clone(),
|
||||||
|
close_on_outside_click: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_open_state(&self, is_open: bool) {
|
||||||
|
let mut modal_state = ModalState::load(&self.ctx, self.id);
|
||||||
|
modal_state.is_open = is_open;
|
||||||
|
modal_state.save(&self.ctx, self.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_outside_clicked(&self, was_clicked: bool) {
|
||||||
|
let mut modal_state = ModalState::load(&self.ctx, self.id);
|
||||||
|
modal_state.was_outside_clicked = was_clicked;
|
||||||
|
modal_state.save(&self.ctx, self.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Was the outer overlay clicked this frame?
|
||||||
|
pub fn was_outside_clicked(&self) -> bool {
|
||||||
|
let modal_state = ModalState::load(&self.ctx, self.id);
|
||||||
|
modal_state.was_outside_clicked
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Is the modal currently open?
|
||||||
|
pub fn is_open(&self) -> bool {
|
||||||
|
let modal_state = ModalState::load(&self.ctx, self.id);
|
||||||
|
modal_state.is_open
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Open the modal; make it visible. The modal prevents user input to other parts of the
|
||||||
|
/// application.
|
||||||
|
///
|
||||||
|
/// ⚠️ WARNING ⚠️: This function requires a write lock to the [`egui::Context`]. Using it within
|
||||||
|
/// closures within functions like [`egui::Ui::input_mut`] will result in a deadlock. [Tracking issue](https://github.com/n00kii/egui-modal/issues/15)
|
||||||
|
pub fn open(&self) {
|
||||||
|
self.set_open_state(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Close the modal so that it is no longer visible, allowing input to flow back into
|
||||||
|
/// the application.
|
||||||
|
///
|
||||||
|
/// ⚠️ WARNING ⚠️: This function requires a write lock to the [`egui::Context`]. Using it within
|
||||||
|
/// closures within functions like [`egui::Ui::input_mut`] will result in a deadlock. [Tracking issue](https://github.com/n00kii/egui-modal/issues/15)
|
||||||
|
pub fn close(&self) {
|
||||||
|
self.set_open_state(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If set to `true`, the modal will close itself if the user clicks outside on the modal window
|
||||||
|
/// (onto the overlay).
|
||||||
|
pub fn with_close_on_outside_click(mut self, do_close_on_click_ouside: bool) -> Self {
|
||||||
|
self.close_on_outside_click = do_close_on_click_ouside;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Change the [`ModalStyle`] of the modal upon creation.
|
||||||
|
pub fn with_style(mut self, style: &ModalStyle) -> Self {
|
||||||
|
self.style = style.clone();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function for styling the title of the modal.
|
||||||
|
/// ```
|
||||||
|
/// let modal = Modal::new(ctx, "modal");
|
||||||
|
/// modal.show(|ui| {
|
||||||
|
/// modal.title(ui, "my title");
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
pub fn title(&self, ui: &mut Ui, text: impl Into<RichText>) {
|
||||||
|
let text: RichText = text.into();
|
||||||
|
ui.vertical_centered(|ui| {
|
||||||
|
ui.heading(text);
|
||||||
|
});
|
||||||
|
ui.separator();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function for styling the icon of the modal.
|
||||||
|
/// ```
|
||||||
|
/// let modal = Modal::new(ctx, "modal");
|
||||||
|
/// modal.show(|ui| {
|
||||||
|
/// modal.frame(ui, |ui| {
|
||||||
|
/// modal.icon(ui, Icon::Info);
|
||||||
|
/// });
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
pub fn icon(&self, ui: &mut Ui, icon: Icon) {
|
||||||
|
let color = match icon {
|
||||||
|
Icon::Info => self.style.info_icon_color,
|
||||||
|
Icon::Warning => self.style.warning_icon_color,
|
||||||
|
Icon::Success => self.style.success_icon_color,
|
||||||
|
Icon::Error => self.style.error_icon_color,
|
||||||
|
Icon::Custom((_, color)) => color,
|
||||||
|
};
|
||||||
|
let text = RichText::new(icon.to_string())
|
||||||
|
.color(color)
|
||||||
|
.size(self.style.icon_size);
|
||||||
|
ui_with_margin(ui, self.style.icon_margin, |ui| {
|
||||||
|
ui.add(egui::Label::new(text));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function for styling the container the of body and icon.
|
||||||
|
/// ```
|
||||||
|
/// let modal = Modal::new(ctx, "modal");
|
||||||
|
/// modal.show(|ui| {
|
||||||
|
/// modal.title(ui, "my title");
|
||||||
|
/// modal.frame(ui, |ui| {
|
||||||
|
/// // inner modal contents go here
|
||||||
|
/// });
|
||||||
|
/// modal.buttons(ui, |ui| {
|
||||||
|
/// // button contents go here
|
||||||
|
/// });
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
pub fn frame<R>(&self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) {
|
||||||
|
let last_frame_height = ModalState::load(&self.ctx, self.id)
|
||||||
|
.last_frame_height
|
||||||
|
.unwrap_or_default();
|
||||||
|
let default_height = self.style.default_height.unwrap_or_default();
|
||||||
|
let space_height = ((default_height - last_frame_height) * 0.5).max(0.);
|
||||||
|
ui.with_layout(
|
||||||
|
Layout::top_down(Align::Center).with_cross_align(Align::Center),
|
||||||
|
|ui| {
|
||||||
|
ui_with_margin(ui, self.style.frame_margin, |ui| {
|
||||||
|
if space_height > 0. {
|
||||||
|
ui.add_space(space_height);
|
||||||
|
add_contents(ui);
|
||||||
|
ui.add_space(space_height);
|
||||||
|
} else {
|
||||||
|
add_contents(ui);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function that should be used when using a body and icon together.
|
||||||
|
/// ```
|
||||||
|
/// let modal = Modal::new(ctx, "modal");
|
||||||
|
/// modal.show(|ui| {
|
||||||
|
/// modal.frame(ui, |ui| {
|
||||||
|
/// modal.body_and_icon(ui, "my modal body", Icon::Warning);
|
||||||
|
/// });
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
pub fn body_and_icon(&self, ui: &mut Ui, text: impl Into<WidgetText>, icon: Icon) {
|
||||||
|
egui::Grid::new(self.id).num_columns(2).show(ui, |ui| {
|
||||||
|
self.icon(ui, icon);
|
||||||
|
self.body(ui, text);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function for styling the body of the modal.
|
||||||
|
/// ```
|
||||||
|
/// let modal = Modal::new(ctx, "modal");
|
||||||
|
/// modal.show(|ui| {
|
||||||
|
/// modal.frame(ui, |ui| {
|
||||||
|
/// modal.body(ui, "my modal body");
|
||||||
|
/// });
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
pub fn body(&self, ui: &mut Ui, text: impl Into<WidgetText>) {
|
||||||
|
let text: WidgetText = text.into();
|
||||||
|
ui.with_layout(Layout::top_down(self.style.body_alignment), |ui| {
|
||||||
|
ui_with_margin(ui, self.style.body_margin, |ui| {
|
||||||
|
ui.label(text);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function for styling the button container of the modal.
|
||||||
|
/// ```
|
||||||
|
/// let modal = Modal::new(ctx, "modal");
|
||||||
|
/// modal.show(|ui| {
|
||||||
|
/// modal.buttons(ui, |ui| {
|
||||||
|
/// modal.button(ui, "my modal button");
|
||||||
|
/// });
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
pub fn buttons<R>(&self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) {
|
||||||
|
ui.separator();
|
||||||
|
ui.with_layout(Layout::right_to_left(Align::Min), add_contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function for creating a normal button for the modal.
|
||||||
|
/// Automatically closes the modal on click.
|
||||||
|
pub fn button(&self, ui: &mut Ui, text: impl Into<WidgetText>) -> Response {
|
||||||
|
self.styled_button(ui, text, ModalButtonStyle::None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function for creating a "cautioned" button for the modal.
|
||||||
|
/// Automatically closes the modal on click.
|
||||||
|
pub fn caution_button(&self, ui: &mut Ui, text: impl Into<WidgetText>) -> Response {
|
||||||
|
self.styled_button(ui, text, ModalButtonStyle::Caution)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function for creating a "suggested" button for the modal.
|
||||||
|
/// Automatically closes the modal on click.
|
||||||
|
pub fn suggested_button(&self, ui: &mut Ui, text: impl Into<WidgetText>) -> Response {
|
||||||
|
self.styled_button(ui, text, ModalButtonStyle::Suggested)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn styled_button(
|
||||||
|
&self,
|
||||||
|
ui: &mut Ui,
|
||||||
|
text: impl Into<WidgetText>,
|
||||||
|
button_style: ModalButtonStyle,
|
||||||
|
) -> Response {
|
||||||
|
let button = match button_style {
|
||||||
|
ModalButtonStyle::Suggested => {
|
||||||
|
let text: WidgetText = text.into().color(self.style.suggested_button_text_color);
|
||||||
|
Button::new(text).fill(self.style.suggested_button_fill)
|
||||||
|
}
|
||||||
|
ModalButtonStyle::Caution => {
|
||||||
|
let text: WidgetText = text.into().color(self.style.caution_button_text_color);
|
||||||
|
Button::new(text).fill(self.style.caution_button_fill)
|
||||||
|
}
|
||||||
|
ModalButtonStyle::None => Button::new(text.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let response = ui.add(button);
|
||||||
|
if response.clicked() {
|
||||||
|
self.close()
|
||||||
|
}
|
||||||
|
response
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The ui contained in this function will be shown within the modal window. The modal will only actually show
|
||||||
|
/// when [`Modal::open`] is used.
|
||||||
|
pub fn show<R>(&self, add_contents: impl FnOnce(&mut Ui) -> R) {
|
||||||
|
let mut modal_state = ModalState::load(&self.ctx, self.id);
|
||||||
|
self.set_outside_clicked(false);
|
||||||
|
if modal_state.is_open {
|
||||||
|
let ctx_clone = self.ctx.clone();
|
||||||
|
let area_resp = Area::new(self.id)
|
||||||
|
.interactable(true)
|
||||||
|
.fixed_pos(Pos2::ZERO)
|
||||||
|
.show(&self.ctx, |ui: &mut Ui| {
|
||||||
|
let screen_rect = ui.ctx().input(|i| i.screen_rect);
|
||||||
|
let area_response = ui.allocate_response(screen_rect.size(), Sense::click());
|
||||||
|
// let current_focus = area_response.ctx.memory().focus().clone();
|
||||||
|
// let top_layer = area_response.ctx.memory().layer_ids().last();
|
||||||
|
// if let Some(focus) = current_focus {
|
||||||
|
// area_response.ctx.memory().surrender_focus(focus)
|
||||||
|
// }
|
||||||
|
if area_response.clicked() {
|
||||||
|
self.set_outside_clicked(true);
|
||||||
|
if self.close_on_outside_click {
|
||||||
|
self.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ui.painter()
|
||||||
|
.rect_filled(screen_rect, CornerRadius::ZERO, self.style.overlay_color);
|
||||||
|
});
|
||||||
|
|
||||||
|
ctx_clone.move_to_top(area_resp.response.layer_id);
|
||||||
|
|
||||||
|
// the below lines of code addresses a weird problem where if the default_height changes, egui doesnt respond unless
|
||||||
|
// it's a different window id
|
||||||
|
let mut window_id = self
|
||||||
|
.style
|
||||||
|
.default_width
|
||||||
|
.map_or(self.window_id, |w| self.window_id.with(w.to_string()));
|
||||||
|
|
||||||
|
window_id = self
|
||||||
|
.style
|
||||||
|
.default_height
|
||||||
|
.map_or(window_id, |h| window_id.with(h.to_string()));
|
||||||
|
|
||||||
|
let mut window = Window::new("")
|
||||||
|
.id(window_id)
|
||||||
|
.open(&mut modal_state.is_open)
|
||||||
|
.title_bar(false)
|
||||||
|
.anchor(Align2::CENTER_CENTER, [0., 0.])
|
||||||
|
.resizable(false);
|
||||||
|
|
||||||
|
let recalculating_height =
|
||||||
|
self.style.default_height.is_some() && modal_state.last_frame_height.is_none();
|
||||||
|
|
||||||
|
if let Some(default_height) = self.style.default_height {
|
||||||
|
window = window.default_height(default_height);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(default_width) = self.style.default_width {
|
||||||
|
window = window.default_width(default_width);
|
||||||
|
}
|
||||||
|
|
||||||
|
let response = window.show(&ctx_clone, add_contents);
|
||||||
|
|
||||||
|
if let Some(inner_response) = response {
|
||||||
|
ctx_clone.move_to_top(inner_response.response.layer_id);
|
||||||
|
if recalculating_height {
|
||||||
|
let mut modal_state = ModalState::load(&self.ctx, self.id);
|
||||||
|
modal_state.last_frame_height = Some(inner_response.response.rect.height());
|
||||||
|
modal_state.save(&self.ctx, self.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Open the modal as a dialog. This is a shorthand way of defining a [`Modal::show`] once,
|
||||||
|
/// for example, if a function returns an `Error`. This should be used in conjunction with
|
||||||
|
/// [`Modal::show_dialog`].
|
||||||
|
#[deprecated(since = "0.3.0", note = "use `Modal::dialog`")]
|
||||||
|
pub fn open_dialog(
|
||||||
|
&self,
|
||||||
|
title: Option<impl std::fmt::Display>,
|
||||||
|
body: Option<impl std::fmt::Display>,
|
||||||
|
icon: Option<Icon>,
|
||||||
|
) {
|
||||||
|
let modal_data = DialogData {
|
||||||
|
title: title.map(|s| s.to_string()),
|
||||||
|
body: body.map(|s| s.to_string()),
|
||||||
|
icon,
|
||||||
|
};
|
||||||
|
let mut modal_state = ModalState::load(&self.ctx, self.id);
|
||||||
|
modal_state.modal_type = ModalType::Dialog(modal_data);
|
||||||
|
modal_state.is_open = true;
|
||||||
|
modal_state.save(&self.ctx, self.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a `DialogBuilder` for this modal. Make sure to use `DialogBuilder::open`
|
||||||
|
/// to open the dialog.
|
||||||
|
pub fn dialog(&self) -> DialogBuilder {
|
||||||
|
DialogBuilder {
|
||||||
|
data: DialogData::default(),
|
||||||
|
modal_id: self.id.clone(),
|
||||||
|
ctx: self.ctx.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Needed in order to use [`Modal::dialog`]. Make sure this is called every frame, as
|
||||||
|
/// it renders the necessary ui when using a modal as a dialog.
|
||||||
|
pub fn show_dialog(&mut self) {
|
||||||
|
let modal_state = ModalState::load(&self.ctx, self.id);
|
||||||
|
if let ModalType::Dialog(modal_data) = modal_state.modal_type {
|
||||||
|
self.close_on_outside_click = true;
|
||||||
|
self.show(|ui| {
|
||||||
|
if let Some(title) = modal_data.title {
|
||||||
|
self.title(ui, title)
|
||||||
|
}
|
||||||
|
self.frame(ui, |ui| {
|
||||||
|
if modal_data.body.is_none() {
|
||||||
|
if let Some(icon) = modal_data.icon {
|
||||||
|
self.icon(ui, icon)
|
||||||
|
}
|
||||||
|
} else if modal_data.icon.is_none() {
|
||||||
|
if let Some(body) = modal_data.body {
|
||||||
|
self.body(ui, body)
|
||||||
|
}
|
||||||
|
} else if modal_data.icon.is_some() && modal_data.icon.is_some() {
|
||||||
|
self.body_and_icon(ui, modal_data.body.unwrap(), modal_data.icon.unwrap())
|
||||||
|
}
|
||||||
|
});
|
||||||
|
self.buttons(ui, |ui| {
|
||||||
|
ui.with_layout(Layout::top_down_justified(Align::Center), |ui| {
|
||||||
|
self.button(ui, &self.style.dialog_ok_text)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DialogBuilder {
|
||||||
|
/// Construct this dialog with the given title.
|
||||||
|
pub fn with_title(mut self, title: impl std::fmt::Display) -> Self {
|
||||||
|
self.data.title = Some(title.to_string());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Construct this dialog with the given body.
|
||||||
|
pub fn with_body(mut self, body: impl std::fmt::Display) -> Self {
|
||||||
|
self.data.body = Some(body.to_string());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Construct this dialog with the given icon.
|
||||||
|
pub fn with_icon(mut self, icon: Icon) -> Self {
|
||||||
|
self.data.icon = Some(icon);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Open the dialog.
|
||||||
|
///
|
||||||
|
/// ⚠️ WARNING ⚠️: This function requires a write lock to the [`egui::Context`]. Using it within
|
||||||
|
/// closures within functions like [`egui::Ui::input_mut`] will result in a deadlock. [Tracking issue](https://github.com/n00kii/egui-modal/issues/15)
|
||||||
|
pub fn open(self) {
|
||||||
|
let mut modal_state = ModalState::load(&self.ctx, self.modal_id);
|
||||||
|
modal_state.modal_type = ModalType::Dialog(self.data);
|
||||||
|
modal_state.is_open = true;
|
||||||
|
modal_state.save(&self.ctx, self.modal_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
12
flake.lock
generated
12
flake.lock
generated
@@ -19,11 +19,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1743568003,
|
"lastModified": 1744502386,
|
||||||
"narHash": "sha256-ZID5T65E8ruHqWRcdvZLsczWDOAWIE7om+vQOREwiX0=",
|
"narHash": "sha256-QAd1L37eU7ktL2WeLLLTmI6P9moz9+a/ONO8qNBYJgM=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "b7ba7f9f45c5cd0d8625e9e217c28f8eb6a19a76",
|
"rev": "f6db44a8daa59c40ae41ba6e5823ec77fe0d2124",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -61,11 +61,11 @@
|
|||||||
"nixpkgs": "nixpkgs_2"
|
"nixpkgs": "nixpkgs_2"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1743561237,
|
"lastModified": 1744513456,
|
||||||
"narHash": "sha256-dd97LXek202OWmUXvKYFdYWj0jHrn3p+L5Ojh1SEOqs=",
|
"narHash": "sha256-NLVluTmK8d01Iz+WyarQhwFcXpHEwU7m5hH3YQQFJS0=",
|
||||||
"owner": "oxalica",
|
"owner": "oxalica",
|
||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"rev": "1de27ae43712a971c1da100dcd84386356f03ec7",
|
"rev": "730fd8e82799219754418483fabe1844262fd1e2",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
30
flake.nix
30
flake.nix
@@ -15,7 +15,7 @@
|
|||||||
inherit overlays;
|
inherit overlays;
|
||||||
};
|
};
|
||||||
native-deps = with pkgs; [
|
native-deps = with pkgs; [
|
||||||
pkg-config pipewire lld clang libclang alsa-lib openssl zlib libxkbcommon libGL wayland mangohud
|
pkg-config pipewire lld clang libclang alsa-lib openssl zlib libxkbcommon libGL wayland mangohud upx
|
||||||
];
|
];
|
||||||
in
|
in
|
||||||
pkgs.mkShell {
|
pkgs.mkShell {
|
||||||
@@ -42,36 +42,10 @@
|
|||||||
buildInputs = with pkgs; [
|
buildInputs = with pkgs; [
|
||||||
(rust-bin.stable.latest.default.override {
|
(rust-bin.stable.latest.default.override {
|
||||||
extensions = [ "rust-src" ];
|
extensions = [ "rust-src" ];
|
||||||
targets = ["aarch64-apple-darwin" ];
|
targets = ["aarch64-apple-darwin" "x86_64-apple-darwin"];
|
||||||
})
|
})
|
||||||
cargo-bloat
|
cargo-bloat
|
||||||
] ++ native-deps;
|
] ++ native-deps;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
# flake-utils.lib.eachDefaultSystem (system:
|
|
||||||
# let
|
|
||||||
# overlays = [ (import rust-overlay) ];
|
|
||||||
# pkgs = import nixpkgs {
|
|
||||||
# inherit system overlays;
|
|
||||||
# };
|
|
||||||
# native-deps = with pkgs; [
|
|
||||||
# pkg-config pipewire lld clang libclang alsa-lib openssl zlib libxkbcommon libGL wayland mangohud
|
|
||||||
# ];
|
|
||||||
# in
|
|
||||||
# with pkgs;
|
|
||||||
# {
|
|
||||||
# devShells.default = mkShell {
|
|
||||||
# buildInputs = with pkgs; [
|
|
||||||
# (rust-bin.stable.latest.default.override {
|
|
||||||
# extensions = [ "rust-src" ];
|
|
||||||
# targets = ["x86_64-unknown-linux-gnu" "x86_64-pc-windows-gnu"];
|
|
||||||
# })
|
|
||||||
# cargo-bloat
|
|
||||||
# pkgs.pkgsCross.mingwW64.buildPackages.gcc
|
|
||||||
# ] ++ native-deps;
|
|
||||||
# LD_LIBRARY_PATH = (lib.makeLibraryPath native-deps);
|
|
||||||
# CARGO_TARGET_X86_64_PC_WINDOWS_GNU_RUSTFLAGS = "-L native=${pkgs.pkgsCross.mingwW64.windows.pthreads}/lib";
|
|
||||||
# };
|
|
||||||
# }
|
|
||||||
# );
|
|
||||||
}
|
}
|
||||||
|
|||||||
160
src/app_logic.rs
Normal file
160
src/app_logic.rs
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
use std::{path::PathBuf, sync::Arc};
|
||||||
|
|
||||||
|
use crossbeam_channel::Receiver;
|
||||||
|
use egui::mutex::Mutex;
|
||||||
|
use log::{debug, info};
|
||||||
|
|
||||||
|
use crate::{config::{self, ConfigProvider}, mod_builder::ModConfiguration, omori_locator};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum UiState {
|
||||||
|
Loading,
|
||||||
|
KeyRequired(String),
|
||||||
|
PickGame(Result<PathBuf, String>, Vec<(PathBuf, u64)>),
|
||||||
|
PickPlaytest(Vec<(PathBuf, u64)>),
|
||||||
|
Configure(ModConfiguration),
|
||||||
|
Error(String)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct UiStateHolder {
|
||||||
|
pub state: Arc<Mutex<UiState>>
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum UiEvent {
|
||||||
|
SetKey(Vec<u8>),
|
||||||
|
UsePath(PathBuf),
|
||||||
|
UseSteamPath,
|
||||||
|
UseConfiguration(ModConfiguration)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AppThread {
|
||||||
|
ui_state: UiStateHolder,
|
||||||
|
context: egui::Context,
|
||||||
|
ui_event_channel: Receiver<UiEvent>,
|
||||||
|
decryption_key: Vec<u8>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppThread {
|
||||||
|
fn commit(&self, s: UiState) {
|
||||||
|
let mut state = self.ui_state.state.lock();
|
||||||
|
*state = s;
|
||||||
|
drop(state);
|
||||||
|
self.context.request_repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_key(&self, reason: String) -> anyhow::Result<Vec<u8>> {
|
||||||
|
self.commit(UiState::KeyRequired(reason));
|
||||||
|
loop {
|
||||||
|
match self.ui_event_channel.recv()? {
|
||||||
|
UiEvent::SetKey(key) => {
|
||||||
|
self.commit(UiState::Loading);
|
||||||
|
return Ok(key);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pick_game(&self, provider: &mut ConfigProvider) -> anyhow::Result<PathBuf> {
|
||||||
|
let steam_location = match omori_locator::get_omori_path() {
|
||||||
|
Ok(l) => Ok(l),
|
||||||
|
Err(e) => Err(String::from(format!("{:#}", e)))
|
||||||
|
};
|
||||||
|
|
||||||
|
let location_history = provider.get_game_paths()?;
|
||||||
|
|
||||||
|
self.commit(UiState::PickGame(steam_location.clone(), location_history.clone()));
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match self.ui_event_channel.recv()? {
|
||||||
|
UiEvent::UsePath(path) => {
|
||||||
|
provider.set_game_path_used(&path)?;
|
||||||
|
self.commit(UiState::Loading);
|
||||||
|
return Ok(path);
|
||||||
|
},
|
||||||
|
UiEvent::UseSteamPath => {
|
||||||
|
self.commit(UiState::Loading);
|
||||||
|
return Ok(steam_location.expect("The steam location was not set even if the UI told us to use it. This should never happen"));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pick_playtest(&self, provider: &mut ConfigProvider) -> anyhow::Result<PathBuf> {
|
||||||
|
let playtest_history = provider.get_playtest_paths()?;
|
||||||
|
self.commit(UiState::PickPlaytest(playtest_history.clone()));
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match self.ui_event_channel.recv()? {
|
||||||
|
UiEvent::UsePath(path) => {
|
||||||
|
provider.set_playtest_path_used(&path)?;
|
||||||
|
self.commit(UiState::Loading);
|
||||||
|
return Ok(path);
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn configure_bundling(&self, provider: &mut ConfigProvider, path: &PathBuf) -> anyhow::Result<ModConfiguration> {
|
||||||
|
let configuration = provider.get_configuration_for_playtest(path);
|
||||||
|
self.commit(UiState::Configure(configuration));
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match self.ui_event_channel.recv()? {
|
||||||
|
UiEvent::UseConfiguration(config) => {
|
||||||
|
provider.set_configuration_for_playtest(&path, &config)?;
|
||||||
|
self.commit(UiState::Loading);
|
||||||
|
return Ok(config);
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create(state_holder: &UiStateHolder, context: &egui::Context, ui_event_channel: &Receiver<UiEvent>) -> AppThread {
|
||||||
|
AppThread {
|
||||||
|
ui_state: state_holder.clone(),
|
||||||
|
context: context.clone(),
|
||||||
|
ui_event_channel: ui_event_channel.clone(),
|
||||||
|
decryption_key: Vec::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn real_main(&mut self) -> anyhow::Result<()> {
|
||||||
|
debug!("I am ALIVE! HAHAHAHA!");
|
||||||
|
self.commit(UiState::Loading);
|
||||||
|
|
||||||
|
let mut config_provider = config::ConfigProvider::new()?;
|
||||||
|
|
||||||
|
self.decryption_key = match config_provider.get_key() {
|
||||||
|
Some(key) => key,
|
||||||
|
None => match omori_locator::get_omori_key() {
|
||||||
|
Ok(key) => key,
|
||||||
|
Err(reason) => self.get_key(format!("{:#}", reason))?,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
config_provider.set_key(&self.decryption_key)?;
|
||||||
|
|
||||||
|
let game_path = self.pick_game(&mut config_provider)?;
|
||||||
|
info!("Will use {:?}", game_path);
|
||||||
|
|
||||||
|
let playtest_path = self.pick_playtest(&mut config_provider)?;
|
||||||
|
info!("Playtest {:?}", playtest_path);
|
||||||
|
|
||||||
|
let config = self.configure_bundling(&mut config_provider, &playtest_path)?;
|
||||||
|
info!("Config {:?}", config);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main(&mut self) {
|
||||||
|
match self.real_main() {
|
||||||
|
Ok(_) => {},
|
||||||
|
Err(e) => self.commit(UiState::Error(format!("{:#}", e))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
145
src/config.rs
Normal file
145
src/config.rs
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
use std::{fs::{self, File}, path::PathBuf, time::SystemTime};
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
|
use egui::ahash::HashMap;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde;
|
||||||
|
|
||||||
|
use crate::{mod_builder::ModConfiguration, omori_locator};
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
struct Config {
|
||||||
|
key: Option<Vec<u8>>,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
recently_used_game_paths: HashMap<PathBuf, u64>,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
recently_used_playtest_paths: HashMap<PathBuf, u64>,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
configuration_history: HashMap<PathBuf, ModConfiguration>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Config {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
key: Default::default(),
|
||||||
|
recently_used_game_paths: Default::default(),
|
||||||
|
recently_used_playtest_paths: Default::default(),
|
||||||
|
configuration_history: Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ConfigProvider {
|
||||||
|
config: Config,
|
||||||
|
base_path: PathBuf
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConfigProvider {
|
||||||
|
pub fn new() -> anyhow::Result<ConfigProvider> {
|
||||||
|
let mut base_path = dirs::preference_dir().with_context(|| "Failed to find your preference directory")?;
|
||||||
|
base_path.push(env!("CARGO_PKG_NAME"));
|
||||||
|
|
||||||
|
fs::create_dir_all(&base_path)?;
|
||||||
|
|
||||||
|
base_path.push("config.json");
|
||||||
|
|
||||||
|
if !fs::exists(&base_path)? {
|
||||||
|
let mut fd = File::create(&base_path)?;
|
||||||
|
serde_json::to_writer(&mut fd, &Config::default())?;
|
||||||
|
drop(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut fd = File::open(&base_path)?;
|
||||||
|
let config: Config = serde_json::from_reader(&mut fd)?;
|
||||||
|
drop(fd);
|
||||||
|
|
||||||
|
Ok(ConfigProvider { config, base_path })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn commit(&mut self) -> anyhow::Result<()> {
|
||||||
|
let mut fd = File::create(&self.base_path)?;
|
||||||
|
serde_json::to_writer(&mut fd, &self.config)?;
|
||||||
|
drop(fd);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_key(&self) -> Option<Vec<u8>> {
|
||||||
|
return self.config.key.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_key(&mut self, key: &Vec<u8>) -> anyhow::Result<()> {
|
||||||
|
self.config.key = Some(key.clone());
|
||||||
|
self.commit()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_game_paths(&self) -> anyhow::Result<Vec<(PathBuf, u64)>> {
|
||||||
|
let mut paths: Vec<(&PathBuf, &u64)> = self.config.recently_used_game_paths.iter().collect();
|
||||||
|
paths.sort_by(|a, b| a.1.cmp(b.1).reverse());
|
||||||
|
|
||||||
|
Ok(paths
|
||||||
|
.iter()
|
||||||
|
.filter(|p| omori_locator::validate_omori_installation(p.0))
|
||||||
|
.map(|v| (v.0.clone(), *v.1))
|
||||||
|
.collect()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_game_path_used(&mut self, which: &PathBuf) -> anyhow::Result<()> {
|
||||||
|
let current_time = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?.as_secs();
|
||||||
|
self.config.recently_used_game_paths.insert(which.clone(), current_time);
|
||||||
|
self.config.recently_used_game_paths = self.config.recently_used_game_paths
|
||||||
|
.clone()
|
||||||
|
.into_iter()
|
||||||
|
.filter(|p| omori_locator::validate_omori_installation(&p.0))
|
||||||
|
.collect();
|
||||||
|
self.commit()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_playtest_paths(&self) -> anyhow::Result<Vec<(PathBuf, u64)>> {
|
||||||
|
let mut paths: Vec<(&PathBuf, &u64)> = self.config.recently_used_playtest_paths.iter().collect();
|
||||||
|
paths.sort_by(|a, b| a.1.cmp(b.1).reverse());
|
||||||
|
|
||||||
|
Ok(paths
|
||||||
|
.iter()
|
||||||
|
.filter(|p| omori_locator::validate_playtest(p.0))
|
||||||
|
.map(|v| (v.0.clone(), *v.1))
|
||||||
|
.collect()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_playtest_path_used(&mut self, which: &PathBuf) -> anyhow::Result<()> {
|
||||||
|
let current_time = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?.as_secs();
|
||||||
|
self.config.recently_used_playtest_paths.insert(which.clone(), current_time);
|
||||||
|
self.config.recently_used_playtest_paths = self.config.recently_used_playtest_paths
|
||||||
|
.clone()
|
||||||
|
.into_iter()
|
||||||
|
.filter(|p| omori_locator::validate_playtest(&p.0))
|
||||||
|
.collect();
|
||||||
|
self.commit()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_configuration_for_playtest(&self, which: &PathBuf) -> ModConfiguration {
|
||||||
|
match self.config.configuration_history.get(which) {
|
||||||
|
Some(v) => v.clone(),
|
||||||
|
None => Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_configuration_for_playtest(&mut self, which: &PathBuf, config: &ModConfiguration) -> anyhow::Result<()> {
|
||||||
|
self.config.configuration_history.insert(which.clone(), config.clone());
|
||||||
|
self.commit()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/keyhash
Normal file
1
src/keyhash
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<EFBFBD><EFBFBD>
|
||||||
416
src/main.rs
416
src/main.rs
@@ -1,27 +1,60 @@
|
|||||||
mod omori_locator;
|
mod omori_locator;
|
||||||
|
mod app_logic;
|
||||||
|
mod config;
|
||||||
|
mod mod_builder;
|
||||||
|
|
||||||
use std::{thread, time::Duration};
|
use std::{process::exit, sync::Arc, thread, time::SystemTime, default::Default};
|
||||||
|
|
||||||
|
use app_logic::{AppThread, UiEvent, UiState, UiStateHolder};
|
||||||
|
use crossbeam_channel::{Receiver, Sender};
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
use egui::{RichText, ThemePreference};
|
use egui::{mutex::Mutex, Align, Layout, RichText, TextStyle, ThemePreference};
|
||||||
use omori_locator::get_omori_path;
|
use egui_alignments::{center_vertical, top_horizontal};
|
||||||
|
use egui_extras::{Column, TableBuilder};
|
||||||
|
use egui_modal::Modal;
|
||||||
|
use mod_builder::ModConfiguration;
|
||||||
|
use sha2::Digest;
|
||||||
|
|
||||||
fn main() -> anyhow::Result<()> {
|
fn main() -> anyhow::Result<()> {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
log::trace!("How do you even do serious work on ValorantOS??");
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
log::trace!("Mac user, rich fuck, send me $500");
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
log::trace!("Linux user... Please touch grass for once");
|
||||||
|
|
||||||
|
|
||||||
let options = eframe::NativeOptions {
|
let options = eframe::NativeOptions {
|
||||||
viewport: egui::ViewportBuilder::default()
|
viewport: egui::ViewportBuilder::default()
|
||||||
.with_min_inner_size([640.0, 480.0]),
|
.with_min_inner_size([640.0, 480.0])
|
||||||
|
.with_inner_size([640.0, 480.0]),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let app_state = UiStateHolder {
|
||||||
|
state: Arc::new(Mutex::new(UiState::Loading))
|
||||||
|
};
|
||||||
|
|
||||||
println!("{:?}", get_omori_path().unwrap());
|
let (sender, receiver): (Sender<UiEvent>, Receiver<UiEvent>) = crossbeam_channel::unbounded();
|
||||||
|
|
||||||
eframe::run_native("TundleBool", options, Box::new(|cc| {
|
eframe::run_native("TundleBool", options, Box::new(|cc| {
|
||||||
cc.egui_ctx.set_theme(ThemePreference::System);
|
cc.egui_ctx.set_theme(ThemePreference::System);
|
||||||
|
cc.egui_ctx.style_mut(|style| {
|
||||||
|
style.interaction.selectable_labels = false;
|
||||||
|
});
|
||||||
|
|
||||||
Ok(Box::<Application>::default())
|
let mut app_thread = AppThread::create(&app_state, &cc.egui_ctx, &receiver);
|
||||||
|
|
||||||
|
thread::spawn(move || {
|
||||||
|
app_thread.main();
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(Box::<Application>::new(Application::create(sender, app_state)))
|
||||||
})).unwrap();
|
})).unwrap();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -29,32 +62,373 @@ fn main() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
|
|
||||||
struct Application {
|
struct Application {
|
||||||
updates: u32
|
state: UiStateHolder,
|
||||||
|
sender: Sender<UiEvent>,
|
||||||
|
key_input: String,
|
||||||
|
configuration: ModConfiguration,
|
||||||
|
did_fill_configuration: bool
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Application {
|
impl Application {
|
||||||
fn default() -> Self {
|
fn create(sender: Sender<UiEvent>, state: UiStateHolder) -> Application {
|
||||||
Self {
|
Application {
|
||||||
updates: 0
|
sender,
|
||||||
|
state,
|
||||||
|
key_input: "".to_string(),
|
||||||
|
configuration: Default::default(),
|
||||||
|
did_fill_configuration: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const GAME_KEY_HASH: &[u8; 32] = include_bytes!("keyhash");
|
||||||
|
|
||||||
impl eframe::App for Application {
|
impl eframe::App for Application {
|
||||||
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
fn on_exit(&mut self, _gl: Option<&eframe::glow::Context>) {
|
||||||
ctx.request_repaint();
|
exit(0); // TODO: Prompt the user to maybe please consider not actually exiting the app while it's working
|
||||||
|
}
|
||||||
|
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||||
|
let state = self.state.state.lock();
|
||||||
|
let state = state.clone();
|
||||||
|
|
||||||
|
ctx.request_repaint_after_secs(10.0);
|
||||||
|
|
||||||
|
match state {
|
||||||
|
UiState::Loading => {
|
||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
ui.vertical_centered(|ui| {
|
center_vertical(ui,|ui| {
|
||||||
ui.label(RichText::new("Welcome to BundleTool").size(32.0));
|
ui.label(RichText::new("Loading").size(32.0));
|
||||||
ui.label(RichText::new(format!("{}", self.updates)).size(32.0));
|
|
||||||
});
|
});
|
||||||
self.updates += 1;
|
});
|
||||||
ui.separator();
|
},
|
||||||
egui::ScrollArea::vertical().max_width(300.0).auto_shrink(false).show(ui, |ui| {
|
UiState::Error(reason) => {
|
||||||
for i in 1..3000 {
|
egui::TopBottomPanel::top("error_title_bar").show(ctx, |ui| {
|
||||||
ui.add(egui::Label::new("Meow"));
|
ui.with_layout(Layout::top_down(Align::Center), |ui| {
|
||||||
|
ui.label(RichText::new("An error has occured :(").size(32.0));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
|
center_vertical(ui,|ui| {
|
||||||
|
ui.label(RichText::new(reason).size(32.0));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
UiState::KeyRequired(reason) => {
|
||||||
|
let mut is_valid = true;
|
||||||
|
let hash = sha2::Sha256::digest(&self.key_input.as_bytes());
|
||||||
|
for i in 0..32 {
|
||||||
|
if hash[i] != GAME_KEY_HASH[i] {
|
||||||
|
is_valid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
egui::TopBottomPanel::top("key_prompt_title_bar").show(ctx, |ui| {
|
||||||
|
ui.with_layout(Layout::top_down(Align::Center), |ui| {
|
||||||
|
ui.label(RichText::new("Decryption key required").size(32.0));
|
||||||
|
ui.label(RichText::new(format!("Reason: {reason}")).size(24.0));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
|
top_horizontal(ui, |ui| {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("Decryption key: ");
|
||||||
|
ui.text_edit_singleline(&mut self.key_input);
|
||||||
|
if is_valid {
|
||||||
|
ui.label("Valid");
|
||||||
|
} else {
|
||||||
|
ui.label(RichText::new("Invalid").color(ui.visuals().error_fg_color));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
egui::TopBottomPanel::bottom("key_prompt_button_bar").show(ctx, |ui| {
|
||||||
|
if !is_valid {
|
||||||
|
ui.disable();
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.with_layout(Layout::top_down(Align::Max), |ui| {
|
||||||
|
if ui.button("Continue").clicked() {
|
||||||
|
self.sender.send(UiEvent::SetKey(self.key_input.as_bytes().to_vec())).expect("Failed to send");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
},
|
||||||
|
UiState::PickGame(steam, others) => {
|
||||||
|
let invalid_path_modal = Modal::new(ctx, "invalid_path_modal");
|
||||||
|
invalid_path_modal.show(|ui| {
|
||||||
|
invalid_path_modal.title(ui, "Invalid path");
|
||||||
|
invalid_path_modal.frame(ui, |ui| {
|
||||||
|
invalid_path_modal.body(ui, "Please pick a correct installation of OMORI.");
|
||||||
|
});
|
||||||
|
invalid_path_modal.buttons(ui, |ui| {
|
||||||
|
invalid_path_modal.button(ui, "OK");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
egui::TopBottomPanel::top("game_prompt_title_bar").show(ctx, |ui| {
|
||||||
|
ui.with_layout(Layout::top_down(Align::Center), |ui| {
|
||||||
|
ui.label(RichText::new("Select your base game").size(32.0));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
|
ui.columns(2, |columns| {
|
||||||
|
columns[0].vertical(|ui| {
|
||||||
|
ui.label(RichText::new("Steam Installation").size(24.0));
|
||||||
|
match steam {
|
||||||
|
Ok(path) => {
|
||||||
|
ui.label(RichText::new(format!("Found game at:\n{}", path.display())).italics());
|
||||||
|
ui.separator();
|
||||||
|
if ui.button(RichText::new("Use Steam version").size(16.0)).clicked() {
|
||||||
|
self.sender.send(UiEvent::UseSteamPath).expect("Failed to send");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
ui.label(RichText::new(format!("Failed to find game:\n{}", e)).color(ui.visuals().error_fg_color));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
columns[1].vertical(|ui| {
|
||||||
|
ui.label(RichText::new("Custom").size(24.0));
|
||||||
|
if ui.button(RichText::new("Pick game location").size(16.0)).clicked() {
|
||||||
|
match rfd::FileDialog::new().pick_folder() {
|
||||||
|
Some(path) => {
|
||||||
|
if omori_locator::validate_omori_installation(&path) {
|
||||||
|
self.sender.send(UiEvent::UsePath(path)).expect("Failed to send");
|
||||||
|
} else {
|
||||||
|
invalid_path_modal.open();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
|
if others.len() > 0 {
|
||||||
|
ui.label("History of game locations");
|
||||||
|
|
||||||
|
TableBuilder::new(ui)
|
||||||
|
.striped(true)
|
||||||
|
.column(Column::remainder())
|
||||||
|
.column(Column::auto().at_least(100.0))
|
||||||
|
.sense(egui::Sense::hover() | egui::Sense::click())
|
||||||
|
.body(|body| {
|
||||||
|
body.rows(20.0, others.len(), |mut row| {
|
||||||
|
let item = &others[row.index()];
|
||||||
|
let dt =
|
||||||
|
(item.1 as i64) -
|
||||||
|
(SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).expect("Failed to get the time").as_secs() as i64);
|
||||||
|
|
||||||
|
let dt = chrono::Duration::from(chrono::TimeDelta::seconds(dt as i64));
|
||||||
|
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.with_layout(Layout::right_to_left(Align::Center).with_main_align(Align::Min).with_main_justify(true), |ui| {
|
||||||
|
ui.label(format!("{}", item.0.display()));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.with_layout(Layout::right_to_left(Align::Center).with_main_align(Align::Max), |ui| {
|
||||||
|
ui.label(format!("{}", chrono_humanize::HumanTime::from(dt)));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
if row.response().clicked() {
|
||||||
|
if omori_locator::validate_omori_installation(&item.0) {
|
||||||
|
self.sender.send(UiEvent::UsePath(item.0.clone())).expect("Failed to send");
|
||||||
|
} else {
|
||||||
|
invalid_path_modal.open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if row.response().hovered() {
|
||||||
|
ctx.set_cursor_icon(egui::CursorIcon::PointingHand);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
ui.label("Once you use a custom location, it will be remembered here.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
UiState::PickPlaytest(playtest_history) => {
|
||||||
|
let invalid_path_modal = Modal::new(ctx, "invalid_playtest_modal");
|
||||||
|
invalid_path_modal.show(|ui| {
|
||||||
|
invalid_path_modal.title(ui, "Invalid path");
|
||||||
|
invalid_path_modal.frame(ui, |ui| {
|
||||||
|
invalid_path_modal.body(ui, "Please pick a valid, Oneloader-generated playtest.");
|
||||||
|
});
|
||||||
|
invalid_path_modal.buttons(ui, |ui| {
|
||||||
|
invalid_path_modal.button(ui, "OK");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
egui::TopBottomPanel::top("playtest_prompt_title_bar").show(ctx, |ui| {
|
||||||
|
ui.with_layout(Layout::top_down(Align::Center), |ui| {
|
||||||
|
ui.label(RichText::new("Select your playtest").size(32.0));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
|
ui.with_layout(Layout::right_to_left(Align::Min).with_main_justify(true).with_main_align(Align::Center), |ui| {
|
||||||
|
if ui.button(RichText::new("Pick playtest").size(16.0)).clicked() {
|
||||||
|
match rfd::FileDialog::new().pick_folder() {
|
||||||
|
Some(path) => {
|
||||||
|
if omori_locator::validate_playtest(&path) {
|
||||||
|
self.sender.send(UiEvent::UsePath(path)).expect("Failed to send");
|
||||||
|
} else {
|
||||||
|
invalid_path_modal.open();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if playtest_history.len() > 0 {
|
||||||
|
ui.label("History of playtests");
|
||||||
|
ui.separator();
|
||||||
|
TableBuilder::new(ui)
|
||||||
|
.striped(true)
|
||||||
|
.column(Column::remainder())
|
||||||
|
.column(Column::auto().at_least(100.0))
|
||||||
|
.sense(egui::Sense::hover() | egui::Sense::click())
|
||||||
|
.body(|body| {
|
||||||
|
body.rows(20.0, playtest_history.len(), |mut row| {
|
||||||
|
let item = &playtest_history[row.index()];
|
||||||
|
let dt =
|
||||||
|
(item.1 as i64) -
|
||||||
|
(SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).expect("Failed to get the time").as_secs() as i64);
|
||||||
|
|
||||||
|
let dt = chrono::Duration::from(chrono::TimeDelta::seconds(dt as i64));
|
||||||
|
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.with_layout(Layout::right_to_left(Align::Center).with_main_align(Align::Min).with_main_justify(true), |ui| {
|
||||||
|
ui.label(format!("{}", item.0.display()));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.with_layout(Layout::right_to_left(Align::Center).with_main_align(Align::Max), |ui| {
|
||||||
|
ui.label(format!("{}", chrono_humanize::HumanTime::from(dt)));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
if row.response().clicked() {
|
||||||
|
if omori_locator::validate_playtest(&item.0) {
|
||||||
|
self.sender.send(UiEvent::UsePath(item.0.clone())).expect("Failed to send");
|
||||||
|
} else {
|
||||||
|
invalid_path_modal.open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if row.response().hovered() {
|
||||||
|
ctx.set_cursor_icon(egui::CursorIcon::PointingHand);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ui.label("Any playtests you've used before will be remembered here.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
UiState::Configure(initial_configuration) => {
|
||||||
|
if !self.did_fill_configuration {
|
||||||
|
self.did_fill_configuration = true;
|
||||||
|
self.configuration = initial_configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut font = TextStyle::Body.resolve(&ctx.style());
|
||||||
|
font.size = 16.0;
|
||||||
|
|
||||||
|
egui::TopBottomPanel::top("configure_title_bar").show(ctx, |ui| {
|
||||||
|
ui.with_layout(Layout::top_down(Align::Center), |ui| {
|
||||||
|
ui.label(RichText::new("Configure your mod").size(32.0));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
|
top_horizontal(ui, |ui| {
|
||||||
|
TableBuilder::new(ui)
|
||||||
|
.column(Column::auto().at_least(100.0))
|
||||||
|
.column(Column::exact(300.0))
|
||||||
|
.body(|mut body| {
|
||||||
|
body.row(24.0, |mut row| {
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.label(RichText::new("Mod ID").size(16.0));
|
||||||
|
});
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.add(egui::TextEdit::singleline(&mut self.configuration.mod_id).font(font.clone()));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
body.row(24.0, |mut row| {
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.label(RichText::new("Mod Name").size(16.0));
|
||||||
|
});
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.add(egui::TextEdit::singleline(&mut self.configuration.mod_name).font(font.clone()));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
body.row(24.0, |mut row| {
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.label(RichText::new("Mod Description").size(16.0));
|
||||||
|
});
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.add(egui::TextEdit::singleline(&mut self.configuration.mod_description).font(font.clone()));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
body.row(24.0, |mut row| {
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.label(RichText::new("Mod Version").size(16.0));
|
||||||
|
});
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.add(egui::TextEdit::singleline(&mut self.configuration.mod_version).font(font.clone()));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
|
top_horizontal(ui, |ui| {
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
ui.add(egui::Checkbox::new(&mut self.configuration.include_audio, RichText::new("Include /audio").size(16.0)));
|
||||||
|
ui.add(egui::Checkbox::new(&mut self.configuration.include_data, RichText::new("Include /data").size(16.0)));
|
||||||
|
ui.add(egui::Checkbox::new(&mut self.configuration.include_fonts, RichText::new("Include /fonts").size(16.0)));
|
||||||
|
ui.add(egui::Checkbox::new(&mut self.configuration.include_icon, RichText::new("Include /icon").size(16.0)));
|
||||||
|
ui.add(egui::Checkbox::new(&mut self.configuration.include_img, RichText::new("Include /img").size(16.0)));
|
||||||
|
ui.add(egui::Checkbox::new(&mut self.configuration.include_languages, RichText::new("Include /languages").size(16.0)));
|
||||||
|
ui.add(egui::Checkbox::new(&mut self.configuration.include_maps, RichText::new("Include /maps").size(16.0)));
|
||||||
|
ui.add(egui::Checkbox::new(&mut self.configuration.include_movies, RichText::new("Include /movies").size(16.0)));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: Implement performance settings here
|
||||||
|
});
|
||||||
|
|
||||||
|
egui::TopBottomPanel::bottom("configuration_start").show(ctx, |ui| {
|
||||||
|
let mut is_valid = self.configuration.include_audio || self.configuration.include_data ||
|
||||||
|
self.configuration.include_fonts || self.configuration.include_icon ||
|
||||||
|
self.configuration.include_img || self.configuration.include_languages ||
|
||||||
|
self.configuration.include_maps || self.configuration.include_movies ||
|
||||||
|
self.configuration.include_plugins;
|
||||||
|
|
||||||
|
if self.configuration.mod_description.len() < 2 { is_valid = false; }
|
||||||
|
if self.configuration.mod_id.len() < 2 { is_valid = false; }
|
||||||
|
if self.configuration.mod_name.len() < 2 { is_valid = false; }
|
||||||
|
if self.configuration.mod_version.len() < 2 { is_valid = false; }
|
||||||
|
|
||||||
|
ui.with_layout(Layout::top_down(Align::Max), |ui| {
|
||||||
|
if !is_valid {
|
||||||
|
ui.disable();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ui.button("Continue").clicked() {
|
||||||
|
self.sender.send(UiEvent::UseConfiguration(self.configuration.clone())).expect("Failed to send");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
67
src/mod_builder.rs
Normal file
67
src/mod_builder.rs
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
use std::{fs, path::PathBuf, sync::{atomic::{AtomicU64, Ordering}, Arc}};
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
|
use crossbeam_channel::Sender;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
|
pub struct ModConfiguration {
|
||||||
|
pub mod_id: String,
|
||||||
|
pub mod_name: String,
|
||||||
|
pub mod_description: String,
|
||||||
|
pub mod_version: String,
|
||||||
|
|
||||||
|
pub include_audio: bool,
|
||||||
|
pub include_data: bool,
|
||||||
|
pub include_fonts: bool,
|
||||||
|
pub include_icon: bool,
|
||||||
|
pub include_img: bool,
|
||||||
|
pub include_plugins: bool,
|
||||||
|
pub include_languages: bool,
|
||||||
|
pub include_maps: bool,
|
||||||
|
pub include_movies: bool
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
TODO:
|
||||||
|
IMPORTANT!!!
|
||||||
|
IMPLEMENT ERROR HANDLING AND PROPAGATION
|
||||||
|
**BEFORE** MARKING 1.0.0!!!
|
||||||
|
IMPORTANT!!
|
||||||
|
*/
|
||||||
|
fn walk_inner(relative_base: &PathBuf, real_filesystem_base: &PathBuf, target: &Sender<PathBuf>, counter: Arc<AtomicU64>) -> anyhow::Result<()> {
|
||||||
|
let f = fs::read_dir(real_filesystem_base)?;
|
||||||
|
for entry in f {
|
||||||
|
let entry = entry?;
|
||||||
|
let file_type = entry.file_type()?;
|
||||||
|
let path = entry.path();
|
||||||
|
let path = path.file_name().with_context(|| format!("The file doesn't have a name(????)"))?;
|
||||||
|
|
||||||
|
let mut new_base = relative_base.clone();
|
||||||
|
new_base.push(path);
|
||||||
|
|
||||||
|
|
||||||
|
if file_type.is_dir() {
|
||||||
|
let mut new_realfs_base = real_filesystem_base.clone();
|
||||||
|
new_realfs_base.push(path);
|
||||||
|
|
||||||
|
walk_inner(&new_base, &new_realfs_base, target, counter.clone())?;
|
||||||
|
}
|
||||||
|
if file_type.is_file() {
|
||||||
|
counter.fetch_add(1, Ordering::Relaxed);
|
||||||
|
target.send(new_base.clone())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn walk_dirs(base: &PathBuf, secondary_bases: &Vec<PathBuf>, target: Sender<PathBuf>, counter: Arc<AtomicU64>) {
|
||||||
|
for path in secondary_bases {
|
||||||
|
let mut real = base.clone();
|
||||||
|
real.push(path);
|
||||||
|
|
||||||
|
if walk_inner(path, &real, &target, counter.clone()).is_err() {
|
||||||
|
todo!("Implement error handling you bitch");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use std::{fs::File, io::Read, path::PathBuf};
|
use log::{debug, info, trace};
|
||||||
|
use regex::bytes::Regex;
|
||||||
|
use sha2::Digest;
|
||||||
|
use std::{fs::{self, File}, io::Read, path::PathBuf};
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
fn get_steam_root() -> anyhow::Result<PathBuf> {
|
fn get_steam_root() -> anyhow::Result<PathBuf> {
|
||||||
@@ -11,6 +14,8 @@ fn get_steam_root() -> anyhow::Result<PathBuf> {
|
|||||||
#[cfg(target_os="macos")]
|
#[cfg(target_os="macos")]
|
||||||
base.push("Library/Application Support/Steam");
|
base.push("Library/Application Support/Steam");
|
||||||
|
|
||||||
|
info!("Using {:?} as steam base", base);
|
||||||
|
|
||||||
Ok(base)
|
Ok(base)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,13 +32,20 @@ fn get_steam_root() -> anyhow::Result<PathBuf> {
|
|||||||
|
|
||||||
let mut base = PathBuf::from(value);
|
let mut base = PathBuf::from(value);
|
||||||
|
|
||||||
|
debug!("Using {:?} as steam base", base);
|
||||||
|
|
||||||
Ok(base)
|
Ok(base)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const OMORI_STEAM_APPID: &str = "1150690";
|
||||||
|
|
||||||
pub fn get_omori_path() -> anyhow::Result<PathBuf> {
|
pub fn get_omori_path() -> anyhow::Result<PathBuf> {
|
||||||
let mut base = get_steam_root()?;
|
let mut base = get_steam_root()?;
|
||||||
|
|
||||||
base.push("steamapps/libraryfolders.vdf");
|
base.push("steamapps");
|
||||||
|
base.push("libraryfolders.vdf");
|
||||||
|
|
||||||
|
info!("Searching {:?} for library folders with omori", base);
|
||||||
|
|
||||||
let mut buffer = Vec::new();
|
let mut buffer = Vec::new();
|
||||||
|
|
||||||
@@ -47,7 +59,6 @@ pub fn get_omori_path() -> anyhow::Result<PathBuf> {
|
|||||||
|
|
||||||
let mut omori_path: Option<PathBuf> = None;
|
let mut omori_path: Option<PathBuf> = None;
|
||||||
for key in keys {
|
for key in keys {
|
||||||
println!("{:?}", key);
|
|
||||||
let entry = data.get(key).with_context(|| "Couldn't read specific steam library libraryfolders object.")?;
|
let entry = data.get(key).with_context(|| "Couldn't read specific steam library libraryfolders object.")?;
|
||||||
let entry = entry.get(0).with_context(|| "Couldn't read specific steam library libraryfolders object.")?;
|
let entry = entry.get(0).with_context(|| "Couldn't read specific steam library libraryfolders object.")?;
|
||||||
let entry = entry.get_obj().with_context(|| "libraryfolders parsing failed: Not an object")?;
|
let entry = entry.get_obj().with_context(|| "libraryfolders parsing failed: Not an object")?;
|
||||||
@@ -61,19 +72,161 @@ pub fn get_omori_path() -> anyhow::Result<PathBuf> {
|
|||||||
let path = path.get(0).with_context(|| "libraryfolders parsing failed: No path")?;
|
let path = path.get(0).with_context(|| "libraryfolders parsing failed: No path")?;
|
||||||
let path = path.get_str().with_context(|| "libraryfolders parsing failed: No path")?;
|
let path = path.get_str().with_context(|| "libraryfolders parsing failed: No path")?;
|
||||||
|
|
||||||
if apps.iter().find(|x| (**x).eq("1150690")).is_some() {
|
if apps.iter().find(|x| (**x).eq(OMORI_STEAM_APPID)).is_some() {
|
||||||
let mut base = PathBuf::from(path);
|
let mut base = PathBuf::from(path);
|
||||||
|
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
base.push("steamapps/common/OMORI/www");
|
{
|
||||||
|
base.push("steamapps");
|
||||||
|
base.push("common");
|
||||||
|
base.push("OMORI");
|
||||||
|
base.push("www");
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
base.push("steamapps/common/OMORI/OMORI.app/Contents/Resources/app.nw");
|
{
|
||||||
|
base.push("steamapps");
|
||||||
|
base.push("common");
|
||||||
|
base.push("OMORI");
|
||||||
|
base.push("OMORI.app");
|
||||||
|
base.push("Contents");
|
||||||
|
base.push("Resources");
|
||||||
|
base.push("app.nw");
|
||||||
|
}
|
||||||
|
|
||||||
omori_path = Some(base);
|
omori_path = Some(base);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
omori_path.with_context(|| "Failed to find omori")
|
let path = omori_path.with_context(|| "Failed to find omori")?;
|
||||||
|
debug!("{:?} seems to be it.", path);
|
||||||
|
if validate_omori_installation(&path) {
|
||||||
|
Ok(path)
|
||||||
|
} else {
|
||||||
|
Err(anyhow::anyhow!("The steam installation of OMORI is not valid"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const GAME_KEY_HASH: &[u8; 32] = include_bytes!("keyhash");
|
||||||
|
|
||||||
|
pub fn get_omori_key() -> anyhow::Result<Vec<u8>> {
|
||||||
|
let mut base: PathBuf = get_steam_root()?;
|
||||||
|
|
||||||
|
base.push("appcache");
|
||||||
|
base.push("appinfo.vdf");
|
||||||
|
|
||||||
|
let mut buffer = Vec::new();
|
||||||
|
|
||||||
|
let mut fd = File::open(base)?;
|
||||||
|
fd.read_to_end(&mut buffer)?;
|
||||||
|
drop(fd);
|
||||||
|
|
||||||
|
let re = Regex::new(r"--([0-9a-f]{32})")?;
|
||||||
|
|
||||||
|
for (_, [key]) in re.captures_iter(&buffer).map(|c| c.extract()) {
|
||||||
|
let hash = sha2::Sha256::digest(key);
|
||||||
|
|
||||||
|
let mut equal = true;
|
||||||
|
for i in 0..32 {
|
||||||
|
if hash[i] != GAME_KEY_HASH[i] {
|
||||||
|
equal = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if equal {
|
||||||
|
return Ok(key.to_vec())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Err(anyhow::anyhow!("Couldn't find any valid decryption key for OMORI in your Steam installation."))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const EXPECTED_PATHS_BASE: [&str; 32] = [
|
||||||
|
"index.html",
|
||||||
|
"editor.json",
|
||||||
|
"audio/bgm/AMB_forest.rpgmvo",
|
||||||
|
"audio/me/cutscene_omori_saves_player.rpgmvo",
|
||||||
|
"data/Actors.KEL",
|
||||||
|
"data/System.KEL",
|
||||||
|
"data/Atlas.PLUTO",
|
||||||
|
"data/Troops.KEL",
|
||||||
|
"fonts/OMORI_GAME.ttf",
|
||||||
|
"icon/icon.png",
|
||||||
|
"img/animations/e_calm_down.rpgmvp",
|
||||||
|
"img/atlases/battleATLAS.rpgmvp",
|
||||||
|
"img/battlebacks1/battleback_dw_humphrey.rpgmvp", // best area
|
||||||
|
"img/characters/!FA_OBJECTS_2.rpgmvp",
|
||||||
|
"img/characters/FA_AUBREY.rpgmvp",
|
||||||
|
"img/enemies/!battle_snow_bunny.rpgmvp",
|
||||||
|
"img/faces/01_OMORI_BATTLE.rpgmvp",
|
||||||
|
"img/overlays/fog.rpgmvp",
|
||||||
|
"img/parallaxes/!BS_faces.rpgmvp",
|
||||||
|
"img/pictures/basil_something.rpgmvp",
|
||||||
|
"img/slotmachine/bet_line_2.rpgmvp",
|
||||||
|
"img/sv_actors/!battle_aubrey.rpgmvp",
|
||||||
|
"img/system/bar_gradients.rpgmvp",
|
||||||
|
"img/system/Window.png",
|
||||||
|
"img/tilesets/BS_Beach.rpgmvp",
|
||||||
|
"img/transition/starburst_2.rpgmvp",
|
||||||
|
"js/rpg_core.js",
|
||||||
|
"js/plugins/Omori BASE.OMORI",
|
||||||
|
"languages/en/10_cutscenes_fakeknifefight.HERO",
|
||||||
|
"maps/FA_SUNSET_INTERIOR2.AUBREY",
|
||||||
|
"movies/secret_ending.webm",
|
||||||
|
"movies/Bad End Credits Draft 4.webm"
|
||||||
|
];
|
||||||
|
|
||||||
|
pub fn validate_omori_installation(base: &PathBuf) -> bool {
|
||||||
|
info!("Validating {:?}", base);
|
||||||
|
for path in EXPECTED_PATHS_BASE {
|
||||||
|
trace!("Checking {path}");
|
||||||
|
let mut real = base.clone();
|
||||||
|
real.push(path);
|
||||||
|
|
||||||
|
if let Ok(r) = fs::exists(&real) {
|
||||||
|
if !r { return false; }
|
||||||
|
} else { return false; }
|
||||||
|
}
|
||||||
|
info!("Validation passed");
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
const EXPECTED_PATHS_PLAYTEST: [&str; 17] = [
|
||||||
|
"index.html",
|
||||||
|
"Game.rpgproject",
|
||||||
|
"img",
|
||||||
|
"img/pictures",
|
||||||
|
"audio",
|
||||||
|
"audio/bgm",
|
||||||
|
"js",
|
||||||
|
"languages",
|
||||||
|
"js/plugins",
|
||||||
|
"js/plugins/Omori BASE.js",
|
||||||
|
"package.json",
|
||||||
|
"data/Actors.json",
|
||||||
|
"data/System.json",
|
||||||
|
"data/Atlas.yaml",
|
||||||
|
"data/Troops.json",
|
||||||
|
"img/system/Window.png",
|
||||||
|
"img/atlases/battleATLAS.png",
|
||||||
|
];
|
||||||
|
|
||||||
|
pub fn validate_playtest(base: &PathBuf) -> bool {
|
||||||
|
info!("Validating {:?}", base);
|
||||||
|
for path in EXPECTED_PATHS_PLAYTEST {
|
||||||
|
trace!("Checking {path}");
|
||||||
|
let mut real = base.clone();
|
||||||
|
real.push(path);
|
||||||
|
|
||||||
|
if let Ok(r) = fs::exists(&real) {
|
||||||
|
if !r { return false; }
|
||||||
|
} else { return false; }
|
||||||
|
}
|
||||||
|
info!("Validation passed");
|
||||||
|
|
||||||
|
true
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user