mirror of
https://codeberg.org/unspeaker/tengri.git
synced 2025-12-06 11:46:42 +01:00
Compare commits
178 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8c54510f63 | |||
| 731f4a971e | |||
| 90fc869e14 | |||
| ca862b9802 | |||
| 8dfe20a58c | |||
| c5cdbf4f07 | |||
| d330e88252 | |||
| a4dbf88220 | |||
| 18b6803912 | |||
| d6dcf137a8 | |||
| baa582b9dd | |||
| 5e6338fad8 | |||
| 194f2f9874 | |||
| 1c21a85f27 | |||
| 74b3af2212 | |||
| ff4d0c9db5 | |||
| c57117df9c | |||
| ad2d7c38b1 | |||
| 4f1131744b | |||
| b98fccd98b | |||
| 34982a12ba | |||
| f35ac97737 | |||
| 1b98e468b6 | |||
| d9081087ec | |||
| 375b959e33 | |||
| 022bfa3e20 | |||
| e3e3c163da | |||
| 0621793930 | |||
| 3e2b07158c | |||
| d92e5efdd0 | |||
| 2557a0d253 | |||
| 1ef898ac32 | |||
| ddd162f225 | |||
| cf253c28f9 | |||
| 4fc0db5777 | |||
| d7884f6289 | |||
| f626860924 | |||
| a1190a24a1 | |||
| 3298d6b6e1 | |||
| ab1afa219f | |||
| ab0dc3fae0 | |||
| 35a5784d23 | |||
| 7fd6c91643 | |||
| e839096cf3 | |||
| 24ac52d807 | |||
| b52c1f5828 | |||
| 104bb1c8e7 | |||
| 9ccd7e5c69 | |||
| a601d3d806 | |||
| 643658ab16 | |||
| 9e0b7be9a9 | |||
| 85c305385b | |||
| 9f7d0efda5 | |||
| 8cbd7dd8e8 | |||
| 360b404b69 | |||
| 73eb935282 | |||
| d72a3b5b8f | |||
| 238ac2e888 | |||
| 291b917970 | |||
| a145e332de | |||
| d99b20c99d | |||
| e72225f83c | |||
| ca4c558eab | |||
| 38d29f30a7 | |||
| 7271081fc9 | |||
| 6c3a0964ec | |||
| 11f686650f | |||
| 91dc77cfea | |||
| c8827b43c3 | |||
| 17506726cb | |||
| 21832453d9 | |||
| 08593571fa | |||
| cb47c4d0ff | |||
| 08a8dff93d | |||
| e9d4c7e0bc | |||
| f77139c8fd | |||
| d6e8be6ce5 | |||
| 93b1cf1a5c | |||
| 7097333993 | |||
| f1b24d436a | |||
| 31e84bf5b3 | |||
| 3e1084555b | |||
| 5a2177cc77 | |||
| cbd28a5934 | |||
| ddf0c05d5f | |||
| 583660c330 | |||
| abc87d3234 | |||
| a4a1066f18 | |||
| daef8dfa9e | |||
| 7516517078 | |||
| 776cea6f1b | |||
| 2048dd2263 | |||
| f714302f21 | |||
| 455d6d00d5 | |||
| 7c1cddc759 | |||
| f797a7143d | |||
| f08593f0f8 | |||
| 90f5699fff | |||
| 3bc739328e | |||
| 7ddbace030 | |||
| 921378b6db | |||
| 9a12e0c7ba | |||
| f21781e816 | |||
| 12998a94ea | |||
| b127526570 | |||
| c9f0164871 | |||
| a55e84c29f | |||
| b25977d878 | |||
| 4ff4ea8173 | |||
| c954965ae1 | |||
| f7306de55f | |||
| 4c039c999b | |||
| 496a9202d5 | |||
| 8bfd1a23a1 | |||
| a9619ab9ce | |||
| bad20f5037 | |||
| 5d546affed | |||
| 663efede64 | |||
| b45ac8f417 | |||
| 632977a0dc | |||
| faecc2c304 | |||
| 1868736597 | |||
| ed772b9872 | |||
| f18e01c220 | |||
| 8dda576c9d | |||
| cb8fd26922 | |||
| 4a385b40ff | |||
| fe8ecf8a98 | |||
| 20ccff13de | |||
| 3bb38f2d27 | |||
| 60c0771024 | |||
| ab07fd2b43 | |||
| 5e09f5a4bb | |||
| 22d63eed9c | |||
| b7bb6119aa | |||
| bcbcc387a2 | |||
| a16603fbc8 | |||
| 2a6087e1c7 | |||
| e3bfae8897 | |||
| 046be9a9e1 | |||
| 751e01a41e | |||
| c56b08c24e | |||
| 7df7cb839c | |||
| 7570aefcc2 | |||
| cba23a005c | |||
| b543c43e68 | |||
| 2c797fd41f | |||
| 21f7f6b38a | |||
| 3df1938626 | |||
| 0d4ba4a54e | |||
| 2b208e3c49 | |||
| 9fb5d2d9f7 | |||
| 44ebe17c66 | |||
| 119d5c35f0 | |||
| 4ec51d5b69 | |||
| 35ad371205 | |||
| 47b7f7e7f9 | |||
| 95149b79c4 | |||
| 7ba37f3f02 | |||
| aa66760b8c | |||
| fa5ff90be6 | |||
| 61fd07bffd | |||
| 7ba08b8be3 | |||
| 3d01f5558c | |||
| 844681d6ad | |||
| aa7a2c72f5 | |||
| 222e10239b | |||
| 2e9ba1b92d | |||
| 649a89443b | |||
| b5fbd14f91 | |||
| 91dfed1077 | |||
| 3861439c49 | |||
| fa6f6dab1d | |||
| 1f62c2f86d | |||
| aeddf561b1 | |||
| 471959d1f5 | |||
| 6048d24880 | |||
| f11c27b8c9 |
128 changed files with 5261 additions and 3461 deletions
3
.editorconfig
Normal file
3
.editorconfig
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
root = true
|
||||
[*]
|
||||
max_line_length = 132
|
||||
479
Cargo.lock
generated
479
Cargo.lock
generated
|
|
@ -13,9 +13,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
version = "2.0.0"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
|
||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[package]]
|
||||
name = "allocator-api2"
|
||||
|
|
@ -40,15 +40,15 @@ checksum = "628d228f918ac3b82fe590352cc719d30664a0c13ca3a60266fe02c7132d480a"
|
|||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.4.0"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.74"
|
||||
version = "0.3.75"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
|
||||
checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002"
|
||||
dependencies = [
|
||||
"addr2line",
|
||||
"cfg-if",
|
||||
|
|
@ -56,7 +56,7 @@ dependencies = [
|
|||
"miniz_oxide",
|
||||
"object",
|
||||
"rustc-demangle",
|
||||
"windows-targets",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -86,15 +86,15 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
|
|||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.9.0"
|
||||
version = "2.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
|
||||
checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.17.0"
|
||||
version = "3.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
|
||||
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
|
||||
|
||||
[[package]]
|
||||
name = "by_address"
|
||||
|
|
@ -110,18 +110,18 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
|
|||
|
||||
[[package]]
|
||||
name = "castaway"
|
||||
version = "0.2.3"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5"
|
||||
checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a"
|
||||
dependencies = [
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
|
||||
|
||||
[[package]]
|
||||
name = "compact_str"
|
||||
|
|
@ -151,9 +151,34 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "const_panic"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb8a602185c3c95b52f86dc78e55a6df9a287a7a93ddbcf012509930880cf879"
|
||||
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 = "2459fc9262a1aa204eb4b5764ad4f189caec88aea9634389c0a25f8be7f6265e"
|
||||
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]]
|
||||
name = "crossbeam-utils"
|
||||
|
|
@ -177,6 +202,24 @@ dependencies = [
|
|||
"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]]
|
||||
name = "crossterm_winapi"
|
||||
version = "0.9.1"
|
||||
|
|
@ -221,6 +264,36 @@ dependencies = [
|
|||
"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]]
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
|
|
@ -241,12 +314,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
|||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.11"
|
||||
version = "0.3.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e"
|
||||
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -275,25 +348,25 @@ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
|||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.15"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
||||
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.2"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0"
|
||||
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasi 0.14.2+wasi-0.2.4",
|
||||
"wasi 0.14.3+wasi-0.2.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -304,9 +377,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
|||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.2"
|
||||
version = "0.15.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
|
||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
"equivalent",
|
||||
|
|
@ -333,9 +406,9 @@ checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd"
|
|||
|
||||
[[package]]
|
||||
name = "instability"
|
||||
version = "0.3.7"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bf9fed6d91cfb734e7476a06bde8300a1b94e217e1b523b6f0cd1a01998c71d"
|
||||
checksum = "435d80800b936787d62688c927b6490e887c7ef5ff9ce922c6c6050fca75eb9a"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"indoc",
|
||||
|
|
@ -413,9 +486,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.172"
|
||||
version = "0.2.175"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
||||
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
|
|
@ -430,10 +503,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.12"
|
||||
name = "litrs"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
|
||||
checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"scopeguard",
|
||||
|
|
@ -456,29 +535,29 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
version = "2.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.8"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a"
|
||||
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
|
||||
dependencies = [
|
||||
"adler2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.0.3"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
|
||||
checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"windows-sys 0.52.0",
|
||||
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -515,7 +594,7 @@ dependencies = [
|
|||
"fast-srgb8",
|
||||
"palette_derive",
|
||||
"phf",
|
||||
"rand",
|
||||
"rand 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -532,9 +611,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.3"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
|
||||
checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
|
|
@ -542,15 +621,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.10"
|
||||
version = "0.9.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
|
||||
checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-targets",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -576,7 +655,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
|
||||
dependencies = [
|
||||
"phf_shared",
|
||||
"rand",
|
||||
"rand 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -612,26 +691,26 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.94"
|
||||
version = "1.0.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
|
||||
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proptest"
|
||||
version = "1.6.0"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14cae93065090804185d3b75f0bf93b8eeda30c7a9b4a33d3bdb3988d6229e50"
|
||||
checksum = "6fcdab19deb5195a31cf7726a210015ff1496ba1464fd42cb4f537b8b01b471f"
|
||||
dependencies = [
|
||||
"bit-set",
|
||||
"bit-vec",
|
||||
"bitflags",
|
||||
"lazy_static",
|
||||
"num-traits",
|
||||
"rand",
|
||||
"rand_chacha",
|
||||
"rand 0.9.2",
|
||||
"rand_chacha 0.9.0",
|
||||
"rand_xorshift",
|
||||
"regex-syntax",
|
||||
"rusty-fork",
|
||||
|
|
@ -652,15 +731,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "quanta"
|
||||
version = "0.12.5"
|
||||
version = "0.12.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3bd1fe6824cea6538803de3ff1bc0cf3949024db3d43c9643024bfb33a807c0e"
|
||||
checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"raw-cpuid",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||
"web-sys",
|
||||
"winapi",
|
||||
]
|
||||
|
|
@ -682,9 +761,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.2.0"
|
||||
version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
|
|
@ -693,8 +772,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
"rand_chacha 0.3.1",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
||||
dependencies = [
|
||||
"rand_chacha 0.9.0",
|
||||
"rand_core 0.9.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -704,7 +793,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"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]]
|
||||
|
|
@ -713,16 +812,25 @@ version = "0.6.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom 0.2.15",
|
||||
"getrandom 0.2.16",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
||||
dependencies = [
|
||||
"getrandom 0.3.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_xorshift"
|
||||
version = "0.3.0"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f"
|
||||
checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a"
|
||||
dependencies = [
|
||||
"rand_core",
|
||||
"rand_core 0.9.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -734,7 +842,7 @@ dependencies = [
|
|||
"bitflags",
|
||||
"cassowary",
|
||||
"compact_str",
|
||||
"crossterm",
|
||||
"crossterm 0.28.1",
|
||||
"indoc",
|
||||
"instability",
|
||||
"itertools 0.13.0",
|
||||
|
|
@ -757,24 +865,24 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.11"
|
||||
version = "0.5.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3"
|
||||
checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.5"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.24"
|
||||
version = "0.1.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
|
||||
checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
|
|
@ -791,22 +899,22 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.0.5"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf"
|
||||
checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.9.4",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.20"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
|
||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "rusty-fork"
|
||||
|
|
@ -834,9 +942,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
|||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.3.17"
|
||||
version = "0.3.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
|
||||
checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"signal-hook-registry",
|
||||
|
|
@ -855,9 +963,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.2"
|
||||
version = "1.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
|
||||
checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
|
@ -870,9 +978,9 @@ checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
|
|||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.0"
|
||||
version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
|
|
@ -910,9 +1018,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.100"
|
||||
version = "2.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
|
||||
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
@ -921,90 +1029,117 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.19.1"
|
||||
version = "3.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf"
|
||||
checksum = "15b61f8f20e3a6f7e0649d825294eaf317edce30f82cf6026e7e4cb9222a7d1e"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"getrandom 0.3.2",
|
||||
"getrandom 0.3.3",
|
||||
"once_cell",
|
||||
"rustix 1.0.5",
|
||||
"windows-sys 0.59.0",
|
||||
"rustix 1.0.8",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tengri"
|
||||
version = "0.8.0"
|
||||
version = "0.14.0"
|
||||
dependencies = [
|
||||
"crossterm 0.29.0",
|
||||
"tengri",
|
||||
"tengri_core",
|
||||
"tengri_dsl",
|
||||
"tengri_input",
|
||||
"tengri_output",
|
||||
"tengri_proc",
|
||||
"tengri_tui",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tengri_core"
|
||||
version = "0.14.0"
|
||||
|
||||
[[package]]
|
||||
name = "tengri_dsl"
|
||||
version = "0.8.0"
|
||||
version = "0.14.0"
|
||||
dependencies = [
|
||||
"const_panic",
|
||||
"itertools 0.14.0",
|
||||
"konst",
|
||||
"proptest",
|
||||
"tengri_core",
|
||||
"tengri_tui",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tengri_input"
|
||||
version = "0.8.0"
|
||||
version = "0.14.0"
|
||||
dependencies = [
|
||||
"tengri_core",
|
||||
"tengri_dsl",
|
||||
"tengri_tui",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tengri_output"
|
||||
version = "0.8.0"
|
||||
version = "0.14.0"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proptest",
|
||||
"proptest-derive",
|
||||
"tengri",
|
||||
"tengri_core",
|
||||
"tengri_dsl",
|
||||
"tengri_tui",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tengri_proc"
|
||||
version = "0.14.0"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"tengri_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tengri_tui"
|
||||
version = "0.8.0"
|
||||
version = "0.14.0"
|
||||
dependencies = [
|
||||
"atomic_float",
|
||||
"better-panic",
|
||||
"crossterm",
|
||||
"bumpalo",
|
||||
"crossterm 0.29.0",
|
||||
"konst",
|
||||
"palette",
|
||||
"quanta",
|
||||
"rand",
|
||||
"rand 0.8.5",
|
||||
"ratatui",
|
||||
"tengri",
|
||||
"tengri_core",
|
||||
"tengri_dsl",
|
||||
"tengri_input",
|
||||
"tengri_output",
|
||||
"tengri_proc",
|
||||
"unicode-width 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.12"
|
||||
version = "2.0.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
|
||||
checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.12"
|
||||
version = "2.0.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
|
||||
checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
@ -1013,9 +1148,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "typewit"
|
||||
version = "1.11.0"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb77c29baba9e4d3a6182d51fa75e3215c7fd1dab8f4ea9d107c716878e55fc0"
|
||||
checksum = "4dd91acc53c592cb800c11c83e8e7ee1d48378d05cfa33b5474f5f80c5b236bf"
|
||||
dependencies = [
|
||||
"typewit_proc_macros",
|
||||
]
|
||||
|
|
@ -1067,6 +1202,12 @@ version = "0.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||
|
||||
[[package]]
|
||||
name = "wait-timeout"
|
||||
version = "0.2.1"
|
||||
|
|
@ -1078,17 +1219,17 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
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"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.14.2+wasi-0.2.4"
|
||||
version = "0.14.3+wasi-0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
|
||||
checksum = "6a51ae83037bdd272a9e28ce236db8c07016dd0d50c27038b3f407533c030c95"
|
||||
dependencies = [
|
||||
"wit-bindgen-rt",
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1181,13 +1322,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
name = "windows-link"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
|
|
@ -1195,7 +1333,16 @@ version = "0.59.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
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.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1204,14 +1351,31 @@ version = "0.52.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
"windows_aarch64_gnullvm 0.52.6",
|
||||
"windows_aarch64_msvc 0.52.6",
|
||||
"windows_i686_gnu 0.52.6",
|
||||
"windows_i686_gnullvm 0.52.6",
|
||||
"windows_i686_msvc 0.52.6",
|
||||
"windows_x86_64_gnu 0.52.6",
|
||||
"windows_x86_64_gnullvm 0.52.6",
|
||||
"windows_x86_64_msvc 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.53.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"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]]
|
||||
|
|
@ -1220,42 +1384,84 @@ version = "0.52.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
|
|
@ -1263,28 +1469,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rt"
|
||||
version = "0.39.0"
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.45.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "052283831dbae3d879dc7f51f3d92703a316ca49f91540417d38591826127814"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.24"
|
||||
version = "0.8.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879"
|
||||
checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.24"
|
||||
version = "0.8.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be"
|
||||
checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
|
|||
59
Cargo.toml
59
Cargo.toml
|
|
@ -1,19 +1,52 @@
|
|||
[workspace.package]
|
||||
version = "0.8.0"
|
||||
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"./tengri",
|
||||
"./input",
|
||||
"./output",
|
||||
"./tui",
|
||||
"./dsl"
|
||||
]
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
||||
[profile.coverage]
|
||||
inherits = "test"
|
||||
lto = false
|
||||
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"./tengri",
|
||||
"./core",
|
||||
"./input",
|
||||
"./output",
|
||||
"./tui",
|
||||
"./dsl",
|
||||
"./proc",
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.14.0"
|
||||
edition = "2024"
|
||||
|
||||
[workspace.dependencies]
|
||||
tengri = { path = "./tengri" }
|
||||
tengri_core = { path = "./core" }
|
||||
tengri_input = { path = "./input" }
|
||||
tengri_output = { path = "./output" }
|
||||
tengri_tui = { path = "./tui" }
|
||||
tengri_dsl = { path = "./dsl" }
|
||||
tengri_proc = { path = "./proc" }
|
||||
|
||||
anyhow = { version = "1.0" }
|
||||
atomic_float = { version = "1" }
|
||||
better-panic = { version = "0.3.0" }
|
||||
bumpalo = { version = "3.19.0" }
|
||||
const_panic = { version = "0.2.12", features = [ "derive" ] }
|
||||
crossterm = { version = "0.29.0" }
|
||||
heck = { version = "0.5" }
|
||||
itertools = { version = "0.14.0" }
|
||||
konst = { version = "0.3.16", features = [ "rust_1_83" ] }
|
||||
palette = { version = "0.7.6", features = [ "random" ] }
|
||||
proc-macro2 = { version = "1", features = ["span-locations"] }
|
||||
proptest = { version = "^1" }
|
||||
proptest-derive = { version = "^0.5.1" }
|
||||
quanta = { version = "0.12.3" }
|
||||
quote = { version = "1" }
|
||||
rand = { version = "0.8.5" }
|
||||
ratatui = { version = "0.29.0", features = [ "unstable-widget-ref", "underline-color" ] }
|
||||
syn = { version = "2", features = ["full", "extra-traits"] }
|
||||
thiserror = { version = "2.0" }
|
||||
unicode-width = { version = "0.2" }
|
||||
|
|
|
|||
35
Justfile
35
Justfile
|
|
@ -1,15 +1,34 @@
|
|||
covfig := "CARGO_INCREMENTAL=0 RUSTFLAGS='-Cinstrument-coverage' RUSTDOCFLAGS='-Cinstrument-coverage' LLVM_PROFILE_FILE='cov/cargo-test-%p-%m.profraw'"
|
||||
export LLVM_PROFILE_FILE := "cov/cargo-test-%p-%m.profraw"
|
||||
grcov-binary := "--binary-path ./target/coverage/deps/"
|
||||
grcov-ignore := "--ignore-not-existing --ignore '../*' --ignore \"/*\" --ignore 'target/*'"
|
||||
|
||||
default:
|
||||
just -l
|
||||
|
||||
bacon:
|
||||
bacon -s
|
||||
|
||||
cov:
|
||||
{{covfig}} time cargo test -j4 --workspace --profile coverage
|
||||
CARGO_INCREMENTAL=0 RUSTFLAGS='-Cinstrument-coverage' \
|
||||
time cargo test -j4 --workspace --profile coverage
|
||||
rm -rf target/coverage/html || true
|
||||
{{covfig}} time grcov . -s . {{grcov-binary}} {{grcov-ignore}} -t html -o target/coverage/html
|
||||
time grcov . -s . {{grcov-binary}} {{grcov-ignore}} -t html -o target/coverage/html
|
||||
|
||||
cov-md:
|
||||
{{covfig}} time cargo test -j4 --workspace --profile coverage
|
||||
{{covfig}} time grcov . -s . {{grcov-binary}} {{grcov-ignore}} -t markdown | sort
|
||||
CARGO_INCREMENTAL=0 RUSTFLAGS='-Cinstrument-coverage' \
|
||||
time cargo test -j4 --workspace --profile coverage
|
||||
time grcov . -s . {{grcov-binary}} {{grcov-ignore}} -t markdown | sort
|
||||
|
||||
cov-md-ci:
|
||||
{{covfig}} time cargo test -j4 --workspace --profile coverage -- --skip test_tui_engine
|
||||
{{covfig}} time grcov . -s . {{grcov-binary}} {{grcov-ignore}} -t markdown | sort
|
||||
CARGO_INCREMENTAL=0 RUSTFLAGS='-Cinstrument-coverage' \
|
||||
time cargo test -j4 --workspace --profile coverage -- --skip test_tui_engine
|
||||
time grcov . -s . {{grcov-binary}} {{grcov-ignore}} -t markdown | sort
|
||||
|
||||
doc:
|
||||
cargo doc
|
||||
CARGO_INCREMENTAL=0 RUSTFLAGS='-Cinstrument-coverage' RUSTDOCFLAGS='-Cinstrument-coverage' \
|
||||
cargo doc
|
||||
|
||||
example-tui-00:
|
||||
cargo run -p tengri_tui --example tui_00
|
||||
example-tui-01:
|
||||
cargo run -p tengri_tui --example tui_01
|
||||
|
|
|
|||
44
bacon.toml
Normal file
44
bacon.toml
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
default_job = "check-all"
|
||||
|
||||
env.CARGO_TERM_COLOR = "always"
|
||||
|
||||
[keybindings]
|
||||
c = "job:check"
|
||||
d = "job:doc-open"
|
||||
t = "job:test"
|
||||
n = "job:nextest"
|
||||
l = "job:clippy"
|
||||
|
||||
[jobs]
|
||||
|
||||
[jobs.check]
|
||||
command = ["cargo", "check"]
|
||||
need_stdout = false
|
||||
watch = ["core","dsl","editor","input","output","proc","tengri","tui"]
|
||||
|
||||
[jobs.clippy-all]
|
||||
command = ["cargo", "clippy"]
|
||||
need_stdout = false
|
||||
watch = ["tek", "deps"]
|
||||
|
||||
[jobs.test]
|
||||
command = ["cargo", "test"]
|
||||
need_stdout = true
|
||||
watch = ["tek", "deps"]
|
||||
|
||||
[jobs.doc]
|
||||
command = ["cargo", "doc", "--no-deps"]
|
||||
need_stdout = false
|
||||
|
||||
[jobs.doc-open]
|
||||
command = ["cargo", "doc", "--no-deps", "--open"]
|
||||
need_stdout = false
|
||||
on_success = "back" # so that we don't open the browser at each change
|
||||
|
||||
[skin]
|
||||
status_fg = 251
|
||||
status_bg = 200
|
||||
key_fg = 11
|
||||
status_key_fg = 11
|
||||
project_name_badge_fg = 11
|
||||
project_name_badge_bg = 69
|
||||
6
core/Cargo.toml
Normal file
6
core/Cargo.toml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
[package]
|
||||
name = "tengri_core"
|
||||
description = "UI metaframework, core definitions."
|
||||
version = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
137
core/src/core_macros.rs
Normal file
137
core/src/core_macros.rs
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
/// Define and reexport submodules.
|
||||
#[macro_export] macro_rules! modules(
|
||||
($($($feat:literal?)? $name:ident),* $(,)?) => { $(
|
||||
$(#[cfg(feature=$feat)])? mod $name;
|
||||
$(#[cfg(feature=$feat)])? pub use self::$name::*;
|
||||
)* };
|
||||
($($($feat:literal?)? $name:ident $body:block),* $(,)?) => { $(
|
||||
$(#[cfg(feature=$feat)])? mod $name $body
|
||||
$(#[cfg(feature=$feat)])? pub use self::$name::*;
|
||||
)* };
|
||||
);
|
||||
|
||||
/// Define a trait and implement it for read-only wrapper types.
|
||||
#[macro_export] macro_rules! flex_trait (
|
||||
($Trait:ident $(<$($A:ident:$T:ident),+>)? $(:$dep:ident $(<$dtt:tt>)? $(+$dep2:ident $(<$dtt2:tt>)?)*)? {
|
||||
$(fn $fn:ident $(<$($fl:lifetime),*>)? (& $($fl2:lifetime)* $self:ident $(, $arg:ident:$ty:ty)*) $(-> $ret:ty)? $body:block)*
|
||||
}) => {
|
||||
pub trait $Trait $(<$($A: $T),+>)? $(:$dep $(<$dtt>)? $(+$dep2 $(<$dtt2>)?)*)? {
|
||||
$(fn $fn $(<$($fl),*>)? (& $($fl2)* $self $(,$arg:$ty)*) $(-> $ret)? $body)*
|
||||
}
|
||||
impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for &_T_ {
|
||||
$(fn $fn $(<$($fl),*>)? (& $($fl2)* $self $(,$arg:$ty)*) $(-> $ret)? { (*$self).$fn($($arg),*) })*
|
||||
}
|
||||
impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for &mut _T_ {
|
||||
$(fn $fn $(<$($fl),*>)? (& $($fl2)* $self $(,$arg:$ty)*) $(-> $ret)? { (**$self).$fn($($arg),*) })*
|
||||
}
|
||||
impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::Arc<_T_> {
|
||||
$(fn $fn $(<$($fl),*>)? (& $($fl2)* $self $(,$arg:$ty)*) $(-> $ret)? { (*$self).$fn($($arg),*) })*
|
||||
}
|
||||
impl<$($($A: $T,)+)?> $Trait $(<$($A),+>)? for Box<dyn $Trait $(<$($A),+>)?> {
|
||||
$(fn $fn $(<$($fl),*>)? (& $($fl2)* $self $(,$arg:$ty)*) $(-> $ret)? { (**$self).$fn($($arg),*) })*
|
||||
}
|
||||
//impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for Option<_T_> {
|
||||
//$(fn $fn (&$self $(,$arg:$ty)*) -> $ret {
|
||||
//if let Some(this) = $self { this.$fn($($arg),*) } else { Ok(None) }
|
||||
//})*
|
||||
//}
|
||||
//impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::Mutex<_T_> {
|
||||
//$(fn $fn (&$self $(,$arg:$ty)*) -> $ret { (*$self).lock().unwrap().$fn($($arg),*) })*
|
||||
//}
|
||||
//impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::RwLock<_T_> {
|
||||
//$(fn $fn (&$self $(,$arg:$ty)*) -> $ret { $self.read().unwrap().$fn($($arg),*) })*
|
||||
//}
|
||||
});
|
||||
/// Define a trait an implement it for read-only wrapper types. */
|
||||
#[macro_export] macro_rules! flex_trait_sized (
|
||||
($Trait:ident $(<$($A:ident:$T:ident),+>)? $(:$dep:ident $(<$dtt:tt>)? $(+$dep2:ident $(<$dtt2:tt>)?)*)? {
|
||||
$(fn $fn:ident (&$self:ident $(, $arg:ident:$ty:ty)*) $(-> $ret:ty)? $body:block)*
|
||||
}) => {
|
||||
pub trait $Trait $(<$($A: $T),+>)? : $($dep $(<$dtt>+)? $($dep2 $(<$dtt2>)?)*+)? Sized {
|
||||
$(fn $fn (&$self $(,$arg:$ty)*) $(-> $ret)? $body)*
|
||||
}
|
||||
impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for &_T_ {
|
||||
$(fn $fn (&$self $(,$arg:$ty)*) $(-> $ret)? { (*$self).$fn($($arg),*) })*
|
||||
}
|
||||
impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for &mut _T_ {
|
||||
$(fn $fn (&$self $(,$arg:$ty)*) $(-> $ret)? { (**$self).$fn($($arg),*) })*
|
||||
}
|
||||
impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::Arc<_T_> {
|
||||
$(fn $fn (&$self $(,$arg:$ty)*) $(-> $ret)? { (*$self).$fn($($arg),*) })*
|
||||
}
|
||||
impl<$($($A: $T,)+)?> $Trait $(<$($A),+>)? for Box<dyn $Trait $(<$($A),+>)?> {
|
||||
$(fn $fn (&$self $(,$arg:$ty)*) $(-> $ret)? { (**$self).$fn($($arg),*) })*
|
||||
}
|
||||
//impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for Option<_T_> {
|
||||
//$(fn $fn (&$self $(,$arg:$ty)*) -> $ret {
|
||||
//if let Some(this) = $self { this.$fn($($arg),*) } else { Ok(None) }
|
||||
//})*
|
||||
//}
|
||||
//impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::Mutex<_T_> {
|
||||
//$(fn $fn (&$self $(,$arg:$ty)*) -> $ret { (*$self).lock().unwrap().$fn($($arg),*) })*
|
||||
//}
|
||||
//impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::RwLock<_T_> {
|
||||
//$(fn $fn (&$self $(,$arg:$ty)*) -> $ret { $self.read().unwrap().$fn($($arg),*) })*
|
||||
//}
|
||||
});
|
||||
|
||||
/// Define a trait an implement it for various mutation-enabled wrapper types. */
|
||||
#[macro_export] macro_rules! flex_trait_mut (
|
||||
($Trait:ident $(<$($A:ident:$T:ident),+>)? {
|
||||
$(fn $fn:ident (&mut $self:ident $(, $arg:ident:$ty:ty)*) -> $ret:ty $body:block)*
|
||||
})=>{
|
||||
pub trait $Trait $(<$($A: $T),+>)? {
|
||||
$(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret $body)*
|
||||
}
|
||||
impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for &mut _T_ {
|
||||
$(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret { (*$self).$fn($($arg),*) })*
|
||||
}
|
||||
impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for Option<_T_> {
|
||||
$(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret {
|
||||
if let Some(this) = $self { this.$fn($($arg),*) } else { Ok(None) }
|
||||
})*
|
||||
}
|
||||
impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::Mutex<_T_> {
|
||||
$(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret { $self.get_mut().unwrap().$fn($($arg),*) })*
|
||||
}
|
||||
impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::Arc<::std::sync::Mutex<_T_>> {
|
||||
$(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret { $self.lock().unwrap().$fn($($arg),*) })*
|
||||
}
|
||||
impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::RwLock<_T_> {
|
||||
$(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret { $self.write().unwrap().$fn($($arg),*) })*
|
||||
}
|
||||
impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::Arc<::std::sync::RwLock<_T_>> {
|
||||
$(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret { $self.write().unwrap().$fn($($arg),*) })*
|
||||
}
|
||||
};
|
||||
);
|
||||
|
||||
/// Implement [`Debug`] in bulk.
|
||||
#[macro_export] macro_rules! impl_debug(($($S:ty|$self:ident,$w:ident|$body:block)*)=>{
|
||||
$(impl std::fmt::Debug for $S {
|
||||
fn fmt (&$self, $w: &mut std::fmt::Formatter) -> std::fmt::Result $body
|
||||
})* });
|
||||
|
||||
/// Implement [`From`] in bulk.
|
||||
#[macro_export] macro_rules! from(
|
||||
($(<$($lt:lifetime),+>)?|$state:ident:$Source:ty|$Target:ty=$cb:expr) => {
|
||||
impl $(<$($lt),+>)? From<$Source> for $Target {
|
||||
fn from ($state:$Source) -> Self { $cb }}};
|
||||
($($Struct:ty { $(
|
||||
$(<$($l:lifetime),* $($T:ident$(:$U:ident)?),*>)? ($source:ident: $From:ty) $expr:expr
|
||||
);+ $(;)? })*) => { $(
|
||||
$(impl $(<$($l),* $($T$(:$U)?),*>)? From<$From> for $Struct {
|
||||
fn from ($source: $From) -> Self { $expr } })+ )* }; );
|
||||
|
||||
/// Implement [Has].
|
||||
#[macro_export] macro_rules! has(($T:ty: |$self:ident : $S:ty| $x:expr) => {
|
||||
impl Has<$T> for $S {
|
||||
fn get (&$self) -> &$T { &$x }
|
||||
fn get_mut (&mut $self) -> &mut $T { &mut $x } } };);
|
||||
|
||||
/// Implement [MaybeHas].
|
||||
#[macro_export] macro_rules! maybe_has(
|
||||
($T:ty: |$self:ident : $S:ty| $x:block; $y:block $(;)?) => {
|
||||
impl MaybeHas<$T> for $S {
|
||||
fn get (&$self) -> Option<&$T> $x
|
||||
fn get_mut (&mut $self) -> Option<&mut $T> $y } };);
|
||||
45
core/src/lib.rs
Normal file
45
core/src/lib.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
mod core_macros;
|
||||
pub(crate) use std::error::Error;
|
||||
|
||||
/// Standard result type.
|
||||
pub type Usually<T> = Result<T, Box<dyn Error>>;
|
||||
|
||||
/// Standard optional result type.
|
||||
pub type Perhaps<T> = Result<Option<T>, Box<dyn Error>>;
|
||||
|
||||
/// Type-dispatched `get` and `get_mut`.
|
||||
pub trait Has<T>: Send + Sync { fn get (&self) -> &T; fn get_mut (&mut self) -> &mut T; }
|
||||
|
||||
/// Type-dispatched `get` and `get_mut` that return an [Option]-wrapped result.
|
||||
pub trait MaybeHas<T>: Send + Sync { fn get (&self) -> Option<&T>; fn get_mut (&mut self) -> Option<&mut T>; }
|
||||
|
||||
/// May compute a `RetVal` from `Args`.
|
||||
pub trait Eval<Args, RetVal> {
|
||||
/// A custom operation on [Args] that may return [Result::Err] or [Option::None].
|
||||
fn try_eval (&self, args: &Args) -> Perhaps<RetVal>;
|
||||
/// Invoke a custom operation, converting a `None` result to a custom `Box<dyn Error>`.
|
||||
fn eval <E: Into<Box<dyn std::error::Error>>> (&self, args: &Args, error: impl Fn()->E)
|
||||
-> Usually<RetVal>
|
||||
{
|
||||
match self.try_eval(args)? {
|
||||
Some(value) => Ok(value),
|
||||
_ => Result::Err(format!("Eval: {}", error().into()).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! as_ref {
|
||||
($T:ty: |$self:ident : $S:ty| $x:expr) => {
|
||||
impl AsRef<$T> for $S {
|
||||
fn as_ref (&$self) -> &$T { &$x }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn wrap_inc (index: usize, count: usize) -> usize {
|
||||
if count > 0 { (index + 1) % count } else { 0 }
|
||||
}
|
||||
|
||||
pub fn wrap_dec (index: usize, count: usize) -> usize {
|
||||
if count > 0 { index.overflowing_sub(1).0.min(count.saturating_sub(1)) } else { 0 }
|
||||
}
|
||||
|
|
@ -1,16 +1,18 @@
|
|||
[package]
|
||||
name = "tengri_dsl"
|
||||
edition = "2024"
|
||||
description = "UI metaframework, tiny S-expression-based DSL."
|
||||
version = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
[lib]
|
||||
path = "src/dsl.rs"
|
||||
|
||||
[dependencies]
|
||||
konst = { version = "0.3.16", features = [ "rust_1_83" ] }
|
||||
itertools = "0.14.0"
|
||||
thiserror = "2.0"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
tengri_core = { path = "../core" }
|
||||
konst = { workspace = true }
|
||||
const_panic = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tengri_tui = { path = "../tui" }
|
||||
|
|
|
|||
108
dsl/README.md
108
dsl/README.md
|
|
@ -1,14 +1,25 @@
|
|||
# `ket`
|
||||
[***dizzle***](https://codeberg.org/unspeaker/tengri/src/branch/main/dsl)
|
||||
is a means of adding a tiny interpreted domain-specific language to your programs.
|
||||
|
||||
**ket** is the configuration language of **tek**.
|
||||
it's based on [edn](https://en.wikipedia.org/wiki/Clojure#Extensible_Data_Notation)
|
||||
but without all the features.
|
||||
dizzle currently provides an s-expression based syntax.
|
||||
|
||||
## usage
|
||||
dizzle parses source code by means of the `Dsl`, `DslExpr` and `DslWord` traits.
|
||||
those are implemented for basic stringy types and their `Option` and `Result` wrapped analogs.
|
||||
to customize parsing, define and use your own traits on top of the provided ones.
|
||||
|
||||
### with `tengri_output`
|
||||
dizzle evaluates the parsed source code by means of the `DslNs` trait. the methods of
|
||||
this trait match literals, words, and expressions, against pre-defined lists. the
|
||||
`dsl_words` and `dsl_exprs` macros let you define those lists slightly less verbosely.
|
||||
|
||||
this is a `tengri_output` view layout defined using ket:
|
||||
## goals
|
||||
|
||||
* [x] const parse
|
||||
* [ ] live reload
|
||||
* [ ] serialize modified code back to original indentation
|
||||
|
||||
## examples
|
||||
|
||||
### in [`tengri_output`](../output)
|
||||
|
||||
```edn
|
||||
(bsp/s (fixed/y 2 :toolbar)
|
||||
|
|
@ -16,9 +27,7 @@ this is a `tengri_output` view layout defined using ket:
|
|||
(bsp/s :outputs (bsp/s :inputs (bsp/s :tracks :scenes)))))))
|
||||
```
|
||||
|
||||
### with `tengri_input`
|
||||
|
||||
this is a `tengri_input` keymap defined using ket:
|
||||
### in [`tengri_input`](../input)
|
||||
|
||||
```edn
|
||||
(@u undo 1)
|
||||
|
|
@ -29,66 +38,31 @@ this is a `tengri_input` keymap defined using ket:
|
|||
(@tab pool toggle)
|
||||
```
|
||||
|
||||
## tokens
|
||||
## implementation notes
|
||||
|
||||
ket has 4 "types", represented by variants of the `Value` enum:
|
||||
### `DslExpr` trait behavior
|
||||
|
||||
* `Num` - numeric literal
|
||||
* `Sym` - textual symbol
|
||||
* `Key` - textual key
|
||||
* `Exp` - parenthesized group of tokens
|
||||
this is the trait which differentiates "a thing" from
|
||||
"a thing that is many things".
|
||||
|
||||
### numeric literal
|
||||
|source |key|exp |head |tail |
|
||||
|---------------|---|-------|---------|---------------|
|
||||
|`a` |`a`|e0 |`a` |None |
|
||||
|`(a)` |e1 |`a` |`(a)` |None |
|
||||
|`a b c` |e2 |e0 |`a` |`b c` |
|
||||
|`(a b c)` |e1 |`a b c`|`(a b c)`| |
|
||||
|`(a b c) d e` |e1 |e3 |`(a b c)`|`d e` |
|
||||
|`a (b c d) e f`|e1 |e0 |`a` |`(b c d) e f` |
|
||||
|
||||
numbers are passed through as is. only non-negative integers are supported.
|
||||
* e0: Unexpected 'a'
|
||||
* e1: Unexpected '('
|
||||
* e2: Unexpected 'b'
|
||||
* e3: Unexpected 'd'
|
||||
|
||||
```edn
|
||||
0
|
||||
123456
|
||||
```
|
||||
### possible design for operator-based syntax
|
||||
|
||||
### keys
|
||||
|
||||
keys are the names of available operations. they look like this:
|
||||
|
||||
```edn
|
||||
simple-key
|
||||
multi-part/key
|
||||
```
|
||||
|
||||
keys are implemented by the underlying subsystem:
|
||||
|
||||
* in `tengri_output`, keys are names of layout primitives
|
||||
* in `tengri_input`, keys are names of commands
|
||||
|
||||
### symbols
|
||||
|
||||
symbols that start with `:` represent the names of variables
|
||||
provided by the `Context` trait - such as command parameters,
|
||||
or entire layout components:
|
||||
|
||||
```edn
|
||||
:symbol-name
|
||||
```
|
||||
|
||||
symbols that start with `@` represent keybindings.
|
||||
they are parsed in `tengri_tui` and look like this:
|
||||
|
||||
```edn
|
||||
@ctrl-alt-shift-space
|
||||
```
|
||||
|
||||
### parenthesized groups
|
||||
|
||||
parenthesized groups represent things like expressions
|
||||
or configuration statements, and look like this:
|
||||
|
||||
```edn
|
||||
(some-key :symbol (some/other-key @another-symbol 123) 456)
|
||||
```
|
||||
|
||||
## goals
|
||||
|
||||
* [ ] const parse
|
||||
* [ ] live reload
|
||||
* [ ] serialize modified code back to original indentation
|
||||
* replace: `(:= :name :value1 :valueN)`
|
||||
* append: `(:+ :name :value2 :valueN)`
|
||||
* filter: `(:- :name :value2 :valueN)`
|
||||
* map: `(:* :name op)`
|
||||
* reduce: `(:/ :name op)`
|
||||
|
|
|
|||
|
|
@ -1,130 +0,0 @@
|
|||
use crate::*;
|
||||
pub trait TryFromAtom<'a, T>: Sized {
|
||||
fn try_from_expr (_state: &'a T, _iter: TokenIter<'a>) -> Option<Self> { None }
|
||||
fn try_from_atom (state: &'a T, value: Value<'a>) -> Option<Self> {
|
||||
if let Exp(0, iter) = value { return Self::try_from_expr(state, iter) }
|
||||
None
|
||||
}
|
||||
}
|
||||
pub trait TryIntoAtom<T>: Sized {
|
||||
fn try_into_atom (&self) -> Option<Value>;
|
||||
}
|
||||
/// Map EDN tokens to parameters of a given type for a given context
|
||||
pub trait Context<U>: Sized {
|
||||
fn get (&self, _atom: &Value) -> Option<U> {
|
||||
None
|
||||
}
|
||||
fn get_or_fail (&self, atom: &Value) -> U {
|
||||
self.get(atom).expect("no value")
|
||||
}
|
||||
}
|
||||
impl<T: Context<U>, U> Context<U> for &T {
|
||||
fn get (&self, atom: &Value) -> Option<U> {
|
||||
(*self).get(atom)
|
||||
}
|
||||
fn get_or_fail (&self, atom: &Value) -> U {
|
||||
(*self).get_or_fail(atom)
|
||||
}
|
||||
}
|
||||
impl<T: Context<U>, U> Context<U> for Option<T> {
|
||||
fn get (&self, atom: &Value) -> Option<U> {
|
||||
self.as_ref().map(|s|s.get(atom)).flatten()
|
||||
}
|
||||
fn get_or_fail (&self, atom: &Value) -> U {
|
||||
self.as_ref().map(|s|s.get_or_fail(atom)).expect("no provider")
|
||||
}
|
||||
}
|
||||
/// Implement `Context` for a context and type.
|
||||
#[macro_export] macro_rules! provide {
|
||||
// Provide a value to the EDN template
|
||||
($type:ty:|$self:ident:$State:ty|{ $($pat:pat => $expr:expr),* $(,)? }) => {
|
||||
impl Context<$type> for $State {
|
||||
#[allow(unreachable_code)]
|
||||
fn get (&$self, atom: &Value) -> Option<$type> {
|
||||
use Value::*;
|
||||
Some(match atom { $(Sym($pat) => $expr,)* _ => return None })
|
||||
}
|
||||
}
|
||||
};
|
||||
// Provide a value more generically
|
||||
($lt:lifetime: $type:ty:|$self:ident:$State:ty|{ $($pat:pat => $expr:expr),* $(,)? }) => {
|
||||
impl<$lt> Context<$lt, $type> for $State {
|
||||
#[allow(unreachable_code)]
|
||||
fn get (&$lt $self, atom: &Value) -> Option<$type> {
|
||||
use Value::*;
|
||||
Some(match atom { $(Sym($pat) => $expr,)* _ => return None })
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
/// Implement `Context` for a context and numeric type.
|
||||
///
|
||||
/// This enables support for numeric literals.
|
||||
#[macro_export] macro_rules! provide_num {
|
||||
// Provide a value that may also be a numeric literal in the EDN, to a generic implementation.
|
||||
($type:ty:|$self:ident:<$T:ident:$Trait:path>|{ $($pat:pat => $expr:expr),* $(,)? }) => {
|
||||
impl<$T: $Trait> Context<$type> for $T {
|
||||
fn get (&$self, atom: &Value) -> Option<$type> {
|
||||
use Value::*;
|
||||
Some(match atom { $(Sym($pat) => $expr,)* Num(n) => *n as $type, _ => return None })
|
||||
}
|
||||
}
|
||||
};
|
||||
// Provide a value that may also be a numeric literal in the EDN, to a concrete implementation.
|
||||
($type:ty:|$self:ident:$State:ty|{ $($pat:pat => $expr:expr),* $(,)? }) => {
|
||||
impl Context<$type> for $State {
|
||||
fn get (&$self, atom: &Value) -> Option<$type> {
|
||||
use Value::*;
|
||||
Some(match atom { $(Sym($pat) => $expr,)* Num(n) => *n as $type, _ => return None })
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
/// Implement `Context` for a context and the boolean type.
|
||||
///
|
||||
/// This enables support for boolean literals.
|
||||
#[macro_export] macro_rules! provide_bool {
|
||||
// Provide a value that may also be a numeric literal in the EDN, to a generic implementation.
|
||||
($type:ty:|$self:ident:<$T:ident:$Trait:path>|{ $($pat:pat => $expr:expr),* $(,)? }) => {
|
||||
impl<$T: $Trait> Context<$type> for $T {
|
||||
fn get (&$self, atom: &Value) -> Option<$type> {
|
||||
use Value::*;
|
||||
Some(match atom {
|
||||
Num(n) => match *n { 0 => false, _ => true },
|
||||
Sym(":false") | Sym(":f") => false,
|
||||
Sym(":true") | Sym(":t") => true,
|
||||
$(Sym($pat) => $expr,)*
|
||||
_ => return Context::get(self, atom)
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
// Provide a value that may also be a numeric literal in the EDN, to a concrete implementation.
|
||||
($type:ty:|$self:ident:$State:ty|{ $($pat:pat => $expr:expr),* $(,)? }) => {
|
||||
impl Context<$type> for $State {
|
||||
fn get (&$self, atom: &Value) -> Option<$type> {
|
||||
use Value::*;
|
||||
Some(match atom {
|
||||
Num(n) => match *n { 0 => false, _ => true },
|
||||
Sym(":false") | Sym(":f") => false,
|
||||
Sym(":true") | Sym(":t") => true,
|
||||
$(Sym($pat) => $expr,)*
|
||||
_ => return None
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
#[cfg(test)] #[test] fn test_edn_context () {
|
||||
struct Test;
|
||||
provide_bool!(bool: |self: Test|{
|
||||
":provide-bool" => true
|
||||
});
|
||||
let test = Test;
|
||||
assert_eq!(test.get(&Value::Sym(":false")), Some(false));
|
||||
assert_eq!(test.get(&Value::Sym(":true")), Some(true));
|
||||
assert_eq!(test.get(&Value::Sym(":provide-bool")), Some(true));
|
||||
assert_eq!(test.get(&Value::Sym(":missing-bool")), None);
|
||||
assert_eq!(test.get(&Value::Num(0)), Some(false));
|
||||
assert_eq!(test.get(&Value::Num(1)), Some(true));
|
||||
}
|
||||
227
dsl/src/dsl.rs
Normal file
227
dsl/src/dsl.rs
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
//#![feature(adt_const_params)]
|
||||
//#![feature(type_alias_impl_trait)]
|
||||
#![feature(if_let_guard)]
|
||||
#![feature(impl_trait_in_fn_trait_return)]
|
||||
#![feature(const_precise_live_drops)]
|
||||
#![feature(type_alias_impl_trait)]
|
||||
extern crate const_panic;
|
||||
use const_panic::PanicFmt;
|
||||
use std::fmt::Debug;
|
||||
pub(crate) use ::{
|
||||
std::sync::Arc,
|
||||
//std::error::Error,
|
||||
konst::iter::for_each,
|
||||
konst::string::{str_from, str_range, char_indices},
|
||||
thiserror::Error,
|
||||
tengri_core::*
|
||||
};
|
||||
pub(crate) use self::DslError::*;
|
||||
mod dsl_error; pub use self::dsl_error::*;
|
||||
mod dsl_ns; pub use self::dsl_ns::*;
|
||||
mod dsl_word; pub use self::dsl_word::*;
|
||||
mod dsl_expr; pub use self::dsl_expr::*;
|
||||
mod dsl_text; pub use self::dsl_text::*;
|
||||
#[cfg(test)] mod dsl_test;
|
||||
|
||||
// Trait that designates any string-like as potentially parsable DSL.
|
||||
pub trait Dsl: Debug + Send + Sync {
|
||||
fn src (&self) -> DslPerhaps<&str> { unreachable!("Dsl::src default impl") }
|
||||
}
|
||||
impl<'x> Dsl for &'x str {
|
||||
fn src (&self) -> DslPerhaps<&str> { Ok(Some(self)) }
|
||||
}
|
||||
impl Dsl for Arc<str> {
|
||||
fn src (&self) -> DslPerhaps<&str> { Ok(Some(self.as_ref())) }
|
||||
}
|
||||
impl<D: Dsl> Dsl for &D {
|
||||
fn src (&self) -> DslPerhaps<&str> { (*self).src() }
|
||||
}
|
||||
impl<D: Dsl> Dsl for &mut D {
|
||||
fn src (&self) -> DslPerhaps<&str> { (**self).src() }
|
||||
}
|
||||
impl<D: Dsl> Dsl for Option<D> {
|
||||
fn src (&self) -> DslPerhaps<&str> {Ok(if let Some(dsl) = self { dsl.src()? } else { None })}
|
||||
}
|
||||
impl<D: Dsl> Dsl for Result<D, DslError> {
|
||||
fn src (&self) -> DslPerhaps<&str> {match self {Ok(dsl) => Ok(dsl.src()?), Err(e) => Err(*e)}}
|
||||
}
|
||||
|
||||
/// DSL-specific result type.
|
||||
pub type DslResult<T> = Result<T, DslError>;
|
||||
|
||||
/// DSL-specific optional result type.
|
||||
pub type DslPerhaps<T> = Result<Option<T>, DslError>;
|
||||
|
||||
pub const fn peek (src: &str) -> DslPerhaps<&str> {
|
||||
Ok(Some(if let Ok(Some(expr)) = expr_peek(src) { expr } else
|
||||
if let Ok(Some(word)) = word_peek(src) { word } else
|
||||
if let Ok(Some(text)) = text_peek(src) { text } else
|
||||
if let Err(e) = no_trailing_non_space(src, 0, Some("peek")) { return Err(e) }
|
||||
else { return Ok(None) }))
|
||||
}
|
||||
|
||||
pub const fn seek (src: &str) -> DslPerhaps<(usize, usize)> {
|
||||
Ok(Some(if let Ok(Some(expr)) = expr_seek(src) { expr } else
|
||||
if let Ok(Some(word)) = word_seek(src) { word } else
|
||||
if let Ok(Some(text)) = text_seek(src) { text } else
|
||||
if let Err(e) = no_trailing_non_space(src, 0, Some("seek")) { return Err(e) }
|
||||
else { return Ok(None) }))
|
||||
}
|
||||
|
||||
pub const fn peek_tail (src: &str) -> DslPerhaps<&str> {
|
||||
match seek(src) {
|
||||
Err(e) => Err(e),
|
||||
Ok(None) => Ok(None),
|
||||
Ok(Some((start, length))) => {
|
||||
let tail = str_range(src, start + length, src.len());
|
||||
for_each!((_i, c) in char_indices(tail) => if !is_space(c) { return Ok(Some(tail)) });
|
||||
Ok(None)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn expr_peek_inner (src: &str) -> DslPerhaps<&str> {
|
||||
match expr_peek(src) {
|
||||
Ok(Some(peeked)) => {
|
||||
let len = peeked.len();
|
||||
let start = if len > 0 { 1 } else { 0 };
|
||||
Ok(Some(str_range(src, start, start + len.saturating_sub(2))))
|
||||
},
|
||||
e => e
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn expr_peek_inner_only (src: &str) -> DslPerhaps<&str> {
|
||||
match expr_seek(src) {
|
||||
Err(e) => Err(e),
|
||||
Ok(None) => Ok(None),
|
||||
Ok(Some((start, length))) => {
|
||||
if let Err(e) = no_trailing_non_space(src, start + length, Some("expr_peek_inner_only")) {
|
||||
Err(e)
|
||||
} else {
|
||||
let peeked = str_range(src, start, start + length);
|
||||
let len = peeked.len();
|
||||
let start = if len > 0 { 1 } else { 0 };
|
||||
Ok(Some(str_range(peeked, start, start + len.saturating_sub(2))))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn is_space (c: char) -> bool {
|
||||
matches!(c, ' '|'\n'|'\r'|'\t')
|
||||
}
|
||||
|
||||
pub const fn no_trailing_non_space (
|
||||
src: &str, offset: usize, context: Option<&'static str>
|
||||
) -> DslResult<()> {
|
||||
Ok(for_each!((i, c) in char_indices(str_range(src, offset, src.len())) => if !is_space(c) {
|
||||
return Err(Unexpected(c, Some(offset + i), if let Some(context) = context {
|
||||
Some(context)
|
||||
} else {
|
||||
Some("trailing non-space")
|
||||
}))
|
||||
}))
|
||||
}
|
||||
|
||||
pub const fn is_word_char (c: char) -> bool {
|
||||
matches!(c, 'a'..='z'|'A'..='Z'|'0'..='9'|'-'|'/'|'@'|':')
|
||||
}
|
||||
|
||||
pub const fn is_word_end (c: char) -> bool {
|
||||
is_space(c) || is_expr_end(c)
|
||||
}
|
||||
|
||||
pub const fn is_text_start (c: char) -> bool {
|
||||
matches!(c, '"')
|
||||
}
|
||||
|
||||
pub const fn is_text_end (c: char) -> bool {
|
||||
matches!(c, '"')
|
||||
}
|
||||
|
||||
pub const fn is_expr_start (c: char) -> bool {
|
||||
c == '('
|
||||
}
|
||||
|
||||
pub const fn is_expr_end (c: char) -> bool {
|
||||
c == ')'
|
||||
}
|
||||
|
||||
pub const fn is_digit (c: char) -> bool {
|
||||
matches!(c, '0'..='9')
|
||||
}
|
||||
|
||||
pub const fn to_number (digits: &str) -> Result<usize, DslError> {
|
||||
let mut iter = char_indices(digits);
|
||||
let mut value = 0;
|
||||
while let Some(((_, c), next)) = iter.next() {
|
||||
match to_digit(c) {
|
||||
Ok(digit) => value = 10 * value + digit,
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
iter = next;
|
||||
}
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
pub const fn to_digit (c: char) -> Result<usize, DslError> {
|
||||
Ok(match c {
|
||||
'0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4,
|
||||
'5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9,
|
||||
_ => return Err(Unexpected(c, None, Some("parse digit")))
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn ok_flat <T> (x: Option<DslPerhaps<T>>) -> DslPerhaps<T> {
|
||||
Ok(x.transpose()?.flatten())
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! dsl_type (($T:ident { $($trait:tt)* } {
|
||||
pub const fn $peek:ident $($_1:tt)?;
|
||||
pub const fn $peek_only:ident $($_2:tt)?;
|
||||
pub const fn $seek:ident $($_3:tt)?;
|
||||
pub const fn $seek_start:ident ($source1:ident) $body1:block
|
||||
pub const fn $seek_length:ident ($source2:ident) $body2:block
|
||||
})=>{
|
||||
pub trait $T: Dsl { $($trait)* }
|
||||
impl<D: Dsl + ?Sized> $T for D {}
|
||||
pub const fn $seek_start ($source1: &str) -> DslPerhaps<usize> $body1
|
||||
pub const fn $seek_length ($source2: &str) -> DslPerhaps<usize> $body2
|
||||
/// Find a start and length corresponding to a syntax token.
|
||||
pub const fn $seek (source: &str) -> DslPerhaps<(usize, usize)> {
|
||||
match $seek_start(source) {
|
||||
Err(e) => Err(e),
|
||||
Ok(None) => Ok(None),
|
||||
Ok(Some(start)) => match $seek_length(str_from(source, start)) {
|
||||
Ok(Some(length)) => Ok(Some((start, length))),
|
||||
Ok(None) => Ok(None),
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
}
|
||||
}
|
||||
/// Find a slice corrensponding to a syntax token.
|
||||
pub const fn $peek (source: &str) -> DslPerhaps<&str> {
|
||||
match $seek(source) {
|
||||
Err(e) => Err(e),
|
||||
Ok(None) => Ok(None),
|
||||
Ok(Some((start, length))) => Ok(Some(str_range(source, start, start + length))),
|
||||
}
|
||||
}
|
||||
/// Find a slice corrensponding to a syntax token
|
||||
/// but return an error if it isn't the only thing
|
||||
/// in the source.
|
||||
pub const fn $peek_only (source: &str) -> DslPerhaps<&str> {
|
||||
match $seek(source) {
|
||||
Err(e) => Err(e),
|
||||
Ok(None) => Ok(None),
|
||||
Ok(Some((start, length))) => {
|
||||
if let Err(e) = no_trailing_non_space(source, start + length, Some("peek_only")) {
|
||||
Err(e)
|
||||
} else {
|
||||
Ok(Some(str_range(source, start, start + length)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -1,15 +1,25 @@
|
|||
use crate::*;
|
||||
use thiserror::Error;
|
||||
pub type ParseResult<T> = Result<T, ParseError>;
|
||||
#[derive(Error, Debug, Copy, Clone, PartialEq)] pub enum ParseError {
|
||||
|
||||
/// DSL-specific error codes.
|
||||
#[derive(Error, Debug, Copy, Clone, PartialEq, PanicFmt)]
|
||||
pub enum DslError {
|
||||
|
||||
#[error("parse failed: not implemented")]
|
||||
Unimplemented,
|
||||
|
||||
#[error("parse failed: empty")]
|
||||
Empty,
|
||||
|
||||
#[error("parse failed: incomplete")]
|
||||
Incomplete,
|
||||
|
||||
#[error("parse failed: unexpected character '{0}'")]
|
||||
Unexpected(char),
|
||||
Unexpected(char, Option<usize>, Option<&'static str>),
|
||||
|
||||
#[error("parse failed: error #{0}")]
|
||||
Code(u8),
|
||||
|
||||
#[error("end reached")]
|
||||
End
|
||||
|
||||
}
|
||||
87
dsl/src/dsl_expr.rs
Normal file
87
dsl/src/dsl_expr.rs
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
use crate::*;
|
||||
|
||||
pub type GetDslExpr<'a, S, T> = for<'b> fn(&'a S, &'b str)->Perhaps<T>;
|
||||
|
||||
pub type DslExprs<'a, S, T> = &'a [(&'a str, GetDslExpr<'a, S, T>)];
|
||||
|
||||
dsl_type!(DslExpr {
|
||||
fn expr (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(expr_peek_inner_only))}
|
||||
fn head (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(peek))}
|
||||
fn tail (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(peek_tail))}
|
||||
/// my other car is a cdr :<
|
||||
fn each (&self, mut cb: impl FnMut(&str)->Usually<()>) -> Usually<()> {
|
||||
Ok(if let Some(head) = self.head()? {
|
||||
cb(head)?;
|
||||
if let Some(tail) = self.tail()? {
|
||||
tail.each(cb)?;
|
||||
}
|
||||
})
|
||||
}
|
||||
} {
|
||||
pub const fn expr_peek [generated];
|
||||
pub const fn expr_peek_only [generated];
|
||||
pub const fn expr_seek [generated];
|
||||
pub const fn expr_seek_start (src) {
|
||||
for_each!((i, c) in char_indices(src) =>
|
||||
if is_expr_start(c) { return Ok(Some(i)) } else
|
||||
if !is_space(c) { return Err(Unexpected(c, Some(i), Some("expected expression start"))) });
|
||||
Ok(None)
|
||||
}
|
||||
pub const fn expr_seek_length (src) {
|
||||
let mut depth = 0;
|
||||
for_each!((i, c) in char_indices(src) =>
|
||||
if is_expr_start(c) { depth += 1; } else
|
||||
if is_expr_end(c) {
|
||||
if depth == 0 {
|
||||
return Err(Unexpected(c, Some(i), Some("expected expression end")))
|
||||
} else if depth == 1 {
|
||||
return Ok(Some(i + 1))
|
||||
} else {
|
||||
depth -= 1;
|
||||
}
|
||||
});
|
||||
Err(Incomplete)
|
||||
}
|
||||
});
|
||||
|
||||
#[macro_export] macro_rules!dsl_exprs(($l:lifetime |$state:ident|->$Type:ty$({
|
||||
$($name:literal ($($arg:ident:$ty:ty),* $(,)?) => $body:expr),* $(,)?
|
||||
})?)=>{
|
||||
const EXPRS: DslExprs<$l, Self, $Type> = &[$( $({
|
||||
let get: GetDslExpr<$l, Self, $Type> = |$state: &$l Self, tail_base|{
|
||||
let tail = tail_base;
|
||||
$(
|
||||
let head = tail.head()?.unwrap_or_default();
|
||||
let tail = tail.tail()?.unwrap_or_default();
|
||||
let $arg: $ty = if let Some(arg) = $state.from(&head)? {
|
||||
arg
|
||||
} else {
|
||||
return Err(format!("{}: arg \"{}\" ({}) got: {head} {tail}",
|
||||
$name,
|
||||
stringify!($arg),
|
||||
stringify!($ty),
|
||||
).into())
|
||||
};
|
||||
)*
|
||||
Ok(Some($body/* as $Type*/))
|
||||
};
|
||||
($name, get)
|
||||
}),* )? ];
|
||||
});
|
||||
|
||||
pub trait DslNsExprs<'a, T: 'a>: 'a {
|
||||
/// Known expressions.
|
||||
const EXPRS: DslExprs<'a, Self, T> = &[];
|
||||
/// Resolve an expression if known.
|
||||
fn from_expr (&'a self, dsl: impl DslExpr + 'a) -> Perhaps<T> {
|
||||
if let Some(dsl) = dsl.expr()? {
|
||||
let head = dsl.head()?;
|
||||
for (key, get) in Self::EXPRS.iter() {
|
||||
if Some(*key) == head {
|
||||
return get(self, dsl.tail()?.unwrap_or(""))
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
46
dsl/src/dsl_ns.rs
Normal file
46
dsl/src/dsl_ns.rs
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
use crate::*;
|
||||
|
||||
pub trait DslNs<'a, T: 'a>: DslNsWords<'a, T> + DslNsExprs<'a, T> {
|
||||
/// Resolve an expression or symbol.
|
||||
fn from (&'a self, dsl: impl Dsl + 'a) -> Perhaps<T> {
|
||||
if let Ok(Some(literal)) = self.from_literal(&dsl) {
|
||||
Ok(Some(literal))
|
||||
} else if let Ok(Some(meaning)) = self.from_word(&dsl) {
|
||||
Ok(Some(meaning))
|
||||
} else {
|
||||
self.from_expr(dsl)
|
||||
}
|
||||
}
|
||||
/// Resolve as literal if valid.
|
||||
fn from_literal (&self, _: impl Dsl) -> Perhaps<T> {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! dsl_ns {
|
||||
($State:ty: $Type:ty {
|
||||
$(literal = |$dsl:ident| $literal:expr;)?
|
||||
$(word = |$state_w:ident| {
|
||||
$($word:literal => $body_w:expr),* $(,)?
|
||||
};)?
|
||||
$(expr = |$state_e:ident| {
|
||||
$($head:literal $args:tt => $body_e:expr),* $(,)?
|
||||
};)?
|
||||
}) => {
|
||||
impl<'a> DslNs<'a, $Type> for $State {
|
||||
$(fn from_literal (&self, $dsl: impl Dsl) -> Perhaps<$Type> {
|
||||
$literal
|
||||
})?
|
||||
}
|
||||
impl<'a> DslNsWords<'a, $Type> for $State {
|
||||
$(dsl_words! { 'a |$state_w| -> $Type {
|
||||
$($word => $body_w),*
|
||||
} })?
|
||||
}
|
||||
impl<'a> DslNsExprs<'a, $Type> for $State {
|
||||
$(dsl_exprs! { 'a |$state_e| -> $Type {
|
||||
$($head $args => $body_e),*
|
||||
} })?
|
||||
}
|
||||
}
|
||||
}
|
||||
99
dsl/src/dsl_test.rs
Normal file
99
dsl/src/dsl_test.rs
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
use crate::*;
|
||||
macro_rules!is_some(($expr:expr, $val:expr)=>{assert_eq!($expr, Ok(Some($val)))};);
|
||||
macro_rules!is_none(($expr:expr)=>{assert_eq!($expr, Ok(None))};);
|
||||
macro_rules!is_err(($expr:expr)=>{assert!($expr.is_err())};
|
||||
($expr:expr, $err:expr)=>{assert_eq!($expr, Err($err))};);
|
||||
#[test] fn test_expr () -> Result<(), DslError> {
|
||||
let e0 = DslError::Unexpected('a', None, None);
|
||||
let e1 = DslError::Unexpected('(', None, None);
|
||||
let e2 = DslError::Unexpected('b', None, None);
|
||||
let e3 = DslError::Unexpected('d', None, None);
|
||||
let check = |src: &str, word, expr, head, tail|{
|
||||
assert_eq!(src.word(), word, "{src}");
|
||||
assert_eq!(src.expr(), expr, "{src}");
|
||||
assert_eq!(src.head(), head, "{src}");
|
||||
assert_eq!(src.tail(), tail, "{src}");
|
||||
};
|
||||
check("a", Ok(Some("a")), Err(e0), Ok(Some("a")), Ok(None));
|
||||
check("(a)", Err(e1), Ok(Some("a")), Ok(Some("(a)")), Ok(None));
|
||||
check("a b c", Err(e2), Err(e0), Ok(Some("a")), Ok(Some("b c")));
|
||||
check("(a b c)", Err(e1), Ok(Some("a b c")), Ok(Some("(a b c)")), Ok(None));
|
||||
check("(a b c) d e f", Err(e1), Err(e3), Ok(Some("(a b c)")), Ok(Some("d e f")));
|
||||
check("a (b c d) e f", Err(e1), Err(e0), Ok(Some("a")), Ok(Some("(b c d) e f")));
|
||||
|
||||
is_some!(word_peek("\n :view/transport"), ":view/transport");
|
||||
|
||||
assert!(is_space(' '));
|
||||
assert!(!is_word_char(' '));
|
||||
assert!(is_word_char('f'));
|
||||
|
||||
is_some!(word_seek_start("foo"), 0);
|
||||
is_some!(word_seek_start("foo "), 0);
|
||||
is_some!(word_seek_start(" foo "), 1);
|
||||
is_some!(word_seek_length(&" foo "[1..]), 3);
|
||||
is_some!(word_seek("foo"), (0, 3));
|
||||
is_some!(word_peek("foo"), "foo");
|
||||
is_some!(word_seek("foo "), (0, 3));
|
||||
is_some!(word_peek("foo "), "foo");
|
||||
is_some!(word_seek(" foo "), (1, 3));
|
||||
is_some!(word_peek(" foo "), "foo");
|
||||
|
||||
is_err!("(foo)".word());
|
||||
is_err!("foo".expr());
|
||||
|
||||
is_some!("(foo)".expr(), "foo");
|
||||
is_some!("(foo)".head(), "(foo)");
|
||||
is_none!("(foo)".tail());
|
||||
|
||||
is_some!("(foo bar baz)".expr(), "foo bar baz");
|
||||
is_some!("(foo bar baz)".head(), "(foo bar baz)");
|
||||
is_none!("(foo bar baz)".tail());
|
||||
|
||||
is_some!("(foo bar baz)".expr().head(), "foo");
|
||||
is_some!("(foo bar baz)".expr().tail(), "bar baz");
|
||||
is_some!("(foo bar baz)".expr().tail().head(), "bar");
|
||||
is_some!("(foo bar baz)".expr().tail().tail(), "baz");
|
||||
|
||||
is_err!("foo".expr());
|
||||
is_some!("foo".word(), "foo");
|
||||
is_some!(" foo".word(), "foo");
|
||||
is_some!(" foo ".word(), "foo");
|
||||
|
||||
is_some!(" foo ".head(), "foo");
|
||||
//assert_eq!(" foo ".head().head(), Ok(None));
|
||||
is_none!(" foo ".head().tail());
|
||||
is_none!(" foo ".tail());
|
||||
is_none!(" foo ".tail().head());
|
||||
is_none!(" foo ".tail().tail());
|
||||
|
||||
assert_eq!(" foo bar ".head(), Ok(Some("foo")));
|
||||
//assert_eq!(" foo bar ".head().head(), Ok(None));
|
||||
assert_eq!(" foo bar ".head().tail(), Ok(None));
|
||||
assert_eq!(" foo bar ".tail(), Ok(Some(" bar ")));
|
||||
assert_eq!(" foo bar ".tail().head(), Ok(Some("bar")));
|
||||
assert_eq!(" foo bar ".tail().tail(), Ok(None));
|
||||
|
||||
assert_eq!(" (foo) ".head(), Ok(Some("(foo)")));
|
||||
//assert_eq!(" (foo) ".head().head(), Ok(Some("foo")));
|
||||
//assert_eq!(" (foo) ".head().head().head(), Ok(None));
|
||||
assert_eq!(" (foo) ".tail(), Ok(None));
|
||||
|
||||
assert_eq!(" (foo) (bar) ".head(), Ok(Some("(foo)")));
|
||||
//assert_eq!(" (foo) (bar) ".head().head(), Ok(Some("foo")));
|
||||
//assert_eq!(" (foo) (bar) ".head().head().head(), Ok(None));
|
||||
is_some!(" (foo) (bar) ".tail(), " (bar) ");
|
||||
is_some!(" (foo) (bar) ".tail().head(), "(bar)");
|
||||
is_some!(" (foo) (bar) ".tail().head().head(), "(bar)");
|
||||
is_some!(" (foo) (bar) ".tail().head().expr(), "bar");
|
||||
is_some!(" (foo) (bar) ".tail().head().expr().head(), "bar");
|
||||
|
||||
is_some!(" (foo bar baz) ".head(), "(foo bar baz)");
|
||||
is_some!(" (foo bar baz) ".head().head(), "(foo bar baz)");
|
||||
is_some!(" (foo bar baz) ".expr(), "foo bar baz");
|
||||
is_some!(" (foo bar baz) ".expr().head(), "foo");
|
||||
is_some!(" (foo bar baz) ".expr().tail(), "bar baz");
|
||||
is_some!(" (foo bar baz) ".expr().tail().head(), "bar");
|
||||
is_some!(" (foo bar baz) ".expr().tail().tail(), "baz");
|
||||
is_none!(" (foo bar baz) ".tail());
|
||||
Ok(())
|
||||
}
|
||||
20
dsl/src/dsl_text.rs
Normal file
20
dsl/src/dsl_text.rs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
use crate::*;
|
||||
|
||||
dsl_type!(DslText {
|
||||
fn text (&self) -> DslPerhaps<&str> { ok_flat(self.src()?.map(text_peek_only)) }
|
||||
} {
|
||||
pub const fn text_peek [generated];
|
||||
pub const fn text_peek_only [generated];
|
||||
pub const fn text_seek [generated];
|
||||
pub const fn text_seek_start (src) {
|
||||
for_each!((i, c) in char_indices(src) =>
|
||||
if is_text_start(c) { return Ok(Some(i)) } else
|
||||
if !is_space(c) { return Err(Unexpected(c, Some(i), None)) });
|
||||
Ok(None)
|
||||
}
|
||||
pub const fn text_seek_length (src) {
|
||||
for_each!((i, c) in char_indices(src) =>
|
||||
if is_text_end(c) { return Ok(Some(i)) });
|
||||
Ok(None)
|
||||
}
|
||||
});
|
||||
49
dsl/src/dsl_word.rs
Normal file
49
dsl/src/dsl_word.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
use crate::*;
|
||||
|
||||
pub type GetDslWord<'a, S, T> = fn(&'a S)->Perhaps<T>;
|
||||
|
||||
pub type DslWords<'a, S, T> = &'a [(&'a str, GetDslWord<'a, S, T>)];
|
||||
|
||||
dsl_type!(DslWord {
|
||||
fn word (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(word_peek_only))}
|
||||
} {
|
||||
pub const fn word_peek [generated];
|
||||
pub const fn word_peek_only [generated];
|
||||
pub const fn word_seek [generated];
|
||||
pub const fn word_seek_start (src) {
|
||||
for_each!((i, c) in char_indices(src) => if
|
||||
is_word_char(c) { return Ok(Some(i)) } else
|
||||
if !is_space(c) { return Err(Unexpected(c, Some(i), Some("word_seek_start"))) });
|
||||
Ok(None)
|
||||
}
|
||||
pub const fn word_seek_length (src) {
|
||||
for_each!((i, c) in char_indices(src) => if !is_word_char(c) { return Ok(Some(i)) });
|
||||
Ok(Some(src.len()))
|
||||
}
|
||||
});
|
||||
|
||||
#[macro_export] macro_rules!dsl_words(($l:lifetime |$state:ident|->$Type:ty$({
|
||||
$($word:literal => $body:expr),* $(,)?
|
||||
})?)=>{
|
||||
const WORDS: DslWords<$l, Self, $Type> = &[$( $({
|
||||
let get: GetDslWord<$l, Self, $Type> = |$state: &$l Self|Ok(Some($body));
|
||||
($word, get)
|
||||
}),* )?];
|
||||
});
|
||||
|
||||
pub trait DslNsWords<'a, T: 'a>: 'a {
|
||||
/// Known symbols.
|
||||
const WORDS: DslWords<'a, Self, T> = &[];
|
||||
/// Resolve a symbol if known.
|
||||
fn from_word (&'a self, dsl: impl DslWord) -> Perhaps<T> {
|
||||
if let Some(dsl) = dsl.word()? {
|
||||
for (key, get) in Self::WORDS.iter() {
|
||||
if dsl == *key {
|
||||
let value = get(self);
|
||||
return value
|
||||
}
|
||||
}
|
||||
}
|
||||
return Ok(None)
|
||||
}
|
||||
}
|
||||
144
dsl/src/iter.rs
144
dsl/src/iter.rs
|
|
@ -1,144 +0,0 @@
|
|||
//! The token iterator [TokenIter] allows you to get the
|
||||
//! general-purpose syntactic [Token]s represented by the source text.
|
||||
//!
|
||||
//! Both iterators are `peek`able:
|
||||
//!
|
||||
//! ```
|
||||
//! let src = include_str!("../test.edn");
|
||||
//! let mut view = tengri_dsl::TokenIter::new(src);
|
||||
//! assert_eq!(view.0.0, src);
|
||||
//! assert_eq!(view.peek(), view.0.peek())
|
||||
//! ```
|
||||
use crate::*;
|
||||
/// Provides a native [Iterator] API over the [ConstIntoIter] [SourceIter]
|
||||
/// [TokenIter::next] returns just the [Token] and mutates `self`,
|
||||
/// instead of returning an updated version of the struct as [SourceIter::next] does.
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq)] pub struct TokenIter<'a>(pub SourceIter<'a>);
|
||||
impl<'a> TokenIter<'a> {
|
||||
pub const fn new (source: &'a str) -> Self { Self(SourceIter::new(source)) }
|
||||
pub const fn peek (&self) -> Option<Token<'a>> { self.0.peek() }
|
||||
}
|
||||
impl<'a> Iterator for TokenIter<'a> {
|
||||
type Item = Token<'a>;
|
||||
fn next (&mut self) -> Option<Token<'a>> {
|
||||
self.0.next().map(|(item, rest)|{self.0 = rest; item})
|
||||
}
|
||||
}
|
||||
/// Owns a reference to the source text.
|
||||
/// [SourceIter::next] emits subsequent pairs of:
|
||||
/// * a [Token] and
|
||||
/// * the source text remaining
|
||||
/// * [ ] TODO: maybe [SourceIter::next] should wrap the remaining source in `Self` ?
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq)] pub struct SourceIter<'a>(pub &'a str);
|
||||
const_iter!(<'a>|self: SourceIter<'a>| => Token<'a> => self.next_mut().map(|(result, _)|result));
|
||||
impl<'a> From<&'a str> for SourceIter<'a> {fn from (source: &'a str) -> Self{Self::new(source)}}
|
||||
impl<'a> SourceIter<'a> {
|
||||
pub const fn new (source: &'a str) -> Self { Self(source) }
|
||||
pub const fn chomp (&self, index: usize) -> Self { Self(split_at(self.0, index).1) }
|
||||
pub const fn next (mut self) -> Option<(Token<'a>, Self)> { Self::next_mut(&mut self) }
|
||||
pub const fn peek (&self) -> Option<Token<'a>> { peek_src(self.0) }
|
||||
pub const fn next_mut (&mut self) -> Option<(Token<'a>, Self)> {
|
||||
match self.peek() {
|
||||
Some(token) => Some((token, self.chomp(token.end()))),
|
||||
None => None
|
||||
}
|
||||
}
|
||||
}
|
||||
pub const fn peek_src <'a> (source: &'a str) -> Option<Token<'a>> {
|
||||
let mut token: Token<'a> = Token::new(source, 0, 0, Nil);
|
||||
iterate!(char_indices(source) => (start, c) => token = match token.value() {
|
||||
Err(_) => return Some(token),
|
||||
Nil => match c {
|
||||
' '|'\n'|'\r'|'\t' =>
|
||||
token.grow(),
|
||||
'(' =>
|
||||
Token::new(source, start, 1, Exp(1, TokenIter::new(str_range(source, start, start + 1)))),
|
||||
':'|'@' =>
|
||||
Token::new(source, start, 1, Sym(str_range(source, start, start + 1))),
|
||||
'/'|'a'..='z' =>
|
||||
Token::new(source, start, 1, Key(str_range(source, start, start + 1))),
|
||||
'0'..='9' =>
|
||||
Token::new(source, start, 1, match to_digit(c) {
|
||||
Ok(c) => Value::Num(c),
|
||||
Result::Err(e) => Value::Err(e)
|
||||
}),
|
||||
_ => token.error(Unexpected(c))
|
||||
},
|
||||
Num(n) => match c {
|
||||
'0'..='9' => token.grow_num(n, c),
|
||||
' '|'\n'|'\r'|'\t'|')' => return Some(token),
|
||||
_ => token.error(Unexpected(c))
|
||||
},
|
||||
Sym(_) => match c {
|
||||
'a'..='z'|'A'..='Z'|'0'..='9'|'-' => token.grow_sym(),
|
||||
' '|'\n'|'\r'|'\t'|')' => return Some(token),
|
||||
_ => token.error(Unexpected(c))
|
||||
},
|
||||
Key(_) => match c {
|
||||
'a'..='z'|'0'..='9'|'-'|'/' => token.grow_key(),
|
||||
' '|'\n'|'\r'|'\t'|')' => return Some(token),
|
||||
_ => token.error(Unexpected(c))
|
||||
},
|
||||
Exp(depth, _) => match depth {
|
||||
0 => return Some(token.grow_exp()),
|
||||
_ => match c {
|
||||
')' => token.grow_out(),
|
||||
'(' => token.grow_in(),
|
||||
_ => token.grow_exp(),
|
||||
}
|
||||
},
|
||||
});
|
||||
match token.value() {
|
||||
Nil => None,
|
||||
_ => Some(token),
|
||||
}
|
||||
}
|
||||
pub const fn to_number (digits: &str) -> Result<usize, ParseError> {
|
||||
let mut value = 0;
|
||||
iterate!(char_indices(digits) => (_, c) => match to_digit(c) {
|
||||
Ok(digit) => value = 10 * value + digit,
|
||||
Result::Err(e) => return Result::Err(e)
|
||||
});
|
||||
Ok(value)
|
||||
}
|
||||
pub const fn to_digit (c: char) -> Result<usize, ParseError> {
|
||||
Ok(match c {
|
||||
'0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4,
|
||||
'5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9,
|
||||
_ => return Result::Err(Unexpected(c))
|
||||
})
|
||||
}
|
||||
#[cfg(test)] mod test_token_iter {
|
||||
use super::*;
|
||||
//use proptest::prelude::*;
|
||||
#[test] fn test_iters () {
|
||||
let mut iter = crate::SourceIter::new(&":foo :bar");
|
||||
let _ = iter.next();
|
||||
let mut iter = crate::TokenIter::new(&":foo :bar");
|
||||
let _ = iter.next();
|
||||
}
|
||||
#[test] const fn test_const_iters () {
|
||||
let mut iter = crate::SourceIter::new(&":foo :bar");
|
||||
let _ = iter.next();
|
||||
}
|
||||
#[test] fn test_num () {
|
||||
let digit = to_digit('0');
|
||||
let digit = to_digit('x');
|
||||
let number = to_number(&"123");
|
||||
let number = to_number(&"12asdf3");
|
||||
}
|
||||
//proptest! {
|
||||
//#[test] fn proptest_source_iter (
|
||||
//source in "\\PC*"
|
||||
//) {
|
||||
//let mut iter = crate::SourceIter::new(&source);
|
||||
////let _ = iter.next();
|
||||
//}
|
||||
//#[test] fn proptest_token_iter (
|
||||
//source in "\\PC*"
|
||||
//) {
|
||||
//let mut iter = crate::TokenIter::new(&source);
|
||||
////let _ = iter.next();
|
||||
//}
|
||||
//}
|
||||
}
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
#![feature(adt_const_params)]
|
||||
#![feature(type_alias_impl_trait)]
|
||||
#![feature(impl_trait_in_fn_trait_return)]
|
||||
mod error; pub use self::error::*;
|
||||
mod token; pub use self::token::*;
|
||||
mod iter; pub use self::iter::*;
|
||||
mod context; pub use self::context::*;
|
||||
pub(crate) use self::Value::*;
|
||||
pub(crate) use self::ParseError::*;
|
||||
pub(crate) use konst::iter::{ConstIntoIter, IsIteratorKind};
|
||||
pub(crate) use konst::string::{split_at, str_range, char_indices};
|
||||
pub(crate) use std::fmt::Debug;
|
||||
|
||||
/// Static iteration helper.
|
||||
#[macro_export] macro_rules! iterate {
|
||||
($expr:expr => $arg: pat => $body:expr) => {
|
||||
let mut iter = $expr;
|
||||
while let Some(($arg, next)) = iter.next() {
|
||||
$body;
|
||||
iter = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Implement the const iterator pattern.
|
||||
#[macro_export] macro_rules! const_iter {
|
||||
($(<$l:lifetime>)?|$self:ident: $Struct:ty| => $Item:ty => $expr:expr) => {
|
||||
impl$(<$l>)? Iterator for $Struct {
|
||||
type Item = $Item;
|
||||
fn next (&mut $self) -> Option<$Item> { $expr }
|
||||
}
|
||||
impl$(<$l>)? ConstIntoIter for $Struct {
|
||||
type Kind = IsIteratorKind;
|
||||
type Item = $Item;
|
||||
type IntoIter = Self;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! expose {
|
||||
($([$self:ident:$State:ty] { $($Type:ty => { $($pat:pat => $expr:expr),* $(,)? })* })*) => {
|
||||
$(expose!(@impl [$self: $State] { $($Type => { $($pat => $expr),* })* });)*
|
||||
};
|
||||
(@impl [$self:ident:$State:ty] { $($Type:ty => { $($pat:pat => $expr:expr),* $(,)? })* }) => {
|
||||
$(expose!(@type $Type [$self: $State] => { $($pat => $expr),* });)*
|
||||
};
|
||||
(@type bool [$self:ident: $State:ty] => { $($pat:pat => $expr:expr),* $(,)? }) => {
|
||||
provide_bool!(bool: |$self: $State| { $($pat => $expr),* });
|
||||
};
|
||||
(@type isize [$self:ident: $State:ty] => { $($pat:pat => $expr:expr),* $(,)? }) => {
|
||||
provide_num!(u16: |$self: $State| { $($pat => $expr),* });
|
||||
};
|
||||
(@type usize [$self:ident: $State:ty] => { $($pat:pat => $expr:expr),* $(,)? }) => {
|
||||
provide_num!(usize: |$self: $State| { $($pat => $expr),* });
|
||||
};
|
||||
(@type isize [$self:ident: $State:ty] => { $($pat:pat => $expr:expr),* $(,)? }) => {
|
||||
provide_num!(isize: |$self: $State| { $($pat => $expr),* });
|
||||
};
|
||||
(@type $Type:ty [$self:ident: $State:ty] => { $($pat:pat => $expr:expr),* $(,)? }) => {
|
||||
provide!($Type: |$self: $State| { $($pat => $expr),* });
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! impose {
|
||||
([$self:ident:$Struct:ty] { $($Command:ty => $variants:tt)* }) => {
|
||||
$(atom_command!($Command: |$self: $Struct| $variants);)*
|
||||
};
|
||||
}
|
||||
|
||||
//#[cfg(test)] #[test] fn test_examples () -> Result<(), ParseError> {
|
||||
//// Let's pretend to render some view.
|
||||
//let source = include_str!("../../tek/src/view_arranger.edn");
|
||||
//// The token iterator allows you to get the tokens represented by the source text.
|
||||
//let mut view = TokenIter(source);
|
||||
//// The token iterator wraps a const token+source iterator.
|
||||
//assert_eq!(view.0.0, source);
|
||||
//let mut expr = view.peek();
|
||||
//assert_eq!(view.0.0, source);
|
||||
//assert_eq!(expr, Some(Token {
|
||||
//source, start: 0, length: source.len() - 1, value: Exp(0, SourceIter(&source[1..]))
|
||||
//}));
|
||||
////panic!("{view:?}");
|
||||
////panic!("{:#?}", expr);
|
||||
////for example in [
|
||||
////include_str!("../../tui/examples/edn01.edn"),
|
||||
////include_str!("../../tui/examples/edn02.edn"),
|
||||
////] {
|
||||
//////let items = Atom::read_all(example)?;
|
||||
//////panic!("{layout:?}");
|
||||
//////let content = <dyn ViewContext<::tengri_engine::tui::Tui>>::from(&layout);
|
||||
////}
|
||||
//Ok(())
|
||||
//}
|
||||
169
dsl/src/token.rs
169
dsl/src/token.rs
|
|
@ -1,169 +0,0 @@
|
|||
//! [Token]s are parsed substrings with an associated [Value].
|
||||
//!
|
||||
//! * [ ] FIXME: Value may be [Err] which may shadow [Result::Err]
|
||||
//! * [Value::Exp] wraps an expression depth and a [SourceIter]
|
||||
//! with the remaining part of the expression.
|
||||
//! * expression depth other that 0 mean unclosed parenthesis.
|
||||
//! * closing and unopened parenthesis panics during reading.
|
||||
//! * [ ] TODO: signed depth might be interesting
|
||||
//! * [Value::Sym] and [Value::Key] are stringish literals
|
||||
//! with slightly different parsing rules.
|
||||
//! * [Value::Num] is an unsigned integer literal.
|
||||
//!```
|
||||
//! use tengri_dsl::{*, Value::*};
|
||||
//! let source = include_str!("../test.edn");
|
||||
//! let mut view = TokenIter::new(source);
|
||||
//! assert_eq!(view.peek(), Some(Token {
|
||||
//! source,
|
||||
//! start: 0,
|
||||
//! length: source.len(),
|
||||
//! value: Exp(0, TokenIter::new(&source[1..]))
|
||||
//! }));
|
||||
//!```
|
||||
use crate::*;
|
||||
use self::Value::*;
|
||||
#[derive(Debug, Copy, Clone, Default, PartialEq)] pub struct Token<'a> {
|
||||
pub source: &'a str,
|
||||
pub start: usize,
|
||||
pub length: usize,
|
||||
pub value: Value<'a>,
|
||||
}
|
||||
#[derive(Debug, Copy, Clone, Default, PartialEq)] pub enum Value<'a> {
|
||||
#[default] Nil,
|
||||
Err(ParseError),
|
||||
Num(usize),
|
||||
Sym(&'a str),
|
||||
Key(&'a str),
|
||||
Exp(usize, TokenIter<'a>),
|
||||
}
|
||||
impl<'a> Token<'a> {
|
||||
pub const fn new (source: &'a str, start: usize, length: usize, value: Value<'a>) -> Self {
|
||||
Self { source, start, length, value }
|
||||
}
|
||||
pub const fn end (&self) -> usize {
|
||||
self.start.saturating_add(self.length)
|
||||
}
|
||||
pub const fn slice (&'a self) -> &'a str {
|
||||
self.slice_source(self.source)
|
||||
//str_range(self.source, self.start, self.end())
|
||||
}
|
||||
pub const fn slice_source <'b> (&'a self, source: &'b str) -> &'b str {
|
||||
str_range(source, self.start, self.end())
|
||||
}
|
||||
pub const fn slice_source_exp <'b> (&'a self, source: &'b str) -> &'b str {
|
||||
str_range(source, self.start.saturating_add(1), self.end())
|
||||
}
|
||||
pub const fn value (&self) -> Value {
|
||||
self.value
|
||||
}
|
||||
pub const fn error (self, error: ParseError) -> Self {
|
||||
Self { value: Value::Err(error), ..self }
|
||||
}
|
||||
pub const fn grow (self) -> Self {
|
||||
Self { length: self.length.saturating_add(1), ..self }
|
||||
}
|
||||
pub const fn grow_num (self, m: usize, c: char) -> Self {
|
||||
match to_digit(c) {
|
||||
Ok(n) => Self { value: Num(10*m+n), ..self.grow() },
|
||||
Result::Err(e) => Self { value: Err(e), ..self.grow() },
|
||||
}
|
||||
}
|
||||
pub const fn grow_key (self) -> Self {
|
||||
let mut token = self.grow();
|
||||
token.value = Key(token.slice_source(self.source));
|
||||
token
|
||||
}
|
||||
pub const fn grow_sym (self) -> Self {
|
||||
let mut token = self.grow();
|
||||
token.value = Sym(token.slice_source(self.source));
|
||||
token
|
||||
}
|
||||
pub const fn grow_exp (self) -> Self {
|
||||
let mut token = self.grow();
|
||||
if let Exp(depth, _) = token.value {
|
||||
token.value = Exp(depth, TokenIter::new(token.slice_source_exp(self.source)));
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
token
|
||||
}
|
||||
pub const fn grow_in (self) -> Self {
|
||||
let mut token = self.grow_exp();
|
||||
if let Value::Exp(depth, source) = token.value {
|
||||
token.value = Value::Exp(depth.saturating_add(1), source)
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
token
|
||||
}
|
||||
pub const fn grow_out (self) -> Self {
|
||||
let mut token = self.grow_exp();
|
||||
if let Value::Exp(depth, source) = token.value {
|
||||
if depth > 0 {
|
||||
token.value = Value::Exp(depth - 1, source)
|
||||
} else {
|
||||
return self.error(Unexpected(')'))
|
||||
}
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
token
|
||||
}
|
||||
}
|
||||
#[cfg(test)] mod test_token_prop {
|
||||
use proptest::prelude::*;
|
||||
proptest! {
|
||||
#[test] fn test_token_prop (
|
||||
source in "\\PC*",
|
||||
start in usize::MIN..usize::MAX,
|
||||
length in usize::MIN..usize::MAX,
|
||||
) {
|
||||
let token = crate::Token {
|
||||
source: &source,
|
||||
start,
|
||||
length,
|
||||
value: crate::Value::Nil
|
||||
};
|
||||
let _ = token.slice();
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(test)] #[test] fn test_token () -> Result<(), Box<dyn std::error::Error>> {
|
||||
let source = ":f00";
|
||||
let mut token = Token { source, start: 0, length: 1, value: Sym(":") };
|
||||
token = token.grow_sym();
|
||||
assert_eq!(token, Token { source, start: 0, length: 2, value: Sym(":f") });
|
||||
token = token.grow_sym();
|
||||
assert_eq!(token, Token { source, start: 0, length: 3, value: Sym(":f0") });
|
||||
token = token.grow_sym();
|
||||
assert_eq!(token, Token { source, start: 0, length: 4, value: Sym(":f00") });
|
||||
|
||||
let src = "";
|
||||
assert_eq!(None, SourceIter(src).next());
|
||||
|
||||
let src = " \n \r \t ";
|
||||
assert_eq!(None, SourceIter(src).next());
|
||||
|
||||
let src = "7";
|
||||
assert_eq!(Num(7), SourceIter(src).next().unwrap().0.value);
|
||||
|
||||
let src = " 100 ";
|
||||
assert_eq!(Num(100), SourceIter(src).next().unwrap().0.value);
|
||||
|
||||
let src = " 9a ";
|
||||
assert_eq!(Err(Unexpected('a')), SourceIter(src).next().unwrap().0.value);
|
||||
|
||||
let src = " :123foo ";
|
||||
assert_eq!(Sym(":123foo"), SourceIter(src).next().unwrap().0.value);
|
||||
|
||||
let src = " \r\r\r\n\n\n@bar456\t\t\t\t\t\t";
|
||||
assert_eq!(Sym("@bar456"), SourceIter(src).next().unwrap().0.value);
|
||||
|
||||
let src = "foo123";
|
||||
assert_eq!(Key("foo123"), SourceIter(src).next().unwrap().0.value);
|
||||
|
||||
let src = "foo/bar";
|
||||
assert_eq!(Key("foo/bar"), SourceIter(src).next().unwrap().0.value);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
5
editor/Cargo.toml
Normal file
5
editor/Cargo.toml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
[package]
|
||||
name = "tengri_editor"
|
||||
description = "Embeddable editor for Tengri DSL."
|
||||
version = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
0
editor/src/main.rs
Normal file
0
editor/src/main.rs
Normal file
|
|
@ -1,14 +1,14 @@
|
|||
[package]
|
||||
name = "tengri_input"
|
||||
edition = "2024"
|
||||
description = "UI metaframework, input layer."
|
||||
version = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
[lib]
|
||||
path = "input.rs"
|
||||
|
||||
[dependencies]
|
||||
tengri_dsl = { optional = true, path = "../dsl" }
|
||||
|
||||
[features]
|
||||
dsl = [ "tengri_dsl" ]
|
||||
tengri_core = { path = "../core" }
|
||||
|
||||
[dev-dependencies]
|
||||
tengri_tui = { path = "../tui" }
|
||||
|
|
|
|||
|
|
@ -1,16 +1,6 @@
|
|||
# `tengri_engine`
|
||||
***tengri_input*** is where tengri's input handling is defined.
|
||||
|
||||
## rendering
|
||||
|
||||
## input handling
|
||||
|
||||
the **input thread** polls for keyboard events
|
||||
and passes them onto the application's `Handle::handle` method.
|
||||
|
||||
thus, for a type to be a valid application for engine `E`,
|
||||
it must implement the trait `Handle<E>`, which allows it
|
||||
to respond to user input.
|
||||
|
||||
this thread has write access to the application state,
|
||||
and is responsible for mutating it in response to
|
||||
user activity.
|
||||
the following items are provided:
|
||||
* `Input` trait, for defining for input sources
|
||||
* `Handle` trait and `handle!` macro, for defining input handlers
|
||||
* `Command` trait and the `command!` macro, for defining commands that inputs may result in
|
||||
|
|
|
|||
94
input/input.rs
Normal file
94
input/input.rs
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
#![feature(associated_type_defaults)]
|
||||
#![feature(if_let_guard)]
|
||||
|
||||
pub(crate) use tengri_core::*;
|
||||
|
||||
#[cfg(test)] mod input_test;
|
||||
|
||||
/// Event source
|
||||
pub trait Input: Sized {
|
||||
/// Type of input event
|
||||
type Event;
|
||||
/// Result of handling input
|
||||
type Handled; // TODO: make this an Option<Box dyn Command<Self>> containing the undo
|
||||
/// Currently handled event
|
||||
fn event (&self) -> &Self::Event;
|
||||
/// Whether component should exit
|
||||
fn is_done (&self) -> bool;
|
||||
/// Mark component as done
|
||||
fn done (&self);
|
||||
}
|
||||
|
||||
flex_trait_mut!(Handle <E: Input> {
|
||||
fn handle (&mut self, _input: &E) -> Perhaps<E::Handled> {
|
||||
Ok(None)
|
||||
}
|
||||
});
|
||||
|
||||
pub trait Command<S>: Send + Sync + Sized {
|
||||
fn execute (&self, state: &mut S) -> Perhaps<Self>;
|
||||
fn delegate <T> (&self, state: &mut S, wrap: impl Fn(Self)->T) -> Perhaps<T>
|
||||
where Self: Sized
|
||||
{
|
||||
Ok(self.execute(state)?.map(wrap))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, T: Command<S>> Command<S> for Option<T> {
|
||||
fn execute (&self, _: &mut S) -> Perhaps<Self> {
|
||||
Ok(None)
|
||||
}
|
||||
fn delegate <U> (&self, _: &mut S, _: impl Fn(Self)->U) -> Perhaps<U>
|
||||
where Self: Sized
|
||||
{
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Implement [Command] for given `State` and `handler`
|
||||
#[macro_export] macro_rules! command {
|
||||
($(<$($l:lifetime),+>)?|$self:ident:$Command:ty,$state:ident:$State:ty|$handler:expr) => {
|
||||
impl$(<$($l),+>)? ::tengri::input::Command<$State> for $Command {
|
||||
fn execute (&$self, $state: &mut $State) -> Perhaps<Self> {
|
||||
Ok($handler)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! def_command (($Command:ident: |$state:ident: $State:ty| {
|
||||
$($Variant:ident$({$($arg:ident:$Arg:ty),+ $(,)?})?=>$body:expr),* $(,)?
|
||||
})=>{
|
||||
#[derive(Debug)]
|
||||
pub enum $Command {
|
||||
$($Variant $({ $($arg: $Arg),* })?),*
|
||||
}
|
||||
impl Command<$State> for $Command {
|
||||
fn execute (&self, $state: &mut $State) -> Perhaps<Self> {
|
||||
match self {
|
||||
$(Self::$Variant $({ $($arg),* })? => $body,)*
|
||||
_ => unimplemented!("Command<{}>: {self:?}", stringify!($State)),
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/// Implement [Handle] for given `State` and `handler`.
|
||||
#[macro_export] macro_rules! handle {
|
||||
(|$self:ident:$State:ty,$input:ident|$handler:expr) => {
|
||||
impl<E: Engine> ::tengri::input::Handle<E> for $State {
|
||||
fn handle (&mut $self, $input: &E) -> Perhaps<E::Handled> {
|
||||
$handler
|
||||
}
|
||||
}
|
||||
};
|
||||
($E:ty: |$self:ident:$State:ty,$input:ident|$handler:expr) => {
|
||||
impl ::tengri::input::Handle<$E> for $State {
|
||||
fn handle (&mut $self, $input: &$E) ->
|
||||
Perhaps<<$E as ::tengri::input::Input>::Handled>
|
||||
{
|
||||
$handler
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +1,5 @@
|
|||
#![feature(associated_type_defaults)]
|
||||
mod command; pub use self::command::*;
|
||||
mod handle; pub use self::handle::*;
|
||||
mod keymap; pub use self::keymap::*;
|
||||
use crate::*;
|
||||
|
||||
#[cfg(feature = "dsl")] pub(crate) use ::tengri_dsl::*;
|
||||
|
||||
/// Standard error trait.
|
||||
pub(crate) use std::error::Error;
|
||||
|
||||
/// Standard optional result type.
|
||||
pub(crate) type Perhaps<T> = Result<Option<T>, Box<dyn Error>>;
|
||||
|
||||
/// Standard result type.
|
||||
#[cfg(test)]
|
||||
pub(crate) type Usually<T> = Result<T, Box<dyn Error>>;
|
||||
|
||||
#[cfg(test)]
|
||||
#[test] fn test_stub_input () -> Usually<()> {
|
||||
use crate::*;
|
||||
struct TestInput(bool);
|
||||
|
|
@ -36,3 +20,9 @@ pub(crate) type Usually<T> = Result<T, Box<dyn Error>>;
|
|||
assert!(!TestInput(false).is_done());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
//#[cfg(all(test, feature = "dsl"))] #[test] fn test_dsl_keymap () -> Usually<()> {
|
||||
//let _keymap = CstIter::new("");
|
||||
//Ok(())
|
||||
//}
|
||||
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
use crate::*;
|
||||
#[macro_export] macro_rules! command {
|
||||
($(<$($l:lifetime),+>)?|$self:ident:$Command:ty,$state:ident:$State:ty|$handler:expr) => {
|
||||
impl$(<$($l),+>)? Command<$State> for $Command {
|
||||
fn execute ($self, $state: &mut $State) -> Perhaps<Self> {
|
||||
Ok($handler)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
pub trait Command<S>: Send + Sync + Sized {
|
||||
fn execute (self, state: &mut S) -> Perhaps<Self>;
|
||||
fn delegate <T> (self, state: &mut S, wrap: impl Fn(Self)->T) -> Perhaps<T>
|
||||
where Self: Sized
|
||||
{
|
||||
Ok(self.execute(state)?.map(wrap))
|
||||
}
|
||||
}
|
||||
impl<S, T: Command<S>> Command<S> for Option<T> {
|
||||
fn execute (self, _: &mut S) -> Perhaps<Self> {
|
||||
Ok(None)
|
||||
}
|
||||
fn delegate <U> (self, _: &mut S, _: impl Fn(Self)->U) -> Perhaps<U>
|
||||
where Self: Sized
|
||||
{
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
use crate::*;
|
||||
|
||||
pub struct EventMap<'a, S, I: PartialEq, C> {
|
||||
pub bindings: &'a [(I, &'a dyn Fn(&S) -> Option<C>)],
|
||||
pub fallback: Option<&'a dyn Fn(&S, &I) -> Option<C>>
|
||||
}
|
||||
|
||||
impl<'a, S, I: PartialEq, C> EventMap<'a, S, I, C> {
|
||||
pub fn handle (&self, state: &S, input: &I) -> Option<C> {
|
||||
for (binding, handler) in self.bindings.iter() {
|
||||
if input == binding {
|
||||
return handler(state)
|
||||
}
|
||||
}
|
||||
if let Some(fallback) = self.fallback {
|
||||
fallback(state, input)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! keymap {
|
||||
(
|
||||
$(<$lt:lifetime>)? $KEYS:ident = |$state:ident: $State:ty, $input:ident: $Input:ty| $Command:ty
|
||||
{ $($key:expr => $handler:expr),* $(,)? } $(,)?
|
||||
) => {
|
||||
pub const $KEYS: EventMap<'static, $State, $Input, $Command> = EventMap {
|
||||
fallback: None,
|
||||
bindings: &[ $(($key, &|$state|Some($handler)),)* ]
|
||||
};
|
||||
input_to_command!($(<$lt>)? $Command: |$state: $State, input: $Input|$KEYS.handle($state, input)?);
|
||||
};
|
||||
(
|
||||
$(<$lt:lifetime>)? $KEYS:ident = |$state:ident: $State:ty, $input:ident: $Input:ty| $Command:ty
|
||||
{ $($key:expr => $handler:expr),* $(,)? }, $default:expr
|
||||
) => {
|
||||
pub const $KEYS: EventMap<'static, $State, $Input, $Command> = EventMap {
|
||||
fallback: Some(&|$state, $input|Some($default)),
|
||||
bindings: &[ $(($key, &|$state|Some($handler)),)* ]
|
||||
};
|
||||
input_to_command!($(<$lt>)? $Command: |$state: $State, input: $Input|$KEYS.handle($state, input)?);
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! input_to_command {
|
||||
(<$($l:lifetime),+> $Command:ty: |$state:ident:$State:ty, $input:ident:$Input:ty| $handler:expr) => {
|
||||
impl<$($l),+> InputToCommand<$Input, $State> for $Command {
|
||||
fn input_to_command ($state: &$State, $input: &$Input) -> Option<Self> {
|
||||
Some($handler)
|
||||
}
|
||||
}
|
||||
};
|
||||
($Command:ty: |$state:ident:$State:ty, $input:ident:$Input:ty| $handler:expr) => {
|
||||
impl InputToCommand<$Input, $State> for $Command {
|
||||
fn input_to_command ($state: &$State, $input: &$Input) -> Option<Self> {
|
||||
Some($handler)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait InputToCommand<I, S>: Command<S> + Sized {
|
||||
fn input_to_command (state: &S, input: &I) -> Option<Self>;
|
||||
fn execute_with_state (state: &mut S, input: &I) -> Perhaps<bool> {
|
||||
Ok(if let Some(command) = Self::input_to_command(state, input) {
|
||||
let _undo = command.execute(state)?;
|
||||
Some(true)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
use crate::*;
|
||||
use std::sync::{Mutex, Arc, RwLock};
|
||||
|
||||
/// Event source
|
||||
pub trait Input: Send + Sync + Sized {
|
||||
/// Type of input event
|
||||
type Event;
|
||||
/// Result of handling input
|
||||
type Handled; // TODO: make this an Option<Box dyn Command<Self>> containing the undo
|
||||
/// Currently handled event
|
||||
fn event (&self) -> &Self::Event;
|
||||
/// Whether component should exit
|
||||
fn is_done (&self) -> bool;
|
||||
/// Mark component as done
|
||||
fn done (&self);
|
||||
}
|
||||
|
||||
/// Implement the [Handle] trait.
|
||||
#[macro_export] macro_rules! handle {
|
||||
(|$self:ident:$Struct:ty,$input:ident|$handler:expr) => {
|
||||
impl<E: Engine> Handle<E> for $Struct {
|
||||
fn handle (&mut $self, $input: &E) -> Perhaps<E::Handled> {
|
||||
$handler
|
||||
}
|
||||
}
|
||||
};
|
||||
($E:ty: |$self:ident:$Struct:ty,$input:ident|$handler:expr) => {
|
||||
impl Handle<$E> for $Struct {
|
||||
fn handle (&mut $self, $input: &$E) -> Perhaps<<$E as Input>::Handled> {
|
||||
$handler
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle input
|
||||
pub trait Handle<E: Input>: Send + Sync {
|
||||
fn handle (&mut self, _input: &E) -> Perhaps<E::Handled> {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
impl<E: Input, H: Handle<E>> Handle<E> for &mut H {
|
||||
fn handle (&mut self, context: &E) -> Perhaps<E::Handled> {
|
||||
(*self).handle(context)
|
||||
}
|
||||
}
|
||||
impl<E: Input, H: Handle<E>> Handle<E> for Option<H> {
|
||||
fn handle (&mut self, context: &E) -> Perhaps<E::Handled> {
|
||||
if let Some(handle) = self {
|
||||
handle.handle(context)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<H, E: Input> Handle<E> for Mutex<H> where H: Handle<E> {
|
||||
fn handle (&mut self, context: &E) -> Perhaps<E::Handled> {
|
||||
self.get_mut().unwrap().handle(context)
|
||||
}
|
||||
}
|
||||
impl<H, E: Input> Handle<E> for Arc<Mutex<H>> where H: Handle<E> {
|
||||
fn handle (&mut self, context: &E) -> Perhaps<E::Handled> {
|
||||
self.lock().unwrap().handle(context)
|
||||
}
|
||||
}
|
||||
impl<H, E: Input> Handle<E> for RwLock<H> where H: Handle<E> {
|
||||
fn handle (&mut self, context: &E) -> Perhaps<E::Handled> {
|
||||
self.write().unwrap().handle(context)
|
||||
}
|
||||
}
|
||||
impl<H, E: Input> Handle<E> for Arc<RwLock<H>> where H: Handle<E> {
|
||||
fn handle (&mut self, context: &E) -> Perhaps<E::Handled> {
|
||||
self.write().unwrap().handle(context)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
use crate::*;
|
||||
use std::time::Duration;
|
||||
use std::thread::JoinHandle;
|
||||
|
||||
/// Event source
|
||||
pub trait Input: Send + Sync + Sized {
|
||||
/// Type of input event
|
||||
type Event;
|
||||
/// Result of handling input
|
||||
type Handled; // TODO: make this an Option<Box dyn Command<Self>> containing the undo
|
||||
/// Currently handled event
|
||||
fn event (&self) -> &Self::Event;
|
||||
/// Whether component should exit
|
||||
fn is_done (&self) -> bool;
|
||||
/// Mark component as done
|
||||
fn done (&self);
|
||||
}
|
||||
|
||||
/// Input thread entrypoint.
|
||||
pub trait InputRun<T> {
|
||||
fn run_input (engine: T, state: Self, timer: Duration) -> JoinHandle<()>;
|
||||
}
|
||||
|
||||
/// Handle input through a mutable reference.
|
||||
pub trait Handle<E: Input>: Send + Sync {
|
||||
fn handle (&mut self, _input: &E) -> Perhaps<E::Handled> {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle input through an immutable reference (e.g. [Arc<RwLock>] or [Arc<Mutex>])
|
||||
pub trait HandleRef<E: Input>: Send + Sync {
|
||||
fn handle (&self, _input: &E) -> Perhaps<E::Handled> {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,177 +0,0 @@
|
|||
use crate::*;
|
||||
|
||||
/// [Input] state that can be matched against a [Value].
|
||||
pub trait AtomInput: Input {
|
||||
fn matches_atom (&self, token: &str) -> bool;
|
||||
}
|
||||
|
||||
#[cfg(feature = "dsl")]
|
||||
pub trait KeyMap<'a> {
|
||||
/// Try to find a command that matches the current input event.
|
||||
fn command <S, C: AtomCommand<'a, S>, I: AtomInput> (&'a self, state: &'a S, input: &'a I)
|
||||
-> Option<C>;
|
||||
}
|
||||
|
||||
#[cfg(feature = "dsl")]
|
||||
impl<'a> KeyMap<'a> for SourceIter<'a> {
|
||||
fn command <S, C: AtomCommand<'a, S>, I: AtomInput> (&'a self, state: &'a S, input: &'a I)
|
||||
-> Option<C>
|
||||
{
|
||||
let mut iter = self.clone();
|
||||
while let Some((token, rest)) = iter.next() {
|
||||
iter = rest;
|
||||
match token {
|
||||
Token { value: Value::Exp(0, exp_iter), .. } => {
|
||||
let mut exp_iter = exp_iter.clone();
|
||||
match exp_iter.next() {
|
||||
Some(Token { value: Value::Sym(binding), .. }) => {
|
||||
if input.matches_atom(binding) {
|
||||
if let Some(command) = C::try_from_expr(state, exp_iter.clone()) {
|
||||
return Some(command)
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => panic!("invalid config (expected symbol)")
|
||||
}
|
||||
},
|
||||
_ => panic!("invalid config (expected expression)")
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "dsl")]
|
||||
impl<'a> KeyMap<'a> for TokenIter<'a> {
|
||||
fn command <S, C: AtomCommand<'a, S>, I: AtomInput> (&'a self, state: &'a S, input: &'a I)
|
||||
-> Option<C>
|
||||
{
|
||||
let mut iter = self.clone();
|
||||
while let Some(next) = iter.next() {
|
||||
match next {
|
||||
Token { value: Value::Exp(0, exp_iter), .. } => {
|
||||
let mut exp_iter = exp_iter.clone();
|
||||
match exp_iter.next() {
|
||||
Some(Token { value: Value::Sym(binding), .. }) => {
|
||||
if input.matches_atom(binding) {
|
||||
if let Some(command) = C::try_from_expr(state, exp_iter.clone()) {
|
||||
return Some(command)
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => panic!("invalid config (expected symbol)")
|
||||
}
|
||||
},
|
||||
_ => panic!("invalid config (expected expression)")
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// A [Command] that can be constructed from a [Token].
|
||||
#[cfg(feature = "dsl")]
|
||||
pub trait AtomCommand<'a, C>: TryFromAtom<'a, C> + Command<C> {}
|
||||
|
||||
#[cfg(feature = "dsl")]
|
||||
impl<'a, C, T: TryFromAtom<'a, C> + Command<C>> AtomCommand<'a, C> for T {}
|
||||
|
||||
/** Implement `AtomCommand` for given `State` and `Command` */
|
||||
#[cfg(feature = "dsl")]
|
||||
#[macro_export] macro_rules! atom_command {
|
||||
($Command:ty : |$state:ident:<$State:ident: $Trait:path>| { $((
|
||||
// identifier
|
||||
$key:literal [
|
||||
// named parameters
|
||||
$(
|
||||
// argument name
|
||||
$arg:ident
|
||||
// if type is not provided defaults to Atom
|
||||
$(
|
||||
// type:name separator
|
||||
:
|
||||
// argument type
|
||||
$type:ty
|
||||
)?
|
||||
),*
|
||||
// rest of parameters
|
||||
$(, ..$rest:ident)?
|
||||
]
|
||||
// bound command:
|
||||
$command:expr
|
||||
))* }) => {
|
||||
impl<'a, $State: $Trait> TryFromAtom<'a, $State> for $Command {
|
||||
fn try_from_expr ($state: &$State, iter: TokenIter) -> Option<Self> {
|
||||
let iter = iter.clone();
|
||||
match iter.next() {
|
||||
$(Some(Token { value: Value::Key($key), .. }) => {
|
||||
let iter = iter.clone();
|
||||
$(
|
||||
let next = iter.next();
|
||||
if next.is_none() { panic!("no argument: {}", stringify!($arg)); }
|
||||
let $arg = next.unwrap();
|
||||
$(let $arg: Option<$type> = Context::<$type>::get($state, &$arg.value);)?
|
||||
)*
|
||||
$(let $rest = iter.clone();)?
|
||||
return $command
|
||||
},)*
|
||||
_ => None
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
($Command:ty : |$state:ident:$State:ty| { $((
|
||||
// identifier
|
||||
$key:literal [
|
||||
// named parameters
|
||||
$(
|
||||
// argument name
|
||||
$arg:ident
|
||||
// if type is not provided defaults to Atom
|
||||
$(
|
||||
// type:name separator
|
||||
:
|
||||
// argument type
|
||||
$type:ty
|
||||
)?
|
||||
),*
|
||||
// rest of parameters
|
||||
$(, ..$rest:ident)?
|
||||
]
|
||||
// bound command:
|
||||
$command:expr
|
||||
))* }) => {
|
||||
impl<'a> TryFromAtom<'a, $State> for $Command {
|
||||
fn try_from_expr ($state: &$State, iter: TokenIter) -> Option<Self> {
|
||||
let mut iter = iter.clone();
|
||||
match iter.next() {
|
||||
$(Some(Token { value: Value::Key($key), .. }) => {
|
||||
let mut iter = iter.clone();
|
||||
$(
|
||||
let next = iter.next();
|
||||
if next.is_none() { panic!("no argument: {}", stringify!($arg)); }
|
||||
let $arg = next.unwrap();
|
||||
$(let $arg: Option<$type> = Context::<$type>::get($state, &$arg.value);)?
|
||||
)*
|
||||
$(let $rest = iter.clone();)?
|
||||
return $command
|
||||
}),*
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
(@bind $state:ident =>$arg:ident ? : $type:ty) => {
|
||||
let $arg: Option<$type> = Context::<$type>::get($state, $arg);
|
||||
};
|
||||
(@bind $state:ident => $arg:ident : $type:ty) => {
|
||||
let $arg: $type = Context::<$type>::get_or_fail($state, $arg);
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "dsl"))]
|
||||
#[test] fn test_atom_keymap () -> Usually<()> {
|
||||
let keymap = SourceIter::new("");
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -1,18 +1,24 @@
|
|||
[package]
|
||||
name = "tengri_output"
|
||||
edition = "2024"
|
||||
description = "UI metaframework, output layer."
|
||||
version = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
tengri_dsl = { optional = true, path = "../dsl" }
|
||||
[lib]
|
||||
path = "src/output.rs"
|
||||
|
||||
[features]
|
||||
dsl = [ "tengri_dsl" ]
|
||||
bumpalo = [ "dep:bumpalo" ]
|
||||
dsl = [ "dep:tengri_dsl" ]
|
||||
|
||||
[dependencies]
|
||||
tengri_core = { path = "../core" }
|
||||
tengri_dsl = { optional = true, path = "../dsl" }
|
||||
bumpalo = { optional = true, workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tengri = { path = "../tengri", features = [ "dsl", "tui" ] }
|
||||
tengri_tui = { path = "../tui" }
|
||||
tengri_dsl = { path = "../dsl" }
|
||||
proptest = "^1"
|
||||
proptest-derive = "^0.5.1"
|
||||
proptest = { workspace = true }
|
||||
proptest-derive = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -1,75 +1,20 @@
|
|||
# `tengri_output`
|
||||
***tengri_output*** is an abstract interface layout framework.
|
||||
|
||||
## free floating layout primitives
|
||||
it expresses the following notions:
|
||||
|
||||
this crate exposes several layout operators
|
||||
which work entirely in unsigned coordinates
|
||||
and are generic over the trait `Content`.
|
||||
most importantly, they are not dependent on rendering framework.
|
||||
* [**space:**](./src/space.rs) `Direction`, `Coordinate`, `Area`, `Size`, `Measure`
|
||||
|
||||
|operator|description|
|
||||
|-|-|
|
||||
|**`When(x, a)`**|render `a` only when `x == true`|
|
||||
|**`Either(x, a, b)`**|render `a` when `x == true`, otherwise render `b`|
|
||||
|**`Map(get_iterator, callback)`**|transform items in uniform way|
|
||||
|**`Bsp`**|concatenative layout|
|
||||
|...|...|
|
||||
|**`Align`**|pin content along axis|
|
||||
|...|...|
|
||||
|**`Fill`**|**make content's dimension equal to container's:**|
|
||||
|`Fill::x(a)`|use container's width for content|
|
||||
|`Fill::y(a)`|use container's height for content|
|
||||
|`Fill::xy(a)`|use container's width and height for content|
|
||||
|**`Fixed`**|**assign fixed dimension to content:**|
|
||||
|`Fixed::x(w, a)`|use width `w` for content|
|
||||
|`Fixed::y(w, a)`|use height `w` for content|
|
||||
|`Fixed::xy(w, h, a)`|use width `w` and height `h` for content|
|
||||
|**`Expand`/`Shrink`**|**change dimension of content:**|
|
||||
|`Expand::x(n, a)`/`Shrink::x(n, a)`|increment/decrement width of content area by `n`|
|
||||
|`Expand::y(n, a)`/`Shrink::y(n, a)`|increment/decrement height of content area by `m`|
|
||||
|`Expand::xy(n, m, a)`/`Shrink::xy(n, m, a)`|increment/decrement width of content area by `n`, height by `m`|
|
||||
|**`Min`/`Max`**|**constrain dimension of content:**|
|
||||
|`Min::x(w, a)`/`Max::x(w, a)`|enforce minimum/maximum width `w` for content|
|
||||
|`Min::y(h, a)`/`Max::y(h, a)`|enforce minimum/maximum height `h` for content|
|
||||
|`Min::xy(w, h, a)`/`Max::xy(w, h, a)`|enforce minimum/maximum width `w` and height `h` for content|
|
||||
|**`Push`/`Pull`**|**move content along axis:**|
|
||||
|`Push::x(n, a)`/`Pull::x(n, a)`|increment/decrement `x` of content area|
|
||||
|`Push::y(n, a)`/`Pull::y(n, a)`|increment/decrement `y` of content area|
|
||||
|`Push::xy(n, m, a)`/`Pull::xy(n, m, a)`|increment/decrement `x` and `y` of content area|
|
||||
* [**output:**](./src/output.rs) `Out`, `Draw`, `Content`
|
||||
* the layout operators are generic over `Draw` and/or `Content`
|
||||
* the traits `Draw` and `Content` are generic over `Out`
|
||||
* implement `Out` to bring a layout to a new backend:
|
||||
[see `TuiOut` in `tengri_tui`](../tui/src/tui_engine/tui_output.rs)
|
||||
|
||||
**todo:**
|
||||
* sensible `Margin`/`Padding`
|
||||
* `Reduce`
|
||||
|
||||
## example rendering loop
|
||||
|
||||
the **render thread** continually invokes the
|
||||
`Content::render` method of the application
|
||||
to redraw the display. it does this efficiently
|
||||
by using ratatui's double buffering.
|
||||
|
||||
thus, for a type to be a valid application for engine `E`,
|
||||
it must implement the trait `Content<E>`, which allows
|
||||
it to display content to the engine's output.
|
||||
|
||||
the most important thing about the `Content` trait is that
|
||||
it composes:
|
||||
* you can implement `Content::content` to build
|
||||
`Content`s out of other `Content`s
|
||||
* and/or `Content::area` for custom positioning and sizing,
|
||||
* and/or `Content::render` for custom rendering
|
||||
within the given `Content`'s area.
|
||||
|
||||
the manner of output is determined by the
|
||||
`Engine::Output` type, a mutable pointer to which
|
||||
is passed to the render method, e.g. in the case of
|
||||
the `Tui` engine: `fn render(&self, output: &mut TuiOut)`
|
||||
|
||||
you can use `TuiOut::blit` and `TuiOut::place`
|
||||
to draw at specified coordinates of the display, and/or
|
||||
directly modify the underlying `ratatui::Buffer` at
|
||||
`output.buffer`
|
||||
|
||||
rendering is intended to work with read-only access
|
||||
to the application state. if you really need to update
|
||||
values during rendering, use interior mutability.
|
||||
* [**layout:**](./src/layout.rs)
|
||||
* conditionals: `When`, `Either`
|
||||
* iteration: `Map`
|
||||
* concatenation: `Bsp`
|
||||
* positioning: `Align`, `Push`, `Pull`
|
||||
* sizing: `Fill`, `Fixed`, `Expand`, `Shrink`, `Min`, `Max`
|
||||
* implement custom components (that may be backend-dependent):
|
||||
[see `tui_content` in `tengri_tui`](../tui/src/tui_content)
|
||||
|
|
|
|||
26
output/src/content.rs
Normal file
26
output/src/content.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
use crate::*;
|
||||
|
||||
pub trait Content<O: Out>: Draw<O> + Layout<O> {}
|
||||
|
||||
impl<O: Out, T: Draw<O> + Layout<O>> Content<O> for T {}
|
||||
|
||||
impl<'a, O: Out> AsRef<dyn Draw<O> + 'a> for dyn Content<O> + 'a {
|
||||
fn as_ref (&self) -> &(dyn Draw<O> + 'a) { self }
|
||||
}
|
||||
|
||||
impl<'a, O: Out> AsRef<dyn Layout<O> + 'a> for dyn Content<O> + 'a {
|
||||
fn as_ref (&self) -> &(dyn Layout<O> + 'a) { self }
|
||||
}
|
||||
|
||||
pub trait HasContent<O: Out> {
|
||||
fn content (&self) -> impl Content<O>;
|
||||
}
|
||||
|
||||
//impl<O: Out, T: HasContent<O>> Draw<O> for T {
|
||||
//fn draw (&self, to: &mut O) {
|
||||
//let area = to.area();
|
||||
//*to.area_mut() = self.0;
|
||||
//self.content().draw(to);
|
||||
//*to.area_mut() = area;
|
||||
//}
|
||||
//}
|
||||
30
output/src/draw.rs
Normal file
30
output/src/draw.rs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
use crate::*;
|
||||
|
||||
/// Drawable with dynamic dispatch.
|
||||
pub trait Draw<O: Out> {
|
||||
fn draw (&self, to: &mut O);
|
||||
}
|
||||
impl<O: Out> Draw<O> for () {
|
||||
fn draw (&self, _: &mut O) {}
|
||||
}
|
||||
impl<O: Out> Draw<O> for fn(&mut O) {
|
||||
fn draw (&self, to: &mut O) { (*self)(to) }
|
||||
}
|
||||
impl<O: Out> Draw<O> for Box<dyn Draw<O>> {
|
||||
fn draw (&self, to: &mut O) { (**self).draw(to) }
|
||||
}
|
||||
impl<O: Out, D: Draw<O>> Draw<O> for &D {
|
||||
fn draw (&self, to: &mut O) { (*self).draw(to) }
|
||||
}
|
||||
impl<O: Out, D: Draw<O>> Draw<O> for &mut D {
|
||||
fn draw (&self, to: &mut O) { (**self).draw(to) }
|
||||
}
|
||||
impl<O: Out, D: Draw<O>> Draw<O> for Arc<D> {
|
||||
fn draw (&self, to: &mut O) { (**self).draw(to) }
|
||||
}
|
||||
impl<O: Out, D: Draw<O>> Draw<O> for RwLock<D> {
|
||||
fn draw (&self, to: &mut O) { self.read().unwrap().draw(to) }
|
||||
}
|
||||
impl<O: Out, D: Draw<O>> Draw<O> for Option<D> {
|
||||
fn draw (&self, to: &mut O) { if let Some(draw) = self { draw.draw(to) } }
|
||||
}
|
||||
12
output/src/group.rs
Normal file
12
output/src/group.rs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
#[allow(unused)] use crate::*;
|
||||
|
||||
pub struct Group<T>(T);
|
||||
|
||||
impl<T> Group<T> {
|
||||
pub const fn new () -> Group<()> {
|
||||
Group(())
|
||||
}
|
||||
pub const fn add <U> (self, value: U) -> Group<(T, U)> {
|
||||
Group((self.0, value))
|
||||
}
|
||||
}
|
||||
161
output/src/layout.rs
Normal file
161
output/src/layout.rs
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
use crate::*;
|
||||
|
||||
mod layout_align; pub use self::layout_align::*;
|
||||
mod layout_bsp; pub use self::layout_bsp::*;
|
||||
mod layout_cond; pub use self::layout_cond::*;
|
||||
mod layout_map; pub use self::layout_map::*;
|
||||
mod layout_pad; pub use self::layout_pad::*;
|
||||
mod layout_move; pub use self::layout_move::*;
|
||||
mod layout_size; pub use self::layout_size::*;
|
||||
mod layout_stack; //pub use self::layout_stack::*;
|
||||
|
||||
/// Drawable area of display.
|
||||
pub trait Layout<O: Out> {
|
||||
fn x (&self, to: O::Area) -> O::Unit {
|
||||
to.x()
|
||||
}
|
||||
fn y (&self, to: O::Area) -> O::Unit {
|
||||
to.y()
|
||||
}
|
||||
fn min_w (&self, _to: O::Area) -> O::Unit {
|
||||
0.into()
|
||||
}
|
||||
fn max_w (&self, to: O::Area) -> O::Unit {
|
||||
to.w()
|
||||
}
|
||||
fn w (&self, to: O::Area) -> O::Unit {
|
||||
to.w().max(self.min_w(to)).min(self.max_w(to))
|
||||
}
|
||||
fn min_h (&self, _to: O::Area) -> O::Unit {
|
||||
0.into()
|
||||
}
|
||||
fn max_h (&self, to: O::Area) -> O::Unit {
|
||||
to.h()
|
||||
}
|
||||
fn h (&self, to: O::Area) -> O::Unit {
|
||||
to.h().max(self.min_h(to)).min(self.max_h(to))
|
||||
}
|
||||
fn layout (&self, to: O::Area) -> O::Area {
|
||||
[self.x(to), self.y(to), self.w(to), self.h(to)].into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<O: Out> Layout<O> for () {
|
||||
fn x (&self, a: O::Area) -> O::Unit { a.x() }
|
||||
fn y (&self, a: O::Area) -> O::Unit { a.y() }
|
||||
fn w (&self, _: O::Area) -> O::Unit { 0.into() }
|
||||
fn min_w (&self, _: O::Area) -> O::Unit { 0.into() }
|
||||
fn max_w (&self, _: O::Area) -> O::Unit { 0.into() }
|
||||
fn h (&self, _: O::Area) -> O::Unit { 0.into() }
|
||||
fn min_h (&self, _: O::Area) -> O::Unit { 0.into() }
|
||||
fn max_h (&self, _: O::Area) -> O::Unit { 0.into() }
|
||||
fn layout (&self, a: O::Area) -> O::Area { [a.x(), a.y(), 0.into(), 0.into()].into() }
|
||||
}
|
||||
|
||||
impl<O: Out, L: Layout<O>> Layout<O> for &L {
|
||||
fn x (&self, a: O::Area) -> O::Unit { (*self).x(a) }
|
||||
fn y (&self, a: O::Area) -> O::Unit { (*self).y(a) }
|
||||
fn w (&self, a: O::Area) -> O::Unit { (*self).w(a) }
|
||||
fn min_w (&self, a: O::Area) -> O::Unit { (*self).min_w(a) }
|
||||
fn max_w (&self, a: O::Area) -> O::Unit { (*self).max_w(a) }
|
||||
fn h (&self, a: O::Area) -> O::Unit { (*self).h(a) }
|
||||
fn min_h (&self, a: O::Area) -> O::Unit { (*self).min_h(a) }
|
||||
fn max_h (&self, a: O::Area) -> O::Unit { (*self).max_h(a) }
|
||||
fn layout (&self, a: O::Area) -> O::Area { (*self).layout(a) }
|
||||
}
|
||||
|
||||
impl<O: Out, L: Layout<O>> Layout<O> for &mut L {
|
||||
fn x (&self, a: O::Area) -> O::Unit { (**self).x(a) }
|
||||
fn y (&self, a: O::Area) -> O::Unit { (**self).y(a) }
|
||||
fn w (&self, a: O::Area) -> O::Unit { (**self).w(a) }
|
||||
fn min_w (&self, a: O::Area) -> O::Unit { (**self).min_w(a) }
|
||||
fn max_w (&self, a: O::Area) -> O::Unit { (**self).max_w(a) }
|
||||
fn h (&self, a: O::Area) -> O::Unit { (**self).h(a) }
|
||||
fn min_h (&self, a: O::Area) -> O::Unit { (**self).min_h(a) }
|
||||
fn max_h (&self, a: O::Area) -> O::Unit { (**self).max_h(a) }
|
||||
fn layout (&self, a: O::Area) -> O::Area { (**self).layout(a) }
|
||||
}
|
||||
|
||||
impl<O: Out, L: Layout<O>> Layout<O> for Arc<L> {
|
||||
fn x (&self, a: O::Area) -> O::Unit { (**self).x(a) }
|
||||
fn y (&self, a: O::Area) -> O::Unit { (**self).y(a) }
|
||||
fn w (&self, a: O::Area) -> O::Unit { (**self).w(a) }
|
||||
fn min_w (&self, a: O::Area) -> O::Unit { (**self).min_w(a) }
|
||||
fn max_w (&self, a: O::Area) -> O::Unit { (**self).max_w(a) }
|
||||
fn h (&self, a: O::Area) -> O::Unit { (**self).h(a) }
|
||||
fn min_h (&self, a: O::Area) -> O::Unit { (**self).min_h(a) }
|
||||
fn max_h (&self, a: O::Area) -> O::Unit { (**self).max_h(a) }
|
||||
fn layout (&self, a: O::Area) -> O::Area { (**self).layout(a) }
|
||||
}
|
||||
|
||||
impl<O: Out> Layout<O> for Box<dyn Layout<O>> {
|
||||
fn x (&self, a: O::Area) -> O::Unit { (**self).x(a) }
|
||||
fn y (&self, a: O::Area) -> O::Unit { (**self).y(a) }
|
||||
fn w (&self, a: O::Area) -> O::Unit { (**self).w(a) }
|
||||
fn min_w (&self, a: O::Area) -> O::Unit { (**self).min_w(a) }
|
||||
fn max_w (&self, a: O::Area) -> O::Unit { (**self).max_w(a) }
|
||||
fn h (&self, a: O::Area) -> O::Unit { (**self).h(a) }
|
||||
fn min_h (&self, a: O::Area) -> O::Unit { (**self).min_h(a) }
|
||||
fn max_h (&self, a: O::Area) -> O::Unit { (**self).max_h(a) }
|
||||
fn layout (&self, a: O::Area) -> O::Area { (**self).layout(a) }
|
||||
}
|
||||
|
||||
impl<O: Out, L: Layout<O>> Layout<O> for RwLock<L> {
|
||||
fn x (&self, a: O::Area) -> O::Unit { self.read().unwrap().x(a) }
|
||||
fn y (&self, a: O::Area) -> O::Unit { self.read().unwrap().y(a) }
|
||||
fn w (&self, a: O::Area) -> O::Unit { self.read().unwrap().w(a) }
|
||||
fn min_w (&self, a: O::Area) -> O::Unit { self.read().unwrap().min_w(a) }
|
||||
fn max_w (&self, a: O::Area) -> O::Unit { self.read().unwrap().max_w(a) }
|
||||
fn h (&self, a: O::Area) -> O::Unit { self.read().unwrap().h(a) }
|
||||
fn min_h (&self, a: O::Area) -> O::Unit { self.read().unwrap().min_h(a) }
|
||||
fn max_h (&self, a: O::Area) -> O::Unit { self.read().unwrap().max_h(a) }
|
||||
fn layout (&self, a: O::Area) -> O::Area { self.read().unwrap().layout(a) }
|
||||
}
|
||||
|
||||
impl<O: Out, L: Layout<O>> Layout<O> for Option<L> {
|
||||
fn x (&self, to: O::Area) -> O::Unit {
|
||||
self.as_ref().map(|c|c.x(to)).unwrap_or(to.x())
|
||||
}
|
||||
fn y (&self, to: O::Area) -> O::Unit {
|
||||
self.as_ref().map(|c|c.y(to)).unwrap_or(to.y())
|
||||
}
|
||||
fn min_w (&self, to: O::Area) -> O::Unit {
|
||||
self.as_ref().map(|c|c.min_w(to)).unwrap_or(0.into())
|
||||
}
|
||||
fn max_w (&self, to: O::Area) -> O::Unit {
|
||||
self.as_ref().map(|c|c.max_w(to)).unwrap_or(0.into())
|
||||
}
|
||||
fn w (&self, to: O::Area) -> O::Unit {
|
||||
self.as_ref().map(|c|c.w(to)).unwrap_or(0.into())
|
||||
}
|
||||
fn min_h (&self, to: O::Area) -> O::Unit {
|
||||
self.as_ref().map(|c|c.min_h(to)).unwrap_or(0.into())
|
||||
}
|
||||
fn max_h (&self, to: O::Area) -> O::Unit {
|
||||
self.as_ref().map(|c|c.max_h(to)).unwrap_or(0.into())
|
||||
}
|
||||
fn h (&self, to: O::Area) -> O::Unit {
|
||||
self.as_ref().map(|c|c.h(to)).unwrap_or(0.into())
|
||||
}
|
||||
fn layout (&self, to: O::Area) -> O::Area {
|
||||
self.as_ref().map(|c|c.layout([self.x(to), self.y(to), self.w(to), self.h(to)].into()))
|
||||
.unwrap_or([to.x(), to.y(), 0.into(), 0.into()].into())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Bounded<O: Out, D>(pub O::Area, pub D);
|
||||
|
||||
impl<O: Out, D: Content<O>> HasContent<O> for Bounded<O, D> {
|
||||
fn content (&self) -> impl Content<O> {
|
||||
&self.1
|
||||
}
|
||||
}
|
||||
|
||||
impl<O: Out, T: Draw<O>> Draw<O> for Bounded<O, T> {
|
||||
fn draw (&self, to: &mut O) {
|
||||
let area = to.area();
|
||||
*to.area_mut() = self.0;
|
||||
self.1.draw(to);
|
||||
*to.area_mut() = area;
|
||||
}
|
||||
}
|
||||
69
output/src/layout/layout_align.rs
Normal file
69
output/src/layout/layout_align.rs
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
//! ```
|
||||
//! use ::tengri::{output::*, tui::*};
|
||||
//! let area: [u16;4] = [10, 10, 20, 20];
|
||||
//! fn test (area: [u16;4], item: &impl Draw<TuiOut>, expected: [u16;4]) {
|
||||
//! assert_eq!(Content::layout(item, area), expected);
|
||||
//! assert_eq!(Draw::layout(item, area), expected);
|
||||
//! };
|
||||
//!
|
||||
//! let four = ||Fixed::XY(4, 4, "");
|
||||
//! test(area, &Align::nw(four()), [10, 10, 4, 4]);
|
||||
//! test(area, &Align::n(four()), [18, 10, 4, 4]);
|
||||
//! test(area, &Align::ne(four()), [26, 10, 4, 4]);
|
||||
//! test(area, &Align::e(four()), [26, 18, 4, 4]);
|
||||
//! test(area, &Align::se(four()), [26, 26, 4, 4]);
|
||||
//! test(area, &Align::s(four()), [18, 26, 4, 4]);
|
||||
//! test(area, &Align::sw(four()), [10, 26, 4, 4]);
|
||||
//! test(area, &Align::w(four()), [10, 18, 4, 4]);
|
||||
//!
|
||||
//! let two_by_four = ||Fixed::XY(4, 2, "");
|
||||
//! test(area, &Align::nw(two_by_four()), [10, 10, 4, 2]);
|
||||
//! test(area, &Align::n(two_by_four()), [18, 10, 4, 2]);
|
||||
//! test(area, &Align::ne(two_by_four()), [26, 10, 4, 2]);
|
||||
//! test(area, &Align::e(two_by_four()), [26, 19, 4, 2]);
|
||||
//! test(area, &Align::se(two_by_four()), [26, 28, 4, 2]);
|
||||
//! test(area, &Align::s(two_by_four()), [18, 28, 4, 2]);
|
||||
//! test(area, &Align::sw(two_by_four()), [10, 28, 4, 2]);
|
||||
//! test(area, &Align::w(two_by_four()), [10, 19, 4, 2]);
|
||||
//! ```
|
||||
use crate::*;
|
||||
use Alignment::*;
|
||||
|
||||
/// 9th of area to place.
|
||||
#[derive(Debug, Copy, Clone, Default)]
|
||||
pub enum Alignment { #[default] Center, X, Y, NW, N, NE, E, SE, S, SW, W }
|
||||
pub struct Align<T>(Alignment, T);
|
||||
impl<T> Align<T> {
|
||||
#[inline] pub const fn c (a: T) -> Self { Self(Alignment::Center, a) }
|
||||
#[inline] pub const fn x (a: T) -> Self { Self(Alignment::X, a) }
|
||||
#[inline] pub const fn y (a: T) -> Self { Self(Alignment::Y, a) }
|
||||
#[inline] pub const fn n (a: T) -> Self { Self(Alignment::N, a) }
|
||||
#[inline] pub const fn s (a: T) -> Self { Self(Alignment::S, a) }
|
||||
#[inline] pub const fn e (a: T) -> Self { Self(Alignment::E, a) }
|
||||
#[inline] pub const fn w (a: T) -> Self { Self(Alignment::W, a) }
|
||||
#[inline] pub const fn nw (a: T) -> Self { Self(Alignment::NW, a) }
|
||||
#[inline] pub const fn sw (a: T) -> Self { Self(Alignment::SW, a) }
|
||||
#[inline] pub const fn ne (a: T) -> Self { Self(Alignment::NE, a) }
|
||||
#[inline] pub const fn se (a: T) -> Self { Self(Alignment::SE, a) }
|
||||
}
|
||||
impl<O: Out, T: Content<O>> Draw<O> for Align<T> {
|
||||
fn draw (&self, to: &mut O) { Bounded(self.layout(to.area()), &self.1).draw(to) }
|
||||
}
|
||||
impl<O: Out, T: Layout<O>> Layout<O> for Align<T> {
|
||||
fn x (&self, to: O::Area) -> O::Unit {
|
||||
match self.0 {
|
||||
NW | W | SW => to.x(),
|
||||
N | Center | S => to.x().plus(to.w() / 2.into()).minus(self.1.w(to) / 2.into()),
|
||||
NE | E | SE => to.x().plus(to.w()).minus(self.1.w(to)),
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
fn y (&self, to: O::Area) -> O::Unit {
|
||||
match self.0 {
|
||||
NW | N | NE => to.y(),
|
||||
W | Center | E => to.y().plus(to.h() / 2.into()).minus(self.1.h(to) / 2.into()),
|
||||
SW | S | SE => to.y().plus(to.h()).minus(self.1.h(to)),
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
152
output/src/layout/layout_bsp.rs
Normal file
152
output/src/layout/layout_bsp.rs
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
use crate::*;
|
||||
|
||||
/// A binary split or layer.
|
||||
pub struct Bsp<Head, Tail>(
|
||||
pub(crate) Direction,
|
||||
/// First element.
|
||||
pub(crate) Head,
|
||||
/// Second element.
|
||||
pub(crate) Tail,
|
||||
);
|
||||
|
||||
impl<Head, Tail> Bsp<Head, Tail> {
|
||||
#[inline] pub const fn n (a: Head, b: Tail) -> Self { Self(North, a, b) }
|
||||
#[inline] pub const fn s (a: Head, b: Tail) -> Self { Self(South, a, b) }
|
||||
#[inline] pub const fn e (a: Head, b: Tail) -> Self { Self(East, a, b) }
|
||||
#[inline] pub const fn w (a: Head, b: Tail) -> Self { Self(West, a, b) }
|
||||
#[inline] pub const fn a (a: Head, b: Tail) -> Self { Self(Above, a, b) }
|
||||
#[inline] pub const fn b (a: Head, b: Tail) -> Self { Self(Below, a, b) }
|
||||
}
|
||||
|
||||
impl<O: Out, Head: Content<O>, Tail: Content<O>> Draw<O> for Bsp<Head, Tail> {
|
||||
fn draw (&self, to: &mut O) {
|
||||
match self.0 {
|
||||
South => {
|
||||
panic!("{}", self.1.h(to.area()));
|
||||
let area_1 = self.1.layout(to.area());
|
||||
let area_2 = self.2.layout([
|
||||
to.area().x(),
|
||||
to.area().y().plus(area_1.h()),
|
||||
to.area().w(),
|
||||
to.area().h().minus(area_1.h())
|
||||
].into());
|
||||
panic!("{area_1:?} {area_2:?}");
|
||||
to.place_at(area_1, &self.1);
|
||||
to.place_at(area_2, &self.2);
|
||||
},
|
||||
_ => todo!("{:?}", self.0)
|
||||
}
|
||||
//let [a, b, _] = bsp_areas(to.area(), self.0, &self.1, &self.2);
|
||||
//panic!("{a:?} {b:?}");
|
||||
//if self.0 == Below {
|
||||
//to.place_at(a, &self.1);
|
||||
//to.place_at(b, &self.2);
|
||||
//} else {
|
||||
//to.place_at(b, &self.2);
|
||||
//to.place_at(a, &self.1);
|
||||
//}
|
||||
}
|
||||
}
|
||||
impl<O: Out, Head: Layout<O>, Tail: Layout<O>> Layout<O> for Bsp<Head, Tail> {
|
||||
fn w (&self, area: O::Area) -> O::Unit {
|
||||
match self.0 {
|
||||
North | South | Above | Below => self.1.w(area).max(self.2.w(area)),
|
||||
East | West => self.1.min_w(area).plus(self.2.w(area)),
|
||||
}
|
||||
}
|
||||
fn min_w (&self, area: O::Area) -> O::Unit {
|
||||
match self.0 {
|
||||
North | South | Above | Below => self.1.min_w(area).max(self.2.min_w(area)),
|
||||
East | West => self.1.min_w(area).plus(self.2.min_w(area)),
|
||||
}
|
||||
}
|
||||
fn max_w (&self, area: O::Area) -> O::Unit {
|
||||
match self.0 {
|
||||
North | South | Above | Below => self.1.max_w(area).max(self.2.max_w(area)),
|
||||
East | West => self.1.max_w(area).plus(self.2.max_w(area)),
|
||||
}
|
||||
}
|
||||
fn h (&self, area: O::Area) -> O::Unit {
|
||||
match self.0 {
|
||||
East | West | Above | Below => self.1.h(area).max(self.2.h(area)),
|
||||
North | South => self.1.h(area).plus(self.2.h(area)),
|
||||
}
|
||||
}
|
||||
fn min_h (&self, area: O::Area) -> O::Unit {
|
||||
match self.0 {
|
||||
East | West | Above | Below => self.1.min_h(area).max(self.2.min_h(area)),
|
||||
North | South => self.1.min_h(area).plus(self.2.min_h(area)),
|
||||
}
|
||||
}
|
||||
fn max_h (&self, area: O::Area) -> O::Unit {
|
||||
match self.0 {
|
||||
North | South | Above | Below => self.1.max_h(area).max(self.2.max_h(area)),
|
||||
East | West => self.1.max_h(area).plus(self.2.max_h(area)),
|
||||
}
|
||||
}
|
||||
fn layout (&self, area: O::Area) -> O::Area {
|
||||
bsp_areas(area, self.0, &self.1, &self.2)[2]
|
||||
}
|
||||
}
|
||||
|
||||
fn bsp_areas <O: Out, A: Layout<O>, B: Layout<O>> (
|
||||
area: O::Area, direction: Direction, a: &A, b: &B,
|
||||
) -> [O::Area;3] {
|
||||
let [x, y, w, h] = area.xywh();
|
||||
let [aw, ah] = a.layout(area).wh();
|
||||
let [bw, bh] = b.layout(match direction {
|
||||
Above | Below => area,
|
||||
South => [x, y + ah, w, h.minus(ah)].into(),
|
||||
North => [x, y, w, h.minus(ah)].into(),
|
||||
East => [x + aw, y, w.minus(aw), h].into(),
|
||||
West => [x, y, w.minus(aw), h].into(),
|
||||
}).wh();
|
||||
match direction {
|
||||
Above | Below => {
|
||||
let [x, y, w, h] = area.center_xy([aw.max(bw), ah.max(bh)]);
|
||||
let a = [(x + w/2.into()).minus(aw/2.into()), (y + h/2.into()).minus(ah/2.into()), aw, ah];
|
||||
let b = [(x + w/2.into()).minus(bw/2.into()), (y + h/2.into()).minus(bh/2.into()), bw, bh];
|
||||
[a.into(), b.into(), [x, y, w, h].into()]
|
||||
},
|
||||
South => {
|
||||
let [x, y, w, h] = area.center_xy([aw.max(bw), ah + bh]);
|
||||
let a = [(x + w/2.into()).minus(aw/2.into()), y, aw, ah];
|
||||
let b = [(x + w/2.into()).minus(bw/2.into()), y + ah, bw, bh];
|
||||
[a.into(), b.into(), [x, y, w, h].into()]
|
||||
},
|
||||
North => {
|
||||
let [x, y, w, h] = area.center_xy([aw.max(bw), ah + bh]);
|
||||
let a = [(x + (w/2.into())).minus(aw/2.into()), y + bh, aw, ah];
|
||||
let b = [(x + (w/2.into())).minus(bw/2.into()), y, bw, bh];
|
||||
[a.into(), b.into(), [x, y, w, h].into()]
|
||||
},
|
||||
East => {
|
||||
let [x, y, w, h] = area.center_xy([aw + bw, ah.max(bh)]);
|
||||
let a = [x, (y + h/2.into()).minus(ah/2.into()), aw, ah];
|
||||
let b = [x + aw, (y + h/2.into()).minus(bh/2.into()), bw, bh];
|
||||
[a.into(), b.into(), [x, y, w, h].into()]
|
||||
},
|
||||
West => {
|
||||
let [x, y, w, h] = area.center_xy([aw + bw, ah.max(bh)]);
|
||||
let a = [x + bw, (y + h/2.into()).minus(ah/2.into()), aw, ah];
|
||||
let b = [x, (y + h/2.into()).minus(bh/2.into()), bw, bh];
|
||||
[a.into(), b.into(), [x, y, w, h].into()]
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Stack things on top of each other,
|
||||
#[macro_export] macro_rules! lay (($($expr:expr),* $(,)?) =>
|
||||
{{ let bsp = (); $(let bsp = Bsp::b(bsp, $expr);)*; bsp }});
|
||||
|
||||
/// Stack southward.
|
||||
#[macro_export] macro_rules! col (($($expr:expr),* $(,)?) =>
|
||||
{{ let bsp = (); $(let bsp = Bsp::s(bsp, $expr);)*; bsp }});
|
||||
|
||||
/// Stack northward.
|
||||
#[macro_export] macro_rules! col_up (($($expr:expr),* $(,)?) =>
|
||||
{{ let bsp = (); $(let bsp = Bsp::n(bsp, $expr);)*; bsp }});
|
||||
|
||||
/// Stack eastward.
|
||||
#[macro_export] macro_rules! row (($($expr:expr),* $(,)?) =>
|
||||
{{ let bsp = (); $(let bsp = Bsp::e(bsp, $expr);)*; bsp }});
|
||||
211
output/src/layout/layout_cond.rs
Normal file
211
output/src/layout/layout_cond.rs
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
use crate::*;
|
||||
|
||||
/// Show an item only when a condition is true.
|
||||
pub struct When<O, T>(bool, T, PhantomData<O>);
|
||||
impl<O: Out, T: Content<O>> When<O, T> {
|
||||
/// Create a binary condition.
|
||||
pub const fn new (c: bool, a: T) -> Self { Self(c, a, PhantomData) }
|
||||
}
|
||||
impl<O: Out, T: Layout<O>> Layout<O> for When<O, T> {
|
||||
fn layout (&self, to: O::Area) -> O::Area {
|
||||
let Self(cond, item, ..) = self;
|
||||
if *cond { item.layout(to) } else { O::Area::zero().into() }
|
||||
}
|
||||
}
|
||||
impl<O: Out, T: Content<O>> Draw<O> for When<O, T> {
|
||||
fn draw (&self, to: &mut O) {
|
||||
let Self(cond, item, ..) = self;
|
||||
if *cond { Bounded(self.layout(to.area()), item).draw(to) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Show one item if a condition is true and another if the condition is false
|
||||
pub struct Either<E: Out, A, B>(pub bool, pub A, pub B, pub PhantomData<E>);
|
||||
impl<E: Out, A: Content<E>, B: Content<E>> Either<E, A, B> {
|
||||
/// Create a ternary view condition.
|
||||
pub const fn new (c: bool, a: A, b: B) -> Self {
|
||||
Self(c, a, b, PhantomData)
|
||||
}
|
||||
}
|
||||
impl<E: Out, A: Layout<E>, B: Layout<E>> Layout<E> for Either<E, A, B> {
|
||||
fn layout (&self, to: E::Area) -> E::Area {
|
||||
let Self(cond, a, b, ..) = self;
|
||||
if *cond { a.layout(to) } else { b.layout(to) }
|
||||
}
|
||||
}
|
||||
impl<E: Out, A: Content<E>, B: Content<E>> Draw<E> for Either<E, A, B> {
|
||||
fn draw (&self, to: &mut E) {
|
||||
let Self(cond, a, b, ..) = self;
|
||||
let area = self.layout(to.area());
|
||||
if *cond { Bounded(area, a).draw(to) } else { Bounded(area, b).draw(to) }
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
///// The syntagm `(when :condition :content)` corresponds to a [When] layout element.
|
||||
//impl<S, A> FromDsl<S> for When<A> where bool: FromDsl<S>, A: FromDsl<S> {
|
||||
//fn try_provide (state: &S, source: &DslVal<impl DslStr, impl DslExpr>) -> Perhaps<Self> {
|
||||
//source.exp_match("when", |_, tail|Ok(Some(Self(
|
||||
//FromDsl::<S>::provide(state,
|
||||
//tail.nth(0, ||"no condition".into())?, ||"no condition".into())?,
|
||||
//FromDsl::<S>::provide(state,
|
||||
//tail.nth(1, ||"no content".into())?, ||"no content".into())?,
|
||||
//))))
|
||||
//}
|
||||
//}
|
||||
///// The syntagm `(either :condition :content1 :content2)` corresponds to an [Either] layout element.
|
||||
//impl<S, A, B> FromDsl<S> for Either<A, B> where S: Eval<Ast, bool> + Eval<Ast, A> + Eval<Ast, B> {
|
||||
//fn try_provide (state: &S, source: &DslVal<impl DslStr, impl DslExpr>) -> Perhaps<Self> {
|
||||
//source.exp_match("either", |_, tail|Ok(Some(Self(
|
||||
//state.eval(tail.nth(0, ||"no condition")?, ||"no condition")?,
|
||||
//state.eval(tail.nth(1, ||"no content 1")?, ||"no content 1")?,
|
||||
//state.eval(tail.nth(2, ||"no content 1")?, ||"no content 2")?,
|
||||
//))))
|
||||
//}
|
||||
//}
|
||||
///// The syntagm `(align/* :content)` corresponds to an [Align] layout element,
|
||||
///// where `*` specifies the direction of the alignment.
|
||||
//impl<S, A> FromDsl<S> for Align<A> where S: Eval<Option<Ast>, A> {
|
||||
//fn try_provide (state: &S, source: &DslVal<impl DslStr, impl DslExpr>) -> Perhaps<Self> {
|
||||
//source.exp_match("align/", |head, tail|Ok(Some(match head {
|
||||
//"c" => Self::c(state.eval(tail.nth(0, ||"no content")?, ||"no content")),
|
||||
//"x" => Self::x(state.eval(tail.nth(0, ||"no content")?, ||"no content")),
|
||||
//"y" => Self::y(state.eval(tail.nth(0, ||"no content")?, ||"no content")),
|
||||
//"n" => Self::n(state.eval(tail.nth(0, ||"no content")?, ||"no content")),
|
||||
//"s" => Self::s(state.eval(tail.nth(0, ||"no content")?, ||"no content")),
|
||||
//"e" => Self::e(state.eval(tail.nth(0, ||"no content")?, ||"no content")),
|
||||
//"w" => Self::w(state.eval(tail.nth(0, ||"no content")?, ||"no content")),
|
||||
//"nw" => Self::nw(state.eval(tail.nth(0, ||"no content")?, ||"no content")),
|
||||
//"ne" => Self::ne(state.eval(tail.nth(0, ||"no content")?, ||"no content")),
|
||||
//"sw" => Self::sw(state.eval(tail.nth(0, ||"no content")?, ||"no content")),
|
||||
//"se" => Self::se(state.eval(tail.nth(0, ||"no content")?, ||"no content")),
|
||||
//_ => return Err("invalid align variant".into())
|
||||
//})))
|
||||
//}
|
||||
//}
|
||||
///// The syntagm `(bsp/* :content1 :content2)` corresponds to a [Bsp] layout element,
|
||||
///// where `*` specifies the direction of the split.
|
||||
//impl<S, A, B> FromDsl<S> for Bsp<A, B> where S: Eval<Option<Ast>, A> + Eval<Option<Ast>, B> {
|
||||
//fn try_provide (state: &S, source: &DslVal<impl DslStr, impl DslExpr>) -> Perhaps<Self> {
|
||||
//source.exp_match("bsp/", |head, tail|Ok(Some(match head {
|
||||
//"n" => Self::n(tail.nth(0, ||"no content 1"), tail.nth(1, ||"no content 2")),
|
||||
//"s" => Self::s(tail.nth(0, ||"no content 1"), tail.nth(1, ||"no content 2")),
|
||||
//"e" => Self::e(tail.nth(0, ||"no content 1"), tail.nth(1, ||"no content 2")),
|
||||
//"w" => Self::w(tail.nth(0, ||"no content 1"), tail.nth(1, ||"no content 2")),
|
||||
//"a" => Self::a(tail.nth(0, ||"no content 1"), tail.nth(1, ||"no content 2")),
|
||||
//"b" => Self::b(tail.nth(0, ||"no content 1"), tail.nth(1, ||"no content 2")),
|
||||
//_ => return Ok(None),
|
||||
//})))
|
||||
//}
|
||||
//}
|
||||
//#[cfg(feature = "dsl")] take!($Enum<A>, A|state, words|Ok(
|
||||
//if let Some(Token { value: Key(k), .. }) = words.peek() {
|
||||
//let mut base = words.clone();
|
||||
//let content = state.give_or_fail(words, ||format!("{k}: no content"))?;
|
||||
//return Ok(Some(match words.next() {
|
||||
//Some(Token{value: Key($x),..}) => Self::x(content),
|
||||
//Some(Token{value: Key($y),..}) => Self::y(content),
|
||||
//Some(Token{value: Key($xy),..}) => Self::XY(content),
|
||||
//_ => unreachable!()
|
||||
//}))
|
||||
//} else {
|
||||
//None
|
||||
//}));
|
||||
//#[cfg(feature = "dsl")] take!($Enum<U, A>, U, A|state, words|Ok(
|
||||
//if let Some(Token { value: Key($x|$y|$xy), .. }) = words.peek() {
|
||||
//let mut base = words.clone();
|
||||
//Some(match words.next() {
|
||||
//Some(Token { value: Key($x), .. }) => Self::x(
|
||||
//state.give_or_fail(words, ||"x: no unit")?,
|
||||
//state.give_or_fail(words, ||"x: no content")?,
|
||||
//),
|
||||
//Some(Token { value: Key($y), .. }) => Self::y(
|
||||
//state.give_or_fail(words, ||"y: no unit")?,
|
||||
//state.give_or_fail(words, ||"y: no content")?,
|
||||
//),
|
||||
//Some(Token { value: Key($x), .. }) => Self::XY(
|
||||
//state.give_or_fail(words, ||"xy: no unit x")?,
|
||||
//state.give_or_fail(words, ||"xy: no unit y")?,
|
||||
//state.give_or_fail(words, ||"xy: no content")?
|
||||
//),
|
||||
//_ => unreachable!(),
|
||||
//})
|
||||
//} else {
|
||||
//None
|
||||
//}));
|
||||
//if let Exp(_, exp) = source.value() {
|
||||
//let mut rest = exp.clone();
|
||||
//return Ok(Some(match rest.next().as_ref().and_then(|x|x.key()) {
|
||||
//Some("bsp/n") => Self::n(
|
||||
//state.eval(rest.next(), ||"bsp/n: no content 1")?,
|
||||
//state.eval(rest.next(), ||"bsp/n: no content 2")?,
|
||||
//),
|
||||
//Some("bsp/s") => Self::s(
|
||||
//state.eval(rest.next(), ||"bsp/s: no content 1")?,
|
||||
//state.eval(rest.next(), ||"bsp/s: no content 2")?,
|
||||
//),
|
||||
//Some("bsp/e") => Self::e(
|
||||
//state.eval(rest.next(), ||"bsp/e: no content 1")?,
|
||||
//state.eval(rest.next(), ||"bsp/e: no content 2")?,
|
||||
//),
|
||||
//Some("bsp/w") => Self::w(
|
||||
//state.eval(rest.next(), ||"bsp/w: no content 1")?,
|
||||
//state.eval(rest.next(), ||"bsp/w: no content 2")?,
|
||||
//),
|
||||
//Some("bsp/a") => Self::a(
|
||||
//state.eval(rest.next(), ||"bsp/a: no content 1")?,
|
||||
//state.eval(rest.next(), ||"bsp/a: no content 2")?,
|
||||
//),
|
||||
//Some("bsp/b") => Self::b(
|
||||
//state.eval(rest.next(), ||"bsp/b: no content 1")?,
|
||||
//state.eval(rest.next(), ||"bsp/b: no content 2")?,
|
||||
//),
|
||||
//_ => return Ok(None),
|
||||
//}))
|
||||
//}
|
||||
//Ok(None)
|
||||
//if let Exp(_, source) = source.value() {
|
||||
//let mut rest = source.clone();
|
||||
//return Ok(Some(match rest.next().as_ref().and_then(|x|x.key()) {
|
||||
//Some("align/c") => Self::c(state.eval(rest.next(), ||"align/c: no content")?),
|
||||
//Some("align/x") => Self::x(state.eval(rest.next(), ||"align/x: no content")?),
|
||||
//Some("align/y") => Self::y(state.eval(rest.next(), ||"align/y: no content")?),
|
||||
//Some("align/n") => Self::n(state.eval(rest.next(), ||"align/n: no content")?),
|
||||
//Some("align/s") => Self::s(state.eval(rest.next(), ||"align/s: no content")?),
|
||||
//Some("align/e") => Self::e(state.eval(rest.next(), ||"align/e: no content")?),
|
||||
//Some("align/w") => Self::w(state.eval(rest.next(), ||"align/w: no content")?),
|
||||
//Some("align/nw") => Self::nw(state.eval(rest.next(), ||"align/nw: no content")?),
|
||||
//Some("align/ne") => Self::ne(state.eval(rest.next(), ||"align/ne: no content")?),
|
||||
//Some("align/sw") => Self::sw(state.eval(rest.next(), ||"align/sw: no content")?),
|
||||
//Some("align/se") => Self::se(state.eval(rest.next(), ||"align/se: no content")?),
|
||||
//_ => return Ok(None),
|
||||
//}))
|
||||
//}
|
||||
//Ok(None)
|
||||
//Ok(match source.exp_head().and_then(|e|e.key()) {
|
||||
//Some("either") => Some(Self(
|
||||
//source.exp_tail().and_then(|t|t.get(0)).map(|x|state.eval(x, ||"when: no condition"))?,
|
||||
//source.exp_tail().and_then(|t|t.get(1)).map(|x|state.eval(x, ||"when: no content 1"))?,
|
||||
//source.exp_tail().and_then(|t|t.get(2)).map(|x|state.eval(x, ||"when: no content 2"))?,
|
||||
//)),
|
||||
//_ => None
|
||||
//})
|
||||
//if let Exp(_, mut exp) = source.value()
|
||||
//&& let Some(Ast(Key(id))) = exp.peek() && *id == *"either" {
|
||||
//let _ = exp.next();
|
||||
//return Ok(Some(Self(
|
||||
//state.eval(exp.next().unwrap(), ||"either: no condition")?,
|
||||
//state.eval(exp.next().unwrap(), ||"either: no content 1")?,
|
||||
//state.eval(exp.next().unwrap(), ||"either: no content 2")?,
|
||||
//)))
|
||||
//}
|
||||
//Ok(None)
|
||||
//Ok(match source.exp_head().and_then(|e|e.key()) {
|
||||
//Some("when") => Some(Self(
|
||||
//source.exp_tail().and_then(|t|t.get(0)).map(|x|state.eval(x, ||"when: no condition"))?,
|
||||
//source.exp_tail().and_then(|t|t.get(1)).map(|x|state.eval(x, ||"when: no content"))?,
|
||||
//)),
|
||||
//_ => None
|
||||
//})
|
||||
0
output/src/layout/layout_iter.rs
Normal file
0
output/src/layout/layout_iter.rs
Normal file
139
output/src/layout/layout_map.rs
Normal file
139
output/src/layout/layout_map.rs
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
use crate::*;
|
||||
|
||||
/// Draws items from an iterator.
|
||||
pub struct Map<O, A, B, I, F, G>
|
||||
where
|
||||
I: Iterator<Item = A> + Send + Sync,
|
||||
F: Fn() -> I + Send + Sync,
|
||||
{
|
||||
__: PhantomData<(O, B)>,
|
||||
/// Function that returns iterator over stacked components
|
||||
get_iter: F,
|
||||
/// Function that returns each stacked component
|
||||
get_item: G,
|
||||
}
|
||||
|
||||
impl<'a, O, A, B, I, F, G> Map<O, 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_map_direction (($name:ident, $axis:ident, $align:ident)=>{
|
||||
impl<'a, O, A, B, I, F> Map<
|
||||
O, A, Push<O::Unit, Align<Fixed<O::Unit, Fill<B>>>>, I, F, fn(A, usize)->B
|
||||
> where
|
||||
O: Out,
|
||||
B: Draw<O>,
|
||||
I: Iterator<Item = A> + Send + Sync + 'a,
|
||||
F: Fn() -> I + Send + Sync + 'a
|
||||
{
|
||||
pub const fn $name (
|
||||
size: O::Unit,
|
||||
get_iter: F,
|
||||
get_item: impl Fn(A, usize)->B + Send + Sync
|
||||
) -> Map<
|
||||
O, A,
|
||||
Push<O::Unit, Align<Fixed<O::Unit, B>>>,
|
||||
I, F,
|
||||
impl Fn(A, usize)->Push<O::Unit, Align<Fixed<O::Unit, B>>> + Send + Sync
|
||||
> {
|
||||
Map {
|
||||
__: PhantomData,
|
||||
get_iter,
|
||||
get_item: move |item: A, index: usize|{
|
||||
// FIXME: multiply
|
||||
let mut push: O::Unit = O::Unit::from(0u16);
|
||||
for _ in 0..index {
|
||||
push = push + size;
|
||||
}
|
||||
Push::$axis(push, Align::$align(Fixed::$axis(size, get_item(item, index))))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
impl_map_direction!(east, X, w);
|
||||
impl_map_direction!(south, Y, n);
|
||||
impl_map_direction!(west, X, e);
|
||||
impl_map_direction!(north, Y, s);
|
||||
|
||||
impl<'a, O, A, B, I, F, G> Layout<O> for Map<O, A, B, I, F, G> where
|
||||
O: Out,
|
||||
B: Layout<O>,
|
||||
I: Iterator<Item = A> + Send + Sync + 'a,
|
||||
F: Fn() -> I + Send + Sync + 'a,
|
||||
G: Fn(A, usize)->B + Send + Sync
|
||||
{
|
||||
fn layout (&self, area: O::Area) -> O::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);
|
||||
min_y = min_y.min(y);
|
||||
max_x = max_x.max(x + w);
|
||||
max_y = max_y.max(y + h);
|
||||
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()
|
||||
}
|
||||
}
|
||||
impl<'a, O, A, B, I, F, G> Draw<O> for Map<O, A, B, I, F, G> where
|
||||
O: Out,
|
||||
B: Content<O>,
|
||||
I: Iterator<Item = A> + Send + Sync + 'a,
|
||||
F: Fn() -> I + Send + Sync + 'a,
|
||||
G: Fn(A, usize)->B + Send + Sync
|
||||
{
|
||||
fn draw (&self, to: &mut O) {
|
||||
let Self { get_iter, get_item, .. } = self;
|
||||
let mut index = 0;
|
||||
let area = self.layout(to.area());
|
||||
for item in get_iter() {
|
||||
let item = get_item(item, index);
|
||||
//to.place_at(area.into(), &item);
|
||||
to.place_at(item.layout(area), &item);
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline] pub fn map_south<O: Out>(
|
||||
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: Out>(
|
||||
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: Out>(
|
||||
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))))
|
||||
}
|
||||
|
||||
|
||||
33
output/src/layout/layout_move.rs
Normal file
33
output/src/layout/layout_move.rs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
use crate::*;
|
||||
/// Increment X and/or Y coordinate.
|
||||
pub enum Push<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
|
||||
impl<U, A> Push<U, A> {
|
||||
#[inline] pub const fn inner (&self) -> &A { match self { Self::X(_, c) | Self::Y(_, c) | Self::XY(_, _, c) => c, } }
|
||||
}
|
||||
impl<U: Coordinate, T> Push<U, T> {
|
||||
#[inline] pub fn dx (&self) -> U { match self { Self::X(x, ..) | Self::XY(x, _, _) => *x, Self::Y(_, _) => 0.into() } }
|
||||
#[inline] pub fn dy (&self) -> U { match self { Self::Y(y, ..) | Self::XY(_, y, _) => *y, Self::X(_, _) => 0.into() } }
|
||||
}
|
||||
impl<O: Out, T: Content<O>> Draw<O> for Push<O::Unit, T> {
|
||||
fn draw (&self, to: &mut O) { Bounded(self.layout(to.area()), self.inner()).draw(to) }
|
||||
}
|
||||
impl<O: Out, T: Layout<O>> Layout<O> for Push<O::Unit, T> {
|
||||
fn x (&self, area: O::Area) -> O::Unit { area.x().plus(self.dx()) }
|
||||
fn y (&self, area: O::Area) -> O::Unit { area.y().plus(self.dy()) }
|
||||
}
|
||||
/// Decrement X and/or Y coordinate.
|
||||
pub enum Pull<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
|
||||
impl<U, A> Pull<U, A> {
|
||||
#[inline] pub const fn inner (&self) -> &A { match self { Self::X(_, c) | Self::Y(_, c) | Self::XY(_, _, c) => c, } }
|
||||
}
|
||||
impl<U: Coordinate, T> Pull<U, T> {
|
||||
#[inline] pub fn dx (&self) -> U { match self { Self::X(x, ..) | Self::XY(x, _, _) => *x, Self::Y(_, _) => 0.into() } }
|
||||
#[inline] pub fn dy (&self) -> U { match self { Self::Y(y, ..) | Self::XY(_, y, _) => *y, Self::X(_, _) => 0.into() } }
|
||||
}
|
||||
impl<O: Out, T: Content<O>> Draw<O> for Pull<O::Unit, T> {
|
||||
fn draw (&self, to: &mut O) { Bounded(self.layout(to.area()), self.inner()).draw(to) }
|
||||
}
|
||||
impl<O: Out, T: Layout<O>> Layout<O> for Pull<O::Unit, T> {
|
||||
fn x (&self, area: O::Area) -> O::Unit { area.x().minus(self.dx()) }
|
||||
fn y (&self, area: O::Area) -> O::Unit { area.y().minus(self.dy()) }
|
||||
}
|
||||
27
output/src/layout/layout_pad.rs
Normal file
27
output/src/layout/layout_pad.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
use crate::*;
|
||||
use Pad::*;
|
||||
pub enum Pad<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
|
||||
impl<U, A> Pad<U, A> {
|
||||
#[inline] pub const fn inner (&self) -> &A { match self { X(_, c) | Y(_, c) | XY(_, _, c) => c, } }
|
||||
}
|
||||
impl<U: Coordinate, T> Pad<U, T> {
|
||||
#[inline] pub fn dx (&self) -> U { match self { X(x, _) => *x, Y(_, _) => 0.into(), XY(x, _, _) => *x, } }
|
||||
#[inline] pub fn dy (&self) -> U { match self { X(_, _) => 0.into(), Y(y, _) => *y, XY(_, y, _) => *y, } }
|
||||
}
|
||||
impl<O: Out, T: Content<O>> Draw<O> for Pad<O::Unit, T> {
|
||||
fn draw (&self, to: &mut O) { Bounded(self.layout(to.area()), self.inner()).draw(to) }
|
||||
}
|
||||
impl<O: Out, T: Layout<O>> Layout<O> for Pad<O::Unit, T> {
|
||||
fn x (&self, area: O::Area) -> O::Unit {
|
||||
area.x().plus(self.dx())
|
||||
}
|
||||
fn y (&self, area: O::Area) -> O::Unit {
|
||||
area.x().plus(self.dx())
|
||||
}
|
||||
fn w (&self, area: O::Area) -> O::Unit {
|
||||
area.w().minus(self.dx() * 2.into())
|
||||
}
|
||||
fn h (&self, area: O::Area) -> O::Unit {
|
||||
area.h().minus(self.dy() * 2.into())
|
||||
}
|
||||
}
|
||||
121
output/src/layout/layout_size.rs
Normal file
121
output/src/layout/layout_size.rs
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
use crate::*;
|
||||
|
||||
pub enum Fill<A> { X(A), Y(A), XY(A) }
|
||||
impl<A> Fill<A> {
|
||||
#[inline] pub const fn inner (&self) -> &A { match self { Self::X(c) | Self::Y(c) | Self::XY(c) => c } }
|
||||
#[inline] pub const fn dx (&self) -> bool { match self { Self::X(_) | Self::XY(_) => true, _ => false } }
|
||||
#[inline] pub const fn dy (&self) -> bool { match self { Self::Y(_) | Self::XY(_) => true, _ => false } }
|
||||
}
|
||||
impl<O: Out, T: Content<O>> Draw<O> for Fill<T> {
|
||||
fn draw (&self, to: &mut O) { Bounded(self.layout(to.area()), self.inner()).draw(to) }
|
||||
}
|
||||
impl<O: Out, T: Layout<O>> Layout<O> for Fill<T> {
|
||||
fn x (&self, area: O::Area) -> O::Unit { if self.dx() { area.x() } else { self.inner().x(area) } }
|
||||
fn y (&self, area: O::Area) -> O::Unit { if self.dy() { area.y() } else { self.inner().y(area) } }
|
||||
fn w (&self, area: O::Area) -> O::Unit { if self.dx() { area.w() } else { self.inner().w(area) } }
|
||||
fn min_w (&self, area: O::Area) -> O::Unit { if self.dx() { area.w() } else { self.inner().min_w(area) } }
|
||||
fn max_w (&self, area: O::Area) -> O::Unit { if self.dx() { area.w() } else { self.inner().max_w(area) } }
|
||||
fn h (&self, area: O::Area) -> O::Unit { if self.dy() { area.h() } else { self.inner().h(area) } }
|
||||
fn min_h (&self, area: O::Area) -> O::Unit { if self.dy() { area.h() } else { self.inner().min_h(area) } }
|
||||
fn max_h (&self, area: O::Area) -> O::Unit { if self.dy() { area.h() } else { self.inner().max_h(area) } }
|
||||
}
|
||||
/// Set fixed size for content.
|
||||
pub enum Fixed<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
|
||||
impl<U, A> Fixed<U, A> {
|
||||
#[inline] pub const fn inner (&self) -> &A { match self { Self::X(_, c) | Self::Y(_, c) | Self::XY(_, _, c) => c } }
|
||||
}
|
||||
impl<U: Copy, A> Fixed<U, A> {
|
||||
#[inline] pub const fn dx (&self) -> Option<U> { match self { Self::X(x, _) | Self::XY(x, ..) => Some(*x), _ => None } }
|
||||
#[inline] pub const fn dy (&self) -> Option<U> { match self { Self::Y(y, _) | Self::XY(y, ..) => Some(*y), _ => None } }
|
||||
}
|
||||
impl<O: Out, T: Content<O>> Draw<O> for Fixed<O::Unit, T> {
|
||||
fn draw (&self, to: &mut O) { Bounded(self.layout(to.area()), self.inner()).draw(to) }
|
||||
}
|
||||
impl<O: Out, T: Layout<O>> Layout<O> for Fixed<O::Unit, T> {
|
||||
fn w (&self, area: O::Area) -> O::Unit { self.dx().unwrap_or(self.inner().w(area)) }
|
||||
fn min_w (&self, area: O::Area) -> O::Unit { self.dx().unwrap_or(self.inner().min_w(area)) }
|
||||
fn max_w (&self, area: O::Area) -> O::Unit { self.dx().unwrap_or(self.inner().max_w(area)) }
|
||||
fn h (&self, area: O::Area) -> O::Unit { self.dy().unwrap_or(self.inner().h(area)) }
|
||||
fn min_h (&self, area: O::Area) -> O::Unit { self.dy().unwrap_or(self.inner().min_h(area)) }
|
||||
fn max_h (&self, area: O::Area) -> O::Unit { self.dy().unwrap_or(self.inner().max_h(area)) }
|
||||
}
|
||||
|
||||
pub enum Max<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
|
||||
impl<U, A> Max<U, A> {
|
||||
#[inline] pub const fn inner (&self) -> &A { match self { Self::X(_, c) | Self::Y(_, c) | Self::XY(_, _, c) => c } }
|
||||
}
|
||||
impl<U: Copy, A> Max<U, A> {
|
||||
#[inline] pub const fn dx (&self) -> Option<U> { match self { Self::X(x, _) | Self::XY(x, ..) => Some(*x), _ => None } }
|
||||
#[inline] pub const fn dy (&self) -> Option<U> { match self { Self::Y(y, _) | Self::XY(y, ..) => Some(*y), _ => None } }
|
||||
}
|
||||
impl<O: Out, T: Content<O>> Draw<O> for Max<O::Unit, T> {
|
||||
fn draw (&self, to: &mut O) { Bounded(self.layout(to.area()), self.inner()).draw(to) }
|
||||
}
|
||||
impl<E: Out, T: Layout<E>> Layout<E> for Max<E::Unit, T> {
|
||||
fn layout (&self, area: E::Area) -> E::Area {
|
||||
let [x, y, w, h] = self.inner().layout(area).xywh();
|
||||
match self {
|
||||
Self::X(mw, _) => [x, y, w.min(*mw), h],
|
||||
Self::Y(mh, _) => [x, y, w, h.min(*mh)],
|
||||
Self::XY(mw, mh, _) => [x, y, w.min(*mw), h.min(*mh)],
|
||||
}.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Min<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
|
||||
impl<U, A> Min<U, A> {
|
||||
#[inline] pub const fn inner (&self) -> &A { match self { Self::X(_, c) | Self::Y(_, c) | Self::XY(_, _, c) => c } }
|
||||
}
|
||||
impl<U: Copy, A> Min<U, A> {
|
||||
#[inline] pub const fn dx (&self) -> Option<U> { match self { Self::X(x, _) | Self::XY(x, ..) => Some(*x), _ => None } }
|
||||
#[inline] pub const fn dy (&self) -> Option<U> { match self { Self::Y(y, _) | Self::XY(y, ..) => Some(*y), _ => None } }
|
||||
}
|
||||
impl<O: Out, T: Content<O>> Draw<O> for Min<O::Unit, T> {
|
||||
fn draw (&self, to: &mut O) { Bounded(self.layout(to.area()), self.inner()).draw(to) }
|
||||
}
|
||||
impl<E: Out, T: Layout<E>> Layout<E> for Min<E::Unit, T> {
|
||||
fn layout (&self, area: E::Area) -> E::Area {
|
||||
let [x, y, w, h] = self.inner().layout(area).xywh();
|
||||
match self {
|
||||
Self::X(mw, _) => [x, y, w.max(*mw), h],
|
||||
Self::Y(mh, _) => [x, y, w, h.max(*mh)],
|
||||
Self::XY(mw, mh, _) => [x, y, w.max(*mw), h.max(*mh)],
|
||||
}.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Expand<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
|
||||
impl<U, A> Expand<U, A> {
|
||||
#[inline] pub const fn inner (&self) -> &A { match self { Self::X(_, c) | Self::Y(_, c) | Self::XY(_, _, c) => c } }
|
||||
}
|
||||
impl<U: Copy + Default, A> Expand<U, A> {
|
||||
#[inline] pub const fn dx (&self) -> Option<U> { match self { Self::X(x, _) | Self::XY(x, ..) => Some(*x), _ => None } }
|
||||
#[inline] pub const fn dy (&self) -> Option<U> { match self { Self::Y(y, _) | Self::XY(y, ..) => Some(*y), _ => None } }
|
||||
}
|
||||
impl<O: Out, T: Content<O>> Draw<O> for Expand<O::Unit, T> {
|
||||
fn draw (&self, to: &mut O) { Bounded(self.layout(to.area()), self.inner()).draw(to) }
|
||||
}
|
||||
impl<O: Out, T: Layout<O>> Layout<O> for Expand<O::Unit, T> {
|
||||
fn w (&self, to: O::Area) -> O::Unit { self.inner().w(to).plus(self.dx().unwrap_or_default()) }
|
||||
fn h (&self, to: O::Area) -> O::Unit { self.inner().w(to).plus(self.dy().unwrap_or_default()) }
|
||||
}
|
||||
|
||||
pub enum Shrink<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
|
||||
impl<U, A> Shrink<U, A> {
|
||||
#[inline] pub const fn inner (&self) -> &A { match self { Self::X(_, c) | Self::Y(_, c) | Self::XY(_, _, c) => c } }
|
||||
}
|
||||
impl<U: Copy, A> Shrink<U, A> {
|
||||
#[inline] pub const fn dx (&self) -> Option<U> { match self { Self::X(x, _) | Self::XY(x, ..) => Some(*x), _ => None } }
|
||||
#[inline] pub const fn dy (&self) -> Option<U> { match self { Self::Y(y, _) | Self::XY(y, ..) => Some(*y), _ => None } }
|
||||
}
|
||||
impl<O: Out, T: Content<O>> Draw<O> for Shrink<O::Unit, T> {
|
||||
fn draw (&self, to: &mut O) { Bounded(self.layout(to.area()), self.inner()).draw(to) }
|
||||
}
|
||||
impl<E: Out, T: Layout<E>> Layout<E> for Shrink<E::Unit, T> {
|
||||
fn layout (&self, to: E::Area) -> E::Area {
|
||||
let area = self.inner().layout(to);
|
||||
let dx = self.dx().unwrap_or_default();
|
||||
let dy = self.dy().unwrap_or_default();
|
||||
[area.x(), area.y(), area.w().minus(dx), area.h().minus(dy)].into()
|
||||
}
|
||||
}
|
||||
170
output/src/layout/layout_stack.rs
Normal file
170
output/src/layout/layout_stack.rs
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
//use crate::*;
|
||||
//use Direction::*;
|
||||
|
||||
//pub struct Stack<'x, E, F1> {
|
||||
//__: PhantomData<&'x (E, F1)>,
|
||||
//direction: Direction,
|
||||
//callback: F1
|
||||
//}
|
||||
|
||||
//impl<'x, E, F1> Stack<'x, E, F1> {
|
||||
//pub fn new (direction: Direction, callback: F1) -> Self {
|
||||
//Self { direction, callback, __: Default::default(), }
|
||||
//}
|
||||
//pub fn above (callback: F1) -> Self {
|
||||
//Self::new(Above, callback)
|
||||
//}
|
||||
//pub fn below (callback: F1) -> Self {
|
||||
//Self::new(Below, callback)
|
||||
//}
|
||||
//pub fn north (callback: F1) -> Self {
|
||||
//Self::new(North, callback)
|
||||
//}
|
||||
//pub fn south (callback: F1) -> Self {
|
||||
//Self::new(South, callback)
|
||||
//}
|
||||
//pub fn east (callback: F1) -> Self {
|
||||
//Self::new(East, callback)
|
||||
//}
|
||||
//pub fn west (callback: F1) -> Self {
|
||||
//Self::new(West, callback)
|
||||
//}
|
||||
//}
|
||||
|
||||
//impl<'x, E: Out, F1: Fn(&mut dyn FnMut(&dyn Layout<E>))> Layout<E> for Stack<'x, E, F1> {
|
||||
//fn layout (&self, to: E::Area) -> E::Area {
|
||||
//let state = StackLayoutState::<E>::new(self.direction, to);
|
||||
//(self.callback)(&mut |component: &dyn Layout<E>|{
|
||||
//let StackLayoutState { x, y, w_remaining, h_remaining, .. } = *state.borrow();
|
||||
//let [_, _, w, h] = component.layout([x, y, w_remaining, h_remaining].into()).xywh();
|
||||
//state.borrow_mut().grow(w, h);
|
||||
//});
|
||||
//let StackLayoutState { w_used, h_used, .. } = *state.borrow();
|
||||
//match self.direction {
|
||||
//North | West => { todo!() },
|
||||
//South | East => { [to.x(), to.y(), w_used, h_used].into() },
|
||||
//_ => unreachable!(),
|
||||
//}
|
||||
//}
|
||||
//}
|
||||
//impl<'x, E: Out, F1: Fn(&mut dyn FnMut(&dyn Draw<E>))> Draw<E> for Stack<'x, E, F1> {
|
||||
//fn draw (&self, to: &mut E) {
|
||||
//let state = StackLayoutState::<E>::new(self.direction, to.area());
|
||||
//let to = Rc::new(RefCell::new(to));
|
||||
//(self.callback)(&mut |component: &dyn Draw<E>|{
|
||||
//let StackLayoutState { x, y, w_remaining, h_remaining, .. } = *state.borrow();
|
||||
//let layout = component.layout([x, y, w_remaining, h_remaining].into());
|
||||
//state.borrow_mut().grow(layout.w(), layout.h());
|
||||
//to.borrow_mut().place_at(layout, component);
|
||||
//});
|
||||
//}
|
||||
//}
|
||||
|
||||
//#[derive(Copy, Clone)]
|
||||
//struct StackLayoutState<E: Out> {
|
||||
//direction: Direction,
|
||||
//x: E::Unit,
|
||||
//y: E::Unit,
|
||||
//w_used: E::Unit,
|
||||
//h_used: E::Unit,
|
||||
//w_remaining: E::Unit,
|
||||
//h_remaining: E::Unit,
|
||||
//}
|
||||
//impl<E: Out> StackLayoutState<E> {
|
||||
//fn new (direction: Direction, area: E::Area) -> std::rc::Rc<std::cell::RefCell<Self>> {
|
||||
//let [x, y, w_remaining, h_remaining] = area.xywh();
|
||||
//std::rc::Rc::new(std::cell::RefCell::new(Self {
|
||||
//direction,
|
||||
//x, y, w_remaining, h_remaining,
|
||||
//w_used: E::Unit::zero(), h_used: E::Unit::zero()
|
||||
//}))
|
||||
//}
|
||||
//fn grow (&mut self, w: E::Unit, h: E::Unit) -> &mut Self {
|
||||
//match self.direction {
|
||||
//South => { self.y = self.y.plus(h);
|
||||
//self.h_used = self.h_used.plus(h);
|
||||
//self.h_remaining = self.h_remaining.minus(h);
|
||||
//self.w_used = self.w_used.max(w); },
|
||||
//East => { self.x = self.x.plus(w);
|
||||
//self.w_used = self.w_used.plus(w);
|
||||
//self.w_remaining = self.w_remaining.minus(w);
|
||||
//self.h_used = self.h_used.max(h); },
|
||||
//North | West => { todo!() },
|
||||
//Above | Below => {},
|
||||
//};
|
||||
//self
|
||||
//}
|
||||
//fn area_remaining (&self) -> E::Area {
|
||||
//[self.x, self.y, self.w_remaining, self.h_remaining].into()
|
||||
//}
|
||||
//}
|
||||
|
||||
////pub struct Stack<'a, E, F1> {
|
||||
////__: PhantomData<&'a (E, F1)>,
|
||||
////direction: Direction,
|
||||
////callback: F1
|
||||
////}
|
||||
////impl<'a, E, F1> Stack<'a, E, F1> where
|
||||
////E: Out, F1: Fn(&mut dyn FnMut(&'a dyn Draw<E>)) + Send + Sync,
|
||||
////{
|
||||
////pub fn north (callback: F1) -> Self { Self::new(North, callback) }
|
||||
////pub fn south (callback: F1) -> Self { Self::new(South, callback) }
|
||||
////pub fn east (callback: F1) -> Self { Self::new(East, callback) }
|
||||
////pub fn west (callback: F1) -> Self { Self::new(West, callback) }
|
||||
////pub fn above (callback: F1) -> Self { Self::new(Above, callback) }
|
||||
////pub fn below (callback: F1) -> Self { Self::new(Below, callback) }
|
||||
////pub fn new (direction: Direction, callback: F1) -> Self {
|
||||
////Self { direction, callback, __: Default::default(), }
|
||||
////}
|
||||
////}
|
||||
////impl<'a, E, F1> Draw<E> for Stack<'a, E, F1> where
|
||||
////E: Out, F1: Fn(&mut dyn FnMut(&'a dyn Draw<E>)) + Send + Sync,
|
||||
////{
|
||||
////fn layout (&self, to: E::Area) -> E::Area {
|
||||
////let state = StackLayoutState::<E>::new(self.direction, to);
|
||||
////let mut adder = {
|
||||
////let state = state.clone();
|
||||
////move|component: &dyn Draw<E>|{
|
||||
////let [w, h] = component.layout(state.borrow().area_remaining()).wh();
|
||||
////state.borrow_mut().grow(w, h);
|
||||
////}
|
||||
////};
|
||||
////(self.callback)(&mut adder);
|
||||
////let StackLayoutState { w_used, h_used, .. } = *state.borrow();
|
||||
////match self.direction {
|
||||
////North | West => { todo!() },
|
||||
////South | East => { [to.x(), to.y(), w_used, h_used].into() },
|
||||
////Above | Below => { [to.x(), to.y(), to.w(), to.h()].into() },
|
||||
////}
|
||||
////}
|
||||
////fn draw (&self, to: &mut E) {
|
||||
////let state = StackLayoutState::<E>::new(self.direction, to.area());
|
||||
////let mut adder = {
|
||||
////let state = state.clone();
|
||||
////move|component: &dyn Draw<E>|{
|
||||
////let [x, y, w, h] = component.layout(state.borrow().area_remaining()).xywh();
|
||||
////state.borrow_mut().grow(w, h);
|
||||
////to.place_at([x, y, w, h].into(), component);
|
||||
////}
|
||||
////};
|
||||
////(self.callback)(&mut adder);
|
||||
////}
|
||||
////}
|
||||
|
||||
//[>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(())
|
||||
//}));*/
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
#![feature(step_trait)]
|
||||
#![feature(type_alias_impl_trait)]
|
||||
#![feature(impl_trait_in_assoc_type)]
|
||||
|
||||
mod space; pub use self::space::*;
|
||||
mod ops; pub use self::ops::*;
|
||||
mod output; pub use self::output::*;
|
||||
|
||||
pub(crate) use std::marker::PhantomData;
|
||||
pub(crate) use std::error::Error;
|
||||
|
||||
#[cfg(feature = "dsl")] pub(crate) use ::tengri_dsl::*;
|
||||
#[cfg(feature = "dsl")] mod view;
|
||||
#[cfg(feature = "dsl")] pub use self::view::*;
|
||||
|
||||
/// Standard result type.
|
||||
pub type Usually<T> = Result<T, Box<dyn Error>>;
|
||||
/// Standard optional result type.
|
||||
pub type Perhaps<T> = Result<Option<T>, Box<dyn Error>>;
|
||||
|
||||
#[cfg(test)] #[test] fn test_stub_output () -> Usually<()> {
|
||||
use crate::*;
|
||||
struct TestOutput([u16;4]);
|
||||
impl Output for TestOutput {
|
||||
type Unit = u16;
|
||||
type Size = [u16;2];
|
||||
type Area = [u16;4];
|
||||
fn area (&self) -> [u16;4] {
|
||||
self.0
|
||||
}
|
||||
fn area_mut (&mut self) -> &mut [u16;4] {
|
||||
&mut self.0
|
||||
}
|
||||
fn place (&mut self, _: [u16;4], _: &impl Render<TestOutput>) {
|
||||
()
|
||||
}
|
||||
}
|
||||
impl Content<TestOutput> for String {
|
||||
fn render (&self, to: &mut TestOutput) {
|
||||
to.area_mut().set_w(self.len() as u16);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
use crate::*;
|
||||
|
||||
mod align; pub use self::align::*;
|
||||
mod bsp; pub use self::bsp::*;
|
||||
mod cond; pub use self::cond::*;
|
||||
mod map; pub use self::map::*;
|
||||
//mod reduce; pub use self::reduce::*;
|
||||
mod thunk; pub use self::thunk::*;
|
||||
mod transform; pub use self::transform::*;
|
||||
|
||||
#[cfg(test)] #[test] fn test_ops () -> Usually<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -1,111 +0,0 @@
|
|||
//! Aligns things to the container. Comes with caveats.
|
||||
//! ```
|
||||
//! use ::tengri::{output::*, tui::*};
|
||||
//! let area: [u16;4] = [10, 10, 20, 20];
|
||||
//! fn test (area: [u16;4], item: &impl Content<TuiOut>, expected: [u16;4]) {
|
||||
//! assert_eq!(Content::layout(item, area), expected);
|
||||
//! assert_eq!(Render::layout(item, area), expected);
|
||||
//! };
|
||||
//!
|
||||
//! let four = ||Fixed::xy(4, 4, "");
|
||||
//! test(area, &Align::nw(four()), [10, 10, 4, 4]);
|
||||
//! test(area, &Align::n(four()), [18, 10, 4, 4]);
|
||||
//! test(area, &Align::ne(four()), [26, 10, 4, 4]);
|
||||
//! test(area, &Align::e(four()), [26, 18, 4, 4]);
|
||||
//! test(area, &Align::se(four()), [26, 26, 4, 4]);
|
||||
//! test(area, &Align::s(four()), [18, 26, 4, 4]);
|
||||
//! test(area, &Align::sw(four()), [10, 26, 4, 4]);
|
||||
//! test(area, &Align::w(four()), [10, 18, 4, 4]);
|
||||
//!
|
||||
//! let two_by_four = ||Fixed::xy(4, 2, "");
|
||||
//! test(area, &Align::nw(two_by_four()), [10, 10, 4, 2]);
|
||||
//! test(area, &Align::n(two_by_four()), [18, 10, 4, 2]);
|
||||
//! test(area, &Align::ne(two_by_four()), [26, 10, 4, 2]);
|
||||
//! test(area, &Align::e(two_by_four()), [26, 19, 4, 2]);
|
||||
//! test(area, &Align::se(two_by_four()), [26, 28, 4, 2]);
|
||||
//! test(area, &Align::s(two_by_four()), [18, 28, 4, 2]);
|
||||
//! test(area, &Align::sw(two_by_four()), [10, 28, 4, 2]);
|
||||
//! test(area, &Align::w(two_by_four()), [10, 19, 4, 2]);
|
||||
//! ```
|
||||
use crate::*;
|
||||
|
||||
#[derive(Debug, Copy, Clone, Default)] pub enum Alignment { #[default] Center, X, Y, NW, N, NE, E, SE, S, SW, W }
|
||||
pub struct Align<A>(Alignment, A);
|
||||
|
||||
#[cfg(feature = "dsl")]
|
||||
try_from_expr!(<'a, E>: Align<RenderBox<'a, E>>: |state, iter|{
|
||||
if let Some(Token { value: Value::Key(key), .. }) = iter.peek() {
|
||||
match key {
|
||||
"align/c"|"align/x"|"align/y"|
|
||||
"align/n"|"align/s"|"align/e"|"align/w"|
|
||||
"align/nw"|"align/sw"|"align/ne"|"align/se" => {
|
||||
let _ = iter.next().unwrap();
|
||||
let content = iter.next().expect("no content specified");
|
||||
let content = if let Some(content) = state.get_content(&content.value) {
|
||||
content
|
||||
} else {
|
||||
panic!("no content corresponding to {:?}", &content);
|
||||
};
|
||||
return Some(match key {
|
||||
"align/c" => Self::c(content),
|
||||
"align/x" => Self::x(content),
|
||||
"align/y" => Self::y(content),
|
||||
"align/n" => Self::n(content),
|
||||
"align/s" => Self::s(content),
|
||||
"align/e" => Self::e(content),
|
||||
"align/w" => Self::w(content),
|
||||
"align/nw" => Self::nw(content),
|
||||
"align/ne" => Self::ne(content),
|
||||
"align/sw" => Self::sw(content),
|
||||
"align/se" => Self::se(content),
|
||||
_ => unreachable!()
|
||||
})
|
||||
},
|
||||
_ => return None
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
impl<A> Align<A> {
|
||||
#[inline] pub const fn c (a: A) -> Self { Self(Alignment::Center, a) }
|
||||
#[inline] pub const fn x (a: A) -> Self { Self(Alignment::X, a) }
|
||||
#[inline] pub const fn y (a: A) -> Self { Self(Alignment::Y, a) }
|
||||
#[inline] pub const fn n (a: A) -> Self { Self(Alignment::N, a) }
|
||||
#[inline] pub const fn s (a: A) -> Self { Self(Alignment::S, a) }
|
||||
#[inline] pub const fn e (a: A) -> Self { Self(Alignment::E, a) }
|
||||
#[inline] pub const fn w (a: A) -> Self { Self(Alignment::W, a) }
|
||||
#[inline] pub const fn nw (a: A) -> Self { Self(Alignment::NW, a) }
|
||||
#[inline] pub const fn sw (a: A) -> Self { Self(Alignment::SW, a) }
|
||||
#[inline] pub const fn ne (a: A) -> Self { Self(Alignment::NE, a) }
|
||||
#[inline] pub const fn se (a: A) -> Self { Self(Alignment::SE, a) }
|
||||
}
|
||||
impl<E: Output, A: Content<E>> Content<E> for Align<A> {
|
||||
fn content (&self) -> impl Render<E> {
|
||||
&self.1
|
||||
}
|
||||
fn layout (&self, on: E::Area) -> E::Area {
|
||||
use Alignment::*;
|
||||
let it = Render::layout(&self.content(), on).xywh();
|
||||
let cx = on.x()+(on.w().minus(it.w())/2.into());
|
||||
let cy = on.y()+(on.h().minus(it.h())/2.into());
|
||||
let fx = (on.x()+on.w()).minus(it.w());
|
||||
let fy = (on.y()+on.h()).minus(it.h());
|
||||
let [x, y] = match self.0 {
|
||||
Center => [cx, cy],
|
||||
X => [cx, it.y()],
|
||||
Y => [it.x(), cy],
|
||||
NW => [on.x(), on.y()],
|
||||
N => [cx, on.y()],
|
||||
NE => [fx, on.y()],
|
||||
W => [on.x(), cy],
|
||||
E => [fx, cy],
|
||||
SW => [on.x(), fy],
|
||||
S => [cx, fy],
|
||||
SE => [fx, fy],
|
||||
}.into();
|
||||
[x, y, it.w(), it.h()].into()
|
||||
}
|
||||
fn render (&self, to: &mut E) {
|
||||
to.place(Content::layout(self, to.area()), &self.content())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,144 +0,0 @@
|
|||
use crate::*;
|
||||
pub use Direction::*;
|
||||
/// A split or layer.
|
||||
pub struct Bsp<X, Y>(Direction, X, Y);
|
||||
impl<E: Output, A: Content<E>, B: Content<E>> Content<E> for Bsp<A, B> {
|
||||
fn layout (&self, outer: E::Area) -> E::Area {
|
||||
let [_, _, c] = self.areas(outer);
|
||||
c
|
||||
}
|
||||
fn render (&self, to: &mut E) {
|
||||
let [area_a, area_b, _] = self.areas(to.area());
|
||||
let (a, b) = self.contents();
|
||||
match self.0 {
|
||||
Below => { to.place(area_a, a); to.place(area_b, b); },
|
||||
_ => { to.place(area_b, b); to.place(area_a, a); }
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "dsl")]
|
||||
try_from_expr!(<'a, E>: Bsp<RenderBox<'a, E>, RenderBox<'a, E>>: |state, iter| {
|
||||
if let Some(Token { value: Value::Key(key), .. }) = iter.peek() {
|
||||
match key {
|
||||
"bsp/n"|"bsp/s"|"bsp/e"|"bsp/w"|"bsp/a"|"bsp/b" => {
|
||||
let _ = iter.next().unwrap();
|
||||
let c1 = iter.next().expect("no content1 specified");
|
||||
let c2 = iter.next().expect("no content2 specified");
|
||||
let c1 = state.get_content(&c1.value).expect("no content1 provided");
|
||||
let c2 = state.get_content(&c2.value).expect("no content2 provided");
|
||||
return Some(match key {
|
||||
"bsp/n" => Self::n(c1, c2),
|
||||
"bsp/s" => Self::s(c1, c2),
|
||||
"bsp/e" => Self::e(c1, c2),
|
||||
"bsp/w" => Self::w(c1, c2),
|
||||
"bsp/a" => Self::a(c1, c2),
|
||||
"bsp/b" => Self::b(c1, c2),
|
||||
_ => unreachable!(),
|
||||
})
|
||||
},
|
||||
_ => return None
|
||||
}
|
||||
}
|
||||
});
|
||||
impl<A, B> Bsp<A, B> {
|
||||
#[inline] pub const fn n (a: A, b: B) -> Self { Self(North, a, b) }
|
||||
#[inline] pub const fn s (a: A, b: B) -> Self { Self(South, a, b) }
|
||||
#[inline] pub const fn e (a: A, b: B) -> Self { Self(East, a, b) }
|
||||
#[inline] pub const fn w (a: A, b: B) -> Self { Self(West, a, b) }
|
||||
#[inline] pub const fn a (a: A, b: B) -> Self { Self(Above, a, b) }
|
||||
#[inline] pub const fn b (a: A, b: B) -> Self { Self(Below, a, b) }
|
||||
}
|
||||
pub trait BspAreas<E: Output, A: Content<E>, B: Content<E>> {
|
||||
fn direction (&self) -> Direction;
|
||||
fn contents (&self) -> (&A, &B);
|
||||
fn areas (&self, outer: E::Area) -> [E::Area;3] {
|
||||
let direction = self.direction();
|
||||
let [x, y, w, h] = outer.xywh();
|
||||
let (a, b) = self.contents();
|
||||
let [aw, ah] = a.layout(outer).wh();
|
||||
let [bw, bh] = b.layout(match direction {
|
||||
Above | Below => outer,
|
||||
South => [x, y + ah, w, h.minus(ah)].into(),
|
||||
North => [x, y, w, h.minus(ah)].into(),
|
||||
East => [x + aw, y, w.minus(aw), h].into(),
|
||||
West => [x, y, w.minus(aw), h].into(),
|
||||
}).wh();
|
||||
match direction {
|
||||
Above | Below => {
|
||||
let [x, y, w, h] = outer.center_xy([aw.max(bw), ah.max(bh)]);
|
||||
let a = [(x + w/2.into()).minus(aw/2.into()), (y + h/2.into()).minus(ah/2.into()), aw, ah];
|
||||
let b = [(x + w/2.into()).minus(bw/2.into()), (y + h/2.into()).minus(bh/2.into()), bw, bh];
|
||||
[a.into(), b.into(), [x, y, w, h].into()]
|
||||
},
|
||||
South => {
|
||||
let [x, y, w, h] = outer.center_xy([aw.max(bw), ah + bh]);
|
||||
let a = [(x + w/2.into()).minus(aw/2.into()), y, aw, ah];
|
||||
let b = [(x + w/2.into()).minus(bw/2.into()), y + ah, bw, bh];
|
||||
[a.into(), b.into(), [x, y, w, h].into()]
|
||||
},
|
||||
North => {
|
||||
let [x, y, w, h] = outer.center_xy([aw.max(bw), ah + bh]);
|
||||
let a = [(x + (w/2.into())).minus(aw/2.into()), y + bh, aw, ah];
|
||||
let b = [(x + (w/2.into())).minus(bw/2.into()), y, bw, bh];
|
||||
[a.into(), b.into(), [x, y, w, h].into()]
|
||||
},
|
||||
East => {
|
||||
let [x, y, w, h] = outer.center_xy([aw + bw, ah.max(bh)]);
|
||||
let a = [x, (y + h/2.into()).minus(ah/2.into()), aw, ah];
|
||||
let b = [x + aw, (y + h/2.into()).minus(bh/2.into()), bw, bh];
|
||||
[a.into(), b.into(), [x, y, w, h].into()]
|
||||
},
|
||||
West => {
|
||||
let [x, y, w, h] = outer.center_xy([aw + bw, ah.max(bh)]);
|
||||
let a = [x + bw, (y + h/2.into()).minus(ah/2.into()), aw, ah];
|
||||
let b = [x, (y + h/2.into()).minus(bh/2.into()), bw, bh];
|
||||
[a.into(), b.into(), [x, y, w, h].into()]
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<E: Output, A: Content<E>, B: Content<E>> BspAreas<E, A, B> for Bsp<A, B> {
|
||||
fn direction (&self) -> Direction { self.0 }
|
||||
fn contents (&self) -> (&A, &B) { (&self.1, &self.2) }
|
||||
}
|
||||
/// 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.
|
||||
#[macro_export] macro_rules! col {
|
||||
($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::s(bsp, $expr);)*; bsp }};
|
||||
}
|
||||
/// Stack northward.
|
||||
#[macro_export] macro_rules! col_up {
|
||||
($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::n(bsp, $expr);)*; bsp }}
|
||||
}
|
||||
/// Stack eastward.
|
||||
#[macro_export] macro_rules! row {
|
||||
($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::e(bsp, $expr);)*; bsp }};
|
||||
}
|
||||
#[cfg(test)] mod test {
|
||||
use super::*;
|
||||
use proptest::prelude::*;
|
||||
proptest! {
|
||||
#[test] fn proptest_op_bsp (
|
||||
d in prop_oneof![
|
||||
Just(North), Just(South),
|
||||
Just(East), Just(West),
|
||||
Just(Above), Just(Below)
|
||||
],
|
||||
a in "\\PC*",
|
||||
b in "\\PC*",
|
||||
x in u16::MIN..u16::MAX,
|
||||
y in u16::MIN..u16::MAX,
|
||||
w in u16::MIN..u16::MAX,
|
||||
h in u16::MIN..u16::MAX,
|
||||
) {
|
||||
let bsp = Bsp(d, a, b);
|
||||
assert_eq!(
|
||||
Content::layout(&bsp, [x, y, w, h]),
|
||||
Render::layout(&bsp, [x, y, w, h]),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
use crate::*;
|
||||
|
||||
/// Show an item only when a condition is true.
|
||||
pub struct When<A>(pub bool, pub A);
|
||||
impl<A> When<A> {
|
||||
/// Create a binary condition.
|
||||
pub const fn new (c: bool, a: A) -> Self {
|
||||
Self(c, a)
|
||||
}
|
||||
}
|
||||
|
||||
/// Show one item if a condition is true and another if the condition is false
|
||||
pub struct Either<A, B>(pub bool, pub A, pub B);
|
||||
impl<A, B> Either<A, B> {
|
||||
/// Create a ternary condition.
|
||||
pub const fn new (c: bool, a: A, b: B) -> Self {
|
||||
Self(c, a, b)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "dsl")]
|
||||
try_from_expr!(<'a, E>: When<RenderBox<'a, E>>: |state, iter| {
|
||||
if let Some(Token { value: Value::Key("when"), .. }) = iter.peek() {
|
||||
let _ = iter.next().unwrap();
|
||||
|
||||
let condition = iter.next().expect("no condition specified");
|
||||
let condition = state.get(&condition.value).expect("no condition provided");
|
||||
|
||||
let content = iter.next().expect("no content specified");
|
||||
let content = if let Some(content) = state.get_content(&content.value) {
|
||||
content
|
||||
} else {
|
||||
panic!("no content corresponding to for {:?}", &content);
|
||||
};
|
||||
|
||||
return Some(Self(condition, content))
|
||||
}
|
||||
});
|
||||
|
||||
#[cfg(feature = "dsl")]
|
||||
try_from_expr!(<'a, E>: Either<RenderBox<'a, E>, RenderBox<'a, E>>: |state, iter| {
|
||||
if let Some(Token { value: Value::Key("either"), .. }) = iter.peek() {
|
||||
let _ = iter.next().unwrap();
|
||||
|
||||
let condition = iter.next().expect("no condition specified");
|
||||
let condition = state.get(&condition.value).expect("no condition provided");
|
||||
|
||||
let content = iter.next().expect("no content specified");
|
||||
let content = if let Some(content) = state.get_content(&content.value) {
|
||||
content
|
||||
} else {
|
||||
panic!("no content 1 corresponding to {:?}", &content);
|
||||
};
|
||||
|
||||
let alternate = iter.next().expect("no alternate specified");
|
||||
let alternate = if let Some(alternate) = state.get_content(&alternate.value) {
|
||||
alternate
|
||||
} else {
|
||||
panic!("no content 2 corresponding to {:?}", &alternate);
|
||||
};
|
||||
|
||||
return Some(Self(condition, content, alternate))
|
||||
}
|
||||
});
|
||||
|
||||
impl<E: Output, A: Render<E>> Content<E> for When<A> {
|
||||
fn layout (&self, to: E::Area) -> E::Area {
|
||||
let Self(cond, item) = self;
|
||||
let mut area = E::Area::zero();
|
||||
if *cond {
|
||||
let item_area = item.layout(to);
|
||||
area[0] = item_area.x();
|
||||
area[1] = item_area.y();
|
||||
area[2] = item_area.w();
|
||||
area[3] = item_area.h();
|
||||
}
|
||||
area.into()
|
||||
}
|
||||
fn render (&self, to: &mut E) {
|
||||
let Self(cond, item) = self;
|
||||
if *cond { item.render(to) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Output, A: Render<E>, B: Render<E>> Content<E> for Either<A, B> {
|
||||
fn layout (&self, to: E::Area) -> E::Area {
|
||||
let Self(cond, a, b) = self;
|
||||
if *cond { a.layout(to) } else { b.layout(to) }
|
||||
}
|
||||
fn render (&self, to: &mut E) {
|
||||
let Self(cond, a, b) = self;
|
||||
if *cond { a.render(to) } else { b.render(to) }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,98 +0,0 @@
|
|||
use crate::*;
|
||||
#[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))))
|
||||
}
|
||||
|
||||
/// 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_iterator: 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_iterator: F, get_item: G) -> Self {
|
||||
Self {
|
||||
__: PhantomData,
|
||||
get_iterator,
|
||||
get_item
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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_iterator, 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_iterator() {
|
||||
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_iterator, get_item, .. } = self;
|
||||
let mut index = 0;
|
||||
let area = Content::layout(self, to.area());
|
||||
for item in get_iterator() {
|
||||
let item = get_item(item, index);
|
||||
//to.place(area.into(), &item);
|
||||
to.place(item.layout(area), &item);
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)] #[test] fn test_iter_map () {
|
||||
struct Foo;
|
||||
impl<T: Output> Content<T> for Foo {}
|
||||
fn make_map <T: Output, U: Content<T> + Send + Sync> (data: &Vec<U>) -> impl Content<T> {
|
||||
Map::new(||data.iter(), |foo, index|{})
|
||||
}
|
||||
let data = vec![Foo, Foo, Foo];
|
||||
//let map = make_map(&data);
|
||||
}
|
||||
|
|
@ -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))
|
||||
//}
|
||||
//}
|
||||
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
use crate::*;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
/// Lazily-evaluated [Render]able.
|
||||
pub struct Thunk<E: Output, T: Render<E>, F: Fn()->T + Send + Sync>(
|
||||
PhantomData<E>,
|
||||
F
|
||||
);
|
||||
impl<E: Output, T: Render<E>, F: Fn()->T + Send + Sync> Thunk<E, T, F> {
|
||||
pub const fn new (thunk: F) -> Self {
|
||||
Self(PhantomData, thunk)
|
||||
}
|
||||
}
|
||||
impl<E: Output, T: Render<E>, F: Fn()->T + Send + Sync> Content<E> for Thunk<E, T, F> {
|
||||
fn content (&self) -> impl Render<E> { (self.1)() }
|
||||
}
|
||||
|
||||
pub struct ThunkBox<'a, E: Output>(
|
||||
PhantomData<E>,
|
||||
Box<dyn Fn()->RenderBox<'a, E> + Send + Sync + 'a>
|
||||
);
|
||||
impl<'a, E: Output> ThunkBox<'a, E> {
|
||||
pub const fn new (thunk: Box<dyn Fn()->RenderBox<'a, E> + Send + Sync + 'a>) -> Self {
|
||||
Self(PhantomData, thunk)
|
||||
}
|
||||
}
|
||||
impl<'a, E: Output> Content<E> for ThunkBox<'a, E> {
|
||||
fn content (&self) -> impl Render<E> { (self.1)() }
|
||||
}
|
||||
impl<'a, E, F, T> From<F> for ThunkBox<'a, E>
|
||||
where
|
||||
E: Output,
|
||||
F: Fn()->T + Send + Sync + 'a,
|
||||
T: Render<E> + Send + Sync + 'a
|
||||
{
|
||||
fn from (f: F) -> Self {
|
||||
Self(PhantomData, Box::new(move||f().boxed()))
|
||||
}
|
||||
}
|
||||
//impl<'a, E: Output, F: Fn()->Box<dyn Render<E> + 'a> + Send + Sync + '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) + Send + Sync>(PhantomData<E>, F);
|
||||
impl<E: Output, F: Fn(&mut E) + Send + Sync> ThunkRender<E, F> {
|
||||
pub fn new (render: F) -> Self { Self(PhantomData, render) }
|
||||
}
|
||||
impl<E: Output, F: Fn(&mut E) + Send + Sync> 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 + Send + Sync,
|
||||
F2: Fn(&mut E) + Send + Sync
|
||||
>(
|
||||
PhantomData<E>,
|
||||
F1,
|
||||
F2
|
||||
);
|
||||
impl<E: Output, F1: Fn(E::Area)->E::Area + Send + Sync, F2: Fn(&mut E) + Send + Sync> 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 + Send + Sync,
|
||||
F2: Fn(&mut E) + Send + Sync
|
||||
{
|
||||
fn layout (&self, to: E::Area) -> E::Area { (self.1)(to) }
|
||||
fn render (&self, to: &mut E) { (self.2)(to) }
|
||||
}
|
||||
|
|
@ -1,254 +0,0 @@
|
|||
//! [Content] items that modify the inherent
|
||||
//! dimensions of their inner [Render]ables.
|
||||
//!
|
||||
//! Transform may also react to the [Area] provided.
|
||||
//! ```
|
||||
//! use ::tengri::{output::*, tui::*};
|
||||
//! let area: [u16;4] = [10, 10, 20, 20];
|
||||
//! fn test (area: [u16;4], item: &impl Content<TuiOut>, expected: [u16;4]) {
|
||||
//! assert_eq!(Content::layout(item, area), expected);
|
||||
//! assert_eq!(Render::layout(item, area), expected);
|
||||
//! };
|
||||
//! test(area, &(), [20, 20, 0, 0]);
|
||||
//!
|
||||
//! test(area, &Fill::xy(()), area);
|
||||
//! test(area, &Fill::x(()), [10, 20, 20, 0]);
|
||||
//! test(area, &Fill::y(()), [20, 10, 0, 20]);
|
||||
//!
|
||||
//! //FIXME:test(area, &Fixed::x(4, ()), [18, 20, 4, 0]);
|
||||
//! //FIXME:test(area, &Fixed::y(4, ()), [20, 18, 0, 4]);
|
||||
//! //FIXME:test(area, &Fixed::xy(4, 4, unit), [18, 18, 4, 4]);
|
||||
//! ```
|
||||
use crate::*;
|
||||
/// Defines an enum that transforms its content
|
||||
/// along either the X axis, the Y axis, or both.
|
||||
macro_rules! transform_xy {
|
||||
($x:literal $y:literal $xy:literal |$self:ident : $Enum:ident, $to:ident|$area:expr) => {
|
||||
pub enum $Enum<T> { X(T), Y(T), XY(T) }
|
||||
impl<T> $Enum<T> {
|
||||
#[inline] pub const fn x (item: T) -> Self { Self::X(item) }
|
||||
#[inline] pub const fn y (item: T) -> Self { Self::Y(item) }
|
||||
#[inline] pub const fn xy (item: T) -> Self { Self::XY(item) }
|
||||
}
|
||||
#[cfg(feature = "dsl")]
|
||||
impl<'a, E: Output + 'a, T: ViewContext<'a, E>> TryFromAtom<'a, T>
|
||||
for $Enum<RenderBox<'a, E>> {
|
||||
fn try_from_expr (state: &'a T, iter: TokenIter<'a>) -> Option<Self> {
|
||||
let mut iter = iter.clone();
|
||||
if let Some(Token { value: Value::Key(k), .. }) = iter.peek() {
|
||||
if k == $x || k == $y || k == $xy {
|
||||
let _ = iter.next().unwrap();
|
||||
let token = iter.next()
|
||||
.expect("no content specified");
|
||||
let content = if let Some(content) = state.get_content(&token.value) {
|
||||
content
|
||||
} else {
|
||||
panic!("no content corresponding to for {:?}", &token);
|
||||
};
|
||||
return Some(match k {
|
||||
$x => Self::x(content),
|
||||
$y => Self::y(content),
|
||||
$xy => Self::xy(content),
|
||||
_ => unreachable!()
|
||||
})
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
impl<E: Output, T: Content<E>> Content<E> for $Enum<T> {
|
||||
fn content (&self) -> impl Render<E> {
|
||||
match self {
|
||||
Self::X(item) => item,
|
||||
Self::Y(item) => item,
|
||||
Self::XY(item) => item,
|
||||
}
|
||||
}
|
||||
fn layout (&$self, $to: <E as Output>::Area) -> <E as Output>::Area {
|
||||
use $Enum::*;
|
||||
$area
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines an enum that parametrically transforms its content
|
||||
/// along either the X axis, the Y axis, or both.
|
||||
macro_rules! transform_xy_unit {
|
||||
($x:literal $y:literal $xy:literal |$self:ident : $Enum:ident, $to:ident|$layout:expr) => {
|
||||
pub enum $Enum<U, T> { X(U, T), Y(U, T), XY(U, U, T), }
|
||||
impl<U, T> $Enum<U, T> {
|
||||
#[inline] pub const fn x (x: U, item: T) -> Self { Self::X(x, item) }
|
||||
#[inline] pub const fn y (y: U, item: T) -> Self { Self::Y(y, item) }
|
||||
#[inline] pub const fn xy (x: U, y: U, item: T) -> Self { Self::XY(x, y, item) }
|
||||
}
|
||||
#[cfg(feature = "dsl")]
|
||||
impl<'a, E: Output + 'a, T: ViewContext<'a, E>> TryFromAtom<'a, T>
|
||||
for $Enum<E::Unit, RenderBox<'a, E>> {
|
||||
fn try_from_expr (state: &'a T, iter: TokenIter<'a>) -> Option<Self> {
|
||||
let mut iter = iter.clone();
|
||||
if let Some(Token { value: Value::Key(k), .. }) = iter.peek() {
|
||||
if k == $x || k == $y {
|
||||
let _ = iter.next().unwrap();
|
||||
|
||||
let u = iter.next().expect("no unit specified");
|
||||
let u = state.get(&u.value).expect("no unit provided");
|
||||
|
||||
let c = iter.next().expect("no content specified");
|
||||
let c = if let Some(c) = state.get_content(&c.value) {
|
||||
c
|
||||
} else {
|
||||
panic!("no content corresponding to {:?}", &c);
|
||||
};
|
||||
|
||||
return Some(match k {
|
||||
$x => Self::x(u, c),
|
||||
$y => Self::y(u, c),
|
||||
_ => unreachable!(),
|
||||
})
|
||||
} else if k == $xy {
|
||||
let _ = iter.next().unwrap();
|
||||
|
||||
let u = iter.next().expect("no unit specified");
|
||||
let u = state.get(&u.value).expect("no x unit provided");
|
||||
|
||||
let v = iter.next().expect("no unit specified");
|
||||
let v = state.get(&v.value).expect("no y unit provided");
|
||||
|
||||
let c = iter.next().expect("no content specified");
|
||||
let c = if let Some(c) = state.get_content(&c.value) {
|
||||
c
|
||||
} else {
|
||||
panic!("no content corresponding to {:?}", &c);
|
||||
};
|
||||
|
||||
return Some(Self::xy(u, v, c))
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
impl<E: Output, T: Content<E>> Content<E> for $Enum<E::Unit, T> {
|
||||
fn content (&self) -> impl Render<E> {
|
||||
Some(match self {
|
||||
Self::X(_, content) => content,
|
||||
Self::Y(_, content) => content,
|
||||
Self::XY(_, _, content) => content,
|
||||
})
|
||||
}
|
||||
fn layout (&$self, $to: E::Area) -> E::Area {
|
||||
$layout.into()
|
||||
}
|
||||
}
|
||||
impl<U: Copy + Coordinate, T> $Enum<U, T> {
|
||||
#[inline] pub fn dx (&self) -> U {
|
||||
match self {
|
||||
Self::X(x, _) => *x, Self::Y(_, _) => 0.into(), Self::XY(x, _, _) => *x,
|
||||
}
|
||||
}
|
||||
#[inline] pub fn dy (&self) -> U {
|
||||
match self {
|
||||
Self::X(_, _) => 0.into(), Self::Y(y, _) => *y, Self::XY(_, y, _) => *y,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
transform_xy!("fill/x" "fill/y" "fill/xy" |self: Fill, to|{
|
||||
let [x0, y0, wmax, hmax] = to.xywh();
|
||||
let [x, y, w, h] = self.content().layout(to).xywh();
|
||||
match self {
|
||||
X(_) => [x0, y, wmax, h],
|
||||
Y(_) => [x, y0, w, hmax],
|
||||
XY(_) => [x0, y0, wmax, hmax],
|
||||
}.into() });
|
||||
transform_xy_unit!("fixed/x" "fixed/y" "fixed/xy"|self: Fixed, area|{
|
||||
let [x, y, w, h] = area.xywh();
|
||||
let fixed_area = match self {
|
||||
Self::X(fw, _) => [x, y, *fw, h],
|
||||
Self::Y(fh, _) => [x, y, w, *fh],
|
||||
Self::XY(fw, fh, _) => [x, y, *fw, *fh],
|
||||
};
|
||||
let [x, y, w, h] = Render::layout(&self.content(), fixed_area.into()).xywh();
|
||||
let fixed_area = match self {
|
||||
Self::X(fw, _) => [x, y, *fw, h],
|
||||
Self::Y(fh, _) => [x, y, w, *fh],
|
||||
Self::XY(fw, fh, _) => [x, y, *fw, *fh],
|
||||
};
|
||||
fixed_area });
|
||||
transform_xy_unit!("min/x" "min/y" "min/xy"|self: Min, area|{
|
||||
let area = Render::layout(&self.content(), area);
|
||||
match self {
|
||||
Self::X(mw, _) => [area.x(), area.y(), area.w().max(*mw), area.h()],
|
||||
Self::Y(mh, _) => [area.x(), area.y(), area.w(), area.h().max(*mh)],
|
||||
Self::XY(mw, mh, _) => [area.x(), area.y(), area.w().max(*mw), area.h().max(*mh)],
|
||||
}});
|
||||
transform_xy_unit!("max/x" "max/y" "max/xy"|self: Max, area|{
|
||||
let [x, y, w, h] = area.xywh();
|
||||
Render::layout(&self.content(), match self {
|
||||
Self::X(fw, _) => [x, y, *fw, h],
|
||||
Self::Y(fh, _) => [x, y, w, *fh],
|
||||
Self::XY(fw, fh, _) => [x, y, *fw, *fh],
|
||||
}.into())});
|
||||
transform_xy_unit!("shrink/x" "shrink/y" "shrink/xy"|self: Shrink, area|Render::layout(
|
||||
&self.content(),
|
||||
[area.x(), area.y(), area.w().minus(self.dx()), area.h().minus(self.dy())].into()));
|
||||
transform_xy_unit!("expand/x" "expand/y" "expand/xy"|self: Expand, area|Render::layout(
|
||||
&self.content(),
|
||||
[area.x(), area.y(), area.w().plus(self.dx()), area.h().plus(self.dy())].into()));
|
||||
transform_xy_unit!("push/x" "push/y" "push/xy"|self: Push, area|{
|
||||
let area = Render::layout(&self.content(), area);
|
||||
[area.x().plus(self.dx()), area.y().plus(self.dy()), area.w(), area.h()] });
|
||||
transform_xy_unit!("pull/x" "pull/y" "pull/xy"|self: Pull, area|{
|
||||
let area = Render::layout(&self.content(), area);
|
||||
[area.x().minus(self.dx()), area.y().minus(self.dy()), area.w(), area.h()] });
|
||||
transform_xy_unit!("margin/x" "margin/y" "margin/xy"|self: Margin, area|{
|
||||
let area = Render::layout(&self.content(), area);
|
||||
let dx = self.dx();
|
||||
let dy = self.dy();
|
||||
[area.x().minus(dx), area.y().minus(dy), area.w().plus(dy.plus(dy)), area.h().plus(dy.plus(dy))] });
|
||||
transform_xy_unit!("padding/x" "padding/y" "padding/xy"|self: Padding, area|{
|
||||
let area = Render::layout(&self.content(), area);
|
||||
let dx = self.dx();
|
||||
let dy = self.dy();
|
||||
[area.x().plus(dx), area.y().plus(dy), area.w().minus(dy.plus(dy)), area.h().minus(dy.plus(dy)), ] });
|
||||
|
||||
#[cfg(test)] mod test_op_transform {
|
||||
use super::*;
|
||||
use proptest::prelude::*;
|
||||
use proptest::option::of;
|
||||
macro_rules! test_op_transform {
|
||||
($fn:ident, $Op:ident) => {
|
||||
proptest! {
|
||||
#[test] fn $fn (
|
||||
op_x in of(u16::MIN..u16::MAX),
|
||||
op_y in of(u16::MIN..u16::MAX),
|
||||
content in "\\PC*",
|
||||
x in u16::MIN..u16::MAX,
|
||||
y in u16::MIN..u16::MAX,
|
||||
w in u16::MIN..u16::MAX,
|
||||
h in u16::MIN..u16::MAX,
|
||||
) {
|
||||
if let Some(op) = match (op_x, op_y) {
|
||||
(Some(x), Some(y)) => Some($Op::xy(x, y, content)),
|
||||
(Some(x), None) => Some($Op::x(x, content)),
|
||||
(Some(y), None) => Some($Op::y(y, content)),
|
||||
_ => None
|
||||
} {
|
||||
assert_eq!(Content::layout(&op, [x, y, w, h]),
|
||||
Render::layout(&op, [x, y, w, h]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
test_op_transform!(test_op_fixed, Fixed);
|
||||
test_op_transform!(test_op_min, Min);
|
||||
test_op_transform!(test_op_max, Max);
|
||||
test_op_transform!(test_op_push, Push);
|
||||
test_op_transform!(test_op_pull, Pull);
|
||||
test_op_transform!(test_op_shrink, Shrink);
|
||||
test_op_transform!(test_op_expand, Expand);
|
||||
test_op_transform!(test_op_margin, Margin);
|
||||
test_op_transform!(test_op_padding, Padding);
|
||||
}
|
||||
|
|
@ -1,153 +1,65 @@
|
|||
use crate::*;
|
||||
use std::ops::Deref;
|
||||
/// Render target.
|
||||
pub trait Output: Send + Sync + Sized {
|
||||
#![feature(step_trait)]
|
||||
#![feature(type_alias_impl_trait)]
|
||||
#![feature(impl_trait_in_assoc_type)]
|
||||
#![feature(const_precise_live_drops)]
|
||||
#![feature(type_changing_struct_update)]
|
||||
#![feature(anonymous_lifetime_in_impl_trait)]
|
||||
#![feature(const_option_ops)]
|
||||
#![feature(const_trait_impl)]
|
||||
#![feature(const_default)]
|
||||
#![feature(trait_alias)]
|
||||
//#![feature(non_lifetime_binders)]
|
||||
|
||||
pub(crate) use self::Direction::*;
|
||||
pub(crate) use std::fmt::{Debug, Display};
|
||||
pub(crate) use std::marker::PhantomData;
|
||||
pub(crate) use std::ops::{Add, Sub, Mul, Div};
|
||||
pub(crate) use std::sync::{Arc, RwLock, atomic::{AtomicUsize, Ordering::Relaxed}};
|
||||
pub(crate) use tengri_core::*;
|
||||
|
||||
/// Drawing target.
|
||||
pub trait Out: Send + Sync + Sized {
|
||||
/// Unit of length
|
||||
type Unit: Coordinate;
|
||||
|
||||
/// Rectangle without offset
|
||||
type Size: Size<Self::Unit>;
|
||||
|
||||
/// Rectangle with offset
|
||||
type Area: Area<Self::Unit>;
|
||||
|
||||
/// Render drawable in area specified by `T::layout(self.area())`
|
||||
#[inline] fn place <'t, T: Content<Self> + ?Sized> (
|
||||
&mut self, content: &'t T
|
||||
) {
|
||||
self.place_at(content.layout(self.area()), content)
|
||||
}
|
||||
|
||||
/// Render drawable in area specified by `area`
|
||||
fn place_at <'t, T: Draw<Self> + ?Sized> (&mut self, area: Self::Area, content: &'t T);
|
||||
|
||||
/// Current output area
|
||||
fn area (&self) -> Self::Area;
|
||||
/// Mutable pointer to area
|
||||
fn area_mut (&mut self) -> &mut Self::Area;
|
||||
/// Render widget in area
|
||||
fn place (&mut self, area: Self::Area, content: &impl Render<Self>);
|
||||
#[inline] fn x (&self) -> Self::Unit { self.area().x() }
|
||||
#[inline] fn y (&self) -> Self::Unit { self.area().y() }
|
||||
#[inline] fn w (&self) -> Self::Unit { self.area().w() }
|
||||
#[inline] fn h (&self) -> Self::Unit { self.area().h() }
|
||||
#[inline] fn wh (&self) -> Self::Size { self.area().wh().into() }
|
||||
|
||||
/// Mutable pointer to area.
|
||||
fn area_mut (&mut self) -> &mut Self::Area;
|
||||
}
|
||||
|
||||
/// Renderable with dynamic dispatch.
|
||||
pub trait Render<E: Output> {
|
||||
/// Compute layout.
|
||||
fn layout (&self, area: E::Area) -> E::Area;
|
||||
/// Write data to display.
|
||||
fn render (&self, output: &mut E);
|
||||
/// Perform type erasure, turning `self` into an opaque [RenderBox].
|
||||
fn boxed <'a> (self) -> RenderBox<'a, E> where Self: Send + Sync + Sized + 'a {
|
||||
Box::new(self) as RenderBox<'a, E>
|
||||
}
|
||||
}
|
||||
#[cfg(test)] mod output_test;
|
||||
#[cfg(test)] pub(crate) use proptest_derive::Arbitrary;
|
||||
|
||||
/// Most importantly, every [Content] is also a [Render].
|
||||
///
|
||||
/// However, the converse does not hold true.
|
||||
/// Instead, the [Content::content] method returns an
|
||||
/// opaque [Render] pointer.
|
||||
impl<E: Output, C: Content<E>> Render<E> for C {
|
||||
fn layout (&self, area: E::Area) -> E::Area { Content::layout(self, area) }
|
||||
fn render (&self, output: &mut E) { Content::render(self, output) }
|
||||
}
|
||||
mod content; pub use self::content::*;
|
||||
mod draw; pub use self::draw::*;
|
||||
mod group; pub use self::group::*;
|
||||
mod layout; pub use self::layout::*;
|
||||
mod space; pub use self::space::*;
|
||||
mod thunk; pub use self::thunk::*;
|
||||
mod widget; pub use self::widget::*;
|
||||
|
||||
/// Opaque pointer to a renderable living on the heap.
|
||||
///
|
||||
/// Return this from [Content::content] to use dynamic dispatch.
|
||||
pub type RenderBox<'a, E> = Box<RenderDyn<'a, E>>;
|
||||
|
||||
/// You can render from a box.
|
||||
impl<'a, E: Output> Content<E> for RenderBox<'a, E> {
|
||||
fn content (&self) -> impl Render<E> { self.deref() }
|
||||
//fn boxed <'b> (self) -> RenderBox<'b, E> where Self: Sized + 'b { self }
|
||||
}
|
||||
|
||||
/// Opaque pointer to a renderable.
|
||||
pub type RenderDyn<'a, E> = dyn Render<E> + Send + Sync + 'a;
|
||||
|
||||
/// You can render from an opaque pointer.
|
||||
impl<'a, E: Output> Content<E> for &RenderDyn<'a, E> where Self: Sized {
|
||||
fn content (&self) -> impl Render<E> {
|
||||
#[allow(suspicious_double_ref_op)]
|
||||
self.deref()
|
||||
}
|
||||
fn layout (&self, area: E::Area) -> E::Area {
|
||||
#[allow(suspicious_double_ref_op)]
|
||||
Render::layout(self.deref(), area)
|
||||
}
|
||||
fn render (&self, output: &mut E) {
|
||||
#[allow(suspicious_double_ref_op)]
|
||||
Render::render(self.deref(), output)
|
||||
}
|
||||
}
|
||||
|
||||
/// Composable renderable with static dispatch.
|
||||
pub trait Content<E: Output> {
|
||||
/// Return a [Render]able of a specific type.
|
||||
fn content (&self) -> impl Render<E> { () }
|
||||
/// Perform layout. By default, delegates to [Self::content].
|
||||
fn layout (&self, area: E::Area) -> E::Area { self.content().layout(area) }
|
||||
/// Draw to output. By default, delegates to [Self::content].
|
||||
fn render (&self, output: &mut E) { self.content().render(output) }
|
||||
}
|
||||
|
||||
/// Every pointer to [Content] is a [Content].
|
||||
impl<E: Output, C: Content<E>> Content<E> for &C {
|
||||
fn content (&self) -> impl Render<E> { (*self).content() }
|
||||
fn layout (&self, area: E::Area) -> E::Area { (*self).layout(area) }
|
||||
fn render (&self, output: &mut E) { (*self).render(output) }
|
||||
}
|
||||
|
||||
/// The platonic ideal unit of [Content]: total emptiness at dead center (e=1vg^sqrt(-1))
|
||||
impl<E: Output> Content<E> for () {
|
||||
fn layout (&self, area: E::Area) -> E::Area { area.center().to_area_pos().into() }
|
||||
fn render (&self, _: &mut E) {}
|
||||
}
|
||||
|
||||
impl<E: Output, T: Content<E>> Content<E> for Option<T> {
|
||||
fn content (&self) -> impl Render<E> {
|
||||
self.as_ref()
|
||||
}
|
||||
fn layout (&self, area: E::Area) -> E::Area {
|
||||
self.as_ref()
|
||||
.map(|content|content.layout(area))
|
||||
.unwrap_or([0.into(), 0.into(), 0.into(), 0.into(),].into())
|
||||
}
|
||||
fn render (&self, output: &mut E) {
|
||||
self.as_ref()
|
||||
.map(|content|content.render(output));
|
||||
}
|
||||
}
|
||||
|
||||
/// Implement [Content] with composable content for a struct.
|
||||
#[macro_export] macro_rules! content {
|
||||
// Implement for all [Output]s.
|
||||
(|$self:ident:$Struct:ty| $content:expr) => {
|
||||
impl<E: Output> Content<E> for $Struct {
|
||||
fn content (&$self) -> impl Render<E> { Some($content) }
|
||||
}
|
||||
};
|
||||
// Implement for specific [Output].
|
||||
($Output:ty:|
|
||||
$self:ident:
|
||||
$Struct:ident$(<$($($L:lifetime)? $($T:ident)? $(:$Trait:path)?),+>)?
|
||||
|$content:expr) => {
|
||||
impl $(<$($($L)? $($T)? $(:$Trait)?),+>)? Content<$Output>
|
||||
for $Struct $(<$($($L)? $($T)?),+>)? {
|
||||
fn content (&$self) -> impl Render<$Output> { $content }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Implement [Content] with custom rendering for a struct.
|
||||
#[macro_export] macro_rules! render {
|
||||
(|$self:ident:$Struct:ident $(<
|
||||
$($L:lifetime),* $($T:ident $(:$Trait:path)?),*
|
||||
>)?, $to:ident | $render:expr) => {
|
||||
impl <$($($L),*)? E: Output, $($T$(:$Trait)?),*> Content<E>
|
||||
for $Struct $(<$($L),* $($T),*>>)? {
|
||||
fn render (&$self, $to: &mut E) { $render }
|
||||
}
|
||||
};
|
||||
($Output:ty:|
|
||||
$self:ident:
|
||||
$Struct:ident $(<$($($L:lifetime)? $($T:ident)? $(:$Trait:path)?),+>)?, $to:ident
|
||||
|$render:expr) => {
|
||||
impl $(<$($($L)? $($T)? $(:$Trait)?),+>)? Content<$Output>
|
||||
for $Struct $(<$($($L)? $($T)?),+>)? {
|
||||
fn render (&$self, $to: &mut $Output) { $render }
|
||||
}
|
||||
};
|
||||
}
|
||||
#[cfg(feature = "dsl")] mod view;
|
||||
#[cfg(feature = "dsl")] pub use self::view::*;
|
||||
|
|
|
|||
170
output/src/output_test.rs
Normal file
170
output/src/output_test.rs
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
use crate::{*, Direction::*};
|
||||
//use proptest_derive::Arbitrary;
|
||||
use proptest::{prelude::*, option::of};
|
||||
|
||||
proptest! {
|
||||
#[test] fn proptest_direction (
|
||||
d in prop_oneof![
|
||||
Just(North), Just(South),
|
||||
Just(East), Just(West),
|
||||
Just(Above), Just(Below)
|
||||
],
|
||||
x in u16::MIN..u16::MAX,
|
||||
y in u16::MIN..u16::MAX,
|
||||
w in u16::MIN..u16::MAX,
|
||||
h in u16::MIN..u16::MAX,
|
||||
a in u16::MIN..u16::MAX,
|
||||
) {
|
||||
let _ = d.split_fixed([x, y, w, h], a);
|
||||
}
|
||||
}
|
||||
|
||||
proptest! {
|
||||
#[test] fn proptest_size (
|
||||
x in u16::MIN..u16::MAX,
|
||||
y in u16::MIN..u16::MAX,
|
||||
a in u16::MIN..u16::MAX,
|
||||
b in u16::MIN..u16::MAX,
|
||||
) {
|
||||
let size = [x, y];
|
||||
let _ = size.w();
|
||||
let _ = size.h();
|
||||
let _ = size.wh();
|
||||
let _ = size.clip_w(a);
|
||||
let _ = size.clip_h(b);
|
||||
let _ = size.expect_min(a, b);
|
||||
let _ = size.to_area_pos();
|
||||
let _ = size.to_area_size();
|
||||
}
|
||||
}
|
||||
|
||||
proptest! {
|
||||
#[test] fn proptest_area (
|
||||
x in u16::MIN..u16::MAX,
|
||||
y in u16::MIN..u16::MAX,
|
||||
w in u16::MIN..u16::MAX,
|
||||
h in u16::MIN..u16::MAX,
|
||||
a in u16::MIN..u16::MAX,
|
||||
b in u16::MIN..u16::MAX,
|
||||
) {
|
||||
let _: [u16;4] = <[u16;4] as Area<u16>>::zero();
|
||||
let _: [u16;4] = <[u16;4] as Area<u16>>::from_position([a, b]);
|
||||
let _: [u16;4] = <[u16;4] as Area<u16>>::from_size([a, b]);
|
||||
let area: [u16;4] = [x, y, w, h];
|
||||
let _ = area.expect_min(a, b);
|
||||
let _ = area.xy();
|
||||
let _ = area.wh();
|
||||
let _ = area.xywh();
|
||||
let _ = area.clip_h(a);
|
||||
let _ = area.clip_w(b);
|
||||
let _ = area.clip([a, b]);
|
||||
let _ = area.set_w(a);
|
||||
let _ = area.set_h(b);
|
||||
let _ = area.x2();
|
||||
let _ = area.y2();
|
||||
let _ = area.lrtb();
|
||||
let _ = area.center();
|
||||
let _ = area.center_x(a);
|
||||
let _ = area.center_y(b);
|
||||
let _ = area.center_xy([a, b]);
|
||||
let _ = area.centered();
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! test_op_transform {
|
||||
($fn:ident, $Op:ident) => {
|
||||
proptest! {
|
||||
#[test] fn $fn (
|
||||
op_x in of(u16::MIN..u16::MAX),
|
||||
op_y in of(u16::MIN..u16::MAX),
|
||||
content in "\\PC*",
|
||||
x in u16::MIN..u16::MAX,
|
||||
y in u16::MIN..u16::MAX,
|
||||
w in u16::MIN..u16::MAX,
|
||||
h in u16::MIN..u16::MAX,
|
||||
) {
|
||||
if let Some(op) = match (op_x, op_y) {
|
||||
(Some(x), Some(y)) => Some($Op::XY(x, y, content)),
|
||||
(Some(x), None) => Some($Op::X(x, content)),
|
||||
(None, Some(y)) => Some($Op::y(y, content)),
|
||||
_ => None
|
||||
} {
|
||||
//assert_eq!(Content::layout(&op, [x, y, w, h]),
|
||||
//Draw::layout(&op, [x, y, w, h]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test_op_transform!(proptest_op_fixed, Fixed);
|
||||
test_op_transform!(proptest_op_min, Min);
|
||||
test_op_transform!(proptest_op_max, Max);
|
||||
test_op_transform!(proptest_op_push, Push);
|
||||
test_op_transform!(proptest_op_pull, Pull);
|
||||
test_op_transform!(proptest_op_shrink, Shrink);
|
||||
test_op_transform!(proptest_op_expand, Expand);
|
||||
test_op_transform!(proptest_op_padding, Pad);
|
||||
|
||||
proptest! {
|
||||
#[test] fn proptest_op_bsp (
|
||||
d in prop_oneof![
|
||||
Just(North), Just(South),
|
||||
Just(East), Just(West),
|
||||
Just(Above), Just(Below)
|
||||
],
|
||||
a in "\\PC*",
|
||||
b in "\\PC*",
|
||||
x in u16::MIN..u16::MAX,
|
||||
y in u16::MIN..u16::MAX,
|
||||
w in u16::MIN..u16::MAX,
|
||||
h in u16::MIN..u16::MAX,
|
||||
) {
|
||||
let bsp = Bsp(d, a, b);
|
||||
//assert_eq!(
|
||||
//Content::layout(&bsp, [x, y, w, h]),
|
||||
//Draw::layout(&bsp, [x, y, w, h]),
|
||||
//);
|
||||
}
|
||||
}
|
||||
|
||||
#[test] fn test_stub_output () -> Usually<()> {
|
||||
use crate::*;
|
||||
struct TestOut([u16;4]);
|
||||
impl Out for TestOut {
|
||||
type Unit = u16;
|
||||
type Size = [u16;2];
|
||||
type Area = [u16;4];
|
||||
fn area (&self) -> [u16;4] {
|
||||
self.0
|
||||
}
|
||||
fn area_mut (&mut self) -> &mut [u16;4] {
|
||||
&mut self.0
|
||||
}
|
||||
fn place_at <T: Draw<Self> + ?Sized> (&mut self, area: [u16;4], _: &T) {
|
||||
println!("place_at: {area:?}");
|
||||
()
|
||||
}
|
||||
}
|
||||
impl Draw<TestOut> for String {
|
||||
fn draw (&self, to: &mut TestOut) {
|
||||
to.area_mut().set_w(self.len() as u16);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test] fn test_space () {
|
||||
use crate::*;
|
||||
assert_eq!(Area::center(&[10u16, 10, 20, 20]), [20, 20]);
|
||||
}
|
||||
|
||||
#[test] fn test_iter_map () {
|
||||
struct Foo;
|
||||
impl<T: Out> Content<T> for Foo {}
|
||||
fn _make_map <T: Out, U: Content<T> + Send + Sync> (data: &Vec<U>) -> impl Draw<T> {
|
||||
Map::new(||data.iter(), |_foo, _index|{})
|
||||
}
|
||||
let _data = vec![Foo, Foo, Foo];
|
||||
//let map = make_map(&data);
|
||||
}
|
||||
|
|
@ -1,10 +1,5 @@
|
|||
mod area; pub use self::area::*;
|
||||
mod coordinate; pub use self::coordinate::*;
|
||||
mod direction; pub use self::direction::*;
|
||||
mod measure; pub use self::measure::*;
|
||||
mod size; pub use self::size::*;
|
||||
|
||||
#[cfg(test)] #[test] fn test_space () {
|
||||
use crate::*;
|
||||
assert_eq!(Area::center(&[10u16, 10, 20, 20]), [20, 20]);
|
||||
}
|
||||
mod space_area; pub use self::space_area::*;
|
||||
mod space_coordinate; pub use self::space_coordinate::*;
|
||||
mod space_direction; pub use self::space_direction::*;
|
||||
mod space_measure; pub use self::space_measure::*;
|
||||
mod space_size; pub use self::space_size::*;
|
||||
|
|
|
|||
|
|
@ -1,38 +0,0 @@
|
|||
use crate::*;
|
||||
#[cfg(test)] use proptest_derive::Arbitrary;
|
||||
/// 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())
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(test)] mod test {
|
||||
use super::*;
|
||||
use proptest::prelude::*;
|
||||
proptest! {
|
||||
#[test] fn proptest_direction (
|
||||
d in prop_oneof![
|
||||
Just(North), Just(South),
|
||||
Just(East), Just(West),
|
||||
Just(Above), Just(Below)
|
||||
],
|
||||
x in u16::MIN..u16::MAX,
|
||||
y in u16::MIN..u16::MAX,
|
||||
w in u16::MIN..u16::MAX,
|
||||
h in u16::MIN..u16::MAX,
|
||||
a in u16::MIN..u16::MAX,
|
||||
) {
|
||||
let _ = d.split_fixed([x, y, w, h], a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
use crate::*;
|
||||
use std::fmt::Debug;
|
||||
|
||||
pub trait Area<N: Coordinate>: From<[N;4]> + Debug + Copy {
|
||||
fn x (&self) -> N;
|
||||
|
|
@ -96,40 +95,3 @@ impl<N: Coordinate> Area<N> for [N;4] {
|
|||
fn w (&self) -> N { self[2] }
|
||||
fn h (&self) -> N { self[3] }
|
||||
}
|
||||
|
||||
#[cfg(test)] mod test_area {
|
||||
use super::*;
|
||||
use proptest::prelude::*;
|
||||
proptest! {
|
||||
#[test] fn test_area_prop (
|
||||
x in u16::MIN..u16::MAX,
|
||||
y in u16::MIN..u16::MAX,
|
||||
w in u16::MIN..u16::MAX,
|
||||
h in u16::MIN..u16::MAX,
|
||||
a in u16::MIN..u16::MAX,
|
||||
b in u16::MIN..u16::MAX,
|
||||
) {
|
||||
let _: [u16;4] = <[u16;4] as Area<u16>>::zero();
|
||||
let _: [u16;4] = <[u16;4] as Area<u16>>::from_position([a, b]);
|
||||
let _: [u16;4] = <[u16;4] as Area<u16>>::from_size([a, b]);
|
||||
let area: [u16;4] = [x, y, w, h];
|
||||
let _ = area.expect_min(a, b);
|
||||
let _ = area.xy();
|
||||
let _ = area.wh();
|
||||
let _ = area.xywh();
|
||||
let _ = area.clip_h(a);
|
||||
let _ = area.clip_w(b);
|
||||
let _ = area.clip([a, b]);
|
||||
let _ = area.set_w(a);
|
||||
let _ = area.set_h(b);
|
||||
let _ = area.x2();
|
||||
let _ = area.y2();
|
||||
let _ = area.lrtb();
|
||||
let _ = area.center();
|
||||
let _ = area.center_x(a);
|
||||
let _ = area.center_y(b);
|
||||
let _ = area.center_xy([a, b]);
|
||||
let _ = area.centered();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
use std::fmt::{Debug, Display};
|
||||
use std::ops::{Add, Sub, Mul, Div};
|
||||
use crate::*;
|
||||
|
||||
/// A linear coordinate.
|
||||
pub trait Coordinate: Send + Sync + Copy
|
||||
21
output/src/space/space_direction.rs
Normal file
21
output/src/space/space_direction.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
use crate::*;
|
||||
|
||||
/// 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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,35 +1,31 @@
|
|||
use crate::*;
|
||||
use std::sync::{Arc, atomic::{AtomicUsize, Ordering::Relaxed}};
|
||||
|
||||
pub trait HasSize<E: Output> {
|
||||
fn size (&self) -> &Measure<E>;
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! has_size {
|
||||
(<$E:ty>|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
|
||||
impl $(<$($L),*$($T $(: $U)?),*>)? HasSize<$E> for $Struct $(<$($L),*$($T),*>)? {
|
||||
fn size (&$self) -> &Measure<$E> { $cb }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A widget that tracks its render width and height
|
||||
#[derive(Default)]
|
||||
pub struct Measure<E: Output> {
|
||||
_engine: PhantomData<E>,
|
||||
pub struct Measure<O: Out> {
|
||||
_engine: PhantomData<O>,
|
||||
pub x: Arc<AtomicUsize>,
|
||||
pub y: Arc<AtomicUsize>,
|
||||
}
|
||||
|
||||
impl<O: Out> PartialEq for Measure<O> {
|
||||
fn eq (&self, other: &Self) -> bool {
|
||||
self.x.load(Relaxed) == other.x.load(Relaxed) &&
|
||||
self.y.load(Relaxed) == other.y.load(Relaxed)
|
||||
}
|
||||
}
|
||||
|
||||
impl<O: Out> Layout<O> for Measure<O> {}
|
||||
|
||||
// TODO: 🡘 🡙 ←🡙→ indicator to expand window when too small
|
||||
impl<E: Output> Content<E> for Measure<E> {
|
||||
fn render (&self, to: &mut E) {
|
||||
impl<O: Out> Draw<O> for Measure<O> {
|
||||
fn draw (&self, to: &mut O) {
|
||||
self.x.store(to.area().w().into(), Relaxed);
|
||||
self.y.store(to.area().h().into(), Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Output> Clone for Measure<E> {
|
||||
impl<O: Out> Clone for Measure<O> {
|
||||
fn clone (&self) -> Self {
|
||||
Self {
|
||||
_engine: Default::default(),
|
||||
|
|
@ -39,7 +35,7 @@ impl<E: Output> Clone for Measure<E> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<E: Output> std::fmt::Debug for Measure<E> {
|
||||
impl<O: Out> std::fmt::Debug for Measure<O> {
|
||||
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||
f.debug_struct("Measure")
|
||||
.field("width", &self.x)
|
||||
|
|
@ -48,7 +44,7 @@ impl<E: Output> std::fmt::Debug for Measure<E> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<E: Output> Measure<E> {
|
||||
impl<O: Out> Measure<O> {
|
||||
pub fn new () -> Self {
|
||||
Self {
|
||||
_engine: PhantomData::default(),
|
||||
|
|
@ -81,7 +77,7 @@ impl<E: Output> Measure<E> {
|
|||
pub fn format (&self) -> Arc<str> {
|
||||
format!("{}x{}", self.w(), self.h()).into()
|
||||
}
|
||||
pub fn of <T: Content<E>> (&self, item: T) -> Bsp<Fill<&Self>, T> {
|
||||
Bsp::b(Fill::xy(self), item)
|
||||
pub fn of <T: Draw<O>> (&self, item: T) -> Bsp<Fill<&Self>, T> {
|
||||
Bsp::b(Fill::XY(self), item)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
use crate::*;
|
||||
use std::fmt::Debug;
|
||||
|
||||
pub trait Size<N: Coordinate>: From<[N;2]> + Debug + Copy {
|
||||
fn x (&self) -> N;
|
||||
|
|
@ -39,25 +38,18 @@ impl<N: Coordinate> Size<N> for [N;2] {
|
|||
fn y (&self) -> N { self[1] }
|
||||
}
|
||||
|
||||
#[cfg(test)] mod test_size {
|
||||
use super::*;
|
||||
use proptest::prelude::*;
|
||||
proptest! {
|
||||
#[test] fn test_size (
|
||||
x in u16::MIN..u16::MAX,
|
||||
y in u16::MIN..u16::MAX,
|
||||
a in u16::MIN..u16::MAX,
|
||||
b in u16::MIN..u16::MAX,
|
||||
) {
|
||||
let size = [x, y];
|
||||
let _ = size.w();
|
||||
let _ = size.h();
|
||||
let _ = size.wh();
|
||||
let _ = size.clip_w(a);
|
||||
let _ = size.clip_h(b);
|
||||
let _ = size.expect_min(a, b);
|
||||
let _ = size.to_area_pos();
|
||||
let _ = size.to_area_size();
|
||||
}
|
||||
pub trait HasSize<E: Out> {
|
||||
fn size (&self) -> &Measure<E>;
|
||||
fn width (&self) -> usize {
|
||||
self.size().w()
|
||||
}
|
||||
fn height (&self) -> usize {
|
||||
self.size().h()
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Out, T: Has<Measure<E>>> HasSize<E> for T {
|
||||
fn size (&self) -> &Measure<E> {
|
||||
self.get()
|
||||
}
|
||||
}
|
||||
27
output/src/thunk.rs
Normal file
27
output/src/thunk.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
use crate::*;
|
||||
|
||||
pub struct Lazy<O, T, F>(F, PhantomData<(O, T)>);
|
||||
impl<O: Out, T: Content<O>, F: Fn()->T> Lazy<O, T, F> { pub const fn new (thunk: F) -> Self { Self(thunk, PhantomData) } }
|
||||
|
||||
pub struct Thunk<O: Out, F: Fn(&mut O)>(PhantomData<O>, F);
|
||||
impl<O: Out, F: Fn(&mut O)> Thunk<O, F> { pub const fn new (draw: F) -> Self { Self(PhantomData, draw) } }
|
||||
impl<O: Out, F: Fn(&mut O)> Layout<O> for Thunk<O, F> {}
|
||||
impl<O: Out, F: Fn(&mut O)> Draw<O> for Thunk<O, F> {
|
||||
fn draw (&self, to: &mut O) { (self.1)(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, draw: impl Fn(&mut U, &T, &T)->R) -> Option<R> {
|
||||
if newval != self.value {
|
||||
let result = draw(&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)*) } } }
|
||||
|
|
@ -1,100 +1,147 @@
|
|||
use crate::*;
|
||||
use ::tengri_dsl::{Dsl, DslExpr, DslWord, DslNs};
|
||||
|
||||
#[macro_export] macro_rules! view {
|
||||
($Output:ty: |$self:ident: $State:ty| $expr:expr; {
|
||||
$($sym:literal => $body:expr),* $(,)?
|
||||
}) => {
|
||||
impl Content<$Output> for $State {
|
||||
fn content (&$self) -> impl Render<$Output> { $expr }
|
||||
}
|
||||
impl<'a> ViewContext<'a, $Output> for $State {
|
||||
fn get_content_sym (&'a $self, value: &Value<'a>) -> Option<RenderBox<'a, $Output>> {
|
||||
if let Value::Sym(s) = value {
|
||||
match *s {
|
||||
$($sym => Some($body),)*
|
||||
_ => None
|
||||
}
|
||||
} else {
|
||||
panic!("expected content, got: {value:?}")
|
||||
}
|
||||
}
|
||||
pub trait View<O, U> {
|
||||
fn view_expr <'a> (&'a self, output: &mut O, expr: &'a impl DslExpr) -> Usually<U> {
|
||||
Err(format!("View::view_expr: no exprs defined: {expr:?}").into())
|
||||
}
|
||||
fn view_word <'a> (&'a self, output: &mut O, word: &'a impl DslWord) -> Usually<U> {
|
||||
Err(format!("View::view_word: no words defined: {word:?}").into())
|
||||
}
|
||||
fn view <'a> (&'a self, output: &mut O, dsl: &'a impl Dsl) -> Usually<U> {
|
||||
if let Ok(Some(expr)) = dsl.expr() {
|
||||
self.view_expr(output, &expr)
|
||||
} else if let Ok(Some(word)) = dsl.word() {
|
||||
self.view_word(output, &word)
|
||||
} else {
|
||||
panic!("{dsl:?}: invalid")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// An ephemeral wrapper around view state and view description,
|
||||
// that is meant to be constructed and returned from [Content::content].
|
||||
#[cfg(feature = "dsl")]
|
||||
pub struct View<'a, T>(
|
||||
pub &'a T,
|
||||
pub SourceIter<'a>
|
||||
);
|
||||
|
||||
#[cfg(feature = "dsl")]
|
||||
impl<'a, O: Output + 'a, T: ViewContext<'a, O>> Content<O> for View<'a, T> {
|
||||
fn content (&self) -> impl Render<O> {
|
||||
let iter = self.1.clone();
|
||||
while let Some((Token { value, .. }, _)) = iter.next() {
|
||||
if let Some(content) = self.0.get_content(&value) {
|
||||
return Some(content)
|
||||
}
|
||||
}
|
||||
return None
|
||||
}
|
||||
}
|
||||
|
||||
// Provides components to the view.
|
||||
#[cfg(feature = "dsl")]
|
||||
pub trait ViewContext<'a, E: Output + 'a>: Send + Sync
|
||||
+ Context<bool>
|
||||
+ Context<usize>
|
||||
+ Context<E::Unit>
|
||||
pub fn evaluate_output_expression <'a, O: Out + 'a, S> (
|
||||
state: &S, output: &mut O, expr: &'a impl DslExpr
|
||||
) -> Usually<bool> where
|
||||
S: View<O, ()>
|
||||
+ for<'b>DslNs<'b, bool>
|
||||
+ for<'b>DslNs<'b, O::Unit>
|
||||
{
|
||||
fn get_content (&'a self, value: &Value<'a>) -> Option<RenderBox<'a, E>> {
|
||||
match value {
|
||||
Value::Sym(_) => self.get_content_sym(value),
|
||||
Value::Exp(_, _) => self.get_content_exp(value),
|
||||
_ => panic!("only :symbols and (expressions) accepted here")
|
||||
}
|
||||
}
|
||||
fn get_content_sym (&'a self, value: &Value<'a>) -> Option<RenderBox<'a, E>>;
|
||||
fn get_content_exp (&'a self, value: &Value<'a>) -> Option<RenderBox<'a, E>> {
|
||||
try_delegate!(self, *value, When::<RenderBox<'a, E>>);
|
||||
try_delegate!(self, *value, Either::<RenderBox<'a, E>, RenderBox<'a, E>>);
|
||||
try_delegate!(self, *value, Align::<RenderBox<'a, E>>);
|
||||
try_delegate!(self, *value, Bsp::<RenderBox<'a, E>, RenderBox<'a, E>>);
|
||||
try_delegate!(self, *value, Fill::<RenderBox<'a, E>>);
|
||||
try_delegate!(self, *value, Fixed::<_, RenderBox<'a, E>>);
|
||||
try_delegate!(self, *value, Min::<_, RenderBox<'a, E>>);
|
||||
try_delegate!(self, *value, Max::<_, RenderBox<'a, E>>);
|
||||
try_delegate!(self, *value, Shrink::<_, RenderBox<'a, E>>);
|
||||
try_delegate!(self, *value, Expand::<_, RenderBox<'a, E>>);
|
||||
try_delegate!(self, *value, Push::<_, RenderBox<'a, E>>);
|
||||
try_delegate!(self, *value, Pull::<_, RenderBox<'a, E>>);
|
||||
try_delegate!(self, *value, Margin::<_, RenderBox<'a, E>>);
|
||||
try_delegate!(self, *value, Padding::<_, RenderBox<'a, E>>);
|
||||
None
|
||||
}
|
||||
}
|
||||
// First element of expression is used for dispatch.
|
||||
// Dispatch is proto-namespaced using separator character
|
||||
let head = expr.head()?;
|
||||
let mut frags = head.src()?.unwrap_or_default().split("/");
|
||||
// The rest of the tokens in the expr are arguments.
|
||||
// Their meanings depend on the dispatched operation
|
||||
let args = expr.tail();
|
||||
let arg0 = args.head();
|
||||
let tail0 = args.tail();
|
||||
let arg1 = tail0.head();
|
||||
let tail1 = tail0.tail();
|
||||
let arg2 = tail1.head();
|
||||
// And we also have to do the above binding dance
|
||||
// so that the Perhaps<token>s remain in scope.
|
||||
match frags.next() {
|
||||
|
||||
#[cfg(feature = "dsl")]
|
||||
#[macro_export] macro_rules! try_delegate {
|
||||
($s:ident, $atom:expr, $T:ty) => {
|
||||
if let Some(value) = <$T>::try_from_atom($s, $atom) {
|
||||
return Some(value.boxed())
|
||||
}
|
||||
}
|
||||
}
|
||||
Some("when") => output.place(&When::new(
|
||||
state.from(arg0?)?.unwrap(),
|
||||
Thunk::new(move|output: &mut O|state.view(output, &arg1).unwrap())
|
||||
)),
|
||||
|
||||
#[cfg(feature = "dsl")]
|
||||
#[macro_export] macro_rules! try_from_expr {
|
||||
(<$l:lifetime, $E:ident>: $Struct:ty: |$state:ident, $iter:ident|$body:expr) => {
|
||||
impl<$l, $E: Output + $l, T: ViewContext<$l, $E>> TryFromAtom<$l, T> for $Struct {
|
||||
fn try_from_expr ($state: &$l T, $iter: TokenIter<'a>) -> Option<Self> {
|
||||
let mut $iter = $iter.clone();
|
||||
$body;
|
||||
None
|
||||
Some("either") => output.place(&Either::new(
|
||||
state.from(arg0?)?.unwrap(),
|
||||
Thunk::new(move|output: &mut O|state.view(output, &arg1).unwrap()),
|
||||
Thunk::new(move|output: &mut O|state.view(output, &arg2).unwrap())
|
||||
)),
|
||||
|
||||
Some("bsp") => output.place(&{
|
||||
let a = Thunk::new(move|output: &mut O|state.view(output, &arg0).unwrap());
|
||||
let b = Thunk::new(move|output: &mut O|state.view(output, &arg1).unwrap());
|
||||
match frags.next() {
|
||||
Some("n") => Bsp::n(a, b),
|
||||
Some("s") => Bsp::s(a, b),
|
||||
Some("e") => Bsp::e(a, b),
|
||||
Some("w") => Bsp::w(a, b),
|
||||
Some("a") => Bsp::a(a, b),
|
||||
Some("b") => Bsp::b(a, b),
|
||||
frag => unimplemented!("bsp/{frag:?}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
||||
Some("align") => output.place(&{
|
||||
let a = Thunk::new(move|output: &mut O|state.view(output, &arg0).unwrap());
|
||||
match frags.next() {
|
||||
Some("n") => Align::n(a),
|
||||
Some("s") => Align::s(a),
|
||||
Some("e") => Align::e(a),
|
||||
Some("w") => Align::w(a),
|
||||
Some("x") => Align::x(a),
|
||||
Some("y") => Align::y(a),
|
||||
Some("c") => Align::c(a),
|
||||
frag => unimplemented!("align/{frag:?}")
|
||||
}
|
||||
}),
|
||||
|
||||
Some("fill") => output.place(&{
|
||||
let a = Thunk::new(move|output: &mut O|state.view(output, &arg0).unwrap());
|
||||
match frags.next() {
|
||||
Some("xy") | None => Fill::XY(a),
|
||||
Some("x") => Fill::X(a),
|
||||
Some("y") => Fill::Y(a),
|
||||
frag => unimplemented!("fill/{frag:?}")
|
||||
}
|
||||
}),
|
||||
|
||||
Some("fixed") => output.place(&{
|
||||
let axis = frags.next();
|
||||
let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") | None => arg2, _ => panic!("fixed: unsupported axis {axis:?}") };
|
||||
let cb = Thunk::new(move|output: &mut O|state.view(output, &arg).unwrap());
|
||||
match axis {
|
||||
Some("xy") | None => Fixed::XY(state.from(arg0?)?.unwrap(), state.from(arg1?)?.unwrap(), cb),
|
||||
Some("x") => Fixed::X(state.from(arg0?)?.unwrap(), cb),
|
||||
Some("y") => Fixed::Y(state.from(arg0?)?.unwrap(), cb),
|
||||
frag => unimplemented!("fixed/{frag:?} ({expr:?}) ({head:?}) ({:?})",
|
||||
head.src()?.unwrap_or_default().split("/").next())
|
||||
}
|
||||
}),
|
||||
|
||||
Some("min") => output.place(&{
|
||||
let axis = frags.next();
|
||||
let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") | None => arg2, _ => panic!("fixed: unsupported axis {axis:?}") };
|
||||
let cb = Thunk::new(move|output: &mut O|state.view(output, &arg).unwrap());
|
||||
match axis {
|
||||
Some("xy") | None => Min::XY(state.from(arg0?)?.unwrap(), state.from(arg1?)?.unwrap(), cb),
|
||||
Some("x") => Min::X(state.from(arg0?)?.unwrap(), cb),
|
||||
Some("y") => Min::Y(state.from(arg0?)?.unwrap(), cb),
|
||||
frag => unimplemented!("min/{frag:?}")
|
||||
}
|
||||
}),
|
||||
|
||||
Some("max") => output.place(&{
|
||||
let axis = frags.next();
|
||||
let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") | None => arg2, _ => panic!("fixed: unsupported axis {axis:?}") };
|
||||
let cb = Thunk::new(move|output: &mut O|state.view(output, &arg).unwrap());
|
||||
match axis {
|
||||
Some("xy") | None => Max::XY(state.from(arg0?)?.unwrap(), state.from(arg1?)?.unwrap(), cb),
|
||||
Some("x") => Max::X(state.from(arg0?)?.unwrap(), cb),
|
||||
Some("y") => Max::Y(state.from(arg0?)?.unwrap(), cb),
|
||||
frag => unimplemented!("max/{frag:?}")
|
||||
}
|
||||
}),
|
||||
|
||||
Some("push") => output.place(&{
|
||||
let axis = frags.next();
|
||||
let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") | None => arg2, _ => panic!("fixed: unsupported axis {axis:?}") };
|
||||
let cb = Thunk::new(move|output: &mut O|state.view(output, &arg).unwrap());
|
||||
match axis {
|
||||
Some("xy") | None => Push::XY(state.from(arg0?)?.unwrap(), state.from(arg1?)?.unwrap(), cb),
|
||||
Some("x") => Push::X(state.from(arg0?)?.unwrap(), cb),
|
||||
Some("y") => Push::Y(state.from(arg0?)?.unwrap(), cb),
|
||||
frag => unimplemented!("push/{frag:?}")
|
||||
}
|
||||
}),
|
||||
|
||||
_ => return Ok(false)
|
||||
|
||||
};
|
||||
Ok(true)
|
||||
}
|
||||
|
|
|
|||
4
output/src/widget.rs
Normal file
4
output/src/widget.rs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
mod widget_border; pub use self::widget_border::*;
|
||||
mod widget_form; pub use self::widget_form::*;
|
||||
mod widget_style; pub use self::widget_style::*;
|
||||
mod widget_tryptich; pub use self::widget_tryptich::*;
|
||||
10
output/src/widget/widget_border.rs
Normal file
10
output/src/widget/widget_border.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
use crate::*;
|
||||
|
||||
pub struct Border<S>(pub bool, pub S);
|
||||
impl<O: Out, S: Layout<O>> Layout<O> for Border<S> {
|
||||
fn layout (&self, area: O::Area) -> O::Area {
|
||||
self.1.layout(area)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Bordered<S, W>(pub bool, pub S, pub W);
|
||||
55
output/src/widget/widget_form.rs
Normal file
55
output/src/widget/widget_form.rs
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
use crate::*;
|
||||
|
||||
pub struct FieldH<Theme, Label, Value>(pub Theme, pub Label, pub Value);
|
||||
impl<O: Out, T, L: Content<O>, V: Content<O>> HasContent<O> for FieldH<T, L, V> {
|
||||
fn content (&self) -> impl Content<O> { Bsp::e(&self.1, &self.2) }
|
||||
}
|
||||
impl<O: Out, T, L: Content<O>, V: Content<O>> Layout<O> for FieldH<T, L, V> {
|
||||
fn layout (&self, to: O::Area) -> O::Area { self.content().layout(to) }
|
||||
}
|
||||
impl<O: Out, T, L: Content<O>, V: Content<O>> Draw<O> for FieldH<T, L, V> {
|
||||
fn draw (&self, to: &mut O) { self.content().draw(to) }
|
||||
}
|
||||
|
||||
pub struct FieldV<Theme, Label, Value>(pub Theme, pub Label, pub Value);
|
||||
impl<O: Out, T, L: Content<O>, V: Content<O>> HasContent<O> for FieldV<T, L, V> {
|
||||
fn content (&self) -> impl Content<O> { Bsp::s(&self.1, &self.2) }
|
||||
}
|
||||
impl<O: Out, T, L: Content<O>, V: Content<O>> Layout<O> for FieldV<T, L, V> {
|
||||
fn layout (&self, to: O::Area) -> O::Area { self.content().layout(to) }
|
||||
}
|
||||
impl<O: Out, T, L: Content<O>, V: Content<O>> Draw<O> for FieldV<T, L, V> {
|
||||
fn draw (&self, to: &mut O) { self.content().draw(to) }
|
||||
}
|
||||
|
||||
// TODO:
|
||||
pub struct Field<C, T, U> {
|
||||
pub direction: Direction,
|
||||
pub label: Option<T>,
|
||||
pub label_fg: Option<C>,
|
||||
pub label_bg: Option<C>,
|
||||
pub label_align: Option<Direction>,
|
||||
pub value: Option<U>,
|
||||
pub value_fg: Option<C>,
|
||||
pub value_bg: Option<C>,
|
||||
pub value_align: Option<Direction>,
|
||||
}
|
||||
impl<C, T, U> Field<C, T, U> {
|
||||
pub fn new (direction: Direction) -> Field<C, (), ()> {
|
||||
Field::<C, (), ()> {
|
||||
direction,
|
||||
label: None, label_fg: None, label_bg: None, label_align: None,
|
||||
value: None, value_fg: None, value_bg: None, value_align: None,
|
||||
}
|
||||
}
|
||||
pub fn label <L> (
|
||||
self, label: Option<L>, align: Option<Direction>, fg: Option<C>, bg: Option<C>
|
||||
) -> Field<C, L, U> {
|
||||
Field::<C, L, U> { label, label_fg: fg, label_bg: bg, label_align: align, ..self }
|
||||
}
|
||||
pub fn value <V> (
|
||||
self, value: Option<V>, align: Option<Direction>, fg: Option<C>, bg: Option<C>
|
||||
) -> Field<C, T, V> {
|
||||
Field::<C, T, V> { value, value_fg: fg, value_bg: bg, value_align: align, ..self }
|
||||
}
|
||||
}
|
||||
15
output/src/widget/widget_style.rs
Normal file
15
output/src/widget/widget_style.rs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
#[allow(unused)] use crate::*;
|
||||
|
||||
pub struct Foreground<Color, Item>(pub Color, pub Item);
|
||||
impl<O: Out, Color, Item: Layout<O>> Layout<O> for Foreground<Color, Item> {
|
||||
fn layout (&self, to: O::Area) -> O::Area {
|
||||
self.1.layout(to)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Background<Color, Item>(pub Color, pub Item);
|
||||
impl<O: Out, Color, Item: Layout<O>> Layout<O> for Background<Color, Item> {
|
||||
fn layout (&self, to: O::Area) -> O::Area {
|
||||
self.1.layout(to)
|
||||
}
|
||||
}
|
||||
31
output/src/widget/widget_tryptich.rs
Normal file
31
output/src/widget/widget_tryptich.rs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
#[allow(unused)] use crate::*;
|
||||
|
||||
/// A three-column layout.
|
||||
pub struct Tryptich<A, B, C> {
|
||||
pub top: bool,
|
||||
pub h: u16,
|
||||
pub left: (u16, A),
|
||||
pub middle: (u16, B),
|
||||
pub right: (u16, C),
|
||||
}
|
||||
|
||||
impl Tryptich<(), (), ()> {
|
||||
pub fn center (h: u16) -> Self {
|
||||
Self { h, top: false, left: (0, ()), middle: (0, ()), right: (0, ()) }
|
||||
}
|
||||
pub fn top (h: u16) -> Self {
|
||||
Self { h, top: true, left: (0, ()), middle: (0, ()), right: (0, ()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, B, C> Tryptich<A, B, C> {
|
||||
pub fn left <D> (self, w: u16, content: D) -> Tryptich<D, B, C> {
|
||||
Tryptich { left: (w, content), ..self }
|
||||
}
|
||||
pub fn middle <D> (self, w: u16, content: D) -> Tryptich<A, D, C> {
|
||||
Tryptich { middle: (w, content), ..self }
|
||||
}
|
||||
pub fn right <D> (self, w: u16, content: D) -> Tryptich<A, B, D> {
|
||||
Tryptich { right: (w, content), ..self }
|
||||
}
|
||||
}
|
||||
18
proc/Cargo.toml
Normal file
18
proc/Cargo.toml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
[package]
|
||||
name = "tengri_proc"
|
||||
description = "UI metaframework, procedural macros."
|
||||
version = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
tengri_core = { path = "../core" }
|
||||
syn = { workspace = true }
|
||||
quote = { workspace = true }
|
||||
proc-macro2 = { workspace = true }
|
||||
heck = { workspace = true }
|
||||
|
||||
[target.'cfg(target_os = "linux")']
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||
243
proc/src/lib.rs
Normal file
243
proc/src/lib.rs
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
#![feature(str_as_str)]
|
||||
#![feature(box_patterns)]
|
||||
extern crate proc_macro;
|
||||
|
||||
pub(crate) use std::collections::BTreeMap;
|
||||
pub(crate) use std::cmp::Ordering;
|
||||
pub(crate) use std::sync::Arc;
|
||||
pub(crate) use proc_macro::TokenStream;
|
||||
pub(crate) use proc_macro2::{
|
||||
TokenStream as TokenStream2, Ident, Span, Punct, Group, Delimiter, Spacing::*
|
||||
};
|
||||
pub(crate) use syn::{
|
||||
parse_macro_input, ImplItem, ImplItemFn, LitStr, Type, TypePath,
|
||||
ItemImpl, ReturnType, Signature, FnArg, Pat, PatType, PatIdent,
|
||||
parse::{Parse, ParseStream, Result},
|
||||
};
|
||||
pub(crate) use quote::{quote, TokenStreamExt, ToTokens};
|
||||
pub(crate) use heck::{AsKebabCase, AsUpperCamelCase};
|
||||
|
||||
mod proc_view;
|
||||
mod proc_expose;
|
||||
mod proc_command;
|
||||
|
||||
#[cfg(test)] use syn::parse_quote as pq;
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn expose (meta: TokenStream, item: TokenStream) -> TokenStream {
|
||||
use self::proc_expose::{ExposeDef, ExposeMeta, ExposeImpl};
|
||||
write(ExposeDef(
|
||||
parse_macro_input!(meta as ExposeMeta),
|
||||
parse_macro_input!(item as ExposeImpl),
|
||||
))
|
||||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn command (meta: TokenStream, item: TokenStream) -> TokenStream {
|
||||
use self::proc_command::{CommandDef, CommandMeta, CommandImpl};
|
||||
write(CommandDef(
|
||||
parse_macro_input!(meta as CommandMeta),
|
||||
parse_macro_input!(item as CommandImpl),
|
||||
))
|
||||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn view (meta: TokenStream, item: TokenStream) -> TokenStream {
|
||||
use self::proc_view::{ViewDef, ViewMeta, ViewImpl};
|
||||
write(ViewDef(
|
||||
parse_macro_input!(meta as ViewMeta),
|
||||
parse_macro_input!(item as ViewImpl),
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn write <T: ToTokens> (t: T) -> TokenStream {
|
||||
let mut out = TokenStream2::new();
|
||||
t.to_tokens(&mut out);
|
||||
out.into()
|
||||
}
|
||||
|
||||
pub(crate) fn write_quote (quote: TokenStream2) -> TokenStream2 {
|
||||
let mut out = TokenStream2::new();
|
||||
for token in quote {
|
||||
out.append(token);
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
pub(crate) fn write_quote_to (out: &mut TokenStream2, quote: TokenStream2) {
|
||||
for token in quote {
|
||||
out.append(token);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)] #[test] fn test_proc_view () {
|
||||
let x: crate::proc_view::ViewMeta = pq! { SomeOut };
|
||||
let output: Ident = pq! { SomeOut };
|
||||
assert_eq!(x.output, output);
|
||||
|
||||
// TODO
|
||||
let _x: crate::proc_view::ViewImpl = pq! {
|
||||
impl Foo {
|
||||
/// docstring1
|
||||
#[tengri::view(":view1")] #[bar] fn a_view () {}
|
||||
|
||||
#[baz]
|
||||
/// docstring2
|
||||
#[baz] fn is_not_view () {}
|
||||
}
|
||||
};
|
||||
let _expected_target: Ident = pq! { Foo };
|
||||
//assert_eq!(x.target, expected_target);
|
||||
//assert_eq!(x.items.len(), 2);
|
||||
//assert_eq!(x.items[0].item, pq! {
|
||||
///// docstring1
|
||||
//#[bar] fn a_view () {}
|
||||
//});
|
||||
//assert_eq!(x.items[1].item, pq! {
|
||||
//#[baz]
|
||||
///// docstring2
|
||||
//#[baz] fn is_not_view () {}
|
||||
//});
|
||||
//assert_eq!(x.syms, vec![
|
||||
//ViewArm( { symbol: pq! { ":view1" }, name: pq! { a_view }, },
|
||||
//]);
|
||||
// FIXME
|
||||
//let parsed: ViewDefinition = pq! {
|
||||
//#[tengri_proc::view(SomeOut)]
|
||||
//impl SomeView {
|
||||
//#[tengri::view(":view-1")]
|
||||
//fn view_1 (&self) -> impl Content<SomeOut> + use<'_> {
|
||||
//"view-1"
|
||||
//}
|
||||
//}
|
||||
//};
|
||||
//let written = quote! { #parsed };
|
||||
//assert_eq!(format!("{written}"), format!("{}", quote! {
|
||||
//impl SomeView {
|
||||
//fn view_1 (&self) -> impl Content<SomeOut> + use<'_> {
|
||||
//"view-1"
|
||||
//}
|
||||
//}
|
||||
///// Generated by [tengri_proc].
|
||||
//impl ::tengri::output::Content<SomeOut> for SomeView {
|
||||
//fn content (&self) -> impl Content<SomeOut> {
|
||||
//self.size.of(::tengri::output::View(self, self.config.view))
|
||||
//}
|
||||
//}
|
||||
///// Generated by [tengri_proc].
|
||||
//impl<'a> ::tengri::dsl::ViewContext<'a, SomeOut> for SomeView {
|
||||
//fn get_content_sym (&'a self, value: &Value<'a>) -> Option<DrawBox<'a, SomeOut>> {
|
||||
//match value {
|
||||
//::tengri::dsl::Value::Sym(":view-1") => self.view_1().boxed(),
|
||||
//_ => panic!("expected Sym(content), got: {value:?}")
|
||||
//}
|
||||
//}
|
||||
//}
|
||||
//}));
|
||||
}
|
||||
|
||||
//#[cfg(test)] #[test] fn test_expose_definition () {
|
||||
// TODO
|
||||
//let parsed: ExposeImpl = pq! {
|
||||
////#[tengri_proc::expose]
|
||||
//impl Something {
|
||||
//fn something () -> bool {}
|
||||
//}
|
||||
//};
|
||||
//// FIXME:
|
||||
////assert_eq!(
|
||||
////format!("{}", quote! { #parsed }),
|
||||
////format!("{}", quote! {
|
||||
////impl Something {
|
||||
////fn something () {}
|
||||
////}
|
||||
////impl ::tengri::Context<bool> for Something {
|
||||
////fn get (&self, dsl: &::tengri::Value) -> Option<bool> {
|
||||
////Some(match dsl {
|
||||
////::tengri::Value::Sym(":true") => true,
|
||||
////::tengri::Value::Sym(":false") => false,
|
||||
////::tengri::Value::Sym(":bool1") => true || false,
|
||||
////_ => return None
|
||||
////})
|
||||
////}
|
||||
////}
|
||||
////})
|
||||
////);
|
||||
|
||||
//let parsed: ExposeImpl = pq! {
|
||||
////#[tengri_proc::expose]
|
||||
//impl Something {
|
||||
//#[tengri::expose(bool)] {
|
||||
//":bool1" => true || false,
|
||||
//}
|
||||
//#[tengri::expose(u16)] {
|
||||
//":u161" => 0 + 1,
|
||||
//}
|
||||
//#[tengri::expose(usize)] {
|
||||
//":usize1" => 1 + 2,
|
||||
//}
|
||||
//#[tengri::expose(Arc<str>)] {
|
||||
//":arcstr1" => "foo".into(),
|
||||
//}
|
||||
//#[tengri::expose(Option<Arc<str>>)] {
|
||||
//":optarcstr1" => Some("bar".into()),
|
||||
//":optarcstr2" => Some("baz".into()),
|
||||
//}
|
||||
//fn something () {}
|
||||
//}
|
||||
//};
|
||||
//// FIXME:
|
||||
////assert_eq!(
|
||||
////format!("{}", quote! { #parsed }),
|
||||
////format!("{}", quote! {
|
||||
////impl Something {
|
||||
////fn something () {}
|
||||
////}
|
||||
////impl ::tengri::Context<Arc<str>> for Something {
|
||||
////fn get (&self, dsl: &::tengri::Value) -> Option<Arc<str>> {
|
||||
////Some(match dsl {
|
||||
////::tengri::Value::Sym(":arcstr1") => "foo".into(),
|
||||
////_ => return None
|
||||
////})
|
||||
////}
|
||||
////}
|
||||
////impl ::tengri::Context<Option<Arc<str>>> for Something {
|
||||
////fn get (&self, dsl: &::tengri::Value) -> Option<Option<Arc<str>>> {
|
||||
////Some(match dsl {
|
||||
////::tengri::Value::Sym(":optarcstr1") => Some("bar".into()),
|
||||
////::tengri::Value::Sym(":optarcstr2") => Some("baz".into()),
|
||||
////_ => return None
|
||||
////})
|
||||
////}
|
||||
////}
|
||||
////impl ::tengri::Context<bool> for Something {
|
||||
////fn get (&self, dsl: &::tengri::Value) -> Option<bool> {
|
||||
////Some(match dsl {
|
||||
////::tengri::Value::Sym(":true") => true,
|
||||
////::tengri::Value::Sym(":false") => false,
|
||||
////::tengri::Value::Sym(":bool1") => true || false,
|
||||
////_ => return None
|
||||
////})
|
||||
////}
|
||||
////}
|
||||
////impl ::tengri::Context<u16> for Something {
|
||||
////fn get (&self, dsl: &::tengri::Value) -> Option<u16> {
|
||||
////Some(match dsl {
|
||||
////::tengri::Value::Num(n) => *n as u16,
|
||||
////::tengri::Value::Sym(":u161") => 0 + 1,
|
||||
////_ => return None
|
||||
////})
|
||||
////}
|
||||
////}
|
||||
////impl ::tengri::Context<usize> for Something {
|
||||
////fn get (&self, dsl: &::tengri::Value) -> Option<usize> {
|
||||
////Some(match dsl {
|
||||
////::tengri::Value::Num(n) => *n as usize,
|
||||
////::tengri::Value::Sym(":usize1") => 1 + 2,
|
||||
////_ => return None
|
||||
////})
|
||||
////}
|
||||
////}
|
||||
////})
|
||||
////)
|
||||
//}
|
||||
212
proc/src/proc_command.rs
Normal file
212
proc/src/proc_command.rs
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
use crate::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct CommandDef(pub(crate) CommandMeta, pub(crate) CommandImpl);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct CommandMeta(TypePath);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct CommandImpl(ItemImpl, BTreeMap<Arc<str>, CommandArm>);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct CommandArm(Ident, Vec<FnArg>, #[allow(unused)] ReturnType);
|
||||
|
||||
impl Parse for CommandMeta {
|
||||
fn parse (input: ParseStream) -> Result<Self> {
|
||||
Ok(Self(input.parse()?))
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for CommandImpl {
|
||||
fn parse (input: ParseStream) -> Result<Self> {
|
||||
let block = input.parse::<ItemImpl>()?;
|
||||
let exposed = Self::collect(&block.items).map_err(|e|input.error(e))?;
|
||||
Ok(Self(block, exposed))
|
||||
}
|
||||
}
|
||||
|
||||
impl CommandImpl {
|
||||
fn collect (items: &Vec<ImplItem>)
|
||||
-> std::result::Result<BTreeMap<Arc<str>, CommandArm>, String>
|
||||
{
|
||||
let mut exposed: BTreeMap<Arc<str>, CommandArm> = Default::default();
|
||||
for item in items.iter() {
|
||||
if let ImplItem::Fn(
|
||||
ImplItemFn { sig: Signature { ident, inputs, output, .. }, .. }
|
||||
) = item {
|
||||
let key = CommandArm::ident_to_key(&ident);
|
||||
if exposed.contains_key(&key) {
|
||||
return Err(format!("already defined: {ident}"));
|
||||
}
|
||||
exposed.insert(key, CommandArm(
|
||||
ident.clone(),
|
||||
inputs.iter().map(|x|x.clone()).collect(),
|
||||
output.clone(),
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(exposed)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for CommandDef {
|
||||
fn to_tokens (&self, out: &mut TokenStream2) {
|
||||
let Self(CommandMeta(state), CommandImpl(block, exposed)) = self;
|
||||
|
||||
let command_enum = &block.self_ty;
|
||||
|
||||
let variants = exposed.values().map(|arm|{
|
||||
let mut out = TokenStream2::new();
|
||||
out.append(arm.to_enum_variant_ident());
|
||||
//let ident = &arm.0;
|
||||
if arm.has_args() {
|
||||
out.append(Group::new(Delimiter::Brace, {
|
||||
let mut out = TokenStream2::new();
|
||||
for (arg, ty) in arm.args() {
|
||||
write_quote_to(&mut out, quote! { #arg : #ty , });
|
||||
}
|
||||
out
|
||||
}));
|
||||
}
|
||||
out.append(Punct::new(',', Alone));
|
||||
out
|
||||
});
|
||||
|
||||
let _matchers = exposed.values().map(|arm|{
|
||||
let key = LitStr::new(&arm.to_key(), Span::call_site());
|
||||
let variant = {
|
||||
let mut out = TokenStream2::new();
|
||||
out.append(arm.to_enum_variant_ident());
|
||||
let _ident = &arm.0;
|
||||
if arm.has_args() {
|
||||
out.append(Group::new(Delimiter::Brace, {
|
||||
let mut out = TokenStream2::new();
|
||||
for (arg, _ty) in arm.args() {
|
||||
write_quote_to(&mut out, quote! {
|
||||
#arg: FromDsl::from_dsl(self, words, ||"command error")?,
|
||||
});
|
||||
}
|
||||
out
|
||||
}));
|
||||
}
|
||||
out
|
||||
};
|
||||
write_quote(quote! {
|
||||
Some(::tengri::dsl::Token { value: ::tengri::dsl::Val::Key(#key), .. }) => {
|
||||
let mut words = words.clone();
|
||||
Some(#command_enum::#variant)
|
||||
},
|
||||
//Some(::tengri::dsl::Token {
|
||||
//value: ::tengri::dsl::Value::Key(#key), ..
|
||||
//}) => {
|
||||
//let mut iter = iter.clone(); Some(#command_enum::#variant)
|
||||
//},
|
||||
})
|
||||
});
|
||||
|
||||
let implementations = exposed.values().map(|arm|{
|
||||
let ident = &arm.0;
|
||||
let variant = {
|
||||
let mut out = TokenStream2::new();
|
||||
out.append(arm.to_enum_variant_ident());
|
||||
//let ident = &arm.0;
|
||||
if arm.has_args() {
|
||||
out.append(Group::new(Delimiter::Brace, {
|
||||
let mut out = TokenStream2::new();
|
||||
for (arg, _ty) in arm.args() {
|
||||
write_quote_to(&mut out, quote! { #arg , });
|
||||
}
|
||||
out
|
||||
}));
|
||||
}
|
||||
out
|
||||
};
|
||||
let give_rest = write_quote(quote! { /*TODO*/ });
|
||||
let give_args = arm.args()
|
||||
.map(|(arg, _ty)|write_quote(quote! { #arg, }))
|
||||
.collect::<Vec<_>>();
|
||||
write_quote(quote! {
|
||||
#command_enum::#variant =>
|
||||
#command_enum::#ident(state, #(#give_args)* #give_rest),
|
||||
})
|
||||
});
|
||||
|
||||
write_quote_to(out, quote! {
|
||||
/// Generated by [tengri_proc].
|
||||
#[derive(Clone, Debug)] pub enum #command_enum { #(#variants)* }
|
||||
/// Not generated by [tengri_proc].
|
||||
#block
|
||||
/// Generated by [tengri_proc::command].
|
||||
///
|
||||
/// Means [#command_enum] is now a [Command] over [#state].
|
||||
/// Instances of [#command_enum] can be consumed by a
|
||||
/// mutable pointer to [#state], invoking predefined operations
|
||||
/// and optionally returning undo history data.
|
||||
impl ::tengri::input::Command<#state> for #command_enum {
|
||||
fn execute (&self, state: &mut #state) -> Perhaps<Self> {
|
||||
match self { #(#implementations)* }
|
||||
}
|
||||
}
|
||||
/// Generated by [tengri_proc::command].
|
||||
impl ::tengri::dsl::FromDsl<#state> for #command_enum {
|
||||
fn from_dsl (state: &#state, value: &impl ::tengri::dsl::Dsl)
|
||||
-> Perhaps<Self>
|
||||
{
|
||||
todo!()//Ok(match token { #(#matchers)* _ => None })
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//if exposed.len() > 0 {
|
||||
//panic!("{:#?}", block.self_ty);
|
||||
//if let Type::Path(ref path) = *block.self_ty {
|
||||
//if path.path.segments.get(0).unwrap().ident == "TekCommand" {
|
||||
//panic!("\n{}", quote! {#out});
|
||||
//}
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
impl CommandArm {
|
||||
fn to_key (&self) -> Arc<str> {
|
||||
Self::ident_to_key(&self.0)
|
||||
}
|
||||
fn to_enum_variant_ident (&self) -> Ident {
|
||||
Ident::new(&Self::ident_to_enum_variant(&self.0), Span::call_site())
|
||||
}
|
||||
fn ident_to_key (ident: &Ident) -> Arc<str> {
|
||||
format!("{}", AsKebabCase(format!("{ident}"))).into()
|
||||
}
|
||||
fn ident_to_enum_variant (ident: &Ident) -> Arc<str> {
|
||||
format!("{}", AsUpperCamelCase(format!("{ident}"))).into()
|
||||
}
|
||||
fn has_args (&self) -> bool {
|
||||
self.1.len() > 1
|
||||
}
|
||||
fn args (&self) -> impl Iterator<Item = (&Ident, &Box<Type>)> {
|
||||
self.1.iter().skip(1).filter_map(|arg|if let FnArg::Typed(PatType {
|
||||
ty, pat: box Pat::Ident(PatIdent { ident: arg, .. }), ..
|
||||
}) = arg {
|
||||
Some((arg, ty))
|
||||
} else {
|
||||
unreachable!("only typed args should be present at this position");
|
||||
})
|
||||
}
|
||||
fn _to_enum_variant_def (&self) -> TokenStream2 {
|
||||
let mut out = TokenStream2::new();
|
||||
out.append(self.to_enum_variant_ident());
|
||||
//let ident = &self.0;
|
||||
if self.has_args() {
|
||||
out.append(Group::new(Delimiter::Brace, {
|
||||
let mut out = TokenStream2::new();
|
||||
for (arg, ty) in self.args() {
|
||||
write_quote_to(&mut out, quote! { #arg : #ty , });
|
||||
}
|
||||
out
|
||||
}));
|
||||
}
|
||||
out.append(Punct::new(',', Alone));
|
||||
out
|
||||
}
|
||||
}
|
||||
232
proc/src/proc_expose.rs
Normal file
232
proc/src/proc_expose.rs
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
use crate::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct ExposeDef(pub(crate) ExposeMeta, pub(crate) ExposeImpl);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct ExposeMeta;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct ExposeImpl(ItemImpl, BTreeMap<ExposeType, BTreeMap<String, Ident>>);
|
||||
|
||||
//#[derive(Debug, Clone)]
|
||||
//struct ExposeSym(LitStr);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct ExposeType(Box<Type>);
|
||||
|
||||
impl Parse for ExposeMeta {
|
||||
fn parse (_input: ParseStream) -> Result<Self> {
|
||||
Ok(Self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for ExposeImpl {
|
||||
fn parse (input: ParseStream) -> Result<Self> {
|
||||
let block = input.parse::<ItemImpl>()?;
|
||||
let mut exposed: BTreeMap<ExposeType, BTreeMap<String, Ident>> = Default::default();
|
||||
for item in block.items.iter() {
|
||||
if let ImplItem::Fn(ImplItemFn { sig: Signature { ident, output, .. }, .. }) = item {
|
||||
if let ReturnType::Type(_, return_type) = output {
|
||||
let return_type = ExposeType(return_type.clone());
|
||||
if !exposed.contains_key(&return_type) {
|
||||
exposed.insert(return_type.clone(), Default::default());
|
||||
}
|
||||
let values = exposed.get_mut(&return_type).unwrap();
|
||||
let key = format!(":{}", AsKebabCase(format!("{}", &ident)));
|
||||
if values.contains_key(&key) {
|
||||
return Err(input.error(format!("already defined: {key}")))
|
||||
}
|
||||
values.insert(key, ident.clone());
|
||||
} else {
|
||||
return Err(input.error("output type must be specified"))
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Self(block, exposed))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for ExposeDef {
|
||||
fn to_tokens (&self, out: &mut TokenStream2) {
|
||||
let Self(_meta, data) = self;
|
||||
write_quote_to(out, quote! { #data });
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for ExposeImpl {
|
||||
fn to_tokens (&self, out: &mut TokenStream2) {
|
||||
let Self(block, exposed) = self;
|
||||
write_quote_to(out, quote! { #block });
|
||||
for (t, variants) in exposed.iter() {
|
||||
self.expose_variants(out, t, variants);
|
||||
}
|
||||
if exposed.len() > 0 {
|
||||
//panic!("{}", quote! {#out});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_num (t: &impl AsRef<str>) -> bool {
|
||||
return matches!(t.as_ref(),
|
||||
| "u8" | "u16" | "u32" | "u64" | "usize"
|
||||
| "i8" | "i16" | "i32" | "i64" | "isize") }
|
||||
|
||||
impl ExposeImpl {
|
||||
fn expose_variants (
|
||||
&self, out: &mut TokenStream2, t: &ExposeType, variants: &BTreeMap<String, Ident>
|
||||
) {
|
||||
let Self(ItemImpl { self_ty: state, .. }, ..) = self;
|
||||
let type_is = format!("{}", quote! { #t });
|
||||
let variants = variants.iter().map(|(key, value)|{
|
||||
let key = LitStr::new(&key, Span::call_site());
|
||||
quote! { Some(#key) => Ok(Some(state.#value())), }
|
||||
});
|
||||
write_quote_to(out, if &type_is == "bool" {
|
||||
quote! {
|
||||
/// Generated by [tengri_proc::expose].
|
||||
impl ::tengri::dsl::FromDsl<#state> for #t {
|
||||
fn from_dsl (state: &#state, dsl: &impl Dsl) -> Perhaps<Self> {
|
||||
match dsl.word()? {
|
||||
Some("true") => Ok(Some(true)),
|
||||
Some("false") => Ok(Some(false)),
|
||||
_ => match dsl.word()? { #(#variants)* _ => Ok(None) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if is_num(&type_is) {
|
||||
quote! {}
|
||||
//quote! {
|
||||
///// Generated by [tengri_proc::expose].
|
||||
//impl ::tengri::dsl::FromDsl<#state> for #t {
|
||||
//fn from_dsl (state: &#state, dsl: &impl Dsl) -> Perhaps<Self> {
|
||||
//match dsl.num()? {
|
||||
//Some(n) => Ok(Some(n.parse::<#t>()?)),
|
||||
//_ => match dsl.word()? { #(#variants)* _ => Ok(None) }
|
||||
//}
|
||||
//}
|
||||
//}
|
||||
//}
|
||||
} else {
|
||||
quote! {
|
||||
/// Generated by [tengri_proc::expose].
|
||||
impl ::tengri::dsl::FromDsl<#state> for #t {
|
||||
fn from_dsl (state: &#state, dsl: &impl Dsl) -> Perhaps<Self> {
|
||||
match dsl.word()? { #(#variants)* _ => Ok(None) }
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
//let arms = variants.iter().map(|(key, value)|{
|
||||
//let key = LitStr::new(&key, Span::call_site());
|
||||
//quote! { #key => state.#value(), }
|
||||
//});
|
||||
//if &type_is == "bool" {
|
||||
//return quote! {
|
||||
//::tengri::dsl::Val::Sym(s) => match s.as_ref() {
|
||||
//":true" => true,
|
||||
//":false" => false,
|
||||
//#variants
|
||||
//_ => { return Ok(None) }
|
||||
//},
|
||||
//}
|
||||
//}
|
||||
//if matches!(type_is.as_str(),
|
||||
//"u8" | "u16" | "u32" | "u64" | "usize" |
|
||||
//"i8" | "i16" | "i32" | "i64" | "isize")
|
||||
//{
|
||||
//let num_err = LitStr::new(
|
||||
//&format!("{{n}}: failed to convert to {type_is}"),
|
||||
//Span::call_site()
|
||||
//);
|
||||
//return quote! {
|
||||
//::tengri::dsl::Val::Num(n) => TryInto::<#t>::try_into(n)
|
||||
//.unwrap_or_else(|_|panic!(#num_err)),
|
||||
//::tengri::dsl::Val::Sym(s) => match s.as_ref() {
|
||||
//#variants
|
||||
//_ => { return Ok(None) }
|
||||
//},
|
||||
//}
|
||||
//}
|
||||
//let arms = quote! {
|
||||
//::tengri::dsl::Val::Sym(s) => match s.as_ref() {
|
||||
//#variants
|
||||
//_ => { return Ok(None) }
|
||||
//},
|
||||
//};
|
||||
//let builtins =
|
||||
//write_quote_to(out, quote! {
|
||||
///// Generated by [tengri_proc::expose].
|
||||
//impl ::tengri::dsl::FromDsl<#state> for #t {
|
||||
//fn from_dsl (state: &#state, dsl: &impl Dsl) -> Perhaps<Self> {
|
||||
//$builtins
|
||||
//$defined
|
||||
//Ok(None)
|
||||
//}
|
||||
//}
|
||||
//});
|
||||
}
|
||||
}
|
||||
|
||||
//impl From<LitStr> for ExposeSym { fn from (this: LitStr) -> Self { Self(this) } }
|
||||
|
||||
//impl PartialOrd for ExposeSym {
|
||||
//fn partial_cmp (&self, other: &Self) -> Option<Ordering> {
|
||||
//let this = &self.0;
|
||||
//let that = &other.0;
|
||||
//Some(format!("{}", quote! { #this }).cmp(&format!("{}", quote! { #that })))
|
||||
//}
|
||||
//}
|
||||
|
||||
//impl Ord for ExposeSym {
|
||||
//fn cmp (&self, other: &Self) -> Ordering {
|
||||
//let this = &self.0;
|
||||
//let that = &other.0;
|
||||
//format!("{}", quote! { #this }).cmp(&format!("{}", quote! { #that }))
|
||||
//}
|
||||
//}
|
||||
|
||||
//impl PartialEq for ExposeSym {
|
||||
//fn eq (&self, other: &Self) -> bool {
|
||||
//let this = &self.0;
|
||||
//let that = &other.0;
|
||||
//format!("{}", quote! { #this }) == format!("{}", quote! { #that })
|
||||
//}
|
||||
//}
|
||||
|
||||
//impl Eq for ExposeSym {}
|
||||
|
||||
impl From<Type> for ExposeType { fn from (this: Type) -> Self { Self(Box::new(this)) } }
|
||||
|
||||
impl PartialOrd for ExposeType {
|
||||
fn partial_cmp (&self, other: &Self) -> Option<Ordering> {
|
||||
let this = &self.0;
|
||||
let that = &other.0;
|
||||
Some(format!("{}", quote! { #this }).cmp(&format!("{}", quote! { #that })))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for ExposeType {
|
||||
fn cmp (&self, other: &Self) -> Ordering {
|
||||
let this = &self.0;
|
||||
let that = &other.0;
|
||||
format!("{}", quote! { #this }).cmp(&format!("{}", quote! { #that }))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for ExposeType {
|
||||
fn eq (&self, other: &Self) -> bool {
|
||||
let this = &self.0;
|
||||
let that = &other.0;
|
||||
format!("{}", quote! { #this }) == format!("{}", quote! { #that })
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for ExposeType {}
|
||||
|
||||
impl ToTokens for ExposeType {
|
||||
fn to_tokens (&self, out: &mut TokenStream2) {
|
||||
self.0.to_tokens(out)
|
||||
}
|
||||
}
|
||||
2
proc/src/proc_flex.rs
Normal file
2
proc/src/proc_flex.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
// TODO: #[derive(Flex, FlexMut, FlexOption, FlexResult ... ?)]
|
||||
// better derive + annotations
|
||||
136
proc/src/proc_view.rs
Normal file
136
proc/src/proc_view.rs
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
use crate::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct ViewDef(pub(crate) ViewMeta, pub(crate) ViewImpl);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct ViewMeta { pub(crate) output: Ident }
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct ViewImpl { block: ItemImpl, exposed: BTreeMap<String, Ident>, }
|
||||
|
||||
/// `#[view]` takes 1 arg ([Engine] implementation for which it's valid).
|
||||
impl Parse for ViewMeta {
|
||||
fn parse (input: ParseStream) -> Result<Self> {
|
||||
Ok(Self { output: input.parse::<Ident>()?, })
|
||||
}
|
||||
}
|
||||
|
||||
/// `#[view]` exposes each function as corresponding symbol.
|
||||
///
|
||||
/// Maybe it's best to genericize that pattern rather than have
|
||||
/// 3 whole things for the layers of the program.
|
||||
impl Parse for ViewImpl {
|
||||
fn parse (input: ParseStream) -> Result<Self> {
|
||||
let block = input.parse::<ItemImpl>()?;
|
||||
let mut exposed: BTreeMap<String, Ident> = Default::default();
|
||||
for item in block.items.iter() {
|
||||
if let ImplItem::Fn(ImplItemFn { sig: Signature { ident, inputs, .. }, .. }) = item
|
||||
&& inputs.len() == 1 {
|
||||
let key = format!(":{}", AsKebabCase(format!("{}", &ident)));
|
||||
if exposed.contains_key(&key) {
|
||||
return Err(input.error(format!("already defined: {ident}")));
|
||||
}
|
||||
exposed.insert(key, ident.clone());
|
||||
}
|
||||
}
|
||||
Ok(Self { block, exposed })
|
||||
}
|
||||
}
|
||||
|
||||
/// `#[view]`'s output is a generated block of symbol bindings.
|
||||
impl ToTokens for ViewDef {
|
||||
fn to_tokens (&self, out: &mut TokenStream2) {
|
||||
let Self(_, ViewImpl { block, .. }) = self;
|
||||
let generated = self.generated();
|
||||
write_quote_to(out, quote! {
|
||||
#block
|
||||
#generated
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ViewDef {
|
||||
fn generated (&self) -> impl ToTokens {
|
||||
let Self(ViewMeta { output }, ViewImpl { block, exposed }) = self;
|
||||
let self_ty = &block.self_ty;
|
||||
// Expressions are handled by built-in functions that operate over constants and symbols.
|
||||
let builtins = builtins_with_boxes_output(quote! { #output })
|
||||
.map(|(builtin, builtin_ty)|match builtin {
|
||||
Single(name) => quote! {
|
||||
if dsl.head()?.key()? == Some(#name) {
|
||||
return Ok(Some(#builtin_ty::from_dsl_or_else(self, dsl,
|
||||
||format!("failed to load builtin").into())?.boxed()))
|
||||
}
|
||||
},
|
||||
Prefix(name) => quote! {
|
||||
if let Some(key) = dsl.head()?.key()? && key.starts_with(#name) {
|
||||
return Ok(Some(#builtin_ty::from_dsl_or_else(self, dsl,
|
||||
||format!("failed to load builtin").into())?.boxed()))
|
||||
}
|
||||
},
|
||||
});
|
||||
// Symbols are handled by user-provided functions that take no parameters but `&self`.
|
||||
let exposed = exposed.iter().map(|(key, value)|write_quote(quote! {
|
||||
#key => return Ok(Some(self.#value().boxed())),
|
||||
}));
|
||||
quote! {
|
||||
/// Generated by [tengri_proc].
|
||||
///
|
||||
/// Makes [#self_ty] able to construct the [Draw]able
|
||||
/// which might correspond to a given [TokenStream],
|
||||
/// while taking [#self_ty]'s state into consideration.
|
||||
impl<'state> ::tengri::dsl::DslInto<Box<dyn ::tengri::output::Draw<#output> + 'state>> for #self_ty {
|
||||
fn dsl_into (&self, dsl: &impl ::tengri::dsl::Dsl)
|
||||
-> Perhaps<Box<dyn ::tengri::output::Draw<#output> + 'state>>
|
||||
{
|
||||
#(#builtins)*
|
||||
if let Some(sym) = dsl.sym()? {
|
||||
match sym {
|
||||
#(#exposed)*
|
||||
_ => return Err(format!("unknown symbol {sym}").into())
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum Builtin {
|
||||
Single(TokenStream2),
|
||||
Prefix(TokenStream2),
|
||||
}
|
||||
use Builtin::*;
|
||||
|
||||
fn builtins_with (n: TokenStream2, c: TokenStream2) -> impl Iterator<Item=(Builtin, TokenStream2)> {
|
||||
[
|
||||
(Single(quote!("when")), quote!(When::< #c >)),
|
||||
(Single(quote!("either")), quote!(Either::< #c, #c>)),
|
||||
(Prefix(quote!("align/")), quote!(Align::< #c >)),
|
||||
(Prefix(quote!("bsp/")), quote!(Bsp::< #c, #c>)),
|
||||
(Prefix(quote!("fill/")), quote!(Fill::< #c >)),
|
||||
(Prefix(quote!("fixed/")), quote!(Fixed::<#n, #c >)),
|
||||
(Prefix(quote!("min/")), quote!(Min::<#n, #c >)),
|
||||
(Prefix(quote!("max/")), quote!(Max::<#n, #c >)),
|
||||
(Prefix(quote!("shrink/")), quote!(Shrink::<#n, #c >)),
|
||||
(Prefix(quote!("expand/")), quote!(Expand::<#n, #c >)),
|
||||
(Prefix(quote!("push/")), quote!(Push::<#n, #c >)),
|
||||
(Prefix(quote!("pull/")), quote!(Pull::<#n, #c >)),
|
||||
(Prefix(quote!("margin/")), quote!(Margin::<#n, #c >)),
|
||||
(Prefix(quote!("padding/")), quote!(Padding::<#n, #c >)),
|
||||
].into_iter()
|
||||
}
|
||||
|
||||
fn _builtins_with_holes () -> impl Iterator<Item=(Builtin, TokenStream2)> {
|
||||
builtins_with(quote! { _ }, quote! { _ })
|
||||
}
|
||||
|
||||
fn _builtins_with_boxes () -> impl Iterator<Item=(Builtin, TokenStream2)> {
|
||||
builtins_with(quote! { _ }, quote! { Box<dyn Draw<_>> })
|
||||
}
|
||||
|
||||
fn builtins_with_boxes_output (o: TokenStream2) -> impl Iterator<Item=(Builtin, TokenStream2)> {
|
||||
builtins_with(quote! { _ }, quote! { Box<dyn Draw<#o>> })
|
||||
}
|
||||
|
|
@ -2,10 +2,10 @@
|
|||
{pkgs?import<nixpkgs>{}}:let
|
||||
stdenv = pkgs.clang19Stdenv;
|
||||
name = "tengri";
|
||||
nativeBuildInputs = with pkgs; [ pkg-config libclang ];
|
||||
buildInputs = with pkgs; [ libclang ];
|
||||
nativeBuildInputs = [ pkgs.pkg-config pkgs.libclang pkgs.mold ];
|
||||
buildInputs = [ pkgs.libclang ];
|
||||
LIBCLANG_PATH = "${pkgs.libclang.lib.outPath}/lib";
|
||||
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath (with pkgs; []);
|
||||
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [];
|
||||
in pkgs.mkShell.override {
|
||||
inherit stdenv;
|
||||
} {
|
||||
|
|
|
|||
283
tengri.svg
Normal file
283
tengri.svg
Normal file
|
|
@ -0,0 +1,283 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="100%" height="100%" viewBox="0 0 1000 600" preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="lineGradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" style="stop-color:#4CAF50;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#2196F3;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
|
||||
<feDropShadow dx="2" dy="2" stdDeviation="3" flood-color="#000000" flood-opacity="0.3"/>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<!-- Background -->
|
||||
<rect width="1000" height="600" fill="#1a1a1a"/>
|
||||
|
||||
<!-- Title -->
|
||||
<text x="500" y="50" text-anchor="middle" font-family="Arial, sans-serif" font-size="28" font-weight="bold" fill="#ffffff">
|
||||
Daily Timestamp Heatmap
|
||||
</text>
|
||||
|
||||
<!-- Chart area -->
|
||||
<rect x="80" y="80" width="840" height="440" fill="#2d2d2d" stroke="#404040" stroke-width="2" rx="8"/>
|
||||
|
||||
<!-- Y-axis (hours) -->
|
||||
<line x1="80" y1="80" x2="80" y2="520" stroke="#666" stroke-width="2"/>
|
||||
|
||||
<!-- X-axis (timeline) -->
|
||||
<line x1="80" y1="520" x2="920" y2="520" stroke="#666" stroke-width="2"/>
|
||||
|
||||
<!-- Monday highlight for 03/03 -->
|
||||
<rect x="84" y="80" width="4" height="440" fill="#3a3a3a" opacity="0.3"/>
|
||||
<line x1="84" y1="80" x2="84" y2="520" stroke="#4a9eff" stroke-width="2" opacity="0.8"/>
|
||||
<!-- Monday highlight for 03/10 -->
|
||||
<rect x="119" y="80" width="4" height="440" fill="#3a3a3a" opacity="0.3"/>
|
||||
<line x1="119" y1="80" x2="119" y2="520" stroke="#4a9eff" stroke-width="2" opacity="0.8"/>
|
||||
<!-- Monday highlight for 03/17 -->
|
||||
<rect x="153" y="80" width="4" height="440" fill="#3a3a3a" opacity="0.3"/>
|
||||
<line x1="153" y1="80" x2="153" y2="520" stroke="#4a9eff" stroke-width="2" opacity="0.8"/>
|
||||
<!-- Monday highlight for 03/24 -->
|
||||
<rect x="187" y="80" width="4" height="440" fill="#3a3a3a" opacity="0.3"/>
|
||||
<line x1="187" y1="80" x2="187" y2="520" stroke="#4a9eff" stroke-width="2" opacity="0.8"/>
|
||||
<!-- Monday highlight for 03/31 -->
|
||||
<rect x="221" y="80" width="4" height="440" fill="#3a3a3a" opacity="0.3"/>
|
||||
<line x1="221" y1="80" x2="221" y2="520" stroke="#4a9eff" stroke-width="2" opacity="0.8"/>
|
||||
<!-- Monday highlight for 04/07 -->
|
||||
<rect x="255" y="80" width="4" height="440" fill="#3a3a3a" opacity="0.3"/>
|
||||
<line x1="255" y1="80" x2="255" y2="520" stroke="#4a9eff" stroke-width="2" opacity="0.8"/>
|
||||
<!-- Monday highlight for 04/14 -->
|
||||
<rect x="290" y="80" width="4" height="440" fill="#3a3a3a" opacity="0.3"/>
|
||||
<line x1="290" y1="80" x2="290" y2="520" stroke="#4a9eff" stroke-width="2" opacity="0.8"/>
|
||||
<!-- Monday highlight for 04/21 -->
|
||||
<rect x="324" y="80" width="4" height="440" fill="#3a3a3a" opacity="0.3"/>
|
||||
<line x1="324" y1="80" x2="324" y2="520" stroke="#4a9eff" stroke-width="2" opacity="0.8"/>
|
||||
<!-- Monday highlight for 04/28 -->
|
||||
<rect x="358" y="80" width="4" height="440" fill="#3a3a3a" opacity="0.3"/>
|
||||
<line x1="358" y1="80" x2="358" y2="520" stroke="#4a9eff" stroke-width="2" opacity="0.8"/>
|
||||
<!-- Monday highlight for 05/05 -->
|
||||
<rect x="392" y="80" width="4" height="440" fill="#3a3a3a" opacity="0.3"/>
|
||||
<line x1="392" y1="80" x2="392" y2="520" stroke="#4a9eff" stroke-width="2" opacity="0.8"/>
|
||||
<!-- Monday highlight for 05/12 -->
|
||||
<rect x="426" y="80" width="4" height="440" fill="#3a3a3a" opacity="0.3"/>
|
||||
<line x1="426" y1="80" x2="426" y2="520" stroke="#4a9eff" stroke-width="2" opacity="0.8"/>
|
||||
<!-- Monday highlight for 05/19 -->
|
||||
<rect x="460" y="80" width="4" height="440" fill="#3a3a3a" opacity="0.3"/>
|
||||
<line x1="460" y1="80" x2="460" y2="520" stroke="#4a9eff" stroke-width="2" opacity="0.8"/>
|
||||
<!-- Monday highlight for 05/26 -->
|
||||
<rect x="495" y="80" width="4" height="440" fill="#3a3a3a" opacity="0.3"/>
|
||||
<line x1="495" y1="80" x2="495" y2="520" stroke="#4a9eff" stroke-width="2" opacity="0.8"/>
|
||||
<!-- Monday highlight for 06/02 -->
|
||||
<rect x="529" y="80" width="4" height="440" fill="#3a3a3a" opacity="0.3"/>
|
||||
<line x1="529" y1="80" x2="529" y2="520" stroke="#4a9eff" stroke-width="2" opacity="0.8"/>
|
||||
<!-- Monday highlight for 06/09 -->
|
||||
<rect x="563" y="80" width="4" height="440" fill="#3a3a3a" opacity="0.3"/>
|
||||
<line x1="563" y1="80" x2="563" y2="520" stroke="#4a9eff" stroke-width="2" opacity="0.8"/>
|
||||
<!-- Monday highlight for 06/16 -->
|
||||
<rect x="597" y="80" width="4" height="440" fill="#3a3a3a" opacity="0.3"/>
|
||||
<line x1="597" y1="80" x2="597" y2="520" stroke="#4a9eff" stroke-width="2" opacity="0.8"/>
|
||||
<!-- Monday highlight for 06/23 -->
|
||||
<rect x="631" y="80" width="4" height="440" fill="#3a3a3a" opacity="0.3"/>
|
||||
<line x1="631" y1="80" x2="631" y2="520" stroke="#4a9eff" stroke-width="2" opacity="0.8"/>
|
||||
<!-- Monday highlight for 06/30 -->
|
||||
<rect x="666" y="80" width="4" height="440" fill="#3a3a3a" opacity="0.3"/>
|
||||
<line x1="666" y1="80" x2="666" y2="520" stroke="#4a9eff" stroke-width="2" opacity="0.8"/>
|
||||
<!-- Monday highlight for 07/07 -->
|
||||
<rect x="700" y="80" width="4" height="440" fill="#3a3a3a" opacity="0.3"/>
|
||||
<line x1="700" y1="80" x2="700" y2="520" stroke="#4a9eff" stroke-width="2" opacity="0.8"/>
|
||||
<!-- Monday highlight for 07/14 -->
|
||||
<rect x="734" y="80" width="4" height="440" fill="#3a3a3a" opacity="0.3"/>
|
||||
<line x1="734" y1="80" x2="734" y2="520" stroke="#4a9eff" stroke-width="2" opacity="0.8"/>
|
||||
<!-- Monday highlight for 07/21 -->
|
||||
<rect x="768" y="80" width="4" height="440" fill="#3a3a3a" opacity="0.3"/>
|
||||
<line x1="768" y1="80" x2="768" y2="520" stroke="#4a9eff" stroke-width="2" opacity="0.8"/>
|
||||
<!-- Monday highlight for 07/28 -->
|
||||
<rect x="802" y="80" width="4" height="440" fill="#3a3a3a" opacity="0.3"/>
|
||||
<line x1="802" y1="80" x2="802" y2="520" stroke="#4a9eff" stroke-width="2" opacity="0.8"/>
|
||||
<!-- Monday highlight for 08/04 -->
|
||||
<rect x="836" y="80" width="4" height="440" fill="#3a3a3a" opacity="0.3"/>
|
||||
<line x1="836" y1="80" x2="836" y2="520" stroke="#4a9eff" stroke-width="2" opacity="0.8"/>
|
||||
<!-- Monday highlight for 08/11 -->
|
||||
<rect x="871" y="80" width="4" height="440" fill="#3a3a3a" opacity="0.3"/>
|
||||
<line x1="871" y1="80" x2="871" y2="520" stroke="#4a9eff" stroke-width="2" opacity="0.8"/>
|
||||
<!-- Monday highlight for 08/18 -->
|
||||
<rect x="905" y="80" width="4" height="440" fill="#3a3a3a" opacity="0.3"/>
|
||||
<line x1="905" y1="80" x2="905" y2="520" stroke="#4a9eff" stroke-width="2" opacity="0.8"/>
|
||||
<text x="65" y="89" text-anchor="end" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">23:00</text>
|
||||
<text x="65" y="107" text-anchor="end" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">22:00</text>
|
||||
<text x="65" y="125" text-anchor="end" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">21:00</text>
|
||||
<text x="65" y="144" text-anchor="end" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">20:00</text>
|
||||
<text x="65" y="162" text-anchor="end" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">19:00</text>
|
||||
<text x="65" y="180" text-anchor="end" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">18:00</text>
|
||||
<text x="65" y="199" text-anchor="end" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">17:00</text>
|
||||
<text x="65" y="217" text-anchor="end" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">16:00</text>
|
||||
<text x="65" y="235" text-anchor="end" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">15:00</text>
|
||||
<text x="65" y="254" text-anchor="end" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">14:00</text>
|
||||
<text x="65" y="272" text-anchor="end" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">13:00</text>
|
||||
<text x="65" y="290" text-anchor="end" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">12:00</text>
|
||||
<text x="65" y="309" text-anchor="end" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">11:00</text>
|
||||
<text x="65" y="327" text-anchor="end" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">10:00</text>
|
||||
<text x="65" y="345" text-anchor="end" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">9:00</text>
|
||||
<text x="65" y="364" text-anchor="end" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">8:00</text>
|
||||
<text x="65" y="382" text-anchor="end" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">7:00</text>
|
||||
<text x="65" y="400" text-anchor="end" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">6:00</text>
|
||||
<text x="65" y="419" text-anchor="end" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">5:00</text>
|
||||
<text x="65" y="437" text-anchor="end" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">4:00</text>
|
||||
<text x="65" y="455" text-anchor="end" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">3:00</text>
|
||||
<text x="65" y="474" text-anchor="end" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">2:00</text>
|
||||
<text x="65" y="492" text-anchor="end" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">1:00</text>
|
||||
<text x="65" y="510" text-anchor="end" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">0:00</text>
|
||||
<text x="80" y="550" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">03/02</text>
|
||||
<text x="114" y="550" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">03/09</text>
|
||||
<text x="148" y="550" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">03/16</text>
|
||||
<text x="182" y="550" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">03/23</text>
|
||||
<text x="216" y="550" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">03/30</text>
|
||||
<text x="250" y="550" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">04/06</text>
|
||||
<text x="285" y="550" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">04/13</text>
|
||||
<text x="319" y="550" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">04/20</text>
|
||||
<text x="353" y="550" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">04/27</text>
|
||||
<text x="387" y="550" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">05/04</text>
|
||||
<text x="421" y="550" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">05/11</text>
|
||||
<text x="456" y="550" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">05/18</text>
|
||||
<text x="490" y="550" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">05/25</text>
|
||||
<text x="524" y="550" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">06/01</text>
|
||||
<text x="558" y="550" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">06/08</text>
|
||||
<text x="592" y="550" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">06/15</text>
|
||||
<text x="626" y="550" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">06/22</text>
|
||||
<text x="661" y="550" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">06/29</text>
|
||||
<text x="695" y="550" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">07/06</text>
|
||||
<text x="729" y="550" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">07/13</text>
|
||||
<text x="763" y="550" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">07/20</text>
|
||||
<text x="797" y="550" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">07/27</text>
|
||||
<text x="832" y="550" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">08/03</text>
|
||||
<text x="866" y="550" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">08/10</text>
|
||||
<text x="900" y="550" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#ffffff">08/17</text>
|
||||
<rect x="80" y="391" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="84" y="98" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="89" y="446" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="114" y="300" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="114" y="355" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="138" y="501" width="4" height="18" fill="#ffeb3b" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="148" y="80" width="4" height="18" fill="#ffeb3b" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="148" y="501" width="4" height="18" fill="#ff6b6b" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="153" y="98" width="4" height="18" fill="#ffa726" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="182" y="135" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="182" y="465" width="4" height="18" fill="#ffa726" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="236" y="80" width="4" height="18" fill="#ffa726" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="236" y="98" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="241" y="446" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="246" y="410" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="285" y="446" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="285" y="465" width="4" height="18" fill="#ffa726" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="290" y="410" width="4" height="18" fill="#ffa726" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="299" y="116" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="304" y="318" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="309" y="355" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="329" y="355" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="338" y="318" width="4" height="18" fill="#ffeb3b" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="338" y="336" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="338" y="428" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="338" y="465" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="343" y="428" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="343" y="465" width="4" height="18" fill="#ffeb3b" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="348" y="98" width="4" height="18" fill="#ffeb3b" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="348" y="116" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="348" y="373" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="348" y="465" width="4" height="18" fill="#ffa726" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="353" y="153" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="353" y="428" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="358" y="501" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="368" y="465" width="4" height="18" fill="#ffeb3b" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="368" y="501" width="4" height="18" fill="#ffeb3b" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="377" y="116" width="4" height="18" fill="#ffeb3b" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="377" y="391" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="377" y="410" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="377" y="428" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="387" y="428" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="392" y="80" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="397" y="300" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="397" y="336" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="397" y="465" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="402" y="318" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="402" y="391" width="4" height="18" fill="#ffeb3b" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="402" y="410" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="407" y="98" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="407" y="410" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="407" y="446" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="407" y="483" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="412" y="355" width="4" height="18" fill="#ff6b6b" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="412" y="428" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="412" y="446" width="4" height="18" fill="#ffeb3b" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="412" y="465" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="412" y="483" width="4" height="18" fill="#ffeb3b" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="412" y="501" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="431" y="80" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="431" y="336" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="431" y="391" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="431" y="446" width="4" height="18" fill="#ffa726" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="436" y="483" width="4" height="18" fill="#ffa726" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="441" y="501" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="446" y="171" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="446" y="263" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="446" y="391" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="451" y="80" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="451" y="410" width="4" height="18" fill="#ffeb3b" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="451" y="428" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="451" y="446" width="4" height="18" fill="#ffeb3b" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="451" y="465" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="456" y="80" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="456" y="116" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="460" y="373" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="460" y="410" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="465" y="80" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="465" y="98" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="465" y="116" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="465" y="318" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="465" y="336" width="4" height="18" fill="#ffa726" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="465" y="355" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="465" y="483" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="480" y="80" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="480" y="465" width="4" height="18" fill="#ffeb3b" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="485" y="281" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="485" y="501" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="490" y="98" width="4" height="18" fill="#ffeb3b" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="490" y="483" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="495" y="80" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="495" y="483" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="495" y="501" width="4" height="18" fill="#ffa726" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="553" y="153" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="578" y="281" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="578" y="428" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="578" y="465" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="583" y="373" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="617" y="318" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="622" y="245" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="734" y="483" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="734" y="501" width="4" height="18" fill="#ffeb3b" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="739" y="80" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="744" y="410" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="749" y="465" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="753" y="208" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="753" y="226" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="758" y="135" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="758" y="153" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="758" y="428" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="802" y="336" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="802" y="391" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="807" y="483" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="817" y="465" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="817" y="483" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="827" y="98" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="832" y="355" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="832" y="428" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="832" y="465" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="861" y="98" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="861" y="336" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="861" y="391" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="866" y="465" width="4" height="18" fill="#ffa726" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="871" y="318" width="4" height="18" fill="#ffeb3b" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="890" y="300" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="890" y="373" width="4" height="18" fill="#ffeb3b" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="890" y="465" width="4" height="18" fill="#ffeb3b" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="900" y="428" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
<rect x="920" y="135" width="4" height="18" fill="#66bb6a" opacity="0.9" stroke="#404040" stroke-width="0.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 26 KiB |
|
|
@ -4,15 +4,24 @@ edition = "2024"
|
|||
description = "UI metaframework."
|
||||
version = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
tengri_dsl = { optional = true, path = "../dsl" }
|
||||
tengri_input = { optional = true, path = "../input" }
|
||||
tengri_output = { optional = true, path = "../output" }
|
||||
tengri_tui = { optional = true, path = "../tui" }
|
||||
|
||||
[features]
|
||||
default = [ "input", "output", "tui" ]
|
||||
input = [ "tengri_input" ]
|
||||
output = [ "tengri_output" ]
|
||||
tui = [ "tengri_tui" ]
|
||||
dsl = [ "tengri_dsl", "tengri_input/dsl", "tengri_output/dsl", "tengri_tui/dsl" ]
|
||||
dsl = [ "tengri_dsl", "tengri_output/dsl", "tengri_tui/dsl" ]
|
||||
|
||||
[dependencies]
|
||||
tengri_core = { workspace = true }
|
||||
tengri_dsl = { optional = true, path = "../dsl" }
|
||||
tengri_input = { optional = true, path = "../input" }
|
||||
tengri_output = { optional = true, path = "../output" }
|
||||
tengri_tui = { optional = true, path = "../tui" }
|
||||
|
||||
[dev-dependencies]
|
||||
tengri_proc = { path = "../proc" }
|
||||
tengri = { path = ".", features = [ "dsl" ] }
|
||||
crossterm = { workspace = true }
|
||||
|
||||
[target.'cfg(target_os = "linux")']
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||
|
|
|
|||
|
|
@ -1,3 +1,17 @@
|
|||
# tengri
|
||||
***tengri*** is a metaframework for building interactive applications with rust. (aren't we all?)
|
||||
|
||||
an interface metaframework. currently supports ratatui.
|
||||
tengri is developed as part of [***tek***](https://codeberg.org/unspeaker/tek),
|
||||
a music program for terminals.
|
||||
|
||||
tengri contains:
|
||||
* [***dizzle***](./dsl), a framework for defining domain-specific languages
|
||||
* [***output***](./output), an abstract UI layout framework
|
||||
* [***input***](./input), an abstract UI event framework
|
||||
* [***tui***](./tui), an implementation of tengri over [***ratatui***](https://ratatui.rs/).
|
||||
|
||||
as well as:
|
||||
* [***core***](./core), the shared definitions ("utils") module
|
||||
* [***proc***](./proc), the space for procedural macros
|
||||
* [***tengri***](./tengri), the top-level reexport crate
|
||||
|
||||
tengri is published under [**AGPL3**](./LICENSE).
|
||||
|
|
|
|||
|
|
@ -1,4 +1,8 @@
|
|||
pub use ::tengri_core::*;
|
||||
#[cfg(feature="output")] pub use ::tengri_output as output;
|
||||
#[cfg(feature="input")] pub use ::tengri_input as input;
|
||||
#[cfg(feature="dsl")] pub use ::tengri_dsl as dsl;
|
||||
#[cfg(feature="tui")] pub use ::tengri_tui as tui;
|
||||
|
||||
#[cfg(test)] extern crate tengri_proc;
|
||||
#[cfg(test)] mod test;
|
||||
|
|
|
|||
103
tengri/src/test.rs
Normal file
103
tengri/src/test.rs
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
// FIXME
|
||||
//use crate::*;
|
||||
//use crate::{dsl::*, input::*, tui::TuiIn};
|
||||
//use crossterm::event::{Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState};
|
||||
//use std::cmp::Ordering;
|
||||
|
||||
//#[test] fn test_subcommand () -> Usually<()> {
|
||||
//#[derive(Debug)] struct Event(crossterm::event::Event);
|
||||
//impl Eq for Event {}
|
||||
//impl PartialEq for Event { fn eq (&self, other: &Self) -> bool { todo!() } }
|
||||
//impl Ord for Event { fn cmp (&self, other: &Self) -> Ordering { todo!() } }
|
||||
//impl PartialOrd for Event { fn partial_cmp (&self, other: &Self) -> Option<Ordering> { None } }
|
||||
//struct Test { keys: InputMap<Event, Ast> }
|
||||
|
||||
//handle!(TuiIn: |self: Test, input|Ok(None));[>if let Some(command) = self.keys.command(self, input) {
|
||||
//Ok(Some(true))
|
||||
//} else {
|
||||
//Ok(None)
|
||||
//});*/
|
||||
|
||||
//#[tengri_proc::command(Test)]
|
||||
//impl TestCommand {
|
||||
//fn do_thing (_state: &mut Test) -> Perhaps<Self> {
|
||||
//Ok(None)
|
||||
//}
|
||||
//fn do_thing_arg (_state: &mut Test, _arg: usize) -> Perhaps<Self> {
|
||||
//Ok(None)
|
||||
//}
|
||||
//fn do_sub (state: &mut Test, command: TestSubcommand) -> Perhaps<Self> {
|
||||
//Ok(command.execute(state)?.map(|command|Self::DoSub { command }))
|
||||
//}
|
||||
//}
|
||||
|
||||
//#[tengri_proc::command(Test)]
|
||||
//impl TestSubcommand {
|
||||
//fn do_other_thing (_state: &mut Test) -> Perhaps<Self> {
|
||||
//Ok(None)
|
||||
//}
|
||||
//fn do_other_thing_arg (_state: &mut Test, _arg: usize) -> Perhaps<Self> {
|
||||
//Ok(None)
|
||||
//}
|
||||
//}
|
||||
|
||||
//let mut test = Test {
|
||||
//keys: InputMap::from_source("
|
||||
//(@a do-thing)
|
||||
//(@b do-thing-arg 0)
|
||||
//(@c do-sub do-other-thing)
|
||||
//(@d do-sub do-other-thing-arg 0)
|
||||
//")?
|
||||
//};
|
||||
|
||||
////assert_eq!(Some(true), test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent {
|
||||
////kind: KeyEventKind::Press,
|
||||
////code: KeyCode::Char('a'),
|
||||
////modifiers: KeyModifiers::NONE,
|
||||
////state: KeyEventState::NONE,
|
||||
////})))?);
|
||||
////assert_eq!(Some(true), test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent {
|
||||
////kind: KeyEventKind::Press,
|
||||
////code: KeyCode::Char('b'),
|
||||
////modifiers: KeyModifiers::NONE,
|
||||
////state: KeyEventState::NONE,
|
||||
////})))?);
|
||||
////assert_eq!(Some(true), test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent {
|
||||
////kind: KeyEventKind::Press,
|
||||
////code: KeyCode::Char('c'),
|
||||
////modifiers: KeyModifiers::NONE,
|
||||
////state: KeyEventState::NONE,
|
||||
////})))?);
|
||||
////assert_eq!(Some(true), test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent {
|
||||
////kind: KeyEventKind::Press,
|
||||
////code: KeyCode::Char('d'),
|
||||
////modifiers: KeyModifiers::NONE,
|
||||
////state: KeyEventState::NONE,
|
||||
////})))?);
|
||||
////assert_eq!(None, test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent {
|
||||
////kind: KeyEventKind::Press,
|
||||
////code: KeyCode::Char('z'),
|
||||
////modifiers: KeyModifiers::NONE,
|
||||
////state: KeyEventState::NONE,
|
||||
////})))?);
|
||||
//Ok(())
|
||||
//}
|
||||
|
||||
//FIXME:
|
||||
//#[cfg(test)] #[test] fn test_dsl_context () {
|
||||
//use crate::dsl::{Dsl, Value};
|
||||
|
||||
//struct Test;
|
||||
//#[tengri_proc::expose]
|
||||
//impl Test {
|
||||
//fn some_bool (&self) -> bool {
|
||||
//true
|
||||
//}
|
||||
//}
|
||||
//assert_eq!(Dsl::get(&Test, &Value::Sym(":false")), Some(false));
|
||||
//assert_eq!(Dsl::get(&Test, &Value::Sym(":true")), Some(true));
|
||||
//assert_eq!(Dsl::get(&Test, &Value::Sym(":some-bool")), Some(true));
|
||||
//assert_eq!(Dsl::get(&Test, &Value::Sym(":missing-bool")), None);
|
||||
//assert_eq!(Dsl::get(&Test, &Value::Num(0)), Some(false));
|
||||
//assert_eq!(Dsl::get(&Test, &Value::Num(1)), Some(true));
|
||||
//}
|
||||
|
|
@ -1,27 +1,34 @@
|
|||
[package]
|
||||
name = "tengri_tui"
|
||||
edition = "2024"
|
||||
description = "UI metaframework, Ratatui backend."
|
||||
version = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
palette = { version = "0.7.6", features = [ "random" ] }
|
||||
rand = "0.8.5"
|
||||
crossterm = "0.28.1"
|
||||
ratatui = { version = "0.29.0", features = [ "unstable-widget-ref", "underline-color" ] }
|
||||
better-panic = "0.3.0"
|
||||
konst = { version = "0.3.16", features = [ "rust_1_83" ] }
|
||||
atomic_float = "1"
|
||||
quanta = "0.12.3"
|
||||
unicode-width = "0.2"
|
||||
|
||||
tengri_input = { path = "../input" }
|
||||
tengri_output = { path = "../output" }
|
||||
tengri_dsl = { optional = true, path = "../dsl" }
|
||||
|
||||
[dev-dependencies]
|
||||
tengri = { path = "../tengri", features = [ "dsl" ] }
|
||||
tengri_dsl = { path = "../dsl" }
|
||||
[lib]
|
||||
path = "src/tui.rs"
|
||||
|
||||
[features]
|
||||
dsl = [ "tengri_dsl", "tengri_input/dsl", "tengri_output/dsl" ]
|
||||
dsl = [ "dep:tengri_dsl", "tengri_output/dsl" ]
|
||||
bumpalo = [ "dep:bumpalo" ]
|
||||
|
||||
[dependencies]
|
||||
tengri_core = { workspace = true }
|
||||
tengri_input = { workspace = true }
|
||||
tengri_output = { workspace = true }
|
||||
tengri_dsl = { workspace = true, optional = true }
|
||||
|
||||
atomic_float = { workspace = true }
|
||||
better-panic = { workspace = true }
|
||||
bumpalo = { workspace = true, optional = true }
|
||||
crossterm = { workspace = true }
|
||||
konst = { workspace = true }
|
||||
palette = { workspace = true }
|
||||
quanta = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
ratatui = { workspace = true }
|
||||
unicode-width = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tengri = { workspace = true, features = [ "dsl" ] }
|
||||
tengri_dsl = { workspace = true }
|
||||
tengri_proc = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -1,15 +1,4 @@
|
|||
# `tengri_tui`
|
||||
***tengri_tui*** is an implementation of [tengri_output](../output) and [tengri_input](../input)
|
||||
on top of [ratatui](https://ratatui.rs/) and [crossterm](https://github.com/crossterm-rs/crossterm).
|
||||
|
||||
the `Tui` struct (the *engine*) implements the
|
||||
`tengri_input::Input` and `tengri_output::Output` traits.
|
||||
|
||||
at launch, the `Tui` engine spawns two threads,
|
||||
a **render thread** and an **input thread**. (the
|
||||
application may spawn further threads, such as a
|
||||
**jack thread**.)
|
||||
|
||||
all threads communicate using shared ownership,
|
||||
`Arc<RwLock>` and `Arc<Atomic>`. the engine and
|
||||
application instances are expected to be wrapped
|
||||
in `Arc<RwLock>`; internally, those synchronization
|
||||
mechanisms may be used liberally.
|
||||
tengri is published under [**AGPL3**](../LICENSE).
|
||||
|
|
|
|||
|
|
@ -1,131 +0,0 @@
|
|||
use tek::*;
|
||||
|
||||
fn main () -> Usually<()> {
|
||||
Tui::run(Arc::new(RwLock::new(Demo::new())))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub struct Demo<E: Engine> {
|
||||
index: usize,
|
||||
items: Vec<Box<dyn Render<Engine = E>>>
|
||||
}
|
||||
|
||||
impl Demo<Tui> {
|
||||
fn new () -> Self {
|
||||
Self {
|
||||
index: 0,
|
||||
items: vec![]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Content for Demo<Tui> {
|
||||
type Engine = Tui;
|
||||
fn content (&self) -> dyn Render<Engine = Tui> {
|
||||
let border_style = Style::default().fg(Color::Rgb(0,0,0));
|
||||
Align::Center(Layers::new(move|add|{
|
||||
|
||||
add(&Background(Color::Rgb(0,128,128)))?;
|
||||
|
||||
add(&Margin::XY(1, 1, Stack::down(|add|{
|
||||
|
||||
add(&Layers::new(|add|{
|
||||
add(&Background(Color::Rgb(128,96,0)))?;
|
||||
add(&Border(Square(border_style)))?;
|
||||
add(&Margin::XY(2, 1, "..."))?;
|
||||
Ok(())
|
||||
}).debug())?;
|
||||
|
||||
add(&Layers::new(|add|{
|
||||
add(&Background(Color::Rgb(128,64,0)))?;
|
||||
add(&Border(Lozenge(border_style)))?;
|
||||
add(&Margin::XY(4, 2, "---"))?;
|
||||
Ok(())
|
||||
}).debug())?;
|
||||
|
||||
add(&Layers::new(|add|{
|
||||
add(&Background(Color::Rgb(96,64,0)))?;
|
||||
add(&Border(SquareBold(border_style)))?;
|
||||
add(&Margin::XY(6, 3, "~~~"))?;
|
||||
Ok(())
|
||||
}).debug())?;
|
||||
|
||||
Ok(())
|
||||
})).debug())?;
|
||||
|
||||
Ok(())
|
||||
|
||||
}))
|
||||
//Align::Center(Margin::X(1, Layers::new(|add|{
|
||||
//add(&Background(Color::Rgb(128,0,0)))?;
|
||||
//add(&Stack::down(|add|{
|
||||
//add(&Margin::Y(1, Layers::new(|add|{
|
||||
//add(&Background(Color::Rgb(0,128,0)))?;
|
||||
//add(&Align::Center("12345"))?;
|
||||
//add(&Align::Center("FOO"))
|
||||
//})))?;
|
||||
//add(&Margin::XY(1, 1, Layers::new(|add|{
|
||||
//add(&Align::Center("1234567"))?;
|
||||
//add(&Align::Center("BAR"))?;
|
||||
//add(&Background(Color::Rgb(0,0,128)))
|
||||
//})))
|
||||
//}))
|
||||
//})))
|
||||
|
||||
//Align::Y(Layers::new(|add|{
|
||||
//add(&Background(Color::Rgb(128,0,0)))?;
|
||||
//add(&Margin::X(1, Align::Center(Stack::down(|add|{
|
||||
//add(&Align::X(Margin::Y(1, Layers::new(|add|{
|
||||
//add(&Background(Color::Rgb(0,128,0)))?;
|
||||
//add(&Align::Center("12345"))?;
|
||||
//add(&Align::Center("FOO"))
|
||||
//})))?;
|
||||
//add(&Margin::XY(1, 1, Layers::new(|add|{
|
||||
//add(&Align::Center("1234567"))?;
|
||||
//add(&Align::Center("BAR"))?;
|
||||
//add(&Background(Color::Rgb(0,0,128)))
|
||||
//})))?;
|
||||
//Ok(())
|
||||
//})))))
|
||||
//}))
|
||||
}
|
||||
}
|
||||
|
||||
impl Handle<TuiIn> for Demo<Tui> {
|
||||
fn handle (&mut self, from: &TuiIn) -> Perhaps<bool> {
|
||||
use KeyCode::{PageUp, PageDown};
|
||||
match from.event() {
|
||||
kexp!(PageUp) => {
|
||||
self.index = (self.index + 1) % self.items.len();
|
||||
},
|
||||
kexp!(PageDown) => {
|
||||
self.index = if self.index > 1 {
|
||||
self.index - 1
|
||||
} else {
|
||||
self.items.len() - 1
|
||||
};
|
||||
},
|
||||
_ => return Ok(None)
|
||||
}
|
||||
Ok(Some(true))
|
||||
}
|
||||
}
|
||||
|
||||
//lisp!(CONTENT Demo (LET
|
||||
//(BORDER-STYLE (STYLE (FG (RGB 0 0 0))))
|
||||
//(BG-COLOR-0 (RGB 0 128 128))
|
||||
//(BG-COLOR-1 (RGB 128 96 0))
|
||||
//(BG-COLOR-2 (RGB 128 64 0))
|
||||
//(BG-COLOR-3 (RGB 96 64 0))
|
||||
//(CENTER (LAYERS
|
||||
//(BACKGROUND BG-COLOR-0)
|
||||
//(OUTSET-XY 1 1 (SPLIT-DOWN
|
||||
//(LAYERS (BACKGROUND BG-COLOR-1)
|
||||
//(BORDER SQUARE BORDER-STYLE)
|
||||
//(OUTSET-XY 2 1 "..."))
|
||||
//(LAYERS (BACKGROUND BG-COLOR-2)
|
||||
//(BORDER LOZENGE BORDER-STYLE)
|
||||
//(OUTSET-XY 4 2 "---"))
|
||||
//(LAYERS (BACKGROUND BG-COLOR-3)
|
||||
//(BORDER SQUARE-BOLD BORDER-STYLE)
|
||||
//(OUTSET-XY 2 1 "~~~"))))))))
|
||||
|
|
@ -1 +0,0 @@
|
|||
:hello-world
|
||||
|
|
@ -1 +0,0 @@
|
|||
(bsp/s :hello :world)
|
||||
|
|
@ -1 +0,0 @@
|
|||
(fill/xy :hello-world)
|
||||
|
|
@ -1 +0,0 @@
|
|||
(fixed/xy 20 10 :hello-world)
|
||||
|
|
@ -1 +0,0 @@
|
|||
(bsp/s (fixed/xy 5 6 :hello) (fixed/xy 7 8 :world))
|
||||
|
|
@ -1 +0,0 @@
|
|||
(bsp/e (fixed/xy 5 6 :hello) (fixed/xy 7 8 :world))
|
||||
|
|
@ -1 +0,0 @@
|
|||
(bsp/n (fixed/xy 5 6 :hello) (fixed/xy 7 8 :world))
|
||||
|
|
@ -1 +0,0 @@
|
|||
(bsp/w (fixed/xy 5 6 :hello) (fixed/xy 7 8 :world))
|
||||
|
|
@ -1 +0,0 @@
|
|||
(bsp/a (fixed/xy 5 6 :hello) (fixed/xy 7 8 :world))
|
||||
|
|
@ -1 +0,0 @@
|
|||
(bsp/b (fixed/xy 5 6 :hello) (fixed/xy 7 8 :world))
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
(bsp/s
|
||||
(bsp/e (align/nw (fixed/xy 5 3 :hello))
|
||||
(bsp/e (align/n (fixed/xy 5 3 :hello))
|
||||
(align/ne (fixed/xy 5 3 :hello))))
|
||||
(bsp/s
|
||||
(bsp/e (align/w (fixed/xy 5 3 :hello))
|
||||
(bsp/e (align/c (fixed/xy 5 3 :hello))
|
||||
(align/e (fixed/xy 5 3 :hello))))
|
||||
(bsp/e (align/sw (fixed/xy 5 3 :hello))
|
||||
(bsp/e (align/s (fixed/xy 5 3 :hello))
|
||||
(align/se (fixed/xy 5 3 :hello))))))
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
(bsp/s
|
||||
(bsp/e (fixed/xy 8 5 (align/nw :hello))
|
||||
(bsp/e (fixed/xy 8 5 (align/n :hello))
|
||||
(fixed/xy 8 5 (align/ne :hello))))
|
||||
(bsp/s
|
||||
(bsp/e (fixed/xy 8 5 (align/w :hello))
|
||||
(bsp/e (fixed/xy 8 5 (align/c :hello))
|
||||
(fixed/xy 8 5 (align/e :hello))))
|
||||
(bsp/e (fixed/xy 8 5 (align/sw :hello))
|
||||
(bsp/e (fixed/xy 8 5 (align/s :hello))
|
||||
(fixed/xy 8 5 (align/se :hello))))))
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue