Compare commits

...

178 commits
v0.8.0 ... main

Author SHA1 Message Date
8c54510f63 more Draw and Layout generic impls
Some checks failed
/ build (push) Has been cancelled
- okay, that seems to have fixed the FieldH/FieldV issue?
- thought that'd never happen
- still not happy with how Layout override is working
2025-09-29 06:43:41 +03:00
731f4a971e move Layout trait to separate module
Some checks failed
/ build (push) Has been cancelled
- output: impl Layout for Measure, FieldH, FieldV
- tui: enable #[feature(trait_alias)]
- tui: define some trait aliases
2025-09-27 16:56:40 +03:00
90fc869e14 uuugh 2025-09-09 20:39:08 +03:00
ca862b9802 dsl, output, tui: add tests, examples, root dispatchers 2025-09-08 19:42:44 +03:00
8dfe20a58c tui: update example 2025-09-08 17:48:56 +03:00
c5cdbf4f07 dsl: remove deprecated traits
Some checks are pending
/ build (push) Waiting to run
2025-09-08 02:03:38 +03:00
d330e88252 fix(tui): bg->fg 2025-09-08 02:03:29 +03:00
a4dbf88220 perf: use mold
Some checks are pending
/ build (push) Waiting to run
2025-09-08 00:31:18 +03:00
18b6803912 core: add as_ref 2025-09-07 23:27:35 +03:00
d6dcf137a8 fix some warns 2025-09-07 23:13:19 +03:00
baa582b9dd tui: revive example 2025-09-07 21:28:47 +03:00
5e6338fad8 output: more big refactors 2025-09-07 20:57:44 +03:00
194f2f9874 output: remove RenderBox
Some checks are pending
/ build (push) Waiting to run
2025-09-06 11:18:39 +03:00
1c21a85f27 output: refactor Content and Render traits 2025-09-06 08:57:18 +03:00
74b3af2212 output: modularize space
Some checks failed
/ build (push) Has been cancelled
2025-09-05 03:56:15 +03:00
ff4d0c9db5 dsl: split ns trait
Some checks failed
/ build (push) Has been cancelled
2025-09-03 03:30:06 +03:00
c57117df9c dsl: simplify trait further 2025-09-03 02:56:27 +03:00
ad2d7c38b1 fix(dsl): take some more ownership
Some checks are pending
/ build (push) Waiting to run
why implement Dsl for &Dsl otherwise?
2025-09-02 22:38:50 +03:00
4f1131744b output: remodularize 2025-09-02 22:36:34 +03:00
b98fccd98b output: try to fix lifetimes of Stack
Some checks are pending
/ build (push) Waiting to run
2025-09-01 22:49:17 +03:00
34982a12ba dsl: extract macros, remove 2 conv traits
Some checks failed
/ build (push) Has been cancelled
2025-08-30 03:58:07 +03:00
f35ac97737 output: fix(?) the stacks 2025-08-30 03:57:54 +03:00
1b98e468b6 update lockfile 2025-08-30 03:56:25 +03:00
d9081087ec feat(output): impl PartialEq for Measure
Some checks failed
/ build (push) Has been cancelled
2025-08-24 03:21:32 +03:00
375b959e33 feat(input): add def_command 2025-08-23 23:19:39 +03:00
022bfa3e20 fix(proc): Command self type 2025-08-23 23:19:23 +03:00
e3e3c163da input: refactor, move dsl stuff to tek config
Some checks are pending
/ build (push) Waiting to run
2025-08-23 13:47:45 +03:00
0621793930 dsl: update docs 2025-08-23 13:10:59 +03:00
3e2b07158c output: update README 2025-08-23 12:50:31 +03:00
d92e5efdd0 dsl: add dsl_words, dsl_exprs, from_literal 2025-08-23 12:19:02 +03:00
2557a0d253 dsl: refactor with eyes closed
Some checks are pending
/ build (push) Waiting to run
2025-08-22 23:15:38 +03:00
1ef898ac32 remove DslKey
Some checks failed
/ build (push) Has been cancelled
2025-08-17 19:32:43 +03:00
ddd162f225 dsl: exp -> expr, sym -> word 2025-08-17 19:24:06 +03:00
cf253c28f9 dsl: modularize 2025-08-16 16:28:24 +03:00
4fc0db5777 dsl eval and ns again 2025-08-16 14:34:41 +03:00
d7884f6289 dsl: some ns progress
Some checks are pending
/ build (push) Waiting to run
2025-08-15 21:24:13 +03:00
f626860924 wip: dsl refactor 2025-08-15 21:03:49 +03:00
a1190a24a1 dsl: add DslExpNs::from_exp; return Perhaps rather than Usually
Some checks failed
/ build (push) Has been cancelled
2025-08-12 13:15:22 +03:00
3298d6b6e1 tui: trim all strings
no newline or wrapping yet
2025-08-12 13:15:03 +03:00
ab1afa219f input, output: formatting, warnings
Some checks failed
/ build (push) Has been cancelled
2025-08-10 21:50:17 +03:00
ab0dc3fae0 dsl: add ns 2025-08-10 21:50:03 +03:00
35a5784d23 stack: support above/below 2025-08-10 21:05:05 +03:00
7fd6c91643 need const trie :(
Some checks are pending
/ build (push) Waiting to run
2025-08-10 17:00:58 +03:00
e839096cf3 docs: operators idea 2025-08-10 14:21:59 +03:00
24ac52d807 tui: keybinds work?
Some checks are pending
/ build (push) Waiting to run
2025-08-10 02:06:17 +03:00
b52c1f5828 tui: add ErrorBoundary component
Some checks failed
/ build (push) Has been cancelled
2025-08-04 15:17:46 +03:00
104bb1c8e7 more dsl runaround
Some checks are pending
/ build (push) Waiting to run
2025-08-03 21:22:55 +03:00
9ccd7e5c69 dsl: macro dsl_for_each -> method each 2025-08-03 19:36:01 +03:00
a601d3d806 so many ood tests
Some checks are pending
/ build (push) Waiting to run
2025-08-03 01:58:50 +03:00
643658ab16 dsl: fixed expression handling 2025-08-03 01:53:58 +03:00
9e0b7be9a9 fix(dsl): tail condition 2025-07-31 21:00:13 +03:00
85c305385b fix(input): wat
Some checks failed
/ build (push) Has been cancelled
2025-07-29 22:07:03 +03:00
9f7d0efda5 core, input, output, dsl: factor away 'flexi' traits 2025-07-29 17:09:27 +03:00
8cbd7dd8e8 core, input: add flex_trait 2025-07-29 14:17:01 +03:00
360b404b69 fix(proc): update macros
Some checks failed
/ build (push) Has been cancelled
2025-07-20 04:54:38 +03:00
73eb935282 refactor(dsl): use traits instead of enums
Some checks are pending
/ build (push) Waiting to run
2025-07-20 04:29:06 +03:00
d72a3b5b8f fix(input): sorting out event map 2025-07-19 20:24:38 +03:00
238ac2e888 simplify 2025-07-19 18:03:18 +03:00
291b917970 this one at least compiles 2025-07-19 07:55:56 +03:00
a145e332de 1 file, 300 lines, many worries
Some checks are pending
/ build (push) Waiting to run
2025-07-17 21:45:59 +03:00
d99b20c99d wip: fix(dsl): kinda patch it up 2025-07-17 19:29:14 +03:00
e72225f83c wip: fix dsl
Some checks failed
/ build (push) Has been cancelled
2025-07-16 00:10:03 +03:00
ca4c558eab fix(input): InputMap manual Default impl 2025-07-14 23:06:29 +03:00
38d29f30a7 fix(proc): expose variants 2025-07-14 23:06:17 +03:00
7271081fc9 wip: mrr 2025-07-14 22:22:45 +03:00
6c3a0964ec wip: fix(input): grr
Some checks failed
/ build (push) Has been cancelled
todo: per-key inptut map layering
2025-06-22 11:00:51 +03:00
11f686650f wip: fix(dsl): maybe getting somewhere?
Some checks are pending
/ build (push) Waiting to run
2025-06-21 19:20:39 +03:00
91dc77cfea wip: refactor dsl
Some checks failed
/ build (push) Has been cancelled
2025-06-18 21:32:46 +03:00
c8827b43c3 wip: slowly remembering where i was
Some checks failed
/ build (push) Has been cancelled
2025-06-13 11:42:20 +03:00
17506726cb wip: updating tests
Some checks are pending
/ build (push) Waiting to run
2025-06-12 21:56:59 +03:00
21832453d9 dsl,input,output,proc,tui: fix warnings 2025-06-12 19:29:12 +03:00
08593571fa output: fix expressions 2025-06-08 04:45:14 +03:00
cb47c4d0ff dsl goes fat
Some checks failed
/ build (push) Has been cancelled
2025-05-27 00:53:06 +03:00
08a8dff93d wip: dsl: continuing to offload to Ast 2025-05-26 23:39:02 +03:00
e9d4c7e0bc AstToken -> Ast <- AstValue 2025-05-26 23:32:59 +03:00
f77139c8fd wip: wee 2025-05-26 23:21:38 +03:00
d6e8be6ce5 dsl gets the gordian treatment 2025-05-26 22:49:55 +03:00
93b1cf1a5c wip: dsl: getting interesting
Some checks are pending
/ build (push) Waiting to run
2025-05-26 01:45:08 +03:00
7097333993 wip: dsl: more rework 2025-05-26 01:30:13 +03:00
f1b24d436a wip: ast/cst
Some checks are pending
/ build (push) Waiting to run
2025-05-25 22:48:29 +03:00
31e84bf5b3 proc: builtims
Some checks are pending
/ build (push) Waiting to run
2025-05-25 11:43:35 +03:00
3e1084555b wip: dsl, output, input, proc, tui: sorting out give and take
Some checks are pending
/ build (push) Waiting to run
2025-05-24 23:57:12 +03:00
5a2177cc77 dsl: give! and take! macros
Some checks are pending
/ build (push) Waiting to run
2025-05-24 00:29:50 +03:00
cbd28a5934 dsl: auto-impl the obvious one (hopefully)
Some checks are pending
/ build (push) Waiting to run
re foreign trait constraints
2025-05-23 21:52:08 +03:00
ddf0c05d5f dsl: Provide -> Take, Receive -> Give (swap + shorten) 2025-05-23 21:39:29 +03:00
583660c330 wip: finally, informative type errors from the macro
Some checks failed
/ build (push) Has been cancelled
fixin mixin
2025-05-21 15:54:27 +03:00
abc87d3234 dsl, output: error handlers
Some checks are pending
/ build (push) Waiting to run
2025-05-21 14:17:27 +03:00
a4a1066f18 output: remove view; group tests 2025-05-21 14:11:37 +03:00
daef8dfa9e dsl: spiffier notfounds 2025-05-21 14:10:40 +03:00
7516517078 proc, view: fix usage of builtins 2025-05-21 13:57:03 +03:00
776cea6f1b dsl: reduce number of lifetime arguments
Some checks are pending
/ build (push) Waiting to run
2025-05-21 02:50:26 +03:00
2048dd2263 output: add Memo 2025-05-21 01:44:09 +03:00
f714302f21 FromDsl -> Namespace 2025-05-21 00:06:36 +03:00
455d6d00d5 read explicit lifetime to FromDsl
Some checks are pending
/ build (push) Waiting to run
2025-05-20 22:02:51 +03:00
7c1cddc759 wip: directionalize!
can't fit all into 1 trait because of directionality
of trait implementation rules and constraints :(
2025-05-20 19:04:39 +03:00
f797a7143d extact dsl_token; flip Dsl; try to obviate ViewContext 2025-05-20 16:27:05 +03:00
f08593f0f8 remove View; allow rendering Result
Some checks failed
/ build (push) Has been cancelled
2025-05-19 02:23:26 +03:00
90f5699fff dsl: use only Dsl trait 2025-05-19 00:06:03 +03:00
3bc739328e output: somewhat better error handling in cond and either
Some checks are pending
/ build (push) Waiting to run
2025-05-18 18:32:07 +03:00
7ddbace030 proc: allow implementing #[command] over more complex types 2025-05-18 18:31:40 +03:00
921378b6db tui: remove buttons 2025-05-18 00:22:48 +03:00
9a12e0c7ba output: format
Some checks are pending
/ build (push) Waiting to run
2025-05-17 21:56:16 +03:00
f21781e816 tui: adjust color balance
Some checks are pending
/ build (push) Waiting to run
2025-05-17 20:57:51 +03:00
12998a94ea output: report more info on error from bsp 2025-05-17 20:07:57 +03:00
b127526570 fix(output): fix cond 2025-05-17 19:27:04 +03:00
c9f0164871 tui: add cb/br open/close 2025-05-17 17:59:22 +03:00
a55e84c29f output, tui: Stack implementation
Some checks are pending
/ build (push) Waiting to run
2025-05-17 10:15:07 +03:00
b25977d878 add has!, MaybeHas, maybe_has! 2025-05-17 05:47:51 +03:00
4ff4ea8173 wip: field
Some checks failed
/ build (push) Has been cancelled
2025-05-15 23:17:19 +03:00
c954965ae1 measure: w -> h
Some checks are pending
/ build (push) Waiting to run
2025-05-14 22:37:43 +03:00
f7306de55f field: align west 2025-05-14 22:17:27 +03:00
4c039c999b output: add memory of Stack primitive 2025-05-14 22:17:07 +03:00
496a9202d5 input: better dsl error handling 2025-05-14 22:16:52 +03:00
8bfd1a23a1 core, output: add Has, HasSize
Some checks are pending
/ build (push) Waiting to run
2025-05-14 17:53:34 +03:00
a9619ab9ce fix(proc): use TryFromDsl locally 2025-05-14 14:35:38 +03:00
bad20f5037 tui: add button_2, button_3
Some checks are pending
/ build (push) Waiting to run
2025-05-14 00:46:09 +03:00
5d546affed tui: remove tui_file 2025-05-13 20:27:41 +03:00
663efede64 dsl: don't eat error 2025-05-13 20:27:12 +03:00
b45ac8f417 tui: remove FileBrowser 2025-05-13 20:25:55 +03:00
632977a0dc dsl: implement Display for Value 2025-05-13 20:25:44 +03:00
faecc2c304 update dependencies
Some checks failed
/ build (push) Has been cancelled
2025-05-10 15:50:07 +03:00
1868736597 proc: log parse info on give_err 2025-05-10 15:49:46 +03:00
ed772b9872 dsl: extract dsl_error; ParseError -> DslError 2025-05-10 15:49:33 +03:00
f18e01c220 partially fix tests and examples 2025-05-10 15:28:42 +03:00
8dda576c9d add tengri_core; fix errors and warnings; unify deps 2025-05-10 15:25:09 +03:00
cb8fd26922 collect tests
Some checks are pending
/ build (push) Waiting to run
2025-05-09 23:00:36 +03:00
4a385b40ff test subcommand handling 2025-05-09 22:43:21 +03:00
fe8ecf8a98 input, proc: add full paths in macros 2025-05-09 22:43:12 +03:00
20ccff13de proc: auto implement Context on command target
Context and TryFromDsl overlap
2025-05-09 21:13:52 +03:00
3bb38f2d27 proc: view: list available on error 2025-05-09 20:21:31 +03:00
60c0771024 proc, input, output: cleanup warnings 2025-05-09 20:02:24 +03:00
ab07fd2b43 dsl: compact 2025-05-09 19:45:25 +03:00
5e09f5a4bb wip: dsl, input, output, proc: more precise lifetimes
Some checks are pending
/ build (push) Waiting to run
2025-05-09 18:17:10 +03:00
22d63eed9c input, dsl: cleanup
Some checks are pending
/ build (push) Waiting to run
2025-05-09 01:38:18 +03:00
b7bb6119aa remove old declarative macros 2025-05-08 22:14:16 +03:00
bcbcc387a2 proc: cleanup 2025-05-08 20:20:24 +03:00
a16603fbc8 proc: command: associated fns instead of methods 2025-05-08 18:37:42 +03:00
2a6087e1c7 fix: command: refs
Some checks are pending
/ build (push) Waiting to run
2025-05-08 17:39:02 +03:00
e3bfae8897 fix: command: commas 2025-05-08 17:25:45 +03:00
046be9a9e1 proc: working command, expose 2025-05-08 13:46:29 +03:00
751e01a41e :
Some checks are pending
/ build (push) Waiting to run
wip: proc: command (pt.3)
2025-05-08 03:57:43 +03:00
c56b08c24e proc: expose: fix output match statement 2025-05-07 12:37:38 +03:00
7df7cb839c wip: proc: command macro
Some checks are pending
/ build (push) Waiting to run
2025-05-06 23:23:03 +03:00
7570aefcc2 proc: simplify expose macro 2025-05-06 21:33:46 +03:00
cba23a005c proc: expose macro implementation
Some checks are pending
/ build (push) Waiting to run
2025-05-04 23:58:28 +03:00
b543c43e68 proc: view macro implementation
Some checks are pending
/ build (push) Waiting to run
2025-05-04 15:27:24 +03:00
2c797fd41f wip: scaffold proc crate and view macro 2025-05-03 17:32:57 +03:00
21f7f6b38a 0.13.0: release
Some checks are pending
/ build (push) Waiting to run
2025-05-03 02:14:03 +03:00
3df1938626 dsl: InputLayerCond; collect macros 2025-05-03 02:13:22 +03:00
0d4ba4a54e dsl: add Str token
Some checks are pending
/ build (push) Waiting to run
2025-05-02 19:16:28 +03:00
2b208e3c49 output: collect tests; formatting
Some checks failed
/ build (push) Has been cancelled
2025-05-01 01:18:23 +03:00
9fb5d2d9f7 fix(tui): add feature guard 2025-04-30 23:48:33 +03:00
44ebe17c66 input_dsl: cleanup commented code
Some checks are pending
/ build (push) Waiting to run
2025-04-30 21:51:07 +03:00
119d5c35f0 input_dsl: expose InputMap layers; add From<SourceIter> for TokenIter 2025-04-30 21:49:01 +03:00
4ec51d5b69 input, dsl: implement InputMap command matching
Some checks failed
/ build (push) Has been cancelled
2025-04-28 23:28:53 +03:00
35ad371205 input: add InputMap; dsl/output/tui: Atom->Dsl
Some checks are pending
/ build (push) Waiting to run
2025-04-28 04:55:27 +03:00
47b7f7e7f9 0.12.0: release 2025-04-27 19:41:15 +03:00
95149b79c4 tui: add pgup/pgdn and extract tui_keys 2025-04-27 16:28:05 +03:00
7ba37f3f02 tui: ItemPalette->ItemTheme 2025-04-27 02:04:18 +03:00
aa66760b8c cleanup warnings 2025-04-27 01:57:20 +03:00
fa5ff90be6 input: sexpr defcom 2025-04-27 01:57:10 +03:00
61fd07bffd dsl: need square brackets in expose 2025-04-26 21:31:06 +03:00
7ba08b8be3 output: autobox in view! macro 2025-04-26 21:19:48 +03:00
3d01f5558c dsl: add sexpr syntaces; prefix modules 2025-04-26 21:11:23 +03:00
844681d6ad 0.11.0: release 2025-04-25 21:08:13 +03:00
aa7a2c72f5 chore: fix warnings 2025-04-25 21:07:50 +03:00
222e10239b ops: fix Map::east/south with example 2025-04-25 19:01:02 +03:00
2e9ba1b92d fix tui example 2025-04-25 14:11:04 +03:00
649a89443b output: finally Map::east, Map::south 2025-04-25 13:58:44 +03:00
b5fbd14f91 tui_phat: export LO and HI chars 2025-04-25 13:58:19 +03:00
91dfed1077 feat(tui): add Tryptich (3-col layout) 2025-04-24 21:03:52 +03:00
3861439c49 feat(input): add defcom! macro 2025-04-24 21:03:39 +03:00
fa6f6dab1d 0.10.0: update deps and lockfile 2025-04-23 15:35:16 +03:00
1f62c2f86d fix(ci): missing import in tests 2025-04-19 15:26:14 +03:00
aeddf561b1 fix warnings 2025-04-18 13:45:49 +03:00
471959d1f5 0.10.0: fixed expose! syntax 2025-04-17 02:21:00 +03:00
6048d24880 0.9.1: fix get_value/get_content invocations 2025-04-15 18:17:58 +03:00
f11c27b8c9 0.9.0: add get_value! and get_content! macros for dsl impl 2025-04-15 18:12:40 +03:00
128 changed files with 5261 additions and 3461 deletions

3
.editorconfig Normal file
View file

@ -0,0 +1,3 @@
root = true
[*]
max_line_length = 132

479
Cargo.lock generated
View file

@ -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",

View file

@ -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" }

View file

@ -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
View 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
View 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
View 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
View 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 }
}

View file

@ -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" }

View file

@ -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)`

View file

@ -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
View 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)))
}
}
}
}
});

View file

@ -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
View 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
View 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
View 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
View 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
View 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)
}
}

View file

@ -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();
//}
//}
}

View file

@ -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(())
//}

View file

@ -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
View 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
View file

View 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" }

View file

@ -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
View 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
}
}
}
}

View file

@ -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(())
//}

View file

@ -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)
}
}

View file

@ -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
})
}
}

View file

@ -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)
}
}

View file

@ -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)
}
}

View file

@ -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(())
}

View file

@ -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 }

View file

@ -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
View 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
View 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
View 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
View 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;
}
}

View 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!(),
}
}
}

View 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 }});

View 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
//})

View file

View 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))))
}

View 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()) }
}

View 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())
}
}

View 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()
}
}

View 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(())
//}));*/

View file

@ -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(())
}

View file

@ -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(())
}

View file

@ -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())
}
}

View file

@ -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]),
);
}
}
}

View file

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

View file

@ -1,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) }
}
}

View file

@ -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);
}

View file

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

View file

@ -1,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) }
}

View file

@ -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);
}

View file

@ -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
View 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);
}

View file

@ -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::*;

View file

@ -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);
}
}
}

View file

@ -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();
}
}
}

View file

@ -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

View 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())
}
}
}

View file

@ -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)
}
}

View file

@ -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
View 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)*) } } }

View file

@ -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
View 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::*;

View 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);

View 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 }
}
}

View 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)
}
}

View 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
View 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
View 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
View 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
View 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
View file

@ -0,0 +1,2 @@
// TODO: #[derive(Flex, FlexMut, FlexOption, FlexResult ... ?)]
// better derive + annotations

136
proc/src/proc_view.rs Normal file
View 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>> })
}

View file

@ -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
View 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

View file

@ -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"]

View file

@ -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).

View file

@ -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
View 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));
//}

View file

@ -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 }

View file

@ -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).

View file

@ -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 "~~~"))))))))

View file

@ -1 +0,0 @@
:hello-world

View file

@ -1 +0,0 @@
(bsp/s :hello :world)

View file

@ -1 +0,0 @@
(fill/xy :hello-world)

View file

@ -1 +0,0 @@
(fixed/xy 20 10 :hello-world)

View file

@ -1 +0,0 @@
(bsp/s (fixed/xy 5 6 :hello) (fixed/xy 7 8 :world))

View file

@ -1 +0,0 @@
(bsp/e (fixed/xy 5 6 :hello) (fixed/xy 7 8 :world))

View file

@ -1 +0,0 @@
(bsp/n (fixed/xy 5 6 :hello) (fixed/xy 7 8 :world))

View file

@ -1 +0,0 @@
(bsp/w (fixed/xy 5 6 :hello) (fixed/xy 7 8 :world))

View file

@ -1 +0,0 @@
(bsp/a (fixed/xy 5 6 :hello) (fixed/xy 7 8 :world))

View file

@ -1 +0,0 @@
(bsp/b (fixed/xy 5 6 :hello) (fixed/xy 7 8 :world))

View file

@ -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))))))

View file

@ -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