Compare commits

...

2 commits

Author SHA1 Message Date
7b67d29c01 simplify
Some checks are pending
/ build (push) Waiting to run
2025-07-19 08:42:08 +03:00
291b917970 this one at least compiles 2025-07-19 07:55:56 +03:00
24 changed files with 1185 additions and 1096 deletions

381
Cargo.lock generated
View file

@ -13,9 +13,9 @@ dependencies = [
[[package]] [[package]]
name = "adler2" name = "adler2"
version = "2.0.0" version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]] [[package]]
name = "allocator-api2" name = "allocator-api2"
@ -40,9 +40,9 @@ checksum = "628d228f918ac3b82fe590352cc719d30664a0c13ca3a60266fe02c7132d480a"
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.4.0" version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]] [[package]]
name = "backtrace" name = "backtrace"
@ -56,7 +56,7 @@ dependencies = [
"miniz_oxide", "miniz_oxide",
"object", "object",
"rustc-demangle", "rustc-demangle",
"windows-targets", "windows-targets 0.52.6",
] ]
[[package]] [[package]]
@ -86,15 +86,15 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.9.0" version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.17.0" version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
[[package]] [[package]]
name = "by_address" name = "by_address"
@ -110,18 +110,18 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
[[package]] [[package]]
name = "castaway" name = "castaway"
version = "0.2.3" version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a"
dependencies = [ dependencies = [
"rustversion", "rustversion",
] ]
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.0" version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
[[package]] [[package]]
name = "compact_str" name = "compact_str"
@ -154,6 +154,31 @@ name = "const_panic"
version = "0.2.12" version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2459fc9262a1aa204eb4b5764ad4f189caec88aea9634389c0a25f8be7f6265e" checksum = "2459fc9262a1aa204eb4b5764ad4f189caec88aea9634389c0a25f8be7f6265e"
dependencies = [
"const_panic_proc_macros",
"typewit",
]
[[package]]
name = "const_panic_proc_macros"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c5b80a80fb52c1a6ca02e3cd829a76b472ff0a15588196fd8da95221f0c1e4b"
dependencies = [
"proc-macro2",
"quote",
"syn",
"unicode-xid",
]
[[package]]
name = "convert_case"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7"
dependencies = [
"unicode-segmentation",
]
[[package]] [[package]]
name = "crossbeam-utils" name = "crossbeam-utils"
@ -177,6 +202,24 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "crossterm"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b"
dependencies = [
"bitflags",
"crossterm_winapi",
"derive_more",
"document-features",
"mio",
"parking_lot",
"rustix 1.0.8",
"signal-hook",
"signal-hook-mio",
"winapi",
]
[[package]] [[package]]
name = "crossterm_winapi" name = "crossterm_winapi"
version = "0.9.1" version = "0.9.1"
@ -221,6 +264,36 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "derive_more"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678"
dependencies = [
"derive_more-impl",
]
[[package]]
name = "derive_more-impl"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3"
dependencies = [
"convert_case",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "document-features"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d"
dependencies = [
"litrs",
]
[[package]] [[package]]
name = "either" name = "either"
version = "1.15.0" version = "1.15.0"
@ -241,12 +314,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]] [[package]]
name = "errno" name = "errno"
version = "0.3.11" version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys 0.59.0", "windows-sys 0.60.2",
] ]
[[package]] [[package]]
@ -281,7 +354,7 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"wasi 0.11.0+wasi-snapshot-preview1", "wasi 0.11.1+wasi-snapshot-preview1",
] ]
[[package]] [[package]]
@ -304,9 +377,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.15.3" version = "0.15.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
dependencies = [ dependencies = [
"allocator-api2", "allocator-api2",
"equivalent", "equivalent",
@ -333,9 +406,9 @@ checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd"
[[package]] [[package]]
name = "instability" name = "instability"
version = "0.3.7" version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf9fed6d91cfb734e7476a06bde8300a1b94e217e1b523b6f0cd1a01998c71d" checksum = "435d80800b936787d62688c927b6490e887c7ef5ff9ce922c6c6050fca75eb9a"
dependencies = [ dependencies = [
"darling", "darling",
"indoc", "indoc",
@ -413,9 +486,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.172" version = "0.2.174"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
@ -430,10 +503,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
[[package]] [[package]]
name = "lock_api" name = "litrs"
version = "0.4.12" version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5"
[[package]]
name = "lock_api"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"scopeguard", "scopeguard",
@ -456,29 +535,29 @@ dependencies = [
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.7.4" version = "2.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.8.8" version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
dependencies = [ dependencies = [
"adler2", "adler2",
] ]
[[package]] [[package]]
name = "mio" name = "mio"
version = "1.0.3" version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
dependencies = [ dependencies = [
"libc", "libc",
"log", "log",
"wasi 0.11.0+wasi-snapshot-preview1", "wasi 0.11.1+wasi-snapshot-preview1",
"windows-sys 0.52.0", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
@ -515,7 +594,7 @@ dependencies = [
"fast-srgb8", "fast-srgb8",
"palette_derive", "palette_derive",
"phf", "phf",
"rand", "rand 0.8.5",
] ]
[[package]] [[package]]
@ -532,9 +611,9 @@ dependencies = [
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
version = "0.12.3" version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13"
dependencies = [ dependencies = [
"lock_api", "lock_api",
"parking_lot_core", "parking_lot_core",
@ -542,15 +621,15 @@ dependencies = [
[[package]] [[package]]
name = "parking_lot_core" name = "parking_lot_core"
version = "0.9.10" version = "0.9.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"redox_syscall", "redox_syscall",
"smallvec", "smallvec",
"windows-targets", "windows-targets 0.52.6",
] ]
[[package]] [[package]]
@ -576,7 +655,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
dependencies = [ dependencies = [
"phf_shared", "phf_shared",
"rand", "rand 0.8.5",
] ]
[[package]] [[package]]
@ -621,17 +700,17 @@ dependencies = [
[[package]] [[package]]
name = "proptest" name = "proptest"
version = "1.6.0" version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14cae93065090804185d3b75f0bf93b8eeda30c7a9b4a33d3bdb3988d6229e50" checksum = "6fcdab19deb5195a31cf7726a210015ff1496ba1464fd42cb4f537b8b01b471f"
dependencies = [ dependencies = [
"bit-set", "bit-set",
"bit-vec", "bit-vec",
"bitflags", "bitflags",
"lazy_static", "lazy_static",
"num-traits", "num-traits",
"rand", "rand 0.9.1",
"rand_chacha", "rand_chacha 0.9.0",
"rand_xorshift", "rand_xorshift",
"regex-syntax", "regex-syntax",
"rusty-fork", "rusty-fork",
@ -652,15 +731,15 @@ dependencies = [
[[package]] [[package]]
name = "quanta" name = "quanta"
version = "0.12.5" version = "0.12.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3bd1fe6824cea6538803de3ff1bc0cf3949024db3d43c9643024bfb33a807c0e" checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7"
dependencies = [ dependencies = [
"crossbeam-utils", "crossbeam-utils",
"libc", "libc",
"once_cell", "once_cell",
"raw-cpuid", "raw-cpuid",
"wasi 0.11.0+wasi-snapshot-preview1", "wasi 0.11.1+wasi-snapshot-preview1",
"web-sys", "web-sys",
"winapi", "winapi",
] ]
@ -682,9 +761,9 @@ dependencies = [
[[package]] [[package]]
name = "r-efi" name = "r-efi"
version = "5.2.0" version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]] [[package]]
name = "rand" name = "rand"
@ -693,8 +772,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [ dependencies = [
"libc", "libc",
"rand_chacha", "rand_chacha 0.3.1",
"rand_core", "rand_core 0.6.4",
]
[[package]]
name = "rand"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
dependencies = [
"rand_chacha 0.9.0",
"rand_core 0.9.3",
] ]
[[package]] [[package]]
@ -704,7 +793,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [ dependencies = [
"ppv-lite86", "ppv-lite86",
"rand_core", "rand_core 0.6.4",
]
[[package]]
name = "rand_chacha"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core 0.9.3",
] ]
[[package]] [[package]]
@ -717,12 +816,21 @@ dependencies = [
] ]
[[package]] [[package]]
name = "rand_xorshift" name = "rand_core"
version = "0.3.0" version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
dependencies = [ dependencies = [
"rand_core", "getrandom 0.3.3",
]
[[package]]
name = "rand_xorshift"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a"
dependencies = [
"rand_core 0.9.3",
] ]
[[package]] [[package]]
@ -734,7 +842,7 @@ dependencies = [
"bitflags", "bitflags",
"cassowary", "cassowary",
"compact_str", "compact_str",
"crossterm", "crossterm 0.28.1",
"indoc", "indoc",
"instability", "instability",
"itertools 0.13.0", "itertools 0.13.0",
@ -757,9 +865,9 @@ dependencies = [
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.5.12" version = "0.5.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6"
dependencies = [ dependencies = [
"bitflags", "bitflags",
] ]
@ -772,9 +880,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
version = "0.1.24" version = "0.1.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f"
[[package]] [[package]]
name = "rustix" name = "rustix"
@ -791,22 +899,22 @@ dependencies = [
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "1.0.7" version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"errno", "errno",
"libc", "libc",
"linux-raw-sys 0.9.4", "linux-raw-sys 0.9.4",
"windows-sys 0.59.0", "windows-sys 0.60.2",
] ]
[[package]] [[package]]
name = "rustversion" name = "rustversion"
version = "1.0.20" version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
[[package]] [[package]]
name = "rusty-fork" name = "rusty-fork"
@ -870,9 +978,9 @@ checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.15.0" version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]] [[package]]
name = "static_assertions" name = "static_assertions"
@ -910,9 +1018,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.101" version = "2.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -921,14 +1029,14 @@ dependencies = [
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.19.1" version = "3.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1"
dependencies = [ dependencies = [
"fastrand", "fastrand",
"getrandom 0.3.3", "getrandom 0.3.3",
"once_cell", "once_cell",
"rustix 1.0.7", "rustix 1.0.8",
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
@ -936,7 +1044,7 @@ dependencies = [
name = "tengri" name = "tengri"
version = "0.13.0" version = "0.13.0"
dependencies = [ dependencies = [
"crossterm", "crossterm 0.29.0",
"tengri", "tengri",
"tengri_core", "tengri_core",
"tengri_dsl", "tengri_dsl",
@ -954,6 +1062,7 @@ version = "0.13.0"
name = "tengri_dsl" name = "tengri_dsl"
version = "0.13.0" version = "0.13.0"
dependencies = [ dependencies = [
"const_panic",
"itertools 0.14.0", "itertools 0.14.0",
"konst", "konst",
"proptest", "proptest",
@ -1000,11 +1109,11 @@ version = "0.13.0"
dependencies = [ dependencies = [
"atomic_float", "atomic_float",
"better-panic", "better-panic",
"crossterm", "crossterm 0.29.0",
"konst", "konst",
"palette", "palette",
"quanta", "quanta",
"rand", "rand 0.8.5",
"ratatui", "ratatui",
"tengri", "tengri",
"tengri_core", "tengri_core",
@ -1091,6 +1200,12 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
[[package]]
name = "unicode-xid"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]] [[package]]
name = "wait-timeout" name = "wait-timeout"
version = "0.2.1" version = "0.2.1"
@ -1102,9 +1217,9 @@ dependencies = [
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.11.0+wasi-snapshot-preview1" version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]] [[package]]
name = "wasi" name = "wasi"
@ -1204,22 +1319,22 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.59.0" version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [ dependencies = [
"windows-targets", "windows-targets 0.52.6",
]
[[package]]
name = "windows-sys"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
"windows-targets 0.53.2",
] ]
[[package]] [[package]]
@ -1228,14 +1343,30 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [ dependencies = [
"windows_aarch64_gnullvm", "windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc", "windows_aarch64_msvc 0.52.6",
"windows_i686_gnu", "windows_i686_gnu 0.52.6",
"windows_i686_gnullvm", "windows_i686_gnullvm 0.52.6",
"windows_i686_msvc", "windows_i686_msvc 0.52.6",
"windows_x86_64_gnu", "windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm", "windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc", "windows_x86_64_msvc 0.52.6",
]
[[package]]
name = "windows-targets"
version = "0.53.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef"
dependencies = [
"windows_aarch64_gnullvm 0.53.0",
"windows_aarch64_msvc 0.53.0",
"windows_i686_gnu 0.53.0",
"windows_i686_gnullvm 0.53.0",
"windows_i686_msvc 0.53.0",
"windows_x86_64_gnu 0.53.0",
"windows_x86_64_gnullvm 0.53.0",
"windows_x86_64_msvc 0.53.0",
] ]
[[package]] [[package]]
@ -1244,48 +1375,96 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnu"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
[[package]] [[package]]
name = "windows_i686_gnullvm" name = "windows_i686_gnullvm"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_i686_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnu"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
[[package]] [[package]]
name = "wit-bindgen-rt" name = "wit-bindgen-rt"
version = "0.39.0" version = "0.39.0"
@ -1297,18 +1476,18 @@ dependencies = [
[[package]] [[package]]
name = "zerocopy" name = "zerocopy"
version = "0.8.25" version = "0.8.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f"
dependencies = [ dependencies = [
"zerocopy-derive", "zerocopy-derive",
] ]
[[package]] [[package]]
name = "zerocopy-derive" name = "zerocopy-derive"
version = "0.8.25" version = "0.8.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View file

@ -25,7 +25,7 @@ edition = "2024"
atomic_float = { version = "1" } atomic_float = { version = "1" }
better-panic = { version = "0.3.0" } better-panic = { version = "0.3.0" }
const_panic = { version = "0.2.12", features = [ "derive" ] } const_panic = { version = "0.2.12", features = [ "derive" ] }
crossterm = { version = "0.28.1" } crossterm = { version = "0.29.0" }
heck = { version = "0.5" } heck = { version = "0.5" }
itertools = { version = "0.14.0" } itertools = { version = "0.14.0" }
konst = { version = "0.3.16", features = [ "rust_1_83" ] } konst = { version = "0.3.16", features = [ "rust_1_83" ] }

View file

@ -5,6 +5,7 @@
extern crate const_panic; extern crate const_panic;
use const_panic::{concat_panic, PanicFmt}; use const_panic::{concat_panic, PanicFmt};
pub(crate) use ::tengri_core::*; pub(crate) use ::tengri_core::*;
use std::ops::Deref;
pub(crate) use std::error::Error; pub(crate) use std::error::Error;
pub(crate) use std::fmt::Debug; pub(crate) use std::fmt::Debug;
pub(crate) use std::sync::Arc; pub(crate) use std::sync::Arc;
@ -44,13 +45,16 @@ impl<Str: DslStr, Exp: DslExp> Val<Str, Exp> {
T::Str: for<'a> From<&'a Str>, T::Str: for<'a> From<&'a Str>,
T::Exp: for<'a> From<&'a Exp> T::Exp: for<'a> From<&'a Exp>
{ {
match self { Val::Nil => Val::Nil, match self {
Val::Num(u) => Val::Num(*u), Val::Nil => Val::Nil,
Val::Sym(s) => Val::Sym(s.into()), Val::Num(u) => Val::Num(*u),
Val::Key(s) => Val::Key(s.into()), Val::Sym(s) => Val::Sym(s.into()),
Val::Str(s) => Val::Str(s.into()), Val::Key(s) => Val::Key(s.into()),
Val::Exp(d, x) => Val::Exp(*d, x.into()), Val::Str(s) => Val::Str(s.into()),
Val::Error(e) => Val::Error(*e) } } Val::Exp(d, x) => Val::Exp(*d, x.into()),
Val::Error(e) => Val::Error(*e)
}
}
} }
/// The expression representation for a [Dsl] implementation. /// The expression representation for a [Dsl] implementation.
/// [Cst] uses [CstIter]. [Ast] uses [VecDeque]. /// [Cst] uses [CstIter]. [Ast] uses [VecDeque].
@ -58,8 +62,11 @@ pub trait DslExp: PartialEq + Clone + Default + Debug + Dsl {}
impl<T: PartialEq + Clone + Default + Debug + Dsl> DslExp for T {} impl<T: PartialEq + Clone + Default + Debug + Dsl> DslExp for T {}
/// The string representation for a [Dsl] implementation. /// The string representation for a [Dsl] implementation.
/// [Cst] uses `&'s str`. [Ast] uses `Arc<str>`. /// [Cst] uses `&'s str`. [Ast] uses `Arc<str>`.
pub trait DslStr: PartialEq + Clone + Default + Debug + AsRef<str> + std::ops::Deref<Target = str> {} pub trait DslStr: PartialEq + Clone + Default + Debug + AsRef<str> + Deref<Target = str> {
impl<T: PartialEq + Clone + Default + Debug + AsRef<str> + std::ops::Deref<Target = str>> DslStr for T {} fn as_str (&self) -> &str { self.as_ref() }
fn as_arc (&self) -> Arc<str> { self.as_ref().into() }
}
impl<Str: PartialEq + Clone + Default + Debug + AsRef<str> + Deref<Target = str>> DslStr for Str {}
impl<Str: DslStr, Exp: DslExp + Dsl<Str=Str, Exp=Exp>> Val<Str, Exp> { impl<Str: DslStr, Exp: DslExp + Dsl<Str=Str, Exp=Exp>> Val<Str, Exp> {
pub const fn err (&self) -> Option<DslError> {match self{Val::Error(e)=>Some(*e), _=>None}} pub const fn err (&self) -> Option<DslError> {match self{Val::Error(e)=>Some(*e), _=>None}}
pub const fn nil (&self) -> bool {match self{Val::Nil=>true, _=>false}} pub const fn nil (&self) -> bool {match self{Val::Nil=>true, _=>false}}
@ -96,22 +103,6 @@ impl<Str, Exp> Token<Str, Exp> {
Self { value: self.value, ..*self } Self { value: self.value, ..*self }
} }
} }
impl<'s> CstToken<'s> {
pub const fn slice (&self) -> &str {
str_range(self.source, self.start, self.end()) }
pub const fn slice_exp (&self) -> &str {
str_range(self.source, self.start.saturating_add(1), self.end()) }
pub const fn grow (&mut self) -> &mut Self {
let max_length = self.source.len().saturating_sub(self.start);
self.length = self.length + 1;
if self.length > max_length { self.length = max_length }
self
}
pub const fn grow_exp (&'s mut self, depth: isize, source: &'s str) -> &mut Self {
self.value = Val::Exp(depth, Cst(CstConstIter(source)));
self
}
}
/// To the [Dsl], a token is equivalent to its `value` field. /// To the [Dsl], a token is equivalent to its `value` field.
impl<Str: DslStr, Exp: DslExp> Dsl for Token<Str, Exp> { impl<Str: DslStr, Exp: DslExp> Dsl for Token<Str, Exp> {
type Str = Str; type Exp = Exp; type Str = Str; type Exp = Exp;
@ -126,16 +117,17 @@ pub trait Dsl: Clone + Debug {
/// Request the top-level DSL [Val]ue. /// Request the top-level DSL [Val]ue.
/// May perform cloning or parsing. /// May perform cloning or parsing.
fn dsl (&self) -> Val<Self::Str, Self::Exp>; fn dsl (&self) -> Val<Self::Str, Self::Exp>;
fn err (&self) -> Option<DslError> {self.dsl().err()} fn err (&self) -> Option<DslError> {self.dsl().err()}
fn nil (&self) -> bool {self.dsl().nil()} fn nil (&self) -> bool {self.dsl().nil()}
fn num (&self) -> Option<usize> {self.dsl().num()} fn num (&self) -> Option<usize> {self.dsl().num()}
fn sym (&self) -> Option<Self::Str> {self.dsl().sym()} fn sym (&self) -> Option<Self::Str> {self.dsl().sym()}
fn key (&self) -> Option<Self::Str> {self.dsl().key()} fn key (&self) -> Option<Self::Str> {self.dsl().key()}
fn str (&self) -> Option<Self::Str> {self.dsl().str()} fn str (&self) -> Option<Self::Str> {self.dsl().str()}
fn exp (&self) -> Option<Self::Exp> {self.dsl().exp()} fn exp (&self) -> Option<Self::Exp> {self.dsl().exp()}
fn exp_depth (&self) -> Option<isize> {self.dsl().exp_depth()} fn exp_depth (&self) -> Option<isize> {self.dsl().exp_depth()}
fn exp_head (&self) -> Val<Self::Str, Self::Exp> {self.dsl().exp_head()} fn exp_head (&self) -> Val<Self::Str, Self::Exp> {self.dsl().exp_head()}
fn exp_tail (&self) -> Val<Self::Str, Self::Exp> {self.dsl().exp_tail()} fn exp_tail (&self) -> Self::Exp {self.dsl().exp_tail()}
fn exp_each (&self, f: impl Fn(&Self) -> Usually<()>) -> Usually<()> { todo!() }
} }
/// The most basic implementor of the [Dsl] trait. /// The most basic implementor of the [Dsl] trait.
impl<Str: DslStr, Exp: DslExp> Dsl for Val<Str, Exp> { impl<Str: DslStr, Exp: DslExp> Dsl for Val<Str, Exp> {
@ -146,25 +138,71 @@ impl<Str: DslStr, Exp: DslExp> Dsl for Val<Str, Exp> {
/// by cloning source slices into owned ([Arc]) string slices. /// by cloning source slices into owned ([Arc]) string slices.
#[derive(Debug, Clone, Default, PartialEq)] #[derive(Debug, Clone, Default, PartialEq)]
pub struct Ast(Arc<VecDeque<Arc<Token<Arc<str>, Ast>>>>); pub struct Ast(Arc<VecDeque<Arc<Token<Arc<str>, Ast>>>>);
pub type AstVal = Val<Arc<str>, Ast>;
pub type AstToken = Token<Arc<str>, Ast>;
impl Dsl for Ast { impl Dsl for Ast {
type Str = Arc<str>; type Exp = Ast; type Str = Arc<str>; type Exp = Ast;
fn dsl (&self) -> Val<Arc<str>, Ast> { Val::Exp(0, Ast(self.0.clone())) } fn dsl (&self) -> Val<Arc<str>, Ast> { Val::Exp(0, Ast(self.0.clone())) }
} }
impl<'s> From<&'s str> for Ast {
fn from (source: &'s str) -> Self {
let source: Arc<str> = source.into();
Self(CstIter(CstConstIter(source.as_ref()))
.map(|token|Arc::new(Token {
source: source.clone(),
start: token.start,
length: token.length,
value: match token.value {
Val::Nil => Val::Nil,
Val::Num(u) => Val::Num(u),
Val::Sym(s) => Val::Sym(s.into()),
Val::Key(s) => Val::Key(s.into()),
Val::Str(s) => Val::Str(s.into()),
Val::Exp(d, x) => Val::Exp(d, x.into()),
Val::Error(e) => Val::Error(e.into())
},
}))
.collect::<VecDeque<_>>()
.into())
}
}
impl<'s> From<Cst<'s>> for Ast {
fn from (cst: Cst<'s>) -> Self {
let mut tokens: VecDeque<_> = Default::default();
Self(tokens.into())
}
}
/// The concrete syntax tree (CST) implements zero-copy /// The concrete syntax tree (CST) implements zero-copy
/// parsing of the DSL from a string reference. CST items /// parsing of the DSL from a string reference. CST items
/// preserve info about their location in the source. /// preserve info about their location in the source.
/// CST stores strings as source references and expressions as [CstIter] instances. /// CST stores strings as source references and expressions as [CstIter] instances.
#[derive(Debug, Copy, Clone, Default, PartialEq)] #[derive(Debug, Copy, Clone, Default, PartialEq)]
pub struct Cst<'s>(pub CstConstIter<'s>); pub struct Cst<'s>(pub CstIter<'s>);
pub type CstVal<'s> = Val<&'s str, Cst<'s>>; pub type CstVal<'s> = Val<&'s str, Cst<'s>>;
pub type CstToken<'s> = Token<&'s str, Cst<'s>>; pub type CstToken<'s> = Token<&'s str, Cst<'s>>;
impl<'s> CstToken<'s> {
pub const fn slice (&self) -> &str {
str_range(self.source, self.start, self.end()) }
pub const fn slice_exp (&self) -> &str {
str_range(self.source, self.start.saturating_add(1), self.end()) }
pub const fn grow (&mut self) -> &mut Self {
let max_length = self.source.len().saturating_sub(self.start);
self.length = self.length + 1;
if self.length > max_length { self.length = max_length }
self
}
pub const fn grow_exp (&'s mut self, depth: isize, source: &'s str) -> &mut Self {
self.value = Val::Exp(depth, Cst(CstIter(CstConstIter(source))));
self
}
}
impl<'s> Dsl for Cst<'s> { impl<'s> Dsl for Cst<'s> {
type Str = &'s str; type Exp = Cst<'s>; type Str = &'s str; type Exp = Cst<'s>;
fn dsl (&self) -> Val<Self::Str, Self::Exp> { Val::Exp(0, Cst(self.0)) } fn dsl (&self) -> Val<Self::Str, Self::Exp> { Val::Exp(0, Cst(self.0)) }
} }
impl<'s> From<&'s str> for Cst<'s> { impl<'s> From<&'s str> for Cst<'s> {
fn from (source: &'s str) -> Self { fn from (source: &'s str) -> Self {
Self(CstConstIter(source)) Self(CstIter(CstConstIter(source)))
} }
} }
/// DSL-specific error codes. /// DSL-specific error codes.
@ -250,7 +288,7 @@ pub const fn peek <'s> (mut value: CstVal<'s>, source: &'s str) -> CstToken<'s>
start = i; start = i;
length = 1; length = 1;
if is_exp_start(c) { if is_exp_start(c) {
value = Exp(1, Cst(CstConstIter(str_range(source, i, i+1)))); value = Exp(1, Cst(CstIter(CstConstIter(str_range(source, i, i+1)))));
} else if is_str_start(c) { } else if is_str_start(c) {
value = Str(str_range(source, i, i+1)); value = Str(str_range(source, i, i+1));
} else if is_sym_start(c) { } else if is_sym_start(c) {
@ -289,17 +327,14 @@ pub const fn peek <'s> (mut value: CstVal<'s>, source: &'s str) -> CstToken<'s>
} }
} else if let Exp(depth, exp) = value { } else if let Exp(depth, exp) = value {
if depth == 0 { if depth == 0 {
value = Exp(0, Cst(CstConstIter(str_range(source, start, start + length)))); value = Exp(0, Cst(CstIter(CstConstIter(str_range(source, start, start + length)))));
break break
} }
length += 1; length += 1;
if c == ')' { value = Exp(
value = Exp(depth-1, Cst(CstConstIter(str_range(source, start, start + length)))); if c == ')' { depth-1 } else if c == '(' { depth+1 } else { depth },
} else if c == '(' { Cst(CstIter(CstConstIter(str_range(source, start, start + length))))
value = Exp(depth+1, Cst(CstConstIter(str_range(source, start, start + length)))); );
} else {
value = Exp(depth, Cst(CstConstIter(str_range(source, start, start + length))));
}
} else if let Num(m) = value { } else if let Num(m) = value {
if is_num_end(c) { if is_num_end(c) {
break break

View file

@ -4,24 +4,64 @@ use crate::*;
/// Each contained layer defines a mapping from input event to command invocation /// Each contained layer defines a mapping from input event to command invocation
/// over a given state. Furthermore, each layer may have an associated cond, /// over a given state. Furthermore, each layer may have an associated cond,
/// so that only certain layers are active at a given time depending on state. /// so that only certain layers are active at a given time depending on state.
#[derive(Debug)] pub struct InputMap<I, D: Dsl>( ///
/// Map of input event (key combination) to /// When a key is pressed, the bindings for it are checked in sequence.
/// When the first non-conditional or true conditional binding is executed,
/// that binding's value is returned.
#[derive(Debug)] pub struct EventMap<E, D: Dsl>(
/// Map of each event (e.g. key combination) to
/// all command expressions bound to it by /// all command expressions bound to it by
/// all loaded input layers. /// all loaded input layers.
pub BTreeMap<I, Vec<InputBinding<D>>> pub EventBindings<E, D>
); );
impl<I, D: Dsl> Default for InputMap<I, D> { type EventBindings<E, D> = BTreeMap<E, Vec<Binding<D>>>;
fn default () -> Self { /// An input binding.
Self(Default::default()) #[derive(Debug, Default)] pub struct Binding<D: Dsl> {
}
}
#[derive(Debug, Default)] pub struct InputBinding<D: Dsl> {
condition: Option<D>, condition: Option<D>,
command: D, command: D,
description: Option<Arc<str>>, description: Option<Arc<str>>,
source: Option<Arc<PathBuf>>, source: Option<Arc<PathBuf>>,
} }
impl<'s, I: Debug + Ord, D: Dsl + From<Cst<'s>>> InputMap<I, D> { impl<E, D: Dsl> Default for EventMap<E, D> {
fn default () -> Self {
Self(Default::default())
}
}
impl<E: Ord + Debug + From<Arc<str>> + Clone> EventMap<E, Ast> {
/// Create input layer collection from DSL.
pub fn new (dsl: Ast) -> Usually<Self> {
use Val::*;
let mut input_map: EventBindings<E, Ast> = Default::default();
match dsl.exp_head() {
Str(path) => {
let path = PathBuf::from(path.as_ref() as &str);
for (key, val) in Self::from_path(&path)?.0.into_iter() {
todo!("import {path:?} {key:?} {val:?}");
let key: E = key.into();
if !input_map.contains_key(&key) {
input_map.insert(key, vec![]);
}
}
},
Sym(s) => {
let key: Arc<str> = s.as_ref().into();
let key: E = key.into();
if !input_map.contains_key(&key) {
input_map.insert(key.clone(), vec![]);
}
todo!("binding {s:?} {:?}", dsl.exp_tail());
},
Key(k) if k.as_ref() == "if" => {
todo!("conditional binding {:?}", dsl.exp_tail());
},
_ => return Err(format!("invalid form in keymap: {dsl:?}").into())
}
Ok(Self(input_map))
}
/// Create input layer collection from string.
pub fn from_source (source: &str) -> Usually<Self> {
Self::new(Ast::from(source))
}
/// Create input layer collection from path to text file. /// Create input layer collection from path to text file.
pub fn from_path <P: Debug + AsRef<Path>> (path: P) -> Usually<Self> { pub fn from_path <P: Debug + AsRef<Path>> (path: P) -> Usually<Self> {
if !exists(path.as_ref())? { if !exists(path.as_ref())? {
@ -29,41 +69,9 @@ impl<'s, I: Debug + Ord, D: Dsl + From<Cst<'s>>> InputMap<I, D> {
} }
Self::from_source(read_and_leak(path)?) Self::from_source(read_and_leak(path)?)
} }
/// Create input layer collection from string.
pub fn from_source (source: &'s str) -> Usually<Self> {
Self::from_dsl(D::from(Cst(CstConstIter(source))))
}
/// Create input layer collection from DSL.
pub fn from_dsl (dsl: D) -> Usually<Self> {
use Val::*;
let mut input_map: BTreeMap<I, Vec<InputBinding<D>>> = Default::default();
match dsl.exp_head() {
Str(path) => {
let path = PathBuf::from(path.as_ref());
for (key, val) in InputMap::<I, D>::from_path(&path)?.0.into_iter() {
todo!("import {path:?} {key:?} {val:?}");
if !input_map.contains_key(&key) {
input_map.insert(key, vec![]);
}
}
},
Sym(sym) => {
//let key: I = sym.into();
//if !input_map.contains_key(&key) {
//input_map.insert(key, vec![]);
//}
todo!("binding {sym:?} {:?}", dsl.exp_tail());
},
Key(s) if s.as_ref() == "if" => {
todo!("conditional binding {:?}", dsl.exp_tail());
},
_ => return Err(format!("invalid form in keymap: {dsl:?}").into())
}
Ok(Self(input_map))
}
/// Evaluate the active layers for a given state, /// Evaluate the active layers for a given state,
/// returning the command to be executed, if any. /// returning the command to be executed, if any.
pub fn handle <S, O> (&self, state: &mut S, input: I) -> Perhaps<O> where pub fn handle <S, O> (&self, state: &mut S, event: Ast) -> Perhaps<O> where
S: DslInto<bool> + DslInto<O>, S: DslInto<bool> + DslInto<O>,
O: Command<S> O: Command<S>
{ {
@ -76,7 +84,7 @@ impl<'s, I: Debug + Ord, D: Dsl + From<Cst<'s>>> InputMap<I, D> {
//} //}
//if matches //if matches
//&& let Some(exp) = bind.val().exp_head() //&& let Some(exp) = bind.val().exp_head()
//&& input.dsl_into(exp, ||format!("InputMap: input.eval(binding) failed").into())? //&& input.dsl_into(exp, ||format!("EventMap: input.eval(binding) failed").into())?
//&& let Some(command) = state.try_dsl_into(exp)? { //&& let Some(command) = state.try_dsl_into(exp)? {
//return Ok(Some(command)) //return Ok(Some(command))
//} //}
@ -112,7 +120,7 @@ impl<'s, I: Debug + Ord, D: Dsl + From<Cst<'s>>> InputMap<I, D> {
//let mut keys = iter.unwrap(); //let mut keys = iter.unwrap();
//let mut map = InputMap::default(); //let mut map = EventMap::default();
//while let Some(token) = keys.next() { //while let Some(token) = keys.next() {
//if let Value::Exp(_, mut exp) = token.value { //if let Value::Exp(_, mut exp) = token.value {
//let next = exp.next(); //let next = exp.next();

View file

@ -3,6 +3,9 @@
#![feature(impl_trait_in_assoc_type)] #![feature(impl_trait_in_assoc_type)]
pub(crate) use std::marker::PhantomData; pub(crate) use std::marker::PhantomData;
pub(crate) use std::fmt::{Debug, Display};
pub(crate) use std::ops::{Add, Sub, Mul, Div};
pub(crate) use std::sync::{Arc, atomic::{AtomicUsize, Ordering::Relaxed}};
pub(crate) use tengri_core::*; pub(crate) use tengri_core::*;
#[cfg(feature = "dsl")] pub(crate) use ::tengri_dsl::*; #[cfg(feature = "dsl")] pub(crate) use ::tengri_dsl::*;

View file

@ -48,32 +48,21 @@
use crate::*; use crate::*;
use Direction::*; use Direction::*;
use std::marker::PhantomData;
use std::sync::{Arc, RwLock};
mod map; pub use self::map::*; /// Stack things on top of each other,
mod memo; pub use self::memo::*; #[macro_export] macro_rules! lay (($($expr:expr),* $(,)?) =>
mod stack; pub use self::stack::*; {{ let bsp = (); $(let bsp = Bsp::b(bsp, $expr);)*; bsp }});
mod thunk; pub use self::thunk::*;
/// Renders multiple things on top of each other,
#[macro_export] macro_rules! lay {
($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::b(bsp, $expr);)*; bsp }}
}
/// Stack southward. /// Stack southward.
#[macro_export] macro_rules! col { #[macro_export] macro_rules! col (($($expr:expr),* $(,)?) =>
($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::s(bsp, $expr);)*; bsp }}; {{ let bsp = (); $(let bsp = Bsp::s(bsp, $expr);)*; bsp }});
}
/// Stack northward. /// Stack northward.
#[macro_export] macro_rules! col_up { #[macro_export] macro_rules! col_up (($($expr:expr),* $(,)?) =>
($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::n(bsp, $expr);)*; bsp }} {{ let bsp = (); $(let bsp = Bsp::n(bsp, $expr);)*; bsp }});
}
/// Stack eastward. /// Stack eastward.
#[macro_export] macro_rules! row { #[macro_export] macro_rules! row (($($expr:expr),* $(,)?) =>
($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::e(bsp, $expr);)*; bsp }}; {{ let bsp = (); $(let bsp = Bsp::e(bsp, $expr);)*; bsp }});
}
/// Show an item only when a condition is true. /// Show an item only when a condition is true.
pub struct When<A>(pub bool, pub A); pub struct When<A>(pub bool, pub A);
impl<A> When<A> { impl<A> When<A> {
@ -686,3 +675,353 @@ transform_xy_unit!("padding/x" "padding/y" "padding/xy"|self: Padding, area|{
//_ => None //_ => None
//}) //})
} }
/// Lazily-evaluated [Render]able.
pub struct Thunk<E: Output, T: Render<E>, F: Fn()->T>(
PhantomData<E>,
F
);
impl<E: Output, T: Render<E>, F: Fn()->T> Thunk<E, T, F> {
pub const fn new (thunk: F) -> Self {
Self(PhantomData, thunk)
}
}
impl<E: Output, T: Render<E>, F: Fn()->T> Content<E> for Thunk<E, T, F> {
fn content (&self) -> impl Render<E> { (self.1)() }
}
pub struct ThunkBox<E: Output>(
PhantomData<E>,
Box<dyn Fn()->Box<dyn Render<E>>>,
);
impl<E: Output> ThunkBox<E> {
pub const fn new (thunk: Box<dyn Fn()->Box<dyn Render<E>>>) -> Self {
Self(PhantomData, thunk)
}
}
impl<E: Output> Content<E> for ThunkBox<E> {
fn content (&self) -> impl Render<E> { (&self.1)() }
}
impl<E: Output> From<Box<dyn Fn()->Box<dyn Render<E>>>> for ThunkBox<E> {
fn from (f: Box<dyn Fn()->Box<dyn Render<E>>>) -> Self {
Self(PhantomData, f)
}
}
//impl<'a, E: Output, F: Fn()->Box<dyn Render<E> + 'a> + 'a> From<F> for ThunkBox<'a, E> {
//fn from (f: F) -> Self {
//Self(Default::default(), Box::new(f))
//}
//}
pub struct ThunkRender<E: Output, F: Fn(&mut E)>(PhantomData<E>, F);
impl<E: Output, F: Fn(&mut E)> ThunkRender<E, F> {
pub fn new (render: F) -> Self { Self(PhantomData, render) }
}
impl<E: Output, F: Fn(&mut E)> Content<E> for ThunkRender<E, F> {
fn render (&self, to: &mut E) { (self.1)(to) }
}
pub struct ThunkLayout<
E: Output,
F1: Fn(E::Area)->E::Area,
F2: Fn(&mut E)
>(
PhantomData<E>,
F1,
F2
);
impl<E: Output, F1: Fn(E::Area)->E::Area, F2: Fn(&mut E)> ThunkLayout<E, F1, F2> {
pub fn new (layout: F1, render: F2) -> Self { Self(PhantomData, layout, render) }
}
impl<E, F1, F2> Content<E> for ThunkLayout<E, F1, F2>
where
E: Output,
F1: Fn(E::Area)->E::Area,
F2: Fn(&mut E)
{
fn layout (&self, to: E::Area) -> E::Area { (self.1)(to) }
fn render (&self, to: &mut E) { (self.2)(to) }
}
#[derive(Debug, Default)] pub struct Memo<T, U> {
pub value: T,
pub view: Arc<RwLock<U>>
}
impl<T: PartialEq, U> Memo<T, U> {
pub fn new (value: T, view: U) -> Self {
Self { value, view: Arc::new(view.into()) }
}
pub fn update <R> (
&mut self,
newval: T,
render: impl Fn(&mut U, &T, &T)->R
) -> Option<R> {
if newval != self.value {
let result = render(&mut*self.view.write().unwrap(), &newval, &self.value);
self.value = newval;
return Some(result);
}
None
}
}
/// Clear a pre-allocated buffer, then write into it.
#[macro_export] macro_rules! rewrite {
($buf:ident, $($rest:tt)*) => { |$buf,_,_|{ $buf.clear(); write!($buf, $($rest)*) } }
}
pub struct Stack<E, F> {
__: PhantomData<E>,
direction: Direction,
callback: F
}
impl<E, F> Stack<E, F> {
pub fn new (direction: Direction, callback: F) -> Self {
Self { direction, callback, __: Default::default(), }
}
pub fn north (callback: F) -> Self {
Self::new(North, callback)
}
pub fn south (callback: F) -> Self {
Self::new(South, callback)
}
pub fn east (callback: F) -> Self {
Self::new(East, callback)
}
pub fn west (callback: F) -> Self {
Self::new(West, callback)
}
}
impl<E: Output, F: Fn(&mut dyn FnMut(&dyn Render<E>)->())->()> Content<E> for Stack<E, F> {
fn layout (&self, to: E::Area) -> E::Area {
let mut x = to.x();
let mut y = to.y();
let (mut w_used, mut w_remaining) = (E::Unit::zero(), to.w());
let (mut h_used, mut h_remaining) = (E::Unit::zero(), to.h());
(self.callback)(&mut move |component: &dyn Render<E>|{
let [_, _, w, h] = component.layout([x, y, w_remaining, h_remaining].into()).xywh();
match self.direction {
South => {
y = y.plus(h);
h_used = h_used.plus(h);
h_remaining = h_remaining.minus(h);
w_used = w_used.max(w);
},
East => {
x = x.plus(w);
w_used = w_used.plus(w);
w_remaining = w_remaining.minus(w);
h_used = h_used.max(h);
},
North | West => {
todo!()
},
_ => unreachable!(),
}
});
match self.direction {
North | West => {
todo!()
},
South | East => {
[to.x(), to.y(), w_used.into(), h_used.into()].into()
},
_ => unreachable!(),
}
}
fn render (&self, to: &mut E) {
let mut x = to.x();
let mut y = to.y();
let (mut w_used, mut w_remaining) = (E::Unit::zero(), to.w());
let (mut h_used, mut h_remaining) = (E::Unit::zero(), to.h());
(self.callback)(&mut move |component: &dyn Render<E>|{
let layout = component.layout([x, y, w_remaining, h_remaining].into());
match self.direction {
South => {
y = y.plus(layout.h());
h_remaining = h_remaining.minus(layout.h());
h_used = h_used.plus(layout.h());
to.place(layout, component);
},
East => {
x = x.plus(layout.w());
w_remaining = w_remaining.minus(layout.w());
w_used = w_used.plus(layout.h());
to.place(layout, component);
},
North | West => {
todo!()
},
_ => unreachable!()
}
});
}
}
/*Stack::down(|add|{
let mut i = 0;
for (_, name) in self.dirs.iter() {
if i >= self.scroll {
add(&Tui::bold(i == self.index, name.as_str()))?;
}
i += 1;
}
for (_, name) in self.files.iter() {
if i >= self.scroll {
add(&Tui::bold(i == self.index, name.as_str()))?;
}
i += 1;
}
add(&format!("{}/{i}", self.index))?;
Ok(())
}));*/
/// Renders items from an iterator.
pub struct Map<E, A, B, I, F, G>
where
I: Iterator<Item = A> + Send + Sync,
F: Fn() -> I + Send + Sync,
{
__: PhantomData<(E, B)>,
/// Function that returns iterator over stacked components
get_iter: F,
/// Function that returns each stacked component
get_item: G,
}
impl<'a, E, A, B, I, F, G> Map<E, A, B, I, F, G> where
I: Iterator<Item = A> + Send + Sync + 'a,
F: Fn() -> I + Send + Sync + 'a,
{
pub const fn new (get_iter: F, get_item: G) -> Self {
Self {
__: PhantomData,
get_iter,
get_item
}
}
}
impl<'a, E, A, B, I, F> Map<E, A, Push<E::Unit, Align<Fixed<E::Unit, Fill<B>>>>, I, F, fn(A, usize)->B>
where
E: Output,
B: Render<E>,
I: Iterator<Item = A> + Send + Sync + 'a,
F: Fn() -> I + Send + Sync + 'a
{
pub const fn east (
size: E::Unit,
get_iter: F,
get_item: impl Fn(A, usize)->B + Send + Sync
) -> Map<
E, A,
Push<E::Unit, Align<Fixed<E::Unit, B>>>,
I, F,
impl Fn(A, usize)->Push<E::Unit, Align<Fixed<E::Unit, B>>> + Send + Sync
> {
Map {
__: PhantomData,
get_iter,
get_item: move |item: A, index: usize|{
// FIXME: multiply
let mut push: E::Unit = E::Unit::from(0u16);
for _ in 0..index {
push = push + size;
}
Push::x(push, Align::w(Fixed::x(size, get_item(item, index))))
}
}
}
pub const fn south (
size: E::Unit,
get_iter: F,
get_item: impl Fn(A, usize)->B + Send + Sync
) -> Map<
E, A,
Push<E::Unit, Align<Fixed<E::Unit, B>>>,
I, F,
impl Fn(A, usize)->Push<E::Unit, Align<Fixed<E::Unit, B>>> + Send + Sync
> where
E: Output,
B: Render<E>,
I: Iterator<Item = A> + Send + Sync + 'a,
F: Fn() -> I + Send + Sync + 'a
{
Map {
__: PhantomData,
get_iter,
get_item: move |item: A, index: usize|{
// FIXME: multiply
let mut push: E::Unit = E::Unit::from(0u16);
for _ in 0..index {
push = push + size;
}
Push::y(push, Align::n(Fixed::y(size, get_item(item, index))))
}
}
}
}
impl<'a, E, A, B, I, F, G> Content<E> for Map<E, A, B, I, F, G> where
E: Output,
B: Render<E>,
I: Iterator<Item = A> + Send + Sync + 'a,
F: Fn() -> I + Send + Sync + 'a,
G: Fn(A, usize)->B + Send + Sync
{
fn layout (&self, area: E::Area) -> E::Area {
let Self { get_iter, get_item, .. } = self;
let mut index = 0;
let [mut min_x, mut min_y] = area.center();
let [mut max_x, mut max_y] = area.center();
for item in get_iter() {
let [x,y,w,h] = get_item(item, index).layout(area).xywh();
min_x = min_x.min(x.into());
min_y = min_y.min(y.into());
max_x = max_x.max((x + w).into());
max_y = max_y.max((y + h).into());
index += 1;
}
let w = max_x - min_x;
let h = max_y - min_y;
//[min_x.into(), min_y.into(), w.into(), h.into()].into()
area.center_xy([w.into(), h.into()].into()).into()
}
fn render (&self, to: &mut E) {
let Self { get_iter, get_item, .. } = self;
let mut index = 0;
let area = Content::layout(self, to.area());
for item in get_iter() {
let item = get_item(item, index);
//to.place(area.into(), &item);
to.place(item.layout(area), &item);
index += 1;
}
}
}
#[inline] pub fn map_south<O: Output>(
item_offset: O::Unit,
item_height: O::Unit,
item: impl Content<O>
) -> impl Content<O> {
Push::y(item_offset, Fixed::y(item_height, Fill::x(item)))
}
#[inline] pub fn map_south_west<O: Output>(
item_offset: O::Unit,
item_height: O::Unit,
item: impl Content<O>
) -> impl Content<O> {
Push::y(item_offset, Align::nw(Fixed::y(item_height, Fill::x(item))))
}
#[inline] pub fn map_east<O: Output>(
item_offset: O::Unit,
item_width: O::Unit,
item: impl Content<O>
) -> impl Content<O> {
Push::x(item_offset, Align::w(Fixed::x(item_width, Fill::y(item))))
}

View file

@ -1,23 +0,0 @@
//! Groupings of elements.
use crate::*;
/// A function or closure that emits renderables.
pub trait Collector<E: Engine>: Send + Sync + Fn(&mut dyn FnMut(&dyn Render<E>)) {}
/// Any function or closure that emits renderables for the given engine matches [CollectCallback].
impl<E, F> Collector<E> for F
where E: Engine, F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render<E>)) {}
pub trait Render<E: Engine> {
fn area (&self, to: E::Area) -> E::Area;
fn render (&self, to: &mut E::Output);
}
impl<E: Engine, C: Content<E>> Render<E> for C {
fn area (&self, to: E::Area) -> E::Area {
Content::area(self, to)
}
fn render (&self, to: &mut E::Output) {
Content::render(self, to)
}
}

View file

@ -1,93 +0,0 @@
use crate::*;
pub struct Reduce<A, B, I, F, G>(pub PhantomData<A>, pub F, pub G) where
A: Send + Sync, B: Send + Sync,
I: Iterator<Item = B> + Send + Sync,
F: Fn() -> I + Send + Sync,
G: Fn(A, B, usize)->A + Send + Sync;
impl<A, B, I, F, G> Reduce<A, B, I, F, G> where
A: Send + Sync, B: Send + Sync,
I: Iterator<Item = B> + Send + Sync,
F: Fn() -> I + Send + Sync,
G: Fn(A, B, usize)->A + Send + Sync
{
pub const fn new (f: F, g: G) -> Self { Self(Default::default(), f, g) }
}
impl<E: Output, A, B, I, F, G> Content<E> for Reduce<A, B, I, F, G> where
A: Send + Sync, B: Send + Sync,
I: Iterator<Item = B> + Send + Sync,
F: Fn() -> I + Send + Sync,
G: Fn(A, B, usize)->A + Send + Sync
{
fn content (&self) -> impl Render<E> {
}
}
/*
//pub fn reduce <E, T, I, R, F>(iterator: I, callback: F) -> Reduce<E, T, I, R, F> where
//E: Output,
//I: Iterator<Item = T> + Send + Sync,
//R: Render<E>,
//F: Fn(R, T, usize) -> R + Send + Sync
//{
//Reduce(Default::default(), iterator, callback)
//}
pub struct Reduce<E, T, I, R, F>(PhantomData<(E, R)>, I, F) where
E: Output,
I: Iterator<Item = T> + Send + Sync,
R: Render<E>,
F: Fn(R, T, usize) -> R + Send + Sync;
impl<E, T, I, R, F> Content<E> for Reduce<E, T, I, R, F> where
E: Output,
I: Iterator<Item = T> + Send + Sync,
R: Render<E>,
F: Fn(R, T, usize) -> R + Send + Sync
{
fn render (&self, to: &mut E) {
todo!()
}
}
*/
//macro_rules! define_ops {
//($Trait:ident<$E:ident:$Output:path> { $(
//$(#[$attr:meta $($attr_args:tt)*])*
//(
//$fn:ident
//$(<$($G:ident$(:$Gen:path)?, )+>)?
//$Op:ident
//($($arg:ident:$Arg:ty),*)
//)
//)* }) => {
//impl<$E: $Output> $Trait<E> for E {}
//pub trait $Trait<$E: $Output> {
//$(
//$(#[$attr $($attr_args)*])*
//fn $fn $(<$($G),+>)?
//($($arg:$Arg),*)-> $Op<$($(, $G)+)?>
//$(where $($G: $($Gen + Send + Sync)?),+)?
//{ $Op($($arg),*) }
//)*
//}
//}
//}
//define_ops! {
//Layout<E: Output> {
//(when <A: Render<E>,>
//When(cond: bool, item: A))
///// When `cond` is `true`, render `a`, otherwise render `b`.
//(either <A: Render<E>, B: Render<E>,>
//Either(cond: bool, a: A, b: B))
///// If `opt` is `Some(T)` renders `cb(t)`, otherwise nothing.
//(opt <A, F: Fn(A) -> B, B: Render<E>,>
//Opt(option: Option<A>, cb: F))
///// Maps items of iterator through callback.
//(map <A, B: Render<E>, I: Iterator<Item = A>, F: Fn() -> I, G: Fn(A, usize)->B,>
//Map(get_iterator: F, callback: G))
//}
//}

View file

@ -1,150 +0,0 @@
use crate::*;
/// Renders items from an iterator.
pub struct Map<E, A, B, I, F, G>
where
I: Iterator<Item = A> + Send + Sync,
F: Fn() -> I + Send + Sync,
{
__: PhantomData<(E, B)>,
/// Function that returns iterator over stacked components
get_iter: F,
/// Function that returns each stacked component
get_item: G,
}
impl<'a, E, A, B, I, F, G> Map<E, A, B, I, F, G> where
I: Iterator<Item = A> + Send + Sync + 'a,
F: Fn() -> I + Send + Sync + 'a,
{
pub const fn new (get_iter: F, get_item: G) -> Self {
Self {
__: PhantomData,
get_iter,
get_item
}
}
}
impl<'a, E, A, B, I, F> Map<E, A, Push<E::Unit, Align<Fixed<E::Unit, Fill<B>>>>, I, F, fn(A, usize)->B>
where
E: Output,
B: Render<E>,
I: Iterator<Item = A> + Send + Sync + 'a,
F: Fn() -> I + Send + Sync + 'a
{
pub const fn east (
size: E::Unit,
get_iter: F,
get_item: impl Fn(A, usize)->B + Send + Sync
) -> Map<
E, A,
Push<E::Unit, Align<Fixed<E::Unit, B>>>,
I, F,
impl Fn(A, usize)->Push<E::Unit, Align<Fixed<E::Unit, B>>> + Send + Sync
> {
Map {
__: PhantomData,
get_iter,
get_item: move |item: A, index: usize|{
// FIXME: multiply
let mut push: E::Unit = E::Unit::from(0u16);
for _ in 0..index {
push = push + size;
}
Push::x(push, Align::w(Fixed::x(size, get_item(item, index))))
}
}
}
pub const fn south (
size: E::Unit,
get_iter: F,
get_item: impl Fn(A, usize)->B + Send + Sync
) -> Map<
E, A,
Push<E::Unit, Align<Fixed<E::Unit, B>>>,
I, F,
impl Fn(A, usize)->Push<E::Unit, Align<Fixed<E::Unit, B>>> + Send + Sync
> where
E: Output,
B: Render<E>,
I: Iterator<Item = A> + Send + Sync + 'a,
F: Fn() -> I + Send + Sync + 'a
{
Map {
__: PhantomData,
get_iter,
get_item: move |item: A, index: usize|{
// FIXME: multiply
let mut push: E::Unit = E::Unit::from(0u16);
for _ in 0..index {
push = push + size;
}
Push::y(push, Align::n(Fixed::y(size, get_item(item, index))))
}
}
}
}
impl<'a, E, A, B, I, F, G> Content<E> for Map<E, A, B, I, F, G> where
E: Output,
B: Render<E>,
I: Iterator<Item = A> + Send + Sync + 'a,
F: Fn() -> I + Send + Sync + 'a,
G: Fn(A, usize)->B + Send + Sync
{
fn layout (&self, area: E::Area) -> E::Area {
let Self { get_iter, get_item, .. } = self;
let mut index = 0;
let [mut min_x, mut min_y] = area.center();
let [mut max_x, mut max_y] = area.center();
for item in get_iter() {
let [x,y,w,h] = get_item(item, index).layout(area).xywh();
min_x = min_x.min(x.into());
min_y = min_y.min(y.into());
max_x = max_x.max((x + w).into());
max_y = max_y.max((y + h).into());
index += 1;
}
let w = max_x - min_x;
let h = max_y - min_y;
//[min_x.into(), min_y.into(), w.into(), h.into()].into()
area.center_xy([w.into(), h.into()].into()).into()
}
fn render (&self, to: &mut E) {
let Self { get_iter, get_item, .. } = self;
let mut index = 0;
let area = Content::layout(self, to.area());
for item in get_iter() {
let item = get_item(item, index);
//to.place(area.into(), &item);
to.place(item.layout(area), &item);
index += 1;
}
}
}
#[inline] pub fn map_south<O: Output>(
item_offset: O::Unit,
item_height: O::Unit,
item: impl Content<O>
) -> impl Content<O> {
Push::y(item_offset, Fixed::y(item_height, Fill::x(item)))
}
#[inline] pub fn map_south_west<O: Output>(
item_offset: O::Unit,
item_height: O::Unit,
item: impl Content<O>
) -> impl Content<O> {
Push::y(item_offset, Align::nw(Fixed::y(item_height, Fill::x(item))))
}
#[inline] pub fn map_east<O: Output>(
item_offset: O::Unit,
item_width: O::Unit,
item: impl Content<O>
) -> impl Content<O> {
Push::x(item_offset, Align::w(Fixed::x(item_width, Fill::y(item))))
}

View file

@ -1,30 +0,0 @@
//use crate::*;
use std::sync::{Arc, RwLock};
#[derive(Debug, Default)] pub struct Memo<T, U> {
pub value: T,
pub view: Arc<RwLock<U>>
}
impl<T: PartialEq, U> Memo<T, U> {
pub fn new (value: T, view: U) -> Self {
Self { value, view: Arc::new(view.into()) }
}
pub fn update <R> (
&mut self,
newval: T,
render: impl Fn(&mut U, &T, &T)->R
) -> Option<R> {
if newval != self.value {
let result = render(&mut*self.view.write().unwrap(), &newval, &self.value);
self.value = newval;
return Some(result);
}
None
}
}
/// Clear a pre-allocated buffer, then write into it.
#[macro_export] macro_rules! rewrite {
($buf:ident, $($rest:tt)*) => { |$buf,_,_|{ $buf.clear(); write!($buf, $($rest)*) } }
}

View file

@ -1,109 +0,0 @@
use crate::*;
use Direction::*;
pub struct Stack<E, F> {
__: PhantomData<E>,
direction: Direction,
callback: F
}
impl<E, F> Stack<E, F> {
pub fn new (direction: Direction, callback: F) -> Self {
Self { direction, callback, __: Default::default(), }
}
pub fn north (callback: F) -> Self {
Self::new(North, callback)
}
pub fn south (callback: F) -> Self {
Self::new(South, callback)
}
pub fn east (callback: F) -> Self {
Self::new(East, callback)
}
pub fn west (callback: F) -> Self {
Self::new(West, callback)
}
}
impl<E: Output, F: Fn(&mut dyn FnMut(&dyn Render<E>)->())->()> Content<E> for Stack<E, F> {
fn layout (&self, to: E::Area) -> E::Area {
let mut x = to.x();
let mut y = to.y();
let (mut w_used, mut w_remaining) = (E::Unit::zero(), to.w());
let (mut h_used, mut h_remaining) = (E::Unit::zero(), to.h());
(self.callback)(&mut move |component: &dyn Render<E>|{
let [_, _, w, h] = component.layout([x, y, w_remaining, h_remaining].into()).xywh();
match self.direction {
South => {
y = y.plus(h);
h_used = h_used.plus(h);
h_remaining = h_remaining.minus(h);
w_used = w_used.max(w);
},
East => {
x = x.plus(w);
w_used = w_used.plus(w);
w_remaining = w_remaining.minus(w);
h_used = h_used.max(h);
},
North | West => {
todo!()
},
_ => unreachable!(),
}
});
match self.direction {
North | West => {
todo!()
},
South | East => {
[to.x(), to.y(), w_used.into(), h_used.into()].into()
},
_ => unreachable!(),
}
}
fn render (&self, to: &mut E) {
let mut x = to.x();
let mut y = to.y();
let (mut w_used, mut w_remaining) = (E::Unit::zero(), to.w());
let (mut h_used, mut h_remaining) = (E::Unit::zero(), to.h());
(self.callback)(&mut move |component: &dyn Render<E>|{
let layout = component.layout([x, y, w_remaining, h_remaining].into());
match self.direction {
South => {
y = y.plus(layout.h());
h_remaining = h_remaining.minus(layout.h());
h_used = h_used.plus(layout.h());
to.place(layout, component);
},
East => {
x = x.plus(layout.w());
w_remaining = w_remaining.minus(layout.w());
w_used = w_used.plus(layout.h());
to.place(layout, component);
},
North | West => {
todo!()
},
_ => unreachable!()
}
});
}
}
/*Stack::down(|add|{
let mut i = 0;
for (_, name) in self.dirs.iter() {
if i >= self.scroll {
add(&Tui::bold(i == self.index, name.as_str()))?;
}
i += 1;
}
for (_, name) in self.files.iter() {
if i >= self.scroll {
add(&Tui::bold(i == self.index, name.as_str()))?;
}
i += 1;
}
add(&format!("{}/{i}", self.index))?;
Ok(())
}));*/

View file

@ -1,69 +0,0 @@
use crate::*;
use std::marker::PhantomData;
/// Lazily-evaluated [Render]able.
pub struct Thunk<E: Output, T: Render<E>, F: Fn()->T>(
PhantomData<E>,
F
);
impl<E: Output, T: Render<E>, F: Fn()->T> Thunk<E, T, F> {
pub const fn new (thunk: F) -> Self {
Self(PhantomData, thunk)
}
}
impl<E: Output, T: Render<E>, F: Fn()->T> Content<E> for Thunk<E, T, F> {
fn content (&self) -> impl Render<E> { (self.1)() }
}
pub struct ThunkBox<E: Output>(
PhantomData<E>,
Box<dyn Fn()->Box<dyn Render<E>>>,
);
impl<E: Output> ThunkBox<E> {
pub const fn new (thunk: Box<dyn Fn()->Box<dyn Render<E>>>) -> Self {
Self(PhantomData, thunk)
}
}
impl<E: Output> Content<E> for ThunkBox<E> {
fn content (&self) -> impl Render<E> { (&self.1)() }
}
impl<E: Output> From<Box<dyn Fn()->Box<dyn Render<E>>>> for ThunkBox<E> {
fn from (f: Box<dyn Fn()->Box<dyn Render<E>>>) -> Self {
Self(PhantomData, f)
}
}
//impl<'a, E: Output, F: Fn()->Box<dyn Render<E> + 'a> + 'a> From<F> for ThunkBox<'a, E> {
//fn from (f: F) -> Self {
//Self(Default::default(), Box::new(f))
//}
//}
pub struct ThunkRender<E: Output, F: Fn(&mut E)>(PhantomData<E>, F);
impl<E: Output, F: Fn(&mut E)> ThunkRender<E, F> {
pub fn new (render: F) -> Self { Self(PhantomData, render) }
}
impl<E: Output, F: Fn(&mut E)> Content<E> for ThunkRender<E, F> {
fn render (&self, to: &mut E) { (self.1)(to) }
}
pub struct ThunkLayout<
E: Output,
F1: Fn(E::Area)->E::Area,
F2: Fn(&mut E)
>(
PhantomData<E>,
F1,
F2
);
impl<E: Output, F1: Fn(E::Area)->E::Area, F2: Fn(&mut E)> ThunkLayout<E, F1, F2> {
pub fn new (layout: F1, render: F2) -> Self { Self(PhantomData, layout, render) }
}
impl<E, F1, F2> Content<E> for ThunkLayout<E, F1, F2>
where
E: Output,
F1: Fn(E::Area)->E::Area,
F2: Fn(&mut E)
{
fn layout (&self, to: E::Area) -> E::Area { (self.1)(to) }
fn render (&self, to: &mut E) { (self.2)(to) }
}

View file

@ -1,5 +1,274 @@
mod area; pub use self::area::*; use crate::*;
mod coordinate; pub use self::coordinate::*; use Direction::*;
mod direction; pub use self::direction::*;
mod measure; pub use self::measure::*; /// A cardinal direction.
mod size; pub use self::size::*; #[derive(Copy, Clone, PartialEq, Debug)]
#[cfg_attr(test, derive(Arbitrary))]
pub enum Direction {
North, South, East, West, Above, Below
}
impl Direction {
pub fn split_fixed <N: Coordinate> (self, area: impl Area<N>, a: N) -> ([N;4],[N;4]) {
let [x, y, w, h] = area.xywh();
match self {
North => ([x, y.plus(h).minus(a), w, a], [x, y, w, h.minus(a)]),
South => ([x, y, w, a], [x, y.plus(a), w, h.minus(a)]),
East => ([x, y, a, h], [x.plus(a), y, w.minus(a), h]),
West => ([x.plus(w).minus(a), y, a, h], [x, y, w.minus(a), h]),
Above | Below => (area.xywh(), area.xywh())
}
}
}
/// A linear coordinate.
pub trait Coordinate: Send + Sync + Copy
+ Add<Self, Output=Self>
+ Sub<Self, Output=Self>
+ Mul<Self, Output=Self>
+ Div<Self, Output=Self>
+ Ord + PartialEq + Eq
+ Debug + Display + Default
+ From<u16> + Into<u16>
+ Into<usize>
+ Into<f64>
{
fn zero () -> Self { 0.into() }
fn plus (self, other: Self) -> Self;
fn minus (self, other: Self) -> Self {
if self >= other {
self - other
} else {
0.into()
}
}
}
impl Coordinate for u16 {
fn plus (self, other: Self) -> Self {
self.saturating_add(other)
}
}
pub trait Area<N: Coordinate>: From<[N;4]> + Debug + Copy {
fn x (&self) -> N;
fn y (&self) -> N;
fn w (&self) -> N;
fn h (&self) -> N;
fn zero () -> [N;4] {
[N::zero(), N::zero(), N::zero(), N::zero()]
}
fn from_position (pos: impl Size<N>) -> [N;4] {
let [x, y] = pos.wh();
[x, y, 0.into(), 0.into()]
}
fn from_size (size: impl Size<N>) -> [N;4] {
let [w, h] = size.wh();
[0.into(), 0.into(), w, h]
}
fn expect_min (&self, w: N, h: N) -> Usually<&Self> {
if self.w() < w || self.h() < h {
Err(format!("min {w}x{h}").into())
} else {
Ok(self)
}
}
fn xy (&self) -> [N;2] {
[self.x(), self.y()]
}
fn wh (&self) -> [N;2] {
[self.w(), self.h()]
}
fn xywh (&self) -> [N;4] {
[self.x(), self.y(), self.w(), self.h()]
}
fn clip_h (&self, h: N) -> [N;4] {
[self.x(), self.y(), self.w(), self.h().min(h)]
}
fn clip_w (&self, w: N) -> [N;4] {
[self.x(), self.y(), self.w().min(w), self.h()]
}
fn clip (&self, wh: impl Size<N>) -> [N;4] {
[self.x(), self.y(), wh.w(), wh.h()]
}
fn set_w (&self, w: N) -> [N;4] {
[self.x(), self.y(), w, self.h()]
}
fn set_h (&self, h: N) -> [N;4] {
[self.x(), self.y(), self.w(), h]
}
fn x2 (&self) -> N {
self.x().plus(self.w())
}
fn y2 (&self) -> N {
self.y().plus(self.h())
}
fn lrtb (&self) -> [N;4] {
[self.x(), self.x2(), self.y(), self.y2()]
}
fn center (&self) -> [N;2] {
[self.x().plus(self.w()/2.into()), self.y().plus(self.h()/2.into())]
}
fn center_x (&self, n: N) -> [N;4] {
let [x, y, w, h] = self.xywh();
[(x.plus(w / 2.into())).minus(n / 2.into()), y.plus(h / 2.into()), n, 1.into()]
}
fn center_y (&self, n: N) -> [N;4] {
let [x, y, w, h] = self.xywh();
[x.plus(w / 2.into()), (y.plus(h / 2.into())).minus(n / 2.into()), 1.into(), n]
}
fn center_xy (&self, [n, m]: [N;2]) -> [N;4] {
let [x, y, w, h] = self.xywh();
[(x.plus(w / 2.into())).minus(n / 2.into()), (y.plus(h / 2.into())).minus(m / 2.into()), n, m]
}
fn centered (&self) -> [N;2] {
[self.x().minus(self.w()/2.into()), self.y().minus(self.h()/2.into())]
}
fn iter_x (&self) -> impl Iterator<Item = N> where N: std::iter::Step {
self.x()..(self.x()+self.w())
}
fn iter_y (&self) -> impl Iterator<Item = N> where N: std::iter::Step {
self.y()..(self.y()+self.h())
}
}
impl<N: Coordinate> Area<N> for (N, N, N, N) {
fn x (&self) -> N { self.0 }
fn y (&self) -> N { self.1 }
fn w (&self) -> N { self.2 }
fn h (&self) -> N { self.3 }
}
impl<N: Coordinate> Area<N> for [N;4] {
fn x (&self) -> N { self[0] }
fn y (&self) -> N { self[1] }
fn w (&self) -> N { self[2] }
fn h (&self) -> N { self[3] }
}
pub trait Size<N: Coordinate>: From<[N;2]> + Debug + Copy {
fn x (&self) -> N;
fn y (&self) -> N;
fn w (&self) -> N { self.x() }
fn h (&self) -> N { self.y() }
fn wh (&self) -> [N;2] { [self.x(), self.y()] }
fn clip_w (&self, w: N) -> [N;2] { [self.w().min(w), self.h()] }
fn clip_h (&self, h: N) -> [N;2] { [self.w(), self.h().min(h)] }
fn expect_min (&self, w: N, h: N) -> Usually<&Self> {
if self.w() < w || self.h() < h {
Err(format!("min {w}x{h}").into())
} else {
Ok(self)
}
}
fn zero () -> [N;2] {
[N::zero(), N::zero()]
}
fn to_area_pos (&self) -> [N;4] {
let [x, y] = self.wh();
[x, y, 0.into(), 0.into()]
}
fn to_area_size (&self) -> [N;4] {
let [w, h] = self.wh();
[0.into(), 0.into(), w, h]
}
}
impl<N: Coordinate> Size<N> for (N, N) {
fn x (&self) -> N { self.0 }
fn y (&self) -> N { self.1 }
}
impl<N: Coordinate> Size<N> for [N;2] {
fn x (&self) -> N { self[0] }
fn y (&self) -> N { self[1] }
}
pub trait HasSize<E: Output> {
fn size (&self) -> &Measure<E>;
fn width (&self) -> usize {
self.size().w()
}
fn height (&self) -> usize {
self.size().h()
}
}
impl<E: Output, T: Has<Measure<E>>> HasSize<E> for T {
fn size (&self) -> &Measure<E> {
self.get()
}
}
/// A widget that tracks its render width and height
#[derive(Default)]
pub struct Measure<E: Output> {
_engine: PhantomData<E>,
pub x: Arc<AtomicUsize>,
pub y: Arc<AtomicUsize>,
}
// TODO: 🡘 🡙 ←🡙→ indicator to expand window when too small
impl<E: Output> Content<E> for Measure<E> {
fn render (&self, to: &mut E) {
self.x.store(to.area().w().into(), Relaxed);
self.y.store(to.area().h().into(), Relaxed);
}
}
impl<E: Output> Clone for Measure<E> {
fn clone (&self) -> Self {
Self {
_engine: Default::default(),
x: self.x.clone(),
y: self.y.clone(),
}
}
}
impl<E: Output> std::fmt::Debug for Measure<E> {
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
f.debug_struct("Measure")
.field("width", &self.x)
.field("height", &self.y)
.finish()
}
}
impl<E: Output> Measure<E> {
pub fn new () -> Self {
Self {
_engine: PhantomData::default(),
x: Arc::new(0.into()),
y: Arc::new(0.into()),
}
}
pub fn set_w (&self, w: impl Into<usize>) -> &Self {
self.x.store(w.into(), Relaxed);
self
}
pub fn set_h (&self, h: impl Into<usize>) -> &Self {
self.y.store(h.into(), Relaxed);
self
}
pub fn set_wh (&self, w: impl Into<usize>, h: impl Into<usize>) -> &Self {
self.set_w(w);
self.set_h(h);
self
}
pub fn w (&self) -> usize {
self.x.load(Relaxed)
}
pub fn h (&self) -> usize {
self.y.load(Relaxed)
}
pub fn wh (&self) -> [usize;2] {
[self.w(), self.h()]
}
pub fn format (&self) -> Arc<str> {
format!("{}x{}", self.w(), self.h()).into()
}
pub fn of <T: Render<E>> (&self, item: T) -> Bsp<Fill<&Self>, T> {
Bsp::b(Fill::xy(self), item)
}
}

View file

@ -1,98 +0,0 @@
use crate::*;
use std::fmt::Debug;
pub trait Area<N: Coordinate>: From<[N;4]> + Debug + Copy {
fn x (&self) -> N;
fn y (&self) -> N;
fn w (&self) -> N;
fn h (&self) -> N;
fn zero () -> [N;4] {
[N::zero(), N::zero(), N::zero(), N::zero()]
}
fn from_position (pos: impl Size<N>) -> [N;4] {
let [x, y] = pos.wh();
[x, y, 0.into(), 0.into()]
}
fn from_size (size: impl Size<N>) -> [N;4] {
let [w, h] = size.wh();
[0.into(), 0.into(), w, h]
}
fn expect_min (&self, w: N, h: N) -> Usually<&Self> {
if self.w() < w || self.h() < h {
Err(format!("min {w}x{h}").into())
} else {
Ok(self)
}
}
fn xy (&self) -> [N;2] {
[self.x(), self.y()]
}
fn wh (&self) -> [N;2] {
[self.w(), self.h()]
}
fn xywh (&self) -> [N;4] {
[self.x(), self.y(), self.w(), self.h()]
}
fn clip_h (&self, h: N) -> [N;4] {
[self.x(), self.y(), self.w(), self.h().min(h)]
}
fn clip_w (&self, w: N) -> [N;4] {
[self.x(), self.y(), self.w().min(w), self.h()]
}
fn clip (&self, wh: impl Size<N>) -> [N;4] {
[self.x(), self.y(), wh.w(), wh.h()]
}
fn set_w (&self, w: N) -> [N;4] {
[self.x(), self.y(), w, self.h()]
}
fn set_h (&self, h: N) -> [N;4] {
[self.x(), self.y(), self.w(), h]
}
fn x2 (&self) -> N {
self.x().plus(self.w())
}
fn y2 (&self) -> N {
self.y().plus(self.h())
}
fn lrtb (&self) -> [N;4] {
[self.x(), self.x2(), self.y(), self.y2()]
}
fn center (&self) -> [N;2] {
[self.x().plus(self.w()/2.into()), self.y().plus(self.h()/2.into())]
}
fn center_x (&self, n: N) -> [N;4] {
let [x, y, w, h] = self.xywh();
[(x.plus(w / 2.into())).minus(n / 2.into()), y.plus(h / 2.into()), n, 1.into()]
}
fn center_y (&self, n: N) -> [N;4] {
let [x, y, w, h] = self.xywh();
[x.plus(w / 2.into()), (y.plus(h / 2.into())).minus(n / 2.into()), 1.into(), n]
}
fn center_xy (&self, [n, m]: [N;2]) -> [N;4] {
let [x, y, w, h] = self.xywh();
[(x.plus(w / 2.into())).minus(n / 2.into()), (y.plus(h / 2.into())).minus(m / 2.into()), n, m]
}
fn centered (&self) -> [N;2] {
[self.x().minus(self.w()/2.into()), self.y().minus(self.h()/2.into())]
}
fn iter_x (&self) -> impl Iterator<Item = N> where N: std::iter::Step {
self.x()..(self.x()+self.w())
}
fn iter_y (&self) -> impl Iterator<Item = N> where N: std::iter::Step {
self.y()..(self.y()+self.h())
}
}
impl<N: Coordinate> Area<N> for (N, N, N, N) {
fn x (&self) -> N { self.0 }
fn y (&self) -> N { self.1 }
fn w (&self) -> N { self.2 }
fn h (&self) -> N { self.3 }
}
impl<N: Coordinate> Area<N> for [N;4] {
fn x (&self) -> N { self[0] }
fn y (&self) -> N { self[1] }
fn w (&self) -> N { self[2] }
fn h (&self) -> N { self[3] }
}

View file

@ -1,31 +0,0 @@
use std::fmt::{Debug, Display};
use std::ops::{Add, Sub, Mul, Div};
/// A linear coordinate.
pub trait Coordinate: Send + Sync + Copy
+ Add<Self, Output=Self>
+ Sub<Self, Output=Self>
+ Mul<Self, Output=Self>
+ Div<Self, Output=Self>
+ Ord + PartialEq + Eq
+ Debug + Display + Default
+ From<u16> + Into<u16>
+ Into<usize>
+ Into<f64>
{
fn zero () -> Self { 0.into() }
fn plus (self, other: Self) -> Self;
fn minus (self, other: Self) -> Self {
if self >= other {
self - other
} else {
0.into()
}
}
}
impl Coordinate for u16 {
fn plus (self, other: Self) -> Self {
self.saturating_add(other)
}
}

View file

@ -1,22 +0,0 @@
use crate::*;
use crate::Direction::*;
/// A cardinal direction.
#[derive(Copy, Clone, PartialEq, Debug)]
#[cfg_attr(test, derive(Arbitrary))]
pub enum Direction {
North, South, East, West, Above, Below
}
impl Direction {
pub fn split_fixed <N: Coordinate> (self, area: impl Area<N>, a: N) -> ([N;4],[N;4]) {
let [x, y, w, h] = area.xywh();
match self {
North => ([x, y.plus(h).minus(a), w, a], [x, y, w, h.minus(a)]),
South => ([x, y, w, a], [x, y.plus(a), w, h.minus(a)]),
East => ([x, y, a, h], [x.plus(a), y, w.minus(a), h]),
West => ([x.plus(w).minus(a), y, a, h], [x, y, w.minus(a), h]),
Above | Below => (area.xywh(), area.xywh())
}
}
}

View file

@ -1,91 +0,0 @@
use crate::*;
use std::sync::{Arc, atomic::{AtomicUsize, Ordering::Relaxed}};
pub trait HasSize<E: Output> {
fn size (&self) -> &Measure<E>;
fn width (&self) -> usize {
self.size().w()
}
fn height (&self) -> usize {
self.size().h()
}
}
impl<E: Output, T: Has<Measure<E>>> HasSize<E> for T {
fn size (&self) -> &Measure<E> {
self.get()
}
}
/// A widget that tracks its render width and height
#[derive(Default)]
pub struct Measure<E: Output> {
_engine: PhantomData<E>,
pub x: Arc<AtomicUsize>,
pub y: Arc<AtomicUsize>,
}
// TODO: 🡘 🡙 ←🡙→ indicator to expand window when too small
impl<E: Output> Content<E> for Measure<E> {
fn render (&self, to: &mut E) {
self.x.store(to.area().w().into(), Relaxed);
self.y.store(to.area().h().into(), Relaxed);
}
}
impl<E: Output> Clone for Measure<E> {
fn clone (&self) -> Self {
Self {
_engine: Default::default(),
x: self.x.clone(),
y: self.y.clone(),
}
}
}
impl<E: Output> std::fmt::Debug for Measure<E> {
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
f.debug_struct("Measure")
.field("width", &self.x)
.field("height", &self.y)
.finish()
}
}
impl<E: Output> Measure<E> {
pub fn new () -> Self {
Self {
_engine: PhantomData::default(),
x: Arc::new(0.into()),
y: Arc::new(0.into()),
}
}
pub fn set_w (&self, w: impl Into<usize>) -> &Self {
self.x.store(w.into(), Relaxed);
self
}
pub fn set_h (&self, h: impl Into<usize>) -> &Self {
self.y.store(h.into(), Relaxed);
self
}
pub fn set_wh (&self, w: impl Into<usize>, h: impl Into<usize>) -> &Self {
self.set_w(w);
self.set_h(h);
self
}
pub fn w (&self) -> usize {
self.x.load(Relaxed)
}
pub fn h (&self) -> usize {
self.y.load(Relaxed)
}
pub fn wh (&self) -> [usize;2] {
[self.w(), self.h()]
}
pub fn format (&self) -> Arc<str> {
format!("{}x{}", self.w(), self.h()).into()
}
pub fn of <T: Render<E>> (&self, item: T) -> Bsp<Fill<&Self>, T> {
Bsp::b(Fill::xy(self), item)
}
}

View file

@ -1,40 +0,0 @@
use crate::*;
use std::fmt::Debug;
pub trait Size<N: Coordinate>: From<[N;2]> + Debug + Copy {
fn x (&self) -> N;
fn y (&self) -> N;
fn w (&self) -> N { self.x() }
fn h (&self) -> N { self.y() }
fn wh (&self) -> [N;2] { [self.x(), self.y()] }
fn clip_w (&self, w: N) -> [N;2] { [self.w().min(w), self.h()] }
fn clip_h (&self, h: N) -> [N;2] { [self.w(), self.h().min(h)] }
fn expect_min (&self, w: N, h: N) -> Usually<&Self> {
if self.w() < w || self.h() < h {
Err(format!("min {w}x{h}").into())
} else {
Ok(self)
}
}
fn zero () -> [N;2] {
[N::zero(), N::zero()]
}
fn to_area_pos (&self) -> [N;4] {
let [x, y] = self.wh();
[x, y, 0.into(), 0.into()]
}
fn to_area_size (&self) -> [N;4] {
let [w, h] = self.wh();
[0.into(), 0.into(), w, h]
}
}
impl<N: Coordinate> Size<N> for (N, N) {
fn x (&self) -> N { self.0 }
fn y (&self) -> N { self.1 }
}
impl<N: Coordinate> Size<N> for [N;2] {
fn x (&self) -> N { self[0] }
fn y (&self) -> N { self[1] }
}

View file

@ -93,7 +93,7 @@ impl ToTokens for CommandDef {
out out
}; };
write_quote(quote! { write_quote(quote! {
Some(::tengri::dsl::Token { value: ::tengri::dsl::DslVal::Key(#key), .. }) => { Some(::tengri::dsl::Token { value: ::tengri::dsl::Val::Key(#key), .. }) => {
let mut words = words.clone(); let mut words = words.clone();
Some(#command_enum::#variant) Some(#command_enum::#variant)
}, },
@ -154,7 +154,7 @@ impl ToTokens for CommandDef {
state: &#state, state: &#state,
value: &impl ::tengri::dsl::Dsl value: &impl ::tengri::dsl::Dsl
) -> Perhaps<Self> { ) -> Perhaps<Self> {
use ::tengri::dsl::DslVal::*; use ::tengri::dsl::Val::*;
todo!()//Ok(match token { #(#matchers)* _ => None }) todo!()//Ok(match token { #(#matchers)* _ => None })
} }
} }

View file

@ -81,7 +81,7 @@ impl ExposeImpl {
/// Generated by [tengri_proc::expose]. /// Generated by [tengri_proc::expose].
impl ::tengri::dsl::FromDsl<#state> for #t { impl ::tengri::dsl::FromDsl<#state> for #t {
fn try_from_dsl (state: &#state, dsl: &impl Dsl) -> Perhaps<Self> { fn try_from_dsl (state: &#state, dsl: &impl Dsl) -> Perhaps<Self> {
Ok(Some(match dsl.val() { Ok(Some(match dsl.dsl() {
#arms #arms
_ => { return Ok(None) } _ => { return Ok(None) }
})) }))
@ -93,7 +93,7 @@ impl ExposeImpl {
let formatted_type = format!("{}", quote! { #t }); let formatted_type = format!("{}", quote! { #t });
if &formatted_type == "bool" { if &formatted_type == "bool" {
return quote! { return quote! {
::tengri::dsl::DslVal::Sym(s) => match s.as_ref() { ::tengri::dsl::Val::Sym(s) => match s.as_ref() {
":true" => true, ":true" => true,
":false" => false, ":false" => false,
#variants #variants
@ -110,16 +110,16 @@ impl ExposeImpl {
Span::call_site() Span::call_site()
); );
return quote! { return quote! {
::tengri::dsl::DslVal::Num(n) => TryInto::<#t>::try_into(n) ::tengri::dsl::Val::Num(n) => TryInto::<#t>::try_into(n)
.unwrap_or_else(|_|panic!(#num_err)), .unwrap_or_else(|_|panic!(#num_err)),
::tengri::dsl::DslVal::Sym(s) => match s.as_ref() { ::tengri::dsl::Val::Sym(s) => match s.as_ref() {
#variants #variants
_ => { return Ok(None) } _ => { return Ok(None) }
}, },
} }
} }
return quote! { return quote! {
::tengri::dsl::DslVal::Sym(s) => match s.as_ref() { ::tengri::dsl::Val::Sym(s) => match s.as_ref() {
#variants #variants
_ => { return Ok(None) } _ => { return Ok(None) }
}, },

View file

@ -50,7 +50,7 @@ impl ToTokens for ViewDef {
impl ViewDef { impl ViewDef {
fn generated (&self) -> impl ToTokens { fn generated (&self) -> impl ToTokens {
let Self(ViewMeta { output }, ViewImpl { block, exposed }) = self; let Self(ViewMeta { output }, ViewImpl { block, .. }) = self;
let self_ty = &block.self_ty; let self_ty = &block.self_ty;
let builtins = self.builtins(); let builtins = self.builtins();
let exposed = self.exposed(); let exposed = self.exposed();
@ -74,9 +74,9 @@ impl ViewDef {
/// Expressions are handled by built-in functions /// Expressions are handled by built-in functions
/// that operate over constants and symbols. /// that operate over constants and symbols.
fn builtins (&self) -> impl ToTokens { fn builtins (&self) -> impl ToTokens {
let Self(ViewMeta { output }, ViewImpl { block, exposed }) = self; let Self(ViewMeta { output }, ViewImpl { .. }) = self;
let builtins = builtins_with_boxes_output(quote! { #output }).map(|builtin|quote! { let builtins = builtins_with_boxes_output(quote! { #output }).map(|builtin|quote! {
::tengri::dsl::DslVal::Exp(_, expr) => return Ok(Some( ::tengri::dsl::Val::Exp(_, expr) => return Ok(Some(
#builtin::from_dsl(self, expr, ||Box::new("failed to load builtin".into()))? #builtin::from_dsl(self, expr, ||Box::new("failed to load builtin".into()))?
.boxed() .boxed()
)), )),
@ -85,11 +85,11 @@ impl ViewDef {
} }
/// Symbols are handled by user-taked functions that take no parameters but `&self`. /// Symbols are handled by user-taked functions that take no parameters but `&self`.
fn exposed (&self) -> impl ToTokens { fn exposed (&self) -> impl ToTokens {
let Self(ViewMeta { output }, ViewImpl { block, exposed }) = self; let Self(ViewMeta { .. }, ViewImpl { exposed, .. }) = self;
let exposed = exposed.iter().map(|(key, value)|write_quote(quote! { let exposed = exposed.iter().map(|(key, value)|write_quote(quote! {
#key => return Ok(Some(self.#value().boxed())), #key => return Ok(Some(self.#value().boxed())),
})); }));
quote! { ::tengri::dsl::DslVal::Sym(key) => match key.as_ref() { #(#exposed)* } } quote! { ::tengri::dsl::Val::Sym(key) => match key.as_ref() { #(#exposed)* } }
} }
} }

View file

@ -3,7 +3,6 @@ use std::time::Duration;
mod tui_buffer; pub use self::tui_buffer::*; mod tui_buffer; pub use self::tui_buffer::*;
mod tui_input; pub use self::tui_input::*; mod tui_input; pub use self::tui_input::*;
mod tui_keys; pub use self::tui_keys::*;
mod tui_output; pub use self::tui_output::*; mod tui_output; pub use self::tui_output::*;
mod tui_perf; pub use self::tui_perf::*; mod tui_perf; pub use self::tui_perf::*;
@ -72,8 +71,7 @@ pub trait TuiRun<R: Render<TuiOut> + Handle<TuiIn> + 'static> {
fn run (&self, state: &Arc<RwLock<R>>) -> Usually<()>; fn run (&self, state: &Arc<RwLock<R>>) -> Usually<()>;
} }
impl<T: Render<TuiOut> + Handle<TuiIn> + Send + Sync + 'static> impl<T: Render<TuiOut> + Handle<TuiIn> + Send + Sync + 'static> TuiRun<T> for Arc<RwLock<Tui>> {
TuiRun<T> for Arc<RwLock<Tui>> {
fn run (&self, state: &Arc<RwLock<T>>) -> Usually<()> { fn run (&self, state: &Arc<RwLock<T>>) -> Usually<()> {
let _input_thread = TuiIn::run_input(self, state, Duration::from_millis(100)); let _input_thread = TuiIn::run_input(self, state, Duration::from_millis(100));
self.write().unwrap().setup()?; self.write().unwrap().setup()?;

View file

@ -4,21 +4,36 @@ use std::thread::{spawn, JoinHandle};
use crossterm::event::{poll, read}; use crossterm::event::{poll, read};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct TuiIn( pub struct TuiIn {
/// Exit flag /// Exit flag
pub Arc<AtomicBool>, pub exited: Arc<AtomicBool>,
/// Input event /// Input event
pub crossterm::event::Event, pub event: TuiEvent,
); }
impl Input for TuiIn {
impl Input for TuiIn { type Event = TuiEvent;
type Event = crossterm::event::Event; type Handled = bool;
type Handled = bool; fn event (&self) -> &TuiEvent { &self.event }
fn event (&self) -> &crossterm::event::Event { &self.1 } fn is_done (&self) -> bool { self.exited.fetch_and(true, Relaxed) }
fn is_done (&self) -> bool { self.0.fetch_and(true, Relaxed) } fn done (&self) { self.exited.store(true, Relaxed); }
fn done (&self) { self.0.store(true, Relaxed); } }
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)]
pub struct TuiEvent(crossterm::event::Event);
impl Ord for TuiEvent {
fn cmp (&self, other: &Self) -> std::cmp::Ordering {
self.partial_cmp(other) .unwrap_or_else(||format!("{:?}", self).cmp(&format!("{other:?}"))) // FIXME perf
}
}
impl From<crossterm::event::Event> for TuiEvent {
fn from (event: crossterm::event::Event) -> Self {
Self(event)
}
}
impl From<Arc<str>> for TuiEvent {
fn from (x: Arc<str>) -> Self {
TuiEvent(TuiKey::new(x.as_ref()).build().unwrap_or_else(||panic!("invalid key: {x}")))
}
} }
impl TuiIn { impl TuiIn {
/// Spawn the input thread. /// Spawn the input thread.
pub fn run_input <T: Handle<TuiIn> + Send + Sync + 'static> ( pub fn run_input <T: Handle<TuiIn> + Send + Sync + 'static> (
@ -35,7 +50,6 @@ impl TuiIn {
if poll(timer).is_ok() { if poll(timer).is_ok() {
let event = read().unwrap(); let event = read().unwrap();
match event { match event {
crossterm::event::Event::Key(KeyEvent { crossterm::event::Event::Key(KeyEvent {
code: KeyCode::Char('c'), code: KeyCode::Char('c'),
modifiers: KeyModifiers::CONTROL, modifiers: KeyModifiers::CONTROL,
@ -46,7 +60,8 @@ impl TuiIn {
}, },
_ => { _ => {
let exited = exited.clone(); let exited = exited.clone();
if let Err(e) = state.write().unwrap().handle(&TuiIn(exited, event)) { let event = event.into();
if let Err(e) = state.write().unwrap().handle(&TuiIn { exited, event }) {
panic!("{e}") panic!("{e}")
} }
} }
@ -59,10 +74,101 @@ impl TuiIn {
//#[cfg(feature = "dsl")] //#[cfg(feature = "dsl")]
//impl DslInput for TuiIn { //impl DslInput for TuiIn {
//fn matches_dsl (&self, token: &str) -> bool { //fn matches_dsl (&self, token: &str) -> bool {
//if let Some(event) = KeyMatcher::new(token).build() { //if let Some(event) = TuiKey::new(token).build() {
//&event == self.event() //&event == self.event()
//} else { //} else {
//false //false
//} //}
//} //}
//} //}
pub struct TuiKey {
valid: bool,
key: Option<KeyCode>,
mods: KeyModifiers,
}
impl TuiKey {
pub fn new (token: impl AsRef<str>) -> Self {
let token = token.as_ref();
if token.len() < 2 {
Self { valid: false, key: None, mods: KeyModifiers::NONE }
} else if token.chars().next() != Some('@') {
Self { valid: false, key: None, mods: KeyModifiers::NONE }
} else {
Self { valid: true, key: None, mods: KeyModifiers::NONE }.next(&token[1..])
}
}
pub fn build (self) -> Option<Event> {
if self.valid && self.key.is_some() {
Some(Event::Key(KeyEvent::new(self.key.unwrap(), self.mods)))
} else {
None
}
}
fn next (mut self, token: &str) -> Self {
let mut tokens = token.split('-').peekable();
while let Some(token) = tokens.next() {
if tokens.peek().is_some() {
match token {
"ctrl" | "Ctrl" | "c" | "C" => self.mods |= KeyModifiers::CONTROL,
"alt" | "Alt" | "m" | "M" => self.mods |= KeyModifiers::ALT,
"shift" | "Shift" | "s" | "S" => {
self.mods |= KeyModifiers::SHIFT;
// + TODO normalize character case, BackTab, etc.
},
_ => panic!("unknown modifier {token}"),
}
} else {
self.key = if token.len() == 1 {
Some(KeyCode::Char(token.chars().next().unwrap()))
} else {
Some(Self::named_key(token).unwrap_or_else(||panic!("unknown character {token}")))
}
}
}
self
}
fn named_key (token: &str) -> Option<KeyCode> {
use KeyCode::*;
Some(match token {
"up" => Up,
"down" => Down,
"left" => Left,
"right" => Right,
"esc" | "escape" => Esc,
"enter" | "return" => Enter,
"delete" | "del" => Delete,
"tab" => Tab,
"space" => Char(' '),
"comma" => Char(','),
"period" => Char('.'),
"plus" => Char('+'),
"minus" | "dash" => Char('-'),
"equal" | "equals" => Char('='),
"underscore" => Char('_'),
"backtick" => Char('`'),
"lt" => Char('<'),
"gt" => Char('>'),
"cbopen" | "openbrace" => Char('{'),
"cbclose" | "closebrace" => Char('}'),
"bropen" | "openbracket" => Char('['),
"brclose" | "closebracket" => Char(']'),
"pgup" | "pageup" => PageUp,
"pgdn" | "pagedown" => PageDown,
"f1" => F(1),
"f2" => F(2),
"f3" => F(3),
"f4" => F(4),
"f5" => F(5),
"f6" => F(6),
"f7" => F(7),
"f8" => F(8),
"f9" => F(9),
"f10" => F(10),
"f11" => F(11),
"f12" => F(12),
_ => return None,
})
}
}

View file

@ -1,92 +0,0 @@
use crate::*;
pub struct KeyMatcher {
valid: bool,
key: Option<KeyCode>,
mods: KeyModifiers,
}
impl KeyMatcher {
pub fn new (token: impl AsRef<str>) -> Self {
let token = token.as_ref();
if token.len() < 2 {
Self { valid: false, key: None, mods: KeyModifiers::NONE }
} else if token.chars().next() != Some('@') {
Self { valid: false, key: None, mods: KeyModifiers::NONE }
} else {
Self { valid: true, key: None, mods: KeyModifiers::NONE }.next(&token[1..])
}
}
pub fn build (self) -> Option<Event> {
if self.valid && self.key.is_some() {
Some(Event::Key(KeyEvent::new(self.key.unwrap(), self.mods)))
} else {
None
}
}
fn next (mut self, token: &str) -> Self {
let mut tokens = token.split('-').peekable();
while let Some(token) = tokens.next() {
if tokens.peek().is_some() {
match token {
"ctrl" | "Ctrl" | "c" | "C" => self.mods |= KeyModifiers::CONTROL,
"alt" | "Alt" | "m" | "M" => self.mods |= KeyModifiers::ALT,
"shift" | "Shift" | "s" | "S" => {
self.mods |= KeyModifiers::SHIFT;
// + TODO normalize character case, BackTab, etc.
},
_ => panic!("unknown modifier {token}"),
}
} else {
self.key = if token.len() == 1 {
Some(KeyCode::Char(token.chars().next().unwrap()))
} else {
Some(Self::named_key(token).unwrap_or_else(||panic!("unknown character {token}")))
}
}
}
self
}
fn named_key (token: &str) -> Option<KeyCode> {
use KeyCode::*;
Some(match token {
"up" => Up,
"down" => Down,
"left" => Left,
"right" => Right,
"esc" | "escape" => Esc,
"enter" | "return" => Enter,
"delete" | "del" => Delete,
"tab" => Tab,
"space" => Char(' '),
"comma" => Char(','),
"period" => Char('.'),
"plus" => Char('+'),
"minus" | "dash" => Char('-'),
"equal" | "equals" => Char('='),
"underscore" => Char('_'),
"backtick" => Char('`'),
"lt" => Char('<'),
"gt" => Char('>'),
"cbopen" | "openbrace" => Char('{'),
"cbclose" | "closebrace" => Char('}'),
"bropen" | "openbracket" => Char('['),
"brclose" | "closebracket" => Char(']'),
"pgup" | "pageup" => PageUp,
"pgdn" | "pagedown" => PageDown,
"f1" => F(1),
"f2" => F(2),
"f3" => F(3),
"f4" => F(4),
"f5" => F(5),
"f6" => F(6),
"f7" => F(7),
"f8" => F(8),
"f9" => F(9),
"f10" => F(10),
"f11" => F(11),
"f12" => F(12),
_ => return None,
})
}
}