├── .gitignore ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE.md ├── README.md ├── TODO.md ├── screenshots ├── screenshot_1.png ├── screenshot_2.png ├── screenshot_3.png ├── screenshot_4.png ├── screenshot_5.png ├── screenshot_p0.png ├── screenshot_p1.png ├── screenshot_p2.png ├── screenshot_p3.png ├── screenshot_p4.png ├── screenshot_p5.png └── screenshot_p6.png └── src ├── canvas.rs ├── color.rs ├── config.rs ├── main.rs ├── pipe.rs ├── plane_2d.rs ├── screensaver.rs └── terminal.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # These are backup files generated by rustfmt 7 | **/*.rs.bk 8 | 9 | # MSVC Windows builds of rustc generate these, which store debugging information 10 | *.pdb 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [1.3.0] - 2024-09-07 9 | 10 | ### Added 11 | 12 | - Keybind `,`: change speed by -1. 13 | - Keybind `.`: change speed by +1. 14 | - Keybind `<`: change speed by -10. 15 | - Keybind `>`: change speed by +10. 16 | - Option `-b`/`--bg-color`: background color (optional, by default transparent). 17 | 18 | ### Fixed 19 | 20 | - Lighten rather than darken if pipe piece color is < than the `--darken-min` value. 21 | - Fix invalid handling of `--max-drawn-pieces = 0`. 22 | 23 | ## [1.2.0] - 2024-08-24 24 | 25 | ### Added 26 | 27 | - Depth mode (RGB palette only). 28 | - Gradient mode (RGB palette only). 29 | - Stats widget. 30 | - Keybind `Ctrl-C`: exit. 31 | - Keybind `l` (lowercase L): redraw. 32 | - Keybind `s`: toggle the stats widget. 33 | - Option `g`/`--gradient`: enable gradient mode. 34 | - Option `--gradient-step`: step between gradient transitions. 35 | - Option `-d`/`--depth-mode`: enable depth mode. 36 | - Option `--layer-max-drawn-pieces`: maximal number of pieces in the current layer (depth mode). 37 | - Option `-F`/`--darken-factor`: how much to darken pipe pieces in previous layers? (depth mode). 38 | - Option `-M`/`--darken-min`: the color to gradually darken to (depth mode). 39 | - Option `-s`/`--show-stats`: toggle the stats widget. 40 | 41 | ### Changed 42 | 43 | - Changed default FPS from 20 to 24 frames. 44 | 45 | ## [1.1.0] - 2024-05-09 46 | 47 | ### Added 48 | 49 | - Support for defining custom piece sets. 50 | - Keybind `c`: manual screen cleaning. 51 | 52 | ### Changed 53 | 54 | - The default piece set are now bold pipes (ID: 6). 55 | - Slightly edited the help message. 56 | 57 | ## [1.0.0] - 2024-05-03 58 | 59 | ### Added 60 | 61 | - 6 available piece sets. 62 | - Each pipe has its own color; available palettes are: none (colorless), base colors and RGB. 63 | - Changeable FPS. 64 | - The minimal and maximal length of pipes can be changed. 65 | - The maximal number of drawn characters can be changed. To ignore this setting specify 0 via CLI. 66 | - The probability of turning pipes is changeable, it's given as a percentage in decimal form. 67 | - Screensaver can be paused by pressing spacebar, close with q, Q or escape key. 68 | -------------------------------------------------------------------------------- /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 = "aho-corasick" 7 | version = "1.1.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "anstream" 16 | version = "0.6.15" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" 19 | dependencies = [ 20 | "anstyle", 21 | "anstyle-parse", 22 | "anstyle-query", 23 | "anstyle-wincon", 24 | "colorchoice", 25 | "is_terminal_polyfill", 26 | "utf8parse", 27 | ] 28 | 29 | [[package]] 30 | name = "anstyle" 31 | version = "1.0.8" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" 34 | 35 | [[package]] 36 | name = "anstyle-parse" 37 | version = "0.2.5" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" 40 | dependencies = [ 41 | "utf8parse", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle-query" 46 | version = "1.1.1" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" 49 | dependencies = [ 50 | "windows-sys 0.52.0", 51 | ] 52 | 53 | [[package]] 54 | name = "anstyle-wincon" 55 | version = "3.0.4" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" 58 | dependencies = [ 59 | "anstyle", 60 | "windows-sys 0.52.0", 61 | ] 62 | 63 | [[package]] 64 | name = "anyhow" 65 | version = "1.0.87" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "10f00e1f6e58a40e807377c75c6a7f97bf9044fab57816f2414e6f5f4499d7b8" 68 | 69 | [[package]] 70 | name = "atomic" 71 | version = "0.6.0" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "8d818003e740b63afc82337e3160717f4f63078720a810b7b903e70a5d1d2994" 74 | dependencies = [ 75 | "bytemuck", 76 | ] 77 | 78 | [[package]] 79 | name = "autocfg" 80 | version = "1.3.0" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" 83 | 84 | [[package]] 85 | name = "base64" 86 | version = "0.21.7" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" 89 | 90 | [[package]] 91 | name = "bit-set" 92 | version = "0.5.3" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" 95 | dependencies = [ 96 | "bit-vec", 97 | ] 98 | 99 | [[package]] 100 | name = "bit-vec" 101 | version = "0.6.3" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" 104 | 105 | [[package]] 106 | name = "bitflags" 107 | version = "1.3.2" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 110 | 111 | [[package]] 112 | name = "bitflags" 113 | version = "2.6.0" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 116 | 117 | [[package]] 118 | name = "block-buffer" 119 | version = "0.10.4" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 122 | dependencies = [ 123 | "generic-array", 124 | ] 125 | 126 | [[package]] 127 | name = "bytemuck" 128 | version = "1.18.0" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" 131 | 132 | [[package]] 133 | name = "byteorder" 134 | version = "1.5.0" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 137 | 138 | [[package]] 139 | name = "castaway" 140 | version = "0.2.3" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" 143 | dependencies = [ 144 | "rustversion", 145 | ] 146 | 147 | [[package]] 148 | name = "cfg-if" 149 | version = "1.0.0" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 152 | 153 | [[package]] 154 | name = "cfg_aliases" 155 | version = "0.1.1" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" 158 | 159 | [[package]] 160 | name = "clap" 161 | version = "4.5.17" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" 164 | dependencies = [ 165 | "clap_builder", 166 | "clap_derive", 167 | ] 168 | 169 | [[package]] 170 | name = "clap_builder" 171 | version = "4.5.17" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" 174 | dependencies = [ 175 | "anstream", 176 | "anstyle", 177 | "clap_lex", 178 | "strsim 0.11.1", 179 | ] 180 | 181 | [[package]] 182 | name = "clap_derive" 183 | version = "4.5.13" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" 186 | dependencies = [ 187 | "heck", 188 | "proc-macro2", 189 | "quote", 190 | "syn 2.0.77", 191 | ] 192 | 193 | [[package]] 194 | name = "clap_lex" 195 | version = "0.7.2" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" 198 | 199 | [[package]] 200 | name = "colorchoice" 201 | version = "1.0.2" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" 204 | 205 | [[package]] 206 | name = "compact_str" 207 | version = "0.8.0" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "6050c3a16ddab2e412160b31f2c871015704239bca62f72f6e5f0be631d3f644" 210 | dependencies = [ 211 | "castaway", 212 | "cfg-if", 213 | "itoa", 214 | "rustversion", 215 | "ryu", 216 | "static_assertions", 217 | ] 218 | 219 | [[package]] 220 | name = "cpufeatures" 221 | version = "0.2.14" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" 224 | dependencies = [ 225 | "libc", 226 | ] 227 | 228 | [[package]] 229 | name = "crypto-common" 230 | version = "0.1.6" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 233 | dependencies = [ 234 | "generic-array", 235 | "typenum", 236 | ] 237 | 238 | [[package]] 239 | name = "csscolorparser" 240 | version = "0.6.2" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "eb2a7d3066da2de787b7f032c736763eb7ae5d355f81a68bab2675a96008b0bf" 243 | dependencies = [ 244 | "lab", 245 | "phf", 246 | ] 247 | 248 | [[package]] 249 | name = "deltae" 250 | version = "0.3.2" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "5729f5117e208430e437df2f4843f5e5952997175992d1414f94c57d61e270b4" 253 | 254 | [[package]] 255 | name = "digest" 256 | version = "0.10.7" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 259 | dependencies = [ 260 | "block-buffer", 261 | "crypto-common", 262 | ] 263 | 264 | [[package]] 265 | name = "dirs" 266 | version = "4.0.0" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" 269 | dependencies = [ 270 | "dirs-sys", 271 | ] 272 | 273 | [[package]] 274 | name = "dirs-sys" 275 | version = "0.3.7" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" 278 | dependencies = [ 279 | "libc", 280 | "redox_users", 281 | "winapi", 282 | ] 283 | 284 | [[package]] 285 | name = "errno" 286 | version = "0.3.9" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" 289 | dependencies = [ 290 | "libc", 291 | "windows-sys 0.52.0", 292 | ] 293 | 294 | [[package]] 295 | name = "euclid" 296 | version = "0.22.11" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "ad9cdb4b747e485a12abb0e6566612956c7a1bafa3bdb8d682c5b6d403589e48" 299 | dependencies = [ 300 | "num-traits", 301 | ] 302 | 303 | [[package]] 304 | name = "eyre" 305 | version = "0.6.12" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" 308 | dependencies = [ 309 | "indenter", 310 | "once_cell", 311 | ] 312 | 313 | [[package]] 314 | name = "fancy-regex" 315 | version = "0.11.0" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2" 318 | dependencies = [ 319 | "bit-set", 320 | "regex", 321 | ] 322 | 323 | [[package]] 324 | name = "fastrand" 325 | version = "2.1.1" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" 328 | 329 | [[package]] 330 | name = "filedescriptor" 331 | version = "0.8.2" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "7199d965852c3bac31f779ef99cbb4537f80e952e2d6aa0ffeb30cce00f4f46e" 334 | dependencies = [ 335 | "libc", 336 | "thiserror", 337 | "winapi", 338 | ] 339 | 340 | [[package]] 341 | name = "finl_unicode" 342 | version = "1.2.0" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" 345 | 346 | [[package]] 347 | name = "fixedbitset" 348 | version = "0.4.2" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" 351 | 352 | [[package]] 353 | name = "fnv" 354 | version = "1.0.7" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 357 | 358 | [[package]] 359 | name = "generic-array" 360 | version = "0.14.7" 361 | source = "registry+https://github.com/rust-lang/crates.io-index" 362 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 363 | dependencies = [ 364 | "typenum", 365 | "version_check", 366 | ] 367 | 368 | [[package]] 369 | name = "getrandom" 370 | version = "0.2.15" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 373 | dependencies = [ 374 | "cfg-if", 375 | "libc", 376 | "wasi", 377 | ] 378 | 379 | [[package]] 380 | name = "heck" 381 | version = "0.5.0" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 384 | 385 | [[package]] 386 | name = "hex" 387 | version = "0.4.3" 388 | source = "registry+https://github.com/rust-lang/crates.io-index" 389 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 390 | 391 | [[package]] 392 | name = "hex_color" 393 | version = "3.0.0" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "d37f101bf4c633f7ca2e4b5e136050314503dd198e78e325ea602c327c484ef0" 396 | dependencies = [ 397 | "rand", 398 | ] 399 | 400 | [[package]] 401 | name = "indenter" 402 | version = "0.3.3" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" 405 | 406 | [[package]] 407 | name = "is_terminal_polyfill" 408 | version = "1.70.1" 409 | source = "registry+https://github.com/rust-lang/crates.io-index" 410 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 411 | 412 | [[package]] 413 | name = "itoa" 414 | version = "1.0.11" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 417 | 418 | [[package]] 419 | name = "lab" 420 | version = "0.11.0" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "bf36173d4167ed999940f804952e6b08197cae5ad5d572eb4db150ce8ad5d58f" 423 | 424 | [[package]] 425 | name = "lazy_static" 426 | version = "1.5.0" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 429 | 430 | [[package]] 431 | name = "libc" 432 | version = "0.2.158" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" 435 | 436 | [[package]] 437 | name = "libredox" 438 | version = "0.1.3" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" 441 | dependencies = [ 442 | "bitflags 2.6.0", 443 | "libc", 444 | ] 445 | 446 | [[package]] 447 | name = "linux-raw-sys" 448 | version = "0.4.14" 449 | source = "registry+https://github.com/rust-lang/crates.io-index" 450 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 451 | 452 | [[package]] 453 | name = "log" 454 | version = "0.4.22" 455 | source = "registry+https://github.com/rust-lang/crates.io-index" 456 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 457 | 458 | [[package]] 459 | name = "mac_address" 460 | version = "1.1.7" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "8836fae9d0d4be2c8b4efcdd79e828a2faa058a90d005abf42f91cac5493a08e" 463 | dependencies = [ 464 | "nix 0.28.0", 465 | "winapi", 466 | ] 467 | 468 | [[package]] 469 | name = "memchr" 470 | version = "2.7.4" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 473 | 474 | [[package]] 475 | name = "memmem" 476 | version = "0.1.1" 477 | source = "registry+https://github.com/rust-lang/crates.io-index" 478 | checksum = "a64a92489e2744ce060c349162be1c5f33c6969234104dbd99ddb5feb08b8c15" 479 | 480 | [[package]] 481 | name = "memoffset" 482 | version = "0.7.1" 483 | source = "registry+https://github.com/rust-lang/crates.io-index" 484 | checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" 485 | dependencies = [ 486 | "autocfg", 487 | ] 488 | 489 | [[package]] 490 | name = "memoffset" 491 | version = "0.9.1" 492 | source = "registry+https://github.com/rust-lang/crates.io-index" 493 | checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" 494 | dependencies = [ 495 | "autocfg", 496 | ] 497 | 498 | [[package]] 499 | name = "minimal-lexical" 500 | version = "0.2.1" 501 | source = "registry+https://github.com/rust-lang/crates.io-index" 502 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 503 | 504 | [[package]] 505 | name = "nix" 506 | version = "0.26.4" 507 | source = "registry+https://github.com/rust-lang/crates.io-index" 508 | checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" 509 | dependencies = [ 510 | "bitflags 1.3.2", 511 | "cfg-if", 512 | "libc", 513 | "memoffset 0.7.1", 514 | "pin-utils", 515 | ] 516 | 517 | [[package]] 518 | name = "nix" 519 | version = "0.28.0" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" 522 | dependencies = [ 523 | "bitflags 2.6.0", 524 | "cfg-if", 525 | "cfg_aliases", 526 | "libc", 527 | "memoffset 0.9.1", 528 | ] 529 | 530 | [[package]] 531 | name = "nom" 532 | version = "7.1.3" 533 | source = "registry+https://github.com/rust-lang/crates.io-index" 534 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 535 | dependencies = [ 536 | "memchr", 537 | "minimal-lexical", 538 | ] 539 | 540 | [[package]] 541 | name = "num-derive" 542 | version = "0.3.3" 543 | source = "registry+https://github.com/rust-lang/crates.io-index" 544 | checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" 545 | dependencies = [ 546 | "proc-macro2", 547 | "quote", 548 | "syn 1.0.109", 549 | ] 550 | 551 | [[package]] 552 | name = "num-traits" 553 | version = "0.2.19" 554 | source = "registry+https://github.com/rust-lang/crates.io-index" 555 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 556 | dependencies = [ 557 | "autocfg", 558 | ] 559 | 560 | [[package]] 561 | name = "once_cell" 562 | version = "1.19.0" 563 | source = "registry+https://github.com/rust-lang/crates.io-index" 564 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 565 | 566 | [[package]] 567 | name = "ordered-float" 568 | version = "4.2.2" 569 | source = "registry+https://github.com/rust-lang/crates.io-index" 570 | checksum = "4a91171844676f8c7990ce64959210cd2eaef32c2612c50f9fae9f8aaa6065a6" 571 | dependencies = [ 572 | "num-traits", 573 | ] 574 | 575 | [[package]] 576 | name = "pest" 577 | version = "2.7.12" 578 | source = "registry+https://github.com/rust-lang/crates.io-index" 579 | checksum = "9c73c26c01b8c87956cea613c907c9d6ecffd8d18a2a5908e5de0adfaa185cea" 580 | dependencies = [ 581 | "memchr", 582 | "thiserror", 583 | "ucd-trie", 584 | ] 585 | 586 | [[package]] 587 | name = "pest_derive" 588 | version = "2.7.12" 589 | source = "registry+https://github.com/rust-lang/crates.io-index" 590 | checksum = "664d22978e2815783adbdd2c588b455b1bd625299ce36b2a99881ac9627e6d8d" 591 | dependencies = [ 592 | "pest", 593 | "pest_generator", 594 | ] 595 | 596 | [[package]] 597 | name = "pest_generator" 598 | version = "2.7.12" 599 | source = "registry+https://github.com/rust-lang/crates.io-index" 600 | checksum = "a2d5487022d5d33f4c30d91c22afa240ce2a644e87fe08caad974d4eab6badbe" 601 | dependencies = [ 602 | "pest", 603 | "pest_meta", 604 | "proc-macro2", 605 | "quote", 606 | "syn 2.0.77", 607 | ] 608 | 609 | [[package]] 610 | name = "pest_meta" 611 | version = "2.7.12" 612 | source = "registry+https://github.com/rust-lang/crates.io-index" 613 | checksum = "0091754bbd0ea592c4deb3a122ce8ecbb0753b738aa82bc055fcc2eccc8d8174" 614 | dependencies = [ 615 | "once_cell", 616 | "pest", 617 | "sha2", 618 | ] 619 | 620 | [[package]] 621 | name = "phf" 622 | version = "0.11.2" 623 | source = "registry+https://github.com/rust-lang/crates.io-index" 624 | checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" 625 | dependencies = [ 626 | "phf_macros", 627 | "phf_shared", 628 | ] 629 | 630 | [[package]] 631 | name = "phf_codegen" 632 | version = "0.11.2" 633 | source = "registry+https://github.com/rust-lang/crates.io-index" 634 | checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" 635 | dependencies = [ 636 | "phf_generator", 637 | "phf_shared", 638 | ] 639 | 640 | [[package]] 641 | name = "phf_generator" 642 | version = "0.11.2" 643 | source = "registry+https://github.com/rust-lang/crates.io-index" 644 | checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" 645 | dependencies = [ 646 | "phf_shared", 647 | "rand", 648 | ] 649 | 650 | [[package]] 651 | name = "phf_macros" 652 | version = "0.11.2" 653 | source = "registry+https://github.com/rust-lang/crates.io-index" 654 | checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" 655 | dependencies = [ 656 | "phf_generator", 657 | "phf_shared", 658 | "proc-macro2", 659 | "quote", 660 | "syn 2.0.77", 661 | ] 662 | 663 | [[package]] 664 | name = "phf_shared" 665 | version = "0.11.2" 666 | source = "registry+https://github.com/rust-lang/crates.io-index" 667 | checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" 668 | dependencies = [ 669 | "siphasher", 670 | ] 671 | 672 | [[package]] 673 | name = "pin-utils" 674 | version = "0.1.0" 675 | source = "registry+https://github.com/rust-lang/crates.io-index" 676 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 677 | 678 | [[package]] 679 | name = "ppv-lite86" 680 | version = "0.2.20" 681 | source = "registry+https://github.com/rust-lang/crates.io-index" 682 | checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" 683 | dependencies = [ 684 | "zerocopy", 685 | ] 686 | 687 | [[package]] 688 | name = "proc-macro2" 689 | version = "1.0.86" 690 | source = "registry+https://github.com/rust-lang/crates.io-index" 691 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" 692 | dependencies = [ 693 | "unicode-ident", 694 | ] 695 | 696 | [[package]] 697 | name = "quote" 698 | version = "1.0.37" 699 | source = "registry+https://github.com/rust-lang/crates.io-index" 700 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 701 | dependencies = [ 702 | "proc-macro2", 703 | ] 704 | 705 | [[package]] 706 | name = "rand" 707 | version = "0.8.5" 708 | source = "registry+https://github.com/rust-lang/crates.io-index" 709 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 710 | dependencies = [ 711 | "libc", 712 | "rand_chacha", 713 | "rand_core", 714 | ] 715 | 716 | [[package]] 717 | name = "rand_chacha" 718 | version = "0.3.1" 719 | source = "registry+https://github.com/rust-lang/crates.io-index" 720 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 721 | dependencies = [ 722 | "ppv-lite86", 723 | "rand_core", 724 | ] 725 | 726 | [[package]] 727 | name = "rand_core" 728 | version = "0.6.4" 729 | source = "registry+https://github.com/rust-lang/crates.io-index" 730 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 731 | dependencies = [ 732 | "getrandom", 733 | ] 734 | 735 | [[package]] 736 | name = "redox_users" 737 | version = "0.4.6" 738 | source = "registry+https://github.com/rust-lang/crates.io-index" 739 | checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" 740 | dependencies = [ 741 | "getrandom", 742 | "libredox", 743 | "thiserror", 744 | ] 745 | 746 | [[package]] 747 | name = "regex" 748 | version = "1.10.6" 749 | source = "registry+https://github.com/rust-lang/crates.io-index" 750 | checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" 751 | dependencies = [ 752 | "aho-corasick", 753 | "memchr", 754 | "regex-automata", 755 | "regex-syntax", 756 | ] 757 | 758 | [[package]] 759 | name = "regex-automata" 760 | version = "0.4.7" 761 | source = "registry+https://github.com/rust-lang/crates.io-index" 762 | checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" 763 | dependencies = [ 764 | "aho-corasick", 765 | "memchr", 766 | "regex-syntax", 767 | ] 768 | 769 | [[package]] 770 | name = "regex-syntax" 771 | version = "0.8.4" 772 | source = "registry+https://github.com/rust-lang/crates.io-index" 773 | checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" 774 | 775 | [[package]] 776 | name = "rustix" 777 | version = "0.38.36" 778 | source = "registry+https://github.com/rust-lang/crates.io-index" 779 | checksum = "3f55e80d50763938498dd5ebb18647174e0c76dc38c5505294bb224624f30f36" 780 | dependencies = [ 781 | "bitflags 2.6.0", 782 | "errno", 783 | "libc", 784 | "linux-raw-sys", 785 | "windows-sys 0.52.0", 786 | ] 787 | 788 | [[package]] 789 | name = "rustversion" 790 | version = "1.0.17" 791 | source = "registry+https://github.com/rust-lang/crates.io-index" 792 | checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" 793 | 794 | [[package]] 795 | name = "rxpipes" 796 | version = "1.3.0" 797 | dependencies = [ 798 | "clap", 799 | "compact_str", 800 | "eyre", 801 | "hex_color", 802 | "rand", 803 | "termwiz", 804 | "unicode-segmentation", 805 | ] 806 | 807 | [[package]] 808 | name = "ryu" 809 | version = "1.0.18" 810 | source = "registry+https://github.com/rust-lang/crates.io-index" 811 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 812 | 813 | [[package]] 814 | name = "semver" 815 | version = "0.11.0" 816 | source = "registry+https://github.com/rust-lang/crates.io-index" 817 | checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" 818 | dependencies = [ 819 | "semver-parser", 820 | ] 821 | 822 | [[package]] 823 | name = "semver-parser" 824 | version = "0.10.2" 825 | source = "registry+https://github.com/rust-lang/crates.io-index" 826 | checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" 827 | dependencies = [ 828 | "pest", 829 | ] 830 | 831 | [[package]] 832 | name = "sha2" 833 | version = "0.10.8" 834 | source = "registry+https://github.com/rust-lang/crates.io-index" 835 | checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" 836 | dependencies = [ 837 | "cfg-if", 838 | "cpufeatures", 839 | "digest", 840 | ] 841 | 842 | [[package]] 843 | name = "signal-hook" 844 | version = "0.3.17" 845 | source = "registry+https://github.com/rust-lang/crates.io-index" 846 | checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" 847 | dependencies = [ 848 | "libc", 849 | "signal-hook-registry", 850 | ] 851 | 852 | [[package]] 853 | name = "signal-hook-registry" 854 | version = "1.4.2" 855 | source = "registry+https://github.com/rust-lang/crates.io-index" 856 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 857 | dependencies = [ 858 | "libc", 859 | ] 860 | 861 | [[package]] 862 | name = "siphasher" 863 | version = "0.3.11" 864 | source = "registry+https://github.com/rust-lang/crates.io-index" 865 | checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" 866 | 867 | [[package]] 868 | name = "static_assertions" 869 | version = "1.1.0" 870 | source = "registry+https://github.com/rust-lang/crates.io-index" 871 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 872 | 873 | [[package]] 874 | name = "strsim" 875 | version = "0.10.0" 876 | source = "registry+https://github.com/rust-lang/crates.io-index" 877 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 878 | 879 | [[package]] 880 | name = "strsim" 881 | version = "0.11.1" 882 | source = "registry+https://github.com/rust-lang/crates.io-index" 883 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 884 | 885 | [[package]] 886 | name = "syn" 887 | version = "1.0.109" 888 | source = "registry+https://github.com/rust-lang/crates.io-index" 889 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 890 | dependencies = [ 891 | "proc-macro2", 892 | "quote", 893 | "unicode-ident", 894 | ] 895 | 896 | [[package]] 897 | name = "syn" 898 | version = "2.0.77" 899 | source = "registry+https://github.com/rust-lang/crates.io-index" 900 | checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" 901 | dependencies = [ 902 | "proc-macro2", 903 | "quote", 904 | "unicode-ident", 905 | ] 906 | 907 | [[package]] 908 | name = "tempfile" 909 | version = "3.12.0" 910 | source = "registry+https://github.com/rust-lang/crates.io-index" 911 | checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" 912 | dependencies = [ 913 | "cfg-if", 914 | "fastrand", 915 | "once_cell", 916 | "rustix", 917 | "windows-sys 0.59.0", 918 | ] 919 | 920 | [[package]] 921 | name = "terminfo" 922 | version = "0.8.0" 923 | source = "registry+https://github.com/rust-lang/crates.io-index" 924 | checksum = "666cd3a6681775d22b200409aad3b089c5b99fb11ecdd8a204d9d62f8148498f" 925 | dependencies = [ 926 | "dirs", 927 | "fnv", 928 | "nom", 929 | "phf", 930 | "phf_codegen", 931 | ] 932 | 933 | [[package]] 934 | name = "termios" 935 | version = "0.3.3" 936 | source = "registry+https://github.com/rust-lang/crates.io-index" 937 | checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b" 938 | dependencies = [ 939 | "libc", 940 | ] 941 | 942 | [[package]] 943 | name = "termwiz" 944 | version = "0.22.0" 945 | source = "registry+https://github.com/rust-lang/crates.io-index" 946 | checksum = "5a75313e21da5d4406ea31402035b3b97aa74c04356bdfafa5d1043ab4e551d1" 947 | dependencies = [ 948 | "anyhow", 949 | "base64", 950 | "bitflags 2.6.0", 951 | "fancy-regex", 952 | "filedescriptor", 953 | "finl_unicode", 954 | "fixedbitset", 955 | "hex", 956 | "lazy_static", 957 | "libc", 958 | "log", 959 | "memmem", 960 | "nix 0.26.4", 961 | "num-derive", 962 | "num-traits", 963 | "ordered-float", 964 | "pest", 965 | "pest_derive", 966 | "phf", 967 | "semver", 968 | "sha2", 969 | "signal-hook", 970 | "siphasher", 971 | "tempfile", 972 | "terminfo", 973 | "termios", 974 | "thiserror", 975 | "ucd-trie", 976 | "unicode-segmentation", 977 | "vtparse", 978 | "wezterm-bidi", 979 | "wezterm-blob-leases", 980 | "wezterm-color-types", 981 | "wezterm-dynamic", 982 | "wezterm-input-types", 983 | "winapi", 984 | ] 985 | 986 | [[package]] 987 | name = "thiserror" 988 | version = "1.0.63" 989 | source = "registry+https://github.com/rust-lang/crates.io-index" 990 | checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" 991 | dependencies = [ 992 | "thiserror-impl", 993 | ] 994 | 995 | [[package]] 996 | name = "thiserror-impl" 997 | version = "1.0.63" 998 | source = "registry+https://github.com/rust-lang/crates.io-index" 999 | checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" 1000 | dependencies = [ 1001 | "proc-macro2", 1002 | "quote", 1003 | "syn 2.0.77", 1004 | ] 1005 | 1006 | [[package]] 1007 | name = "typenum" 1008 | version = "1.17.0" 1009 | source = "registry+https://github.com/rust-lang/crates.io-index" 1010 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" 1011 | 1012 | [[package]] 1013 | name = "ucd-trie" 1014 | version = "0.1.6" 1015 | source = "registry+https://github.com/rust-lang/crates.io-index" 1016 | checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" 1017 | 1018 | [[package]] 1019 | name = "unicode-ident" 1020 | version = "1.0.12" 1021 | source = "registry+https://github.com/rust-lang/crates.io-index" 1022 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 1023 | 1024 | [[package]] 1025 | name = "unicode-segmentation" 1026 | version = "1.11.0" 1027 | source = "registry+https://github.com/rust-lang/crates.io-index" 1028 | checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" 1029 | 1030 | [[package]] 1031 | name = "utf8parse" 1032 | version = "0.2.2" 1033 | source = "registry+https://github.com/rust-lang/crates.io-index" 1034 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 1035 | 1036 | [[package]] 1037 | name = "uuid" 1038 | version = "1.10.0" 1039 | source = "registry+https://github.com/rust-lang/crates.io-index" 1040 | checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" 1041 | dependencies = [ 1042 | "atomic", 1043 | "getrandom", 1044 | ] 1045 | 1046 | [[package]] 1047 | name = "version_check" 1048 | version = "0.9.5" 1049 | source = "registry+https://github.com/rust-lang/crates.io-index" 1050 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 1051 | 1052 | [[package]] 1053 | name = "vtparse" 1054 | version = "0.6.2" 1055 | source = "registry+https://github.com/rust-lang/crates.io-index" 1056 | checksum = "6d9b2acfb050df409c972a37d3b8e08cdea3bddb0c09db9d53137e504cfabed0" 1057 | dependencies = [ 1058 | "utf8parse", 1059 | ] 1060 | 1061 | [[package]] 1062 | name = "wasi" 1063 | version = "0.11.0+wasi-snapshot-preview1" 1064 | source = "registry+https://github.com/rust-lang/crates.io-index" 1065 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1066 | 1067 | [[package]] 1068 | name = "wezterm-bidi" 1069 | version = "0.2.3" 1070 | source = "registry+https://github.com/rust-lang/crates.io-index" 1071 | checksum = "0c0a6e355560527dd2d1cf7890652f4f09bb3433b6aadade4c9b5ed76de5f3ec" 1072 | dependencies = [ 1073 | "log", 1074 | "wezterm-dynamic", 1075 | ] 1076 | 1077 | [[package]] 1078 | name = "wezterm-blob-leases" 1079 | version = "0.1.0" 1080 | source = "registry+https://github.com/rust-lang/crates.io-index" 1081 | checksum = "8e5a5e0adf7eed68976410def849a4bdab6f6e9f6163f152de9cb89deea9e60b" 1082 | dependencies = [ 1083 | "getrandom", 1084 | "mac_address", 1085 | "once_cell", 1086 | "sha2", 1087 | "thiserror", 1088 | "uuid", 1089 | ] 1090 | 1091 | [[package]] 1092 | name = "wezterm-color-types" 1093 | version = "0.3.0" 1094 | source = "registry+https://github.com/rust-lang/crates.io-index" 1095 | checksum = "7de81ef35c9010270d63772bebef2f2d6d1f2d20a983d27505ac850b8c4b4296" 1096 | dependencies = [ 1097 | "csscolorparser", 1098 | "deltae", 1099 | "lazy_static", 1100 | "wezterm-dynamic", 1101 | ] 1102 | 1103 | [[package]] 1104 | name = "wezterm-dynamic" 1105 | version = "0.2.0" 1106 | source = "registry+https://github.com/rust-lang/crates.io-index" 1107 | checksum = "dfb128bacfa86734e07681fb6068e34c144698e84ee022d6e009145d1abb77b5" 1108 | dependencies = [ 1109 | "log", 1110 | "ordered-float", 1111 | "strsim 0.10.0", 1112 | "thiserror", 1113 | "wezterm-dynamic-derive", 1114 | ] 1115 | 1116 | [[package]] 1117 | name = "wezterm-dynamic-derive" 1118 | version = "0.1.0" 1119 | source = "registry+https://github.com/rust-lang/crates.io-index" 1120 | checksum = "0c9f5ef318442d07b3d071f9f43ea40b80992f87faee14bb4d017b6991c307f0" 1121 | dependencies = [ 1122 | "proc-macro2", 1123 | "quote", 1124 | "syn 1.0.109", 1125 | ] 1126 | 1127 | [[package]] 1128 | name = "wezterm-input-types" 1129 | version = "0.1.0" 1130 | source = "registry+https://github.com/rust-lang/crates.io-index" 1131 | checksum = "7012add459f951456ec9d6c7e6fc340b1ce15d6fc9629f8c42853412c029e57e" 1132 | dependencies = [ 1133 | "bitflags 1.3.2", 1134 | "euclid", 1135 | "lazy_static", 1136 | "wezterm-dynamic", 1137 | ] 1138 | 1139 | [[package]] 1140 | name = "winapi" 1141 | version = "0.3.9" 1142 | source = "registry+https://github.com/rust-lang/crates.io-index" 1143 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1144 | dependencies = [ 1145 | "winapi-i686-pc-windows-gnu", 1146 | "winapi-x86_64-pc-windows-gnu", 1147 | ] 1148 | 1149 | [[package]] 1150 | name = "winapi-i686-pc-windows-gnu" 1151 | version = "0.4.0" 1152 | source = "registry+https://github.com/rust-lang/crates.io-index" 1153 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1154 | 1155 | [[package]] 1156 | name = "winapi-x86_64-pc-windows-gnu" 1157 | version = "0.4.0" 1158 | source = "registry+https://github.com/rust-lang/crates.io-index" 1159 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1160 | 1161 | [[package]] 1162 | name = "windows-sys" 1163 | version = "0.52.0" 1164 | source = "registry+https://github.com/rust-lang/crates.io-index" 1165 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1166 | dependencies = [ 1167 | "windows-targets", 1168 | ] 1169 | 1170 | [[package]] 1171 | name = "windows-sys" 1172 | version = "0.59.0" 1173 | source = "registry+https://github.com/rust-lang/crates.io-index" 1174 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 1175 | dependencies = [ 1176 | "windows-targets", 1177 | ] 1178 | 1179 | [[package]] 1180 | name = "windows-targets" 1181 | version = "0.52.6" 1182 | source = "registry+https://github.com/rust-lang/crates.io-index" 1183 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1184 | dependencies = [ 1185 | "windows_aarch64_gnullvm", 1186 | "windows_aarch64_msvc", 1187 | "windows_i686_gnu", 1188 | "windows_i686_gnullvm", 1189 | "windows_i686_msvc", 1190 | "windows_x86_64_gnu", 1191 | "windows_x86_64_gnullvm", 1192 | "windows_x86_64_msvc", 1193 | ] 1194 | 1195 | [[package]] 1196 | name = "windows_aarch64_gnullvm" 1197 | version = "0.52.6" 1198 | source = "registry+https://github.com/rust-lang/crates.io-index" 1199 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1200 | 1201 | [[package]] 1202 | name = "windows_aarch64_msvc" 1203 | version = "0.52.6" 1204 | source = "registry+https://github.com/rust-lang/crates.io-index" 1205 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1206 | 1207 | [[package]] 1208 | name = "windows_i686_gnu" 1209 | version = "0.52.6" 1210 | source = "registry+https://github.com/rust-lang/crates.io-index" 1211 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1212 | 1213 | [[package]] 1214 | name = "windows_i686_gnullvm" 1215 | version = "0.52.6" 1216 | source = "registry+https://github.com/rust-lang/crates.io-index" 1217 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1218 | 1219 | [[package]] 1220 | name = "windows_i686_msvc" 1221 | version = "0.52.6" 1222 | source = "registry+https://github.com/rust-lang/crates.io-index" 1223 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1224 | 1225 | [[package]] 1226 | name = "windows_x86_64_gnu" 1227 | version = "0.52.6" 1228 | source = "registry+https://github.com/rust-lang/crates.io-index" 1229 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1230 | 1231 | [[package]] 1232 | name = "windows_x86_64_gnullvm" 1233 | version = "0.52.6" 1234 | source = "registry+https://github.com/rust-lang/crates.io-index" 1235 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1236 | 1237 | [[package]] 1238 | name = "windows_x86_64_msvc" 1239 | version = "0.52.6" 1240 | source = "registry+https://github.com/rust-lang/crates.io-index" 1241 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1242 | 1243 | [[package]] 1244 | name = "zerocopy" 1245 | version = "0.7.35" 1246 | source = "registry+https://github.com/rust-lang/crates.io-index" 1247 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 1248 | dependencies = [ 1249 | "byteorder", 1250 | "zerocopy-derive", 1251 | ] 1252 | 1253 | [[package]] 1254 | name = "zerocopy-derive" 1255 | version = "0.7.35" 1256 | source = "registry+https://github.com/rust-lang/crates.io-index" 1257 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 1258 | dependencies = [ 1259 | "proc-macro2", 1260 | "quote", 1261 | "syn 2.0.77", 1262 | ] 1263 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rxpipes" 3 | description = "2D version of the ancient pipes screensaver for terminals" 4 | authors = ["inunix3 "] 5 | keywords = ["screensaver", "tui", "pipes", "terminal", "termwiz"] 6 | categories = ["command-line-utilities"] 7 | license = "MIT" 8 | version = "1.3.0" 9 | readme = "README.md" 10 | repository = "https://github.com/inunix3/rxpipes" 11 | edition = "2021" 12 | 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | 15 | [dependencies] 16 | clap = { version = "4.5.4", features = ["derive"] } 17 | compact_str = "0.8.0" 18 | eyre = "0.6.12" 19 | hex_color = "3.0.0" 20 | rand = "0.8.5" 21 | termwiz = "0.22.0" 22 | unicode-segmentation = "1.11.0" 23 | 24 | [features] 25 | default = ["alternate-screen"] 26 | alternate-screen = [] 27 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2024 inunix3 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rxpipes 2 | This program is a 2D screensaver which recreates the Pipes screensaver from old MS Windows versions. 3 | 4 | ## Features 5 | - 6 available piece sets (see the [Piece Sets](#piece-sets) section to see them). 6 | - Custom piece sets are supported. 7 | - **Depth mode** - in this mode several layers of pipes are drawn, and when a new layer is created, 8 | old pipes are made darker which gives a sense of depth. Usable only with RGB palette. 9 | - Background color setting (by default transparent). 10 | - Each pipe has its own color; available palettes are: none (colorless), base colors (16 colors 11 | defined by your terminal) and RGB. 12 | - There is a gradient mode for use with RGB palette. 13 | - Stats widget - decoration that shows various pipe/piece/layer counters and the current pipe color. 14 | - Changeable FPS (frames per second). 15 | - The minimal and maximal length of pipes can be specified. 16 | - The maximal number of drawn characters can be also specified. To ignore this setting specify 0 17 | via CLI. The screen will be cleared when this number is reached. 18 | - The probability of turning pipes is changeable, it's given as a percentage in decimal form (0 .. 1). 19 | - It enters an alternate screen so it won't mess up your previous output (if your terminal does not 20 | support alternate screen, see [Installation](#installation)). 21 | 22 | ## Screenshots 23 | 24 |

25 | First screenshot with doubled pipes 26 | Second screenshot with bold pipes 27 | Third screenshot with depth-mode 28 | Fourth screenshot with enabled gradient mode 29 | Fifth screenshot with stats widget 30 |

31 | 32 | ## Installation 33 | You'll need the Rust toolchain ([rustup](https://rustup.rs/) or from system package repo) and make 34 | sure it's up to date. 35 | 36 | When the toolchain will be prepared, type `cargo install rxpipes`. If you want to disable the 37 | alternate screen feature, add `--no-default-features` (currently, cargo does not support disabling 38 | of individual features). 39 | 40 | If you have installed successfully rxpipes, you can now run the it simply by typing `rxpipes`. If 41 | the shell says that the command does not exists, make sure that `$HOME/.cargo/bin` (or whatever the 42 | default cargo dir will be) is in the `PATH` environment variable. 43 | 44 | To see all available options, pass `-h` or `--help`. 45 | 46 | ## Controls 47 | | Key | Action | 48 | |---------------------------------|-----------------------------| 49 | | `q` / `Q` / `Escape` / `Ctrl-C` | Quit | 50 | | `Space` | Pause | 51 | | `c` | Clear screen | 52 | | `s` | Show stats widget | 53 | | `l` | Clear and redraw everything | 54 | | `,` | Change speed by -1 | 55 | | `.` | Change speed by +1 | 56 | | `<` | Change speed by -10 | 57 | | `>` | Change speed by +10 | 58 | 59 | ## Piece Sets 60 | 61 | You can select a set by passing `-P ` to rxpipes. 62 | 63 | | ID | Description | Image | 64 | |----|---------------------------------|-----------------------------------| 65 | | 0 | ASCII pipes | ![](screenshots/screenshot_p0.png) | 66 | | 1 | Thin dots | ![](screenshots/screenshot_p1.png) | 67 | | 2 | Bold dots | ![](screenshots/screenshot_p2.png) | 68 | | 3 | Thin pipes | ![](screenshots/screenshot_p3.png) | 69 | | 4 | Thin pipes with rounded corners | ![](screenshots/screenshot_p4.png) | 70 | | 5 | Double pipes | ![](screenshots/screenshot_p5.png) | 71 | | 6 | Bold pipes (default) | ![](screenshots/screenshot_p6.png) | 72 | 73 | *The look of the selected set may differ from the screenshots as it depends on the font that you use.* 74 | 75 | To set your own piece set see the `-c`/`--custom-piece-set` flag in the help message (`-h`). 76 | 77 | ## Contribution 78 | If you have found a problem or have a suggestion, feel free to open an issue or send a pull request. 79 | I'd appreciate it. 80 | 81 | ## License 82 | The rxpipes project is licensed under the [MIT license](LICENSE.md). 83 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | ## Release v1.3.0 2 | 3 | - [x] Add background color setting. 4 | - [x] Lighten rather than darken if the pipe color is < background color. 5 | - [x] Add keybinding for changing speed. 6 | 7 | ## Release v1.2.0 8 | 9 | - [x] Migrate from crossterm to termwiz. 10 | - [x] Change the default FPS from 20 to 24. 11 | - [x] Quit on Ctrl-C. 12 | - [x] Add stats (widget in corner with numbre of pipe pieces drawn, remaining pieces, etc.). 13 | - [x] Add depth-mode. 14 | - [x] Add gradient option in addition to color palette. 15 | -------------------------------------------------------------------------------- /screenshots/screenshot_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inunix3/rxpipes/d99c35f2d1d5f623f07c9b74356015480e557a1c/screenshots/screenshot_1.png -------------------------------------------------------------------------------- /screenshots/screenshot_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inunix3/rxpipes/d99c35f2d1d5f623f07c9b74356015480e557a1c/screenshots/screenshot_2.png -------------------------------------------------------------------------------- /screenshots/screenshot_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inunix3/rxpipes/d99c35f2d1d5f623f07c9b74356015480e557a1c/screenshots/screenshot_3.png -------------------------------------------------------------------------------- /screenshots/screenshot_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inunix3/rxpipes/d99c35f2d1d5f623f07c9b74356015480e557a1c/screenshots/screenshot_4.png -------------------------------------------------------------------------------- /screenshots/screenshot_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inunix3/rxpipes/d99c35f2d1d5f623f07c9b74356015480e557a1c/screenshots/screenshot_5.png -------------------------------------------------------------------------------- /screenshots/screenshot_p0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inunix3/rxpipes/d99c35f2d1d5f623f07c9b74356015480e557a1c/screenshots/screenshot_p0.png -------------------------------------------------------------------------------- /screenshots/screenshot_p1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inunix3/rxpipes/d99c35f2d1d5f623f07c9b74356015480e557a1c/screenshots/screenshot_p1.png -------------------------------------------------------------------------------- /screenshots/screenshot_p2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inunix3/rxpipes/d99c35f2d1d5f623f07c9b74356015480e557a1c/screenshots/screenshot_p2.png -------------------------------------------------------------------------------- /screenshots/screenshot_p3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inunix3/rxpipes/d99c35f2d1d5f623f07c9b74356015480e557a1c/screenshots/screenshot_p3.png -------------------------------------------------------------------------------- /screenshots/screenshot_p4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inunix3/rxpipes/d99c35f2d1d5f623f07c9b74356015480e557a1c/screenshots/screenshot_p4.png -------------------------------------------------------------------------------- /screenshots/screenshot_p5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inunix3/rxpipes/d99c35f2d1d5f623f07c9b74356015480e557a1c/screenshots/screenshot_p5.png -------------------------------------------------------------------------------- /screenshots/screenshot_p6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inunix3/rxpipes/d99c35f2d1d5f623f07c9b74356015480e557a1c/screenshots/screenshot_p6.png -------------------------------------------------------------------------------- /src/canvas.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 inunix3 2 | // 3 | // This file is licensed under the MIT License (see LICENSE.md). 4 | 5 | use crate::plane_2d::Point; 6 | use termwiz::{ 7 | cell::AttributeChange, 8 | color::{ColorAttribute, SrgbaTuple}, 9 | surface::{Change, Position, Surface}, 10 | }; 11 | 12 | /// Drawing area of the terminal. 13 | pub struct Canvas { 14 | /// Cell buffer. 15 | surface: Surface, 16 | /// Size of the canvas. 17 | size: (usize, usize), 18 | /// Position of the canvas. 19 | pub pos: Point, 20 | } 21 | 22 | impl Canvas { 23 | /// Create a `Canvas` with specified size. 24 | pub fn new(pos: Point, size: (usize, usize)) -> Self { 25 | let surface = Surface::new(size.0, size.1); 26 | 27 | Self { surface, size, pos } 28 | } 29 | 30 | /// Resize canvas to specified size. 31 | pub fn resize(&mut self, size: (usize, usize)) { 32 | self.size = size; 33 | self.surface.resize(size.0, size.1); 34 | } 35 | 36 | /// Make the canvas blank. 37 | pub fn clear(&mut self) { 38 | self.surface 39 | .add_change(Change::ClearScreen(ColorAttribute::Default)); 40 | } 41 | 42 | /// Fill the canvas with specified color. 43 | pub fn fill(&mut self, c: ColorAttribute) { 44 | self.surface.add_change(Change::ClearScreen(c)); 45 | } 46 | 47 | /// Move the cursor to the 2D point. 48 | pub fn move_to(&mut self, p: Point) { 49 | self.surface.add_change(Change::CursorPosition { 50 | x: Position::Absolute(p.x as usize), 51 | y: Position::Absolute(p.y as usize), 52 | }); 53 | } 54 | 55 | /// Set the foreground color of new cells. 56 | pub fn set_fg_color(&mut self, c: ColorAttribute) { 57 | self.surface 58 | .add_change(Change::Attribute(AttributeChange::Foreground(c))); 59 | } 60 | 61 | /// Set the background color of new cells. 62 | pub fn set_bg_color(&mut self, c: ColorAttribute) { 63 | self.surface 64 | .add_change(Change::Attribute(AttributeChange::Background(c))); 65 | } 66 | 67 | /// Print string at the current position of the cursor. 68 | pub fn put_str(&mut self, s: impl AsRef) { 69 | self.surface 70 | .add_change(Change::Text(String::from(s.as_ref()))); 71 | } 72 | 73 | /// Makes all characters darker upto the minimal color. If the minimal color is lighter than 74 | /// character's color, the character will be lighten instead. 75 | pub fn darken(&mut self, amount: f32, min: SrgbaTuple) { 76 | let mut changes: Vec = vec![]; 77 | 78 | for (i, l) in self.surface.screen_cells().iter().enumerate() { 79 | for (j, cell) in l.iter().enumerate() { 80 | if cell.str().trim_ascii().is_empty() { 81 | continue; 82 | } 83 | 84 | let attrs = cell.attrs(); 85 | let mut fg = attrs.foreground(); 86 | 87 | let approach = |x: f32, a: f32, amount: f32| -> f32 { 88 | let mut x = x; 89 | 90 | if x > a { 91 | x *= amount; 92 | x = x.clamp(a, 1.0); 93 | } else if x < a { 94 | x *= 1.0 + amount; 95 | x = x.clamp(0.0, a); 96 | } 97 | 98 | x 99 | }; 100 | 101 | fg = match fg { 102 | ColorAttribute::TrueColorWithDefaultFallback(mut cell_color) => { 103 | cell_color.0 = approach(cell_color.0, min.0, amount); 104 | cell_color.1 = approach(cell_color.1, min.1, amount); 105 | cell_color.2 = approach(cell_color.2, min.2, amount); 106 | 107 | ColorAttribute::TrueColorWithDefaultFallback(cell_color) 108 | } 109 | _ => fg, 110 | }; 111 | 112 | // In order to apply the foreground change, we need so print something. 113 | let text = cell.str().to_string(); 114 | 115 | changes.push(Change::CursorPosition { 116 | x: Position::Absolute(j), 117 | y: Position::Absolute(i), 118 | }); 119 | 120 | changes.push(Change::Attribute(AttributeChange::Foreground(fg))); 121 | changes.push(Change::Text(text)); 122 | } 123 | } 124 | 125 | self.surface.add_changes(changes); 126 | } 127 | 128 | /// Retrieve the size of the area. 129 | pub fn size(&self) -> (usize, usize) { 130 | self.size 131 | } 132 | 133 | /// Retrieve a reference to the buffer. 134 | pub fn surface(&self) -> &Surface { 135 | &self.surface 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/color.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 inunix3 2 | // 3 | // This file is licensed under the MIT License (see LICENSE.md). 4 | 5 | use clap::ValueEnum; 6 | 7 | use rand::{ 8 | distributions::{Distribution, Standard}, 9 | Rng, 10 | }; 11 | 12 | #[derive(Copy, Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord)] 13 | pub enum GradientDir { 14 | #[default] 15 | Up, 16 | Down, 17 | } 18 | 19 | impl Distribution for Standard { 20 | fn sample(&self, rng: &mut R) -> GradientDir { 21 | match rng.gen_range(0..=3) { 22 | 0 => GradientDir::Up, 23 | _ => GradientDir::Down, 24 | } 25 | } 26 | } 27 | 28 | #[derive(Copy, Clone, Eq, Default, PartialEq, Debug, ValueEnum)] 29 | pub enum ColorPalette { 30 | None, 31 | #[default] 32 | BaseColors, 33 | Rgb, 34 | } 35 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 inunix3 2 | // 3 | // This file is licensed under the MIT License (see LICENSE.md). 4 | 5 | use crate::color::ColorPalette; 6 | use clap::Parser; 7 | 8 | /// Screensaver settings and CLI parser. 9 | #[derive(Debug, Parser)] 10 | #[command( 11 | about = "2D version of the ancient pipes screensaver for terminals.", 12 | author = "inunix3", 13 | version = "1.3.0", 14 | long_about = None, 15 | )] 16 | pub struct Config { 17 | /// Frames per second. 18 | #[arg(short, long, value_parser = 1.., default_value_t = 24)] 19 | pub fps: i64, 20 | /// Maximum drawn pieces of pipes on the screen. 21 | /// When this maximum is reached, the screen will be cleared. 22 | /// Set it to 0 to remove the limit. 23 | #[arg(short, long, default_value_t = 10000, verbatim_doc_comment)] 24 | pub max_drawn_pieces: u64, 25 | /// Maximum length of pipe in pieces. 26 | /// Must not equal to or be less than --min-pipe-length. 27 | #[arg(long, default_value_t = 300, verbatim_doc_comment)] 28 | pub max_pipe_length: u64, 29 | /// Minimal length of pipe in pieces. 30 | /// Must not equal to or be greater than --max-pipe-length. 31 | #[arg(long, default_value_t = 7, verbatim_doc_comment)] 32 | pub min_pipe_length: u64, 33 | /// Probability of turning a pipe as a percentage in a decimal form. 34 | #[arg(short = 't', long, default_value_t = 0.2)] 35 | pub turning_prob: f64, 36 | /// Set of colors used for coloring each pipe. 37 | /// `None` disables this feature. Base colors are 16 colors predefined by the terminal. 38 | /// The RGB option is for terminals with true color support (all 16 million colors). 39 | #[arg(short, long, default_value_t, value_enum, verbatim_doc_comment)] 40 | pub palette: ColorPalette, 41 | /// Enable gradient. Use only with RGB palette. 42 | #[arg(short, long)] 43 | pub gradient: bool, 44 | /// Gradient: the step to lighten/darken the color. 45 | #[arg(long, default_value_t = 0.005)] 46 | pub gradient_step: f32, 47 | /// In this mode multiple layers of pipes are drawn. If the number of currently drawn pieces in 48 | /// layer is >= layer_max_drawn_pieces, all pipe pieces are made darker and a new layer is created 49 | /// on top of them. See also darken_factor and darken_min. RGB palette only! 50 | #[arg(short, long, verbatim_doc_comment)] 51 | pub depth_mode: bool, 52 | /// Depth-mode: maximum drawn pipe pieces in the current layer. 53 | #[arg(long, default_value_t = 1000)] 54 | pub layer_max_drawn_pieces: u64, 55 | /// Depth-mode: how much to darken pipe pieces in previous layers? 56 | #[arg(short = 'F', long, default_value_t = 0.8)] 57 | pub darken_factor: f32, 58 | /// Depth-mode: the color to gradually darken to. 59 | #[arg(short = 'M', long, default_value = "#000000")] 60 | pub darken_min: String, 61 | /// Color of the background. 62 | #[arg(short = 'b', long)] 63 | pub bg_color: Option, 64 | /// A default set of pieces to use. 65 | /// Available piece sets: 66 | /// 0 - ASCII pipes: 67 | /// |- ++ ++ +- -+ -|- 68 | /// 1 - thin dots: 69 | /// ·· ·· ·· ·· ·· ··· 70 | /// 2 - bold dots: 71 | /// •• •• •• •• •• ••• 72 | /// 3 - thin pipes: 73 | /// │─ ┐└ ┘┌ └─ ─┐ ─│─ 74 | /// 4 - thin pipes with rounded corners: 75 | /// │─ ╮╰ ╯╭ ╰─ ─╮ ─│─ 76 | /// 5 - double pipes: 77 | /// ║═ ╗╚ ╝╔ ╚═ ═╗ ═║═ 78 | /// 6 - bold pipes (default): 79 | /// ┃━ ┓┗ ┛┏ ┗━ ━┓ ━┃━ 80 | /// This parameter expects a numeric ID. 81 | #[arg(short = 'P', long, default_value_t = 6, value_parser = 0..=6, verbatim_doc_comment)] 82 | pub piece_set: i64, 83 | /// A string representing custom piece set (takes precedence over -P/--piece-set). 84 | /// The string must have length of 6 characters. Write it according to `│─┌┐└┘`. 85 | /// This string must define all 6 pieces, otherwise rxpipes will crash. 86 | /// Unicode grapheme clusters are supported and treated as single characters. 87 | #[arg(name = "custom-piece-set", short = 'c', long, verbatim_doc_comment)] 88 | pub custom_piece_set_: Option, 89 | /// Show statistics in the bottom of screen (how many pieces drawn, pipes drawn, etc.) 90 | #[arg(short = 's', long)] 91 | pub show_stats: bool, 92 | 93 | // TODO: implement validation of length for custom-piece-set. 94 | #[clap(skip)] 95 | pub custom_piece_set: Option>, 96 | } 97 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 inunix3 2 | // 3 | // This file is licensed under the MIT License (see LICENSE.md). 4 | 5 | mod canvas; 6 | mod color; 7 | mod config; 8 | mod pipe; 9 | mod plane_2d; 10 | mod screensaver; 11 | mod terminal; 12 | 13 | use crate::{config::Config, screensaver::Screensaver, terminal::TerminalScreen}; 14 | use clap::Parser; 15 | use eyre::{Result, WrapErr}; 16 | use std::panic::{set_hook, take_hook}; 17 | use termwiz::{caps::Capabilities, terminal::SystemTerminal}; 18 | use unicode_segmentation::UnicodeSegmentation; 19 | 20 | /// Set a panic hook that will restore the terminal state when the program panics. 21 | fn set_panic_hook() { 22 | let old_hook = take_hook(); 23 | 24 | set_hook(Box::new(move |panic_info| { 25 | let term = SystemTerminal::new_from_stdio(Capabilities::new_from_env().unwrap()).unwrap(); 26 | let mut term_scr = TerminalScreen::new(term).unwrap(); 27 | let _ = term_scr.deinit(); 28 | 29 | old_hook(panic_info); 30 | })); 31 | } 32 | 33 | fn parse_cli() -> Config { 34 | let mut cfg = Config::parse(); 35 | 36 | if let Some(s) = &cfg.custom_piece_set_ { 37 | cfg.custom_piece_set = Some( 38 | s.graphemes(true) // true here means iterate over extended grapheme clusters (UAX #29). 39 | .map(|s| s.to_string()) 40 | .collect(), 41 | ); 42 | } 43 | 44 | cfg 45 | } 46 | 47 | /// An entry point. 48 | fn main() -> Result<()> { 49 | let cfg = parse_cli(); 50 | 51 | let term = SystemTerminal::new_from_stdio( 52 | Capabilities::new_from_env().wrap_err("cannot read terminal capabilities")?, 53 | ) 54 | .wrap_err("failed to associate terminal with screen buffer")?; 55 | let mut term_scr = TerminalScreen::new(term).wrap_err("cannot set up terminal screen")?; 56 | 57 | set_panic_hook(); 58 | 59 | term_scr 60 | .init() 61 | .wrap_err("failed to prepare terminal for drawing")?; 62 | 63 | let mut app = Screensaver::new(term_scr, cfg)?; 64 | let r = app.run(); 65 | 66 | app.deinit() 67 | .wrap_err("failed to restore the terminal previous state")?; 68 | 69 | r 70 | } 71 | -------------------------------------------------------------------------------- /src/pipe.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 inunix3 2 | // 3 | // This file is licensed under the MIT License (see LICENSE.md). 4 | 5 | use crate::{ 6 | color::{ColorPalette, GradientDir}, 7 | plane_2d::{Direction, Point}, 8 | }; 9 | use rand::{thread_rng, Rng}; 10 | use termwiz::color::{ColorAttribute, SrgbaTuple}; 11 | 12 | /// Represents a piece of pipe. 13 | #[derive(Copy, Clone, Default, Debug)] 14 | pub struct PipePiece { 15 | /// Position of the piece. 16 | pub pos: Point, 17 | /// Direction of the preceeding piece. 18 | pub prev_dir: Direction, 19 | /// Direction of the piece. 20 | pub dir: Direction, 21 | /// Color of the piece. 22 | pub color: Option, 23 | /// Gradient direction. 24 | pub gradient: GradientDir, 25 | } 26 | 27 | impl PipePiece { 28 | /// Create a `PipePiece` with position `(0, 0)`, unspecified directions and without a color. 29 | pub fn new() -> Self { 30 | Default::default() 31 | } 32 | 33 | /// Create a piece with random direction and color. 34 | pub fn gen(palette: ColorPalette) -> Self { 35 | let mut rng = thread_rng(); 36 | let initial_dir: Direction = rng.gen(); 37 | 38 | Self { 39 | pos: Point { x: 0, y: 0 }, 40 | prev_dir: initial_dir, 41 | dir: initial_dir, 42 | color: gen_color(palette), 43 | gradient: rng.gen(), 44 | } 45 | } 46 | } 47 | 48 | /// Pick random color from the specified palette. 49 | fn gen_color(palette: ColorPalette) -> Option { 50 | let mut rng = thread_rng(); 51 | 52 | match palette { 53 | ColorPalette::None => None, 54 | ColorPalette::BaseColors => Some(ColorAttribute::PaletteIndex(rng.gen_range(0..16))), 55 | ColorPalette::Rgb => Some(ColorAttribute::TrueColorWithDefaultFallback(SrgbaTuple( 56 | rng.gen(), 57 | rng.gen(), 58 | rng.gen(), 59 | 1.0, 60 | ))), 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/plane_2d.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 inunix3 2 | // 3 | // This file is licensed under the MIT License (see LICENSE.md). 4 | 5 | use rand::{ 6 | distributions::{Distribution, Standard}, 7 | Rng, 8 | }; 9 | 10 | /// 2D point: `(x, y)`. 11 | #[derive(Copy, Clone, Debug, Default)] 12 | pub struct Point { 13 | pub x: isize, 14 | pub y: isize, 15 | } 16 | 17 | impl Point { 18 | /// Move a point one unit in the specified direction. 19 | pub fn advance(&mut self, dir: Direction) { 20 | match dir { 21 | Direction::Up => self.y -= 1, 22 | Direction::Down => self.y += 1, 23 | Direction::Right => self.x += 1, 24 | Direction::Left => self.x -= 1, 25 | }; 26 | } 27 | 28 | /// Wrap a point within the plane (specified by width and height). 29 | /// 30 | /// E.g. for a plane 24 units wide, the x-coord -28 will be wrapped as 20 units, because if we 31 | /// are at the 24th point, then after going 24 units left, we will be at the 24th point again. 32 | /// And if we go another 4 units left, we'll end up at the 20th point. 33 | pub fn wrap(&mut self, width: isize, height: isize) { 34 | let wrap_coord = |x: isize, m: isize| -> isize { 35 | if x < 0 { 36 | m - x.abs() % m 37 | } else if x >= m { 38 | x % m 39 | } else { 40 | x 41 | } 42 | }; 43 | 44 | self.x = wrap_coord(self.x, width); 45 | self.y = wrap_coord(self.y, height); 46 | } 47 | } 48 | 49 | /// Main four (cardinal) directions. 50 | #[derive(Copy, Clone, Eq, PartialEq, Debug, Default)] 51 | pub enum Direction { 52 | #[default] 53 | Up, 54 | Down, 55 | Right, 56 | Left, 57 | } 58 | 59 | impl Distribution for Standard { 60 | fn sample(&self, rng: &mut R) -> Direction { 61 | match rng.gen_range(0..=3) { 62 | 0 => Direction::Up, 63 | 1 => Direction::Down, 64 | 2 => Direction::Right, 65 | _ => Direction::Left, 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/screensaver.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 inunix3 2 | // 3 | // This file is licensed under the MIT License (see LICENSE.md). 4 | 5 | use crate::{ 6 | canvas::Canvas, 7 | color::GradientDir, 8 | config::Config, 9 | pipe::PipePiece, 10 | plane_2d::{Direction, Point}, 11 | terminal::TerminalScreen, 12 | }; 13 | use eyre::{Result, WrapErr}; 14 | use hex_color::HexColor; 15 | use rand::{thread_rng, Rng}; 16 | use std::time::Duration; 17 | use termwiz::{ 18 | color::{ColorAttribute, SrgbaTuple}, 19 | input::{InputEvent, KeyCode, KeyEvent, Modifiers}, 20 | terminal::Terminal, 21 | }; 22 | 23 | /// Map of default piece sets. 24 | const DEFAULT_PIECE_SETS: [[char; 6]; 7] = [ 25 | ['|', '-', '+', '+', '+', '+'], 26 | ['·', '·', '·', '·', '·', '·'], 27 | ['•', '•', '•', '•', '•', '•'], 28 | ['│', '─', '┌', '┐', '└', '┘'], 29 | ['│', '─', '╭', '╮', '╰', '╯'], 30 | ['║', '═', '╔', '╗', '╚', '╝'], 31 | ['┃', '━', '┏', '┓', '┗', '┛'], // default 32 | ]; 33 | 34 | /// Map from directions to indices for indexing default piece sets. 35 | /// 36 | /// Index via `[DIRECTION OF THE PREVIOUS PIECE][CURRENT DIRECTION]` 37 | const PIECE_SETS_IDX_MAP: [[usize; 4]; 4] = [ 38 | // Up 39 | [0, 0, 2, 3], 40 | // Down 41 | [0, 0, 4, 5], 42 | // Right 43 | [5, 3, 1, 1], 44 | // Left 45 | [4, 2, 1, 1], 46 | ]; 47 | 48 | /// State of the screensaver. 49 | #[derive(Debug)] 50 | struct State { 51 | /// Current pipe piece to be drawn. 52 | pipe_piece: PipePiece, 53 | /// Total of all drawn pieces. 54 | pieces_total: u64, 55 | /// Total of all drawn pieces in the current layer. 56 | layer_pieces_total: u64, 57 | /// Number of currently drawn pieces. 58 | currently_drawn_pieces: u64, 59 | /// Number of pieces not drawn yet. 60 | pieces_remaining: u64, 61 | /// Total of all drawn pipes. 62 | pipes_total: u64, 63 | /// Total of all drawn layers since last screen clear. 64 | layers_drawn: u64, 65 | /// Indicates when to end the main loop. 66 | quit: bool, 67 | /// Indicates when to stop updating the state. 68 | pause: bool, 69 | } 70 | 71 | impl Default for State { 72 | fn default() -> Self { 73 | Self { 74 | pipe_piece: PipePiece::new(), 75 | pieces_total: 0, 76 | layer_pieces_total: 0, 77 | currently_drawn_pieces: 0, 78 | pieces_remaining: 0, 79 | pipes_total: 0, 80 | layers_drawn: 0, 81 | quit: false, 82 | pause: false, 83 | } 84 | } 85 | } 86 | 87 | impl State { 88 | /// Create a `State`. 89 | fn new() -> Self { 90 | Default::default() 91 | } 92 | } 93 | 94 | /// Represents the screensaver application. 95 | pub struct Screensaver { 96 | state: State, 97 | term_scr: TerminalScreen, 98 | canv: Canvas, 99 | darken_min: SrgbaTuple, 100 | bg_color: Option, 101 | stats_canv: Canvas, 102 | delay: Duration, 103 | cfg: Config, 104 | } 105 | 106 | impl Screensaver { 107 | /// Create a `Screensaver`. 108 | pub fn new(term_scr: TerminalScreen, cfg: Config) -> Result { 109 | let scr_size = term_scr.size(); 110 | 111 | let mut s = Ok(Self { 112 | state: State::new(), 113 | term_scr, 114 | canv: Canvas::new(Point { x: 0, y: 0 }, scr_size), 115 | darken_min: { 116 | let hc = HexColor::parse_rgb(&cfg.darken_min)?; 117 | 118 | SrgbaTuple( 119 | hc.r as f32 / 255.0, 120 | hc.g as f32 / 255.0, 121 | hc.b as f32 / 255.0, 122 | 1.0, 123 | ) 124 | }, 125 | bg_color: { 126 | if let Some(c) = &cfg.bg_color { 127 | let hc = HexColor::parse_rgb(c)?; 128 | 129 | Some(SrgbaTuple( 130 | hc.r as f32 / 255.0, 131 | hc.g as f32 / 255.0, 132 | hc.b as f32 / 255.0, 133 | hc.a as f32 / 255.0, 134 | )) 135 | } else { 136 | None 137 | } 138 | }, 139 | stats_canv: Canvas::new( 140 | Point { 141 | x: 0, 142 | y: scr_size.1 as isize - 1, 143 | }, 144 | (scr_size.0, 3), 145 | ), 146 | delay: Screensaver::calculate_delay(cfg.fps), 147 | cfg, 148 | }); 149 | 150 | if let Ok(ref mut s) = s { 151 | s.draw_bg(); 152 | } 153 | s 154 | } 155 | 156 | /// Free all resources. 157 | pub fn deinit(&mut self) -> Result<()> { 158 | self.term_scr.deinit() 159 | } 160 | 161 | /// Generate the next pipe pieces. 162 | fn gen_next_piece(&mut self) { 163 | // Aliases with shorter names 164 | let state = &mut self.state; 165 | let canv = &mut self.canv; 166 | let cfg = &self.cfg; 167 | let piece = &mut state.pipe_piece; 168 | 169 | let mut rng = thread_rng(); 170 | 171 | if state.pieces_remaining == 0 { 172 | state.pieces_remaining = rng.gen_range(cfg.min_pipe_length..=cfg.max_pipe_length); 173 | 174 | *piece = PipePiece::gen(cfg.palette); 175 | piece.pos = Point { 176 | x: rng.gen_range(0..canv.size().0) as isize, 177 | y: rng.gen_range(0..canv.size().1) as isize, 178 | }; 179 | 180 | if state.pieces_total > 0 { 181 | state.pipes_total += 1; 182 | } 183 | 184 | state.currently_drawn_pieces = 0; 185 | } 186 | 187 | piece.pos.advance(piece.dir); 188 | piece 189 | .pos 190 | .wrap(canv.size().0 as isize, canv.size().1 as isize); 191 | piece.prev_dir = piece.dir; 192 | 193 | // Try to turn the pipe in other direction 194 | if rng.gen_bool(cfg.turning_prob) { 195 | let choice = rng.gen_bool(0.5); 196 | 197 | piece.dir = match piece.dir { 198 | Direction::Up | Direction::Down => { 199 | if choice { 200 | Direction::Right 201 | } else { 202 | Direction::Left 203 | } 204 | } 205 | Direction::Right | Direction::Left => { 206 | if choice { 207 | Direction::Up 208 | } else { 209 | Direction::Down 210 | } 211 | } 212 | } 213 | } 214 | } 215 | 216 | /// Display the current state. 217 | fn draw_pipe_piece(&mut self) { 218 | // Aliases with shorter names 219 | let state = &mut self.state; 220 | let canv = &mut self.canv; 221 | let cfg = &self.cfg; 222 | let piece = &mut state.pipe_piece; 223 | 224 | canv.move_to(piece.pos); 225 | 226 | if let Some(color) = piece.color { 227 | let color = if cfg.gradient { 228 | let step = match piece.gradient { 229 | GradientDir::Up => cfg.gradient_step, 230 | GradientDir::Down => -cfg.gradient_step, 231 | }; 232 | 233 | let srgba = if let ColorAttribute::TrueColorWithDefaultFallback(srgba) = color { 234 | let r = (srgba.0 + step).clamp(0.0, 1.0); 235 | let g = (srgba.1 + step).clamp(0.0, 1.0); 236 | let b = (srgba.2 + step).clamp(0.0, 1.0); 237 | 238 | SrgbaTuple(r, g, b, 1.0) 239 | } else { 240 | unreachable!() 241 | }; 242 | 243 | ColorAttribute::TrueColorWithDefaultFallback(srgba) 244 | } else { 245 | color 246 | }; 247 | 248 | piece.color = Some(color); 249 | canv.set_fg_color(color) 250 | } 251 | 252 | let piece_idx = PIECE_SETS_IDX_MAP[piece.prev_dir as usize][piece.dir as usize]; 253 | 254 | if let Some(pieces) = &cfg.custom_piece_set { 255 | canv.put_str(&pieces[piece_idx]); 256 | } else { 257 | canv.put_str(DEFAULT_PIECE_SETS[cfg.piece_set as usize][piece_idx].to_string()); 258 | } 259 | 260 | state.pieces_total += 1; 261 | state.layer_pieces_total += 1; 262 | state.currently_drawn_pieces += 1; 263 | state.pieces_remaining -= 1; 264 | 265 | if cfg.max_drawn_pieces != 0 && state.pieces_total >= cfg.max_drawn_pieces { 266 | self.clear(); 267 | } else if cfg.depth_mode && state.layer_pieces_total >= cfg.layer_max_drawn_pieces { 268 | self.darken_previous_layers(); 269 | } 270 | } 271 | 272 | /// Clear the screen and reset all pipe/piece/layer counters. 273 | fn clear(&mut self) { 274 | self.state.currently_drawn_pieces = 0; 275 | self.state.pieces_remaining = 0; 276 | self.state.layer_pieces_total = 0; 277 | self.state.pieces_total = 0; 278 | self.state.layers_drawn = 0; 279 | self.state.pipes_total = 0; 280 | 281 | self.draw_bg(); 282 | } 283 | 284 | /// Fill the screen with background color. 285 | fn draw_bg(&mut self) { 286 | if let Some(c) = self.bg_color { 287 | self.canv 288 | .fill(ColorAttribute::TrueColorWithDefaultFallback(c)); 289 | } else { 290 | self.canv.fill(ColorAttribute::Default); 291 | } 292 | } 293 | 294 | /// Make all pipe pieces in previous layers darker. 295 | fn darken_previous_layers(&mut self) { 296 | self.state.currently_drawn_pieces = 0; 297 | self.state.pieces_remaining = 0; 298 | self.state.layer_pieces_total = 0; 299 | self.state.layers_drawn += 1; 300 | 301 | self.canv.darken(self.cfg.darken_factor, self.darken_min); 302 | } 303 | 304 | /// Render pipes and maybe stats. 305 | fn render(&mut self) -> Result<()> { 306 | self.term_scr.copy_canvas(&self.canv); 307 | 308 | if self.cfg.show_stats { 309 | self.term_scr.copy_canvas(&self.stats_canv); 310 | } 311 | 312 | self.term_scr.render()?; 313 | 314 | Ok(()) 315 | } 316 | 317 | /// Run the main loop in the current thread until an external event is received (a key press or 318 | /// signal) or some internal error is occurred. 319 | pub fn run(&mut self) -> Result<()> { 320 | while !self.state.quit { 321 | self.handle_events(self.delay)?; 322 | 323 | if !self.state.pause { 324 | self.gen_next_piece(); 325 | self.draw_pipe_piece(); 326 | 327 | if self.cfg.show_stats { 328 | self.draw_stats(); 329 | } 330 | 331 | self.render()?; 332 | } 333 | } 334 | 335 | Ok(()) 336 | } 337 | 338 | fn calculate_delay(fps: i64) -> Duration { 339 | Duration::from_millis(1000 / fps as u64) 340 | } 341 | 342 | /// Handle input and incoming events. 343 | fn handle_events(&mut self, delay: Duration) -> Result<()> { 344 | // The poll_input function blocks the thread if the argument is nonzero, so we can use it 345 | // for a frame rate limit. The only downside is that if the incoming events are 346 | // received (e.g., a key press or window resize), this function immediately returns, 347 | // so the delay isn't always the same. But since the user isn't expected to make 348 | // thousands of key presses or crazily drag the corner of the window while using 349 | // screensaver, we can ignore this. 350 | if let Some(event) = self 351 | .term_scr 352 | .terminal() 353 | .terminal() 354 | .poll_input(Some(delay)) 355 | .wrap_err("cannot read incoming events")? 356 | { 357 | match event { 358 | InputEvent::Key(KeyEvent { 359 | key, 360 | modifiers: Modifiers::NONE, 361 | }) => match key { 362 | KeyCode::Escape | KeyCode::Char('q') | KeyCode::Char('Q') => { 363 | self.state.quit = true 364 | } 365 | KeyCode::Char(' ') => self.state.pause = !self.state.pause, 366 | KeyCode::Char('c') => self.clear(), 367 | KeyCode::Char('l') => self.redraw()?, 368 | KeyCode::Char('s') => self.cfg.show_stats = !self.cfg.show_stats, 369 | KeyCode::Char(',') => { 370 | self.cfg.fps -= 1; 371 | self.cfg.fps = self.cfg.fps.clamp(1, i64::MAX); 372 | 373 | self.delay = Self::calculate_delay(self.cfg.fps) 374 | } 375 | KeyCode::Char('.') => { 376 | self.cfg.fps = self.cfg.fps.saturating_add(1); 377 | 378 | self.delay = Self::calculate_delay(self.cfg.fps) 379 | } 380 | KeyCode::Char('<') => { 381 | self.cfg.fps -= 10; 382 | self.cfg.fps = self.cfg.fps.clamp(1, i64::MAX); 383 | 384 | self.delay = Self::calculate_delay(self.cfg.fps) 385 | } 386 | KeyCode::Char('>') => { 387 | self.cfg.fps = self.cfg.fps.saturating_add(10); 388 | 389 | self.delay = Self::calculate_delay(self.cfg.fps) 390 | } 391 | _ => {} 392 | }, 393 | InputEvent::Key(KeyEvent { 394 | key: KeyCode::Char('c'), 395 | modifiers: Modifiers::CTRL, 396 | }) => self.state.quit = true, 397 | InputEvent::Resized { cols, rows } => { 398 | self.canv.resize((cols, rows)); 399 | self.draw_bg(); 400 | 401 | // self.stats_canv.resize((cols, self.stats_canv.size().1)); 402 | self.stats_canv.pos.y = rows as isize - 1; 403 | self.stats_canv.resize((cols, self.stats_canv.size().1)); 404 | self.term_scr.resize((cols, rows)); 405 | 406 | self.redraw()? 407 | } 408 | _ => {} 409 | } 410 | } 411 | 412 | Ok(()) 413 | } 414 | 415 | fn redraw(&mut self) -> Result<()> { 416 | self.term_scr.clear(); 417 | self.render()?; 418 | 419 | Ok(()) 420 | } 421 | 422 | /// Draw a stats widget which shows pipe/piece/layers counters and the current pipe color. 423 | fn draw_stats(&mut self) { 424 | // Stats string will have a black background 425 | self.stats_canv.fill(ColorAttribute::PaletteIndex(0)); 426 | // Stats string will have a gray foreground 427 | self.stats_canv 428 | .set_fg_color(ColorAttribute::PaletteIndex(7)); 429 | 430 | let pipe_len = self.state.currently_drawn_pieces + self.state.pieces_remaining; 431 | 432 | let color = self 433 | .state 434 | .pipe_piece 435 | .color 436 | .map_or("DEFAULT".to_string(), |c| match c { 437 | ColorAttribute::Default => "DEFAULT".to_string(), 438 | ColorAttribute::PaletteIndex(i) => match i { 439 | 0 => "BLACK", 440 | 1 => "RED", 441 | 2 => "GREEN", 442 | 3 => "YELLOW", 443 | 4 => "BLUE", 444 | 5 => "MAGENTA", 445 | 6 => "CYAN", 446 | 7 => "WHITE", 447 | 8 => "BRIGHT BLACK", 448 | 9 => "BRIGHT RED", 449 | 10 => "BRIGHT GREEN", 450 | 11 => "BRIGHT YELLOW", 451 | 12 => "BRIGHT BLUE", 452 | 13 => "BRIGHT MAGENTA", 453 | 14 => "BRIGHT CYAN", 454 | 15 => "BRIGHT GRAY", 455 | _ => unreachable!(), 456 | } 457 | .to_string(), 458 | ColorAttribute::TrueColorWithPaletteFallback(c, _) 459 | | ColorAttribute::TrueColorWithDefaultFallback(c) => c.to_rgb_string(), 460 | }); 461 | 462 | let s = format!( 463 | "pcs. drawn: {}, lpcs. drawn: {}, c. pcs. drawn: {}, pps. drawn: {}, pcs. rem: {}, l. drawn: {}, pps. len: {}, pipe color: {}, fps: {}", 464 | self.state.pieces_total, 465 | self.state.layer_pieces_total, 466 | self.state.currently_drawn_pieces, 467 | self.state.pipes_total, 468 | self.state.pieces_remaining, 469 | self.state.layers_drawn, 470 | pipe_len, 471 | color, 472 | self.cfg.fps, 473 | ); 474 | 475 | self.stats_canv.put_str(s); 476 | } 477 | } 478 | -------------------------------------------------------------------------------- /src/terminal.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 inunix3 2 | // 3 | // This file is licensed under the MIT License (see LICENSE.md). 4 | 5 | use crate::canvas::Canvas; 6 | use eyre::{Result, WrapErr}; 7 | use termwiz::{ 8 | color::ColorAttribute, 9 | surface::{Change, CursorVisibility}, 10 | terminal::{buffered::BufferedTerminal, SystemTerminal, Terminal}, 11 | }; 12 | 13 | /// Represents a terminal screen. 14 | pub struct TerminalScreen { 15 | /// Associated terminal. 16 | term: BufferedTerminal, 17 | /// Size. 18 | size: (usize, usize), 19 | } 20 | 21 | impl TerminalScreen { 22 | /// Create a new `TerminalScreen` instance. 23 | pub fn new(mut term: SystemTerminal) -> Result { 24 | let size = term 25 | .get_screen_size() 26 | .wrap_err("failed to query the size of the terminal") 27 | .map(|s| (s.cols, s.rows))?; 28 | 29 | Ok(Self { 30 | term: BufferedTerminal::new(term)?, 31 | size, 32 | }) 33 | } 34 | 35 | /// Initialize the terminal screen - enables alternate screen / clear screen, sets raw mode and hides cursor. 36 | pub fn init(&mut self) -> Result<()> { 37 | self.enter_alternate_screen()?; 38 | self.term 39 | .terminal() 40 | .set_raw_mode() 41 | .wrap_err("failed to set raw mode")?; 42 | self.term 43 | .add_change(Change::CursorVisibility(CursorVisibility::Hidden)); 44 | 45 | Ok(()) 46 | } 47 | 48 | /// Restore previous state of the terminal; exit alternate screen / clear the terminal screen, 49 | /// restore the cursor and disable raw mode. 50 | pub fn deinit(&mut self) -> Result<()> { 51 | self.term 52 | .add_change(Change::CursorVisibility(CursorVisibility::Visible)); 53 | self.term 54 | .terminal() 55 | .set_cooked_mode() 56 | .wrap_err("failed to unset raw mode")?; 57 | self.leave_alternate_screen()?; 58 | 59 | Ok(()) 60 | } 61 | 62 | /// Clear the screen. 63 | pub fn clear(&mut self) { 64 | self.term 65 | .add_change(Change::ClearScreen(ColorAttribute::Default)); 66 | } 67 | 68 | /// Resize terminal screen buffer to specified size. 69 | pub fn resize(&mut self, size: (usize, usize)) { 70 | self.size = size; 71 | self.term.resize(size.0, size.1); 72 | } 73 | 74 | /// Copy canvas buffer to the terminal screen buffer. 75 | pub fn copy_canvas(&mut self, canv: &Canvas) { 76 | self.term 77 | .draw_from_screen(canv.surface(), canv.pos.x as usize, canv.pos.y as usize); 78 | } 79 | 80 | /// Renders all changes since the last render. 81 | pub fn render(&mut self) -> Result<()> { 82 | self.term.flush()?; 83 | 84 | Ok(()) 85 | } 86 | 87 | /// If the `alternate-screen` feature is enabled, enter the alternate screen. If it's not, 88 | /// just clear the terminal screen. 89 | #[cfg(feature = "alternate-screen")] 90 | pub fn enter_alternate_screen(&mut self) -> Result<()> { 91 | self.term 92 | .terminal() 93 | .enter_alternate_screen() 94 | .wrap_err("failed to enter alternate screen")?; 95 | 96 | Ok(()) 97 | } 98 | 99 | /// If the `alternate-screen` feature is enabled, leave the alternate screen. If it's not, 100 | /// just clear the terminal screen. 101 | #[cfg(feature = "alternate-screen")] 102 | pub fn leave_alternate_screen(&mut self) -> Result<()> { 103 | self.term 104 | .terminal() 105 | .exit_alternate_screen() 106 | .wrap_err("failed to leave alternate screen")?; 107 | 108 | Ok(()) 109 | } 110 | 111 | #[cfg(not(feature = "alternate-screen"))] 112 | pub fn enter_alternate_screen(&mut self) -> Result<()> { 113 | self.clear(); 114 | 115 | Ok(()) 116 | } 117 | 118 | #[cfg(not(feature = "alternate-screen"))] 119 | pub fn leave_alternate_screen(&mut self) -> Result<()> { 120 | self.clear(); 121 | 122 | Ok(()) 123 | } 124 | 125 | /// Retrieve reference the associated terminal. 126 | pub fn terminal(&mut self) -> &mut BufferedTerminal { 127 | &mut self.term 128 | } 129 | 130 | /// Retrieve the size of the screen. 131 | pub fn size(&self) -> (usize, usize) { 132 | self.size 133 | } 134 | } 135 | --------------------------------------------------------------------------------