🏡 index : ~doyle/shalom.git

author Jordan Doyle <jordan@doyle.la> 2023-10-22 22:03:16.0 +00:00:00
committer Jordan Doyle <jordan@doyle.la> 2023-10-30 20:54:16.0 +00:00:00
commit
1d584771d0461934b2af2ceb005808eae59fbe5e [patch]
tree
962cf5feaacaf541fe173e0ca45b2c3a9e218f33
download
1d584771d0461934b2af2ceb005808eae59fbe5e.tar.gz

Initial commit



Diff

 .gitattributes                     |    1 +-
 .gitignore                         |    2 +-
 Cargo.lock                         | 3732 +++++++++++++++++++++++++++++++++++++-
 Cargo.toml                         |    8 +-
 README.md                          |    8 +-
 assets/icons/README.md             |    3 +-
 assets/icons/back.svg              |    3 +-
 assets/icons/backward.svg          |    3 +-
 assets/icons/forward.svg           |    3 +-
 assets/icons/hamburger.svg         |    3 +-
 assets/icons/home.svg              |    3 +-
 assets/icons/light-bulb.svg        |    3 +-
 assets/icons/pause.svg             |    3 +-
 assets/icons/play.svg              |    3 +-
 assets/icons/repeat.svg            |    3 +-
 assets/icons/speaker-muted.svg     |    3 +-
 assets/icons/speaker.svg           |    3 +-
 assets/images/bathroom.jpg         |    3 +-
 assets/images/bedroom.jpg          |    3 +-
 assets/images/dining_room.jpg      |    3 +-
 assets/images/kitchen.jpg          |    3 +-
 assets/images/living_room.jpg      |    3 +-
 config-example.toml                |    3 +-
 rustfmt.toml                       |    7 +-
 shalom/Cargo.toml                  |   19 +-
 shalom/src/config.rs               |   16 +-
 shalom/src/hass_client.rs          |  142 +-
 shalom/src/main.rs                 |  202 ++-
 shalom/src/oracle.rs               |    1 +-
 shalom/src/pages/mod.rs            |    2 +-
 shalom/src/pages/omni.rs           |   83 +-
 shalom/src/pages/room.rs           |  100 +-
 shalom/src/theme.rs                |  126 +-
 shalom/src/widgets/context_menu.rs |  458 +++++-
 shalom/src/widgets/image_card.rs   |  237 ++-
 shalom/src/widgets/media_player.rs |  233 ++-
 shalom/src/widgets/mod.rs          |    6 +-
 shalom/src/widgets/mouse_area.rs   |  347 +++-
 shalom/src/widgets/toggle_card.rs  |  240 ++-
 shalom/src/widgets/track_card.rs   |   65 +-
 40 files changed, 6089 insertions(+)

diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..4fae6dc
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
*.jpg filter=lfs diff=lfs merge=lfs -text
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..fabfb87
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
/target
/config.toml
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..9b5d1f8
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,3732 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3

[[package]]
name = "ab_glyph"
version = "0.2.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1061f3ff92c2f65800df1f12fc7b4ff44ee14783104187dd04dfee6f11b0fd2"
dependencies = [
 "ab_glyph_rasterizer",
 "owned_ttf_parser",
]

[[package]]
name = "ab_glyph_rasterizer"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046"

[[package]]
name = "addr2line"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
dependencies = [
 "gimli",
]

[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"

[[package]]
name = "ahash"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
dependencies = [
 "cfg-if",
 "once_cell",
 "version_check",
]

[[package]]
name = "aliasable"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd"

[[package]]
name = "allocator-api2"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"

[[package]]
name = "android-activity"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64529721f27c2314ced0890ce45e469574a73e5e6fdd6e9da1860eb29285f5e0"
dependencies = [
 "android-properties",
 "bitflags 1.3.2",
 "cc",
 "jni-sys",
 "libc",
 "log",
 "ndk",
 "ndk-context",
 "ndk-sys",
 "num_enum 0.6.1",
]

[[package]]
name = "android-properties"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04"

[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
 "libc",
]

[[package]]
name = "approx"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
dependencies = [
 "num-traits",
]

[[package]]
name = "arrayref"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545"

[[package]]
name = "arrayvec"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"

[[package]]
name = "ash"
version = "0.37.3+1.3.251"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39e9c3835d686b0a6084ab4234fcd1b07dbf6e4767dce60874b12356a25ecd4a"
dependencies = [
 "libloading 0.7.4",
]

[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"

[[package]]
name = "backtrace"
version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
dependencies = [
 "addr2line",
 "cc",
 "cfg-if",
 "libc",
 "miniz_oxide",
 "object",
 "rustc-demangle",
]

[[package]]
name = "base64"
version = "0.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2"

[[package]]
name = "bit-set"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
dependencies = [
 "bit-vec",
]

[[package]]
name = "bit-vec"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"

[[package]]
name = "bit_field"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61"

[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"

[[package]]
name = "bitflags"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"

[[package]]
name = "block"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"

[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
 "generic-array",
]

[[package]]
name = "block-sys"
version = "0.1.0-beta.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa55741ee90902547802152aaf3f8e5248aab7e21468089560d4c8840561146"
dependencies = [
 "objc-sys",
]

[[package]]
name = "block2"
version = "0.2.0-alpha.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8dd9e63c1744f755c2f60332b88de39d341e5e86239014ad839bd71c106dec42"
dependencies = [
 "block-sys",
 "objc2-encode",
]

[[package]]
name = "bumpalo"
version = "3.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"

[[package]]
name = "bytemuck"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6"
dependencies = [
 "bytemuck_derive",
]

[[package]]
name = "bytemuck_derive"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1"
dependencies = [
 "proc-macro2",
 "quote",
 "syn 2.0.38",
]

[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"

[[package]]
name = "bytes"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"

[[package]]
name = "calloop"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52e0d00eb1ea24371a97d2da6201c6747a633dc6dc1988ef503403b4c59504a8"
dependencies = [
 "bitflags 1.3.2",
 "log",
 "nix 0.25.1",
 "slotmap",
 "thiserror",
 "vec_map",
]

[[package]]
name = "cc"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
dependencies = [
 "jobserver",
 "libc",
]

[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"

[[package]]
name = "cfg_aliases"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"

[[package]]
name = "clipboard-win"
version = "4.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362"
dependencies = [
 "error-code",
 "str-buf",
 "winapi",
]

[[package]]
name = "clipboard_macos"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "145a7f9e9b89453bc0a5e32d166456405d389cea5b578f57f1274b1397588a95"
dependencies = [
 "objc",
 "objc-foundation",
 "objc_id",
]

[[package]]
name = "clipboard_wayland"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f6364a9f7a66f2ac1a1a098aa1c7f6b686f2496c6ac5e5c0d773445df912747"
dependencies = [
 "smithay-clipboard",
]

[[package]]
name = "clipboard_x11"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "983a7010836ecd04dde2c6d27a0cb56ec5d21572177e782bdcb24a600124e921"
dependencies = [
 "thiserror",
 "x11rb 0.9.0",
]

[[package]]
name = "cocoa"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a"
dependencies = [
 "bitflags 1.3.2",
 "block",
 "cocoa-foundation",
 "core-foundation",
 "core-graphics",
 "foreign-types",
 "libc",
 "objc",
]

[[package]]
name = "cocoa-foundation"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7"
dependencies = [
 "bitflags 1.3.2",
 "block",
 "core-foundation",
 "core-graphics-types",
 "libc",
 "objc",
]

[[package]]
name = "codespan-reporting"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
dependencies = [
 "termcolor",
 "unicode-width",
]

[[package]]
name = "color_quant"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"

[[package]]
name = "com-rs"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf43edc576402991846b093a7ca18a3477e0ef9c588cde84964b5d3e43016642"

[[package]]
name = "core-foundation"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
dependencies = [
 "core-foundation-sys",
 "libc",
]

[[package]]
name = "core-foundation-sys"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"

[[package]]
name = "core-graphics"
version = "0.22.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb"
dependencies = [
 "bitflags 1.3.2",
 "core-foundation",
 "core-graphics-types",
 "foreign-types",
 "libc",
]

[[package]]
name = "core-graphics-types"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bb142d41022986c1d8ff29103a1411c8a3dfad3552f87a4f8dc50d61d4f4e33"
dependencies = [
 "bitflags 1.3.2",
 "core-foundation",
 "libc",
]

[[package]]
name = "cosmic-text"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0b68966c2543609f8d92f9d33ac3b719b2a67529b0c6c0b3e025637b477eef9"
dependencies = [
 "aliasable",
 "fontdb",
 "libm",
 "log",
 "rangemap",
 "rustybuzz 0.8.0",
 "swash",
 "sys-locale",
 "unicode-bidi",
 "unicode-linebreak",
 "unicode-script",
 "unicode-segmentation",
]

[[package]]
name = "cpufeatures"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fbc60abd742b35f2492f808e1abbb83d45f72db402e14c55057edc9c7b1e9e4"
dependencies = [
 "libc",
]

[[package]]
name = "crc32fast"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
dependencies = [
 "cfg-if",
]

[[package]]
name = "crossbeam-deque"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
dependencies = [
 "cfg-if",
 "crossbeam-epoch",
 "crossbeam-utils",
]

[[package]]
name = "crossbeam-epoch"
version = "0.9.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7"
dependencies = [
 "autocfg",
 "cfg-if",
 "crossbeam-utils",
 "memoffset 0.9.0",
 "scopeguard",
]

[[package]]
name = "crossbeam-utils"
version = "0.8.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
dependencies = [
 "cfg-if",
]

[[package]]
name = "crunchy"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"

[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
 "generic-array",
 "typenum",
]

[[package]]
name = "d3d12"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8f0de2f5a8e7bd4a9eec0e3c781992a4ce1724f68aec7d7a3715344de8b39da"
dependencies = [
 "bitflags 1.3.2",
 "libloading 0.7.4",
 "winapi",
]

[[package]]
name = "data-encoding"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"

[[package]]
name = "data-url"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d7439c3735f405729d52c3fbbe4de140eaf938a1fe47d227c27f8254d4302a5"

[[package]]
name = "deranged"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3"
dependencies = [
 "powerfmt",
]

[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
 "block-buffer",
 "crypto-common",
]

[[package]]
name = "dispatch"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"

[[package]]
name = "dlib"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
dependencies = [
 "libloading 0.8.1",
]

[[package]]
name = "downcast-rs"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"

[[package]]
name = "either"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"

[[package]]
name = "equivalent"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"

[[package]]
name = "error-code"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21"
dependencies = [
 "libc",
 "str-buf",
]

[[package]]
name = "etagere"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcf22f748754352918e082e0039335ee92454a5d62bcaf69b5e8daf5907d9644"
dependencies = [
 "euclid",
 "svg_fmt",
]

[[package]]
name = "euclid"
version = "0.22.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f253bc5c813ca05792837a0ff4b3a580336b224512d48f7eda1d7dd9210787"
dependencies = [
 "num-traits",
]

[[package]]
name = "exr"
version = "1.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "279d3efcc55e19917fff7ab3ddd6c14afb6a90881a0078465196fe2f99d08c56"
dependencies = [
 "bit_field",
 "flume",
 "half",
 "lebe",
 "miniz_oxide",
 "rayon-core",
 "smallvec",
 "zune-inflate",
]

[[package]]
name = "fast-srgb8"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1"

[[package]]
name = "fastrand"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
dependencies = [
 "instant",
]

[[package]]
name = "fdeflate"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d329bdeac514ee06249dabc27877490f17f5d371ec693360768b838e19f3ae10"
dependencies = [
 "simd-adler32",
]

[[package]]
name = "flate2"
version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
dependencies = [
 "crc32fast",
 "miniz_oxide",
]

[[package]]
name = "float-cmp"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"

[[package]]
name = "flume"
version = "0.10.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577"
dependencies = [
 "futures-core",
 "futures-sink",
 "nanorand",
 "pin-project",
 "spin",
]

[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"

[[package]]
name = "fontconfig-parser"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "674e258f4b5d2dcd63888c01c68413c51f565e8af99d2f7701c7b81d79ef41c4"
dependencies = [
 "roxmltree",
]

[[package]]
name = "fontdb"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af8d8cbea8f21307d7e84bca254772981296f058a1d36b461bf4d83a7499fc9e"
dependencies = [
 "fontconfig-parser",
 "log",
 "memmap2 0.6.2",
 "slotmap",
 "tinyvec",
 "ttf-parser 0.19.2",
]

[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
 "foreign-types-shared",
]

[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"

[[package]]
name = "form_urlencoded"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
dependencies = [
 "percent-encoding",
]

[[package]]
name = "futures"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40"
dependencies = [
 "futures-channel",
 "futures-core",
 "futures-executor",
 "futures-io",
 "futures-sink",
 "futures-task",
 "futures-util",
]

[[package]]
name = "futures-channel"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2"
dependencies = [
 "futures-core",
 "futures-sink",
]

[[package]]
name = "futures-core"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"

[[package]]
name = "futures-executor"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0"
dependencies = [
 "futures-core",
 "futures-task",
 "futures-util",
 "num_cpus",
]

[[package]]
name = "futures-io"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"

[[package]]
name = "futures-macro"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
dependencies = [
 "proc-macro2",
 "quote",
 "syn 2.0.38",
]

[[package]]
name = "futures-sink"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e"

[[package]]
name = "futures-task"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"

[[package]]
name = "futures-util"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
dependencies = [
 "futures-channel",
 "futures-core",
 "futures-io",
 "futures-macro",
 "futures-sink",
 "futures-task",
 "memchr",
 "pin-project-lite",
 "pin-utils",
 "slab",
]

[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
 "typenum",
 "version_check",
]

[[package]]
name = "gethostname"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e"
dependencies = [
 "libc",
 "winapi",
]

[[package]]
name = "getrandom"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
dependencies = [
 "cfg-if",
 "js-sys",
 "libc",
 "wasi",
 "wasm-bindgen",
]

[[package]]
name = "gif"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045"
dependencies = [
 "color_quant",
 "weezl",
]

[[package]]
name = "gimli"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"

[[package]]
name = "glam"
version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5418c17512bdf42730f9032c74e1ae39afc408745ebb2acf72fbc4691c17945"

[[package]]
name = "glow"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca0fe580e4b60a8ab24a868bc08e2f03cbcb20d3d676601fa909386713333728"
dependencies = [
 "js-sys",
 "slotmap",
 "wasm-bindgen",
 "web-sys",
]

[[package]]
name = "glyphon"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e87caa7459145f5e5f167bf34db4532901404c679e62339fb712a0e3ccf722a"
dependencies = [
 "cosmic-text",
 "etagere",
 "lru",
 "wgpu",
]

[[package]]
name = "gpu-alloc"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22beaafc29b38204457ea030f6fb7a84c9e4dd1b86e311ba0542533453d87f62"
dependencies = [
 "bitflags 1.3.2",
 "gpu-alloc-types",
]

[[package]]
name = "gpu-alloc-types"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54804d0d6bc9d7f26db4eaec1ad10def69b599315f487d32c334a80d1efe67a5"
dependencies = [
 "bitflags 1.3.2",
]

[[package]]
name = "gpu-allocator"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce95f9e2e11c2c6fadfce42b5af60005db06576f231f5c92550fdded43c423e8"
dependencies = [
 "backtrace",
 "log",
 "thiserror",
 "winapi",
 "windows",
]

[[package]]
name = "gpu-descriptor"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c"
dependencies = [
 "bitflags 2.4.1",
 "gpu-descriptor-types",
 "hashbrown 0.14.2",
]

[[package]]
name = "gpu-descriptor-types"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bf0b36e6f090b7e1d8a4b49c0cb81c1f8376f72198c65dd3ad9ff3556b8b78c"
dependencies = [
 "bitflags 2.4.1",
]

[[package]]
name = "guillotiere"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b62d5865c036cb1393e23c50693df631d3f5d7bcca4c04fe4cc0fd592e74a782"
dependencies = [
 "euclid",
 "svg_fmt",
]

[[package]]
name = "half"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc52e53916c08643f1b56ec082790d1e86a32e58dc5268f897f313fbae7b4872"
dependencies = [
 "cfg-if",
 "crunchy",
]

[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"

[[package]]
name = "hashbrown"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156"
dependencies = [
 "ahash",
 "allocator-api2",
]

[[package]]
name = "hassle-rs"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1397650ee315e8891a0df210707f0fc61771b0cc518c3023896064c5407cb3b0"
dependencies = [
 "bitflags 1.3.2",
 "com-rs",
 "libc",
 "libloading 0.7.4",
 "thiserror",
 "widestring",
 "winapi",
]

[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"

[[package]]
name = "hermit-abi"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"

[[package]]
name = "hexf-parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"

[[package]]
name = "http"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482"
dependencies = [
 "bytes",
 "fnv",
 "itoa",
]

[[package]]
name = "httparse"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"

[[package]]
name = "iced"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c708807ec86f99dd729dc4d42db5239acf118cec14d3c5f57679dcfdbbc472b1"
dependencies = [
 "iced_core",
 "iced_futures",
 "iced_renderer",
 "iced_widget",
 "iced_winit",
 "image",
 "thiserror",
]

[[package]]
name = "iced_core"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64d0bc4fbf018576d08d93f838e6058cc6f10bbc05e04ae249a2a44dffb4ebc8"
dependencies = [
 "bitflags 1.3.2",
 "instant",
 "log",
 "palette",
 "thiserror",
 "twox-hash",
]

[[package]]
name = "iced_futures"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14dab0054a9c7a1cbce227a8cd9ee4a094497b3d06094551ac6c1488d563802e"
dependencies = [
 "futures",
 "iced_core",
 "log",
 "tokio",
 "wasm-bindgen-futures",
 "wasm-timer",
]

[[package]]
name = "iced_graphics"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67ff14447a221e9e9205a13d84d7bbdf0636a3b1daa02cfca690ed09689c4d2b"
dependencies = [
 "bitflags 1.3.2",
 "bytemuck",
 "glam",
 "half",
 "iced_core",
 "image",
 "kamadak-exif",
 "log",
 "raw-window-handle",
 "thiserror",
]

[[package]]
name = "iced_renderer"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1033385b0db0099a0d13178c9ff93c1ce11e7d0177522acf578bf79febdb2af8"
dependencies = [
 "iced_graphics",
 "iced_tiny_skia",
 "iced_wgpu",
 "log",
 "raw-window-handle",
 "thiserror",
]

[[package]]
name = "iced_runtime"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c6c89853e1250c6fac82c5015fa2144517be9b33d4b8e456f10e198b23e28bd"
dependencies = [
 "iced_core",
 "iced_futures",
 "thiserror",
]

[[package]]
name = "iced_style"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d85c47d9d13e2281f75ddf98c865daf2101632bd2b855c401dd0b1c8b81a31a0"
dependencies = [
 "iced_core",
 "once_cell",
 "palette",
]

[[package]]
name = "iced_tiny_skia"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7715f6222c9470bbbd75a39f70478fa0d1bdfb81a377a34fd1b090ffccc480b"
dependencies = [
 "bytemuck",
 "cosmic-text",
 "iced_graphics",
 "kurbo",
 "log",
 "raw-window-handle",
 "resvg",
 "rustc-hash",
 "softbuffer",
 "tiny-skia 0.10.0",
 "twox-hash",
]

[[package]]
name = "iced_wgpu"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "703f7c5de46b997ed7b18e05ec67059dcdf3beeac51e917c21071b021bb848b9"
dependencies = [
 "bitflags 1.3.2",
 "bytemuck",
 "futures",
 "glam",
 "glyphon",
 "guillotiere",
 "iced_graphics",
 "log",
 "once_cell",
 "raw-window-handle",
 "resvg",
 "rustc-hash",
 "twox-hash",
 "wgpu",
]

[[package]]
name = "iced_widget"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a177219ae51c3ba08f228ab932354b360cc669e94aec50c01e7c9b675f074c7c"
dependencies = [
 "iced_renderer",
 "iced_runtime",
 "iced_style",
 "num-traits",
 "ouroboros",
 "thiserror",
 "unicode-segmentation",
]

[[package]]
name = "iced_winit"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ecea26fcc8074373f6011d2193d63445617d900a428eb4df66c0e5658408fb6"
dependencies = [
 "iced_graphics",
 "iced_runtime",
 "iced_style",
 "log",
 "raw-window-handle",
 "thiserror",
 "web-sys",
 "winapi",
 "window_clipboard",
 "winit",
]

[[package]]
name = "idna"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
dependencies = [
 "unicode-bidi",
 "unicode-normalization",
]

[[package]]
name = "image"
version = "0.24.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f3dfdbdd72063086ff443e297b61695500514b1e41095b6fb9a5ab48a70a711"
dependencies = [
 "bytemuck",
 "byteorder",
 "color_quant",
 "exr",
 "gif",
 "jpeg-decoder",
 "num-rational",
 "num-traits",
 "png",
 "qoi",
 "tiff",
]

[[package]]
name = "imagesize"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284"

[[package]]
name = "indexmap"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [
 "autocfg",
 "hashbrown 0.12.3",
]

[[package]]
name = "indexmap"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897"
dependencies = [
 "equivalent",
 "hashbrown 0.14.2",
]

[[package]]
name = "instant"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
 "cfg-if",
 "js-sys",
 "wasm-bindgen",
 "web-sys",
]

[[package]]
name = "io-lifetimes"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
dependencies = [
 "hermit-abi",
 "libc",
 "windows-sys 0.48.0",
]

[[package]]
name = "itertools"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
dependencies = [
 "either",
]

[[package]]
name = "itoa"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"

[[package]]
name = "jni-sys"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"

[[package]]
name = "jobserver"
version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d"
dependencies = [
 "libc",
]

[[package]]
name = "jpeg-decoder"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e"
dependencies = [
 "rayon",
]

[[package]]
name = "js-sys"
version = "0.3.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a"
dependencies = [
 "wasm-bindgen",
]

[[package]]
name = "kamadak-exif"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef4fc70d0ab7e5b6bafa30216a6b48705ea964cdfc29c050f2412295eba58077"
dependencies = [
 "mutate_once",
]

[[package]]
name = "keyframe"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60708bf7981518d09095d6f5673ce5cf6a64f1e0d9708b554f670e6d9d2bd9a9"
dependencies = [
 "mint",
 "num-traits",
]

[[package]]
name = "khronos-egl"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c2352bd1d0bceb871cb9d40f24360c8133c11d7486b68b5381c1dd1a32015e3"
dependencies = [
 "libc",
 "libloading 0.7.4",
 "pkg-config",
]

[[package]]
name = "kurbo"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd85a5776cd9500c2e2059c8c76c3b01528566b7fcbaf8098b55a33fc298849b"
dependencies = [
 "arrayvec",
]

[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"

[[package]]
name = "lebe"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"

[[package]]
name = "libc"
version = "0.2.149"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b"

[[package]]
name = "libloading"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
dependencies = [
 "cfg-if",
 "winapi",
]

[[package]]
name = "libloading"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161"
dependencies = [
 "cfg-if",
 "windows-sys 0.48.0",
]

[[package]]
name = "libm"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"

[[package]]
name = "lock_api"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
dependencies = [
 "autocfg",
 "scopeguard",
]

[[package]]
name = "log"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"

[[package]]
name = "lru"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a83fb7698b3643a0e34f9ae6f2e8f0178c0fd42f8b59d493aa271ff3a5bf21"
dependencies = [
 "hashbrown 0.14.2",
]

[[package]]
name = "malloc_buf"
version = "0.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
dependencies = [
 "libc",
]

[[package]]
name = "memchr"
version = "2.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"

[[package]]
name = "memmap2"
version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327"
dependencies = [
 "libc",
]

[[package]]
name = "memmap2"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d28bba84adfe6646737845bc5ebbfa2c08424eb1c37e94a1fd2a82adb56a872"
dependencies = [
 "libc",
]

[[package]]
name = "memoffset"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
dependencies = [
 "autocfg",
]

[[package]]
name = "memoffset"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
dependencies = [
 "autocfg",
]

[[package]]
name = "memoffset"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
dependencies = [
 "autocfg",
]

[[package]]
name = "metal"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de11355d1f6781482d027a3b4d4de7825dcedb197bf573e0596d00008402d060"
dependencies = [
 "bitflags 1.3.2",
 "block",
 "core-graphics-types",
 "foreign-types",
 "log",
 "objc",
]

[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"

[[package]]
name = "miniz_oxide"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
dependencies = [
 "adler",
 "simd-adler32",
]

[[package]]
name = "mint"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e53debba6bda7a793e5f99b8dacf19e626084f525f7829104ba9898f367d85ff"

[[package]]
name = "mio"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
dependencies = [
 "libc",
 "log",
 "wasi",
 "windows-sys 0.48.0",
]

[[package]]
name = "mutate_once"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16cf681a23b4d0a43fc35024c176437f9dcd818db34e0f42ab456a0ee5ad497b"

[[package]]
name = "naga"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbcc2e0513220fd2b598e6068608d4462db20322c0e77e47f6f488dfcfc279cb"
dependencies = [
 "bit-set",
 "bitflags 1.3.2",
 "codespan-reporting",
 "hexf-parse",
 "indexmap 1.9.3",
 "log",
 "num-traits",
 "rustc-hash",
 "spirv",
 "termcolor",
 "thiserror",
 "unicode-xid",
]

[[package]]
name = "nanorand"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3"
dependencies = [
 "getrandom",
]

[[package]]
name = "ndk"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "451422b7e4718271c8b5b3aadf5adedba43dc76312454b387e98fae0fc951aa0"
dependencies = [
 "bitflags 1.3.2",
 "jni-sys",
 "ndk-sys",
 "num_enum 0.5.11",
 "raw-window-handle",
 "thiserror",
]

[[package]]
name = "ndk-context"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b"

[[package]]
name = "ndk-sys"
version = "0.4.1+23.1.7779620"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cf2aae958bd232cac5069850591667ad422d263686d75b52a065f9badeee5a3"
dependencies = [
 "jni-sys",
]

[[package]]
name = "nix"
version = "0.22.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4916f159ed8e5de0082076562152a76b7a1f64a01fd9d1e0fea002c37624faf"
dependencies = [
 "bitflags 1.3.2",
 "cc",
 "cfg-if",
 "libc",
 "memoffset 0.6.5",
]

[[package]]
name = "nix"
version = "0.24.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069"
dependencies = [
 "bitflags 1.3.2",
 "cfg-if",
 "libc",
 "memoffset 0.6.5",
]

[[package]]
name = "nix"
version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4"
dependencies = [
 "autocfg",
 "bitflags 1.3.2",
 "cfg-if",
 "libc",
 "memoffset 0.6.5",
]

[[package]]
name = "nix"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b"
dependencies = [
 "bitflags 1.3.2",
 "cfg-if",
 "libc",
 "memoffset 0.7.1",
 "pin-utils",
]

[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
 "memchr",
 "minimal-lexical",
]

[[package]]
name = "num-integer"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
 "autocfg",
 "num-traits",
]

[[package]]
name = "num-rational"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
dependencies = [
 "autocfg",
 "num-integer",
 "num-traits",
]

[[package]]
name = "num-traits"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
dependencies = [
 "autocfg",
 "libm",
]

[[package]]
name = "num_cpus"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
dependencies = [
 "hermit-abi",
 "libc",
]

[[package]]
name = "num_enum"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9"
dependencies = [
 "num_enum_derive 0.5.11",
]

[[package]]
name = "num_enum"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a015b430d3c108a207fd776d2e2196aaf8b1cf8cf93253e3a097ff3085076a1"
dependencies = [
 "num_enum_derive 0.6.1",
]

[[package]]
name = "num_enum_derive"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799"
dependencies = [
 "proc-macro-crate",
 "proc-macro2",
 "quote",
 "syn 1.0.109",
]

[[package]]
name = "num_enum_derive"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6"
dependencies = [
 "proc-macro-crate",
 "proc-macro2",
 "quote",
 "syn 2.0.38",
]

[[package]]
name = "objc"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
dependencies = [
 "malloc_buf",
 "objc_exception",
]

[[package]]
name = "objc-foundation"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9"
dependencies = [
 "block",
 "objc",
 "objc_id",
]

[[package]]
name = "objc-sys"
version = "0.2.0-beta.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b9834c1e95694a05a828b59f55fa2afec6288359cda67146126b3f90a55d7"

[[package]]
name = "objc2"
version = "0.3.0-beta.3.patch-leaks.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e01640f9f2cb1220bbe80325e179e532cb3379ebcd1bf2279d703c19fe3a468"
dependencies = [
 "block2",
 "objc-sys",
 "objc2-encode",
]

[[package]]
name = "objc2-encode"
version = "2.0.0-pre.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abfcac41015b00a120608fdaa6938c44cb983fee294351cc4bac7638b4e50512"
dependencies = [
 "objc-sys",
]

[[package]]
name = "objc_exception"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4"
dependencies = [
 "cc",
]

[[package]]
name = "objc_id"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b"
dependencies = [
 "objc",
]

[[package]]
name = "object"
version = "0.32.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0"
dependencies = [
 "memchr",
]

[[package]]
name = "once_cell"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"

[[package]]
name = "orbclient"
version = "0.3.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8378ac0dfbd4e7895f2d2c1f1345cab3836910baf3a300b000d04250f0c8428f"
dependencies = [
 "redox_syscall 0.3.5",
]

[[package]]
name = "ouroboros"
version = "0.17.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2ba07320d39dfea882faa70554b4bd342a5f273ed59ba7c1c6b4c840492c954"
dependencies = [
 "aliasable",
 "ouroboros_macro",
 "static_assertions",
]

[[package]]
name = "ouroboros_macro"
version = "0.17.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec4c6225c69b4ca778c0aea097321a64c421cf4577b331c61b229267edabb6f8"
dependencies = [
 "heck",
 "proc-macro-error",
 "proc-macro2",
 "quote",
 "syn 2.0.38",
]

[[package]]
name = "owned_ttf_parser"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "706de7e2214113d63a8238d1910463cfce781129a6f263d13fdb09ff64355ba4"
dependencies = [
 "ttf-parser 0.19.2",
]

[[package]]
name = "palette"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2e2f34147767aa758aa649415b50a69eeb46a67f9dc7db8011eeb3d84b351dc"
dependencies = [
 "approx",
 "fast-srgb8",
 "palette_derive",
 "phf",
]

[[package]]
name = "palette_derive"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7db010ec5ff3d4385e4f133916faacd9dad0f6a09394c92d825b3aed310fa0a"
dependencies = [
 "proc-macro2",
 "quote",
 "syn 2.0.38",
]

[[package]]
name = "parking_lot"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
dependencies = [
 "instant",
 "lock_api",
 "parking_lot_core 0.8.6",
]

[[package]]
name = "parking_lot"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [
 "lock_api",
 "parking_lot_core 0.9.9",
]

[[package]]
name = "parking_lot_core"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
dependencies = [
 "cfg-if",
 "instant",
 "libc",
 "redox_syscall 0.2.16",
 "smallvec",
 "winapi",
]

[[package]]
name = "parking_lot_core"
version = "0.9.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
dependencies = [
 "cfg-if",
 "libc",
 "redox_syscall 0.4.1",
 "smallvec",
 "windows-targets 0.48.5",
]

[[package]]
name = "percent-encoding"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"

[[package]]
name = "phf"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
dependencies = [
 "phf_macros",
 "phf_shared",
]

[[package]]
name = "phf_generator"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0"
dependencies = [
 "phf_shared",
 "rand",
]

[[package]]
name = "phf_macros"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b"
dependencies = [
 "phf_generator",
 "phf_shared",
 "proc-macro2",
 "quote",
 "syn 2.0.38",
]

[[package]]
name = "phf_shared"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b"
dependencies = [
 "siphasher",
]

[[package]]
name = "pico-args"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"

[[package]]
name = "pin-project"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422"
dependencies = [
 "pin-project-internal",
]

[[package]]
name = "pin-project-internal"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
dependencies = [
 "proc-macro2",
 "quote",
 "syn 2.0.38",
]

[[package]]
name = "pin-project-lite"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"

[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"

[[package]]
name = "pkg-config"
version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"

[[package]]
name = "png"
version = "0.17.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd75bf2d8dd3702b9707cdbc56a5b9ef42cec752eb8b3bafc01234558442aa64"
dependencies = [
 "bitflags 1.3.2",
 "crc32fast",
 "fdeflate",
 "flate2",
 "miniz_oxide",
]

[[package]]
name = "powerfmt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"

[[package]]
name = "ppv-lite86"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"

[[package]]
name = "proc-macro-crate"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
dependencies = [
 "once_cell",
 "toml_edit 0.19.15",
]

[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
 "proc-macro-error-attr",
 "proc-macro2",
 "quote",
 "syn 1.0.109",
 "version_check",
]

[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
 "proc-macro2",
 "quote",
 "version_check",
]

[[package]]
name = "proc-macro2"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
dependencies = [
 "unicode-ident",
]

[[package]]
name = "profiling"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f89dff0959d98c9758c88826cc002e2c3d0b9dfac4139711d1f30de442f1139b"

[[package]]
name = "qoi"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001"
dependencies = [
 "bytemuck",
]

[[package]]
name = "quick-xml"
version = "0.28.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ce5e73202a820a31f8a0ee32ada5e21029c81fd9e3ebf668a40832e4219d9d1"
dependencies = [
 "memchr",
]

[[package]]
name = "quote"
version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
dependencies = [
 "proc-macro2",
]

[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
 "libc",
 "rand_chacha",
 "rand_core",
]

[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
 "ppv-lite86",
 "rand_core",
]

[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
 "getrandom",
]

[[package]]
name = "range-alloc"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8a99fddc9f0ba0a85884b8d14e3592853e787d581ca1816c91349b10e4eeab"

[[package]]
name = "rangemap"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "977b1e897f9d764566891689e642653e5ed90c6895106acd005eb4c1d0203991"

[[package]]
name = "raw-window-handle"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9"

[[package]]
name = "rayon"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1"
dependencies = [
 "either",
 "rayon-core",
]

[[package]]
name = "rayon-core"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed"
dependencies = [
 "crossbeam-deque",
 "crossbeam-utils",
]

[[package]]
name = "rctree"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b42e27ef78c35d3998403c1d26f3efd9e135d3e5121b0a4845cc5cc27547f4f"

[[package]]
name = "redox_syscall"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
 "bitflags 1.3.2",
]

[[package]]
name = "redox_syscall"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
dependencies = [
 "bitflags 1.3.2",
]

[[package]]
name = "redox_syscall"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
dependencies = [
 "bitflags 1.3.2",
]

[[package]]
name = "renderdoc-sys"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "216080ab382b992234dda86873c18d4c48358f5cfcb70fd693d7f6f2131b628b"

[[package]]
name = "resvg"
version = "0.35.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6554f47c38eca56827eea7f285c2a3018b4e12e0e195cc105833c008be338f1"
dependencies = [
 "gif",
 "jpeg-decoder",
 "log",
 "pico-args",
 "png",
 "rgb",
 "svgtypes",
 "tiny-skia 0.10.0",
 "usvg",
]

[[package]]
name = "rgb"
version = "0.8.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20ec2d3e3fc7a92ced357df9cebd5a10b6fb2aa1ee797bf7e9ce2f17dffc8f59"
dependencies = [
 "bytemuck",
]

[[package]]
name = "roxmltree"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "862340e351ce1b271a378ec53f304a5558f7db87f3769dc655a8f6ecbb68b302"
dependencies = [
 "xmlparser",
]

[[package]]
name = "rustc-demangle"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"

[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"

[[package]]
name = "rustybuzz"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "162bdf42e261bee271b3957691018634488084ef577dddeb6420a9684cab2a6a"
dependencies = [
 "bitflags 1.3.2",
 "bytemuck",
 "smallvec",
 "ttf-parser 0.18.1",
 "unicode-bidi-mirroring",
 "unicode-ccc",
 "unicode-general-category",
 "unicode-script",
]

[[package]]
name = "rustybuzz"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82eea22c8f56965eeaf3a209b3d24508256c7b920fb3b6211b8ba0f7c0583250"
dependencies = [
 "bitflags 1.3.2",
 "bytemuck",
 "libm",
 "smallvec",
 "ttf-parser 0.19.2",
 "unicode-bidi-mirroring",
 "unicode-ccc",
 "unicode-general-category",
 "unicode-script",
]

[[package]]
name = "ryu"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"

[[package]]
name = "scoped-tls"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"

[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"

[[package]]
name = "sctk-adwaita"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cda4e97be1fd174ccc2aae81c8b694e803fa99b34e8fd0f057a9d70698e3ed09"
dependencies = [
 "ab_glyph",
 "log",
 "memmap2 0.5.10",
 "smithay-client-toolkit",
 "tiny-skia 0.8.4",
]

[[package]]
name = "serde"
version = "1.0.189"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537"
dependencies = [
 "serde_derive",
]

[[package]]
name = "serde_derive"
version = "1.0.189"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5"
dependencies = [
 "proc-macro2",
 "quote",
 "syn 2.0.38",
]

[[package]]
name = "serde_json"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65"
dependencies = [
 "itoa",
 "ryu",
 "serde",
]

[[package]]
name = "serde_spanned"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12022b835073e5b11e90a14f86838ceb1c8fb0325b72416845c487ac0fa95e80"
dependencies = [
 "serde",
]

[[package]]
name = "sha1"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
dependencies = [
 "cfg-if",
 "cpufeatures",
 "digest",
]

[[package]]
name = "shalom"
version = "0.1.0"
dependencies = [
 "iced",
 "image",
 "itertools",
 "keyframe",
 "once_cell",
 "serde",
 "serde_json",
 "time",
 "tokio",
 "tokio-tungstenite",
 "toml",
]

[[package]]
name = "simd-adler32"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"

[[package]]
name = "simplecss"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a11be7c62927d9427e9f40f3444d5499d868648e2edbc4e2116de69e7ec0e89d"
dependencies = [
 "log",
]

[[package]]
name = "siphasher"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"

[[package]]
name = "slab"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
dependencies = [
 "autocfg",
]

[[package]]
name = "slotmap"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342"
dependencies = [
 "version_check",
]

[[package]]
name = "smallvec"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a"

[[package]]
name = "smithay-client-toolkit"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "870427e30b8f2cbe64bf43ec4b86e88fe39b0a84b3f15efd9c9c2d020bc86eb9"
dependencies = [
 "bitflags 1.3.2",
 "calloop",
 "dlib",
 "lazy_static",
 "log",
 "memmap2 0.5.10",
 "nix 0.24.3",
 "pkg-config",
 "wayland-client 0.29.5",
 "wayland-cursor",
 "wayland-protocols",
]

[[package]]
name = "smithay-clipboard"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a345c870a1fae0b1b779085e81b51e614767c239e93503588e54c5b17f4b0e8"
dependencies = [
 "smithay-client-toolkit",
 "wayland-client 0.29.5",
]

[[package]]
name = "socket2"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
dependencies = [
 "libc",
 "windows-sys 0.48.0",
]

[[package]]
name = "softbuffer"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2b953f6ba7285f0af131eb748aabd8ddaf53e0b81dda3ba5d803b0847d6559f"
dependencies = [
 "bytemuck",
 "cfg_aliases",
 "cocoa",
 "core-graphics",
 "fastrand",
 "foreign-types",
 "log",
 "nix 0.26.4",
 "objc",
 "raw-window-handle",
 "redox_syscall 0.3.5",
 "thiserror",
 "wasm-bindgen",
 "wayland-backend",
 "wayland-client 0.30.2",
 "wayland-sys 0.30.1",
 "web-sys",
 "windows-sys 0.48.0",
 "x11-dl",
 "x11rb 0.11.1",
]

[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
dependencies = [
 "lock_api",
]

[[package]]
name = "spirv"
version = "0.2.0+1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "246bfa38fe3db3f1dfc8ca5a2cdeb7348c78be2112740cc0ec8ef18b6d94f830"
dependencies = [
 "bitflags 1.3.2",
 "num-traits",
]

[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"

[[package]]
name = "str-buf"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0"

[[package]]
name = "strict-num"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
dependencies = [
 "float-cmp",
]

[[package]]
name = "svg_fmt"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fb1df15f412ee2e9dfc1c504260fa695c1c3f10fe9f4a6ee2d2184d7d6450e2"

[[package]]
name = "svgtypes"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed4b0611e7f3277f68c0fa18e385d9e2d26923691379690039548f867cef02a7"
dependencies = [
 "kurbo",
 "siphasher",
]

[[package]]
name = "swash"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b7c73c813353c347272919aa1af2885068b05e625e5532b43049e4f641ae77f"
dependencies = [
 "yazi",
 "zeno",
]

[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
 "proc-macro2",
 "quote",
 "unicode-ident",
]

[[package]]
name = "syn"
version = "2.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
dependencies = [
 "proc-macro2",
 "quote",
 "unicode-ident",
]

[[package]]
name = "sys-locale"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e801cf239ecd6ccd71f03d270d67dd53d13e90aab208bf4b8fe4ad957ea949b0"
dependencies = [
 "libc",
]

[[package]]
name = "termcolor"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64"
dependencies = [
 "winapi-util",
]

[[package]]
name = "thiserror"
version = "1.0.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2"
dependencies = [
 "thiserror-impl",
]

[[package]]
name = "thiserror-impl"
version = "1.0.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
dependencies = [
 "proc-macro2",
 "quote",
 "syn 2.0.38",
]

[[package]]
name = "tiff"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d172b0f4d3fba17ba89811858b9d3d97f928aece846475bbda076ca46736211"
dependencies = [
 "flate2",
 "jpeg-decoder",
 "weezl",
]

[[package]]
name = "time"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5"
dependencies = [
 "deranged",
 "powerfmt",
 "serde",
 "time-core",
]

[[package]]
name = "time-core"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"

[[package]]
name = "tiny-skia"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df8493a203431061e901613751931f047d1971337153f96d0e5e363d6dbf6a67"
dependencies = [
 "arrayref",
 "arrayvec",
 "bytemuck",
 "cfg-if",
 "png",
 "tiny-skia-path 0.8.4",
]

[[package]]
name = "tiny-skia"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7db11798945fa5c3e5490c794ccca7c6de86d3afdd54b4eb324109939c6f37bc"
dependencies = [
 "arrayref",
 "arrayvec",
 "bytemuck",
 "cfg-if",
 "log",
 "png",
 "tiny-skia-path 0.10.0",
]

[[package]]
name = "tiny-skia-path"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adbfb5d3f3dd57a0e11d12f4f13d4ebbbc1b5c15b7ab0a156d030b21da5f677c"
dependencies = [
 "arrayref",
 "bytemuck",
 "strict-num",
]

[[package]]
name = "tiny-skia-path"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f60aa35c89ac2687ace1a2556eaaea68e8c0d47408a2e3e7f5c98a489e7281c"
dependencies = [
 "arrayref",
 "bytemuck",
 "strict-num",
]

[[package]]
name = "tinyvec"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
dependencies = [
 "tinyvec_macros",
]

[[package]]
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"

[[package]]
name = "tokio"
version = "1.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653"
dependencies = [
 "backtrace",
 "bytes",
 "libc",
 "mio",
 "num_cpus",
 "pin-project-lite",
 "socket2",
 "tokio-macros",
 "windows-sys 0.48.0",
]

[[package]]
name = "tokio-macros"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
dependencies = [
 "proc-macro2",
 "quote",
 "syn 2.0.38",
]

[[package]]
name = "tokio-tungstenite"
version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c"
dependencies = [
 "futures-util",
 "log",
 "tokio",
 "tungstenite",
]

[[package]]
name = "toml"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ff9e3abce27ee2c9a37f9ad37238c1bdd4e789c84ba37df76aa4d528f5072cc"
dependencies = [
 "serde",
 "serde_spanned",
 "toml_datetime",
 "toml_edit 0.20.7",
]

[[package]]
name = "toml_datetime"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
dependencies = [
 "serde",
]

[[package]]
name = "toml_edit"
version = "0.19.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
dependencies = [
 "indexmap 2.0.2",
 "toml_datetime",
 "winnow",
]

[[package]]
name = "toml_edit"
version = "0.20.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81"
dependencies = [
 "indexmap 2.0.2",
 "serde",
 "serde_spanned",
 "toml_datetime",
 "winnow",
]

[[package]]
name = "ttf-parser"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0609f771ad9c6155384897e1df4d948e692667cc0588548b68eb44d052b27633"

[[package]]
name = "ttf-parser"
version = "0.19.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49d64318d8311fc2668e48b63969f4343e0a85c4a109aa8460d6672e364b8bd1"

[[package]]
name = "tungstenite"
version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9"
dependencies = [
 "byteorder",
 "bytes",
 "data-encoding",
 "http",
 "httparse",
 "log",
 "rand",
 "sha1",
 "thiserror",
 "url",
 "utf-8",
]

[[package]]
name = "twox-hash"
version = "1.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675"
dependencies = [
 "cfg-if",
 "rand",
 "static_assertions",
]

[[package]]
name = "typenum"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"

[[package]]
name = "unicode-bidi"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"

[[package]]
name = "unicode-bidi-mirroring"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56d12260fb92d52f9008be7e4bca09f584780eb2266dc8fecc6a192bec561694"

[[package]]
name = "unicode-ccc"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc2520efa644f8268dce4dcd3050eaa7fc044fca03961e9998ac7e2e92b77cf1"

[[package]]
name = "unicode-general-category"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2281c8c1d221438e373249e065ca4989c4c36952c211ff21a0ee91c44a3869e7"

[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"

[[package]]
name = "unicode-linebreak"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"

[[package]]
name = "unicode-normalization"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
dependencies = [
 "tinyvec",
]

[[package]]
name = "unicode-script"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d817255e1bed6dfd4ca47258685d14d2bdcfbc64fdc9e3819bd5848057b8ecc"

[[package]]
name = "unicode-segmentation"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"

[[package]]
name = "unicode-vo"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94"

[[package]]
name = "unicode-width"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"

[[package]]
name = "unicode-xid"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"

[[package]]
name = "url"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
dependencies = [
 "form_urlencoded",
 "idna",
 "percent-encoding",
]

[[package]]
name = "usvg"
version = "0.35.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14d09ddfb0d93bf84824c09336d32e42f80961a9d1680832eb24fdf249ce11e6"
dependencies = [
 "base64",
 "log",
 "pico-args",
 "usvg-parser",
 "usvg-text-layout",
 "usvg-tree",
 "xmlwriter",
]

[[package]]
name = "usvg-parser"
version = "0.35.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d19bf93d230813599927d88557014e0908ecc3531666d47c634c6838bc8db408"
dependencies = [
 "data-url",
 "flate2",
 "imagesize",
 "kurbo",
 "log",
 "roxmltree",
 "simplecss",
 "siphasher",
 "svgtypes",
 "usvg-tree",
]

[[package]]
name = "usvg-text-layout"
version = "0.35.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "035044604e89652c0a2959b8b356946997a52649ba6cade45928c2842376feb4"
dependencies = [
 "fontdb",
 "kurbo",
 "log",
 "rustybuzz 0.7.0",
 "unicode-bidi",
 "unicode-script",
 "unicode-vo",
 "usvg-tree",
]

[[package]]
name = "usvg-tree"
version = "0.35.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7939a7e4ed21cadb5d311d6339730681c3e24c3e81d60065be80e485d3fc8b92"
dependencies = [
 "rctree",
 "strict-num",
 "svgtypes",
 "tiny-skia-path 0.10.0",
]

[[package]]
name = "utf-8"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"

[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"

[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"

[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"

[[package]]
name = "wasm-bindgen"
version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342"
dependencies = [
 "cfg-if",
 "wasm-bindgen-macro",
]

[[package]]
name = "wasm-bindgen-backend"
version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd"
dependencies = [
 "bumpalo",
 "log",
 "once_cell",
 "proc-macro2",
 "quote",
 "syn 2.0.38",
 "wasm-bindgen-shared",
]

[[package]]
name = "wasm-bindgen-futures"
version = "0.4.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03"
dependencies = [
 "cfg-if",
 "js-sys",
 "wasm-bindgen",
 "web-sys",
]

[[package]]
name = "wasm-bindgen-macro"
version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d"
dependencies = [
 "quote",
 "wasm-bindgen-macro-support",
]

[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
dependencies = [
 "proc-macro2",
 "quote",
 "syn 2.0.38",
 "wasm-bindgen-backend",
 "wasm-bindgen-shared",
]

[[package]]
name = "wasm-bindgen-shared"
version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1"

[[package]]
name = "wasm-timer"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f"
dependencies = [
 "futures",
 "js-sys",
 "parking_lot 0.11.2",
 "pin-utils",
 "wasm-bindgen",
 "wasm-bindgen-futures",
 "web-sys",
]

[[package]]
name = "wayland-backend"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41b48e27457e8da3b2260ac60d0a94512f5cba36448679f3747c0865b7893ed8"
dependencies = [
 "cc",
 "downcast-rs",
 "io-lifetimes",
 "nix 0.26.4",
 "scoped-tls",
 "smallvec",
 "wayland-sys 0.30.1",
]

[[package]]
name = "wayland-client"
version = "0.29.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f3b068c05a039c9f755f881dc50f01732214f5685e379829759088967c46715"
dependencies = [
 "bitflags 1.3.2",
 "downcast-rs",
 "libc",
 "nix 0.24.3",
 "scoped-tls",
 "wayland-commons",
 "wayland-scanner 0.29.5",
 "wayland-sys 0.29.5",
]

[[package]]
name = "wayland-client"
version = "0.30.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "489c9654770f674fc7e266b3c579f4053d7551df0ceb392f153adb1f9ed06ac8"
dependencies = [
 "bitflags 1.3.2",
 "nix 0.26.4",
 "wayland-backend",
 "wayland-scanner 0.30.1",
]

[[package]]
name = "wayland-commons"
version = "0.29.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8691f134d584a33a6606d9d717b95c4fa20065605f798a3f350d78dced02a902"
dependencies = [
 "nix 0.24.3",
 "once_cell",
 "smallvec",
 "wayland-sys 0.29.5",
]

[[package]]
name = "wayland-cursor"
version = "0.29.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661"
dependencies = [
 "nix 0.24.3",
 "wayland-client 0.29.5",
 "xcursor",
]

[[package]]
name = "wayland-protocols"
version = "0.29.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6"
dependencies = [
 "bitflags 1.3.2",
 "wayland-client 0.29.5",
 "wayland-commons",
 "wayland-scanner 0.29.5",
]

[[package]]
name = "wayland-scanner"
version = "0.29.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f4303d8fa22ab852f789e75a967f0a2cdc430a607751c0499bada3e451cbd53"
dependencies = [
 "proc-macro2",
 "quote",
 "xml-rs",
]

[[package]]
name = "wayland-scanner"
version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9b873b257fbc32ec909c0eb80dea312076a67014e65e245f5eb69a6b8ab330e"
dependencies = [
 "proc-macro2",
 "quick-xml",
 "quote",
]

[[package]]
name = "wayland-sys"
version = "0.29.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be12ce1a3c39ec7dba25594b97b42cb3195d54953ddb9d3d95a7c3902bc6e9d4"
dependencies = [
 "dlib",
 "lazy_static",
 "pkg-config",
]

[[package]]
name = "wayland-sys"
version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96b2a02ac608e07132978689a6f9bf4214949c85998c247abadd4f4129b1aa06"
dependencies = [
 "dlib",
 "lazy_static",
 "log",
 "pkg-config",
]

[[package]]
name = "web-sys"
version = "0.3.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b"
dependencies = [
 "js-sys",
 "wasm-bindgen",
]

[[package]]
name = "weezl"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb"

[[package]]
name = "wgpu"
version = "0.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "480c965c9306872eb6255fa55e4b4953be55a8b64d57e61d7ff840d3dcc051cd"
dependencies = [
 "arrayvec",
 "cfg-if",
 "js-sys",
 "log",
 "naga",
 "parking_lot 0.12.1",
 "profiling",
 "raw-window-handle",
 "smallvec",
 "static_assertions",
 "wasm-bindgen",
 "wasm-bindgen-futures",
 "web-sys",
 "wgpu-core",
 "wgpu-hal",
 "wgpu-types",
]

[[package]]
name = "wgpu-core"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f478237b4bf0d5b70a39898a66fa67ca3a007d79f2520485b8b0c3dfc46f8c2"
dependencies = [
 "arrayvec",
 "bit-vec",
 "bitflags 2.4.1",
 "codespan-reporting",
 "log",
 "naga",
 "parking_lot 0.12.1",
 "profiling",
 "raw-window-handle",
 "rustc-hash",
 "smallvec",
 "thiserror",
 "web-sys",
 "wgpu-hal",
 "wgpu-types",
]

[[package]]
name = "wgpu-hal"
version = "0.16.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ecb3258078e936deee14fd4e0febe1cfe9bbb5ffef165cb60218d2ee5eb4448"
dependencies = [
 "android_system_properties",
 "arrayvec",
 "ash",
 "bit-set",
 "bitflags 2.4.1",
 "block",
 "core-graphics-types",
 "d3d12",
 "foreign-types",
 "glow",
 "gpu-alloc",
 "gpu-allocator",
 "gpu-descriptor",
 "hassle-rs",
 "js-sys",
 "khronos-egl",
 "libc",
 "libloading 0.8.1",
 "log",
 "metal",
 "naga",
 "objc",
 "parking_lot 0.12.1",
 "profiling",
 "range-alloc",
 "raw-window-handle",
 "renderdoc-sys",
 "rustc-hash",
 "smallvec",
 "thiserror",
 "wasm-bindgen",
 "web-sys",
 "wgpu-types",
 "winapi",
]

[[package]]
name = "wgpu-types"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0c153280bb108c2979eb5c7391cb18c56642dd3c072e55f52065e13e2a1252a"
dependencies = [
 "bitflags 2.4.1",
 "js-sys",
 "web-sys",
]

[[package]]
name = "widestring"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8"

[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
 "winapi-i686-pc-windows-gnu",
 "winapi-x86_64-pc-windows-gnu",
]

[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"

[[package]]
name = "winapi-util"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
dependencies = [
 "winapi",
]

[[package]]
name = "winapi-wsapoll"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c17110f57155602a80dca10be03852116403c9ff3cd25b079d666f2aa3df6e"
dependencies = [
 "winapi",
]

[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

[[package]]
name = "window_clipboard"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63287c9c4396ccf5346d035a9b0fcaead9e18377637f5eaa78b7ac65c873ff7d"
dependencies = [
 "clipboard-win",
 "clipboard_macos",
 "clipboard_wayland",
 "clipboard_x11",
 "raw-window-handle",
 "thiserror",
]

[[package]]
name = "windows"
version = "0.44.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b"
dependencies = [
 "windows-targets 0.42.2",
]

[[package]]
name = "windows-sys"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
dependencies = [
 "windows-targets 0.42.2",
]

[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
 "windows-targets 0.48.5",
]

[[package]]
name = "windows-targets"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
dependencies = [
 "windows_aarch64_gnullvm 0.42.2",
 "windows_aarch64_msvc 0.42.2",
 "windows_i686_gnu 0.42.2",
 "windows_i686_msvc 0.42.2",
 "windows_x86_64_gnu 0.42.2",
 "windows_x86_64_gnullvm 0.42.2",
 "windows_x86_64_msvc 0.42.2",
]

[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
 "windows_aarch64_gnullvm 0.48.5",
 "windows_aarch64_msvc 0.48.5",
 "windows_i686_gnu 0.48.5",
 "windows_i686_msvc 0.48.5",
 "windows_x86_64_gnu 0.48.5",
 "windows_x86_64_gnullvm 0.48.5",
 "windows_x86_64_msvc 0.48.5",
]

[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"

[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"

[[package]]
name = "windows_aarch64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"

[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"

[[package]]
name = "windows_i686_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"

[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"

[[package]]
name = "windows_i686_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"

[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"

[[package]]
name = "windows_x86_64_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"

[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"

[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"

[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"

[[package]]
name = "windows_x86_64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"

[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"

[[package]]
name = "winit"
version = "0.28.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9596d90b45384f5281384ab204224876e8e8bf7d58366d9b795ad99aa9894b94"
dependencies = [
 "android-activity",
 "bitflags 1.3.2",
 "cfg_aliases",
 "core-foundation",
 "core-graphics",
 "dispatch",
 "instant",
 "libc",
 "log",
 "mio",
 "ndk",
 "objc2",
 "once_cell",
 "orbclient",
 "percent-encoding",
 "raw-window-handle",
 "redox_syscall 0.3.5",
 "sctk-adwaita",
 "smithay-client-toolkit",
 "wasm-bindgen",
 "wayland-client 0.29.5",
 "wayland-commons",
 "wayland-protocols",
 "wayland-scanner 0.29.5",
 "web-sys",
 "windows-sys 0.45.0",
 "x11-dl",
]

[[package]]
name = "winnow"
version = "0.5.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3b801d0e0a6726477cc207f60162da452f3a95adb368399bef20a946e06f65c"
dependencies = [
 "memchr",
]

[[package]]
name = "x11-dl"
version = "2.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f"
dependencies = [
 "libc",
 "once_cell",
 "pkg-config",
]

[[package]]
name = "x11rb"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e99be55648b3ae2a52342f9a870c0e138709a3493261ce9b469afe6e4df6d8a"
dependencies = [
 "gethostname",
 "nix 0.22.3",
 "winapi",
 "winapi-wsapoll",
]

[[package]]
name = "x11rb"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdf3c79412dd91bae7a7366b8ad1565a85e35dd049affc3a6a2c549e97419617"
dependencies = [
 "gethostname",
 "libc",
 "libloading 0.7.4",
 "nix 0.25.1",
 "once_cell",
 "winapi",
 "winapi-wsapoll",
 "x11rb-protocol",
]

[[package]]
name = "x11rb-protocol"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0b1513b141123073ce54d5bb1d33f801f17508fbd61e02060b1214e96d39c56"
dependencies = [
 "nix 0.25.1",
]

[[package]]
name = "xcursor"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "463705a63313cd4301184381c5e8042f0a7e9b4bb63653f216311d4ae74690b7"
dependencies = [
 "nom",
]

[[package]]
name = "xml-rs"
version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a"

[[package]]
name = "xmlparser"
version = "0.13.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4"

[[package]]
name = "xmlwriter"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"

[[package]]
name = "yazi"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c94451ac9513335b5e23d7a8a2b61a7102398b8cca5160829d313e84c9d98be1"

[[package]]
name = "zeno"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd15f8e0dbb966fd9245e7498c7e9e5055d9e5c8b676b95bd67091cd11a1e697"

[[package]]
name = "zune-inflate"
version = "0.2.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02"
dependencies = [
 "simd-adler32",
]
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..277348f
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,8 @@
[workspace]
resolver = "2"
members = [
    "shalom"
]

[profile.dev.package.image]
opt-level = 3
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..83d5fa6
--- /dev/null
+++ b/README.md
@@ -0,0 +1,8 @@
# shalom

A work-in-progress native UI for Home Manager, designed to
be ran on a Raspberry Pi with a touch-screen for a "home
automation tablet" experience, but can also be used on the
desktop if so desired.

Written in Rust using [iced]https://github.com/iced-rs/iced.
diff --git a/assets/icons/README.md b/assets/icons/README.md
new file mode 100644
index 0000000..2aa9bfb
--- /dev/null
+++ b/assets/icons/README.md
@@ -0,0 +1,3 @@
# Resources

- https://heroicons.com/
diff --git a/assets/icons/back.svg b/assets/icons/back.svg
new file mode 100644
index 0000000..8572e1d
--- /dev/null
+++ b/assets/icons/back.svg
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
    <path stroke-linecap="round" stroke-linejoin="round" d="M9 15L3 9m0 0l6-6M3 9h12a6 6 0 010 12h-3" />
</svg>
diff --git a/assets/icons/backward.svg b/assets/icons/backward.svg
new file mode 100644
index 0000000..a358c55
--- /dev/null
+++ b/assets/icons/backward.svg
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
    <path stroke-linecap="round" stroke-linejoin="round" d="M21 16.811c0 .864-.933 1.405-1.683.977l-7.108-4.062a1.125 1.125 0 010-1.953l7.108-4.062A1.125 1.125 0 0121 8.688v8.123zM11.25 16.811c0 .864-.933 1.405-1.683.977l-7.108-4.062a1.125 1.125 0 010-1.953L9.567 7.71a1.125 1.125 0 011.683.977v8.123z" />
</svg>
diff --git a/assets/icons/forward.svg b/assets/icons/forward.svg
new file mode 100644
index 0000000..7366fe5
--- /dev/null
+++ b/assets/icons/forward.svg
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
    <path stroke-linecap="round" stroke-linejoin="round" d="M3 8.688c0-.864.933-1.405 1.683-.977l7.108 4.062a1.125 1.125 0 010 1.953l-7.108 4.062A1.125 1.125 0 013 16.81V8.688zM12.75 8.688c0-.864.933-1.405 1.683-.977l7.108 4.062a1.125 1.125 0 010 1.953l-7.108 4.062a1.125 1.125 0 01-1.683-.977V8.688z" />
</svg>
diff --git a/assets/icons/hamburger.svg b/assets/icons/hamburger.svg
new file mode 100644
index 0000000..f1788e7
--- /dev/null
+++ b/assets/icons/hamburger.svg
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
    <path stroke-linecap="round" stroke-linejoin="round" d="M3.75 5.25h16.5m-16.5 4.5h16.5m-16.5 4.5h16.5m-16.5 4.5h16.5" />
</svg>
diff --git a/assets/icons/home.svg b/assets/icons/home.svg
new file mode 100644
index 0000000..4cf85cc
--- /dev/null
+++ b/assets/icons/home.svg
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
    <path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25" />
</svg>
diff --git a/assets/icons/light-bulb.svg b/assets/icons/light-bulb.svg
new file mode 100644
index 0000000..f77dcf0
--- /dev/null
+++ b/assets/icons/light-bulb.svg
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
    <path stroke-linecap="round" stroke-linejoin="round" d="M12 18v-5.25m0 0a6.01 6.01 0 001.5-.189m-1.5.189a6.01 6.01 0 01-1.5-.189m3.75 7.478a12.06 12.06 0 01-4.5 0m3.75 2.383a14.406 14.406 0 01-3 0M14.25 18v-.192c0-.983.658-1.823 1.508-2.316a7.5 7.5 0 10-7.517 0c.85.493 1.509 1.333 1.509 2.316V18" />
</svg>
diff --git a/assets/icons/pause.svg b/assets/icons/pause.svg
new file mode 100644
index 0000000..68225b1
--- /dev/null
+++ b/assets/icons/pause.svg
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
    <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 5.25v13.5m-7.5-13.5v13.5" />
</svg>
diff --git a/assets/icons/play.svg b/assets/icons/play.svg
new file mode 100644
index 0000000..cecf998
--- /dev/null
+++ b/assets/icons/play.svg
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
    <path stroke-linecap="round" stroke-linejoin="round" d="M5.25 5.653c0-.856.917-1.398 1.667-.986l11.54 6.348a1.125 1.125 0 010 1.971l-11.54 6.347a1.125 1.125 0 01-1.667-.985V5.653z" />
</svg>
diff --git a/assets/icons/repeat.svg b/assets/icons/repeat.svg
new file mode 100644
index 0000000..e63b652
--- /dev/null
+++ b/assets/icons/repeat.svg
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
    <path stroke-linecap="round" stroke-linejoin="round" d="M19.5 12c0-1.232-.046-2.453-.138-3.662a4.006 4.006 0 00-3.7-3.7 48.678 48.678 0 00-7.324 0 4.006 4.006 0 00-3.7 3.7c-.017.22-.032.441-.046.662M19.5 12l3-3m-3 3l-3-3m-12 3c0 1.232.046 2.453.138 3.662a4.006 4.006 0 003.7 3.7 48.656 48.656 0 007.324 0 4.006 4.006 0 003.7-3.7c.017-.22.032-.441.046-.662M4.5 12l3 3m-3-3l-3 3" />
</svg>
diff --git a/assets/icons/speaker-muted.svg b/assets/icons/speaker-muted.svg
new file mode 100644
index 0000000..e98295a
--- /dev/null
+++ b/assets/icons/speaker-muted.svg
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
    <path stroke-linecap="round" stroke-linejoin="round" d="M17.25 9.75L19.5 12m0 0l2.25 2.25M19.5 12l2.25-2.25M19.5 12l-2.25 2.25m-10.5-6l4.72-4.72a.75.75 0 011.28.531V19.94a.75.75 0 01-1.28.53l-4.72-4.72H4.51c-.88 0-1.704-.506-1.938-1.354A9.01 9.01 0 012.25 12c0-.83.112-1.633.322-2.395C2.806 8.757 3.63 8.25 4.51 8.25H6.75z" />
</svg>
diff --git a/assets/icons/speaker.svg b/assets/icons/speaker.svg
new file mode 100644
index 0000000..804f3f6
--- /dev/null
+++ b/assets/icons/speaker.svg
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
    <path stroke-linecap="round" stroke-linejoin="round" d="M19.114 5.636a9 9 0 010 12.728M16.463 8.288a5.25 5.25 0 010 7.424M6.75 8.25l4.72-4.72a.75.75 0 011.28.53v15.88a.75.75 0 01-1.28.53l-4.72-4.72H4.51c-.88 0-1.704-.507-1.938-1.354A9.01 9.01 0 012.25 12c0-.83.112-1.633.322-2.396C2.806 8.756 3.63 8.25 4.51 8.25H6.75z" />
</svg>
diff --git a/assets/images/bathroom.jpg b/assets/images/bathroom.jpg
new file mode 100644
index 0000000..4a200d3
--- /dev/null
+++ b/assets/images/bathroom.jpg
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ae50401046451ca72968d1b242a8ab35cb01396980880f3eece801d2f5f0fbc0
size 37142
diff --git a/assets/images/bedroom.jpg b/assets/images/bedroom.jpg
new file mode 100644
index 0000000..61c5f50
--- /dev/null
+++ b/assets/images/bedroom.jpg
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7d5c47ab43f8e0cd77524a5dad1c9acaffa09d453d965fdc8f23629b22a7efd8
size 45824
diff --git a/assets/images/dining_room.jpg b/assets/images/dining_room.jpg
new file mode 100644
index 0000000..0367f26
--- /dev/null
+++ b/assets/images/dining_room.jpg
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:70c09703929f74abd0318dd81acf62f8ab63daeec50caed0ade9d0f8bfc2689a
size 57022
diff --git a/assets/images/kitchen.jpg b/assets/images/kitchen.jpg
new file mode 100644
index 0000000..b756ae7
--- /dev/null
+++ b/assets/images/kitchen.jpg
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b3cbb2be83046cea19ed0942c536f3fe41cb9a5382cbdcfd25cbe0efc5c94671
size 37129
diff --git a/assets/images/living_room.jpg b/assets/images/living_room.jpg
new file mode 100644
index 0000000..f2988a3
--- /dev/null
+++ b/assets/images/living_room.jpg
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8444deae7567063002173fdfe848ea2f6f3ebe116b4f2007207df43c41caf9ab
size 66944
diff --git a/config-example.toml b/config-example.toml
new file mode 100644
index 0000000..135142d
--- /dev/null
+++ b/config-example.toml
@@ -0,0 +1,3 @@
[home-assistant]
uri = "http://192.168.1.100/"
token = "abcdef"
diff --git a/rustfmt.toml b/rustfmt.toml
new file mode 100644
index 0000000..fd4d8a5
--- /dev/null
+++ b/rustfmt.toml
@@ -0,0 +1,7 @@
edition = "2021"
unstable_features = true
imports_granularity = "Crate"
group_imports = "StdExternalCrate"
normalize_comments = true
comment_width = 100
wrap_comments = true
diff --git a/shalom/Cargo.toml b/shalom/Cargo.toml
new file mode 100644
index 0000000..99447ad
--- /dev/null
+++ b/shalom/Cargo.toml
@@ -0,0 +1,19 @@
[package]
name = "shalom"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
iced = { version = "0.10", features = ["tokio", "svg", "lazy", "advanced", "image"] }
image = "0.24"
once_cell = "1.18"
itertools = "0.11"
keyframe = "1.1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tokio = { version = "1.33", features = ["net", "sync", "rt", "macros", "time", "fs"] }
tokio-tungstenite = "0.20"
toml = "0.8"
time = { version = "0.3", features = ["std"] }
diff --git a/shalom/src/config.rs b/shalom/src/config.rs
new file mode 100644
index 0000000..520a98a
--- /dev/null
+++ b/shalom/src/config.rs
@@ -0,0 +1,16 @@
#![allow(clippy::module_name_repetitions)]

use serde::Deserialize;

#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Config {
    pub home_assistant: HomeAssistantConfig,
}

#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct HomeAssistantConfig {
    pub uri: String,
    pub token: String,
}
diff --git a/shalom/src/hass_client.rs b/shalom/src/hass_client.rs
new file mode 100644
index 0000000..4c23919
--- /dev/null
+++ b/shalom/src/hass_client.rs
@@ -0,0 +1,142 @@
use std::{fs::File, io::Write, time::Duration};

use iced::futures::{SinkExt, StreamExt};
use serde::{Deserialize, Serialize};
use time::OffsetDateTime;
use tokio::{net::TcpStream, sync::mpsc};
use tokio_tungstenite::{tungstenite::Message, MaybeTlsStream, WebSocketStream};

use crate::config::HomeAssistantConfig;

type SocketStream = WebSocketStream<MaybeTlsStream<TcpStream>>;

pub enum ClientMessage {
    // Ready,
}

pub async fn create(config: HomeAssistantConfig) -> mpsc::Receiver<ClientMessage> {
    let (_send, recv) = mpsc::channel(10);

    let uri = format!("ws://{}/api/websocket", config.uri);
    let (mut connection, _response) = tokio_tungstenite::connect_async(&uri).await.unwrap();

    tokio::spawn(async move {
        let mut interval = tokio::time::interval(Duration::from_secs(10));

        loop {
            tokio::select! {
                Some(message) = connection.next() => {
                    let message = message.unwrap();
                    eprintln!("recv: {message:?}");

                    #[allow(clippy::match_same_arms)]
                    match message {
                        Message::Pong(ts) => {
                            let ts = i128::from_be_bytes(ts.try_into().unwrap());
                            let ts = OffsetDateTime::from_unix_timestamp_nanos(ts).unwrap();

                            eprintln!("rtt: {}", OffsetDateTime::now_utc() - ts);
                        }
                        Message::Text(payload) => {
                            handle_hass_response(&config, serde_json::from_str(&payload).unwrap(), &mut connection).await;
                        }
                        Message::Close(_) => {
                            // eprintln!("Reconnecting...");
                            // connection = tokio_tungstenite::connect_async(&uri).await.unwrap().0;
                        }
                        _ => {}
                    }
                }
                _ = interval.tick() => {
                    connection.send(Message::Ping(OffsetDateTime::now_utc().unix_timestamp_nanos().to_be_bytes().to_vec())).await.unwrap();
                }
            }
        }
    });

    recv
}

async fn handle_hass_response(
    config: &HomeAssistantConfig,
    v: HassResponse,
    socket: &mut SocketStream,
) {
    #[allow(clippy::match_same_arms)]
    match v {
        HassResponse::AuthRequired => {
            socket
                .send(
                    HassRequest::Auth {
                        access_token: config.token.clone(),
                    }
                    .to_request(),
                )
                .await
                .unwrap();
        }
        HassResponse::AuthOk => {
            // Lists: [aliases, area_id, name, picture]
            // socket
            //     .send(HassRequest::AreaRegistry { id: 3 }.to_request())
            //     .await
            //     .unwrap();

            // Lists: [area_id, entity_id]
            // socket.send(HassRequest::EntityRegistry { id: 3 }.to_request())
            //     .await
            //     .unwrap();

            // Lists: versions, area id, manufacturer, etc
            // socket.send(HassRequest::DeviceRegistry { id: 3 }.to_request())
            //     .await
            //     .unwrap();
        }
        HassResponse::AuthInvalid => {}
        HassResponse::Result(value) => {
            File::create("test")
                .unwrap()
                .write_all(&serde_json::to_vec(&value).unwrap())
                .unwrap();
        }
    }
}

#[derive(Deserialize)]
#[serde(rename_all = "snake_case", tag = "type", content = "result")]
#[allow(clippy::enum_variant_names)]
enum HassResponse {
    AuthRequired,
    AuthOk,
    AuthInvalid,
    Result(serde_json::Value),
}

#[derive(Serialize)]
#[serde(rename_all = "snake_case", tag = "type")]
enum HassRequest {
    Auth {
        access_token: String,
    },
    GetStates {
        id: u32,
    },
    #[serde(rename = "config/area_registry/list")]
    AreaRegistry {
        id: u32,
    },
    #[serde(rename = "config/entity_registry/list")]
    EntityRegistry {
        id: u32,
    },
    #[serde(rename = "config/device_registry/list")]
    DeviceRegistry {
        id: u32,
    },
}

impl HassRequest {
    pub fn to_request(&self) -> Message {
        Message::text(serde_json::to_string(&self).unwrap())
    }
}
diff --git a/shalom/src/main.rs b/shalom/src/main.rs
new file mode 100644
index 0000000..6906c64
--- /dev/null
+++ b/shalom/src/main.rs
@@ -0,0 +1,202 @@
#![deny(clippy::pedantic)]

mod config;
mod hass_client;
mod oracle;
mod pages;
mod theme;
mod widgets;

use std::sync::Arc;

use iced::{
    alignment::{Horizontal, Vertical},
    font::{Stretch, Weight},
    widget::{column, container, row, scrollable, svg, text, vertical_slider, Column},
    Alignment, Application, Command, ContentFit, Element, Font, Length, Renderer, Settings, Theme,
};

use crate::{
    config::Config,
    theme::{Icon, Image},
    widgets::{context_menu::ContextMenu, mouse_area::mouse_area},
};

pub struct Shalom {
    page: ActivePage,
    context_menu: Option<ActiveContextMenu>,
    homepage: ActivePage,
}

impl Application for Shalom {
    type Executor = iced::executor::Default;
    type Message = Message;
    type Theme = Theme;
    type Flags = ();

    fn new(_flags: Self::Flags) -> (Self, Command<Self::Message>) {
        let this = Self {
            page: ActivePage::Loading,
            context_menu: None,
            homepage: ActivePage::Room("Living Room"),
        };

        // this is only best-effort to try and prevent blocking when loading
        // the omni-view, we don't need to block on this at boot
        tokio::task::spawn_blocking(Image::preload);

        let command = Command::perform(
            async {
                let config = load_config().await;
                let client = hass_client::create(config.home_assistant).await;

                Arc::new(client)
            },
            |_client| Message::Loaded,
        );

        (this, command)
    }

    fn title(&self) -> String {
        String::from("Shalom")
    }

    fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
        match message {
            Message::Loaded => {
                self.page = self.homepage.clone();
            }
            Message::CloseContextMenu => {
                self.context_menu = None;
            }
            Message::OpenContextMenu(menu) => {
                self.context_menu = Some(menu);
            }
            Message::ChangePage(page) => {
                self.page = page;
            }
        }

        Command::none()
    }

    fn view(&self) -> Element<'_, Self::Message, Renderer<Self::Theme>> {
        let page_content = match &self.page {
            ActivePage::Loading => Element::from(column!["Loading...",].spacing(20)),
            ActivePage::Room(room) => {
                Element::from(pages::room::Room::new(room, Message::OpenContextMenu))
            }
            ActivePage::Omni => Element::from(pages::omni::Omni::new(Message::ChangePage)),
        };

        let mut content = Column::new().push(scrollable(page_content));

        let (show_back, show_home) = match &self.page {
            _ if self.page == self.homepage => (true, false),
            ActivePage::Loading => (false, false),
            ActivePage::Omni => (false, true),
            ActivePage::Room(_) => (true, true),
        };

        let back = mouse_area(
            svg(Icon::Back)
                .height(32)
                .width(32)
                .content_fit(ContentFit::None),
        )
        .on_press(Message::ChangePage(ActivePage::Omni));
        let home = mouse_area(
            svg(Icon::Home)
                .height(32)
                .width(32)
                .content_fit(ContentFit::None),
        )
        .on_press(Message::ChangePage(self.homepage.clone()));

        let navigation = match (show_back, show_home) {
            (true, true) => Some(Element::from(
                row![
                    back,
                    container(home)
                        .width(Length::Fill)
                        .align_x(Horizontal::Right),
                ]
                .height(32),
            )),
            (false, true) => Some(Element::from(
                row![container(home)
                    .width(Length::Fill)
                    .align_x(Horizontal::Right),]
                .height(32),
            )),
            (true, false) => Some(Element::from(back)),
            (false, false) => None,
        };

        if let Some(navigation) = navigation {
            content = content.push(
                container(navigation)
                    .height(Length::Fill)
                    .width(Length::Fill)
                    .align_y(Vertical::Bottom)
                    .padding(40),
            );
        }

        if let Some(context_menu) = &self.context_menu {
            let context_menu = match context_menu {
                ActiveContextMenu::LightOptions(name) => container(column![
                    text(name).size(40).font(Font {
                        weight: Weight::Bold,
                        stretch: Stretch::Condensed,
                        ..Font::with_name("Helvetica Neue")
                    }),
                    row![vertical_slider(0..=100, 0, |_v| Message::Loaded).height(200)]
                        .align_items(Alignment::Center)
                ])
                .width(Length::Fill)
                .padding(40),
            };

            ContextMenu::new(content, context_menu)
                .on_close(Message::CloseContextMenu)
                .into()
        } else {
            content.into()
        }
    }
}

async fn load_config() -> Config {
    let content = tokio::fs::read_to_string("./config.toml").await.unwrap();
    toml::from_str(&content).unwrap()
}

#[derive(Debug, Clone)]
pub enum Message {
    Loaded,
    CloseContextMenu,
    ChangePage(ActivePage),
    OpenContextMenu(ActiveContextMenu),
}

#[derive(Debug, Clone, PartialEq)]
pub enum ActivePage {
    Loading,
    Room(&'static str),
    Omni,
}

#[derive(Clone, Debug)]
pub enum ActiveContextMenu {
    LightOptions(&'static str),
}

fn main() {
    Shalom::run(Settings {
        antialiasing: true,
        ..Settings::default()
    })
    .unwrap();
}
diff --git a/shalom/src/oracle.rs b/shalom/src/oracle.rs
new file mode 100644
index 0000000..dde3cea
--- /dev/null
+++ b/shalom/src/oracle.rs
@@ -0,0 +1 @@
pub struct Oracle {}
diff --git a/shalom/src/pages/mod.rs b/shalom/src/pages/mod.rs
new file mode 100644
index 0000000..61845db
--- /dev/null
+++ b/shalom/src/pages/mod.rs
@@ -0,0 +1,2 @@
pub mod omni;
pub mod room;
diff --git a/shalom/src/pages/omni.rs b/shalom/src/pages/omni.rs
new file mode 100644
index 0000000..233a012
--- /dev/null
+++ b/shalom/src/pages/omni.rs
@@ -0,0 +1,83 @@
use iced::{
    advanced::graphics::core::Element,
    font::{Stretch, Weight},
    widget::{column, component, scrollable, text, Column, Component, Row},
    Font, Renderer,
};
use itertools::Itertools;

use crate::{theme::Image, widgets::image_card, ActivePage};

pub struct Omni<M> {
    open_page: fn(ActivePage) -> M,
}

impl<M> Omni<M> {
    pub fn new(open_page: fn(ActivePage) -> M) -> Self {
        Self { open_page }
    }
}

impl<M: Clone> Component<M, Renderer> for Omni<M> {
    type State = State;
    type Event = Event;

    fn update(&mut self, _state: &mut Self::State, event: Self::Event) -> Option<M> {
        match event {
            Event::OpenRoom(room) => Some((self.open_page)(ActivePage::Room(room))),
        }
    }

    fn view(&self, _state: &Self::State) -> Element<'_, Self::Event, Renderer> {
        let header = |v| {
            text(v).size(60).font(Font {
                weight: Weight::Bold,
                stretch: Stretch::Condensed,
                ..Font::with_name("Helvetica Neue")
            })
        };

        let room = |room, image| {
            image_card::image_card(image, room).on_press(Event::OpenRoom(room))
            // .height(Length::Fixed(128.0))
            // .width(Length::FillPortion(1))
        };

        let rooms = [
            room("Living Room", Image::LivingRoom),
            room("Kitchen", Image::Kitchen),
            room("Bathroom", Image::Bathroom),
            room("Bedroom", Image::Bedroom),
            room("Dining Room", Image::DiningRoom),
        ]
        .into_iter()
        .chunks(2)
        .into_iter()
        .map(|children| children.into_iter().fold(Row::new().spacing(10), Row::push))
        .fold(Column::new().spacing(10), Column::push);

        scrollable(
            column![header("Cameras"), header("Rooms"), rooms,]
                .spacing(20)
                .padding(40),
        )
        .into()
    }
}

#[derive(Default, Hash)]
pub struct State {}

#[derive(Clone)]
pub enum Event {
    OpenRoom(&'static str),
}

impl<'a, M> From<Omni<M>> for Element<'a, M, Renderer>
where
    M: 'a + Clone,
{
    fn from(card: Omni<M>) -> Self {
        component(card)
    }
}
diff --git a/shalom/src/pages/room.rs b/shalom/src/pages/room.rs
new file mode 100644
index 0000000..0cff432
--- /dev/null
+++ b/shalom/src/pages/room.rs
@@ -0,0 +1,100 @@
use std::collections::HashMap;

use iced::{
    advanced::graphics::core::Element,
    font::{Stretch, Weight},
    widget::{column, component, container, row, text, Component},
    Font, Renderer,
};

use crate::{theme::Icon, widgets, ActiveContextMenu};

pub struct Room<M> {
    name: &'static str,
    open_context_menu: fn(ActiveContextMenu) -> M,
}

impl<M> Room<M> {
    pub fn new(name: &'static str, open_context_menu: fn(ActiveContextMenu) -> M) -> Self {
        Self {
            name,
            open_context_menu,
        }
    }
}

impl<M: Clone> Component<M, Renderer> for Room<M> {
    type State = State;
    type Event = Event;

    fn update(&mut self, state: &mut Self::State, event: Self::Event) -> Option<M> {
        match event {
            Event::LightToggle(name) => {
                let x = state.lights.entry(name).or_default();
                if *x == 0 {
                    *x = 1;
                } else {
                    *x = 0;
                }

                None
            }
            Event::OpenLightOptions(name) => Some((self.open_context_menu)(
                ActiveContextMenu::LightOptions(name),
            )),
            Event::UpdateLightAmount(name, v) => {
                let x = state.lights.entry(name).or_default();
                *x = v;
                None
            }
        }
    }

    fn view(&self, state: &Self::State) -> Element<'_, Self::Event, Renderer> {
        let header = text(self.name).size(60).font(Font {
            weight: Weight::Bold,
            stretch: Stretch::Condensed,
            ..Font::with_name("Helvetica Neue")
        });

        let light = |name| {
            widgets::toggle_card::toggle_card(
                name,
                state.lights.get(name).copied().unwrap_or_default() > 0,
            )
            .icon(Icon::Bulb)
            .on_press(Event::LightToggle(name))
            .on_long_press(Event::OpenLightOptions(name))
        };

        column![
            header,
            container(widgets::media_player::media_player()).padding([12, 0, 24, 0]),
            row![light("Main"), light("Lamp"), light("TV")].spacing(10),
        ]
        .spacing(20)
        .padding(40)
        .into()
    }
}

#[derive(Default)]
pub struct State {
    lights: HashMap<&'static str, u8>,
}

#[derive(Clone)]
pub enum Event {
    LightToggle(&'static str),
    OpenLightOptions(&'static str),
    UpdateLightAmount(&'static str, u8),
}

impl<'a, M> From<Room<M>> for Element<'a, M, Renderer>
where
    M: 'a + Clone,
{
    fn from(card: Room<M>) -> Self {
        component(card)
    }
}
diff --git a/shalom/src/theme.rs b/shalom/src/theme.rs
new file mode 100644
index 0000000..3f57e9c
--- /dev/null
+++ b/shalom/src/theme.rs
@@ -0,0 +1,126 @@
use ::image::GenericImageView;
use iced::{
    advanced::svg::Handle,
    widget::{image, svg},
};
use once_cell::sync::Lazy;

pub mod colours {
    use iced::Color;

    macro_rules! colour {
        ($r:literal, $g:literal, $b:literal) => {{
            Color {
                r: $r / 255.0,
                g: $g / 255.0,
                b: $b / 255.0,
                a: 1.0,
            }
        }};
    }

    pub const SLATE_200: Color = colour!(226.0, 232.0, 240.0);
    pub const SLATE_300: Color = colour!(203.0, 213.0, 225.0);
    pub const SLATE_400: Color = colour!(148.0, 163.0, 184.0);
    pub const SLATE_600: Color = colour!(71.0, 85.0, 105.0);

    pub const SKY_400: Color = colour!(56.0, 189.0, 248.0);
    pub const SKY_500: Color = colour!(14.0, 165.0, 233.0);

    pub const AMBER_200: Color = colour!(253.0, 230.0, 138.0);
}

#[derive(Copy, Clone)]
pub enum Icon {
    Home,
    Back,
    Bulb,
    Hamburger,
    Speaker,
    SpeakerMuted,
    Backward,
    Forward,
    Play,
    Pause,
    Repeat,
}

impl Icon {
    pub fn handle(self) -> svg::Handle {
        macro_rules! image {
            ($path:expr) => {{
                static FILE: &[u8] = include_bytes!($path);
                static HANDLE: Lazy<svg::Handle> = Lazy::new(|| svg::Handle::from_memory(FILE));
                (*HANDLE).clone()
            }};
        }

        match self {
            Self::Home => image!("../../assets/icons/home.svg"),
            Self::Back => image!("../../assets/icons/back.svg"),
            Self::Bulb => image!("../../assets/icons/light-bulb.svg"),
            Self::Hamburger => image!("../../assets/icons/hamburger.svg"),
            Self::Speaker => image!("../../assets/icons/speaker.svg"),
            Self::SpeakerMuted => image!("../../assets/icons/speaker-muted.svg"),
            Self::Backward => image!("../../assets/icons/backward.svg"),
            Self::Forward => image!("../../assets/icons/forward.svg"),
            Self::Play => image!("../../assets/icons/play.svg"),
            Self::Pause => image!("../../assets/icons/pause.svg"),
            Self::Repeat => image!("../../assets/icons/repeat.svg"),
        }
    }
}

impl From<Icon> for svg::Handle {
    fn from(value: Icon) -> Handle {
        value.handle()
    }
}

#[derive(Clone, Copy, Hash, Eq, PartialEq)]
pub enum Image {
    LivingRoom,
    Kitchen,
    Bathroom,
    Bedroom,
    DiningRoom,
}

impl Image {
    fn handle(self) -> image::Handle {
        macro_rules! image {
            ($path:expr) => {{
                static FILE: &[u8] = include_bytes!($path);
                static HANDLE: Lazy<image::Handle> = Lazy::new(|| {
                    let img = ::image::load_from_memory(FILE).unwrap();
                    let (h, w) = img.dimensions();
                    let data = img.into_rgba8().into_raw();
                    image::Handle::from_pixels(h, w, data)
                });
                (*HANDLE).clone()
            }};
        }

        match self {
            Image::LivingRoom => image!("../../assets/images/living_room.jpg"),
            Image::Kitchen => image!("../../assets/images/kitchen.jpg"),
            Image::Bathroom => image!("../../assets/images/bathroom.jpg"),
            Image::Bedroom => image!("../../assets/images/bedroom.jpg"),
            Image::DiningRoom => image!("../../assets/images/dining_room.jpg"),
        }
    }

    pub fn preload() {
        Self::LivingRoom.handle();
        Self::Kitchen.handle();
        Self::Bathroom.handle();
        Self::Bedroom.handle();
        Self::DiningRoom.handle();
    }
}

impl From<Image> for image::Handle {
    fn from(value: Image) -> Self {
        value.handle()
    }
}
diff --git a/shalom/src/widgets/context_menu.rs b/shalom/src/widgets/context_menu.rs
new file mode 100644
index 0000000..70200b6
--- /dev/null
+++ b/shalom/src/widgets/context_menu.rs
@@ -0,0 +1,458 @@
use std::{
    cmp::Ordering,
    time::{Duration, Instant},
};

use iced::{
    advanced::{
        layout::{Limits, Node},
        overlay, renderer,
        renderer::Style,
        widget::{tree, tree::Tag, Operation, Tree},
        Clipboard, Layout, Renderer as RendererT, Shell, Widget,
    },
    event::Status,
    mouse,
    mouse::{Button, Cursor, Interaction},
    window,
    window::RedrawRequest,
    Alignment, BorderRadius, Color, Element, Event, Length, Point, Rectangle, Renderer, Size,
    Theme,
};
use keyframe::{functions::EaseOutQuint, keyframes, AnimationSequence};

pub struct ContextMenu<'a, M> {
    base: Element<'a, M, Renderer>,
    content: Element<'a, M, Renderer>,
    on_close: Option<M>,
    max_height: f32,
}

impl<'a, M> ContextMenu<'a, M> {
    pub fn new(
        base: impl Into<Element<'a, M, Renderer>>,
        content: impl Into<Element<'a, M, Renderer>>,
    ) -> Self {
        Self {
            base: base.into(),
            content: content.into(),
            on_close: None,
            max_height: 400.,
        }
    }

    pub fn on_close(mut self, msg: M) -> Self {
        self.on_close = Some(msg);
        self
    }
}

impl<'a, M: Clone> Widget<M, Renderer> for ContextMenu<'a, M> {
    fn width(&self) -> Length {
        self.base.as_widget().width()
    }

    fn height(&self) -> Length {
        self.base.as_widget().height()
    }

    fn layout(&self, renderer: &Renderer, limits: &Limits) -> Node {
        self.base.as_widget().layout(renderer, limits)
    }

    fn draw(
        &self,
        state: &Tree,
        renderer: &mut Renderer,
        theme: &Theme,
        style: &Style,
        layout: Layout<'_>,
        cursor: Cursor,
        viewport: &Rectangle,
    ) {
        self.base.as_widget().draw(
            &state.children[0],
            renderer,
            theme,
            style,
            layout,
            cursor,
            viewport,
        );
    }

    fn tag(&self) -> Tag {
        Tag::of::<OverlayState>()
    }

    fn state(&self) -> tree::State {
        tree::State::new(OverlayState {
            max_height: self.max_height,
            height: 0.,
            state: State::Animate(Instant::now(), keyframes![(0.0, 0.0), (400.0, 0.7)]),
        })
    }

    fn children(&self) -> Vec<Tree> {
        vec![Tree::new(&self.base), Tree::new(&self.content)]
    }

    fn diff(&self, tree: &mut Tree) {
        tree.diff_children(&[&self.base, &self.content]);
    }

    fn operate(
        &self,
        state: &mut Tree,
        layout: Layout<'_>,
        renderer: &Renderer,
        operation: &mut dyn Operation<M>,
    ) {
        self.base
            .as_widget()
            .operate(state, layout, renderer, operation);
    }

    fn on_event(
        &mut self,
        state: &mut Tree,
        event: Event,
        layout: Layout<'_>,
        cursor: Cursor,
        renderer: &Renderer,
        clipboard: &mut dyn Clipboard,
        shell: &mut Shell<'_, M>,
        viewport: &Rectangle,
    ) -> Status {
        self.base.as_widget_mut().on_event(
            &mut state.children[0],
            event,
            layout,
            cursor,
            renderer,
            clipboard,
            shell,
            viewport,
        )
    }

    fn overlay<'b>(
        &'b mut self,
        state: &'b mut Tree,
        layout: Layout<'_>,
        _renderer: &Renderer,
    ) -> Option<overlay::Element<'b, M, Renderer>> {
        Some(overlay::Element::new(
            layout.position(),
            Box::new(Overlay {
                content: &mut self.content,
                tree: &mut state.children[1],
                on_close: self.on_close.clone(),
                state: state.state.downcast_mut::<OverlayState>(),
            }),
        ))
    }
}

struct Overlay<'a, 'b, M> {
    content: &'b mut Element<'a, M, Renderer>,
    tree: &'b mut Tree,
    on_close: Option<M>,
    state: &'b mut OverlayState,
}

impl<'a, 'b, M: Clone> Overlay<'a, 'b, M> {
    pub fn handle_mouse_event(
        &mut self,
        event: &mouse::Event,
        layout: &Layout<'_>,
        cursor: Cursor,
    ) -> bool {
        if matches!(self.state.state, State::Closed) {
            return false;
        }

        match (&self.state.state, event) {
            (State::Dragging(_drag_start, _last_position), mouse::Event::CursorMoved { .. }) => {
                true
            }
            (_, mouse::Event::ButtonPressed(Button::Left)) => {
                self.state.state =
                    State::Dragging(Instant::now(), cursor.position().unwrap_or_default());
                true
            }
            (
                State::Dragging(drag_start, _last_position),
                mouse::Event::ButtonReleased(Button::Left),
            ) => {
                if drag_start.elapsed() <= Duration::from_millis(100)
                    && !cursor.is_over(layout.children().next().unwrap().bounds())
                {
                    // assume all fast clicks outside of content is an intent to
                    // close
                    self.state.state = State::Animate(
                        Instant::now(),
                        keyframes![(self.state.height, 0.0, EaseOutQuint), (0.0, 0.75)],
                    );
                } else if self.state.height <= 0. {
                    // dragged all the way closed, no need to animate anything
                    self.state.state = State::Closed;
                } else if self.state.height < (self.state.max_height / 1.25) {
                    // snap height reached for closing
                    self.state.state = State::Animate(
                        Instant::now(),
                        keyframes![
                            (self.state.height, 0.0, EaseOutQuint),
                            (
                                0.0,
                                (0.75 * (self.state.height.abs() / self.state.max_height.abs()))
                            )
                        ],
                    );
                } else {
                    // snap back to max height
                    self.state.state = State::Animate(
                        Instant::now(),
                        keyframes![
                            (self.state.height, 0.0, EaseOutQuint),
                            (
                                self.state.max_height,
                                (0.75 * (self.state.height.abs() / self.state.max_height.abs()))
                            )
                        ],
                    );
                }

                true
            }
            _ => false,
        }
    }

    pub fn handle_redraw(&mut self, shell: &mut Shell<M>, cursor: Cursor) {
        match &mut self.state.state {
            State::Open | State::Closed => {
                // don't need to do anything here
            }
            State::Dragging(_, last_position) => {
                if let Some(current_position) = cursor.position() {
                    let dy = current_position.y - last_position.y;

                    match dy.total_cmp(&0.) {
                        Ordering::Greater => {
                            self.state.height -= dy;
                            self.state.height = self.state.height.max(0.);
                        }
                        Ordering::Less if self.state.height > self.state.max_height => {
                            self.state.height -=
                                dy / (10. * (self.state.height / self.state.max_height));
                        }
                        Ordering::Less => {
                            self.state.height -= dy;
                        }
                        Ordering::Equal => {}
                    }

                    *last_position = current_position;
                }
            }
            State::Animate(instant, keyframes) => {
                keyframes.advance_by(instant.elapsed().as_secs_f64());
                self.state.height = keyframes.now();
                *instant = Instant::now();

                if keyframes.finished() {
                    if self.state.height <= 0. {
                        if let Some(event) = self.on_close.clone() {
                            shell.publish(event);
                        }

                        self.state.state = State::Closed;
                    } else {
                        self.state.state = State::Open;
                    }
                } else {
                    shell.request_redraw(RedrawRequest::NextFrame);
                }
            }
        }
    }
}

impl<'a, 'b, M: Clone> overlay::Overlay<M, Renderer> for Overlay<'a, 'b, M> {
    fn layout(&self, renderer: &Renderer, bounds: Size, position: Point) -> Node {
        let limits = Limits::new(Size::ZERO, bounds)
            .width(Length::Fill)
            .height(Length::Fill);

        let mut child = self.content.as_widget().layout(renderer, &limits);
        child.align(Alignment::Start, Alignment::Start, limits.max());
        child.move_to(Point {
            x: 0.0,
            y: bounds.height - self.state.height,
        });

        let mut node = Node::with_children(bounds, vec![child]);
        node.move_to(position);

        node
    }

    fn draw(
        &self,
        renderer: &mut Renderer,
        theme: &Theme,
        style: &Style,
        layout: Layout<'_>,
        cursor: Cursor,
    ) {
        if matches!(self.state.state, State::Closed) {
            return;
        }

        renderer.fill_quad(
            renderer::Quad {
                bounds: layout.bounds(),
                border_radius: BorderRadius::default(),
                border_width: 0.0,
                border_color: Color::TRANSPARENT,
            },
            Color {
                a: (0.8 / (self.state.max_height / self.state.height)).min(0.9),
                ..Color::BLACK
            },
        );

        let bounds = Rectangle::new(
            Point::new(0.0, layout.bounds().height - self.state.height),
            Size {
                width: layout.bounds().width,
                height: self.state.height,
            },
        );

        renderer.fill_quad(
            renderer::Quad {
                bounds,
                border_radius: 0.0.into(),
                border_width: 0.0,
                border_color: Color::default(),
            },
            Color::WHITE,
        );

        self.content.as_widget().draw(
            self.tree,
            renderer,
            theme,
            style,
            layout.children().next().unwrap(),
            cursor,
            &bounds,
        );
    }

    fn operate(
        &mut self,
        layout: Layout<'_>,
        renderer: &Renderer,
        operation: &mut dyn Operation<M>,
    ) {
        self.content.as_widget().operate(
            self.tree,
            layout.children().next().unwrap(),
            renderer,
            operation,
        );
    }

    fn on_event(
        &mut self,
        event: Event,
        layout: Layout<'_>,
        cursor: Cursor,
        renderer: &Renderer,
        clipboard: &mut dyn Clipboard,
        shell: &mut Shell<'_, M>,
    ) -> Status {
        let mut status = self.content.as_widget_mut().on_event(
            self.tree,
            event.clone(),
            layout.children().next().unwrap(),
            cursor,
            renderer,
            clipboard,
            shell,
            &layout.bounds(),
        );

        if let (Status::Ignored, Event::Mouse(mouse_event)) = (status, &event) {
            let captured = self.handle_mouse_event(mouse_event, &layout, cursor);

            if captured {
                status = Status::Captured;

                if let (Some(msg), State::Closed) = (&self.on_close, &self.state.state) {
                    shell.publish(msg.clone());
                } else {
                    shell.request_redraw(RedrawRequest::NextFrame);
                }
            }
        } else if let Event::Window(window::Event::RedrawRequested(_)) = &event {
            self.handle_redraw(shell, cursor);
        }

        status
    }

    fn mouse_interaction(
        &self,
        layout: Layout<'_>,
        cursor: Cursor,
        viewport: &Rectangle,
        renderer: &Renderer,
    ) -> Interaction {
        self.content.as_widget().mouse_interaction(
            self.tree,
            layout.children().next().unwrap(),
            cursor,
            viewport,
            renderer,
        )
    }

    fn overlay<'c>(
        &'c mut self,
        layout: Layout<'_>,
        renderer: &Renderer,
    ) -> Option<overlay::Element<'c, M, Renderer>> {
        self.content
            .as_widget_mut()
            .overlay(self.tree, layout.children().next().unwrap(), renderer)
    }
}

impl<'a, M> From<ContextMenu<'a, M>> for Element<'a, M>
where
    M: 'a + Clone,
{
    fn from(modal: ContextMenu<'a, M>) -> Self {
        Element::new(modal)
    }
}

#[derive(Default)]
pub struct OverlayState {
    max_height: f32,
    height: f32,
    state: State,
}

#[derive(Default)]
pub enum State {
    #[default]
    Open,
    Animate(Instant, AnimationSequence<f32>),
    Dragging(Instant, Point),
    Closed,
}
diff --git a/shalom/src/widgets/image_card.rs b/shalom/src/widgets/image_card.rs
new file mode 100644
index 0000000..da515ec
--- /dev/null
+++ b/shalom/src/widgets/image_card.rs
@@ -0,0 +1,237 @@
use iced::{
    advanced::{
        image::{Data, Renderer as ImageRenderer},
        layout::{Limits, Node},
        overlay,
        renderer::{Quad, Style},
        widget::Tree,
        Clipboard, Layout, Renderer as IRenderer, Shell, Widget,
    },
    event::Status,
    font::{Stretch, Weight},
    gradient::Linear,
    mouse,
    mouse::{Button, Cursor},
    theme::Text,
    touch,
    widget::{image, text},
    Alignment, Background, Color, ContentFit, Degrees, Element, Event, Font, Gradient, Length,
    Point, Rectangle, Renderer, Size, Theme, Vector,
};

pub fn image_card<'a, M: 'a>(
    handle: impl Into<image::Handle>,
    caption: &'a str,
) -> ImageCard<'a, M> {
    let image_handle = handle.into();

    ImageCard {
        image_handle: image_handle.clone(),
        text: text(caption)
            .size(14)
            .font(Font {
                weight: Weight::Bold,
                stretch: Stretch::Condensed,
                ..Font::with_name("Helvetica Neue")
            })
            .style(Text::Color(Color::WHITE))
            .into(),
        on_press: None,
        width: Length::FillPortion(1),
        height: Length::Fixed(128.0),
    }
}

pub struct ImageCard<'a, M> {
    image_handle: image::Handle,
    text: Element<'a, M, Renderer>,
    on_press: Option<M>,
    width: Length,
    height: Length,
}

impl<'a, M> ImageCard<'a, M> {
    pub fn on_press(mut self, msg: M) -> Self {
        self.on_press = Some(msg);
        self
    }
}

impl<'a, M: Clone> Widget<M, Renderer> for ImageCard<'a, M> {
    fn width(&self) -> Length {
        self.width
    }

    fn height(&self) -> Length {
        self.height
    }

    fn layout(&self, _renderer: &Renderer, limits: &Limits) -> Node {
        let limits = limits.width(self.width).height(self.height);
        let size = limits.resolve(Size::ZERO);

        Node::new(size)
    }

    fn draw(
        &self,
        _state: &Tree,
        renderer: &mut Renderer,
        _theme: &Theme,
        _style: &Style,
        layout: Layout<'_>,
        _cursor: Cursor,
        _viewport: &Rectangle,
    ) {
        let bounds = layout.bounds();

        // The raw w/h of the underlying image, renderer.dimensions is _really_
        // slow so enforce the use of preparsed images from `theme`.
        #[allow(clippy::cast_precision_loss)]
        let image_size = match self.image_handle.data() {
            Data::Rgba { width, height, .. } => Size::new(*width as f32, *height as f32),
            Data::Path(_) | Data::Bytes(_) => panic!("only parsed images are supported"),
        };

        let adjusted_fit = ContentFit::Cover.fit(image_size, bounds.size());

        renderer.with_layer(bounds, |renderer| {
            let offset = Vector::new(
                (bounds.width - adjusted_fit.width).min(0.0) / 1.5,
                (bounds.height - adjusted_fit.height).min(0.0) / 1.5,
            );

            let drawing_bounds = Rectangle {
                width: adjusted_fit.width,
                height: adjusted_fit.height,
                ..bounds
            };

            renderer.draw(self.image_handle.clone(), drawing_bounds + offset);
        });
    }

    fn children(&self) -> Vec<Tree> {
        vec![Tree::new(&self.text)]
    }

    fn diff(&self, tree: &mut Tree) {
        tree.diff_children(&[&self.text]);
    }

    fn overlay<'b>(
        &'b mut self,
        state: &'b mut Tree,
        layout: Layout<'_>,
        _renderer: &Renderer,
    ) -> Option<iced::advanced::overlay::Element<'b, M, Renderer>> {
        Some(overlay::Element::new(
            layout.position(),
            Box::new(Overlay {
                text: &mut self.text,
                tree: &mut state.children[0],
                size: layout.bounds().size(),
                on_press: self.on_press.as_ref(),
            }),
        ))
    }
}

struct Overlay<'a, 'b, M> {
    text: &'b mut Element<'a, M, Renderer>,
    tree: &'b mut Tree,
    size: Size,
    on_press: Option<&'b M>,
}

impl<'a, 'b, M: Clone> overlay::Overlay<M, Renderer> for Overlay<'a, 'b, M> {
    fn layout(&self, renderer: &Renderer, _bounds: Size, position: Point) -> Node {
        let limits = Limits::new(Size::ZERO, self.size).pad([0, 0, 10, 0].into());

        let mut child = self.text.as_widget().layout(renderer, &limits);
        child.align(Alignment::Center, Alignment::End, limits.max());

        let mut node = Node::with_children(self.size, vec![child]);
        node.move_to(position);

        node
    }

    fn draw(
        &self,
        renderer: &mut Renderer,
        theme: &Theme,
        style: &Style,
        layout: Layout<'_>,
        cursor: Cursor,
    ) {
        renderer.fill_quad(
            Quad {
                bounds: Rectangle {
                    height: self.size.height + 12.,
                    width: self.size.width + 12.,
                    x: layout.bounds().x - 6.,
                    y: layout.bounds().y - 6.,
                },
                border_radius: [20., 20., 20., 20.].into(),
                border_width: 10.,
                border_color: Color::WHITE,
            },
            Background::Gradient(Gradient::Linear(
                Linear::new(Degrees(270.))
                    .add_stop(0.0, Color::from_rgba8(0, 0, 0, 0.0))
                    .add_stop(0.4, Color::from_rgba8(0, 0, 0, 0.0))
                    .add_stop(1.0, Color::from_rgba8(0, 0, 0, 0.8)),
            )),
        );

        self.text.as_widget().draw(
            self.tree,
            renderer,
            theme,
            style,
            layout.children().next().unwrap(),
            cursor,
            &layout.bounds(),
        );
    }

    fn on_event(
        &mut self,
        event: Event,
        layout: Layout<'_>,
        cursor: Cursor,
        _renderer: &Renderer,
        _clipboard: &mut dyn Clipboard,
        shell: &mut Shell<'_, M>,
    ) -> Status {
        if !cursor.is_over(Rectangle {
            height: self.size.height,
            width: self.size.width,
            ..layout.bounds()
        }) {
            return Status::Ignored;
        }

        if let Some(on_press) = self.on_press {
            if let Event::Mouse(mouse::Event::ButtonPressed(Button::Left))
            | Event::Touch(touch::Event::FingerPressed { .. }) = &event
            {
                shell.publish(on_press.clone());

                return Status::Captured;
            }
        }

        Status::Ignored
    }
}

impl<'a, M> From<ImageCard<'a, M>> for Element<'a, M>
where
    M: 'a + Clone,
{
    fn from(modal: ImageCard<'a, M>) -> Self {
        Element::new(modal)
    }
}
diff --git a/shalom/src/widgets/media_player.rs b/shalom/src/widgets/media_player.rs
new file mode 100644
index 0000000..d31a863
--- /dev/null
+++ b/shalom/src/widgets/media_player.rs
@@ -0,0 +1,233 @@
use std::{fmt::Display, time::Duration};

use iced::{
    advanced::graphics::core::Element,
    theme::{Svg, Text},
    widget::{column as icolumn, component, container, row, slider, svg, text, Component},
    Alignment, Length, Renderer, Theme,
};

use crate::{
    theme::{
        colours::{SKY_500, SLATE_400, SLATE_600},
        Icon,
    },
    widgets::mouse_area::mouse_area,
};

pub fn media_player<M>() -> MediaPlayer<M> {
    MediaPlayer::default()
}

pub struct MediaPlayer<M> {
    height: Length,
    width: Length,
    now_playing: NowPlaying,
    on_something: Option<M>,
    track_length: Duration,
}

impl<M> Default for MediaPlayer<M> {
    fn default() -> Self {
        Self {
            height: Length::Shrink,
            width: Length::Fill,
            now_playing: NowPlaying {
                album_art: "https://i.scdn.co/image/ab67616d00004851d771166c366eff01950de570"
                    .to_string(),
                song: "Almost Had to Start a Fight/In and Out of Patience".to_string(),
                artist: "Parquet Court".to_string(),
                loved: true,
            },
            on_something: None,
            track_length: Duration::from_secs(194),
        }
    }
}

impl<M> Component<M, Renderer> for MediaPlayer<M> {
    type State = State;
    type Event = Event;

    fn update(&mut self, state: &mut Self::State, event: Self::Event) -> Option<M> {
        match event {
            Event::VolumeChange(new) => {
                state.volume = new;
                None
            }
            Event::PositionChange(new) => {
                state.track_position = Duration::from_secs_f64(new);
                None
            }
            Event::TogglePlaying => {
                state.playing = !state.playing;
                None
            }
            Event::ToggleMute => {
                state.muted = !state.muted;
                None
            }
            Event::ToggleRepeat => {
                state.repeat = !state.repeat;
                None
            }
        }
    }

    fn view(&self, state: &Self::State) -> Element<'_, Self::Event, Renderer> {
        let icon_style = |v| Svg::Custom(Box::new(if v { Style::Active } else { Style::Inactive }));

        container(
            row![
                container(crate::widgets::track_card::track_card(
                    self.now_playing.artist.clone(),
                    self.now_playing.song.clone(),
                    false,
                ),)
                .width(Length::FillPortion(8)),
                icolumn![
                    // container(
                    //     svg(Icon::Hamburger)
                    //         .height(30)
                    //         .width(30),
                    // )
                    // .align_x(Horizontal::Right)
                    // .align_y(Vertical::Center)
                    // .width(Length::Fill),
                    row![
                        svg(Icon::Backward)
                            .height(24)
                            .width(24)
                            .style(icon_style(false)),
                        mouse_area(
                            svg(if state.playing {
                                Icon::Pause
                            } else {
                                Icon::Play
                            })
                            .height(24)
                            .width(24)
                            .style(icon_style(false))
                        )
                        .on_press(Event::TogglePlaying),
                        svg(Icon::Forward)
                            .height(24)
                            .width(24)
                            .style(icon_style(false)),
                        mouse_area(
                            svg(Icon::Repeat)
                                .height(24)
                                .width(24)
                                .style(icon_style(state.repeat)),
                        )
                        .on_press(Event::ToggleRepeat),
                    ]
                    .spacing(14),
                    row![
                        text(format_time(state.track_position))
                            .style(Text::Color(SLATE_400))
                            .size(12),
                        slider(
                            0.0..=self.track_length.as_secs_f64(),
                            state.track_position.as_secs_f64(),
                            Event::PositionChange
                        ),
                        text(format_time(self.track_length))
                            .style(Text::Color(SLATE_400))
                            .size(12),
                    ]
                    .spacing(14)
                    .align_items(Alignment::Center),
                ]
                .spacing(8)
                .align_items(Alignment::Center)
                .width(Length::FillPortion(12)),
                row![
                    mouse_area(
                        svg(if state.muted {
                            Icon::SpeakerMuted
                        } else {
                            Icon::Speaker
                        })
                        .height(16)
                        .width(16)
                        .style(icon_style(false)),
                    )
                    .on_press(Event::ToggleMute),
                    slider(0..=100, state.volume, Event::VolumeChange).width(128),
                ]
                .align_items(Alignment::Center)
                .width(Length::FillPortion(4))
                .spacing(12),
            ]
            .align_items(Alignment::Center)
            .spacing(48),
        )
        .height(self.height)
        .width(self.width)
        .center_x()
        .center_y()
        .into()
    }
}

pub struct NowPlaying {
    album_art: String,
    song: String,
    artist: String,
    loved: bool,
}

#[derive(Default)]
pub struct State {
    muted: bool,
    volume: u8,
    track_position: Duration,
    playing: bool,
    repeat: bool,
}

#[derive(Clone)]
pub enum Event {
    TogglePlaying,
    ToggleMute,
    ToggleRepeat,
    VolumeChange(u8),
    PositionChange(f64),
}

impl<'a, M> From<MediaPlayer<M>> for Element<'a, M, Renderer>
where
    M: 'a + Clone,
{
    fn from(card: MediaPlayer<M>) -> Self {
        component(card)
    }
}

fn format_time(duration: Duration) -> impl Display {
    let secs = duration.as_secs();
    let minutes = secs / 60;
    let seconds = secs % 60;

    format!("{minutes:02}:{seconds:02}")
}

#[derive(Copy, Clone)]
pub enum Style {
    Active,
    Inactive,
}

impl svg::StyleSheet for Style {
    type Style = Theme;

    fn appearance(&self, _style: &Self::Style) -> svg::Appearance {
        let color = match self {
            Self::Active => SKY_500,
            Self::Inactive => SLATE_600,
        };

        svg::Appearance { color: Some(color) }
    }
}
diff --git a/shalom/src/widgets/mod.rs b/shalom/src/widgets/mod.rs
new file mode 100644
index 0000000..413cbd5
--- /dev/null
+++ b/shalom/src/widgets/mod.rs
@@ -0,0 +1,6 @@
pub mod context_menu;
pub mod image_card;
pub mod media_player;
pub mod mouse_area;
pub mod toggle_card;
pub mod track_card;
diff --git a/shalom/src/widgets/mouse_area.rs b/shalom/src/widgets/mouse_area.rs
new file mode 100644
index 0000000..f6bbb59
--- /dev/null
+++ b/shalom/src/widgets/mouse_area.rs
@@ -0,0 +1,347 @@
//! A container for capturing mouse events.
//!
//! Pretty much just a copy of [`iced::widget::mouse_area`] but with the ability
//! to trigger an event after a button has been held down for a set amount of
//! time.

use std::time::{Duration, Instant};

use iced::{
    advanced::{
        layout, mouse, overlay, renderer,
        widget::{tree, Operation, Tree},
        Clipboard, Layout, Shell, Widget,
    },
    event, touch,
    window::RedrawRequest,
    Element, Event, Length, Rectangle,
};

pub fn mouse_area<'a, Message, Renderer>(
    widget: impl Into<Element<'a, Message, Renderer>>,
) -> MouseArea<'a, Message, Renderer>
where
    Renderer: renderer::Renderer,
{
    MouseArea::new(widget)
}

/// Emit messages on mouse events.
#[allow(missing_debug_implementations)]
pub struct MouseArea<'a, Message, Renderer> {
    content: Element<'a, Message, Renderer>,
    on_press: Option<Message>,
    on_cancel: Option<Message>,
    on_hold: Option<(Message, Duration)>,
    on_release: Option<Message>,
    on_right_press: Option<Message>,
    on_right_release: Option<Message>,
    on_middle_press: Option<Message>,
    on_middle_release: Option<Message>,
}

impl<'a, Message, Renderer> MouseArea<'a, Message, Renderer> {
    /// The message to emit on a left button press.
    #[must_use]
    pub fn on_press(mut self, message: Message) -> Self {
        self.on_press = Some(message);
        self
    }

    /// The message to emit when holding down an item.
    #[must_use]
    pub fn on_hold(mut self, message: Message, duration: Duration) -> Self {
        self.on_hold = Some((message, duration));
        self
    }

    /// The message to emit when a user "cancels" a `press`.
    #[must_use]
    pub fn on_cancel(mut self, message: Message) -> Self {
        self.on_cancel = Some(message);
        self
    }

    /// The message to emit on a left button release.
    #[must_use]
    pub fn on_release(mut self, message: Message) -> Self {
        self.on_release = Some(message);
        self
    }

    /// The message to emit on a right button press.
    #[must_use]
    pub fn on_right_press(mut self, message: Message) -> Self {
        self.on_right_press = Some(message);
        self
    }

    /// The message to emit on a right button release.
    #[must_use]
    pub fn on_right_release(mut self, message: Message) -> Self {
        self.on_right_release = Some(message);
        self
    }

    /// The message to emit on a middle button press.
    #[must_use]
    pub fn on_middle_press(mut self, message: Message) -> Self {
        self.on_middle_press = Some(message);
        self
    }

    /// The message to emit on a middle button release.
    #[must_use]
    pub fn on_middle_release(mut self, message: Message) -> Self {
        self.on_middle_release = Some(message);
        self
    }
}

/// Local state of the [`MouseArea`].
#[derive(Default)]
struct State {
    // TODO: Support on_mouse_enter and on_mouse_exit
    held_since: Option<Instant>,
}

impl<'a, Message, Renderer> MouseArea<'a, Message, Renderer> {
    /// Creates a [`MouseArea`] with the given content.
    pub fn new(content: impl Into<Element<'a, Message, Renderer>>) -> Self {
        MouseArea {
            content: content.into(),
            on_press: None,
            on_hold: None,
            on_cancel: None,
            on_release: None,
            on_right_press: None,
            on_right_release: None,
            on_middle_press: None,
            on_middle_release: None,
        }
    }
}

impl<'a, Message, Renderer> Widget<Message, Renderer> for MouseArea<'a, Message, Renderer>
where
    Renderer: renderer::Renderer,
    Message: Clone,
{
    fn tag(&self) -> tree::Tag {
        tree::Tag::of::<State>()
    }

    fn state(&self) -> tree::State {
        tree::State::new(State::default())
    }

    fn children(&self) -> Vec<Tree> {
        vec![Tree::new(&self.content)]
    }

    fn diff(&self, tree: &mut Tree) {
        tree.diff_children(std::slice::from_ref(&self.content));
    }

    fn width(&self) -> Length {
        self.content.as_widget().width()
    }

    fn height(&self) -> Length {
        self.content.as_widget().height()
    }

    fn layout(&self, renderer: &Renderer, limits: &layout::Limits) -> layout::Node {
        self.content.as_widget().layout(renderer, limits)
    }

    fn operate(
        &self,
        tree: &mut Tree,
        layout: Layout<'_>,
        renderer: &Renderer,
        operation: &mut dyn Operation<Message>,
    ) {
        self.content
            .as_widget()
            .operate(&mut tree.children[0], layout, renderer, operation);
    }

    fn on_event(
        &mut self,
        tree: &mut Tree,
        event: Event,
        layout: Layout<'_>,
        cursor: mouse::Cursor,
        renderer: &Renderer,
        clipboard: &mut dyn Clipboard,
        shell: &mut Shell<'_, Message>,
        viewport: &Rectangle,
    ) -> event::Status {
        if let event::Status::Captured = self.content.as_widget_mut().on_event(
            &mut tree.children[0],
            event.clone(),
            layout,
            cursor,
            renderer,
            clipboard,
            shell,
            viewport,
        ) {
            return event::Status::Captured;
        }

        let state = tree.state.downcast_mut::<State>();

        update(self, state, &event, layout, cursor, shell)
    }

    fn mouse_interaction(
        &self,
        tree: &Tree,
        layout: Layout<'_>,
        cursor: mouse::Cursor,
        viewport: &Rectangle,
        renderer: &Renderer,
    ) -> mouse::Interaction {
        self.content.as_widget().mouse_interaction(
            &tree.children[0],
            layout,
            cursor,
            viewport,
            renderer,
        )
    }

    fn draw(
        &self,
        tree: &Tree,
        renderer: &mut Renderer,
        theme: &Renderer::Theme,
        renderer_style: &renderer::Style,
        layout: Layout<'_>,
        cursor: mouse::Cursor,
        viewport: &Rectangle,
    ) {
        self.content.as_widget().draw(
            &tree.children[0],
            renderer,
            theme,
            renderer_style,
            layout,
            cursor,
            viewport,
        );
    }

    fn overlay<'b>(
        &'b mut self,
        tree: &'b mut Tree,
        layout: Layout<'_>,
        renderer: &Renderer,
    ) -> Option<overlay::Element<'b, Message, Renderer>> {
        self.content
            .as_widget_mut()
            .overlay(&mut tree.children[0], layout, renderer)
    }
}

impl<'a, Message, Renderer> From<MouseArea<'a, Message, Renderer>>
    for Element<'a, Message, Renderer>
where
    Message: 'a + Clone,
    Renderer: 'a + renderer::Renderer,
{
    fn from(area: MouseArea<'a, Message, Renderer>) -> Element<'a, Message, Renderer> {
        Element::new(area)
    }
}

/// Processes the given [`Event`] and updates the [`State`] of an [`MouseArea`]
/// accordingly.
fn update<Message: Clone, Renderer>(
    widget: &mut MouseArea<'_, Message, Renderer>,
    state: &mut State,
    event: &Event,
    layout: Layout<'_>,
    cursor: mouse::Cursor,
    shell: &mut Shell<'_, Message>,
) -> event::Status {
    if !cursor.is_over(layout.bounds()) {
        if let (Some(message), Some(_)) = (&widget.on_cancel, state.held_since) {
            state.held_since = None;
            shell.publish(message.clone());
        }

        return event::Status::Ignored;
    }

    if let (Some((message, duration)), Some(held_since)) =
        (widget.on_hold.as_ref(), state.held_since)
    {
        if held_since.elapsed() > *duration {
            state.held_since = None;
            shell.publish(message.clone());
        }
    }

    if let Some(message) = widget.on_press.as_ref() {
        if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
        | Event::Touch(touch::Event::FingerPressed { .. }) = event
        {
            shell.publish(message.clone());

            if let Some((_message, duration)) = widget.on_hold.clone() {
                state.held_since = Some(Instant::now());
                shell.request_redraw(RedrawRequest::At(Instant::now() + duration));
            }

            return event::Status::Captured;
        }
    }

    if let Some(message) = widget.on_release.as_ref() {
        if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
        | Event::Touch(touch::Event::FingerLifted { .. }) = event
        {
            shell.publish(message.clone());
            state.held_since = None;

            return event::Status::Captured;
        }
    }

    if let Some(message) = widget.on_right_press.as_ref() {
        if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right)) = event {
            shell.publish(message.clone());

            return event::Status::Captured;
        }
    }

    if let Some(message) = widget.on_right_release.as_ref() {
        if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Right)) = event {
            shell.publish(message.clone());

            return event::Status::Captured;
        }
    }

    if let Some(message) = widget.on_middle_press.as_ref() {
        if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Middle)) = event {
            shell.publish(message.clone());

            return event::Status::Captured;
        }
    }

    if let Some(message) = widget.on_middle_release.as_ref() {
        if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Middle)) = event {
            shell.publish(message.clone());

            return event::Status::Captured;
        }
    }

    event::Status::Ignored
}
diff --git a/shalom/src/widgets/toggle_card.rs b/shalom/src/widgets/toggle_card.rs
new file mode 100644
index 0000000..930d402
--- /dev/null
+++ b/shalom/src/widgets/toggle_card.rs
@@ -0,0 +1,240 @@
#![allow(clippy::module_name_repetitions)]

use std::time::{Duration, Instant};

use iced::{
    alignment::Vertical,
    font::{Stretch, Weight},
    theme::{Container, Svg},
    widget::{column, component, container, svg, text},
    Alignment, Background, Color, Element, Font, Length, Renderer, Theme,
};

use crate::{
    theme::{
        colours::{AMBER_200, SKY_400, SKY_500, SLATE_200, SLATE_300},
        Icon,
    },
    widgets::mouse_area::mouse_area,
};

pub const LONG_PRESS_LENGTH: Duration = Duration::from_millis(350);

pub fn toggle_card<M>(name: &'static str, active: bool) -> ToggleCard<M> {
    ToggleCard {
        name,
        active,
        ..ToggleCard::default()
    }
}

pub struct ToggleCard<M> {
    icon: Option<Icon>,
    name: &'static str,
    height: Length,
    width: Length,
    active: bool,
    on_press: Option<M>,
    on_long_press: Option<M>,
}

impl<M> Default for ToggleCard<M> {
    fn default() -> Self {
        Self {
            icon: None,
            name: "",
            height: Length::Shrink,
            width: Length::Fill,
            active: false,
            on_press: None,
            on_long_press: None,
        }
    }
}

impl<M> ToggleCard<M> {
    pub fn on_press(mut self, msg: M) -> Self {
        self.on_press = Some(msg);
        self
    }

    pub fn on_long_press(mut self, msg: M) -> Self {
        self.on_long_press = Some(msg);
        self
    }

    pub fn icon(mut self, icon: Icon) -> Self {
        self.icon = Some(icon);
        self
    }

    pub fn height(mut self, height: Length) -> Self {
        self.height = height;
        self
    }

    pub fn width(mut self, width: Length) -> Self {
        self.width = width;
        self
    }
}

impl<M: Clone> iced::widget::Component<M, Renderer> for ToggleCard<M> {
    type State = State;
    type Event = ToggleCardEvent;

    fn update(&mut self, state: &mut Self::State, event: Self::Event) -> Option<M> {
        match event {
            ToggleCardEvent::Down => {
                state.mouse_down_start = Some(Instant::now());

                None
            }
            ToggleCardEvent::Up => {
                let Some(start) = state.mouse_down_start.take() else {
                    return None;
                };

                if start.elapsed() > LONG_PRESS_LENGTH {
                    self.on_long_press.clone().or_else(|| self.on_press.clone())
                } else {
                    self.on_press.clone()
                }
            }
            ToggleCardEvent::Hold => self.on_long_press.clone(),
            ToggleCardEvent::Cancel => {
                state.mouse_down_start = None;

                None
            }
        }
    }

    fn view(&self, state: &Self::State) -> Element<'_, Self::Event, Renderer> {
        let style = match (self.active, state.mouse_down_start) {
            (true, None) => Style::Active,
            (true, Some(_)) => Style::ActiveHover,
            (false, None) => Style::Inactive,
            (false, Some(_)) => Style::InactiveHover,
        };

        let icon = self.icon.map(|icon| {
            svg(icon)
                .height(32)
                .width(32)
                .style(Svg::Custom(Box::new(style)))
        });

        let name = text(self.name).size(14).font(Font {
            weight: Weight::Bold,
            stretch: Stretch::Condensed,
            ..Font::with_name("Helvetica Neue")
        });

        let col = if let Some(icon) = icon {
            column![icon, name]
        } else {
            column![name]
        };

        mouse_area(
            container(
                col.spacing(5)
                    .width(self.width)
                    .align_items(Alignment::Center),
            )
            .height(self.height)
            .width(self.width)
            .style(Container::Custom(Box::new(style)))
            .align_y(Vertical::Bottom)
            .padding([20, 20]),
        )
        .on_press(ToggleCardEvent::Down)
        .on_release(ToggleCardEvent::Up)
        .on_hold(ToggleCardEvent::Hold, LONG_PRESS_LENGTH)
        .on_cancel(ToggleCardEvent::Cancel)
        .into()
    }
}

impl<'a, M> From<ToggleCard<M>> for Element<'a, M, Renderer>
where
    M: 'a + Clone,
{
    fn from(card: ToggleCard<M>) -> Self {
        component(card)
    }
}

#[derive(Default)]
pub struct State {
    mouse_down_start: Option<Instant>,
}

#[derive(Clone)]
pub enum ToggleCardEvent {
    Down,
    Up,
    Hold,
    Cancel,
}

#[derive(Copy, Clone)]
pub enum Style {
    Active,
    ActiveHover,
    Inactive,
    InactiveHover,
}

impl container::StyleSheet for Style {
    type Style = Theme;

    fn appearance(&self, _style: &Self::Style) -> container::Appearance {
        match self {
            Style::Inactive => container::Appearance {
                text_color: Some(Color::BLACK),
                background: Some(Background::Color(SLATE_200)),
                border_radius: 5.0.into(),
                border_width: 0.0,
                border_color: Color::default(),
            },
            Style::InactiveHover => container::Appearance {
                text_color: Some(Color::BLACK),
                background: Some(Background::Color(SLATE_300)),
                border_radius: 5.0.into(),
                border_width: 0.0,
                border_color: Color::default(),
            },
            Style::Active => container::Appearance {
                text_color: Some(Color::WHITE),
                background: Some(Background::Color(SKY_400)),
                border_radius: 5.0.into(),
                border_width: 0.0,
                border_color: Color::default(),
            },
            Style::ActiveHover => container::Appearance {
                text_color: Some(Color::WHITE),
                background: Some(Background::Color(SKY_500)),
                border_radius: 5.0.into(),
                border_width: 0.0,
                border_color: Color::default(),
            },
        }
    }
}

impl svg::StyleSheet for Style {
    type Style = Theme;

    fn appearance(&self, _style: &Self::Style) -> svg::Appearance {
        match self {
            Style::Active | Style::ActiveHover => svg::Appearance {
                color: Some(AMBER_200),
            },
            Style::Inactive | Style::InactiveHover => svg::Appearance {
                color: Some(Color::BLACK),
            },
        }
    }
}
diff --git a/shalom/src/widgets/track_card.rs b/shalom/src/widgets/track_card.rs
new file mode 100644
index 0000000..aaad3e2
--- /dev/null
+++ b/shalom/src/widgets/track_card.rs
@@ -0,0 +1,65 @@
use iced::{
    advanced::graphics::core::Element,
    theme::Text,
    widget::{
        column as icolumn, component,
        image::{self, Image},
        row, text, Component,
    },
    Alignment, Renderer,
};

use crate::theme::colours::SLATE_400;

pub fn track_card(artist: String, song: String, loved: bool) -> TrackCard {
    TrackCard {
        artist,
        song,
        loved,
    }
}

pub struct TrackCard {
    artist: String,
    song: String,
    loved: bool,
}

impl<M> Component<M, Renderer> for TrackCard {
    type State = State;
    type Event = Event;

    fn update(&mut self, _state: &mut Self::State, event: Self::Event) -> Option<M> {
        match event {}
    }

    fn view(&self, _state: &Self::State) -> Element<'_, Self::Event, Renderer> {
        row![
            Image::new(image::Handle::from_path("/tmp/tmp.jpg"))
                .width(64)
                .height(64),
            icolumn![
                text(&self.song).size(14),
                text(&self.artist).style(Text::Color(SLATE_400)).size(14)
            ]
        ]
        .align_items(Alignment::Center)
        .spacing(10)
        .into()
    }
}

#[derive(Default)]
pub struct State {}

#[derive(Clone)]
pub enum Event {}

impl<'a, M> From<TrackCard> for Element<'a, M, Renderer>
where
    M: 'a + Clone,
{
    fn from(card: TrackCard) -> Self {
        component(card)
    }
}