diff --git a/Cargo.lock b/Cargo.lock index 7f81531c7..366d5994f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "Inflector" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" - [[package]] name = "adler" version = "1.0.2" @@ -20,6 +14,17 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + [[package]] name = "aho-corasick" version = "0.7.18" @@ -29,12 +34,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "aliasable" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" - [[package]] name = "android_glue" version = "0.2.3" @@ -53,12 +52,53 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5d78ce20460b82d3fa150275ed9d55e21064fc7951177baacf86a145c4a4b1f" +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + +[[package]] +name = "ash" +version = "0.34.0+1.2.203" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0f780da53d0063880d45554306489f09dd8d1bda47688b4a57bc579119356df" +dependencies = [ + "libloading", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "bit-set" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" +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 = "bitflags" version = "1.3.2" @@ -146,6 +186,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + [[package]] name = "cgl" version = "0.3.2" @@ -220,6 +266,16 @@ dependencies = [ "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" @@ -245,6 +301,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "copyless" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2df960f5d869b2dd8532793fde43eb5427cceb126c929747a26823ab0eeb536" + [[package]] name = "core-foundation" version = "0.7.0" @@ -378,6 +440,17 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" +[[package]] +name = "d3d12" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2daefd788d1e96e0a9d66dee4b828b883509bc3ea9ce30665f04c3246372690c" +dependencies = [ + "bitflags", + "libloading", + "winapi", +] + [[package]] name = "darling" version = "0.13.1" @@ -488,6 +561,19 @@ dependencies = [ "encoding_rs", ] +[[package]] +name = "env_logger" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aafcde04e90a5226a6443b7aabdb016ba2f8307c847d524724bd9b346dd1a2d3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + [[package]] name = "error-code" version = "2.3.1" @@ -509,28 +595,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "femtovg" -version = "0.3.3" -dependencies = [ - "bitflags", - "fnv", - "generational-arena", - "glow", - "glutin", - "image", - "imgref", - "lru", - "ouroboros", - "rgb", - "rustybuzz", - "ttf-parser", - "unicode-bidi", - "unicode-segmentation", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "fern" version = "0.6.1" @@ -617,12 +681,12 @@ dependencies = [ ] [[package]] -name = "generational-arena" -version = "0.2.8" +name = "fxhash" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d3b771574f62d0548cee0ad9057857e9fc25d7a3335f140c84f6acd0bf601" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" dependencies = [ - "cfg-if 0.1.10", + "byteorder", ] [[package]] @@ -647,6 +711,12 @@ dependencies = [ "xml-rs", ] +[[package]] +name = "glam" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3412e74893a912839a67e975aca0c88561e20e5461d2d358a5fa6d3b229fae59" + [[package]] name = "globset" version = "0.4.8" @@ -744,6 +814,45 @@ dependencies = [ "gl_generator", ] +[[package]] +name = "gpu-alloc" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fc59e5f710e310e76e6707f86c561dd646f69a8876da9131703b2f717de818d" +dependencies = [ + "bitflags", + "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", +] + +[[package]] +name = "gpu-descriptor" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a538f217be4d405ff4719a283ca68323cc2384003eca5baaa87501e821c81dda" +dependencies = [ + "bitflags", + "gpu-descriptor-types", + "hashbrown", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "363e3677e55ad168fef68cf9de3a4a310b53124c5e784c53a1d70e92d23f2126" +dependencies = [ + "bitflags", +] + [[package]] name = "grep-matcher" version = "0.1.5" @@ -783,6 +892,15 @@ dependencies = [ "memmap2", ] +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash", +] + [[package]] name = "helix-core" version = "0.6.0" @@ -805,7 +923,7 @@ dependencies = [ "smartstring", "toml", "tree-sitter", - "unicode-general-category 0.5.1", + "unicode-general-category", "unicode-segmentation", "unicode-width", ] @@ -916,12 +1034,16 @@ name = "helix-ui" version = "0.1.0" dependencies = [ "console_error_panic_hook", - "femtovg", + "env_logger", + "glam", "glutin", "helix-view", "image", "instant", + "pollster", "resource", + "swash", + "wgpu", "winit", ] @@ -970,6 +1092,21 @@ dependencies = [ "libc", ] +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +dependencies = [ + "quick-error", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -1022,10 +1159,20 @@ dependencies = [ ] [[package]] -name = "imgref" -version = "1.9.1" +name = "indexmap" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "inplace_it" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d0c0db6c932f8262e0ed8909f2e7f8c0e9b1cfb4da884267ce09a10be54365" +checksum = "90953f308a79fe6d62a4643e51f848fbfddcd05975a38e69fdf4ab86a7baf7ca" [[package]] name = "instant" @@ -1079,6 +1226,16 @@ dependencies = [ "serde_json", ] +[[package]] +name = "khronos-egl" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c2352bd1d0bceb871cb9d40f24360c8133c11d7486b68b5381c1dd1a32015e3" +dependencies = [ + "libc", + "libloading", +] + [[package]] name = "khronos_api" version = "3.1.0" @@ -1126,12 +1283,6 @@ dependencies = [ "cfg-if 1.0.0", ] -[[package]] -name = "lru" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb87f3080f6d1d69e8c564c0fcfde1d7aa8cc451ce40cae89479111f03bc0eb" - [[package]] name = "lsp-types" version = "0.93.0" @@ -1184,6 +1335,20 @@ dependencies = [ "autocfg", ] +[[package]] +name = "metal" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0514f491f4cc03632ab399ee01e2c1c1b12d3e1cf2d667c1ff5f87d6dcd2084" +dependencies = [ + "bitflags", + "block", + "core-graphics-types", + "foreign-types", + "log", + "objc", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1222,6 +1387,24 @@ dependencies = [ "winapi", ] +[[package]] +name = "naga" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3012f2dbcc79e8e0b5825a4836a7106a75dd9b2fe42c528163be0f572538c705" +dependencies = [ + "bit-set", + "bitflags", + "codespan-reporting", + "hexf-parse", + "indexmap", + "log", + "num-traits", + "rustc-hash", + "spirv", + "thiserror", +] + [[package]] name = "ndk" version = "0.5.0" @@ -1386,6 +1569,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" dependencies = [ "malloc_buf", + "objc_exception", +] + +[[package]] +name = "objc_exception" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +dependencies = [ + "cc", ] [[package]] @@ -1403,30 +1596,6 @@ dependencies = [ "shared_library", ] -[[package]] -name = "ouroboros" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f31a3b678685b150cba82b702dcdc5e155893f63610cf388d30cd988d4ca2bf" -dependencies = [ - "aliasable", - "ouroboros_macro", - "stable_deref_trait", -] - -[[package]] -name = "ouroboros_macro" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "084fd65d5dd8b3772edccb5ffd1e4b7eba43897ecd0f9401e330e8c542959408" -dependencies = [ - "Inflector", - "proc-macro-error", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "parking_lot" version = "0.11.2" @@ -1511,6 +1680,12 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "pollster" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5da3b0203fd7ee5720aa0b5e790b591aa5d3f41c3ed2c34a3a393382198af2f7" + [[package]] name = "proc-macro-crate" version = "1.1.3" @@ -1521,30 +1696,6 @@ dependencies = [ "toml", ] -[[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", - "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.36" @@ -1554,6 +1705,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "profiling" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9145ac0af1d93c638c98c40cf7d25665f427b2a44ad0a99b1dccf3e2f25bb987" + [[package]] name = "pulldown-cmark" version = "0.9.1" @@ -1565,6 +1722,12 @@ dependencies = [ "unicase", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quickcheck" version = "1.0.3" @@ -1601,6 +1764,12 @@ dependencies = [ "getrandom", ] +[[package]] +name = "range-alloc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e935c45e09cc6dcf00d2f0b2d630a58f4095320223d47fc68918722f0538b6" + [[package]] name = "raw-window-handle" version = "0.4.2" @@ -1653,6 +1822,12 @@ version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" +[[package]] +name = "renderdoc-sys" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1382d1f0a252c4bf97dc20d979a2fdd05b024acd7c2ed0f7595d7817666a157" + [[package]] name = "resource" version = "0.5.0" @@ -1665,15 +1840,6 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c31b5c4033f8fdde8700e4657be2c497e7288f01515be52168c631e2e4d4086" -[[package]] -name = "rgb" -version = "0.8.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e74fdc210d8f24a7dbfedc13b04ba5764f5232754ccebfdf5fff1bad791ccbc6" -dependencies = [ - "bytemuck", -] - [[package]] name = "ropey" version = "1.4.1" @@ -1685,20 +1851,10 @@ dependencies = [ ] [[package]] -name = "rustybuzz" -version = "0.5.0" +name = "rustc-hash" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ff94f20221325d000e552781713e53b0d85c1d9551b6f420d12daf5a08eace" -dependencies = [ - "bitflags", - "bytemuck", - "smallvec", - "ttf-parser", - "unicode-bidi-mirroring", - "unicode-ccc", - "unicode-general-category 0.4.0", - "unicode-script", -] +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "ryu" @@ -1889,10 +2045,14 @@ dependencies = [ ] [[package]] -name = "stable_deref_trait" -version = "1.2.0" +name = "spirv" +version = "0.2.0+1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "246bfa38fe3db3f1dfc8ca5a2cdeb7348c78be2112740cc0ec8ef18b6d94f830" +dependencies = [ + "bitflags", + "num-traits", +] [[package]] name = "static_assertions" @@ -1918,6 +2078,15 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "swash" +version = "0.1.4" +source = "git+https://github.com/dfrg/swash#d3f8bbfa5f96688ab94278db6f85e00ba383e3f8" +dependencies = [ + "yazi", + "zeno", +] + [[package]] name = "syn" version = "1.0.90" @@ -1929,6 +2098,15 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + [[package]] name = "thiserror" version = "1.0.30" @@ -2043,12 +2221,6 @@ dependencies = [ "regex", ] -[[package]] -name = "ttf-parser" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c74c96594835e10fa545e2a51e8709f30b173a092bfd6036ef2cec53376244f3" - [[package]] name = "unicase" version = "2.6.0" @@ -2064,24 +2236,6 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" -[[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.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07547e3ee45e28326cc23faac56d44f58f16ab23e413db526debce3b0bfd2742" - [[package]] name = "unicode-general-category" version = "0.5.1" @@ -2097,12 +2251,6 @@ dependencies = [ "tinyvec", ] -[[package]] -name = "unicode-script" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58dd944fd05f2f0b5c674917aea8a4df6af84f2d8de3fe8d988b95d28fb8fb09" - [[package]] name = "unicode-segmentation" version = "1.9.0" @@ -2188,6 +2336,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb6ec270a31b1d3c7e266b999739109abce8b6c87e4b31fcfcd788b65267395" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.79" @@ -2310,6 +2470,97 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "wgpu" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97cd781ff044d6d697b632a2e212032c2e957d1afaa21dbf58069cbb8f78567" +dependencies = [ + "arrayvec", + "js-sys", + "log", + "naga", + "parking_lot 0.11.2", + "raw-window-handle", + "smallvec", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4688c000eb841ca55f7b35db659b78d6e1cd77d7caf8fb929f4e181f754047d" +dependencies = [ + "arrayvec", + "bitflags", + "cfg_aliases", + "codespan-reporting", + "copyless", + "fxhash", + "log", + "naga", + "parking_lot 0.11.2", + "profiling", + "raw-window-handle", + "smallvec", + "thiserror", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-hal" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93b1a9400e8d7f32dd4dd909bb9a391015d70633d639775ddd3f14d1104bc970" +dependencies = [ + "arrayvec", + "ash", + "bit-set", + "bitflags", + "block", + "core-graphics-types", + "d3d12", + "foreign-types", + "fxhash", + "glow", + "gpu-alloc", + "gpu-descriptor", + "inplace_it", + "js-sys", + "khronos-egl", + "libloading", + "log", + "metal", + "naga", + "objc", + "parking_lot 0.11.2", + "profiling", + "range-alloc", + "raw-window-handle", + "renderdoc-sys", + "thiserror", + "wasm-bindgen", + "web-sys", + "wgpu-types", + "winapi", +] + +[[package]] +name = "wgpu-types" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "549533d9e1cdd4b4cda7718d33ff500fc4c34b5467b71d76b547ae0324f3b2a2" +dependencies = [ + "bitflags", +] + [[package]] name = "which" version = "4.2.5" @@ -2462,3 +2713,15 @@ dependencies = [ "helix-term", "toml", ] + +[[package]] +name = "yazi" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c03b3e19c937b5b9bd8e52b1c88f30cce5c0d33d676cf174866175bb794ff658" + +[[package]] +name = "zeno" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c110ba09c9b3a43edd4803d570df0da2414fed6e822e22b976a4e3ef50860701" diff --git a/Cargo.toml b/Cargo.toml index 6c47188a6..14ef30723 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,8 @@ default-members = [ "helix-term" ] +resolver = "2" + [profile.dev] split-debuginfo = "unpacked" diff --git a/helix-ui/Cargo.toml b/helix-ui/Cargo.toml index 04c092e5e..f101c87ce 100644 --- a/helix-ui/Cargo.toml +++ b/helix-ui/Cargo.toml @@ -7,10 +7,22 @@ edition = "2021" [dependencies] helix-view = { version = "0.6", path = "../helix-view", features = ["ui"] } -femtovg = { path = "../../femtovg" } +# femtovg = { path = "../../femtovg" } +# femtovg = "0.3.3" winit = { version = "0.26.1", default-features = false, features = ["wayland"] } # TODO: figure out wayland-dlopen resource = "0.5.0" image = { version = "0.24.0", default-features = false, features = ["jpeg", "png"] } console_error_panic_hook = "0.1.7" glutin = "0.28.0" instant = "0.1.12" + +# swash = "0.1.4" +swash = { git = "https://github.com/dfrg/swash" } +# parley = { git = "https://github.com/dfrg/parley" } + +# lyon = "0.17.10" + +wgpu = "0.12" +pollster = "0.2" +glam = "0.20" +env_logger = "0.6" diff --git a/helix-ui/assets/fonts/Fira Code/FiraCode-VF.ttf b/helix-ui/assets/fonts/Fira Code/FiraCode-VF.ttf new file mode 100644 index 000000000..c05ac827c Binary files /dev/null and b/helix-ui/assets/fonts/Fira Code/FiraCode-VF.ttf differ diff --git a/helix-ui/assets/fonts/ttf/FiraCode-Bold.ttf b/helix-ui/assets/fonts/ttf/FiraCode-Bold.ttf new file mode 100644 index 000000000..f891bde43 Binary files /dev/null and b/helix-ui/assets/fonts/ttf/FiraCode-Bold.ttf differ diff --git a/helix-ui/assets/fonts/ttf/FiraCode-Light.ttf b/helix-ui/assets/fonts/ttf/FiraCode-Light.ttf new file mode 100644 index 000000000..5664ec3d3 Binary files /dev/null and b/helix-ui/assets/fonts/ttf/FiraCode-Light.ttf differ diff --git a/helix-ui/assets/fonts/ttf/FiraCode-Medium.ttf b/helix-ui/assets/fonts/ttf/FiraCode-Medium.ttf new file mode 100644 index 000000000..2c0ecdf12 Binary files /dev/null and b/helix-ui/assets/fonts/ttf/FiraCode-Medium.ttf differ diff --git a/helix-ui/assets/fonts/ttf/FiraCode-Regular.ttf b/helix-ui/assets/fonts/ttf/FiraCode-Regular.ttf new file mode 100644 index 000000000..bd7368519 Binary files /dev/null and b/helix-ui/assets/fonts/ttf/FiraCode-Regular.ttf differ diff --git a/helix-ui/assets/fonts/ttf/FiraCode-Retina.ttf b/helix-ui/assets/fonts/ttf/FiraCode-Retina.ttf new file mode 100644 index 000000000..660742d61 Binary files /dev/null and b/helix-ui/assets/fonts/ttf/FiraCode-Retina.ttf differ diff --git a/helix-ui/assets/fonts/ttf/FiraCode-SemiBold.ttf b/helix-ui/assets/fonts/ttf/FiraCode-SemiBold.ttf new file mode 100644 index 000000000..d8dcef664 Binary files /dev/null and b/helix-ui/assets/fonts/ttf/FiraCode-SemiBold.ttf differ diff --git a/helix-ui/src/femto.rs b/helix-ui/src/femto.rs new file mode 100644 index 000000000..e5945dca3 --- /dev/null +++ b/helix-ui/src/femto.rs @@ -0,0 +1,965 @@ +use resource::resource; + +use instant::Instant; +use winit::event::{ElementState, Event, KeyboardInput, MouseButton, VirtualKeyCode, WindowEvent}; +use winit::event_loop::{ControlFlow, EventLoop}; +use winit::window::WindowBuilder; +//use glutin::{GlRequest, Api}; + +use femtovg::{ + //CompositeOperation, + renderer::OpenGl, + Align, + Baseline, + Canvas, + Color, + FontId, + ImageFlags, + Paint, + Path, + Renderer, + Solidity, +}; + +// mezzopiano +// — +// 03/13/2022 +// I'm also assuming that there's some logic bugs in the demo application, which wasn't built with this in mind; I have a much simpler application that I'm happy to show if that would be helpful (would need to extract an example). As a rough solution, I apply the following transformation on Winit's WindowEvent::ScaleFactorChanged: + +// /* ... in an application struct/impl ... */ +// pub fn rescale( +// &mut self, +// new_size: winit::dpi::PhysicalSize, +// new_scale_factor: f64, +// ) { +// // Update translation +// // (TODO: This is a guestimate and not well-tested; +// // the size updates might turn out ot be completely unnecessary) +// let shift = self.size.height as f32 - new_size.height as f32; +// self.canvas.translate(0.0, -shift); + +// // Update properties +// self.size = new_size; +// self.scale_factor = new_scale_factor; +// self.canvas.set_size( +// self.size.width, +// self.size.height, +// self.scale_factor as f32, +// ) +// } + +// With this, the canvas position and scale is preserved while the window is moved across screens, but as I'd like to apply further translations and keeping track of everything is getting very hard 😅 . I'm wondering if I'm making a mistake somewhere, or if there might be some way to do this in femto. + +pub fn quantize(a: f32, d: f32) -> f32 { + (a / d + 0.5).trunc() * d +} + +struct Fonts { + regular: FontId, + bold: FontId, + icons: FontId, +} + +fn main() { + // This provides better error messages in debug mode. + // It's disabled in release mode so it doesn't bloat up the file size. + #[cfg(all(debug_assertions, target_arch = "wasm32"))] + console_error_panic_hook::set_once(); + + let el = EventLoop::new(); + + #[cfg(not(target_arch = "wasm32"))] + let (renderer, windowed_context) = { + use glutin::ContextBuilder; + + let wb = WindowBuilder::new() + .with_inner_size(winit::dpi::LogicalSize::::new(1000., 600.)) + .with_title("femtovg demo"); + + //let windowed_context = ContextBuilder::new().with_gl(GlRequest::Specific(Api::OpenGlEs, (2, 0))).with_vsync(false).build_windowed(wb, &el).unwrap(); + //let windowed_context = ContextBuilder::new().with_vsync(false).with_multisampling(8).build_windowed(wb, &el).unwrap(); + let windowed_context = ContextBuilder::new() + .with_vsync(true) // TODO: set to true? + .build_windowed(wb, &el) + .unwrap(); + let windowed_context = unsafe { windowed_context.make_current().unwrap() }; + + let renderer = + OpenGl::new_from_glutin_context(&windowed_context).expect("Cannot create renderer"); + + (renderer, windowed_context) + }; + + #[cfg(target_arch = "wasm32")] + let (renderer, window) = { + use wasm_bindgen::JsCast; + + let canvas = web_sys::window() + .unwrap() + .document() + .unwrap() + .get_element_by_id("canvas") + .unwrap() + .dyn_into::() + .unwrap(); + + use winit::platform::web::WindowBuilderExtWebSys; + + let renderer = OpenGl::new_from_html_canvas(&canvas).expect("Cannot create renderer"); + + let window = WindowBuilder::new() + .with_canvas(Some(canvas)) + .build(&el) + .unwrap(); + + (renderer, window) + }; + + let mut canvas = Canvas::new(renderer).expect("Cannot create canvas"); + + // TODO: better femtovg support for variable fonts + let fonts = Fonts { + regular: canvas + .add_font_mem(&resource!("assets/fonts/Inter\ Variable/Inter.ttf")) + .expect("Cannot add font"), + bold: canvas + .add_font_mem(&resource!("assets/fonts/Inter Variable/Inter.ttf")) + .expect("Cannot add font"), + icons: canvas + .add_font_mem(&resource!("assets/entypo.ttf")) + .expect("Cannot add font"), + }; + + //canvas.add_font("/usr/share/fonts/noto/NotoSansArabic-Regular.ttf").expect("Cannot add font"); + + //let image_id = canvas.create_image_file("assets/RoomRender.jpg", ImageFlags::FLIP_Y).expect("Cannot create image"); + //canvas.blur_image(image_id, 10, 1050, 710, 200, 200); + + //let image_id = canvas.load_image_file("assets/RoomRender.jpg", ImageFlags::FLIP_Y).expect("Cannot create image"); + + // let images = vec![ + // canvas + // .load_image_mem(&resource!("assets/images/image1.jpg"), ImageFlags::empty()) + // .unwrap(), + // canvas + // .load_image_mem(&resource!("assets/images/image2.jpg"), ImageFlags::empty()) + // .unwrap(), + // ]; + + let mut screenshot_image_id = None; + + let start = Instant::now(); + let mut prevt = start; + + let mut mousex = 0.0; + let mut mousey = 0.0; + let mut dragging = false; + + let mut perf = PerfGraph::new(); + + el.run(move |event, _, control_flow| { + #[cfg(not(target_arch = "wasm32"))] + let window = windowed_context.window(); + + *control_flow = ControlFlow::Poll; + + match event { + Event::LoopDestroyed => return, + Event::WindowEvent { ref event, .. } => match event { + #[cfg(not(target_arch = "wasm32"))] + WindowEvent::Resized(physical_size) => { + println!("resized!"); + // TODO: use DPI here? + windowed_context.resize(*physical_size); + } + WindowEvent::CursorMoved { + device_id: _, + position, + .. + } => { + if dragging { + let p0 = canvas + .transform() + .inversed() + .transform_point(mousex, mousey); + let p1 = canvas + .transform() + .inversed() + .transform_point(position.x as f32, position.y as f32); + + canvas.translate(p1.0 - p0.0, p1.1 - p0.1); + } + + mousex = position.x as f32; + mousey = position.y as f32; + } + WindowEvent::MouseWheel { + device_id: _, + delta, + .. + } => match delta { + winit::event::MouseScrollDelta::LineDelta(_, y) => { + let pt = canvas + .transform() + .inversed() + .transform_point(mousex, mousey); + canvas.translate(pt.0, pt.1); + canvas.scale(1.0 + (y / 10.0), 1.0 + (y / 10.0)); + canvas.translate(-pt.0, -pt.1); + } + + winit::event::MouseScrollDelta::PixelDelta(pos) => { + let y = pos.y as f32; + let pt = canvas + .transform() + .inversed() + .transform_point(mousex, mousey); + let rate = 2000.0; + canvas.translate(pt.0, pt.1); + canvas.scale(1.0 + (y / rate), 1.0 + (y / rate)); + canvas.translate(-pt.0, -pt.1); + } + }, + WindowEvent::MouseInput { + button: MouseButton::Left, + state, + .. + } => match state { + ElementState::Pressed => dragging = true, + ElementState::Released => dragging = false, + }, + WindowEvent::KeyboardInput { + input: + KeyboardInput { + virtual_keycode: Some(VirtualKeyCode::S), + state: ElementState::Pressed, + .. + }, + .. + } => { + if let Some(screenshot_image_id) = screenshot_image_id { + canvas.delete_image(screenshot_image_id); + } + + if let Ok(image) = canvas.screenshot() { + screenshot_image_id = Some( + canvas + .create_image(image.as_ref(), ImageFlags::empty()) + .unwrap(), + ); + } + } + WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, + _ => (), + }, + Event::RedrawRequested(_) => { + let now = Instant::now(); + let dt = (now - prevt).as_secs_f32(); + prevt = now; + + perf.update(dt); + + let dpi_factor = window.scale_factor(); + // println!("DPI {}", dpi_factor); + // let dpi_factor = 0.5f64; + let size = window.inner_size(); + // let size: winit::dpi::LogicalSize = window.inner_size().to_logical(dpi_factor); // TODO: adjust for dpi + // window.set_inner_size(size); + // let size = window.inner_size(); + + // let t = start.elapsed().as_secs_f32(); + + canvas.set_size(size.width as u32, size.height as u32, dpi_factor as f32); + canvas.clear_rect( + 0, + 0, + size.width as u32, + size.height as u32, + Color::rgbf(0.3, 0.3, 0.32), + ); + + // let height = size.height as f32; + // let width = size.width as f32; + + let winit::dpi::LogicalSize { width, height: _ } = + size.to_logical::(dpi_factor); + + let pt = canvas + .transform() + .inversed() + .transform_point(mousex, mousey); + let rel_mousex = pt.0; + let rel_mousey = pt.1; + + draw_paragraph( + &mut canvas, + fonts.regular, + width - 450.0, + 50.0, + 150.0, + 100.0, + rel_mousex, + rel_mousey, + ); + + draw_window( + &mut canvas, + &fonts, + "Widgets `n Stuff", + 50.0, + 50.0, + 300.0, + 400.0, + ); + + let x = 60.0; + let mut y = 95.0; + + draw_search_box(&mut canvas, &fonts, "Search", x, y, 280.0, 25.0); + y += 40.0; + draw_drop_down(&mut canvas, &fonts, "Effects", 60.0, 135.0, 280.0, 28.0); + y += 45.0; + + draw_label(&mut canvas, &fonts, "Login", x, y, 280.0, 20.0); + y += 25.0; + draw_edit_box(&mut canvas, &fonts, "Email", x, y, 280.0, 28.0); + y += 35.0; + draw_edit_box(&mut canvas, &fonts, "Password", x, y, 280.0, 28.0); + y += 38.0; + draw_check_box(&mut canvas, &fonts, "Remember me", x, y, 140.0, 28.0); + draw_button( + &mut canvas, + &fonts, + Some("\u{E740}"), + "Sign in", + x + 138.0, + y, + 140.0, + 28.0, + Color::rgba(0, 96, 128, 255), + ); + y += 45.0; + + // Slider + draw_label(&mut canvas, &fonts, "Diameter", x, y, 280.0, 20.0); + y += 25.0; + draw_edit_box_num( + &mut canvas, + &fonts, + "123.00", + "px", + x + 180.0, + y, + 100.0, + 28.0, + ); + y += 55.0; + + draw_button( + &mut canvas, + &fonts, + Some("\u{E729}"), + "Delete", + x, + y, + 160.0, + 28.0, + Color::rgba(128, 16, 8, 255), + ); + draw_button( + &mut canvas, + &fonts, + None, + "Cancel", + x + 170.0, + y, + 110.0, + 28.0, + Color::rgba(0, 0, 0, 0), + ); + + /* + draw_spinner(&mut canvas, 15.0, 285.0, 10.0, t); + */ + + if let Some(image_id) = screenshot_image_id { + let x = size.width as f32 - 512.0; + let y = size.height as f32 - 512.0; + + let paint = Paint::image(image_id, x, y, 512.0, 512.0, 0.0, 1.0); + + let mut path = Path::new(); + path.rect(x, y, 512.0, 512.0); + canvas.fill_path(&mut path, paint); + canvas.stroke_path(&mut path, Paint::color(Color::hex("454545"))); + } + + // if true { + // let paint = Paint::image(image_id, size.width as f32, 15.0, 1920.0, 1080.0, 0.0, 1.0); + // let mut path = Path::new(); + // path.rect(size.width as f32, 15.0, 1920.0, 1080.0); + // canvas.fill_path(&mut path, paint); + // } + + canvas.save_with(|canvas| { + canvas.reset(); + perf.render(canvas, 5.0, 5.0); + }); + + //canvas.restore(); + + canvas.flush(); + #[cfg(not(target_arch = "wasm32"))] + windowed_context.swap_buffers().unwrap(); + } + Event::MainEventsCleared => { + //scroll = 1.0; + window.request_redraw() + } + _ => (), + } + }); +} + +fn draw_paragraph( + canvas: &mut Canvas, + font: FontId, + x: f32, + y: f32, + width: f32, + _height: f32, + mx: f32, + my: f32, +) { + let text = "This is longer chunk of text.\n\nWould have used lorem ipsum but she was busy jumping over the lazy dog with the fox and all the men who came to the aid of the party.🎉"; + + canvas.save(); + + let mut paint = Paint::color(Color::rgba(255, 255, 255, 255)); + paint.set_font_size(14.0); + paint.set_font(&[font]); + paint.set_text_align(Align::Left); + paint.set_text_baseline(Baseline::Top); + + let mut gutter_y = 0.0; + let mut gutter = 0; + let mut y = y; + let mut px; + let mut caret_x; + + let lines = canvas + .break_text_vec(width, text, paint) + .expect("Cannot break text"); + + for (line_num, line_range) in lines.into_iter().enumerate() { + if let Ok(res) = canvas.fill_text(x, y, &text[line_range], paint) { + let hit = mx > x && mx < (x + width) && my >= y && my < (y + res.height()); + + if hit { + caret_x = if mx < x + res.width() / 2.0 { + x + } else { + x + res.width() + }; + px = x; + + for glyph in &res.glyphs { + let x0 = glyph.x; + let x1 = x0 + glyph.width; + let gx = x0 * 0.3 + x1 * 0.7; + + if mx >= px && mx < gx { + caret_x = glyph.x; + } + + px = gx; + } + + let mut path = Path::new(); + path.rect(caret_x, y, 1.0, res.height()); + canvas.fill_path(&mut path, Paint::color(Color::rgba(255, 192, 0, 255))); + + gutter = line_num + 1; + + gutter_y = y + 14.0 / 2.0; + } + + y += res.height(); + } + } + + if gutter > 0 { + let mut paint = Paint::color(Color::rgba(255, 192, 0, 255)); + paint.set_font_size(12.0); + paint.set_font(&[font]); + paint.set_text_align(Align::Right); + paint.set_text_baseline(Baseline::Middle); + + let text = format!("{}", gutter); + + if let Ok(res) = canvas.measure_text(x - 10.0, gutter_y, &text, paint) { + let mut path = Path::new(); + path.rounded_rect( + res.x - 4.0, + res.y - 2.0, + res.width() + 8.0, + res.height() + 4.0, + (res.height() + 4.0) / 2.0 - 1.0, + ); + canvas.fill_path(&mut path, paint); + + paint.set_color(Color::rgba(32, 32, 32, 255)); + let _ = canvas.fill_text(x - 10.0, gutter_y, &text, paint); + } + } + + // let mut start = 0; + + // while start < text.len() { + // let substr = &text[start..]; + + // if let Ok(index) = canvas.break_text(width, substr, paint) { + // if let Ok(res) = canvas.fill_text(x, y, &substr[0..index], paint) { + // y += res.height; + // } + + // start += &substr[0..index].len(); + // } else { + // break; + // } + // } + + canvas.restore(); +} + +fn draw_window( + canvas: &mut Canvas, + fonts: &Fonts, + title: &str, + x: f32, + y: f32, + w: f32, + h: f32, +) { + let corner_radius = 3.0; + + canvas.save(); + + //canvas.global_composite_operation(CompositeOperation::Lighter); + + // Window + let mut path = Path::new(); + path.rounded_rect(x, y, w, h, corner_radius); + canvas.fill_path(&mut path, Paint::color(Color::rgba(28, 30, 34, 192))); + + // Drop shadow + let shadow_paint = Paint::box_gradient( + x, + y + 2.0, + w, + h, + corner_radius * 2.0, + 10.0, + Color::rgba(0, 0, 0, 128), + Color::rgba(0, 0, 0, 0), + ); + let mut path = Path::new(); + path.rect(x - 10.0, y - 10.0, w + 20.0, h + 30.0); + path.rounded_rect(x, y, w, h, corner_radius); + path.solidity(Solidity::Hole); + canvas.fill_path(&mut path, shadow_paint); + + // Header + let header_paint = Paint::linear_gradient( + x, + y, + x, + y + 15.0, + Color::rgba(255, 255, 255, 8), + Color::rgba(0, 0, 0, 16), + ); + let mut path = Path::new(); + path.rounded_rect(x + 1.0, y + 1.0, w - 2.0, 30.0, corner_radius - 1.0); + canvas.fill_path(&mut path, header_paint); + + let mut path = Path::new(); + path.move_to(x + 0.5, y + 0.5 + 30.0); + path.line_to(x + 0.5 + w - 1.0, y + 0.5 + 30.0); + canvas.stroke_path(&mut path, Paint::color(Color::rgba(0, 0, 0, 32))); + + let mut text_paint = Paint::color(Color::rgba(0, 0, 0, 32)); + text_paint.set_font_size(16.0); + text_paint.set_font(&[fonts.bold]); + text_paint.set_text_align(Align::Center); + text_paint.set_color(Color::rgba(220, 220, 220, 160)); + + let _ = canvas.fill_text(x + (w / 2.0), y + 19.0, title, text_paint); + + // let bounds = canvas.text_bounds(x + (w / 2.0), y + 19.0, title, text_paint); + // + // let mut path = Path::new(); + // path.rect(bounds[0], bounds[1], bounds[2] - bounds[0], bounds[3] - bounds[1]); + // canvas.stroke_path(&mut path, Paint::color(Color::rgba(0, 0, 0, 255))); + + canvas.restore(); +} + +fn draw_search_box( + canvas: &mut Canvas, + fonts: &Fonts, + title: &str, + x: f32, + y: f32, + w: f32, + h: f32, +) { + let corner_radius = (h / 2.0) - 1.0; + + let bg = Paint::box_gradient( + x, + y + 1.5, + w, + h, + h / 2.0, + 5.0, + Color::rgba(0, 0, 0, 16), + Color::rgba(0, 0, 0, 92), + ); + let mut path = Path::new(); + path.rounded_rect(x, y, w, h, corner_radius); + canvas.fill_path(&mut path, bg); + + let mut text_paint = Paint::color(Color::rgba(255, 255, 255, 64)); + text_paint.set_font_size((h * 1.3).round()); + text_paint.set_font(&[fonts.icons]); + text_paint.set_text_align(Align::Center); + text_paint.set_text_baseline(Baseline::Middle); + let _ = canvas.fill_text(x + h * 0.55, y + h * 0.55, "\u{1F50D}", text_paint); + + let mut text_paint = Paint::color(Color::rgba(255, 255, 255, 32)); + text_paint.set_font_size(16.0); + text_paint.set_font(&[fonts.regular]); + text_paint.set_text_align(Align::Left); + text_paint.set_text_baseline(Baseline::Middle); + let _ = canvas.fill_text(x + h, y + h * 0.5, title, text_paint); + + let mut text_paint = Paint::color(Color::rgba(255, 255, 255, 32)); + text_paint.set_font_size((h * 1.3).round()); + text_paint.set_font(&[fonts.icons]); + text_paint.set_text_align(Align::Center); + text_paint.set_text_baseline(Baseline::Middle); + let _ = canvas.fill_text(x + w - h * 0.55, y + h * 0.45, "\u{2716}", text_paint); +} + +fn draw_drop_down( + canvas: &mut Canvas, + fonts: &Fonts, + title: &str, + x: f32, + y: f32, + w: f32, + h: f32, +) { + let corner_radius = 4.0; + + let bg = Paint::linear_gradient( + x, + y, + x, + y + h, + Color::rgba(255, 255, 255, 16), + Color::rgba(0, 0, 0, 16), + ); + let mut path = Path::new(); + path.rounded_rect(x + 1.0, y + 1.0, w - 2.0, h - 2.0, corner_radius); + canvas.fill_path(&mut path, bg); + + let mut path = Path::new(); + path.rounded_rect(x + 0.5, y + 0.5, w - 1.0, h - 1.0, corner_radius - 0.5); + canvas.stroke_path(&mut path, Paint::color(Color::rgba(0, 0, 0, 48))); + + let mut text_paint = Paint::color(Color::rgba(255, 255, 255, 160)); + text_paint.set_font_size(16.0); + text_paint.set_font(&[fonts.regular]); + text_paint.set_text_align(Align::Left); + text_paint.set_text_baseline(Baseline::Middle); + let _ = canvas.fill_text(x + h * 0.3, y + h * 0.5, title, text_paint); + + let mut text_paint = Paint::color(Color::rgba(255, 255, 255, 64)); + text_paint.set_font_size((h * 1.3).round()); + text_paint.set_font(&[fonts.icons]); + text_paint.set_text_align(Align::Center); + text_paint.set_text_baseline(Baseline::Middle); + let _ = canvas.fill_text(x + w - h * 0.5, y + h * 0.45, "\u{E75E}", text_paint); +} + +fn draw_label( + canvas: &mut Canvas, + fonts: &Fonts, + title: &str, + x: f32, + y: f32, + _w: f32, + h: f32, +) { + let mut text_paint = Paint::color(Color::rgba(255, 255, 255, 128)); + text_paint.set_font_size(14.0); + text_paint.set_font(&[fonts.regular]); + text_paint.set_text_align(Align::Left); + text_paint.set_text_baseline(Baseline::Middle); + let _ = canvas.fill_text(x, y + h * 0.5, title, text_paint); +} + +fn draw_edit_box_base(canvas: &mut Canvas, x: f32, y: f32, w: f32, h: f32) { + let paint = Paint::box_gradient( + x + 1.0, + y + 2.5, + w - 2.0, + h - 2.0, + 3.0, + 4.0, + Color::rgba(255, 255, 255, 32), + Color::rgba(32, 32, 32, 32), + ); + + let mut path = Path::new(); + path.rounded_rect(x + 1.0, y + 1.0, w - 2.0, h - 2.0, 3.0); + canvas.fill_path(&mut path, paint); + + let mut path = Path::new(); + path.rounded_rect(x + 0.5, y + 0.5, w - 1.0, h - 1.0, 3.5); + canvas.stroke_path(&mut path, Paint::color(Color::rgba(0, 0, 0, 48))); +} + +fn draw_edit_box( + canvas: &mut Canvas, + fonts: &Fonts, + title: &str, + x: f32, + y: f32, + w: f32, + h: f32, +) { + draw_edit_box_base(canvas, x, y, w, h); + + let mut text_paint = Paint::color(Color::rgba(255, 255, 255, 64)); + text_paint.set_font_size(16.0); + text_paint.set_font(&[fonts.regular]); + text_paint.set_text_align(Align::Left); + text_paint.set_text_baseline(Baseline::Middle); + let _ = canvas.fill_text(x + h * 0.5, y + h * 0.5, title, text_paint); +} + +fn draw_edit_box_num( + canvas: &mut Canvas, + fonts: &Fonts, + title: &str, + units: &str, + x: f32, + y: f32, + w: f32, + h: f32, +) { + draw_edit_box_base(canvas, x, y, w, h); + + let mut paint = Paint::color(Color::rgba(255, 255, 255, 64)); + paint.set_font_size(14.0); + paint.set_font(&[fonts.regular]); + paint.set_text_align(Align::Right); + paint.set_text_baseline(Baseline::Middle); + + if let Ok(layout) = canvas.measure_text(0.0, 0.0, units, paint) { + let _ = canvas.fill_text(x + w - h * 0.3, y + h * 0.5, units, paint); + + paint.set_font_size(16.0); + paint.set_color(Color::rgba(255, 255, 255, 128)); + + let _ = canvas.fill_text(x + w - layout.width() - h * 0.5, y + h * 0.5, title, paint); + } +} + +fn draw_check_box( + canvas: &mut Canvas, + fonts: &Fonts, + text: &str, + x: f32, + y: f32, + _w: f32, + h: f32, +) { + let mut paint = Paint::color(Color::rgba(255, 255, 255, 160)); + paint.set_font_size(14.0); + paint.set_font(&[fonts.regular]); + paint.set_text_baseline(Baseline::Middle); + + let _ = canvas.fill_text(x + 28.0, y + h * 0.5, text, paint); + + paint = Paint::box_gradient( + x + 1.0, + y + (h * 0.5).floor() - 9.0 + 1.0, + 18.0, + 18.0, + 3.0, + 3.0, + Color::rgba(0, 0, 0, 32), + Color::rgba(0, 0, 0, 92), + ); + let mut path = Path::new(); + path.rounded_rect(x + 1.0, y + (h * 0.5).floor() - 9.0, 18.0, 18.0, 3.0); + canvas.fill_path(&mut path, paint); + + paint = Paint::color(Color::rgba(255, 255, 255, 128)); + paint.set_font_size(36.0); + paint.set_font(&[fonts.icons]); + paint.set_text_align(Align::Center); + paint.set_text_baseline(Baseline::Middle); + let _ = canvas.fill_text(x + 9.0 + 2.0, y + h * 0.5, "\u{2713}", paint); +} + +fn draw_button( + canvas: &mut Canvas, + fonts: &Fonts, + preicon: Option<&str>, + text: &str, + x: f32, + y: f32, + w: f32, + h: f32, + color: Color, +) { + let corner_radius = 4.0; + + let a = if color.is_black() { 16 } else { 32 }; + + let bg = Paint::linear_gradient( + x, + y, + x, + y + h, + Color::rgba(255, 255, 255, a), + Color::rgba(0, 0, 0, a), + ); + + let mut path = Path::new(); + path.rounded_rect(x + 1.0, y + 1.0, w - 2.0, h - 2.0, corner_radius - 1.0); + + if !color.is_black() { + canvas.fill_path(&mut path, Paint::color(color)); + } + + canvas.fill_path(&mut path, bg); + + let mut path = Path::new(); + path.rounded_rect(x + 0.5, y + 0.5, w - 1.0, h - 1.0, corner_radius - 0.5); + canvas.stroke_path(&mut path, Paint::color(Color::rgba(0, 0, 0, 48))); + + let mut paint = Paint::color(Color::rgba(255, 255, 255, 96)); + paint.set_font_size(15.0); + paint.set_font(&[fonts.bold]); + paint.set_text_align(Align::Left); + paint.set_text_baseline(Baseline::Middle); + + let tw = if let Ok(layout) = canvas.measure_text(0.0, 0.0, text, paint) { + layout.width() + } else { + 0.0 + }; + + let mut iw = 0.0; + + if let Some(icon) = preicon { + paint.set_font(&[fonts.icons]); + paint.set_font_size(h * 1.3); + + if let Ok(layout) = canvas.measure_text(0.0, 0.0, icon, paint) { + iw = layout.width() + (h * 0.15); + } + + let _ = canvas.fill_text(x + w * 0.5 - tw * 0.5 - iw * 0.75, y + h * 0.5, icon, paint); + } + + paint.set_font_size(15.0); + paint.set_font(&[fonts.regular]); + paint.set_color(Color::rgba(0, 0, 0, 160)); + let _ = canvas.fill_text( + x + w * 0.5 - tw * 0.5 + iw * 0.25, + y + h * 0.5 - 1.0, + text, + paint, + ); + paint.set_color(Color::rgba(255, 255, 255, 160)); + let _ = canvas.fill_text(x + w * 0.5 - tw * 0.5 + iw * 0.25, y + h * 0.5, text, paint); +} + +struct PerfGraph { + history_count: usize, + values: Vec, + head: usize, +} + +impl PerfGraph { + fn new() -> Self { + Self { + history_count: 100, + values: vec![0.0; 100], + head: Default::default(), + } + } + + fn update(&mut self, frame_time: f32) { + self.head = (self.head + 1) % self.history_count; + self.values[self.head] = frame_time; + } + + fn get_average(&self) -> f32 { + self.values.iter().map(|v| *v).sum::() / self.history_count as f32 + } + + fn render(&self, canvas: &mut Canvas, x: f32, y: f32) { + let avg = self.get_average(); + + let w = 200.0; + let h = 35.0; + + let mut path = Path::new(); + path.rect(x, y, w, h); + canvas.fill_path(&mut path, Paint::color(Color::rgba(0, 0, 0, 128))); + + let mut path = Path::new(); + path.move_to(x, y + h); + + for i in 0..self.history_count { + let mut v = 1.0 / (0.00001 + self.values[(self.head + i) % self.history_count]); + if v > 80.0 { + v = 80.0; + } + let vx = x + (i as f32 / (self.history_count - 1) as f32) * w; + let vy = y + h - ((v / 80.0) * h); + path.line_to(vx, vy); + } + + path.line_to(x + w, y + h); + canvas.fill_path(&mut path, Paint::color(Color::rgba(255, 192, 0, 128))); + + let mut text_paint = Paint::color(Color::rgba(240, 240, 240, 255)); + text_paint.set_font_size(12.0); + let _ = canvas.fill_text(x + 5.0, y + 13.0, "Frame time", text_paint); + + let mut text_paint = Paint::color(Color::rgba(240, 240, 240, 255)); + text_paint.set_font_size(14.0); + text_paint.set_text_align(Align::Right); + text_paint.set_text_baseline(Baseline::Top); + let _ = canvas.fill_text(x + w - 5.0, y, &format!("{:.2} FPS", 1.0 / avg), text_paint); + + let mut text_paint = Paint::color(Color::rgba(240, 240, 240, 200)); + text_paint.set_font_size(12.0); + text_paint.set_text_align(Align::Right); + text_paint.set_text_baseline(Baseline::Alphabetic); + let _ = canvas.fill_text( + x + w - 5.0, + y + h - 5.0, + &format!("{:.2} ms", avg * 1000.0), + text_paint, + ); + } +} diff --git a/helix-ui/src/main.rs b/helix-ui/src/main.rs index 7945b6c23..d5037613d 100644 --- a/helix-ui/src/main.rs +++ b/helix-ui/src/main.rs @@ -1,24 +1,8 @@ -use resource::resource; - -use instant::Instant; -use winit::event::{ElementState, Event, KeyboardInput, MouseButton, VirtualKeyCode, WindowEvent}; -use winit::event_loop::{ControlFlow, EventLoop}; -use winit::window::WindowBuilder; -//use glutin::{GlRequest, Api}; - -use femtovg::{ - //CompositeOperation, - renderer::OpenGl, - Align, - Baseline, - Canvas, - Color, - FontId, - ImageFlags, - Paint, - Path, - Renderer, - Solidity, +use std::borrow::Cow; +use winit::{ + event::{Event, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::Window, }; // new femto-like framework: @@ -33,976 +17,294 @@ use femtovg::{ // resource, image // usvg for svg -// I'm also assuming that there's some logic bugs in the demo application, which wasn't built with this in mind; I have a much simpler application that I'm happy to show if that would be helpful (would need to extract an example). As a rough solution, I apply the following transformation on Winit's WindowEvent::ScaleFactorChanged: - -// /* ... in an application struct/impl ... */ -// pub fn rescale( -// &mut self, -// new_size: winit::dpi::PhysicalSize, -// new_scale_factor: f64, -// ) { -// // Update translation -// // (TODO: This is a guestimate and not well-tested; -// // the size updates might turn out ot be completely unnecessary) -// let shift = self.size.height as f32 - new_size.height as f32; -// self.canvas.translate(0.0, -shift); - -// // Update properties -// self.size = new_size; -// self.scale_factor = new_scale_factor; -// self.canvas.set_size( -// self.size.width, -// self.size.height, -// self.scale_factor as f32, -// ) -// } - -pub fn quantize(a: f32, d: f32) -> f32 { - (a / d + 0.5).trunc() * d -} +use swash::{ + scale::ScaleContext, + shape::ShapeContext, + text::Script, + zeno::{Vector, Verb}, + Attributes, CacheKey, Charmap, FontRef, +}; -struct Fonts { - regular: FontId, - bold: FontId, - code: FontId, - icons: FontId, +pub struct Font { + // Full content of the font file + data: Vec, + // Offset to the table directory + offset: u32, + // Cache key + key: CacheKey, } -fn main() { - // This provides better error messages in debug mode. - // It's disabled in release mode so it doesn't bloat up the file size. - #[cfg(all(debug_assertions, target_arch = "wasm32"))] - console_error_panic_hook::set_once(); - - let el = EventLoop::new(); - - #[cfg(not(target_arch = "wasm32"))] - let (renderer, windowed_context) = { - use glutin::ContextBuilder; - - let wb = WindowBuilder::new() - .with_inner_size(winit::dpi::LogicalSize::::new(1000., 600.)) - .with_title("femtovg demo"); - - //let windowed_context = ContextBuilder::new().with_gl(GlRequest::Specific(Api::OpenGlEs, (2, 0))).with_vsync(false).build_windowed(wb, &el).unwrap(); - //let windowed_context = ContextBuilder::new().with_vsync(false).with_multisampling(8).build_windowed(wb, &el).unwrap(); - let windowed_context = ContextBuilder::new() - .with_vsync(true) // TODO: set to true? - .build_windowed(wb, &el) - .unwrap(); - let windowed_context = unsafe { windowed_context.make_current().unwrap() }; - - let renderer = - OpenGl::new_from_glutin_context(&windowed_context).expect("Cannot create renderer"); - - (renderer, windowed_context) - }; - - #[cfg(target_arch = "wasm32")] - let (renderer, window) = { - use wasm_bindgen::JsCast; - - let canvas = web_sys::window() - .unwrap() - .document() - .unwrap() - .get_element_by_id("canvas") - .unwrap() - .dyn_into::() - .unwrap(); - - use winit::platform::web::WindowBuilderExtWebSys; - - let renderer = OpenGl::new_from_html_canvas(&canvas).expect("Cannot create renderer"); - - let window = WindowBuilder::new() - .with_canvas(Some(canvas)) - .build(&el) - .unwrap(); - - (renderer, window) - }; - - let mut canvas = Canvas::new(renderer).expect("Cannot create canvas"); - - // TODO: better femtovg support for variable fonts - let fonts = Fonts { - regular: canvas - .add_font_mem(&resource!("assets/fonts/Inter Variable/Inter.ttf")) - .expect("Cannot add font"), - bold: canvas - .add_font_mem(&resource!("assets/fonts/Inter Variable/Inter.ttf")) - .expect("Cannot add font"), - code: canvas - .add_font_mem(&resource!("assets/fonts/Fira Code/FiraCode-VF.ttf")) - .expect("Cannot add font"), - icons: canvas - .add_font_mem(&resource!("assets/entypo.ttf")) - .expect("Cannot add font"), - }; - - //canvas.add_font("/usr/share/fonts/noto/NotoSansArabic-Regular.ttf").expect("Cannot add font"); - - //let image_id = canvas.create_image_file("assets/RoomRender.jpg", ImageFlags::FLIP_Y).expect("Cannot create image"); - //canvas.blur_image(image_id, 10, 1050, 710, 200, 200); - - //let image_id = canvas.load_image_file("assets/RoomRender.jpg", ImageFlags::FLIP_Y).expect("Cannot create image"); - - // let images = vec![ - // canvas - // .load_image_mem(&resource!("assets/images/image1.jpg"), ImageFlags::empty()) - // .unwrap(), - // canvas - // .load_image_mem(&resource!("assets/images/image2.jpg"), ImageFlags::empty()) - // .unwrap(), - // ]; - - let mut screenshot_image_id = None; - - // - - // - - let start = Instant::now(); - let mut prevt = start; - - let mut mousex = 0.0; - let mut mousey = 0.0; - let mut dragging = false; - - let mut perf = PerfGraph::new(); - - { - #[cfg(not(target_arch = "wasm32"))] - let window = windowed_context.window(); - let dpi_factor = window.scale_factor(); - canvas.set_size(0, 0, dpi_factor as f32); - canvas.reset(); +impl Font { + pub fn from_file(path: &str, index: usize) -> Option { + // Read the full font file + let data = std::fs::read(path).ok()?; + // Create a temporary font reference for the first font in the file. + // This will do some basic validation, compute the necessary offset + // and generate a fresh cache key for us. + let font = FontRef::from_index(&data, index)?; + let (offset, key) = (font.offset, font.key); + // Return our struct with the original file data and copies of the + // offset and key from the font reference + Some(Self { data, offset, key }) } - el.run(move |event, _, control_flow| { - #[cfg(not(target_arch = "wasm32"))] - let window = windowed_context.window(); - - *control_flow = ControlFlow::Poll; - - match event { - Event::LoopDestroyed => return, - Event::WindowEvent { ref event, .. } => match event { - #[cfg(not(target_arch = "wasm32"))] - WindowEvent::Resized(physical_size) => { - windowed_context.resize(*physical_size); - - let dpi_factor = window.scale_factor(); - canvas.set_size(0, 0, dpi_factor as f32); - canvas.reset(); - } - WindowEvent::CursorMoved { - device_id: _, - position, - .. - } => { - if dragging { - // let p0 = canvas - // .transform() - // .inversed() - // .transform_point(mousex, mousey); - // let p1 = canvas - // .transform() - // .inversed() - // .transform_point(position.x as f32, position.y as f32); - - // canvas.translate(p1.0 - p0.0, p1.1 - p0.1); - } - - mousex = position.x as f32; - mousey = position.y as f32; - } - // WindowEvent::MouseWheel { - // device_id: _, - // delta, - // .. - // } => match delta { - // winit::event::MouseScrollDelta::LineDelta(_, y) => { - // let pt = canvas - // .transform() - // .inversed() - // .transform_point(mousex, mousey); - // canvas.translate(pt.0, pt.1); - // canvas.scale(1.0 + (y / 10.0), 1.0 + (y / 10.0)); - // canvas.translate(-pt.0, -pt.1); - // } - - // winit::event::MouseScrollDelta::PixelDelta(pos) => { - // let y = pos.y as f32; - // let pt = canvas - // .transform() - // .inversed() - // .transform_point(mousex, mousey); - // let rate = 2000.0; - // canvas.translate(pt.0, pt.1); - // canvas.scale(1.0 + (y / rate), 1.0 + (y / rate)); - // canvas.translate(-pt.0, -pt.1); - // } - // }, - WindowEvent::MouseInput { - button: MouseButton::Left, - state, - .. - } => match state { - ElementState::Pressed => dragging = true, - ElementState::Released => dragging = false, - }, - WindowEvent::KeyboardInput { - input: - KeyboardInput { - virtual_keycode: Some(VirtualKeyCode::S), - state: ElementState::Pressed, - .. - }, - .. - } => { - if let Some(screenshot_image_id) = screenshot_image_id { - canvas.delete_image(screenshot_image_id); - } - - if let Ok(image) = canvas.screenshot() { - screenshot_image_id = Some( - canvas - .create_image(image.as_ref(), ImageFlags::empty()) - .unwrap(), - ); - } - } - WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, - _ => (), - }, - Event::RedrawRequested(_) => { - let now = Instant::now(); - let dt = (now - prevt).as_secs_f32(); - prevt = now; - - perf.update(dt); - - let dpi_factor = window.scale_factor(); - // println!("DPI {}", dpi_factor); - // let dpi_factor = 0.5f64; - let size = window.inner_size(); - // let size: winit::dpi::LogicalSize = window.inner_size().to_logical(dpi_factor); // TODO: adjust for dpi - // window.set_inner_size(size); - // let size = window.inner_size(); - - // let t = start.elapsed().as_secs_f32(); - - canvas.set_size(size.width as u32, size.height as u32, dpi_factor as f32); - canvas.clear_rect( - 0, - 0, - size.width as u32, - size.height as u32, - Color::rgbf(0.3, 0.3, 0.32), - ); - - // let height = size.height as f32; - // let width = size.width as f32; - - let winit::dpi::LogicalSize { width, height: _ } = - size.to_logical::(dpi_factor); - - let pt = canvas - .transform() - .inversed() - .transform_point(mousex, mousey); - let rel_mousex = pt.0; - let rel_mousey = pt.1; - - draw_code(&mut canvas, fonts.code, 25.0, 500.0); - - draw_paragraph( - &mut canvas, - fonts.regular, - width - 450.0, - 50.0, - 150.0, - 100.0, - rel_mousex, - rel_mousey, - ); - - draw_window( - &mut canvas, - &fonts, - "Widgets `n Stuff", - 50.0, - 50.0, - 300.0, - 400.0, - ); - - let x = 60.0; - let mut y = 95.0; - - draw_search_box(&mut canvas, &fonts, "Search", x, y, 280.0, 25.0); - y += 40.0; - draw_drop_down(&mut canvas, &fonts, "Effects", 60.0, 135.0, 280.0, 28.0); - y += 45.0; - - draw_label(&mut canvas, &fonts, "Login", x, y, 280.0, 20.0); - y += 25.0; - draw_edit_box(&mut canvas, &fonts, "Email", x, y, 280.0, 28.0); - y += 35.0; - draw_edit_box(&mut canvas, &fonts, "Password", x, y, 280.0, 28.0); - y += 38.0; - draw_check_box(&mut canvas, &fonts, "Remember me", x, y, 140.0, 28.0); - draw_button( - &mut canvas, - &fonts, - Some("\u{E740}"), - "Sign in", - x + 138.0, - y, - 140.0, - 28.0, - Color::rgba(0, 96, 128, 255), - ); - y += 45.0; - - // Slider - draw_label(&mut canvas, &fonts, "Diameter", x, y, 280.0, 20.0); - y += 25.0; - draw_edit_box_num( - &mut canvas, - &fonts, - "123.00", - "px", - x + 180.0, - y, - 100.0, - 28.0, - ); - y += 55.0; - - draw_button( - &mut canvas, - &fonts, - Some("\u{E729}"), - "Delete", - x, - y, - 160.0, - 28.0, - Color::rgba(128, 16, 8, 255), - ); - draw_button( - &mut canvas, - &fonts, - None, - "Cancel", - x + 170.0, - y, - 110.0, - 28.0, - Color::rgba(0, 0, 0, 0), - ); - - /* - draw_spinner(&mut canvas, 15.0, 285.0, 10.0, t); - */ - - if let Some(image_id) = screenshot_image_id { - let x = size.width as f32 - 512.0; - let y = size.height as f32 - 512.0; - - let paint = Paint::image(image_id, x, y, 512.0, 512.0, 0.0, 1.0); - - let mut path = Path::new(); - path.rect(x, y, 512.0, 512.0); - canvas.fill_path(&mut path, paint); - canvas.stroke_path(&mut path, Paint::color(Color::hex("454545"))); - } - - // if true { - // let paint = Paint::image(image_id, size.width as f32, 15.0, 1920.0, 1080.0, 0.0, 1.0); - // let mut path = Path::new(); - // path.rect(size.width as f32, 15.0, 1920.0, 1080.0); - // canvas.fill_path(&mut path, paint); - // } - - canvas.save_with(|canvas| { - canvas.reset(); - perf.render(canvas, 5.0, 5.0); - }); - - //canvas.restore(); - - canvas.flush(); - #[cfg(not(target_arch = "wasm32"))] - windowed_context.swap_buffers().unwrap(); - } - Event::MainEventsCleared => { - //scroll = 1.0; - window.request_redraw() - } - _ => (), - } - }); -} - -fn draw_code(canvas: &mut Canvas, font: FontId, x: f32, y: f32) { - let text = "canvas.fill_text(x, y, &text[line_range], paint)"; - - canvas.save(); - - let mut paint = Paint::color(Color::rgba(255, 255, 255, 255)); - paint.set_font_size(14.0); - paint.set_font(&[font]); - paint.set_text_align(Align::Left); - paint.set_text_baseline(Baseline::Top); - - canvas.fill_text(x, y, text, paint).unwrap(); - - canvas.restore(); -} - -fn draw_paragraph( - canvas: &mut Canvas, - font: FontId, - x: f32, - y: f32, - width: f32, - _height: f32, - mx: f32, - my: f32, -) { - let text = "This is longer chunk of text.\n\nWould have used lorem ipsum but she was busy jumping over the lazy dog with the fox and all the men who came to the aid of the party.🎉"; - - canvas.save(); - - let mut paint = Paint::color(Color::rgba(255, 255, 255, 255)); - paint.set_font_size(14.0); - paint.set_font(&[font]); - paint.set_text_align(Align::Left); - paint.set_text_baseline(Baseline::Top); - - let mut gutter_y = 0.0; - let mut gutter = 0; - let mut y = y; - let mut px; - let mut caret_x; - - let lines = canvas - .break_text_vec(width, text, paint) - .expect("Cannot break text"); - - for (line_num, line_range) in lines.into_iter().enumerate() { - if let Ok(res) = canvas.fill_text(x, y, &text[line_range], paint) { - let hit = mx > x && mx < (x + width) && my >= y && my < (y + res.height()); - - if hit { - caret_x = if mx < x + res.width() / 2.0 { - x - } else { - x + res.width() - }; - px = x; - - for glyph in &res.glyphs { - let x0 = glyph.x; - let x1 = x0 + glyph.width; - let gx = x0 * 0.3 + x1 * 0.7; - - if mx >= px && mx < gx { - caret_x = glyph.x; - } - - px = gx; - } - - let mut path = Path::new(); - path.rect(caret_x, y, 1.0, res.height()); - canvas.fill_path(&mut path, Paint::color(Color::rgba(255, 192, 0, 255))); - - gutter = line_num + 1; - - gutter_y = y + 14.0 / 2.0; - } - - y += res.height(); - } + // As a convenience, you may want to forward some methods. + pub fn attributes(&self) -> Attributes { + self.as_ref().attributes() } - if gutter > 0 { - let mut paint = Paint::color(Color::rgba(255, 192, 0, 255)); - paint.set_font_size(12.0); - paint.set_font(&[font]); - paint.set_text_align(Align::Right); - paint.set_text_baseline(Baseline::Middle); - - let text = format!("{}", gutter); - - if let Ok(res) = canvas.measure_text(x - 10.0, gutter_y, &text, paint) { - let mut path = Path::new(); - path.rounded_rect( - res.x - 4.0, - res.y - 2.0, - res.width() + 8.0, - res.height() + 4.0, - (res.height() + 4.0) / 2.0 - 1.0, - ); - canvas.fill_path(&mut path, paint); - - paint.set_color(Color::rgba(32, 32, 32, 255)); - let _ = canvas.fill_text(x - 10.0, gutter_y, &text, paint); - } + pub fn charmap(&self) -> Charmap { + self.as_ref().charmap() } - // let mut start = 0; - - // while start < text.len() { - // let substr = &text[start..]; - - // if let Ok(index) = canvas.break_text(width, substr, paint) { - // if let Ok(res) = canvas.fill_text(x, y, &substr[0..index], paint) { - // y += res.height; - // } - - // start += &substr[0..index].len(); - // } else { - // break; - // } - // } - - canvas.restore(); -} - -fn draw_window( - canvas: &mut Canvas, - fonts: &Fonts, - title: &str, - x: f32, - y: f32, - w: f32, - h: f32, -) { - let corner_radius = 3.0; - - canvas.save(); - - //canvas.global_composite_operation(CompositeOperation::Lighter); - - // Window - let mut path = Path::new(); - path.rounded_rect(x, y, w, h, corner_radius); - canvas.fill_path(&mut path, Paint::color(Color::rgba(28, 30, 34, 192))); - - // Drop shadow - let shadow_paint = Paint::box_gradient( - x, - y + 2.0, - w, - h, - corner_radius * 2.0, - 10.0, - Color::rgba(0, 0, 0, 128), - Color::rgba(0, 0, 0, 0), - ); - let mut path = Path::new(); - path.rect(x - 10.0, y - 10.0, w + 20.0, h + 30.0); - path.rounded_rect(x, y, w, h, corner_radius); - path.solidity(Solidity::Hole); - canvas.fill_path(&mut path, shadow_paint); - - // Header - let header_paint = Paint::linear_gradient( - x, - y, - x, - y + 15.0, - Color::rgba(255, 255, 255, 8), - Color::rgba(0, 0, 0, 16), - ); - let mut path = Path::new(); - path.rounded_rect(x + 1.0, y + 1.0, w - 2.0, 30.0, corner_radius - 1.0); - canvas.fill_path(&mut path, header_paint); - - let mut path = Path::new(); - path.move_to(x + 0.5, y + 0.5 + 30.0); - path.line_to(x + 0.5 + w - 1.0, y + 0.5 + 30.0); - canvas.stroke_path(&mut path, Paint::color(Color::rgba(0, 0, 0, 32))); - - let mut text_paint = Paint::color(Color::rgba(0, 0, 0, 32)); - text_paint.set_font_size(16.0); - text_paint.set_font(&[fonts.bold]); - text_paint.set_text_align(Align::Center); - text_paint.set_color(Color::rgba(220, 220, 220, 160)); - - let _ = canvas.fill_text(x + (w / 2.0), y + 19.0, title, text_paint); - - // let bounds = canvas.text_bounds(x + (w / 2.0), y + 19.0, title, text_paint); - // - // let mut path = Path::new(); - // path.rect(bounds[0], bounds[1], bounds[2] - bounds[0], bounds[3] - bounds[1]); - // canvas.stroke_path(&mut path, Paint::color(Color::rgba(0, 0, 0, 255))); - - canvas.restore(); -} - -fn draw_search_box( - canvas: &mut Canvas, - fonts: &Fonts, - title: &str, - x: f32, - y: f32, - w: f32, - h: f32, -) { - let corner_radius = (h / 2.0) - 1.0; - - let bg = Paint::box_gradient( - x, - y + 1.5, - w, - h, - h / 2.0, - 5.0, - Color::rgba(0, 0, 0, 16), - Color::rgba(0, 0, 0, 92), - ); - let mut path = Path::new(); - path.rounded_rect(x, y, w, h, corner_radius); - canvas.fill_path(&mut path, bg); - - let mut text_paint = Paint::color(Color::rgba(255, 255, 255, 64)); - text_paint.set_font_size((h * 1.3).round()); - text_paint.set_font(&[fonts.icons]); - text_paint.set_text_align(Align::Center); - text_paint.set_text_baseline(Baseline::Middle); - let _ = canvas.fill_text(x + h * 0.55, y + h * 0.55, "\u{1F50D}", text_paint); - - let mut text_paint = Paint::color(Color::rgba(255, 255, 255, 32)); - text_paint.set_font_size(16.0); - text_paint.set_font(&[fonts.regular]); - text_paint.set_text_align(Align::Left); - text_paint.set_text_baseline(Baseline::Middle); - let _ = canvas.fill_text(x + h, y + h * 0.5, title, text_paint); - - let mut text_paint = Paint::color(Color::rgba(255, 255, 255, 32)); - text_paint.set_font_size((h * 1.3).round()); - text_paint.set_font(&[fonts.icons]); - text_paint.set_text_align(Align::Center); - text_paint.set_text_baseline(Baseline::Middle); - let _ = canvas.fill_text(x + w - h * 0.55, y + h * 0.45, "\u{2716}", text_paint); -} - -fn draw_drop_down( - canvas: &mut Canvas, - fonts: &Fonts, - title: &str, - x: f32, - y: f32, - w: f32, - h: f32, -) { - let corner_radius = 4.0; - - let bg = Paint::linear_gradient( - x, - y, - x, - y + h, - Color::rgba(255, 255, 255, 16), - Color::rgba(0, 0, 0, 16), - ); - let mut path = Path::new(); - path.rounded_rect(x + 1.0, y + 1.0, w - 2.0, h - 2.0, corner_radius); - canvas.fill_path(&mut path, bg); - - let mut path = Path::new(); - path.rounded_rect(x + 0.5, y + 0.5, w - 1.0, h - 1.0, corner_radius - 0.5); - canvas.stroke_path(&mut path, Paint::color(Color::rgba(0, 0, 0, 48))); - - let mut text_paint = Paint::color(Color::rgba(255, 255, 255, 160)); - text_paint.set_font_size(16.0); - text_paint.set_font(&[fonts.regular]); - text_paint.set_text_align(Align::Left); - text_paint.set_text_baseline(Baseline::Middle); - let _ = canvas.fill_text(x + h * 0.3, y + h * 0.5, title, text_paint); - - let mut text_paint = Paint::color(Color::rgba(255, 255, 255, 64)); - text_paint.set_font_size((h * 1.3).round()); - text_paint.set_font(&[fonts.icons]); - text_paint.set_text_align(Align::Center); - text_paint.set_text_baseline(Baseline::Middle); - let _ = canvas.fill_text(x + w - h * 0.5, y + h * 0.45, "\u{E75E}", text_paint); -} - -fn draw_label( - canvas: &mut Canvas, - fonts: &Fonts, - title: &str, - x: f32, - y: f32, - _w: f32, - h: f32, -) { - let mut text_paint = Paint::color(Color::rgba(255, 255, 255, 128)); - text_paint.set_font_size(14.0); - text_paint.set_font(&[fonts.regular]); - text_paint.set_text_align(Align::Left); - text_paint.set_text_baseline(Baseline::Middle); - let _ = canvas.fill_text(x, y + h * 0.5, title, text_paint); -} - -fn draw_edit_box_base(canvas: &mut Canvas, x: f32, y: f32, w: f32, h: f32) { - let paint = Paint::box_gradient( - x + 1.0, - y + 2.5, - w - 2.0, - h - 2.0, - 3.0, - 4.0, - Color::rgba(255, 255, 255, 32), - Color::rgba(32, 32, 32, 32), - ); - - let mut path = Path::new(); - path.rounded_rect(x + 1.0, y + 1.0, w - 2.0, h - 2.0, 3.0); - canvas.fill_path(&mut path, paint); - - let mut path = Path::new(); - path.rounded_rect(x + 0.5, y + 0.5, w - 1.0, h - 1.0, 3.5); - canvas.stroke_path(&mut path, Paint::color(Color::rgba(0, 0, 0, 48))); -} - -fn draw_edit_box( - canvas: &mut Canvas, - fonts: &Fonts, - title: &str, - x: f32, - y: f32, - w: f32, - h: f32, -) { - draw_edit_box_base(canvas, x, y, w, h); - - let mut text_paint = Paint::color(Color::rgba(255, 255, 255, 64)); - text_paint.set_font_size(16.0); - text_paint.set_font(&[fonts.regular]); - text_paint.set_text_align(Align::Left); - text_paint.set_text_baseline(Baseline::Middle); - let _ = canvas.fill_text(x + h * 0.5, y + h * 0.5, title, text_paint); -} - -fn draw_edit_box_num( - canvas: &mut Canvas, - fonts: &Fonts, - title: &str, - units: &str, - x: f32, - y: f32, - w: f32, - h: f32, -) { - draw_edit_box_base(canvas, x, y, w, h); - - let mut paint = Paint::color(Color::rgba(255, 255, 255, 64)); - paint.set_font_size(14.0); - paint.set_font(&[fonts.regular]); - paint.set_text_align(Align::Right); - paint.set_text_baseline(Baseline::Middle); - - if let Ok(layout) = canvas.measure_text(0.0, 0.0, units, paint) { - let _ = canvas.fill_text(x + w - h * 0.3, y + h * 0.5, units, paint); - - paint.set_font_size(16.0); - paint.set_color(Color::rgba(255, 255, 255, 128)); - - let _ = canvas.fill_text(x + w - layout.width() - h * 0.5, y + h * 0.5, title, paint); + // Create the transient font reference for accessing this crate's + // functionality. + pub fn as_ref(&self) -> FontRef { + // Note that you'll want to initialize the struct directly here as + // using any of the FontRef constructors will generate a new key which, + // while completely safe, will nullify the performance optimizations of + // the caching mechanisms used in this crate. + FontRef { + data: &self.data, + offset: self.offset, + key: self.key, + } } } +fn font() { + let font = Font::from_file("assets/fonts/Inter Variable/Inter.ttf", 0).unwrap(); + let font = font.as_ref(); + + // -- Shaping + + let mut context = ShapeContext::new(); + let mut shaper = context + .builder(font) + .script(Script::Latin) + .size(14.) + .variations(&[("wght", 520.5)]) + .build(); + + shaper.add_str("a quick brown fox?"); + + // add_str with boundary analysis + // use swash::text::{analyze, Script}; + // use swash::text::cluster::{CharInfo, Parser, Token}; + // let text = "a quick brown fox?"; + // let mut parser = Parser::new( + // Script::Latin, + // text.char_indices() + // // Call analyze passing the same text and zip + // // the results + // .zip(analyze(text.chars())) + // // Analyze yields the tuple (Properties, Boundary) + // .map(|((i, ch), (props, boundary))| Token { + // ch, + // offset: i as u32, + // len: ch.len_utf8() as u8, + // // Create character information from properties and boundary + // info: CharInfo::new(props, boundary), + // data: 0, + // }), + // ); + + shaper.shape_with(|c| { + // use the glyph cluster + + // c.glyphs + }); -fn draw_check_box( - canvas: &mut Canvas, - fonts: &Fonts, - text: &str, - x: f32, - y: f32, - _w: f32, - h: f32, -) { - let mut paint = Paint::color(Color::rgba(255, 255, 255, 160)); - paint.set_font_size(14.0); - paint.set_font(&[fonts.regular]); - paint.set_text_baseline(Baseline::Middle); - - let _ = canvas.fill_text(x + 28.0, y + h * 0.5, text, paint); + let mut context = ScaleContext::new(); + let mut scaler = context + .builder(font) + .hint(true) + .size(12.) + .variations(&[("wght", 520.5)]) + .build(); + let glyph_id = font.charmap().map('Q'); + let outline = scaler.scale_outline(glyph_id).unwrap(); - paint = Paint::box_gradient( - x + 1.0, - y + (h * 0.5).floor() - 9.0 + 1.0, - 18.0, - 18.0, - 3.0, - 3.0, - Color::rgba(0, 0, 0, 32), - Color::rgba(0, 0, 0, 92), - ); - let mut path = Path::new(); - path.rounded_rect(x + 1.0, y + (h * 0.5).floor() - 9.0, 18.0, 18.0, 3.0); - canvas.fill_path(&mut path, paint); + append_outline((), outline.verbs(), outline.points()); - paint = Paint::color(Color::rgba(255, 255, 255, 128)); - paint.set_font_size(36.0); - paint.set_font(&[fonts.icons]); - paint.set_text_align(Align::Center); - paint.set_text_baseline(Baseline::Middle); - let _ = canvas.fill_text(x + 9.0 + 2.0, y + h * 0.5, "\u{2713}", paint); + // -- Scaling } -fn draw_button( - canvas: &mut Canvas, - fonts: &Fonts, - preicon: Option<&str>, - text: &str, - x: f32, - y: f32, - w: f32, - h: f32, - color: Color, -) { - let corner_radius = 4.0; - - let a = if color.is_black() { 16 } else { 32 }; - - let bg = Paint::linear_gradient( - x, - y, - x, - y + h, - Color::rgba(255, 255, 255, a), - Color::rgba(0, 0, 0, a), - ); - - let mut path = Path::new(); - path.rounded_rect(x + 1.0, y + 1.0, w - 2.0, h - 2.0, corner_radius - 1.0); - - if !color.is_black() { - canvas.fill_path(&mut path, Paint::color(color)); - } - - canvas.fill_path(&mut path, bg); - - let mut path = Path::new(); - path.rounded_rect(x + 0.5, y + 0.5, w - 1.0, h - 1.0, corner_radius - 0.5); - canvas.stroke_path(&mut path, Paint::color(Color::rgba(0, 0, 0, 48))); - - let mut paint = Paint::color(Color::rgba(255, 255, 255, 96)); - paint.set_font_size(15.0); - paint.set_font(&[fonts.bold]); - paint.set_text_align(Align::Left); - paint.set_text_baseline(Baseline::Middle); - - let tw = if let Ok(layout) = canvas.measure_text(0.0, 0.0, text, paint) { - layout.width() - } else { - 0.0 - }; - - let mut iw = 0.0; - - if let Some(icon) = preicon { - paint.set_font(&[fonts.icons]); - paint.set_font_size(h * 1.3); - - if let Ok(layout) = canvas.measure_text(0.0, 0.0, icon, paint) { - iw = layout.width() + (h * 0.15); +fn append_outline(_encoder: (), verbs: &[Verb], points: &[Vector]) { + for verb in verbs { + println!("{:?}", verb); + match verb { + Verb::MoveTo => { + // + } + Verb::LineTo => { + // + } + Verb::QuadTo => { + // + } + Verb::CurveTo => { + // + } + Verb::Close => { + // + } } - - let _ = canvas.fill_text(x + w * 0.5 - tw * 0.5 - iw * 0.75, y + h * 0.5, icon, paint); } - - paint.set_font_size(15.0); - paint.set_font(&[fonts.regular]); - paint.set_color(Color::rgba(0, 0, 0, 160)); - let _ = canvas.fill_text( - x + w * 0.5 - tw * 0.5 + iw * 0.25, - y + h * 0.5 - 1.0, - text, - paint, - ); - paint.set_color(Color::rgba(255, 255, 255, 160)); - let _ = canvas.fill_text(x + w * 0.5 - tw * 0.5 + iw * 0.25, y + h * 0.5, text, paint); -} - -struct PerfGraph { - history_count: usize, - values: Vec, - head: usize, } +async fn run(event_loop: EventLoop<()>, window: Window) { + let size = window.inner_size(); + let instance = wgpu::Instance::new(wgpu::Backends::all()); + let surface = unsafe { instance.create_surface(&window) }; + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::default(), // TODO: select based on backend + force_fallback_adapter: false, + // Request an adapter which can render to our surface + compatible_surface: Some(&surface), + }) + .await + .expect("Failed to find an appropriate adapter"); + + // Create the logical device and command queue + let (device, queue) = adapter + .request_device( + &wgpu::DeviceDescriptor { + label: None, + features: wgpu::Features::empty(), + // Make sure we use the texture resolution limits from the adapter, so we can support images the size of the swapchain. + limits: wgpu::Limits::downlevel_webgl2_defaults() + .using_resolution(adapter.limits()), + }, + None, + ) + .await + .expect("Failed to create device"); + + // Load the shaders from disk + let shader = device.create_shader_module(&wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))), + }); -impl PerfGraph { - fn new() -> Self { - Self { - history_count: 100, - values: vec![0.0; 100], - head: Default::default(), - } - } + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[], + push_constant_ranges: &[], + }); - fn update(&mut self, frame_time: f32) { - self.head = (self.head + 1) % self.history_count; - self.values[self.head] = frame_time; - } + let swapchain_format = surface.get_preferred_format(&adapter).unwrap(); + + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: None, + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + buffers: &[], + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[swapchain_format.into()], + }), + primitive: wgpu::PrimitiveState::default(), + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview: None, + }); - fn get_average(&self) -> f32 { - self.values.iter().map(|v| *v).sum::() / self.history_count as f32 - } + let mut config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: swapchain_format, + width: size.width, + height: size.height, + present_mode: wgpu::PresentMode::Mailbox, + }; - fn render(&self, canvas: &mut Canvas, x: f32, y: f32) { - let avg = self.get_average(); + surface.configure(&device, &config); - let w = 200.0; - let h = 35.0; + font(); - let mut path = Path::new(); - path.rect(x, y, w, h); - canvas.fill_path(&mut path, Paint::color(Color::rgba(0, 0, 0, 128))); + event_loop.run(move |event, _, control_flow| { + // Have the closure take ownership of the resources. + // `event_loop.run` never returns, therefore we must do this to ensure + // the resources are properly cleaned up. + let _ = (&instance, &adapter, &shader, &pipeline_layout); - let mut path = Path::new(); - path.move_to(x, y + h); + *control_flow = ControlFlow::Wait; + match event { + Event::WindowEvent { + event: WindowEvent::Resized(size), + .. + } => { + // Reconfigure the surface with the new size + config.width = size.width; + config.height = size.height; + surface.configure(&device, &config); + // On macos the window needs to be redrawn manually after resizing + window.request_redraw(); + } + Event::RedrawRequested(_) => { + let frame = surface + .get_current_texture() + .expect("Failed to acquire next swap chain texture"); + let view = frame + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + let mut encoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + { + let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[wgpu::RenderPassColorAttachment { + view: &view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::GREEN), + store: true, + }, + }], + depth_stencil_attachment: None, + }); + rpass.set_pipeline(&render_pipeline); + rpass.draw(0..3, 0..1); + } - for i in 0..self.history_count { - let mut v = 1.0 / (0.00001 + self.values[(self.head + i) % self.history_count]); - if v > 80.0 { - v = 80.0; + queue.submit(Some(encoder.finish())); + frame.present(); } - let vx = x + (i as f32 / (self.history_count - 1) as f32) * w; - let vy = y + h - ((v / 80.0) * h); - path.line_to(vx, vy); + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => *control_flow = ControlFlow::Exit, + _ => {} } + }); +} - path.line_to(x + w, y + h); - canvas.fill_path(&mut path, Paint::color(Color::rgba(255, 192, 0, 128))); - - let mut text_paint = Paint::color(Color::rgba(240, 240, 240, 255)); - text_paint.set_font_size(12.0); - let _ = canvas.fill_text(x + 5.0, y + 13.0, "Frame time", text_paint); - - let mut text_paint = Paint::color(Color::rgba(240, 240, 240, 255)); - text_paint.set_font_size(14.0); - text_paint.set_text_align(Align::Right); - text_paint.set_text_baseline(Baseline::Top); - let _ = canvas.fill_text(x + w - 5.0, y, &format!("{:.2} FPS", 1.0 / avg), text_paint); - - let mut text_paint = Paint::color(Color::rgba(240, 240, 240, 200)); - text_paint.set_font_size(12.0); - text_paint.set_text_align(Align::Right); - text_paint.set_text_baseline(Baseline::Alphabetic); - let _ = canvas.fill_text( - x + w - 5.0, - y + h - 5.0, - &format!("{:.2} ms", avg * 1000.0), - text_paint, - ); +fn main() { + let event_loop = EventLoop::new(); + let window = winit::window::Window::new(&event_loop).unwrap(); + #[cfg(not(target_arch = "wasm32"))] + { + env_logger::init(); + // Temporarily avoid srgb formats for the swapchain on the web + pollster::block_on(run(event_loop, window)); + } + #[cfg(target_arch = "wasm32")] + { + std::panic::set_hook(Box::new(console_error_panic_hook::hook)); + console_log::init().expect("could not initialize logger"); + use winit::platform::web::WindowExtWebSys; + // On wasm, append the canvas to the document body + web_sys::window() + .and_then(|win| win.document()) + .and_then(|doc| doc.body()) + .and_then(|body| { + body.append_child(&web_sys::Element::from(window.canvas())) + .ok() + }) + .expect("couldn't append canvas to document body"); + wasm_bindgen_futures::spawn_local(run(event_loop, window)); } } diff --git a/helix-ui/src/shader.wgsl b/helix-ui/src/shader.wgsl new file mode 100644 index 000000000..a1ef447d6 --- /dev/null +++ b/helix-ui/src/shader.wgsl @@ -0,0 +1,11 @@ +[[stage(vertex)]] +fn vs_main([[builtin(vertex_index)]] in_vertex_index: u32) -> [[builtin(position)]] vec4 { + let x = f32(i32(in_vertex_index) - 1); + let y = f32(i32(in_vertex_index & 1u) * 2 - 1); + return vec4(x, y, 0.0, 1.0); +} + +[[stage(fragment)]] +fn fs_main() -> [[location(0)]] vec4 { + return vec4(1.0, 0.0, 0.0, 1.0); +}