diff --git a/Cargo.lock b/Cargo.lock
index 54489cd..5336d3b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -69,6 +69,12 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd"
+[[package]]
+name = "aligned-vec"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1"
+
[[package]]
name = "allocator-api2"
version = "0.2.18"
@@ -117,6 +123,12 @@ dependencies = [
"libc",
]
+[[package]]
+name = "anyhow"
+version = "1.0.89"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6"
+
[[package]]
name = "approx"
version = "0.5.1"
@@ -126,6 +138,23 @@ dependencies = [
"num-traits",
]
+[[package]]
+name = "arbitrary"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110"
+
+[[package]]
+name = "arg_enum_proc_macro"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.77",
+]
+
[[package]]
name = "arrayref"
version = "0.3.9"
@@ -308,6 +337,29 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
+[[package]]
+name = "av1-grain"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf"
+dependencies = [
+ "anyhow",
+ "arrayvec",
+ "log",
+ "nom",
+ "num-rational",
+ "v_frame",
+]
+
+[[package]]
+name = "avif-serialize"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "876c75a42f6364451a033496a14c44bffe41f5f4a8236f697391f11024e596d2"
+dependencies = [
+ "arrayvec",
+]
+
[[package]]
name = "backtrace"
version = "0.3.74"
@@ -377,6 +429,12 @@ version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
+[[package]]
+name = "bitstream-io"
+version = "2.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b81e1519b0d82120d2fd469d5bfb2919a9361c48b02d82d04befc1cdd2002452"
+
[[package]]
name = "block"
version = "0.1.6"
@@ -414,6 +472,12 @@ dependencies = [
"piper",
]
+[[package]]
+name = "built"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "236e6289eda5a812bc6b53c3b024039382a2895fbbeef2d748b2931546d392c4"
+
[[package]]
name = "bumpalo"
version = "3.16.0"
@@ -452,6 +516,12 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+[[package]]
+name = "byteorder-lite"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
+
[[package]]
name = "bytes"
version = "1.7.2"
@@ -501,6 +571,16 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
+[[package]]
+name = "cfg-expr"
+version = "0.15.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02"
+dependencies = [
+ "smallvec",
+ "target-lexicon",
+]
+
[[package]]
name = "cfg-if"
version = "1.0.0"
@@ -533,6 +613,20 @@ dependencies = [
"windows-targets 0.52.6",
]
+[[package]]
+name = "clipboard-rs"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd61885ce9f906c60f40d384b132fed04685f51d3da3576d27b8e4f274b6100d"
+dependencies = [
+ "clipboard-win",
+ "image 0.25.2",
+ "objc2",
+ "objc2-app-kit",
+ "objc2-foundation",
+ "x11rb",
+]
+
[[package]]
name = "clipboard-win"
version = "5.4.0"
@@ -540,6 +634,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892"
dependencies = [
"error-code",
+ "windows-win",
]
[[package]]
@@ -1013,12 +1108,6 @@ dependencies = [
"syn 2.0.77",
]
-[[package]]
-name = "env_home"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe"
-
[[package]]
name = "equivalent"
version = "1.0.1"
@@ -1679,6 +1768,7 @@ dependencies = [
"tokio",
"tokio-rustls",
"tower-service",
+ "webpki-roots",
]
[[package]]
@@ -1751,7 +1841,7 @@ dependencies = [
"iced_renderer",
"iced_widget",
"iced_winit",
- "image",
+ "image 0.24.9",
"thiserror",
]
@@ -1790,6 +1880,15 @@ dependencies = [
"web-time",
]
+[[package]]
+name = "iced_fonts"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df7deb0800a850ee25c8a42559f72c0f249e577feb3aad37b9b65dc1e517e52a"
+dependencies = [
+ "iced_core",
+]
+
[[package]]
name = "iced_futures"
version = "0.13.2"
@@ -1830,7 +1929,7 @@ dependencies = [
"half",
"iced_core",
"iced_futures",
- "image",
+ "image 0.24.9",
"kamadak-exif",
"log",
"lyon_path",
@@ -1953,9 +2052,10 @@ dependencies = [
name = "icy_browser"
version = "0.1.0"
dependencies = [
- "env_home",
+ "clipboard-rs",
"iced",
"iced_aw",
+ "iced_fonts",
"iced_on_focus_widget",
"rand",
"reqwest",
@@ -1997,6 +2097,45 @@ dependencies = [
"tiff",
]
+[[package]]
+name = "image"
+version = "0.25.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10"
+dependencies = [
+ "bytemuck",
+ "byteorder-lite",
+ "color_quant",
+ "exr",
+ "gif",
+ "image-webp",
+ "num-traits",
+ "png",
+ "qoi",
+ "ravif",
+ "rayon",
+ "rgb",
+ "tiff",
+ "zune-core",
+ "zune-jpeg",
+]
+
+[[package]]
+name = "image-webp"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f79afb8cbee2ef20f59ccd477a218c12a93943d075b492015ecb1bb81f8ee904"
+dependencies = [
+ "byteorder-lite",
+ "quick-error",
+]
+
+[[package]]
+name = "imgref"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126"
+
[[package]]
name = "indexmap"
version = "2.5.0"
@@ -2016,6 +2155,17 @@ dependencies = [
"cfg-if",
]
+[[package]]
+name = "interpolate_name"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.77",
+]
+
[[package]]
name = "ipnet"
version = "2.10.0"
@@ -2149,6 +2299,17 @@ version = "0.2.158"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
+[[package]]
+name = "libfuzzer-sys"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7"
+dependencies = [
+ "arbitrary",
+ "cc",
+ "once_cell",
+]
+
[[package]]
name = "libloading"
version = "0.7.4"
@@ -2225,6 +2386,15 @@ version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
+[[package]]
+name = "loop9"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062"
+dependencies = [
+ "imgref",
+]
+
[[package]]
name = "lru"
version = "0.12.4"
@@ -2301,6 +2471,15 @@ dependencies = [
"libc",
]
+[[package]]
+name = "maybe-rayon"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519"
+dependencies = [
+ "cfg-if",
+]
+
[[package]]
name = "memchr"
version = "2.7.4"
@@ -2346,6 +2525,12 @@ version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
+[[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.4"
@@ -2459,6 +2644,12 @@ dependencies = [
"jni-sys",
]
+[[package]]
+name = "new_debug_unreachable"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
+
[[package]]
name = "nix"
version = "0.29.0"
@@ -2472,6 +2663,22 @@ dependencies = [
"memoffset",
]
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
+[[package]]
+name = "noop_proc_macro"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
+
[[package]]
name = "nt-time"
version = "0.8.1"
@@ -2482,12 +2689,33 @@ dependencies = [
"time",
]
+[[package]]
+name = "num-bigint"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
+dependencies = [
+ "num-integer",
+ "num-traits",
+]
+
[[package]]
name = "num-conv"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
+[[package]]
+name = "num-derive"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.77",
+]
+
[[package]]
name = "num-format"
version = "0.4.4"
@@ -2498,6 +2726,26 @@ dependencies = [
"itoa",
]
+[[package]]
+name = "num-integer"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-rational"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
+dependencies = [
+ "num-bigint",
+ "num-integer",
+ "num-traits",
+]
+
[[package]]
name = "num-traits"
version = "0.2.19"
@@ -3149,6 +3397,19 @@ name = "profiling"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58"
+dependencies = [
+ "profiling-procmacros",
+]
+
+[[package]]
+name = "profiling-procmacros"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd"
+dependencies = [
+ "quote",
+ "syn 2.0.77",
+]
[[package]]
name = "qoi"
@@ -3159,6 +3420,12 @@ dependencies = [
"bytemuck",
]
+[[package]]
+name = "quick-error"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
+
[[package]]
name = "quick-xml"
version = "0.36.2"
@@ -3168,6 +3435,54 @@ dependencies = [
"memchr",
]
+[[package]]
+name = "quinn"
+version = "0.11.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684"
+dependencies = [
+ "bytes",
+ "pin-project-lite",
+ "quinn-proto",
+ "quinn-udp",
+ "rustc-hash 2.0.0",
+ "rustls",
+ "socket2",
+ "thiserror",
+ "tokio",
+ "tracing",
+]
+
+[[package]]
+name = "quinn-proto"
+version = "0.11.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6"
+dependencies = [
+ "bytes",
+ "rand",
+ "ring",
+ "rustc-hash 2.0.0",
+ "rustls",
+ "slab",
+ "thiserror",
+ "tinyvec",
+ "tracing",
+]
+
+[[package]]
+name = "quinn-udp"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fe68c2e9e1a1234e218683dbdf9f9dfcb094113c5ac2b938dfcb9bab4c4140b"
+dependencies = [
+ "libc",
+ "once_cell",
+ "socket2",
+ "tracing",
+ "windows-sys 0.59.0",
+]
+
[[package]]
name = "quote"
version = "1.0.37"
@@ -3219,6 +3534,55 @@ version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f60fcc7d6849342eff22c4350c8b9a989ee8ceabc4b481253e8946b9fe83d684"
+[[package]]
+name = "rav1e"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9"
+dependencies = [
+ "arbitrary",
+ "arg_enum_proc_macro",
+ "arrayvec",
+ "av1-grain",
+ "bitstream-io",
+ "built",
+ "cfg-if",
+ "interpolate_name",
+ "itertools 0.12.1",
+ "libc",
+ "libfuzzer-sys",
+ "log",
+ "maybe-rayon",
+ "new_debug_unreachable",
+ "noop_proc_macro",
+ "num-derive",
+ "num-traits",
+ "once_cell",
+ "paste",
+ "profiling",
+ "rand",
+ "rand_chacha",
+ "simd_helpers",
+ "system-deps",
+ "thiserror",
+ "v_frame",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "ravif"
+version = "0.11.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8f0bfd976333248de2078d350bfdf182ff96e168a24d23d2436cef320dd4bdd"
+dependencies = [
+ "avif-serialize",
+ "imgref",
+ "loop9",
+ "quick-error",
+ "rav1e",
+ "rgb",
+]
+
[[package]]
name = "raw-window-handle"
version = "0.6.2"
@@ -3326,7 +3690,10 @@ dependencies = [
"once_cell",
"percent-encoding",
"pin-project-lite",
+ "quinn",
+ "rustls",
"rustls-pemfile",
+ "rustls-pki-types",
"serde",
"serde_json",
"serde_urlencoded",
@@ -3334,14 +3701,25 @@ dependencies = [
"system-configuration",
"tokio",
"tokio-native-tls",
+ "tokio-rustls",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
+ "webpki-roots",
"windows-registry",
]
+[[package]]
+name = "rgb"
+version = "0.8.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a"
+dependencies = [
+ "bytemuck",
+]
+
[[package]]
name = "ring"
version = "0.17.8"
@@ -3590,6 +3968,15 @@ dependencies = [
"syn 2.0.77",
]
+[[package]]
+name = "serde_spanned"
+version = "0.6.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
+dependencies = [
+ "serde",
+]
+
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
@@ -3662,6 +4049,15 @@ version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
+[[package]]
+name = "simd_helpers"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6"
+dependencies = [
+ "quote",
+]
+
[[package]]
name = "siphasher"
version = "0.3.11"
@@ -3925,6 +4321,25 @@ dependencies = [
"libc",
]
+[[package]]
+name = "system-deps"
+version = "6.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349"
+dependencies = [
+ "cfg-expr",
+ "heck 0.5.0",
+ "pkg-config",
+ "toml",
+ "version-compare",
+]
+
+[[package]]
+name = "target-lexicon"
+version = "0.12.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
+
[[package]]
name = "tempfile"
version = "3.12.0"
@@ -4111,11 +4526,26 @@ dependencies = [
"tokio",
]
+[[package]]
+name = "toml"
+version = "0.8.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
+dependencies = [
+ "serde",
+ "serde_spanned",
+ "toml_datetime",
+ "toml_edit",
+]
+
[[package]]
name = "toml_datetime"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
+dependencies = [
+ "serde",
+]
[[package]]
name = "toml_edit"
@@ -4124,6 +4554,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b072cee73c449a636ffd6f32bd8de3a9f7119139aff882f44943ce2986dc5cf"
dependencies = [
"indexmap",
+ "serde",
+ "serde_spanned",
"toml_datetime",
"winnow",
]
@@ -4351,12 +4783,29 @@ dependencies = [
"percent-encoding",
]
+[[package]]
+name = "v_frame"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b"
+dependencies = [
+ "aligned-vec",
+ "num-traits",
+ "wasm-bindgen",
+]
+
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+[[package]]
+name = "version-compare"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b"
+
[[package]]
name = "version_check"
version = "0.9.5"
@@ -4894,6 +5343,15 @@ dependencies = [
"windows_x86_64_msvc 0.52.6",
]
+[[package]]
+name = "windows-win"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58e23e33622b3b52f948049acbec9bcc34bf6e26d74176b88941f213c75cf2dc"
+dependencies = [
+ "error-code",
+]
+
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.2"
@@ -5276,6 +5734,12 @@ version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
+[[package]]
+name = "zune-core"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a"
+
[[package]]
name = "zune-inflate"
version = "0.2.54"
@@ -5285,6 +5749,15 @@ dependencies = [
"simd-adler32",
]
+[[package]]
+name = "zune-jpeg"
+version = "0.4.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16099418600b4d8f028622f73ff6e3deaabdff330fb9a2a131dea781ee8b0768"
+dependencies = [
+ "zune-core",
+]
+
[[package]]
name = "zvariant"
version = "4.2.0"
diff --git a/Cargo.toml b/Cargo.toml
index de977db..1eead15 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -22,22 +22,19 @@ incremental = true
opt-level = "s"
lto = "thin"
-[build]
-rustflags = ["-Z", "threads=8"]
-
[features]
-default = ["webkit"]
+default = ["webkit", "native-tls", "ultralight-resources"]
webkit = ["ultralight"]
ultralight = ["ul-next"]
+ultralight-resources = []
+native-tls = ["reqwest/native-tls"]
+rustls-tls = ["reqwest/rustls-tls"]
[dependencies]
-env_home = "0.1.0"
+clipboard-rs = "0.2.1"
iced = { version = "0.13", features = ["advanced", "image", "tokio", "lazy"] }
-iced_aw = { version = "0.10", features = [
- "tab_bar",
- "icons",
- "selection_list",
-] }
+iced_aw = { version = "0.10", features = ["tab_bar", "selection_list"] }
+iced_fonts = { version = "0.1.1", features = ["bootstrap"] }
iced_on_focus_widget = "0.1.1"
rand = "0.8.5"
reqwest = "0.12.5"
diff --git a/README.md b/README.md
index ab88561..e3fdff5 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,7 @@
## Iced library to create custom browsers
+
-[![Build](https://github.com/LegitCamper/rust-browser/actions/workflows/ci.yml/badge.svg)](https://github.com/LegitCamper/rust-browser/actions/workflows/ci.yml)
-
-
-### Supported Platforms
-| Platform | Support |
-| Windows | ✓ |
-| Linux | ✓ |
-
+[![Build](https://github.com/LegitCamper/icy_browser/actions/workflows/ci.yml/badge.svg)](https://github.com/LegitCamper/icy_browser/actions/workflows/ci.yml)
### Supported Browser Engines
| Browser Engine | Support |
@@ -19,64 +13,38 @@
### Browser Widgets
- Navigation Bar
- Tab Bar
+- Bookmark Bar
- Browser View
### Examples
#### basic_browser.rs
-
+
``` Rust
-use iced::{Element, Settings, Theme};
-use iced_aw::BOOTSTRAP_FONT_BYTES;
+use iced::{Settings, Task, Theme};
+use icy_browser::{get_fonts, Bookmark, IcyBrowser, Message, Ultralight};
-use icy_browser::{widgets, BrowserWidget, Ultralight};
+fn run() -> (IcyBrowser, Task) {
+ (
+ IcyBrowser::new()
+ .with_tab_bar()
+ .with_nav_bar()
+ .with_bookmark_bar(&[Bookmark::new("https://www.rust-lang.org", "rust-lang.org")])
+ .build(),
+ Task::none(),
+ )
+}
fn main() -> iced::Result {
- // This imports `icons` for widgets
- let bootstrap_font = BOOTSTRAP_FONT_BYTES.into();
let settings = Settings {
- fonts: vec![bootstrap_font],
+ fonts: get_fonts(),
..Default::default()
};
- iced::application("Basic Browser Example", Browser::update, Browser::view)
+ iced::application("Basic Browser", IcyBrowser::update, IcyBrowser::view)
+ .subscription(IcyBrowser::subscription)
.settings(settings)
.theme(|_| Theme::Dark)
- .run()
-}
-
-#[derive(Debug, Clone)]
-pub enum Message {
- BrowserWidget(widgets::Message),
-}
-
-struct Browser {
- widgets: BrowserWidget,
-}
-
-impl Browser {
- fn update(&mut self, message: Message) {
- match message {
- Message::BrowserWidget(msg) => {
- self.widgets.update(msg);
- }
- }
- }
-
- fn view(&self) -> Element {
- self.widgets.view().map(Message::BrowserWidget)
- }
-}
-
-impl Default for Browser {
- fn default() -> Self {
- let widgets = BrowserWidget::new_with_ultralight()
- .with_tab_bar()
- .with_nav_bar()
- .with_browsesr_view()
- .build();
-
- Self { widgets }
- }
+ .run_with(run)
}
```
diff --git a/build.rs b/build.rs
index bd3bd7f..c85e226 100644
--- a/build.rs
+++ b/build.rs
@@ -1,15 +1,28 @@
+use std::env::var;
use std::fs::{self, DirEntry};
use std::path::Path;
-const PATH: &str = env!("CARGO_MANIFEST_DIR");
-
fn main() {
- // ensure runtime resources exist
- #[cfg(feature = "ultralight")]
+ // ensure runtime resources exist - for examples & local tests
+ #[cfg(feature = "ultralight-resources")]
{
+ let out = var("OUT_DIR").unwrap();
+ // This allows it to work in this project but also other projects too
+ let path = Path::new(&out)
+ .parent()
+ .unwrap()
+ .parent()
+ .unwrap()
+ .parent()
+ .unwrap()
+ .parent()
+ .unwrap()
+ .parent()
+ .unwrap();
+
let mut possible_directories = Vec::new();
- let target = Path::new(PATH).join("target");
+ let target = Path::new(path).join("target");
let debug_path = target.clone().join("debug");
let release_path = target.clone().join("release");
@@ -33,33 +46,24 @@ fn main() {
assert!(!possible_directories.is_empty());
- let local_resources = Path::new(PATH).join("resources");
+ let local_resources = Path::new(path).join("resources");
for path in possible_directories {
- if let Ok(resources) = fs::exists(path.path().join("out/ul-sdk/resources")) {
+ let resources_dir = path.path().join("out/ul-sdk/resources");
+ if let Ok(resources) = fs::exists(resources_dir.clone()) {
if resources {
if let Ok(local_resources_exist) = fs::exists(local_resources.clone()) {
if local_resources_exist {
- fs::remove_dir_all(local_resources.clone())
+ fs::remove_file(local_resources.clone())
.expect("Failed to delete resources dir")
}
}
- fs::create_dir(local_resources.clone())
- .expect("Failed to create resources dir");
-
- copy_file(
- path.path().join("out/ul-sdk/resources").as_path(),
- local_resources.clone().join("").as_path(),
- "cacert.pem",
- )
- .expect("Failed to copy cacert.pem");
- copy_file(
- path.path().join("out/ul-sdk/resources").as_path(),
- local_resources.clone().join("").as_path(),
- "icudt67l.dat",
- )
- .expect("Failed to copy icudt67l.dat");
+ #[cfg(unix)]
+ {
+ std::os::unix::fs::symlink(resources_dir, local_resources)
+ .expect("Failed to sym link resource dir")
+ }
break;
}
@@ -69,15 +73,11 @@ fn main() {
}
}
- println!("cargo:rerun-if-changed=resources");
+ println!("cargo:rerun-if-changed=target");
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=Cargo.lock");
}
-fn copy_file(from: &Path, to: &Path, file_name: &str) -> Result {
- fs::copy(from.join(file_name), to.join(file_name))
-}
-
fn get_paths(possible_paths: &mut Vec, path_str: String) {
let mut paths: Vec = fs::read_dir(path_str)
.expect("Could not read dir")
diff --git a/examples/basic_browser.rs b/examples/basic_browser.rs
index d1fa436..ee8d680 100644
--- a/examples/basic_browser.rs
+++ b/examples/basic_browser.rs
@@ -1,62 +1,28 @@
-// Simple browser with familiar browser widget and the ultralight(webkit) webengine as a backend
+// Simple browser with familiar browser widgets and the ultralight(webkit) webengine as a backend
-use iced::Theme;
-use iced::{Element, Settings, Subscription, Task};
-use iced_aw::BOOTSTRAP_FONT_BYTES;
-use std::time::Duration;
+use iced::{Settings, Task, Theme};
+use icy_browser::{get_fonts, Bookmark, IcyBrowser, Message, Ultralight};
-use icy_browser::{widgets, BrowserWidget, Ultralight};
+fn run() -> (IcyBrowser, Task) {
+ (
+ IcyBrowser::new()
+ .with_tab_bar()
+ .with_nav_bar()
+ .with_bookmark_bar(&[Bookmark::new("https://www.rust-lang.org", "rust-lang.org")])
+ .build(),
+ Task::none(),
+ )
+}
fn main() -> iced::Result {
- // This imports `icons` for widgets
- let bootstrap_font = BOOTSTRAP_FONT_BYTES.into();
let settings = Settings {
- fonts: vec![bootstrap_font],
+ fonts: get_fonts(),
..Default::default()
};
- iced::application("Basic Browser Example", Browser::update, Browser::view)
- .subscription(Browser::subscription)
+ iced::application("Basic Browser", IcyBrowser::update, IcyBrowser::view)
+ .subscription(IcyBrowser::subscription)
.settings(settings)
.theme(|_| Theme::Dark)
- .run()
-}
-
-#[derive(Debug, Clone)]
-pub enum Message {
- BrowserWidget(widgets::Message), // Passes messagees to Browser widgets
- Update,
-}
-
-struct Browser {
- widgets: BrowserWidget,
-}
-
-impl Default for Browser {
- fn default() -> Self {
- // Customize the look and feel of the browser here
- let widgets = BrowserWidget::new_with_ultralight()
- .with_tab_bar()
- .with_nav_bar()
- .build();
-
- Self { widgets }
- }
-}
-
-impl Browser {
- fn update(&mut self, message: Message) -> Task {
- match message {
- Message::BrowserWidget(msg) => self.widgets.update(msg).map(Message::BrowserWidget),
- Message::Update => self.widgets.force_update().map(Message::BrowserWidget),
- }
- }
-
- fn view(&self) -> Element {
- self.widgets.view().map(Message::BrowserWidget)
- }
-
- fn subscription(&self) -> Subscription {
- iced::time::every(Duration::from_millis(10)).map(move |_| Message::Update)
- }
+ .run_with(run)
}
diff --git a/examples/custom_widgets.rs b/examples/custom_widgets.rs
new file mode 100644
index 0000000..dd280fe
--- /dev/null
+++ b/examples/custom_widgets.rs
@@ -0,0 +1,92 @@
+// Custom view example with rainbow border
+
+use iced::widget::container;
+use iced::{time, Border};
+use iced::{Color, Element, Length, Settings, Subscription, Task, Theme};
+use std::time::{Duration, Instant};
+
+use icy_browser::{get_fonts, widgets, IcyBrowser, Ultralight};
+
+fn main() -> iced::Result {
+ let settings = Settings {
+ fonts: get_fonts(),
+ ..Default::default()
+ };
+
+ iced::application("Keyboard Driven Browser", Browser::update, Browser::view)
+ .subscription(Browser::subscription)
+ .settings(settings)
+ .theme(|_| Theme::Dark)
+ .run()
+}
+
+#[derive(Debug, Clone)]
+pub enum Message {
+ BrowserWidget(widgets::Message), // Passes messagees to Browser widgets
+ Update,
+ Tick,
+}
+
+#[derive(Debug, Clone)]
+struct CustomWidgetState {
+ border_colors: Vec,
+ start_time: Instant,
+}
+
+struct Browser {
+ icy_browser: IcyBrowser,
+ custom_widget_state: CustomWidgetState,
+}
+
+impl Default for Browser {
+ fn default() -> Self {
+ Self {
+ icy_browser: IcyBrowser::new().with_tab_bar().with_nav_bar().build(),
+ custom_widget_state: CustomWidgetState {
+ border_colors: vec![
+ Color::from_rgb(1.0, 0.0, 0.0), // Red
+ Color::from_rgb(1.0, 0.5, 0.0), // Orange
+ Color::from_rgb(1.0, 1.0, 0.0), // Yellow
+ Color::from_rgb(0.0, 1.0, 0.0), // Green
+ Color::from_rgb(0.0, 0.0, 1.0), // Blue
+ Color::from_rgb(0.29, 0.0, 0.51), // Indigo
+ Color::from_rgb(0.56, 0.0, 1.0), // Violet
+ ],
+ start_time: Instant::now(),
+ },
+ }
+ }
+}
+
+impl Browser {
+ fn update(&mut self, message: Message) -> Task {
+ match message {
+ Message::BrowserWidget(msg) => self.icy_browser.update(msg).map(Message::BrowserWidget),
+ Message::Update => self.icy_browser.force_update().map(Message::BrowserWidget),
+ Message::Tick => Task::none(), // Tick
+ }
+ }
+
+ fn view(&self) -> Element {
+ let elapsed = self.custom_widget_state.start_time.elapsed().as_secs_f32();
+ let color_index = (elapsed * 2.0) as usize % self.custom_widget_state.border_colors.len();
+ let color = self.custom_widget_state.border_colors[color_index];
+
+ container(self.icy_browser.view().map(Message::BrowserWidget))
+ .center_x(Length::Fill)
+ .center_y(Length::Fill)
+ .padding(20)
+ .style(move |_theme| container::Style {
+ border: Border::default().color(color).width(20),
+ ..Default::default()
+ })
+ .into()
+ }
+
+ fn subscription(&self) -> Subscription {
+ Subscription::batch([
+ time::every(Duration::from_millis(10)).map(move |_| Message::Update),
+ time::every(Duration::from_millis(16)).map(|_| Message::Tick),
+ ])
+ }
+}
diff --git a/examples/keyboard_driven.rs b/examples/keyboard_driven.rs
index 1fd4b63..9b948cc 100644
--- a/examples/keyboard_driven.rs
+++ b/examples/keyboard_driven.rs
@@ -3,22 +3,21 @@
use iced::event::{self, Event};
use iced::Theme;
use iced::{Element, Settings, Subscription, Task};
-use iced_aw::BOOTSTRAP_FONT_BYTES;
use std::time::Duration;
use icy_browser::{
- widgets, BrowserWidget, KeyType, Message as WidgetMessage, ShortcutBuilder, ShortcutModifier,
- Ultralight,
+ get_fonts, widgets, Bookmark, IcyBrowser, KeyType, Message as WidgetMessage, ShortcutBuilder,
+ ShortcutModifier, Ultralight,
};
fn main() -> iced::Result {
- // This imports `icons` for widgets
- let bootstrap_font = BOOTSTRAP_FONT_BYTES.into();
let settings = Settings {
- fonts: vec![bootstrap_font],
+ fonts: get_fonts(),
..Default::default()
};
+ println!("Press 'Crtl + E' to open to Command palatte");
+
iced::application("Keyboard Driven Browser", Browser::update, Browser::view)
.subscription(Browser::subscription)
.settings(settings)
@@ -34,7 +33,7 @@ pub enum Message {
}
struct Browser {
- widgets: BrowserWidget,
+ icy_browser: IcyBrowser,
}
impl Default for Browser {
@@ -48,30 +47,39 @@ impl Default for Browser {
],
)
.build();
- let widgets = BrowserWidget::new_with_ultralight()
+ let widgets = IcyBrowser::new()
.with_custom_shortcuts(shortcuts)
.with_tab_bar()
- .with_nav_bar()
+ .with_bookmark_bar(&[
+ Bookmark::new("https://www.rust-lang.org", "rust-lang.org"),
+ Bookmark::new(
+ "https://github.com/LegitCamper/icy_browser",
+ "icy_browser github",
+ ),
+ Bookmark::new("https://docs.rs/iced/latest/iced/", "iced docs"),
+ ])
.build();
- Self { widgets }
+ Self {
+ icy_browser: widgets,
+ }
}
}
impl Browser {
fn update(&mut self, message: Message) -> Task {
match message {
- Message::BrowserWidget(msg) => self.widgets.update(msg).map(Message::BrowserWidget),
- Message::Update => self.widgets.force_update().map(Message::BrowserWidget),
+ Message::BrowserWidget(msg) => self.icy_browser.update(msg).map(Message::BrowserWidget),
+ Message::Update => self.icy_browser.force_update().map(Message::BrowserWidget),
Message::Event(event) => self
- .widgets
- .update(widgets::Message::Event(Some(event)))
+ .icy_browser
+ .update(widgets::Message::IcedEvent(Some(event)))
.map(Message::BrowserWidget),
}
}
fn view(&self) -> Element {
- self.widgets.view().map(Message::BrowserWidget)
+ self.icy_browser.view().map(Message::BrowserWidget)
}
fn subscription(&self) -> Subscription {
diff --git a/src/engines/ultralight.rs b/src/engines/ultralight.rs
index 4eba7ae..fbd43f8 100644
--- a/src/engines/ultralight.rs
+++ b/src/engines/ultralight.rs
@@ -1,3 +1,4 @@
+use clipboard_rs::{Clipboard, ClipboardContext};
use iced::keyboard::{self};
use iced::mouse::{self, ScrollDelta};
use iced::{Point, Size};
@@ -7,7 +8,7 @@ use ul_next::{
config::Config,
event::{self, KeyEventCreationInfo, MouseEvent, ScrollEvent},
key_code::VirtualKeyCode,
- platform::{self, LogLevel, Logger},
+ platform,
renderer::Renderer,
view::{View, ViewConfig},
window::Cursor,
@@ -15,15 +16,21 @@ use ul_next::{
};
use url::Url;
-#[cfg(not(debug_assertions))]
-use env_home::env_home_dir;
-
use super::{BrowserEngine, PixelFormat, Tab, TabInfo, Tabs};
-struct UlLogger;
-impl Logger for UlLogger {
- fn log_message(&mut self, log_level: LogLevel, message: String) {
- println!("{:?}: {}", log_level, message);
+struct UlClipboard;
+impl platform::Clipboard for UlClipboard {
+ fn clear(&mut self) {}
+
+ fn read_plain_text(&mut self) -> Option {
+ let ctx = clipboard_rs::ClipboardContext::new().ok()?;
+ Some(ctx.get_text().unwrap_or("".to_string()))
+ }
+
+ fn write_plain_text(&mut self, text: &str) {
+ let ctx = ClipboardContext::new().expect("Failed to open clipboard");
+ ctx.set_text(text.into())
+ .expect("Failed to set contents of clipboard");
}
}
@@ -35,9 +42,7 @@ pub struct UltalightTabInfo {
impl TabInfo for UltalightTabInfo {
fn title(&self) -> String {
- self.view
- .title()
- .expect("Failed to get title from ultralight")
+ self.view.title().unwrap_or("Title Error".to_string())
}
fn url(&self) -> String {
@@ -61,26 +66,8 @@ impl Ultralight {
pub fn new() -> Self {
let config = Config::start().build().unwrap();
platform::enable_platform_fontloader();
-
- #[cfg(not(debug_assertions))]
- let mut home_dir = env_home_dir().unwrap();
- #[cfg(not(debug_assertions))]
- home_dir.push(".icy_browser");
- #[cfg(not(debug_assertions))]
- platform::enable_platform_filesystem(home_dir.as_path()).unwrap();
-
- #[cfg(debug_assertions)]
platform::enable_platform_filesystem(".").unwrap();
-
- platform::set_logger(UlLogger);
-
- #[cfg(not(debug_assertions))]
- home_dir.push("logs.txt");
- #[cfg(not(debug_assertions))]
- platform::enable_default_logger(home_dir.as_path()).unwrap();
-
- #[cfg(debug_assertions)]
- platform::enable_default_logger("./logs.txt").unwrap();
+ platform::set_clipboard(UlClipboard);
let renderer = Renderer::create(config).unwrap();
let view_config = ViewConfig::start()
@@ -270,10 +257,11 @@ impl BrowserEngine for Ultralight {
location,
modifiers,
text,
- modified_key: _,
+ modified_key,
physical_key: _,
} => iced_key_to_ultralight_key(
KeyPress::Press,
+ Some(modified_key),
Some(key),
Some(location),
modifiers,
@@ -285,13 +273,14 @@ impl BrowserEngine for Ultralight {
modifiers,
} => iced_key_to_ultralight_key(
KeyPress::Unpress,
+ None,
Some(key),
Some(location),
modifiers,
None,
),
keyboard::Event::ModifiersChanged(modifiers) => {
- iced_key_to_ultralight_key(KeyPress::Press, None, None, modifiers, None)
+ iced_key_to_ultralight_key(KeyPress::Press, None, None, None, modifiers, None)
}
};
@@ -384,7 +373,8 @@ enum KeyPress {
fn iced_key_to_ultralight_key(
press: KeyPress,
- key: Option,
+ modified_key: Option,
+ key: Option, // This one is modified by ctrl and results in wrong key
_location: Option,
modifiers: keyboard::Modifiers,
text: Option,
@@ -592,7 +582,7 @@ fn iced_key_to_ultralight_key(
keyboard::key::Named::F12 => (
VirtualKeyCode::F12,
#[cfg(windows)]
- 122,
+ 123,
#[cfg(unix)]
70,
),
@@ -872,6 +862,97 @@ fn iced_key_to_ultralight_key(
#[cfg(unix)]
39,
),
+ "-" => (
+ VirtualKeyCode::OemMinus,
+ #[cfg(windows)]
+ 189,
+ #[cfg(unix)]
+ 12,
+ ),
+ "_" => (
+ VirtualKeyCode::OemMinus,
+ #[cfg(windows)]
+ 189,
+ #[cfg(unix)]
+ 12,
+ ),
+ "+" => (
+ VirtualKeyCode::OemPlus,
+ #[cfg(windows)]
+ 187,
+ #[cfg(unix)]
+ 78,
+ ),
+ "=" => (
+ VirtualKeyCode::OemPlus,
+ #[cfg(windows)]
+ 187,
+ #[cfg(unix)]
+ 78,
+ ),
+ "\\" => (
+ VirtualKeyCode::Oem5,
+ #[cfg(windows)]
+ 220,
+ #[cfg(unix)]
+ 43,
+ ),
+ "|" => (
+ VirtualKeyCode::Oem5,
+ #[cfg(windows)]
+ 220,
+ #[cfg(unix)]
+ 43,
+ ),
+ "`" => (
+ VirtualKeyCode::Oem3,
+ #[cfg(windows)]
+ 192,
+ #[cfg(unix)]
+ 41,
+ ),
+ "?" => (
+ VirtualKeyCode::Oem2,
+ #[cfg(windows)]
+ 191,
+ #[cfg(unix)]
+ 53,
+ ),
+ "/" => (
+ VirtualKeyCode::Oem2,
+ #[cfg(windows)]
+ 191,
+ #[cfg(unix)]
+ 53,
+ ),
+ ">" => (
+ VirtualKeyCode::Oem102,
+ #[cfg(windows)]
+ 226,
+ #[cfg(unix)]
+ 52,
+ ),
+ "<" => (
+ VirtualKeyCode::Oem102,
+ #[cfg(windows)]
+ 226,
+ #[cfg(unix)]
+ 52,
+ ),
+ "[" => (
+ VirtualKeyCode::Oem4,
+ #[cfg(windows)]
+ 219,
+ #[cfg(unix)]
+ 26,
+ ),
+ "]" => (
+ VirtualKeyCode::Oem6,
+ #[cfg(windows)]
+ 221,
+ #[cfg(unix)]
+ 27,
+ ),
_ => return None,
},
keyboard::Key::Unidentified => return None,
@@ -882,7 +963,16 @@ fn iced_key_to_ultralight_key(
}
};
- let ty = if !text.is_empty() && text.is_ascii() && press == KeyPress::Press {
+ let modifiers = event::KeyEventModifiers {
+ alt: modifiers.alt(),
+ ctrl: modifiers.control(),
+ meta: modifiers.logo(),
+ shift: modifiers.shift(),
+ };
+
+ let ty = if modifiers.ctrl {
+ event::KeyEventType::RawKeyDown
+ } else if !text.is_empty() && text.is_ascii() && press == KeyPress::Press {
event::KeyEventType::Char
} else {
match press {
@@ -891,25 +981,19 @@ fn iced_key_to_ultralight_key(
}
};
- let modifiers = event::KeyEventModifiers {
- alt: modifiers.alt(),
- ctrl: modifiers.control(),
- meta: modifiers.logo(),
- shift: modifiers.shift(),
- };
-
let creation_info = KeyEventCreationInfo {
ty,
modifiers,
virtual_key_code: virtual_key,
native_key_code: native_key,
text: text.as_str(),
- unmodified_text: text.as_str(),
+ unmodified_text: if let Some(keyboard::Key::Character(char)) = modified_key {
+ &char.to_string()
+ } else {
+ text.as_str()
+ },
is_keypad: false,
is_auto_repeat: false,
- #[cfg(windows)]
- is_system_key: true,
- #[cfg(not(windows))]
is_system_key: false,
};
diff --git a/src/helpers.rs b/src/helpers.rs
new file mode 100644
index 0000000..9fd9993
--- /dev/null
+++ b/src/helpers.rs
@@ -0,0 +1,102 @@
+use iced::widget::{
+ button,
+ image::{Handle, Image},
+ Button,
+};
+pub use iced_fonts::BOOTSTRAP_FONT_BYTES;
+use std::{borrow::Cow, str::FromStr};
+use url::{ParseError, Url};
+
+use super::{Message, PixelFormat};
+
+// Helper function to ensure required icons are imported
+pub fn get_fonts() -> Vec> {
+ vec![BOOTSTRAP_FONT_BYTES.into()]
+}
+
+// Image details for passing the view around
+#[derive(Debug, Clone)]
+pub struct ImageInfo {
+ pub pixels: Vec,
+ pub width: u32,
+ pub height: u32,
+}
+
+impl Default for ImageInfo {
+ fn default() -> Self {
+ Self {
+ pixels: vec![255; (Self::WIDTH as usize * Self::HEIGHT as usize) * 4],
+ width: Self::WIDTH,
+ height: Self::HEIGHT,
+ }
+ }
+}
+
+impl ImageInfo {
+ // The default dimentions
+ const WIDTH: u32 = 800;
+ const HEIGHT: u32 = 800;
+
+ pub fn new(pixels: Vec, format: PixelFormat, width: u32, height: u32) -> Self {
+ // R, G, B, A
+ assert_eq!(pixels.len() % 4, 0);
+
+ let pixels = match format {
+ PixelFormat::Rgba => pixels,
+ PixelFormat::Bgra => pixels
+ .chunks(4)
+ .flat_map(|chunk| [chunk[2], chunk[1], chunk[0], chunk[3]])
+ .collect(),
+ };
+
+ Self {
+ pixels,
+ width,
+ height,
+ }
+ }
+
+ pub fn as_image(&self) -> Image {
+ Image::new(Handle::from_rgba(
+ self.width,
+ self.height,
+ self.pixels.clone(),
+ ))
+ }
+}
+
+pub fn to_url(url: &str) -> Option {
+ match Url::parse(url) {
+ Ok(url) => Some(url),
+ Err(error) => {
+ if let ParseError::RelativeUrlWithoutBase = error {
+ let mut base = String::from("https://");
+ base.push_str(url);
+ Url::parse(&base).ok()
+ } else {
+ None
+ }
+ }
+ }
+}
+
+pub type Bookmarks = Vec;
+
+#[derive(Debug, Clone)]
+pub struct Bookmark {
+ url: Url,
+ name: String,
+ // icon: Optional<>
+}
+impl Bookmark {
+ pub fn new(url: &str, name: &str) -> Self {
+ Bookmark {
+ url: Url::from_str(url).expect("Failed to parse url from bookmark url"),
+ name: name.to_string(),
+ }
+ }
+
+ pub fn as_button(&self) -> Button {
+ button(self.name.as_str()).on_press(Message::GoToUrl(self.url.to_string()))
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 6c47b35..a14bb4f 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,5 +1,5 @@
-use iced::widget::image::{Handle, Image};
-use url::{ParseError, Url};
+pub use iced_fonts::BOOTSTRAP_FONT_BYTES;
+pub use iced_on_focus_widget::hoverable;
mod engines;
pub use engines::{BrowserEngine, PixelFormat, Tab, TabInfo, Tabs};
@@ -8,73 +8,10 @@ pub use engines::{BrowserEngine, PixelFormat, Tab, TabInfo, Tabs};
pub use engines::ultralight::Ultralight;
pub mod widgets;
-pub use widgets::{nav_bar, tab_bar, BrowserWidget, Message};
+pub use widgets::{browser_view, command_window, nav_bar, tab_bar, IcyBrowser, Message};
+
+mod helpers;
+pub use helpers::{get_fonts, to_url, Bookmark, Bookmarks, ImageInfo};
mod shortcut;
pub use shortcut::{KeyType, Shortcut, ShortcutBuilder, ShortcutModifier, Shortcuts};
-
-// Image details for passing the view around
-#[derive(Debug, Clone)]
-pub struct ImageInfo {
- pixels: Vec,
- width: u32,
- height: u32,
-}
-
-impl Default for ImageInfo {
- fn default() -> Self {
- Self {
- pixels: vec![255; (Self::WIDTH as usize * Self::HEIGHT as usize) * 4],
- width: Self::WIDTH,
- height: Self::HEIGHT,
- }
- }
-}
-
-impl ImageInfo {
- // The default dimentions
- const WIDTH: u32 = 800;
- const HEIGHT: u32 = 800;
-
- pub fn new(pixels: Vec, format: PixelFormat, width: u32, height: u32) -> Self {
- // R, G, B, A
- assert_eq!(pixels.len() % 4, 0);
-
- let pixels = match format {
- PixelFormat::Rgba => pixels,
- PixelFormat::Bgra => pixels
- .chunks(4)
- .flat_map(|chunk| [chunk[2], chunk[1], chunk[0], chunk[3]])
- .collect(),
- };
-
- Self {
- pixels,
- width,
- height,
- }
- }
-
- fn as_image(&self) -> Image {
- Image::new(Handle::from_rgba(
- self.width,
- self.height,
- self.pixels.clone(),
- ))
- }
-}
-
-fn to_url(url: &str) -> Option {
- match Url::parse(url) {
- Ok(url) => Some(url),
- Err(error) => {
- if let ParseError::RelativeUrlWithoutBase = error {
- let mut base = String::from("https://");
- base.push_str(url);
- Url::parse(&base).ok()
- } else {
- None
- }
- }
- }
-}
diff --git a/src/widgets/bookmark_bar.rs b/src/widgets/bookmark_bar.rs
new file mode 100644
index 0000000..dc707c4
--- /dev/null
+++ b/src/widgets/bookmark_bar.rs
@@ -0,0 +1,17 @@
+use iced::{widget::Row, Element};
+
+use super::Message;
+use crate::Bookmark;
+
+/// Creates bookmark bar widget
+pub fn bookmark_bar(bookmarks: &[Bookmark]) -> Element {
+ Row::from_vec(
+ bookmarks
+ .iter()
+ .map(|bookmark| bookmark.as_button().into())
+ .collect(),
+ )
+ .padding(5)
+ .spacing(5)
+ .into()
+}
diff --git a/src/widgets/command_window.rs b/src/widgets/command_window.rs
index 99668be..40af61f 100644
--- a/src/widgets/command_window.rs
+++ b/src/widgets/command_window.rs
@@ -1,13 +1,18 @@
use iced::widget::{center, column, container, mouse_area, opaque, stack, text_input};
-use iced::{border, Color, Element, Length, Theme};
+use iced::{border, Color, Element, Font, Length, Theme};
use iced_aw::SelectionList;
use strum::IntoEnumIterator;
use super::Message;
+// pub enum ResultType {
+// Command(Message),
+// // Bookmark,
+// }
+
pub struct CommandWindowState {
pub query: String,
- actions: Vec,
+ commands: Vec,
pub selected_action: String,
pub selected_index: usize,
}
@@ -16,7 +21,7 @@ impl CommandWindowState {
pub fn new() -> Self {
Self {
query: String::new(),
- actions: Message::iter().map(|e| e.clone().to_string()).collect(),
+ commands: Message::iter().map(|e| e.clone().to_string()).collect(),
selected_action: String::new(),
selected_index: 0,
}
@@ -34,15 +39,24 @@ pub fn command_window<'a>(
state: &'a CommandWindowState,
) -> Element<'a, Message> {
let window = container(column![
- text_input("Command Menu", &state.query).on_input(Message::QueryChanged),
- SelectionList::new(&state.actions, Message::CommandSelectionChanged)
- .width(Length::Fill)
- .height(Length::Fill)
- .style(|theme: &Theme, _| iced_aw::style::selection_list::Style {
+ text_input("Command Menu", &state.query)
+ .on_input(Message::QueryChanged)
+ .size(25),
+ SelectionList::new_with(
+ &state.commands,
+ Message::CommandSelectionChanged,
+ 15.,
+ 5,
+ |theme: &Theme, _| iced_aw::style::selection_list::Style {
text_color: theme.palette().text,
background: theme.palette().background.into(),
..Default::default()
- }),
+ },
+ None,
+ Font::DEFAULT
+ )
+ .width(Length::Fill)
+ .height(Length::Fill)
])
.padding(10)
.center(600)
diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs
index 993fd37..b524bdb 100644
--- a/src/widgets/mod.rs
+++ b/src/widgets/mod.rs
@@ -1,26 +1,46 @@
use command_window::CommandWindowState;
use iced::keyboard::{self, key};
use iced::widget::{self, column};
-use iced::{event::Event, mouse, Element, Point, Size, Task};
+use iced::{mouse, Element, Event, Point, Size, Subscription, Task};
use iced_on_focus_widget::hoverable;
use nav_bar::NavBarState;
use std::string::ToString;
+use std::time::Duration;
use strum_macros::{Display, EnumIter};
use url::Url;
mod browser_view;
pub use browser_view::browser_view;
-mod nav_bar;
+pub mod nav_bar;
pub use nav_bar::nav_bar;
-mod tab_bar;
+pub mod tab_bar;
pub use tab_bar::tab_bar;
-mod command_window;
+pub mod bookmark_bar;
+pub use bookmark_bar::bookmark_bar;
+
+pub mod command_window;
pub use command_window::command_window;
-use crate::{engines::BrowserEngine, shortcut::check_shortcut, to_url, ImageInfo, Shortcuts};
+use crate::{
+ engines::BrowserEngine, shortcut::check_shortcut, to_url, Bookmark, Bookmarks, ImageInfo,
+ Shortcuts,
+};
+
+/// Allows users to implement their own custom view view with custom widgets and configurations
+pub trait CustomWidget {
+ fn update(&mut self, message: Message);
+ fn view(
+ &self,
+ nav_bar_state: NavBarState,
+ command_window_state: CommandWindowState,
+ bookmarks: Option>,
+ shortcuts: Shortcuts,
+ );
+ fn subscription(&self) -> Subscription;
+}
// Options exist only to have defaults for EnumIter
#[derive(Debug, Clone, PartialEq, Display, EnumIter)]
@@ -51,14 +71,16 @@ pub enum Message {
HideOverlay,
// Internal only - for widgets
+ Update,
UrlChanged(String),
UpdateUrl,
QueryChanged(String),
CommandSelectionChanged(usize, String),
+ CommandSelectionSelected,
SendKeyboardEvent(Option),
SendMouseEvent(Point, Option),
UpdateViewSize(Size),
- Event(Option),
+ IcedEvent(Option),
}
/// Allows different widgets to interact in their native way
@@ -73,31 +95,30 @@ impl Default for TabSelectionType {
}
}
-pub struct BrowserWidget {
- engine: Option,
+pub struct IcyBrowser {
+ engine: Engine,
home: Url,
- nav_bar_state: NavBarState,
+ nav_bar_state: Option,
command_window_state: CommandWindowState,
with_tab_bar: bool,
with_nav_bar: bool,
+ bookmarks: Option,
show_overlay: bool,
shortcuts: Shortcuts,
view_size: Size,
}
-impl Default for BrowserWidget
-where
- Engine: BrowserEngine,
-{
+impl Default for IcyBrowser {
fn default() -> Self {
let home = Url::parse(Self::HOME).unwrap();
Self {
- engine: None,
+ engine: Engine::new(),
home,
- nav_bar_state: NavBarState::new(),
+ nav_bar_state: None,
command_window_state: CommandWindowState::new(),
with_tab_bar: false,
with_nav_bar: false,
+ bookmarks: None,
show_overlay: false,
shortcuts: Shortcuts::default(),
view_size: Size::new(800, 800),
@@ -105,30 +126,11 @@ where
}
}
-#[cfg(feature = "ultralight")]
-use crate::engines::ultralight::Ultralight;
-
-#[cfg(feature = "ultralight")]
-impl BrowserWidget {
- pub fn new_with_ultralight() -> BrowserWidget {
- BrowserWidget {
- engine: Some(Ultralight::new()),
- ..BrowserWidget::default()
- }
- }
-}
-
-impl BrowserWidget
-where
- Engine: BrowserEngine,
-{
+impl IcyBrowser {
const HOME: &'static str = "https://google.com";
pub fn new() -> Self {
- Self {
- engine: Some(Engine::new()),
- ..Default::default()
- }
+ Self::default()
}
pub fn with_homepage(mut self, homepage: &str) -> Self {
@@ -143,6 +145,12 @@ where
pub fn with_nav_bar(mut self) -> Self {
self.with_nav_bar = true;
+ self.nav_bar_state = Some(NavBarState::new());
+ self
+ }
+
+ pub fn with_bookmark_bar(mut self, bookmarks: &[Bookmark]) -> Self {
+ self.bookmarks = Some(bookmarks.to_vec());
self
}
@@ -152,40 +160,33 @@ where
}
pub fn build(self) -> Self {
- assert!(self.engine.is_some());
-
let mut build = Self { ..self };
let _ = build.update(Message::CreateTab); // disregaurd task::none() for update
build
}
- fn engine(&self) -> &Engine {
- self.engine
- .as_ref()
- .expect("Browser was created without a backend engine!")
+ /// Allows creation of custom widgets that need interal info
+ pub fn engine(&self) -> &Engine {
+ &self.engine
}
- fn engine_mut(&mut self) -> &mut Engine {
- self.engine
- .as_mut()
- .expect("Browser was created without a backend engine!")
+ /// Allows creation of custom widgets that need interal info
+ pub fn mut_engine(&mut self) -> &mut Engine {
+ &mut self.engine
}
fn update_engine(&mut self) {
- self.engine().do_work();
- if self.engine().has_loaded() {
- if self.engine().need_render() {
- let (format, image_data) = self.engine_mut().pixel_buffer();
+ self.engine.do_work();
+ if self.engine.has_loaded() {
+ if self.engine.need_render() {
+ let (format, image_data) = self.engine.pixel_buffer();
let view = ImageInfo::new(
image_data,
format,
self.view_size.width,
self.view_size.height,
);
- self.engine_mut()
- .get_tabs_mut()
- .get_current_mut()
- .set_view(view)
+ self.engine.get_tabs_mut().get_current_mut().set_view(view)
}
} else {
let view = ImageInfo {
@@ -193,121 +194,126 @@ where
height: self.view_size.height,
..Default::default()
};
- self.engine_mut()
- .get_tabs_mut()
- .get_current_mut()
- .set_view(view)
+ self.engine.get_tabs_mut().get_current_mut().set_view(view)
}
}
/// This is used to periodically update browserview
pub fn force_update(&mut self) -> Task {
- self.engine().do_work();
- let (format, image_data) = self.engine_mut().pixel_buffer();
+ self.engine.do_work();
+ let (format, image_data) = self.engine.pixel_buffer();
let view = ImageInfo::new(
image_data,
format,
self.view_size.width,
self.view_size.height,
);
- self.engine_mut()
- .get_tabs_mut()
- .get_current_mut()
- .set_view(view);
+ self.engine.get_tabs_mut().get_current_mut().set_view(view);
Task::none()
}
- pub fn update(&mut self, message: Message) -> Task {
- let task = match message {
+ pub fn update(&mut self, event: Message) -> Task {
+ let task = match event {
+ Message::Update => self.force_update(),
Message::UpdateViewSize(size) => {
self.view_size = size;
- self.engine_mut().resize(size);
+ self.engine.resize(size);
Task::none()
}
Message::SendKeyboardEvent(event) => {
- self.engine()
+ self.engine
.handle_keyboard_event(event.expect("Value cannot be none"));
Task::none()
}
Message::SendMouseEvent(point, event) => {
- self.engine_mut()
+ self.engine
.handle_mouse_event(point, event.expect("Value cannot be none"));
Task::none()
}
Message::ChangeTab(index_type) => {
let id = match index_type {
TabSelectionType::Id(id) => id,
- TabSelectionType::Index(index) => {
- self.engine_mut().get_tabs().index_to_id(index)
- }
+ TabSelectionType::Index(index) => self.engine.get_tabs().index_to_id(index),
};
- self.engine_mut().get_tabs_mut().set_current_id(id);
- self.nav_bar_state.0 = self.engine().get_tabs().get_current().url();
+ self.engine.get_tabs_mut().set_current_id(id);
+ if let Some(state) = self.nav_bar_state.as_mut() {
+ state.0 = self.engine.get_tabs().get_current().url();
+ }
Task::none()
}
Message::CloseCurrentTab => Task::done(Message::CloseTab(TabSelectionType::Id(
- self.engine().get_tabs().get_current_id(),
+ self.engine.get_tabs().get_current_id(),
))),
Message::CloseTab(index_type) => {
// ensure there is always at least one tab
- if self.engine().get_tabs().tabs().len() == 1 {
+ if self.engine.get_tabs().tabs().len() == 1 {
let _ = self.update(Message::CreateTab); // ignore task
}
let id = match index_type {
TabSelectionType::Id(id) => id,
- TabSelectionType::Index(index) => {
- self.engine_mut().get_tabs().index_to_id(index)
- }
+ TabSelectionType::Index(index) => self.engine.get_tabs().index_to_id(index),
};
- self.engine_mut().get_tabs_mut().remove(id);
- self.nav_bar_state.0 = self.engine().get_tabs().get_current().url();
+ self.engine.get_tabs_mut().remove(id);
+ if let Some(state) = self.nav_bar_state.as_mut() {
+ state.0 = self.engine.get_tabs().get_current().url();
+ }
Task::none()
}
Message::CreateTab => {
- self.nav_bar_state.0 = self.home.to_string();
+ if let Some(state) = self.nav_bar_state.as_mut() {
+ state.0 = self.home.to_string();
+ }
let home = self.home.clone();
let bounds = self.view_size;
- let tab = self.engine_mut().new_tab(
+ let tab = self.engine.new_tab(
home.clone(),
Size::new(bounds.width + 10, bounds.height - 10),
);
- let id = self.engine_mut().get_tabs_mut().insert(tab);
- self.engine_mut().get_tabs_mut().set_current_id(id);
- self.engine_mut().force_need_render();
- self.engine_mut().resize(bounds);
- self.engine().goto_url(&home);
+ let id = self.engine.get_tabs_mut().insert(tab);
+ self.engine.get_tabs_mut().set_current_id(id);
+ self.engine.force_need_render();
+ self.engine.resize(bounds);
+ self.engine.goto_url(&home);
Task::none()
}
Message::GoBackward => {
- self.engine().go_back();
- self.nav_bar_state.0 = self.engine().get_tabs().get_current().url();
+ self.engine.go_back();
+ if let Some(state) = self.nav_bar_state.as_mut() {
+ state.0 = self.engine.get_tabs().get_current().url();
+ }
Task::none()
}
Message::GoForward => {
- self.engine().go_forward();
- self.nav_bar_state.0 = self.engine().get_tabs().get_current().url();
+ self.engine.go_forward();
+ if let Some(state) = self.nav_bar_state.as_mut() {
+ state.0 = self.engine.get_tabs().get_current().url();
+ }
Task::none()
}
Message::Refresh => {
- self.engine().refresh();
+ self.engine.refresh();
Task::none()
}
Message::GoHome => {
- self.engine().goto_url(&self.home);
+ self.engine.goto_url(&self.home);
Task::none()
}
Message::GoToUrl(url) => {
- self.engine().goto_url(&to_url(&url).unwrap());
+ self.engine.goto_url(&to_url(&url).unwrap());
Task::none()
}
Message::UpdateUrl => {
- self.nav_bar_state.0 = self.engine().get_tabs().get_current().url();
+ if let Some(state) = self.nav_bar_state.as_mut() {
+ state.0 = self.engine.get_tabs().get_current().url();
+ }
Task::none()
}
Message::UrlChanged(url) => {
- self.nav_bar_state.0 = url;
+ if let Some(state) = self.nav_bar_state.as_mut() {
+ state.0 = url;
+ }
Task::none()
}
Message::QueryChanged(query) => {
@@ -319,6 +325,9 @@ where
self.command_window_state.selected_action = name;
Task::none()
}
+ Message::CommandSelectionSelected => {
+ unimplemented!()
+ }
Message::ToggleOverlay => {
if self.show_overlay {
Task::done(Message::HideOverlay)
@@ -334,7 +343,7 @@ where
self.show_overlay = false;
widget::focus_next()
}
- Message::Event(event) => {
+ Message::IcedEvent(event) => {
match event {
Some(Event::Keyboard(key)) => {
if let iced::keyboard::Event::KeyPressed {
@@ -347,10 +356,16 @@ where
} = key
{
// Default behaviors
- if key == keyboard::Key::Named(key::Named::Escape) && self.show_overlay
+ // escape to exit command palatte
+ if self.show_overlay && key == keyboard::Key::Named(key::Named::Escape)
{
return Task::done(Message::HideOverlay);
}
+ // ctrl + R = refresh
+ else if modifiers.control() && key == key::Key::Character("r".into())
+ {
+ return Task::done(Message::Refresh);
+ }
// Shortcut (Customizable) behaviors
for shortcut in self.shortcuts.iter() {
@@ -376,16 +391,21 @@ where
let mut column = column![];
if self.with_tab_bar {
- column = column.push(tab_bar(self.engine().get_tabs()))
+ column = column.push(tab_bar(self.engine.get_tabs()))
}
if self.with_nav_bar {
- column = column
- .push(hoverable(nav_bar(&self.nav_bar_state)).on_focus_change(Message::UpdateUrl))
+ column = column.push(
+ hoverable(nav_bar(self.nav_bar_state.as_ref().unwrap()))
+ .on_focus_change(Message::UpdateUrl),
+ )
+ }
+ if let Some(bookmarks) = self.bookmarks.as_ref() {
+ column = column.push(bookmark_bar(bookmarks))
}
let browser_view = browser_view(
self.view_size,
- self.engine().get_tabs().get_current().get_view(),
+ self.engine.get_tabs().get_current().get_view(),
!self.show_overlay,
);
if self.show_overlay {
@@ -396,4 +416,11 @@ where
column.into()
}
+
+ pub fn subscription(&self) -> Subscription {
+ Subscription::batch([
+ iced::time::every(Duration::from_millis(10)).map(move |_| Message::Update),
+ iced::event::listen().map(|e: iced::Event| Message::IcedEvent(Some(e))),
+ ])
+ }
}
diff --git a/src/widgets/nav_bar.rs b/src/widgets/nav_bar.rs
index e082e0d..779afa3 100644
--- a/src/widgets/nav_bar.rs
+++ b/src/widgets/nav_bar.rs
@@ -19,6 +19,7 @@ impl Default for NavBarState {
}
}
+/// Creates Navigation bar widget
pub fn nav_bar(state: &NavBarState) -> Element {
let back = tooltip_helper(
Button::new(icon_to_text(Bootstrap::ChevronBarLeft))
diff --git a/src/widgets/tab_bar.rs b/src/widgets/tab_bar.rs
index 945c7d9..dac86b7 100644
--- a/src/widgets/tab_bar.rs
+++ b/src/widgets/tab_bar.rs
@@ -6,8 +6,8 @@ use iced_aw::{TabBar as TB, TabLabel};
use super::{Message, TabSelectionType};
use crate::engines::{TabInfo, Tabs};
-// helper function to create navigation bar
-pub fn tab_bar(tabs: &Tabs) -> Element<'static, Message> {
+/// Creates Tab bar widget
+pub fn tab_bar(tabs: &Tabs) -> Element {
let current_id = tabs.get_current_id();
let active_tab = tabs
.tabs()