├── .github
└── workflows
│ ├── main.yml
│ └── rust.yml
├── .gitignore
├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── Makefile
├── README.md
├── examples
├── custom_chunk.rs
├── custom_set.rs
├── custom_state.rs
├── message.rs
└── minimal.rs
├── src
├── app.rs
├── chunks.rs
├── events.rs
├── layout.rs
├── lib.rs
├── set.rs
├── setup.rs
├── states.rs
├── widget
│ ├── function_widget.rs
│ ├── into_widget.rs
│ ├── into_widget_set.rs
│ ├── mod.rs
│ └── param.rs
└── widgets
│ ├── message.rs
│ └── mod.rs
└── tui-helper-proc-macro
├── Cargo.lock
├── Cargo.toml
└── src
└── lib.rs
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: Publish
2 |
3 | on:
4 | push:
5 | tags:
6 | - '*'
7 |
8 | jobs:
9 | publish:
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - uses: actions/checkout@v3
14 | - uses: actions-rs/toolchain@v1
15 | with:
16 | toolchain: stable
17 | - run : |
18 | VERSION="${GITHUB_REF#refs/*/}" make publish
19 | env:
20 | CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_KEY }}
21 |
--------------------------------------------------------------------------------
/.github/workflows/rust.yml:
--------------------------------------------------------------------------------
1 | name: Rust
2 |
3 | on:
4 | push:
5 | branches: [ "master" ]
6 | pull_request:
7 | branches: [ "master" ]
8 |
9 | env:
10 | CARGO_TERM_COLOR: always
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | steps:
18 | - uses: actions/checkout@v3
19 | - name: Build
20 | run: cargo build --verbose
21 | - name: Run tests
22 | run: cargo test --verbose
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 |
--------------------------------------------------------------------------------
/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | version = 3
4 |
5 | [[package]]
6 | name = "allocator-api2"
7 | version = "0.2.18"
8 | source = "registry+https://github.com/rust-lang/crates.io-index"
9 | checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
10 |
11 | [[package]]
12 | name = "anyhow"
13 | version = "1.0.86"
14 | source = "registry+https://github.com/rust-lang/crates.io-index"
15 | checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
16 |
17 | [[package]]
18 | name = "autocfg"
19 | version = "1.3.0"
20 | source = "registry+https://github.com/rust-lang/crates.io-index"
21 | checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
22 |
23 | [[package]]
24 | name = "bitflags"
25 | version = "2.6.0"
26 | source = "registry+https://github.com/rust-lang/crates.io-index"
27 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
28 |
29 | [[package]]
30 | name = "cassowary"
31 | version = "0.3.0"
32 | source = "registry+https://github.com/rust-lang/crates.io-index"
33 | checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
34 |
35 | [[package]]
36 | name = "castaway"
37 | version = "0.2.3"
38 | source = "registry+https://github.com/rust-lang/crates.io-index"
39 | checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5"
40 | dependencies = [
41 | "rustversion",
42 | ]
43 |
44 | [[package]]
45 | name = "cfg-if"
46 | version = "1.0.0"
47 | source = "registry+https://github.com/rust-lang/crates.io-index"
48 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
49 |
50 | [[package]]
51 | name = "compact_str"
52 | version = "0.8.0"
53 | source = "registry+https://github.com/rust-lang/crates.io-index"
54 | checksum = "6050c3a16ddab2e412160b31f2c871015704239bca62f72f6e5f0be631d3f644"
55 | dependencies = [
56 | "castaway",
57 | "cfg-if",
58 | "itoa",
59 | "rustversion",
60 | "ryu",
61 | "static_assertions",
62 | ]
63 |
64 | [[package]]
65 | name = "crossterm"
66 | version = "0.28.1"
67 | source = "registry+https://github.com/rust-lang/crates.io-index"
68 | checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
69 | dependencies = [
70 | "bitflags",
71 | "crossterm_winapi",
72 | "mio",
73 | "parking_lot",
74 | "rustix",
75 | "signal-hook",
76 | "signal-hook-mio",
77 | "winapi",
78 | ]
79 |
80 | [[package]]
81 | name = "crossterm_winapi"
82 | version = "0.9.1"
83 | source = "registry+https://github.com/rust-lang/crates.io-index"
84 | checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
85 | dependencies = [
86 | "winapi",
87 | ]
88 |
89 | [[package]]
90 | name = "either"
91 | version = "1.13.0"
92 | source = "registry+https://github.com/rust-lang/crates.io-index"
93 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
94 |
95 | [[package]]
96 | name = "equivalent"
97 | version = "1.0.1"
98 | source = "registry+https://github.com/rust-lang/crates.io-index"
99 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
100 |
101 | [[package]]
102 | name = "errno"
103 | version = "0.3.9"
104 | source = "registry+https://github.com/rust-lang/crates.io-index"
105 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
106 | dependencies = [
107 | "libc",
108 | "windows-sys",
109 | ]
110 |
111 | [[package]]
112 | name = "foldhash"
113 | version = "0.1.3"
114 | source = "registry+https://github.com/rust-lang/crates.io-index"
115 | checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2"
116 |
117 | [[package]]
118 | name = "hashbrown"
119 | version = "0.15.0"
120 | source = "registry+https://github.com/rust-lang/crates.io-index"
121 | checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb"
122 | dependencies = [
123 | "allocator-api2",
124 | "equivalent",
125 | "foldhash",
126 | ]
127 |
128 | [[package]]
129 | name = "heck"
130 | version = "0.5.0"
131 | source = "registry+https://github.com/rust-lang/crates.io-index"
132 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
133 |
134 | [[package]]
135 | name = "hermit-abi"
136 | version = "0.3.9"
137 | source = "registry+https://github.com/rust-lang/crates.io-index"
138 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
139 |
140 | [[package]]
141 | name = "indoc"
142 | version = "2.0.5"
143 | source = "registry+https://github.com/rust-lang/crates.io-index"
144 | checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5"
145 |
146 | [[package]]
147 | name = "instability"
148 | version = "0.3.2"
149 | source = "registry+https://github.com/rust-lang/crates.io-index"
150 | checksum = "b23a0c8dfe501baac4adf6ebbfa6eddf8f0c07f56b058cc1288017e32397846c"
151 | dependencies = [
152 | "quote",
153 | "syn",
154 | ]
155 |
156 | [[package]]
157 | name = "itertools"
158 | version = "0.13.0"
159 | source = "registry+https://github.com/rust-lang/crates.io-index"
160 | checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
161 | dependencies = [
162 | "either",
163 | ]
164 |
165 | [[package]]
166 | name = "itoa"
167 | version = "1.0.11"
168 | source = "registry+https://github.com/rust-lang/crates.io-index"
169 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
170 |
171 | [[package]]
172 | name = "libc"
173 | version = "0.2.158"
174 | source = "registry+https://github.com/rust-lang/crates.io-index"
175 | checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
176 |
177 | [[package]]
178 | name = "linux-raw-sys"
179 | version = "0.4.14"
180 | source = "registry+https://github.com/rust-lang/crates.io-index"
181 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
182 |
183 | [[package]]
184 | name = "lock_api"
185 | version = "0.4.12"
186 | source = "registry+https://github.com/rust-lang/crates.io-index"
187 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
188 | dependencies = [
189 | "autocfg",
190 | "scopeguard",
191 | ]
192 |
193 | [[package]]
194 | name = "log"
195 | version = "0.4.22"
196 | source = "registry+https://github.com/rust-lang/crates.io-index"
197 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
198 |
199 | [[package]]
200 | name = "lru"
201 | version = "0.12.5"
202 | source = "registry+https://github.com/rust-lang/crates.io-index"
203 | checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38"
204 | dependencies = [
205 | "hashbrown",
206 | ]
207 |
208 | [[package]]
209 | name = "mio"
210 | version = "1.0.2"
211 | source = "registry+https://github.com/rust-lang/crates.io-index"
212 | checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec"
213 | dependencies = [
214 | "hermit-abi",
215 | "libc",
216 | "log",
217 | "wasi",
218 | "windows-sys",
219 | ]
220 |
221 | [[package]]
222 | name = "parking_lot"
223 | version = "0.12.3"
224 | source = "registry+https://github.com/rust-lang/crates.io-index"
225 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
226 | dependencies = [
227 | "lock_api",
228 | "parking_lot_core",
229 | ]
230 |
231 | [[package]]
232 | name = "parking_lot_core"
233 | version = "0.9.10"
234 | source = "registry+https://github.com/rust-lang/crates.io-index"
235 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
236 | dependencies = [
237 | "cfg-if",
238 | "libc",
239 | "redox_syscall",
240 | "smallvec",
241 | "windows-targets",
242 | ]
243 |
244 | [[package]]
245 | name = "paste"
246 | version = "1.0.15"
247 | source = "registry+https://github.com/rust-lang/crates.io-index"
248 | checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
249 |
250 | [[package]]
251 | name = "proc-macro2"
252 | version = "1.0.86"
253 | source = "registry+https://github.com/rust-lang/crates.io-index"
254 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
255 | dependencies = [
256 | "unicode-ident",
257 | ]
258 |
259 | [[package]]
260 | name = "quote"
261 | version = "1.0.36"
262 | source = "registry+https://github.com/rust-lang/crates.io-index"
263 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
264 | dependencies = [
265 | "proc-macro2",
266 | ]
267 |
268 | [[package]]
269 | name = "ratatui"
270 | version = "0.29.0"
271 | source = "registry+https://github.com/rust-lang/crates.io-index"
272 | checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b"
273 | dependencies = [
274 | "bitflags",
275 | "cassowary",
276 | "compact_str",
277 | "crossterm",
278 | "indoc",
279 | "instability",
280 | "itertools",
281 | "lru",
282 | "paste",
283 | "strum",
284 | "unicode-segmentation",
285 | "unicode-truncate",
286 | "unicode-width 0.2.0",
287 | ]
288 |
289 | [[package]]
290 | name = "redox_syscall"
291 | version = "0.5.2"
292 | source = "registry+https://github.com/rust-lang/crates.io-index"
293 | checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd"
294 | dependencies = [
295 | "bitflags",
296 | ]
297 |
298 | [[package]]
299 | name = "rustix"
300 | version = "0.38.35"
301 | source = "registry+https://github.com/rust-lang/crates.io-index"
302 | checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f"
303 | dependencies = [
304 | "bitflags",
305 | "errno",
306 | "libc",
307 | "linux-raw-sys",
308 | "windows-sys",
309 | ]
310 |
311 | [[package]]
312 | name = "rustversion"
313 | version = "1.0.18"
314 | source = "registry+https://github.com/rust-lang/crates.io-index"
315 | checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248"
316 |
317 | [[package]]
318 | name = "ryu"
319 | version = "1.0.18"
320 | source = "registry+https://github.com/rust-lang/crates.io-index"
321 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
322 |
323 | [[package]]
324 | name = "scopeguard"
325 | version = "1.2.0"
326 | source = "registry+https://github.com/rust-lang/crates.io-index"
327 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
328 |
329 | [[package]]
330 | name = "signal-hook"
331 | version = "0.3.17"
332 | source = "registry+https://github.com/rust-lang/crates.io-index"
333 | checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
334 | dependencies = [
335 | "libc",
336 | "signal-hook-registry",
337 | ]
338 |
339 | [[package]]
340 | name = "signal-hook-mio"
341 | version = "0.2.4"
342 | source = "registry+https://github.com/rust-lang/crates.io-index"
343 | checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd"
344 | dependencies = [
345 | "libc",
346 | "mio",
347 | "signal-hook",
348 | ]
349 |
350 | [[package]]
351 | name = "signal-hook-registry"
352 | version = "1.4.2"
353 | source = "registry+https://github.com/rust-lang/crates.io-index"
354 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
355 | dependencies = [
356 | "libc",
357 | ]
358 |
359 | [[package]]
360 | name = "smallvec"
361 | version = "1.13.2"
362 | source = "registry+https://github.com/rust-lang/crates.io-index"
363 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
364 |
365 | [[package]]
366 | name = "static_assertions"
367 | version = "1.1.0"
368 | source = "registry+https://github.com/rust-lang/crates.io-index"
369 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
370 |
371 | [[package]]
372 | name = "strum"
373 | version = "0.26.3"
374 | source = "registry+https://github.com/rust-lang/crates.io-index"
375 | checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
376 | dependencies = [
377 | "strum_macros",
378 | ]
379 |
380 | [[package]]
381 | name = "strum_macros"
382 | version = "0.26.4"
383 | source = "registry+https://github.com/rust-lang/crates.io-index"
384 | checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be"
385 | dependencies = [
386 | "heck",
387 | "proc-macro2",
388 | "quote",
389 | "rustversion",
390 | "syn",
391 | ]
392 |
393 | [[package]]
394 | name = "syn"
395 | version = "2.0.68"
396 | source = "registry+https://github.com/rust-lang/crates.io-index"
397 | checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9"
398 | dependencies = [
399 | "proc-macro2",
400 | "quote",
401 | "unicode-ident",
402 | ]
403 |
404 | [[package]]
405 | name = "thiserror"
406 | version = "1.0.61"
407 | source = "registry+https://github.com/rust-lang/crates.io-index"
408 | checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
409 | dependencies = [
410 | "thiserror-impl",
411 | ]
412 |
413 | [[package]]
414 | name = "thiserror-impl"
415 | version = "1.0.61"
416 | source = "registry+https://github.com/rust-lang/crates.io-index"
417 | checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
418 | dependencies = [
419 | "proc-macro2",
420 | "quote",
421 | "syn",
422 | ]
423 |
424 | [[package]]
425 | name = "tui-helper-proc-macro"
426 | version = "0.0.0"
427 | dependencies = [
428 | "quote",
429 | "syn",
430 | ]
431 |
432 | [[package]]
433 | name = "unicode-ident"
434 | version = "1.0.12"
435 | source = "registry+https://github.com/rust-lang/crates.io-index"
436 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
437 |
438 | [[package]]
439 | name = "unicode-segmentation"
440 | version = "1.12.0"
441 | source = "registry+https://github.com/rust-lang/crates.io-index"
442 | checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
443 |
444 | [[package]]
445 | name = "unicode-truncate"
446 | version = "1.1.0"
447 | source = "registry+https://github.com/rust-lang/crates.io-index"
448 | checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf"
449 | dependencies = [
450 | "itertools",
451 | "unicode-segmentation",
452 | "unicode-width 0.1.14",
453 | ]
454 |
455 | [[package]]
456 | name = "unicode-width"
457 | version = "0.1.14"
458 | source = "registry+https://github.com/rust-lang/crates.io-index"
459 | checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
460 |
461 | [[package]]
462 | name = "unicode-width"
463 | version = "0.2.0"
464 | source = "registry+https://github.com/rust-lang/crates.io-index"
465 | checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
466 |
467 | [[package]]
468 | name = "wasi"
469 | version = "0.11.0+wasi-snapshot-preview1"
470 | source = "registry+https://github.com/rust-lang/crates.io-index"
471 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
472 |
473 | [[package]]
474 | name = "widgetui"
475 | version = "0.0.0"
476 | dependencies = [
477 | "anyhow",
478 | "crossterm",
479 | "ratatui",
480 | "thiserror",
481 | "tui-helper-proc-macro",
482 | ]
483 |
484 | [[package]]
485 | name = "winapi"
486 | version = "0.3.9"
487 | source = "registry+https://github.com/rust-lang/crates.io-index"
488 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
489 | dependencies = [
490 | "winapi-i686-pc-windows-gnu",
491 | "winapi-x86_64-pc-windows-gnu",
492 | ]
493 |
494 | [[package]]
495 | name = "winapi-i686-pc-windows-gnu"
496 | version = "0.4.0"
497 | source = "registry+https://github.com/rust-lang/crates.io-index"
498 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
499 |
500 | [[package]]
501 | name = "winapi-x86_64-pc-windows-gnu"
502 | version = "0.4.0"
503 | source = "registry+https://github.com/rust-lang/crates.io-index"
504 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
505 |
506 | [[package]]
507 | name = "windows-sys"
508 | version = "0.52.0"
509 | source = "registry+https://github.com/rust-lang/crates.io-index"
510 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
511 | dependencies = [
512 | "windows-targets",
513 | ]
514 |
515 | [[package]]
516 | name = "windows-targets"
517 | version = "0.52.5"
518 | source = "registry+https://github.com/rust-lang/crates.io-index"
519 | checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
520 | dependencies = [
521 | "windows_aarch64_gnullvm",
522 | "windows_aarch64_msvc",
523 | "windows_i686_gnu",
524 | "windows_i686_gnullvm",
525 | "windows_i686_msvc",
526 | "windows_x86_64_gnu",
527 | "windows_x86_64_gnullvm",
528 | "windows_x86_64_msvc",
529 | ]
530 |
531 | [[package]]
532 | name = "windows_aarch64_gnullvm"
533 | version = "0.52.5"
534 | source = "registry+https://github.com/rust-lang/crates.io-index"
535 | checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
536 |
537 | [[package]]
538 | name = "windows_aarch64_msvc"
539 | version = "0.52.5"
540 | source = "registry+https://github.com/rust-lang/crates.io-index"
541 | checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
542 |
543 | [[package]]
544 | name = "windows_i686_gnu"
545 | version = "0.52.5"
546 | source = "registry+https://github.com/rust-lang/crates.io-index"
547 | checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
548 |
549 | [[package]]
550 | name = "windows_i686_gnullvm"
551 | version = "0.52.5"
552 | source = "registry+https://github.com/rust-lang/crates.io-index"
553 | checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
554 |
555 | [[package]]
556 | name = "windows_i686_msvc"
557 | version = "0.52.5"
558 | source = "registry+https://github.com/rust-lang/crates.io-index"
559 | checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
560 |
561 | [[package]]
562 | name = "windows_x86_64_gnu"
563 | version = "0.52.5"
564 | source = "registry+https://github.com/rust-lang/crates.io-index"
565 | checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
566 |
567 | [[package]]
568 | name = "windows_x86_64_gnullvm"
569 | version = "0.52.5"
570 | source = "registry+https://github.com/rust-lang/crates.io-index"
571 | checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
572 |
573 | [[package]]
574 | name = "windows_x86_64_msvc"
575 | version = "0.52.5"
576 | source = "registry+https://github.com/rust-lang/crates.io-index"
577 | checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
578 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "widgetui"
3 | edition = "2021"
4 | description = "A bevy like widget system for ratatui and crossterm"
5 | license = "MIT OR Apache-2.0"
6 | repository = "https://github.com/TheEmeraldBee/widgetui"
7 | version.workspace = true
8 |
9 | [workspace]
10 | members = ["tui-helper-proc-macro"]
11 | package.version = "0.0.0"
12 |
13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
14 |
15 | [dependencies]
16 | anyhow = "1.0.75"
17 | crossterm = "0.28.1"
18 | ratatui = "0.29.0"
19 | thiserror = "1.0.61"
20 | tui-helper-proc-macro = { path = "tui-helper-proc-macro", version = "0.0.0" }
21 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 TheEmeraldBee
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | VERSION_FILE := Cargo.toml
2 |
3 | run:
4 | cargo run
5 | build:
6 | cargo build
7 | publish:
8 | sed -i -r "s/package.version=\"0\.0\.0\"/package.version=\"${VERSION}\"/g" "$(VERSION_FILE)" \
9 | && sed -i -r "s/0\.0\.0/${VERSION}/g" "$(VERSION_FILE)" \
10 | && cargo publish --package tui-helper-proc-macro --allow-dirty \
11 | && cargo publish --allow-dirty \
12 |
13 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Widgetui
2 | Turn
3 |
4 | Ratatui Minimal
5 |
6 | ```rust
7 | fn main() -> Result<(), Box> {
8 | let mut terminal = setup_terminal()?;
9 | run(&mut terminal)?;
10 | restore_terminal(&mut terminal)?;
11 | Ok(())
12 | }
13 |
14 | fn setup_terminal() -> Result>, Box> {
15 | let mut stdout = io::stdout();
16 | enable_raw_mode()?;
17 | execute!(stdout, EnterAlternateScreen)?;
18 | Ok(Terminal::new(CrosstermBackend::new(stdout))?)
19 | }
20 |
21 | fn restore_terminal(
22 | terminal: &mut Terminal>,
23 | ) -> Result<(), Box> {
24 | disable_raw_mode()?;
25 | execute!(terminal.backend_mut(), LeaveAlternateScreen,)?;
26 | Ok(terminal.show_cursor()?)
27 | }
28 |
29 | fn run(terminal: &mut Terminal>) -> Result<(), Box> {
30 | Ok(loop {
31 | terminal.draw(|frame| {
32 | let greeting = Paragraph::new("Hello World!");
33 | frame.render_widget(greeting, frame.size());
34 | })?;
35 | if event::poll(Duration::from_millis(250))? {
36 | if let Event::Key(key) = event::read()? {
37 | if KeyCode::Char('q') == key.code {
38 | break;
39 | }
40 | }
41 | }
42 | })
43 | }
44 | ```
45 |
46 |
47 | Into
48 |
49 |
50 | Much Better
51 |
52 | ```rust
53 | use crossterm::event::KeyCode;
54 | use ratatui::widgets::Paragraph;
55 | use widgetui::*;
56 |
57 | use std::error::Error;
58 |
59 | fn widget(mut frame: ResMut, mut events: ResMut) -> WidgetResult {
60 | let size = frame.size();
61 | frame.render_widget(Paragraph::new("Hello, world!"), size);
62 |
63 | if events.key(KeyCode::Char('q')) {
64 | events.register_exit();
65 | }
66 |
67 | Ok(())
68 | }
69 |
70 | fn main() -> Result<(), Box> {
71 | Ok(App::new(100)?.widgets(widget).run()?)
72 | }
73 | ```
74 |
75 |
76 | The goal of this project is to simplify the requirements to make a good project using tui.
77 | It removes boilerplate, and improves the developer experience by using the power of typemaps, and dependency injection
78 |
79 | # Installation
80 | Run the following within your project directory
81 | ```bash
82 | cargo add widgetui
83 | ```
84 | # Introduction
85 |
86 | Widgetui is a wrapper over Ratatui's Crossterm backend which allows for powerful abstraction, and simplifies creating a good app within Ratatui.
87 | ## Why pick this over Ratatui?
88 | Widgetui isn't meant to replace or undermine Ratatui. It is simply a wrapper. Without Ratatui, this crate would not exist, as well, you will still require Ratatui and Crossterm crates just to work with the apps.
89 |
90 | **TLDR; Don't, use both together to improve developer experience, and build your apps faster!**
91 |
92 | # Quickstart
93 | ```rust
94 | use crossterm::event::KeyCode;
95 | use ratatui::widgets::Paragraph;
96 | use widgetui::*;
97 |
98 | use std::error::Error;
99 |
100 | fn widget(mut frame: ResMut, mut events: ResMut) -> WidgetResult {
101 | let size = frame.size();
102 | frame.render_widget(Paragraph::new("Hello, world!"), size);
103 |
104 | if events.key(KeyCode::Char('q')) {
105 | events.register_exit();
106 | }
107 |
108 | Ok(())
109 | }
110 |
111 | fn main() -> Result<(), impl Error> {
112 | App::new(100)?.widgets(widget).run()
113 | }
114 | ```
115 |
116 | The above will create an application that will display an empty terminal window, then close once you press `q`.
117 |
118 | This application, with many less lines, will render the same thing that Ratatui's Quickstart renders.
119 | # Documentation
120 | Documentation can be found on [docs.rs](https://docs.rs/widgetui).
121 | Need help? Check the [wiki](https://github.com/TheEmeraldBee/widgetui/wiki)!
122 |
123 | # Fun Facts
124 | - I chose `WidgetFrame` because if I just used `Widget`, then you couldn't do the awesome
125 | ```rust
126 | use widgetui::*;
127 | use widgetui::ratatui::prelude::*;
128 | ```
129 |
130 | - It took about 10 hours to get this project initially set up!
131 | - Much longer after I decided to add bevy system like widget methods.
132 |
133 | - You used to have to take in a States struct, but in order to fix it, there is a lot of behind the scenes things going on!
134 |
135 | - You can only use 11 states in the same widget!
136 | - If you need more, please add an issue with details on why, and we may consider adding access to more at once!
137 |
--------------------------------------------------------------------------------
/examples/custom_chunk.rs:
--------------------------------------------------------------------------------
1 | use std::error::Error;
2 |
3 | use ratatui::{
4 | prelude::{Constraint, Direction, Layout},
5 | widgets::Paragraph,
6 | };
7 | use widgetui::*;
8 |
9 | struct TestChunk;
10 |
11 | pub fn chunk_generator(frame: Res, mut chunks: ResMut) -> WidgetResult {
12 | // A Custom macro to simplify creating your chunks!
13 | let chunk = layout! {
14 | frame.size(),
15 | (%50),
16 | (#1) => {#3, %100, #3},
17 | (%50)
18 | }[1][1];
19 |
20 | chunks.register_chunk::(chunk);
21 |
22 | Ok(())
23 | }
24 |
25 | pub fn render(
26 | mut frame: ResMut,
27 | chunks: Res,
28 | mut events: ResMut,
29 | ) -> WidgetResult {
30 | let chunk = chunks.get_chunk::()?;
31 |
32 | frame.render_widget(Paragraph::new("Hello, world!"), chunk);
33 |
34 | if events.key(crossterm::event::KeyCode::Char('q')) {
35 | events.register_exit();
36 | }
37 |
38 | Ok(())
39 | }
40 |
41 | fn main() -> Result<(), Box> {
42 | Ok(App::new(100)?
43 | .handle_panics()
44 | .widgets((chunk_generator, render))
45 | .run()?)
46 | }
47 |
--------------------------------------------------------------------------------
/examples/custom_set.rs:
--------------------------------------------------------------------------------
1 | use std::error::Error;
2 |
3 | use ratatui::widgets::Paragraph;
4 | use widgetui::*;
5 |
6 | #[derive(State)]
7 | pub struct CoolState {
8 | pub q_count: i32,
9 | }
10 |
11 | impl Default for CoolState {
12 | fn default() -> Self {
13 | Self { q_count: 8 }
14 | }
15 | }
16 |
17 | pub fn widget(
18 | mut frame: ResMut,
19 | mut events: ResMut,
20 | mut state: ResMut,
21 | ) -> WidgetResult {
22 | if events.key(crossterm::event::KeyCode::Char('q')) {
23 | state.q_count -= 1;
24 | if state.q_count <= 0 {
25 | events.register_exit();
26 | return Ok(());
27 | }
28 | }
29 |
30 | let size = frame.size();
31 |
32 | frame.render_widget(
33 | Paragraph::new(format!("Press `q` {} more times", state.q_count)),
34 | size,
35 | );
36 |
37 | Ok(())
38 | }
39 |
40 | #[set]
41 | pub fn CoolSet(app: App) -> App {
42 | app.widgets(widget).states(CoolState::default())
43 | }
44 |
45 | fn main() -> Result<(), Box> {
46 | Ok(App::new(100)?.sets(CoolSet).run()?)
47 | }
48 |
--------------------------------------------------------------------------------
/examples/custom_state.rs:
--------------------------------------------------------------------------------
1 | use std::error::Error;
2 |
3 | use ratatui::widgets::Paragraph;
4 | use widgetui::*;
5 |
6 | #[derive(State)]
7 | pub struct CustomState {
8 | state: i32,
9 | }
10 |
11 | pub struct CustomChunk;
12 |
13 | pub fn handle_state(
14 | mut frame: ResMut,
15 | mut custom_state: ResMut,
16 | mut events: ResMut,
17 | mut chunks: ResMut,
18 | ) -> WidgetResult {
19 | // Register A Test Chunk
20 | chunks.register_chunk::(frame.size());
21 | let chunk = chunks.get_chunk::()?;
22 |
23 | custom_state.state += 1;
24 |
25 | if custom_state.state >= 50 {
26 | events.register_exit();
27 | }
28 |
29 | frame.render_widget(
30 | Paragraph::new(format!("Custom State: {}", custom_state.state)),
31 | chunk,
32 | );
33 |
34 | Ok(())
35 | }
36 |
37 | fn main() -> Result<(), Box> {
38 | Ok(App::new(100)?
39 | .states(CustomState { state: 0 })
40 | .widgets(handle_state)
41 | .handle_panics()
42 | .run()?)
43 | }
44 |
--------------------------------------------------------------------------------
/examples/message.rs:
--------------------------------------------------------------------------------
1 | use std::{error::Error, time::Duration};
2 |
3 | use ratatui::prelude::{Constraint, Direction, Layout};
4 | use widgetui::{
5 | widgets::message::{Message, MessageChunk, MessageState},
6 | *,
7 | };
8 |
9 | fn chunk_builder(frame: Res<'_, WidgetFrame>, mut chunks: ResMut<'_, Chunks>) -> WidgetResult {
10 | let popup = layout![
11 | frame.size(),
12 | (%50),
13 | (>3) => {
14 | %10,
15 | %80,
16 | %10
17 | },
18 | (%50)
19 | ][1][1];
20 |
21 | chunks.register_chunk::(popup);
22 |
23 | Ok(())
24 | }
25 |
26 | fn my_widget(mut events: ResMut, mut message: ResMut) -> WidgetResult {
27 | if events.key(crossterm::event::KeyCode::Char('m')) {
28 | message.render_message("Custom Message", Duration::from_millis(500));
29 | }
30 |
31 | if events.key(crossterm::event::KeyCode::Char('n')) {
32 | message.render_message("Cool", Duration::from_millis(500));
33 | }
34 |
35 | if events.key(crossterm::event::KeyCode::Char('q')) {
36 | events.register_exit()
37 | }
38 |
39 | Ok(())
40 | }
41 |
42 | fn main() -> Result<(), Box> {
43 | Ok(App::new(100)?
44 | .handle_panics()
45 | .widgets((chunk_builder, my_widget))
46 | .sets(Message)
47 | .run()?)
48 | }
49 |
--------------------------------------------------------------------------------
/examples/minimal.rs:
--------------------------------------------------------------------------------
1 | use crossterm::event::KeyCode;
2 | use ratatui::widgets::Paragraph;
3 | use widgetui::*;
4 |
5 | use std::error::Error;
6 |
7 | fn widget(mut frame: ResMut, mut events: ResMut) -> WidgetResult {
8 | let size = frame.size();
9 | frame.render_widget(Paragraph::new("Hello, world!"), size);
10 |
11 | if events.key(KeyCode::Char('q')) {
12 | events.register_exit();
13 | }
14 |
15 | Ok(())
16 | }
17 |
18 | fn main() -> Result<(), Box> {
19 | Ok(App::new(100)?.widgets(widget).run()?)
20 | }
21 |
--------------------------------------------------------------------------------
/src/app.rs:
--------------------------------------------------------------------------------
1 | use std::{
2 | any::{Any, TypeId},
3 | cell::RefCell,
4 | collections::HashMap,
5 | error::Error,
6 | io,
7 | ops::Deref,
8 | time::{Duration, SystemTime},
9 | };
10 |
11 | use ratatui::{buffer::Buffer, prelude::Backend};
12 |
13 | use crate::{
14 | chunks::Chunks,
15 | events::Events,
16 | set::{Set, Sets},
17 | setup::{reset_terminal, restore_terminal, setup_terminal, WidgetFrame, WidgetTerminal},
18 | states::{MultiFromStates, States, Time},
19 | widget::{into_widget::IntoWidget, into_widget_set::IntoWidgetSet, Widget},
20 | widgets::message::MessageState,
21 | Res, ResMut, WidgetParam, WidgetResult,
22 | };
23 |
24 | /// The powerhouse of widgetui, runs all defined widgets for you
25 | pub struct App {
26 | terminal: WidgetTerminal,
27 | widgets: Vec>,
28 | pub(crate) states: States,
29 | clock: Duration,
30 | }
31 |
32 | impl App {
33 | /// Create a new app with the given clock time (in ms)
34 | pub fn new(clock: u64) -> Result {
35 | let terminal = setup_terminal()?;
36 |
37 | Ok(Self {
38 | terminal,
39 | widgets: vec![],
40 | states: HashMap::new(),
41 | clock: Duration::from_millis(clock),
42 | }
43 | .handle_panics()
44 | .states((Chunks::default(), Time::default(), Events::default())))
45 | }
46 |
47 | /// Running this will ensure that any panic that happens, this will catch
48 | /// And prevent your terminal from messing up.
49 | pub fn handle_panics(self) -> Self {
50 | let original_hook = std::panic::take_hook();
51 |
52 | std::panic::set_hook(Box::new(move |panic| {
53 | reset_terminal().unwrap();
54 | original_hook(panic);
55 | }));
56 |
57 | self
58 | }
59 |
60 | /// Adds the following Widgets to the system.
61 | /// This will take in a tuple of widgets, or a single widget.
62 | pub fn widgets(mut self, widget: impl IntoWidgetSet) -> Self {
63 | for widget in widget.into_widget_set() {
64 | self.widgets.push(widget);
65 | }
66 | self
67 | }
68 |
69 | pub fn widget(mut self, widget: W) -> Self {
70 | self.widgets.push(Box::new(widget));
71 | self
72 | }
73 |
74 | /// Add the following states to the system
75 | /// This will take in a state or a tuple of states.
76 | pub fn states(self, state: S) -> Self {
77 | state.insert_states(self)
78 | }
79 |
80 | /// Add a set to the system
81 | pub fn sets(self, set: impl Sets) -> Self {
82 | set.register_sets(self)
83 | }
84 |
85 | /// Run the app, returning an error if any of the functions error out.
86 | pub fn run(mut self) -> WidgetResult {
87 | let result = self.inner_run();
88 |
89 | restore_terminal(self.terminal)?;
90 |
91 | result
92 | }
93 |
94 | fn inner_run(&mut self) -> WidgetResult {
95 | self.terminal.hide_cursor()?;
96 |
97 | loop {
98 | self.terminal.autoresize()?;
99 | let mut frame = self.terminal.get_frame();
100 |
101 | let widget_frame = WidgetFrame {
102 | cursor_position: None,
103 | buffer: frame.buffer_mut().clone(),
104 | viewport_area: frame.area(),
105 | count: frame.count(),
106 | };
107 |
108 | self.states.insert(
109 | TypeId::of::(),
110 | RefCell::new(Box::new(widget_frame)),
111 | );
112 |
113 | {
114 | let mut chunks = ResMut::::retrieve(&self.states);
115 |
116 | chunks.clear();
117 |
118 | let mut events = ResMut::::retrieve(&self.states);
119 |
120 | let mut time = ResMut::