mirror of
https://github.com/ricoriedel/wipe.git
synced 2025-04-07 08:18:21 +00:00
Compare commits
76 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ab16aeaf71 | ||
![]() |
ce2e4d898f | ||
![]() |
979a820200 | ||
![]() |
5c589c0ab5 | ||
![]() |
5932d054f3 | ||
![]() |
46e8a68e75 | ||
![]() |
5f1c8d67ba | ||
![]() |
4ad08ea42d | ||
![]() |
1e4403b5cb | ||
![]() |
19461dde26 | ||
![]() |
edb429ed43 | ||
![]() |
1eddc2e3a2 | ||
![]() |
3fcab89c51 | ||
![]() |
f8dd56ccb6 | ||
![]() |
24bf3797f4 | ||
![]() |
4286d7055d | ||
![]() |
e07d5bfb3e | ||
![]() |
87175b6619 | ||
![]() |
703fe1a62b | ||
![]() |
97aa376e3c | ||
![]() |
9e8e9253bd | ||
![]() |
402077e109 | ||
![]() |
d965ba413a | ||
![]() |
7c13c8838a | ||
![]() |
66a5ec78f3 | ||
![]() |
5bf391e5ff | ||
![]() |
7c11b8e439 | ||
![]() |
dd5b215aaf | ||
![]() |
726f482687 | ||
![]() |
cbb0c532e2 | ||
![]() |
6d60ef0088 | ||
![]() |
711ba4850c | ||
![]() |
c80dac4c4e | ||
![]() |
3318db9a26 | ||
![]() |
852d3e5bf5 | ||
![]() |
91e80cc7c7 | ||
![]() |
74e83cd543 | ||
![]() |
32cd72552e | ||
![]() |
dcbae98ed3 | ||
![]() |
ba68f5e11e | ||
![]() |
38bf1818e4 | ||
![]() |
2943a2514b | ||
![]() |
393d3dd67e | ||
![]() |
ef4eaec34d | ||
![]() |
f5730be835 | ||
![]() |
8d75146744 | ||
![]() |
56e630204a | ||
![]() |
1f03a8fd13 | ||
![]() |
a42d141e6a | ||
![]() |
60ccd12e4b | ||
![]() |
2bdd88a7e8 | ||
![]() |
afe3dc4122 | ||
![]() |
8cd6e4d50f | ||
![]() |
bac17f9bdb | ||
![]() |
a92cf27664 | ||
![]() |
7500e5af1b | ||
![]() |
94888dcc7d | ||
![]() |
09e9fe12e9 | ||
![]() |
eb2d283770 | ||
![]() |
0ef8a0d39b | ||
![]() |
784464c96c | ||
![]() |
202b01ebbf | ||
![]() |
1b49403057 | ||
![]() |
1a3f09f35d | ||
![]() |
ea466f63fd | ||
![]() |
ad95b2d167 | ||
![]() |
f18d09b5c4 | ||
![]() |
96390b09b9 | ||
![]() |
35b555a04b | ||
![]() |
b6a2ae85f4 | ||
![]() |
3f8e27d313 | ||
![]() |
a7b29f9727 | ||
![]() |
3678d26577 | ||
![]() |
042481609a | ||
![]() |
06d4311a9c | ||
![]() |
941ecfb757 |
.gitignoreCargo.lockCargo.tomlREADME.md
dist
doc
misc
res
shell
src
animation
array.rschar.rschoose.rscolor.rsconvert
error.rsexec.rsfill
main.rspattern
printer.rsrender.rsrenderer.rsrunner.rssampler.rssurface.rsterm.rstimer.rstransform
vec.rs
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,2 +1 @@
|
||||
/target
|
||||
Cargo.lock
|
||||
/target
|
747
Cargo.lock
generated
Normal file
747
Cargo.lock
generated
Normal file
@ -0,0 +1,747 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "approx"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "cancellation"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7a879c84c21f354f13535f87ad119ac3be22ebb9097b552a0af6a78f86628c4"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c911b090850d79fc64fe9ea01e28e465f65e821e08813ced95bced72f7a8a9b"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"clap_derive",
|
||||
"clap_lex",
|
||||
"is-terminal",
|
||||
"once_cell",
|
||||
"strsim",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a932373bab67b984c790ddf2c9ca295d8e3af3b7ef92de5a5bacdccdee4b09b"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "033f6b7a4acb1f358c742aaca805c939ee73b4c6209ae4318ec7aca81c42e646"
|
||||
dependencies = [
|
||||
"os_str_bytes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
|
||||
|
||||
[[package]]
|
||||
name = "crossterm"
|
||||
version = "0.26.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"crossterm_winapi",
|
||||
"libc",
|
||||
"mio",
|
||||
"parking_lot",
|
||||
"signal-hook",
|
||||
"signal-hook-mio",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossterm_winapi"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctrlc"
|
||||
version = "3.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbcf33c2a618cbe41ee43ae6e9f2e48368cd9f9db2896f10167d8d762679f639"
|
||||
dependencies = [
|
||||
"nix",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "0.99.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustc_version",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "difflib"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
|
||||
|
||||
[[package]]
|
||||
name = "downcast"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1"
|
||||
dependencies = [
|
||||
"errno-dragonfly",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "errno-dragonfly"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "float-cmp"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fragile"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
|
||||
|
||||
[[package]]
|
||||
name = "io-lifetimes"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09270fd4fa1111bc614ed2246c7ef56239a3063d5be0d1ec3b589c505d400aeb"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8687c819457e979cc940d09cb16e42a1bf70aa6b60a549de6d3a62a0ee90c69e"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"io-lifetimes",
|
||||
"rustix",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.140"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mockall"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50e4a1c770583dac7ab5e2f6c139153b783a53a1bbee9729613f193e59828326"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"downcast",
|
||||
"fragile",
|
||||
"lazy_static",
|
||||
"mockall_derive",
|
||||
"predicates",
|
||||
"predicates-tree",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mockall_derive"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "832663583d5fa284ca8810bf7015e46c9fff9622d3cf34bd1eea5003fec06dd0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.26.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "normalize-line-endings"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
|
||||
|
||||
[[package]]
|
||||
name = "os_str_bytes"
|
||||
version = "6.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||
|
||||
[[package]]
|
||||
name = "predicates"
|
||||
version = "2.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59230a63c37f3e18569bdb90e4a89cbf5bf8b06fea0b84e65ea10cc4df47addd"
|
||||
dependencies = [
|
||||
"difflib",
|
||||
"float-cmp",
|
||||
"itertools",
|
||||
"normalize-line-endings",
|
||||
"predicates-core",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "predicates-core"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174"
|
||||
|
||||
[[package]]
|
||||
name = "predicates-tree"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf"
|
||||
dependencies = [
|
||||
"predicates-core",
|
||||
"termtree",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba466839c78239c09faf015484e5cc04860f88242cff4d03eb038f04b4699b73"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
|
||||
dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.36.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db4165c9963ab29e422d6c26fbc1d37f15bace6b2810221f9d925023480fcf0e"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"io-lifetimes",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.3.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"signal-hook-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-mio"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"mio",
|
||||
"signal-hook",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5aad1363ed6d37b84299588d62d3a7d95b5a5c2d9aad5c85609fda12afaa1f40"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termtree"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.45.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
||||
|
||||
[[package]]
|
||||
name = "wipe"
|
||||
version = "2.1.1"
|
||||
dependencies = [
|
||||
"approx",
|
||||
"cancellation",
|
||||
"clap",
|
||||
"crossterm",
|
||||
"ctrlc",
|
||||
"derive_more",
|
||||
"mockall",
|
||||
"rand",
|
||||
]
|
21
Cargo.toml
21
Cargo.toml
@ -1,17 +1,20 @@
|
||||
# Don't forget to update PKGBUILD
|
||||
|
||||
[package]
|
||||
name = "wipe"
|
||||
version = "1.0.0"
|
||||
version = "2.1.1"
|
||||
edition = "2021"
|
||||
description = "Wipe the content of your terminal."
|
||||
license = "MIT"
|
||||
description = "Wipe your terminal with a random animation."
|
||||
repository = "https://github.com/ricoriedel/wipe"
|
||||
authors = ["Rico Riedel"]
|
||||
authors = ["Rico Riedel <rico.riedel@protonmail.ch>"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
clap = { version = "3.1", features = ["derive"]}
|
||||
crossterm = "0.23"
|
||||
clap = { version = "4.0", features = ["derive"] }
|
||||
crossterm = "0.26"
|
||||
ctrlc = "3.2"
|
||||
cancellation = "0.1"
|
||||
derive_more = "0.99"
|
||||
rand = "0.8"
|
||||
mockall = "0.11"
|
||||
|
||||
[dev-dependencies]
|
||||
mockall = "0.11"
|
||||
approx = "0.5"
|
48
README.md
48
README.md
@ -1,18 +1,42 @@
|
||||
# Wipe
|
||||
Wipe your terminal with a smooth animation.
|
||||
# wipe
|
||||
Wipe the content of your terminal with a random animation.
|
||||
|
||||
This is the perfect program for you, if you like `clear` but want to add an unnecessary animation.
|
||||
This is a fancy alternative to the `clear` command.
|
||||
It plays randomly generated beautiful animations.
|
||||
|
||||
Download options can be found in the [release tab](https://github.com/ricoriedel/wipe/releases).
|
||||
### Build & install
|
||||
Building this project requires Rust and Cargo to be installed.
|
||||
```shell
|
||||
cargo build --release
|
||||
```
|
||||
```shell
|
||||
cp ./target/release/wipe /usr/local/bin
|
||||
```
|
||||
|
||||
## Configuration
|
||||
All configuration is done using command line parameters.
|
||||
If you want a persistent solution, you can add an alias to your `.bashrc` equivalent.
|
||||
### Shell Integration
|
||||
There are scripts for different shells which can be sourced to replace `clear` and `CTRL+L` with this program.
|
||||
The scripts are located in `misc/shell/`.
|
||||
|
||||
For a list of parameters, execute `wipe -h`.
|
||||
Note that some parameters like `--color` can be specified multiple times with different values.
|
||||
| Shell | Script |
|
||||
|:------|:-------------|
|
||||
| ZSH | `wipe.zsh` |
|
||||
| Fish | `wipe.fish` |
|
||||
|
||||
### Arch Linux
|
||||
There is an [AUR package](https://aur.archlinux.org/packages/wipe-term) called `wipe-term`.
|
||||
The scripts can be integrated as follows:
|
||||
|
||||
#### ZSH
|
||||
Put this into your `.zshrc`:
|
||||
```shell
|
||||
source /usr/share/zsh/plugins/wipe/wipe.zsh
|
||||
```
|
||||
|
||||
#### Fish
|
||||
The package will place the script under `/usr/share/fish/vendor_conf.d/`
|
||||
which will be sourced by `fish` with no further configuration required.
|
||||
|
||||
## Showcase
|
||||
[]()
|
||||
[]()
|
||||
[]()
|
||||
[]()
|
||||
[]()
|
||||
[]()
|
28
dist/PKGBUILD
vendored
28
dist/PKGBUILD
vendored
@ -1,28 +0,0 @@
|
||||
pkgname='terminal-wipe-git'
|
||||
pkgver='1.0.0'
|
||||
pkgrel='2'
|
||||
pkgdesc='Wipe your terminal with a random animation.'
|
||||
arch=('x86_64')
|
||||
url='https://github.com/ricoriedel/wipe'
|
||||
license=('MIT')
|
||||
makedepends=('rust')
|
||||
conflicts=('wipe')
|
||||
source=('git+https://www.github.com/ricoriedel/wipe.git')
|
||||
sha256sums=('SKIP')
|
||||
|
||||
build() {
|
||||
cd wipe
|
||||
cargo build --release
|
||||
}
|
||||
|
||||
check() {
|
||||
cd wipe
|
||||
cargo test --release
|
||||
}
|
||||
|
||||
package() {
|
||||
mkdir -p "$pkgdir/usr/bin"
|
||||
mkdir -p "$pkgdir/usr/share/licenses/$pkgname"
|
||||
mv 'wipe/target/release/wipe' "$pkgdir/usr/bin"
|
||||
mv 'wipe/LICENSE' "$pkgdir/usr/share/licenses/$pkgname"
|
||||
}
|
BIN
doc/circle.gif
BIN
doc/circle.gif
Binary file not shown.
Before ![]() (image error) Size: 275 KiB |
BIN
doc/rhombus.gif
BIN
doc/rhombus.gif
Binary file not shown.
Before ![]() (image error) Size: 338 KiB |
BIN
doc/rotation.gif
BIN
doc/rotation.gif
Binary file not shown.
Before ![]() (image error) Size: 223 KiB |
23
misc/res/placeholder.txt
Normal file
23
misc/res/placeholder.txt
Normal file
@ -0,0 +1,23 @@
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
|
||||
incididunt ut labore et dolore magna aliqua. Sapien faucibus et molestie ac.
|
||||
Arcu odio ut sem nulla pharetra. Varius vel pharetra vel turpis nunc eget lorem
|
||||
dolor. Vitae elementum curabitur vitae nunc sed velit dignissim sodales. Felis
|
||||
donec et odio pellentesque diam volutpat commodo sed egestas. Nam at lectus
|
||||
urna duis convallis. Ac turpis egestas sed tempus urna et. Diam quis enim
|
||||
lobortis scelerisque fermentum dui faucibus. Et egestas quis ipsum suspendisse
|
||||
ultrices gravida. Nec dui nunc mattis enim ut. Fermentum posuere urna nec
|
||||
tincidunt praesent semper feugiat nibh sed.
|
||||
|
||||
Aliquet enim tortor at auctor urna nunc. Vulputate enim nulla aliquet porttitor
|
||||
lacus luctus accumsan tortor. Est velit egestas dui id ornare arcu. Luctus
|
||||
accumsan tortor posuere ac ut. At in tellus integer feugiat scelerisque varius
|
||||
morbi enim. Et netus et malesuada fames ac turpis egestas. Aliquet nec
|
||||
ullamcorper sit amet risus nullam eget. Sit amet facilisis magna etiam tempor
|
||||
orci eu. Amet cursus sit amet dictum sit amet justo. Nec ullamcorper sit amet
|
||||
risus nullam eget. Justo eget magna fermentum iaculis. Libero justo laoreet sit
|
||||
amet cursus. Egestas maecenas pharetra convallis posuere. Quis hendrerit dolor
|
||||
magna eget est. Cursus eget nunc scelerisque viverra mauris in aliquam sem
|
||||
fringilla. Amet est placerat in egestas erat imperdiet sed euismod nisi. In
|
||||
aliquam sem fringilla ut morbi tincidunt augue interdum. Mollis aliquam ut
|
||||
porttitor leo. Venenatis urna cursus eget nunc scelerisque viverra mauris in.
|
||||
Egestas quis ipsum suspendisse ultrices gravida dictum fusce ut.
|
BIN
misc/res/rec-1.gif
Normal file
BIN
misc/res/rec-1.gif
Normal file
Binary file not shown.
After ![]() (image error) Size: 2.6 MiB |
BIN
misc/res/rec-2.gif
Normal file
BIN
misc/res/rec-2.gif
Normal file
Binary file not shown.
After ![]() (image error) Size: 2.3 MiB |
BIN
misc/res/rec-3.gif
Normal file
BIN
misc/res/rec-3.gif
Normal file
Binary file not shown.
After ![]() (image error) Size: 2.4 MiB |
16
misc/res/script-1.sh
Executable file
16
misc/res/script-1.sh
Executable file
@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
cat placeholder.txt
|
||||
sleep 1
|
||||
wipe \
|
||||
--char-pattern circle \
|
||||
--char-invert false \
|
||||
--char-segments 3 \
|
||||
--char-shrink 2 \
|
||||
--char-swap false \
|
||||
--color-pattern wheel \
|
||||
--color-segments 2 \
|
||||
--color-invert false \
|
||||
--color-shift true \
|
||||
--color-swap false \
|
||||
--colors rainbow
|
16
misc/res/script-2.sh
Executable file
16
misc/res/script-2.sh
Executable file
@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
cat placeholder.txt
|
||||
sleep 1
|
||||
wipe \
|
||||
--char-pattern wheel \
|
||||
--char-invert false \
|
||||
--char-segments 2 \
|
||||
--char-shrink 2 \
|
||||
--char-swap false \
|
||||
--color-pattern circle \
|
||||
--color-segments 4 \
|
||||
--color-invert false \
|
||||
--color-shift false \
|
||||
--color-swap false \
|
||||
--colors dark-magenta
|
16
misc/res/script-3.sh
Executable file
16
misc/res/script-3.sh
Executable file
@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
cat placeholder.txt
|
||||
sleep 1
|
||||
wipe \
|
||||
--char-pattern rhombus \
|
||||
--char-invert true \
|
||||
--char-segments 2 \
|
||||
--char-shrink 2 \
|
||||
--char-swap false \
|
||||
--color-pattern wheel \
|
||||
--color-segments 2 \
|
||||
--color-invert true \
|
||||
--color-shift true \
|
||||
--color-swap false \
|
||||
--colors cyan
|
7
misc/shell/wipe.fish
Normal file
7
misc/shell/wipe.fish
Normal file
@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env fish
|
||||
|
||||
function clear
|
||||
command wipe
|
||||
end
|
||||
|
||||
bind \cl 'wipe; commandline -f repaint'
|
8
misc/shell/wipe.zsh
Normal file
8
misc/shell/wipe.zsh
Normal file
@ -0,0 +1,8 @@
|
||||
alias clear='wipe'
|
||||
|
||||
_wipe() {
|
||||
wipe
|
||||
zle reset-prompt
|
||||
}
|
||||
zle -N _wipe
|
||||
bindkey '^l' _wipe
|
@ -1,51 +0,0 @@
|
||||
use crate::animation::Animation;
|
||||
use crate::vec::Vector;
|
||||
|
||||
const THICKNESS: f32 = 0.2;
|
||||
const FINAL_RADIUS: f32 = 1.0 + THICKNESS * 2.0;
|
||||
|
||||
pub struct CircleAnimation {
|
||||
center: Vector,
|
||||
thickness: f32,
|
||||
final_radius: f32,
|
||||
}
|
||||
|
||||
impl CircleAnimation {
|
||||
pub fn new(size: Vector) -> Self {
|
||||
let center = size.center();
|
||||
let distance = center.length();
|
||||
|
||||
Self {
|
||||
center,
|
||||
thickness: distance * THICKNESS,
|
||||
final_radius: distance * FINAL_RADIUS,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Animation for CircleAnimation {
|
||||
fn sample(&self, step: f32, pos: Vector) -> f32 {
|
||||
let radius = self.final_radius * step - self.thickness;
|
||||
let distance = (pos - self.center).length();
|
||||
|
||||
(distance - radius) / self.thickness
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn sample() {
|
||||
let anim = CircleAnimation::new(Vector::new(10.0, 20.0));
|
||||
|
||||
let sample_1 = anim.sample(0.5, Vector::new(17.0, 5.0));
|
||||
let sample_2 = anim.sample(0.8, Vector::new(11.0, 8.0));
|
||||
let sample_3 = anim.sample(0.2, Vector::new(7.0, 10.0));
|
||||
|
||||
assert!(3.3 < sample_1 && sample_1 < 3.4);
|
||||
assert!(-1.8 < sample_2 && sample_2 < -1.7);
|
||||
assert!(0.4 < sample_3 && sample_3 < 0.5);
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
pub mod circle;
|
||||
pub mod rotation;
|
||||
pub mod rhombus;
|
||||
|
||||
use crate::vec::Vector;
|
||||
|
||||
use mockall::automock;
|
||||
|
||||
#[automock]
|
||||
pub trait Animation {
|
||||
fn sample(&self, step: f32, pos: Vector) -> f32;
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
use crate::animation::Animation;
|
||||
use crate::vec::Vector;
|
||||
|
||||
const THICKNESS: f32 = 0.2;
|
||||
const FINAL_DISTANCE: f32 = 1.0 + THICKNESS * 2.0;
|
||||
|
||||
pub struct RhombusAnimation {
|
||||
center: Vector,
|
||||
thickness: f32,
|
||||
final_distance: f32,
|
||||
}
|
||||
|
||||
impl RhombusAnimation {
|
||||
pub fn new(size: Vector) -> Self {
|
||||
let center = size.center();
|
||||
let distance = center.sum();
|
||||
|
||||
Self {
|
||||
center,
|
||||
thickness: distance * THICKNESS,
|
||||
final_distance: distance * FINAL_DISTANCE,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Animation for RhombusAnimation {
|
||||
fn sample(&self, step: f32, pos: Vector) -> f32 {
|
||||
let dist = self.final_distance * step - self.thickness;
|
||||
let pos_dist = (self.center - pos).abs().sum();
|
||||
|
||||
(pos_dist - dist) / self.thickness
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn sample() {
|
||||
let anim = RhombusAnimation::new(Vector::new(30.0, 10.0));
|
||||
|
||||
let sample_1 = anim.sample(0.2, Vector::new(5.0, 16.0));
|
||||
let sample_2 = anim.sample(0.7, Vector::new(22.0, 2.0));
|
||||
let sample_3 = anim.sample(0.5, Vector::new(4.0, 7.0));
|
||||
|
||||
assert!(4.8 < sample_1 && sample_1 < 4.9);
|
||||
assert!(-1.5 < sample_2 && sample_2 < -1.4);
|
||||
assert!(0.7 < sample_3 && sample_3 < 0.8);
|
||||
}
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
use std::f32::consts::PI;
|
||||
use crate::animation::Animation;
|
||||
use crate::vec::Vector;
|
||||
|
||||
const TWO_PI: f32 = PI * 2.0;
|
||||
const THICKNESS: f32 = TWO_PI * 0.1;
|
||||
const FULL_ROTATION: f32 = TWO_PI + THICKNESS * 2.0;
|
||||
|
||||
pub struct RotationAnimation {
|
||||
center: Vector
|
||||
}
|
||||
|
||||
impl RotationAnimation {
|
||||
pub fn new(size: Vector) -> Self {
|
||||
Self {
|
||||
center: size.center()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Animation for RotationAnimation {
|
||||
fn sample(&self, step: f32, pos: Vector) -> f32 {
|
||||
let angle = FULL_ROTATION * step - PI - THICKNESS;
|
||||
let pos_angle = (pos - self.center).angle();
|
||||
|
||||
(pos_angle - angle) / THICKNESS
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn sample() {
|
||||
let anim = RotationAnimation::new(Vector::new(30.0, 10.0));
|
||||
|
||||
let sample_1 = anim.sample(0.2, Vector::new(5.0, 16.0));
|
||||
let sample_2 = anim.sample(0.7, Vector::new(22.0, 2.0));
|
||||
let sample_3 = anim.sample(0.5, Vector::new(4.0, 7.0));
|
||||
|
||||
assert!(2.4 < sample_1 && sample_1 < 2.5);
|
||||
assert!(0.7 < sample_2 && sample_2 < 0.8);
|
||||
assert!(-2.3 < sample_3 && sample_3 < -2.2);
|
||||
}
|
||||
}
|
98
src/array.rs
98
src/array.rs
@ -1,98 +0,0 @@
|
||||
use std::ops::{Index, IndexMut};
|
||||
|
||||
/// A two dimensional statically size array.
|
||||
pub struct Array2D<T> {
|
||||
width: usize,
|
||||
height: usize,
|
||||
values: Vec<T>
|
||||
}
|
||||
|
||||
impl<T: Default + Copy> Array2D<T> {
|
||||
pub fn new(width: usize, height: usize) -> Self {
|
||||
Self {
|
||||
width,
|
||||
height,
|
||||
values: vec![T::default(); width * height]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn width(&self) -> usize {
|
||||
self.width
|
||||
}
|
||||
|
||||
pub fn height(&self) -> usize {
|
||||
self.height
|
||||
}
|
||||
|
||||
/// Calculates the physical index of the given position.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if the position is out of bounds.
|
||||
fn index_of(&self, pos: (usize, usize)) -> usize {
|
||||
assert!(pos.0 < self.width);
|
||||
assert!(pos.1 < self.height);
|
||||
pos.0 + pos.1 * self.width
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Default + Copy> Index<(usize, usize)> for Array2D<T> {
|
||||
type Output = T;
|
||||
|
||||
fn index(&self, pos: (usize, usize)) -> &Self::Output {
|
||||
unsafe { self.values.get_unchecked(self.index_of(pos)) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Default + Copy> IndexMut<(usize, usize)> for Array2D<T> {
|
||||
fn index_mut(&mut self, pos: (usize, usize)) -> &mut Self::Output {
|
||||
let i = self.index_of(pos);
|
||||
|
||||
unsafe { self.values.get_unchecked_mut(i) }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::array::Array2D;
|
||||
|
||||
#[test]
|
||||
fn width() {
|
||||
let array = Array2D::<()>::new(10, 4);
|
||||
|
||||
assert_eq!(10, array.width());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn height() {
|
||||
let array = Array2D::<()>::new(2, 5);
|
||||
|
||||
assert_eq!(5, array.height());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn index() {
|
||||
let mut array = Array2D::new(4, 4);
|
||||
|
||||
array[(1, 2)] = 3;
|
||||
array[(3, 3)] = 7;
|
||||
|
||||
assert_eq!(3, array[(1, 2)]);
|
||||
assert_eq!(7, array[(3, 3)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn index_oob_width() {
|
||||
let array = Array2D::<()>::new(5, 10);
|
||||
|
||||
array[(8, 2)];
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn index_oob_height() {
|
||||
let array = Array2D::<()>::new(10, 5);
|
||||
|
||||
array[(3, 7)];
|
||||
}
|
||||
}
|
58
src/char.rs
58
src/char.rs
@ -1,58 +0,0 @@
|
||||
use mockall::automock;
|
||||
|
||||
/// Used to get a character with a given brightness.
|
||||
#[automock]
|
||||
pub trait CharSampler {
|
||||
/// Gets a character with the given brightness.
|
||||
/// # Arguments
|
||||
/// * `level`: `0 <= level` and `level < 1`
|
||||
fn sample(&self, level: f32) -> char;
|
||||
}
|
||||
|
||||
pub struct SimpleCharSampler {
|
||||
chars: String
|
||||
}
|
||||
|
||||
impl SimpleCharSampler {
|
||||
/// # Arguments
|
||||
/// * `chars`: The characters ordered by brightness.
|
||||
pub fn new(chars: String) -> Self {
|
||||
Self { chars }
|
||||
}
|
||||
}
|
||||
|
||||
impl CharSampler for SimpleCharSampler {
|
||||
fn sample(&self, level: f32) -> char {
|
||||
assert!(0.0 <= level && level < 1.0);
|
||||
|
||||
let index = level * self.chars.chars().count() as f32;
|
||||
|
||||
self.chars.chars().nth(index as usize).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn sample() {
|
||||
let sampler = SimpleCharSampler::new("abc".to_string());
|
||||
|
||||
assert_eq!('a', sampler.sample(0.1));
|
||||
assert_eq!('b', sampler.sample(0.4));
|
||||
assert_eq!('c', sampler.sample(0.7));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn sample_index_negative() {
|
||||
SimpleCharSampler::new("abc".to_string()).sample(-0.1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn sample_index_equals_one() {
|
||||
SimpleCharSampler::new("abc".to_string()).sample(1.0);
|
||||
}
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
use rand::prelude::IteratorRandom;
|
||||
use rand::Rng;
|
||||
|
||||
pub trait Options {
|
||||
fn all() -> Vec<Self> where Self: Sized;
|
||||
}
|
||||
|
||||
pub struct Chooser<TRng> {
|
||||
rng: TRng
|
||||
}
|
||||
|
||||
impl<TRng: Rng> Chooser<TRng> {
|
||||
pub fn new(rng: TRng) -> Self {
|
||||
Self { rng }
|
||||
}
|
||||
|
||||
pub fn choose<TValue: Options>(&mut self, selection: Vec<TValue>) -> TValue {
|
||||
let options = if selection.is_empty() {
|
||||
TValue::all()
|
||||
} else {
|
||||
selection
|
||||
};
|
||||
options.into_iter().choose_stable(&mut self.rng).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use rand::rngs::mock::StepRng;
|
||||
use crate::{Chooser, Options};
|
||||
|
||||
enum MockOptions {
|
||||
First,
|
||||
Second,
|
||||
Third
|
||||
}
|
||||
|
||||
impl Options for MockOptions {
|
||||
fn all() -> Vec<Self> where Self: Sized {
|
||||
use MockOptions::*;
|
||||
|
||||
vec![First, Second, Third]
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn choose() {
|
||||
let rng = StepRng::new(0, 1);
|
||||
let mut chooser = Chooser::new(rng);
|
||||
|
||||
assert!(matches!(chooser.choose(vec![MockOptions::First, MockOptions::Second]), MockOptions::Second));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn choose_empty() {
|
||||
let rng = StepRng::new(0, 1);
|
||||
let mut chooser = Chooser::new(rng);
|
||||
|
||||
assert!(matches!(chooser.choose(Vec::new()), MockOptions::Third));
|
||||
}
|
||||
}
|
58
src/color.rs
58
src/color.rs
@ -1,58 +0,0 @@
|
||||
use crossterm::style::Color;
|
||||
use mockall::automock;
|
||||
|
||||
/// A collection of colors.
|
||||
#[automock]
|
||||
pub trait ColorSampler {
|
||||
/// Gets a color for the given fill.
|
||||
/// # Arguments
|
||||
/// * `fill`: `0 <= fill` and `fill < 1`
|
||||
fn sample(&self, fill: f32) -> Color;
|
||||
}
|
||||
|
||||
pub struct SimpleColorSampler {
|
||||
values: Vec<Color>
|
||||
}
|
||||
|
||||
impl SimpleColorSampler {
|
||||
pub fn new(values: Vec<Color>) -> Self {
|
||||
Self { values }
|
||||
}
|
||||
}
|
||||
|
||||
impl ColorSampler for SimpleColorSampler {
|
||||
fn sample(&self, fill: f32) -> Color {
|
||||
assert!(0.0 <= fill && fill < 1.0);
|
||||
|
||||
let index = self.values.len() as f32 * fill;
|
||||
|
||||
self.values[index as usize]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crossterm::style::Color::*;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn sample() {
|
||||
let sampler = SimpleColorSampler::new(vec![Red, Yellow, Green]);
|
||||
|
||||
assert_eq!(Red, sampler.sample(0.1));
|
||||
assert_eq!(Yellow, sampler.sample(0.4));
|
||||
assert_eq!(Green, sampler.sample(0.7));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn sample_index_negative() {
|
||||
SimpleColorSampler::new(Vec::new()).sample(-0.1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn sample_index_equals_one() {
|
||||
SimpleColorSampler::new(Vec::new()).sample(1.0);
|
||||
}
|
||||
}
|
74
src/convert/char.rs
Normal file
74
src/convert/char.rs
Normal file
@ -0,0 +1,74 @@
|
||||
/// A sample for a terminal cell.
|
||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||
pub enum CharSample {
|
||||
/// Keep the char.
|
||||
Keep,
|
||||
/// Override the char.
|
||||
Draw(char),
|
||||
/// Clear the char.
|
||||
Clear,
|
||||
}
|
||||
|
||||
/// A trait to convert a sample to a [CharSample].
|
||||
#[cfg_attr(test, mockall::automock)]
|
||||
pub trait CharConverter {
|
||||
fn convert(&self, level: f32) -> CharSample;
|
||||
}
|
||||
|
||||
/// The implementation of [CharConverter].
|
||||
pub struct CharConverterImpl {
|
||||
chars: Vec<char>,
|
||||
}
|
||||
|
||||
impl CharConverterImpl {
|
||||
/// The chars used for mapping.
|
||||
pub fn new(chars: String) -> Self {
|
||||
let chars = chars.chars().collect();
|
||||
|
||||
Self { chars }
|
||||
}
|
||||
}
|
||||
|
||||
impl CharConverter for CharConverterImpl {
|
||||
fn convert(&self, level: f32) -> CharSample {
|
||||
if level < 0.0 {
|
||||
CharSample::Clear
|
||||
} else if level < 1.0 {
|
||||
let len = self.chars.len() as f32;
|
||||
let index = (level * len) as usize;
|
||||
|
||||
CharSample::Draw(self.chars[index])
|
||||
} else {
|
||||
CharSample::Keep
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn convert_clear() {
|
||||
let converter = CharConverterImpl::new("abc".to_string());
|
||||
|
||||
assert_eq!(CharSample::Clear, converter.convert(-0.1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_draw() {
|
||||
let converter = CharConverterImpl::new("xyz".to_string());
|
||||
|
||||
assert_eq!(CharSample::Draw('x'), converter.convert(0.0));
|
||||
assert_eq!(CharSample::Draw('y'), converter.convert(0.5));
|
||||
assert_eq!(CharSample::Draw('z'), converter.convert(0.9));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_keep() {
|
||||
let converter = CharConverterImpl::new("123".to_string());
|
||||
|
||||
assert_eq!(CharSample::Keep, converter.convert(1.0));
|
||||
assert_eq!(CharSample::Keep, converter.convert(1.5));
|
||||
}
|
||||
}
|
69
src/convert/color.rs
Normal file
69
src/convert/color.rs
Normal file
@ -0,0 +1,69 @@
|
||||
use crossterm::style::Color;
|
||||
|
||||
/// A trait to convert a sample to a [Color].
|
||||
#[cfg_attr(test, mockall::automock)]
|
||||
pub trait ColorConverter {
|
||||
fn convert(&self, level: f32) -> Color;
|
||||
}
|
||||
|
||||
/// The implementation of [ColorConverter].
|
||||
pub struct ColorConverterImpl {
|
||||
colors: Vec<Color>,
|
||||
}
|
||||
|
||||
impl ColorConverterImpl {
|
||||
/// The colors used for mapping.
|
||||
pub fn new(colors: Vec<Color>) -> Self {
|
||||
Self { colors }
|
||||
}
|
||||
}
|
||||
|
||||
impl ColorConverter for ColorConverterImpl {
|
||||
fn convert(&self, level: f32) -> Color {
|
||||
let len = self.colors.len() as f32;
|
||||
let index = (level * len).rem_euclid(len) as usize;
|
||||
|
||||
self.colors[index]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crossterm::style::Color::*;
|
||||
|
||||
#[test]
|
||||
fn convert_negative_index() {
|
||||
let converter = ColorConverterImpl::new(vec![Red, Green, Blue]);
|
||||
|
||||
assert_eq!(Blue, converter.convert(-0.2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_index_zero() {
|
||||
let converter = ColorConverterImpl::new(vec![Red, Green, Blue]);
|
||||
|
||||
assert_eq!(Red, converter.convert(0.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert() {
|
||||
let converter = ColorConverterImpl::new(vec![Red, Green, Blue]);
|
||||
|
||||
assert_eq!(Green, converter.convert(0.5));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_index_one() {
|
||||
let converter = ColorConverterImpl::new(vec![Red, Green, Blue]);
|
||||
|
||||
assert_eq!(Red, converter.convert(1.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_index_above_one() {
|
||||
let converter = ColorConverterImpl::new(vec![Red, Green, Blue]);
|
||||
|
||||
assert_eq!(Green, converter.convert(1.5));
|
||||
}
|
||||
}
|
72
src/convert/mod.rs
Normal file
72
src/convert/mod.rs
Normal file
@ -0,0 +1,72 @@
|
||||
//! Contains structs for converting samples to concrete types.
|
||||
|
||||
mod char;
|
||||
mod color;
|
||||
|
||||
pub use crate::convert::char::*;
|
||||
pub use crate::convert::color::*;
|
||||
|
||||
use crossterm::style::Color;
|
||||
|
||||
/// A trait to convert samples to concrete types.
|
||||
#[cfg_attr(test, mockall::automock)]
|
||||
pub trait Converter {
|
||||
/// Converts a sample to a [CharSample].
|
||||
fn char(&self, level: f32) -> CharSample;
|
||||
/// Converts a sample to a [Color].
|
||||
fn color(&self, level: f32) -> Color;
|
||||
}
|
||||
|
||||
/// The implementation of [Converter].
|
||||
#[derive(derive_more::Constructor)]
|
||||
pub struct ConverterImpl<T1, T2> {
|
||||
char: T1,
|
||||
color: T2,
|
||||
}
|
||||
|
||||
impl<T1: CharConverter, T2: ColorConverter> Converter for ConverterImpl<T1, T2> {
|
||||
fn char(&self, level: f32) -> CharSample {
|
||||
self.char.convert(level)
|
||||
}
|
||||
|
||||
fn color(&self, level: f32) -> Color {
|
||||
self.color.convert(level)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::convert::MockCharConverter;
|
||||
use crate::convert::MockColorConverter;
|
||||
use mockall::predicate::*;
|
||||
|
||||
#[test]
|
||||
fn char() {
|
||||
let mut char = MockCharConverter::new();
|
||||
let color = MockColorConverter::new();
|
||||
|
||||
char.expect_convert()
|
||||
.with(eq(4.0))
|
||||
.return_const(CharSample::Draw('M'));
|
||||
|
||||
let converter = ConverterImpl::new(char, color);
|
||||
|
||||
assert_eq!(CharSample::Draw('M'), converter.char(4.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn color() {
|
||||
let char = MockCharConverter::new();
|
||||
let mut color = MockColorConverter::new();
|
||||
|
||||
color
|
||||
.expect_convert()
|
||||
.with(eq(2.0))
|
||||
.return_const(Color::Yellow);
|
||||
|
||||
let converter = ConverterImpl::new(char, color);
|
||||
|
||||
assert_eq!(Color::Yellow, converter.color(2.0));
|
||||
}
|
||||
}
|
49
src/error.rs
Normal file
49
src/error.rs
Normal file
@ -0,0 +1,49 @@
|
||||
use std::fmt::{Debug, Formatter};
|
||||
|
||||
/// The error type.
|
||||
pub struct Error(String);
|
||||
|
||||
impl Debug for Error {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for Error {
|
||||
fn from(err: std::io::Error) -> Self {
|
||||
Error(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Error {
|
||||
fn from(msg: &str) -> Self {
|
||||
Error(msg.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ctrlc::Error> for Error {
|
||||
fn from(err: ctrlc::Error) -> Self {
|
||||
Error(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn from_std_io_error() {
|
||||
let io_err = std::io::Error::new(std::io::ErrorKind::Other, "123");
|
||||
let msg = io_err.to_string();
|
||||
let err: Error = io_err.into();
|
||||
|
||||
assert_eq!(msg, format!("{:?}", err));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_ref_str() {
|
||||
let err: Error = "123".into();
|
||||
|
||||
assert_eq!("123", format!("{:?}", err));
|
||||
}
|
||||
}
|
196
src/exec.rs
Normal file
196
src/exec.rs
Normal file
@ -0,0 +1,196 @@
|
||||
use crate::Error;
|
||||
use crate::Renderer;
|
||||
use cancellation::CancellationToken;
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
/// A stub for the system clock.
|
||||
#[cfg_attr(test, mockall::automock)]
|
||||
pub trait Clock {
|
||||
/// Returns the current time.
|
||||
fn now(&self) -> Instant;
|
||||
|
||||
/// Sleep for the given duration.
|
||||
fn sleep(&self, duration: Duration);
|
||||
}
|
||||
|
||||
/// The implementation of [Clock].
|
||||
#[derive(derive_more::Constructor)]
|
||||
pub struct ClockImpl;
|
||||
|
||||
impl Clock for ClockImpl {
|
||||
fn now(&self) -> Instant {
|
||||
Instant::now()
|
||||
}
|
||||
|
||||
fn sleep(&self, duration: Duration) {
|
||||
thread::sleep(duration)
|
||||
}
|
||||
}
|
||||
|
||||
/// A timer for rendering.
|
||||
#[derive(derive_more::Constructor)]
|
||||
pub struct Executor<T> {
|
||||
clock: T,
|
||||
duration: Duration,
|
||||
delay: Duration,
|
||||
}
|
||||
|
||||
impl<T: Clock> Executor<T> {
|
||||
/// Runs the animation main loop.
|
||||
pub fn run(&self, mut renderer: impl Renderer, token: &CancellationToken) -> Result<(), Error> {
|
||||
let start = self.clock.now();
|
||||
let mut tick = start;
|
||||
|
||||
while !token.is_canceled() && tick.duration_since(start) < self.duration {
|
||||
let step = tick.duration_since(start).as_secs_f32() / self.duration.as_secs_f32();
|
||||
|
||||
renderer.render(step)?;
|
||||
tick = self.delay(tick);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sleeps until the next frame starts.
|
||||
/// Returns the current time.
|
||||
fn delay(&self, begin: Instant) -> Instant {
|
||||
let end = self.clock.now();
|
||||
|
||||
if self.delay > end.duration_since(begin) {
|
||||
self.clock.sleep(self.delay - end.duration_since(begin));
|
||||
}
|
||||
self.clock.now()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::MockRenderer;
|
||||
use cancellation::CancellationTokenSource;
|
||||
use mockall::predicate::eq;
|
||||
use mockall::Sequence;
|
||||
|
||||
#[test]
|
||||
fn run_steps_correct() {
|
||||
let mut clock = MockClock::new();
|
||||
let clock_seq = &mut Sequence::new();
|
||||
let begin = Instant::now();
|
||||
|
||||
clock
|
||||
.expect_now()
|
||||
.once()
|
||||
.return_const(begin)
|
||||
.in_sequence(clock_seq);
|
||||
clock
|
||||
.expect_now()
|
||||
.times(2)
|
||||
.return_const(begin + Duration::from_secs(10))
|
||||
.in_sequence(clock_seq);
|
||||
clock
|
||||
.expect_now()
|
||||
.times(2)
|
||||
.return_const(begin + Duration::from_secs(20))
|
||||
.in_sequence(clock_seq);
|
||||
|
||||
let timer = Executor::new(clock, Duration::from_secs(20), Duration::from_secs(10));
|
||||
|
||||
let mut renderer = MockRenderer::new();
|
||||
let renderer_seq = &mut Sequence::new();
|
||||
|
||||
renderer
|
||||
.expect_render()
|
||||
.with(eq(0.0))
|
||||
.once()
|
||||
.returning(|_| Ok(()))
|
||||
.in_sequence(renderer_seq);
|
||||
renderer
|
||||
.expect_render()
|
||||
.with(eq(0.5))
|
||||
.once()
|
||||
.returning(|_| Ok(()))
|
||||
.in_sequence(renderer_seq);
|
||||
|
||||
timer.run(renderer, CancellationToken::none()).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_sleep_duration_correct() {
|
||||
let mut clock = MockClock::new();
|
||||
let clock_seq = &mut Sequence::new();
|
||||
let begin = Instant::now();
|
||||
|
||||
clock
|
||||
.expect_now()
|
||||
.once()
|
||||
.return_const(begin)
|
||||
.in_sequence(clock_seq);
|
||||
clock
|
||||
.expect_now()
|
||||
.once()
|
||||
.return_const(begin + Duration::from_secs(4))
|
||||
.in_sequence(clock_seq);
|
||||
clock
|
||||
.expect_sleep()
|
||||
.once()
|
||||
.with(eq(Duration::from_secs(6)))
|
||||
.return_const(())
|
||||
.in_sequence(clock_seq);
|
||||
clock
|
||||
.expect_now()
|
||||
.once()
|
||||
.return_const(begin + Duration::from_secs(10))
|
||||
.in_sequence(clock_seq);
|
||||
|
||||
let timer = Executor::new(clock, Duration::from_secs(10), Duration::from_secs(10));
|
||||
|
||||
let mut renderer = MockRenderer::new();
|
||||
renderer.expect_render().returning(|_| Ok(()));
|
||||
|
||||
timer.run(renderer, CancellationToken::none()).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_delay_exceeded_does_not_sleep() {
|
||||
let mut clock = MockClock::new();
|
||||
let clock_seq = &mut Sequence::new();
|
||||
let begin = Instant::now();
|
||||
|
||||
clock
|
||||
.expect_now()
|
||||
.once()
|
||||
.return_const(begin)
|
||||
.in_sequence(clock_seq);
|
||||
clock
|
||||
.expect_now()
|
||||
.times(2)
|
||||
.return_const(begin + Duration::from_secs(12))
|
||||
.in_sequence(clock_seq);
|
||||
|
||||
let timer = Executor::new(clock, Duration::from_secs(10), Duration::from_secs(10));
|
||||
|
||||
let mut renderer = MockRenderer::new();
|
||||
renderer.expect_render().returning(|_| Ok(()));
|
||||
|
||||
timer.run(renderer, CancellationToken::none()).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_canceled_aborts() {
|
||||
let mut clock = MockClock::new();
|
||||
let mut renderer = MockRenderer::new();
|
||||
let src = CancellationTokenSource::new();
|
||||
let token = src.token().clone();
|
||||
|
||||
clock.expect_now().return_const(Instant::now());
|
||||
clock.expect_sleep().once().return_const(());
|
||||
renderer.expect_render().once().returning(move |_| {
|
||||
src.cancel();
|
||||
|
||||
Ok(())
|
||||
});
|
||||
let timer = Executor::new(clock, Duration::from_secs(10), Duration::from_secs(1));
|
||||
|
||||
timer.run(renderer, &token).unwrap();
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
use crate::fill::FillMode;
|
||||
use crate::vec::Vector;
|
||||
|
||||
const INTERVAL: f32 = 4.0;
|
||||
|
||||
pub struct CircleFillMode {
|
||||
center: Vector,
|
||||
interval: f32
|
||||
}
|
||||
|
||||
impl CircleFillMode {
|
||||
pub fn new(size: Vector) -> Self {
|
||||
Self {
|
||||
center: size.center(),
|
||||
interval: size.smaller() / INTERVAL,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FillMode for CircleFillMode {
|
||||
fn sample(&self, _: f32, pos: Vector) -> f32 {
|
||||
((pos - self.center).length() % self.interval) / self.interval
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn sample() {
|
||||
let fill = CircleFillMode::new(Vector::new(10.0, 8.0));
|
||||
|
||||
let sample_1 = fill.sample(0.0, Vector::new(5.0, 3.0));
|
||||
let sample_2 = fill.sample(0.0, Vector::new(8.5, 4.0));
|
||||
|
||||
assert!(0.4 < sample_1 && sample_1 < 0.6);
|
||||
assert!(0.7 < sample_2 && sample_2 < 0.8);
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
use crate::fill::FillMode;
|
||||
use crate::vec::Vector;
|
||||
|
||||
pub struct LevelFillMode;
|
||||
|
||||
impl LevelFillMode {
|
||||
pub fn new() -> Self {
|
||||
Self { }
|
||||
}
|
||||
}
|
||||
|
||||
impl FillMode for LevelFillMode {
|
||||
fn sample(&self, level: f32, _: Vector) -> f32 {
|
||||
level
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn sample() {
|
||||
let mode = LevelFillMode::new();
|
||||
|
||||
assert_eq!(0.3, mode.sample(0.3, Vector::new(0.0, 0.0)));
|
||||
assert_eq!(0.7, mode.sample(0.7, Vector::new(0.1, 0.2)));
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
pub mod level;
|
||||
pub mod circle;
|
||||
pub mod stripes;
|
||||
|
||||
use crate::vec::Vector;
|
||||
|
||||
use mockall::automock;
|
||||
|
||||
/// Used to choose the colors of characters.
|
||||
#[automock]
|
||||
pub trait FillMode {
|
||||
/// Gets the color for this character.
|
||||
fn sample(&self, level: f32, pos: Vector) -> f32;
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
use crate::FillMode;
|
||||
use crate::vec::Vector;
|
||||
|
||||
const INTERVAL: f32 = 4.0;
|
||||
|
||||
pub struct StripesFillMode {
|
||||
interval: f32
|
||||
}
|
||||
|
||||
impl StripesFillMode {
|
||||
pub fn new(size: Vector) -> Self {
|
||||
Self {
|
||||
interval: size.smaller() / INTERVAL
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FillMode for StripesFillMode {
|
||||
fn sample(&self, _: f32, pos: Vector) -> f32 {
|
||||
(pos.sum() % self.interval) / self.interval
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn sample() {
|
||||
let mode = StripesFillMode::new(Vector::new(8.0, 4.0));
|
||||
|
||||
assert_eq!(0.25, mode.sample(0.0, Vector::new(1.5, 0.75)));
|
||||
assert_eq!(0.5, mode.sample(0.0, Vector::new(4.0, 2.5)));
|
||||
}
|
||||
}
|
556
src/main.rs
556
src/main.rs
@ -1,151 +1,453 @@
|
||||
pub mod convert;
|
||||
pub mod pattern;
|
||||
pub mod transform;
|
||||
|
||||
mod error;
|
||||
mod exec;
|
||||
mod printer;
|
||||
mod renderer;
|
||||
mod term;
|
||||
mod vec;
|
||||
|
||||
pub use error::*;
|
||||
pub use exec::*;
|
||||
pub use printer::*;
|
||||
pub use renderer::*;
|
||||
pub use term::*;
|
||||
pub use vec::*;
|
||||
|
||||
use crate::convert::*;
|
||||
use crate::pattern::*;
|
||||
use crate::transform::*;
|
||||
use cancellation::CancellationTokenSource;
|
||||
use clap::builder::NonEmptyStringValueParser;
|
||||
use clap::{value_parser, Parser, ValueEnum};
|
||||
use crossterm::style::Color;
|
||||
use crossterm::style::Color::*;
|
||||
use rand::prelude::*;
|
||||
use std::io::stdout;
|
||||
use std::time::Duration;
|
||||
use anyhow::Error;
|
||||
use clap::Parser;
|
||||
use clap::ArgEnum;
|
||||
use rand::rngs::OsRng;
|
||||
use crate::animation::Animation;
|
||||
use crate::animation::circle::CircleAnimation;
|
||||
use crate::animation::rhombus::RhombusAnimation;
|
||||
use crate::animation::rotation::RotationAnimation;
|
||||
use crate::char::SimpleCharSampler;
|
||||
use crate::choose::{Chooser, Options};
|
||||
use crate::color::{ColorSampler, SimpleColorSampler};
|
||||
use crate::fill::circle::CircleFillMode;
|
||||
use crate::fill::FillMode;
|
||||
use crate::fill::level::LevelFillMode;
|
||||
use crate::fill::stripes::StripesFillMode;
|
||||
use crate::render::{Renderer, SamplerRenderer};
|
||||
use crate::runner::Runner;
|
||||
use crate::sampler::ComposedSampler;
|
||||
use crate::surface::WriteSurface;
|
||||
use crate::timer::SimpleTimer;
|
||||
use crate::vec::Vector;
|
||||
|
||||
mod color;
|
||||
mod char;
|
||||
mod fill;
|
||||
mod vec;
|
||||
mod array;
|
||||
mod surface;
|
||||
mod animation;
|
||||
mod sampler;
|
||||
mod render;
|
||||
mod timer;
|
||||
mod runner;
|
||||
mod choose;
|
||||
/// The command line arguments.
|
||||
#[derive(Parser, Default)]
|
||||
#[command(
|
||||
author = env!("CARGO_PKG_AUTHORS"),
|
||||
version = env!("CARGO_PKG_VERSION"),
|
||||
about = env!("CARGO_PKG_DESCRIPTION"),
|
||||
)]
|
||||
struct Args {
|
||||
/// Set the animation duration as milliseconds
|
||||
#[arg(
|
||||
long,
|
||||
default_value_t = 2000,
|
||||
value_parser = value_parser!(u64).range(0..=60_000),
|
||||
help = "Set the animation duration [milliseconds]"
|
||||
)]
|
||||
duration: u64,
|
||||
/// Set the frames per second
|
||||
#[arg(long, default_value_t = 60, value_parser = value_parser!(u64).range(1..=480))]
|
||||
fps: u64,
|
||||
/// Choose the chars used to draw the pattern
|
||||
#[arg(long, default_value = ".:+#", value_parser = NonEmptyStringValueParser::new())]
|
||||
chars: String,
|
||||
/// Choose the pattern
|
||||
#[arg(long, value_enum)]
|
||||
char_pattern: Option<PatternEnum>,
|
||||
/// Choose whether to invert the pattern
|
||||
#[arg(long)]
|
||||
char_invert: Option<bool>,
|
||||
/// Choose whether to swap the x-axis and y-axis of the pattern
|
||||
#[arg(long)]
|
||||
char_swap: Option<bool>,
|
||||
/// Choose the segment count of the pattern [default: 1-4]
|
||||
#[arg(long, value_parser = value_parser!(u8).range(1..255))]
|
||||
char_segments: Option<u8>,
|
||||
/// Choose the factor by which to shrink the pattern [default: 1-4]
|
||||
#[arg(long, value_parser = value_parser!(u8).range(1..255))]
|
||||
char_shrink: Option<u8>,
|
||||
/// Choose the colors used for the pattern
|
||||
#[arg(long, value_enum)]
|
||||
colors: Option<PalletEnum>,
|
||||
/// Choose the fill pattern
|
||||
#[arg(long, value_enum)]
|
||||
color_pattern: Option<PatternEnum>,
|
||||
/// Choose whether the fill pattern should move
|
||||
#[arg(long)]
|
||||
color_shift: Option<bool>,
|
||||
/// Choose whether to invert the fill pattern
|
||||
#[arg(long)]
|
||||
color_invert: Option<bool>,
|
||||
/// Choose whether to swap the x-axis and y-axis of the fill pattern
|
||||
#[arg(long)]
|
||||
color_swap: Option<bool>,
|
||||
/// Choose the segment count of the fill pattern [default: 1-4]
|
||||
#[arg(long, value_parser = value_parser!(u8).range(1..255))]
|
||||
color_segments: Option<u8>,
|
||||
}
|
||||
|
||||
macro_rules! options {
|
||||
($name:ident { $($opt:ident,)* }) => {
|
||||
#[derive(Copy, Clone, ArgEnum)]
|
||||
enum $name {
|
||||
$($opt,)*
|
||||
}
|
||||
impl Options for $name {
|
||||
fn all() -> Vec<Self> {
|
||||
vec![$($name::$opt,)*]
|
||||
}
|
||||
/// All color pallets.
|
||||
#[derive(ValueEnum, Copy, Clone)]
|
||||
enum PalletEnum {
|
||||
Red,
|
||||
Yellow,
|
||||
Green,
|
||||
Blue,
|
||||
Magenta,
|
||||
Cyan,
|
||||
Rainbow,
|
||||
|
||||
DarkRed,
|
||||
DarkYellow,
|
||||
DarkGreen,
|
||||
DarkBlue,
|
||||
DarkMagenta,
|
||||
DarkCyan,
|
||||
DarkRainbow,
|
||||
|
||||
RedYellow,
|
||||
YellowGreen,
|
||||
GreenBlue,
|
||||
BlueCyan,
|
||||
CyanMagenta,
|
||||
MagentaRed,
|
||||
|
||||
Gray,
|
||||
}
|
||||
|
||||
/// All possible [Pattern]s.
|
||||
#[derive(ValueEnum, Copy, Clone, PartialEq, Debug)]
|
||||
enum PatternEnum {
|
||||
Circle,
|
||||
Line,
|
||||
Rhombus,
|
||||
Wheel,
|
||||
}
|
||||
|
||||
/// A configuration for a composed [Pattern].
|
||||
#[derive(derive_more::Constructor)]
|
||||
struct PatternConfig {
|
||||
pattern: PatternEnum,
|
||||
shift: bool,
|
||||
invert: bool,
|
||||
swap: bool,
|
||||
segments: f32,
|
||||
shrink: f32,
|
||||
}
|
||||
|
||||
impl Args {
|
||||
/// Returns the configuration for the char [Pattern].
|
||||
fn char_config(&self, rng: &mut impl Rng) -> PatternConfig {
|
||||
PatternConfig::new(
|
||||
choose(self.char_pattern, rng),
|
||||
true,
|
||||
self.char_invert.unwrap_or(rng.gen()),
|
||||
self.char_swap.unwrap_or(rng.gen()),
|
||||
self.char_segments.unwrap_or(rng.gen_range(1..=4)) as f32,
|
||||
self.char_shrink.unwrap_or(rng.gen_range(1..=4)) as f32,
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns the configuration for the color [Pattern].
|
||||
fn color_config(&self, rng: &mut impl Rng) -> PatternConfig {
|
||||
PatternConfig::new(
|
||||
choose(self.color_pattern, rng),
|
||||
self.color_shift.unwrap_or(rng.gen()),
|
||||
self.color_invert.unwrap_or(rng.gen()),
|
||||
self.color_swap.unwrap_or(rng.gen()),
|
||||
self.color_segments.unwrap_or(rng.gen_range(1..=4)) as f32,
|
||||
1.0,
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns the colors for the [ColorConverter].
|
||||
fn pallet(&self, rng: &mut impl Rng) -> Vec<Color> {
|
||||
match choose(self.colors, rng) {
|
||||
PalletEnum::Red => vec![DarkRed, Red, White],
|
||||
PalletEnum::Yellow => vec![DarkYellow, Yellow, White],
|
||||
PalletEnum::Green => vec![DarkGreen, Green, White],
|
||||
PalletEnum::Blue => vec![DarkBlue, Blue, White],
|
||||
PalletEnum::Magenta => vec![DarkMagenta, Magenta, White],
|
||||
PalletEnum::Cyan => vec![DarkCyan, Cyan, White],
|
||||
PalletEnum::Rainbow => vec![Red, Yellow, Green, Blue, Cyan, Magenta],
|
||||
|
||||
PalletEnum::DarkRed => vec![Black, DarkRed, Red],
|
||||
PalletEnum::DarkYellow => vec![Black, DarkYellow, Yellow],
|
||||
PalletEnum::DarkGreen => vec![Black, DarkGreen, Green],
|
||||
PalletEnum::DarkBlue => vec![Black, DarkBlue, Blue],
|
||||
PalletEnum::DarkMagenta => vec![Black, DarkMagenta, Magenta],
|
||||
PalletEnum::DarkCyan => vec![Black, DarkCyan, Cyan],
|
||||
PalletEnum::DarkRainbow => vec![
|
||||
DarkRed,
|
||||
DarkYellow,
|
||||
DarkGreen,
|
||||
DarkBlue,
|
||||
DarkCyan,
|
||||
DarkMagenta,
|
||||
],
|
||||
|
||||
PalletEnum::RedYellow => vec![Red, DarkRed, DarkYellow, Yellow],
|
||||
PalletEnum::YellowGreen => vec![Yellow, DarkYellow, DarkGreen, Green],
|
||||
PalletEnum::GreenBlue => vec![Green, DarkGreen, DarkBlue, Blue],
|
||||
PalletEnum::BlueCyan => vec![Blue, DarkBlue, DarkCyan, Cyan],
|
||||
PalletEnum::CyanMagenta => vec![Cyan, DarkCyan, DarkMagenta, Magenta],
|
||||
PalletEnum::MagentaRed => vec![Magenta, DarkMagenta, DarkRed, Red],
|
||||
|
||||
PalletEnum::Gray => vec![Black, DarkGrey, Grey, White],
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the duration for the [Timer].
|
||||
fn duration(&self) -> Duration {
|
||||
Duration::from_millis(self.duration)
|
||||
}
|
||||
|
||||
/// Returns the delay for the [Timer].
|
||||
fn delay(&self) -> Duration {
|
||||
Duration::from_nanos(1_000_000_000 / self.fps)
|
||||
}
|
||||
}
|
||||
|
||||
options!(AnimationType {
|
||||
Circle,
|
||||
Rhombus,
|
||||
Rotation,
|
||||
});
|
||||
impl PatternConfig {
|
||||
/// Creates a new base [Pattern].
|
||||
fn create_base(&self) -> Box<dyn PatternFactory> {
|
||||
match self.pattern {
|
||||
PatternEnum::Circle => Box::new(CircleFactory::new()),
|
||||
PatternEnum::Line => Box::new(LineFactory::new()),
|
||||
PatternEnum::Rhombus => Box::new(RhombusFactory::new()),
|
||||
PatternEnum::Wheel => Box::new(WheelFactory::new()),
|
||||
}
|
||||
}
|
||||
|
||||
options!(ColorType {
|
||||
Red,
|
||||
Green,
|
||||
Blue,
|
||||
LightRed,
|
||||
LightGreen,
|
||||
LightBlue,
|
||||
Grey,
|
||||
Rainbow,
|
||||
});
|
||||
/// Creates a new composed [Pattern].
|
||||
fn create(&self) -> Box<dyn PatternFactory> {
|
||||
let mut pattern = self.create_base();
|
||||
|
||||
options!(FillModeType {
|
||||
Circle,
|
||||
Level,
|
||||
Stripes,
|
||||
});
|
||||
if self.shift {
|
||||
pattern = Box::new(ShiftFactory::new(pattern))
|
||||
}
|
||||
if self.invert {
|
||||
pattern = Box::new(InvertFactory::new(pattern))
|
||||
}
|
||||
if self.swap {
|
||||
pattern = Box::new(SwapFactory::new(pattern))
|
||||
}
|
||||
if self.segments != 1.0 {
|
||||
pattern = Box::new(SegmentsFactory::new(pattern, self.segments));
|
||||
}
|
||||
if self.shrink != 1.0 {
|
||||
pattern = Box::new(ShrinkFactory::new(pattern, self.shrink));
|
||||
}
|
||||
pattern
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[clap(author = env ! ("CARGO_PKG_AUTHORS"), version = env ! ("CARGO_PKG_VERSION"), about = env ! ("CARGO_PKG_DESCRIPTION"))]
|
||||
struct Args {
|
||||
#[clap(short, long, help = "Add animation", arg_enum)]
|
||||
animation: Vec<AnimationType>,
|
||||
#[clap(short, long, help = "Add fill mode", arg_enum)]
|
||||
fill: Vec<FillModeType>,
|
||||
#[clap(short, long, help = "Add color pallet", arg_enum)]
|
||||
color: Vec<ColorType>,
|
||||
#[clap(long, default_value = ".-+%#", help = "Set chars")]
|
||||
chars: String,
|
||||
#[clap(long, default_value = "30", help = "Set frames per second")]
|
||||
fps: u64,
|
||||
#[clap(long, default_value = "1000", help = "Set duration [milliseconds]")]
|
||||
duration: u64,
|
||||
#[clap(long, help = "Set width [default: terminal width]")]
|
||||
width: Option<usize>,
|
||||
#[clap(long, help = "Set height [default: terminal height]")]
|
||||
height: Option<usize>,
|
||||
/// Returns the value of the [Option] or a random enum variant.
|
||||
fn choose<TValue: ValueEnum, TRand: Rng>(opt: Option<TValue>, rng: &mut TRand) -> TValue {
|
||||
match opt {
|
||||
Some(value) => value.clone(),
|
||||
None => TValue::value_variants().iter().choose(rng).unwrap().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Error> {
|
||||
let args = Args::parse();
|
||||
let mut chooser = Chooser::new(OsRng::default());
|
||||
let rand = &mut thread_rng();
|
||||
|
||||
let terminal = crossterm::terminal::size()?;
|
||||
let width = args.width.unwrap_or(terminal.0 as usize);
|
||||
let height = args.height.unwrap_or(terminal.1 as usize);
|
||||
let size = Vector::from_terminal(width, height);
|
||||
let delay = Duration::from_micros(1_000_000 / args.fps);
|
||||
let duration = Duration::from_millis(args.duration);
|
||||
let char = args.char_config(rand).create();
|
||||
let color = args.color_config(rand).create();
|
||||
let pallet = args.pallet(rand);
|
||||
let duration = args.duration();
|
||||
let delay = args.delay();
|
||||
|
||||
let animation = create_animation(chooser.choose(args.animation), size);
|
||||
let fill = create_fill(chooser.choose(args.fill), size);
|
||||
let color = create_color(chooser.choose(args.color));
|
||||
let char = Box::new(SimpleCharSampler::new(args.chars));
|
||||
let sampler = SamplerFactoryImpl::new(char, color);
|
||||
let char_converter = CharConverterImpl::new(args.chars);
|
||||
let color_converter = ColorConverterImpl::new(pallet);
|
||||
let converter = ConverterImpl::new(char_converter, color_converter);
|
||||
let term = TerminalImpl::new(stdout());
|
||||
let printer = PrinterImpl::new(term)?;
|
||||
let renderer = RendererImpl::new(sampler, converter, printer)?;
|
||||
|
||||
let sampler = ComposedSampler::new(animation, fill, color, char);
|
||||
let surface = WriteSurface::new(stdout(), width, height);
|
||||
let clock = ClockImpl::new();
|
||||
let executor = Executor::new(clock, duration, delay);
|
||||
|
||||
let renderer = SamplerRenderer::new(surface, sampler);
|
||||
let timer = SimpleTimer::new(delay);
|
||||
let runner = Runner::new(duration, timer, renderer);
|
||||
let src = CancellationTokenSource::new();
|
||||
let token = src.token().clone();
|
||||
|
||||
runner.run()
|
||||
ctrlc::set_handler(move || {
|
||||
src.cancel();
|
||||
})?;
|
||||
executor.run(renderer, &token)
|
||||
}
|
||||
|
||||
fn create_animation(animation: AnimationType, size: Vector) -> Box<dyn Animation> {
|
||||
match animation {
|
||||
AnimationType::Circle => Box::new(CircleAnimation::new(size)),
|
||||
AnimationType::Rhombus => Box::new(RhombusAnimation::new(size)),
|
||||
AnimationType::Rotation => Box::new(RotationAnimation::new(size)),
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use approx::*;
|
||||
use rand::rngs::mock::StepRng;
|
||||
|
||||
#[test]
|
||||
fn args_pallet_all_defined() {
|
||||
let rand = &mut StepRng::new(1, 1);
|
||||
|
||||
for value in PalletEnum::value_variants() {
|
||||
let args = Args {
|
||||
colors: Some(*value),
|
||||
..Args::default()
|
||||
};
|
||||
assert!(args.pallet(rand).len() > 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duration() {
|
||||
let args = Args {
|
||||
duration: 3500,
|
||||
..Args::default()
|
||||
};
|
||||
assert_eq!(Duration::from_millis(3500), args.duration());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delay() {
|
||||
let args = Args {
|
||||
fps: 20,
|
||||
..Args::default()
|
||||
};
|
||||
assert_eq!(Duration::from_millis(50), args.delay());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn char_config_pattern() {
|
||||
let rng = &mut StepRng::new(1, 1);
|
||||
let args = Args {
|
||||
char_pattern: Some(PatternEnum::Line),
|
||||
..Args::default()
|
||||
};
|
||||
assert_eq!(PatternEnum::Line, args.char_config(rng).pattern);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn char_config_invert() {
|
||||
let rng = &mut StepRng::new(1, 1);
|
||||
let args = Args {
|
||||
char_invert: Some(false),
|
||||
..Args::default()
|
||||
};
|
||||
assert_eq!(false, args.char_config(rng).invert);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn char_config_shift() {
|
||||
let rng = &mut StepRng::new(1, 1);
|
||||
let args = Args::default();
|
||||
|
||||
assert_eq!(true, args.char_config(rng).shift);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn char_config_swap() {
|
||||
let rng = &mut StepRng::new(1, 1);
|
||||
let args = Args {
|
||||
char_swap: Some(true),
|
||||
..Args::default()
|
||||
};
|
||||
assert_eq!(true, args.char_config(rng).swap);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn char_config_segments() {
|
||||
let rng = &mut StepRng::new(1, 1);
|
||||
let args = Args {
|
||||
char_segments: Some(12),
|
||||
..Args::default()
|
||||
};
|
||||
assert_abs_diff_eq!(12.0, args.char_config(rng).segments);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn char_config_shrink() {
|
||||
let rng = &mut StepRng::new(1, 1);
|
||||
let args = Args {
|
||||
char_shrink: Some(42),
|
||||
..Args::default()
|
||||
};
|
||||
assert_abs_diff_eq!(42.0, args.char_config(rng).shrink);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn color_config_pattern() {
|
||||
let rng = &mut StepRng::new(1, 1);
|
||||
let args = Args {
|
||||
color_pattern: Some(PatternEnum::Circle),
|
||||
..Args::default()
|
||||
};
|
||||
assert_eq!(PatternEnum::Circle, args.color_config(rng).pattern);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn color_config_invert() {
|
||||
let rng = &mut StepRng::new(1, 1);
|
||||
let args = Args {
|
||||
color_invert: Some(true),
|
||||
..Args::default()
|
||||
};
|
||||
assert_eq!(true, args.color_config(rng).invert);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn color_config_shift() {
|
||||
let rng = &mut StepRng::new(1, 1);
|
||||
let args = Args {
|
||||
color_shift: Some(false),
|
||||
..Args::default()
|
||||
};
|
||||
assert_eq!(false, args.color_config(rng).shift);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn color_config_swap() {
|
||||
let rng = &mut StepRng::new(1, 1);
|
||||
let args = Args {
|
||||
color_swap: Some(true),
|
||||
..Args::default()
|
||||
};
|
||||
assert_eq!(true, args.color_config(rng).swap);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn color_config_segments() {
|
||||
let rng = &mut StepRng::new(1, 1);
|
||||
let args = Args {
|
||||
color_segments: Some(23),
|
||||
..Args::default()
|
||||
};
|
||||
|
||||
assert_abs_diff_eq!(23.0, args.color_config(rng).segments);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn color_config_shrink() {
|
||||
let rng = &mut StepRng::new(1, 1);
|
||||
let args = Args::default();
|
||||
|
||||
assert_abs_diff_eq!(1.0, args.color_config(rng).shrink);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pattern_config_all_defined() {
|
||||
for value in PatternEnum::value_variants() {
|
||||
let config = PatternConfig {
|
||||
pattern: *value,
|
||||
shift: true,
|
||||
invert: true,
|
||||
swap: true,
|
||||
segments: 3.0,
|
||||
shrink: 2.0,
|
||||
};
|
||||
config
|
||||
.create()
|
||||
.create(&Config::default())
|
||||
.sample(Vector::default());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_fill(fill: FillModeType, size: Vector) -> Box<dyn FillMode> {
|
||||
match fill {
|
||||
FillModeType::Circle => Box::new(CircleFillMode::new(size)),
|
||||
FillModeType::Level => Box::new(LevelFillMode::new()),
|
||||
FillModeType::Stripes => Box::new(StripesFillMode::new(size))
|
||||
}
|
||||
}
|
||||
|
||||
fn create_color(color: ColorType) -> Box<dyn ColorSampler> {
|
||||
use crossterm::style::Color::*;
|
||||
|
||||
match color {
|
||||
ColorType::Red => Box::new(SimpleColorSampler::new(vec![Yellow, DarkYellow, Red])),
|
||||
ColorType::Green => Box::new(SimpleColorSampler::new(vec![Cyan, DarkGreen, Green])),
|
||||
ColorType::Blue => Box::new(SimpleColorSampler::new(vec![Magenta, DarkBlue, Blue])),
|
||||
ColorType::LightRed => Box::new(SimpleColorSampler::new(vec![White, Yellow, Red])),
|
||||
ColorType::LightGreen => Box::new(SimpleColorSampler::new(vec![White, Cyan, Green])),
|
||||
ColorType::LightBlue => Box::new(SimpleColorSampler::new(vec![White, Blue, Magenta])),
|
||||
ColorType::Grey => Box::new(SimpleColorSampler::new(vec![Black, Grey, White])),
|
||||
ColorType::Rainbow => Box::new(SimpleColorSampler::new(vec![Magenta, Blue, Green, Yellow, Red]))
|
||||
}
|
||||
}
|
51
src/pattern/circle.rs
Normal file
51
src/pattern/circle.rs
Normal file
@ -0,0 +1,51 @@
|
||||
use crate::pattern::*;
|
||||
use crate::Vector;
|
||||
|
||||
/// A factory for [Circle].
|
||||
#[derive(derive_more::Constructor)]
|
||||
pub struct CircleFactory;
|
||||
/// A circular [Pattern].
|
||||
pub struct Circle {
|
||||
center: Vector,
|
||||
radius: f32,
|
||||
}
|
||||
|
||||
impl PatternFactory for CircleFactory {
|
||||
fn create(&self, config: &Config) -> Box<dyn Pattern> {
|
||||
Box::new(Circle::new(config))
|
||||
}
|
||||
}
|
||||
|
||||
impl Circle {
|
||||
pub fn new(config: &Config) -> Self {
|
||||
let center = config.size.center();
|
||||
let radius = center.len();
|
||||
|
||||
Self { center, radius }
|
||||
}
|
||||
}
|
||||
|
||||
impl Pattern for Circle {
|
||||
fn sample(&self, pos: Vector) -> f32 {
|
||||
(pos - self.center).len() / self.radius
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use approx::*;
|
||||
|
||||
#[test]
|
||||
fn sample() {
|
||||
let config = Config {
|
||||
size: Vector::new(10.0, 20.0),
|
||||
..Config::default()
|
||||
};
|
||||
let pattern = CircleFactory::new().create(&config);
|
||||
|
||||
assert_abs_diff_eq!(1.0, pattern.sample(Vector::new(0.0, 0.0)), epsilon = 0.1);
|
||||
assert_abs_diff_eq!(0.0, pattern.sample(Vector::new(5.0, 10.0)), epsilon = 0.1);
|
||||
assert_abs_diff_eq!(0.5, pattern.sample(Vector::new(7.5, 15.0)), epsilon = 0.1);
|
||||
}
|
||||
}
|
50
src/pattern/line.rs
Normal file
50
src/pattern/line.rs
Normal file
@ -0,0 +1,50 @@
|
||||
use crate::pattern::*;
|
||||
use crate::Vector;
|
||||
|
||||
/// A factory for [Line].
|
||||
#[derive(derive_more::Constructor)]
|
||||
pub struct LineFactory;
|
||||
/// A horizontal line [Pattern].
|
||||
pub struct Line {
|
||||
width: f32,
|
||||
}
|
||||
|
||||
impl PatternFactory for LineFactory {
|
||||
fn create(&self, config: &Config) -> Box<dyn Pattern> {
|
||||
Box::new(Line::new(config))
|
||||
}
|
||||
}
|
||||
|
||||
impl Line {
|
||||
pub fn new(config: &Config) -> Self {
|
||||
let width = config.size.x;
|
||||
|
||||
Self { width }
|
||||
}
|
||||
}
|
||||
|
||||
impl Pattern for Line {
|
||||
fn sample(&self, pos: Vector) -> f32 {
|
||||
pos.x / self.width
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use approx::*;
|
||||
|
||||
#[test]
|
||||
fn sample() {
|
||||
let config = Config {
|
||||
size: Vector::new(20.0, 0.0),
|
||||
..Config::default()
|
||||
};
|
||||
let pattern = LineFactory::new().create(&config);
|
||||
|
||||
assert_abs_diff_eq!(0.0, pattern.sample(Vector::new(0.0, 4.0)), epsilon = 0.1);
|
||||
assert_abs_diff_eq!(0.4, pattern.sample(Vector::new(8.0, 8.0)), epsilon = 0.1);
|
||||
assert_abs_diff_eq!(0.8, pattern.sample(Vector::new(16.0, 7.0)), epsilon = 0.1);
|
||||
assert_abs_diff_eq!(1.0, pattern.sample(Vector::new(20.0, 3.0)), epsilon = 0.1);
|
||||
}
|
||||
}
|
157
src/pattern/mod.rs
Normal file
157
src/pattern/mod.rs
Normal file
@ -0,0 +1,157 @@
|
||||
//! Contains all pattern traits and base patterns.
|
||||
|
||||
mod circle;
|
||||
mod line;
|
||||
mod rhombus;
|
||||
mod wheel;
|
||||
|
||||
pub use circle::*;
|
||||
pub use line::*;
|
||||
pub use rhombus::*;
|
||||
pub use wheel::*;
|
||||
|
||||
use crate::Vector;
|
||||
|
||||
/// A configuration for a [Pattern].
|
||||
#[derive(Copy, Clone, Default, PartialEq, Debug)]
|
||||
pub struct Config {
|
||||
/// The size of the terminal.
|
||||
pub size: Vector,
|
||||
/// The current state of the animation.
|
||||
pub step: f32,
|
||||
}
|
||||
|
||||
/// A factory to create a [Pattern].
|
||||
#[cfg_attr(test, mockall::automock)]
|
||||
pub trait PatternFactory {
|
||||
/// Creates a new [Pattern] with the given configuration.
|
||||
fn create(&self, config: &Config) -> Box<dyn Pattern>;
|
||||
}
|
||||
|
||||
/// A pattern for an animation.
|
||||
#[cfg_attr(test, mockall::automock)]
|
||||
pub trait Pattern {
|
||||
/// Returns the level for a given coordinate.
|
||||
/// If it is a base pattern, the start position of the
|
||||
/// animation should by zero and the end position should be one.
|
||||
fn sample(&self, pos: Vector) -> f32;
|
||||
}
|
||||
|
||||
/// A factor for a [Sampler].
|
||||
#[cfg_attr(test, mockall::automock(type Sampler = MockSampler;))]
|
||||
pub trait SamplerFactory {
|
||||
/// The type of the [Sampler].
|
||||
type Sampler: Sampler;
|
||||
|
||||
/// Creates a new [Sampler].
|
||||
fn create(&self, config: &Config) -> Self::Sampler;
|
||||
}
|
||||
|
||||
/// A sampler for multiple values.
|
||||
#[cfg_attr(test, mockall::automock)]
|
||||
pub trait Sampler {
|
||||
/// Returns the char level for a given position.
|
||||
fn char(&self, pos: Vector) -> f32;
|
||||
|
||||
/// Returns the color level for a given position.
|
||||
fn color(&self, pos: Vector) -> f32;
|
||||
}
|
||||
|
||||
/// The implementation of [SamplerFactory].
|
||||
#[derive(derive_more::Constructor)]
|
||||
pub struct SamplerFactoryImpl {
|
||||
char: Box<dyn PatternFactory>,
|
||||
color: Box<dyn PatternFactory>,
|
||||
}
|
||||
|
||||
/// The implementation of [Sampler].
|
||||
#[derive(derive_more::Constructor)]
|
||||
pub struct SamplerImpl {
|
||||
char: Box<dyn Pattern>,
|
||||
color: Box<dyn Pattern>,
|
||||
}
|
||||
|
||||
impl SamplerFactory for SamplerFactoryImpl {
|
||||
type Sampler = SamplerImpl;
|
||||
|
||||
fn create(&self, config: &Config) -> Self::Sampler {
|
||||
SamplerImpl::new(self.char.create(config), self.color.create(config))
|
||||
}
|
||||
}
|
||||
|
||||
impl Sampler for SamplerImpl {
|
||||
fn char(&self, pos: Vector) -> f32 {
|
||||
self.char.sample(pos)
|
||||
}
|
||||
|
||||
fn color(&self, pos: Vector) -> f32 {
|
||||
self.color.sample(pos)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use approx::*;
|
||||
use mockall::predicate::eq;
|
||||
|
||||
#[test]
|
||||
fn char() {
|
||||
let mut char = MockPattern::new();
|
||||
let color = MockPattern::new();
|
||||
|
||||
char.expect_sample()
|
||||
.with(eq(Vector::new(2.0, 5.0)))
|
||||
.return_const(2.5);
|
||||
|
||||
let sampler = SamplerImpl::new(Box::new(char), Box::new(color));
|
||||
|
||||
assert_abs_diff_eq!(2.5, sampler.char(Vector::new(2.0, 5.0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn color() {
|
||||
let char = MockPattern::new();
|
||||
let mut color = MockPattern::new();
|
||||
|
||||
color
|
||||
.expect_sample()
|
||||
.with(eq(Vector::new(4.0, 2.0)))
|
||||
.return_const(3.2);
|
||||
|
||||
let sampler = SamplerImpl::new(Box::new(char), Box::new(color));
|
||||
|
||||
assert_abs_diff_eq!(3.2, sampler.color(Vector::new(4.0, 2.0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn factory() {
|
||||
let mut char = MockPatternFactory::new();
|
||||
let mut color = MockPatternFactory::new();
|
||||
let config = Config {
|
||||
size: Vector::new(2.0, 3.0),
|
||||
step: 0.6,
|
||||
};
|
||||
|
||||
char.expect_create().with(eq(config)).once().returning(|_| {
|
||||
let mut sampler = MockPattern::new();
|
||||
sampler.expect_sample().return_const(3.0);
|
||||
Box::new(sampler)
|
||||
});
|
||||
color
|
||||
.expect_create()
|
||||
.with(eq(config))
|
||||
.once()
|
||||
.returning(|_| {
|
||||
let mut sampler = MockPattern::new();
|
||||
sampler.expect_sample().return_const(5.0);
|
||||
Box::new(sampler)
|
||||
});
|
||||
|
||||
let factory = SamplerFactoryImpl::new(Box::new(char), Box::new(color));
|
||||
let sampler = factory.create(&config);
|
||||
|
||||
assert_abs_diff_eq!(3.0, sampler.char(Vector::default()));
|
||||
assert_abs_diff_eq!(5.0, sampler.color(Vector::default()));
|
||||
}
|
||||
}
|
52
src/pattern/rhombus.rs
Normal file
52
src/pattern/rhombus.rs
Normal file
@ -0,0 +1,52 @@
|
||||
use crate::pattern::*;
|
||||
use crate::Vector;
|
||||
|
||||
/// A factory for [Rhombus].
|
||||
#[derive(derive_more::Constructor)]
|
||||
pub struct RhombusFactory;
|
||||
/// A rhombus shaped [Pattern].
|
||||
pub struct Rhombus {
|
||||
center: Vector,
|
||||
distance: f32,
|
||||
}
|
||||
|
||||
impl PatternFactory for RhombusFactory {
|
||||
fn create(&self, config: &Config) -> Box<dyn Pattern> {
|
||||
Box::new(Rhombus::new(config))
|
||||
}
|
||||
}
|
||||
|
||||
impl Rhombus {
|
||||
pub fn new(config: &Config) -> Rhombus {
|
||||
let center = config.size.center();
|
||||
let distance = center.sum();
|
||||
|
||||
Self { center, distance }
|
||||
}
|
||||
}
|
||||
|
||||
impl Pattern for Rhombus {
|
||||
fn sample(&self, pos: Vector) -> f32 {
|
||||
(pos - self.center).abs().sum() / self.distance
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use approx::*;
|
||||
|
||||
#[test]
|
||||
fn sample() {
|
||||
let config = Config {
|
||||
size: Vector::new(10.0, 5.0),
|
||||
..Config::default()
|
||||
};
|
||||
let pattern = RhombusFactory::new().create(&config);
|
||||
|
||||
assert_abs_diff_eq!(1.0, pattern.sample(Vector::new(0.0, 0.0)), epsilon = 0.1);
|
||||
assert_abs_diff_eq!(1.0, pattern.sample(Vector::new(10.0, 5.0)), epsilon = 0.1);
|
||||
assert_abs_diff_eq!(0.0, pattern.sample(Vector::new(5.0, 2.5)), epsilon = 0.1);
|
||||
assert_abs_diff_eq!(0.5, pattern.sample(Vector::new(7.0, 0.5)), epsilon = 0.1);
|
||||
}
|
||||
}
|
51
src/pattern/wheel.rs
Normal file
51
src/pattern/wheel.rs
Normal file
@ -0,0 +1,51 @@
|
||||
use crate::pattern::*;
|
||||
use crate::Vector;
|
||||
use std::f32::consts::PI;
|
||||
|
||||
/// A factory for [Wheel].
|
||||
#[derive(derive_more::Constructor)]
|
||||
pub struct WheelFactory;
|
||||
/// A fortune wheel [Pattern].
|
||||
pub struct Wheel {
|
||||
center: Vector,
|
||||
}
|
||||
|
||||
impl PatternFactory for WheelFactory {
|
||||
fn create(&self, config: &Config) -> Box<dyn Pattern> {
|
||||
Box::new(Wheel::new(config))
|
||||
}
|
||||
}
|
||||
|
||||
impl Wheel {
|
||||
pub fn new(config: &Config) -> Self {
|
||||
let center = config.size.center();
|
||||
|
||||
Self { center }
|
||||
}
|
||||
}
|
||||
|
||||
impl Pattern for Wheel {
|
||||
fn sample(&self, pos: Vector) -> f32 {
|
||||
((pos - self.center).angle() + PI) / PI / 2.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use approx::*;
|
||||
|
||||
#[test]
|
||||
fn sample() {
|
||||
let config = Config {
|
||||
size: Vector::new(10.0, 20.0),
|
||||
..Config::default()
|
||||
};
|
||||
let pattern = WheelFactory::new().create(&config);
|
||||
|
||||
assert_abs_diff_eq!(0.0, pattern.sample(Vector::new(0.0, 9.0)), epsilon = 0.1);
|
||||
assert_abs_diff_eq!(1.0, pattern.sample(Vector::new(0.0, 10.0)), epsilon = 0.1);
|
||||
assert_abs_diff_eq!(0.5, pattern.sample(Vector::new(10.0, 10.0)), epsilon = 0.1);
|
||||
assert_abs_diff_eq!(0.75, pattern.sample(Vector::new(5.0, 20.0)), epsilon = 0.1);
|
||||
}
|
||||
}
|
335
src/printer.rs
Normal file
335
src/printer.rs
Normal file
@ -0,0 +1,335 @@
|
||||
use crate::Error;
|
||||
use crate::Terminal;
|
||||
use crossterm::cursor::*;
|
||||
use crossterm::style::*;
|
||||
use crossterm::terminal::*;
|
||||
|
||||
/// A trait for performance optimized terminal output.
|
||||
///
|
||||
/// All commands are queue and have to be executed using [Printer::flush].
|
||||
#[cfg_attr(test, mockall::automock)]
|
||||
pub trait Printer {
|
||||
/// Shows the cursor if it isn't visible.
|
||||
fn show_cursor(&mut self) -> Result<(), Error>;
|
||||
/// Hides the cursor if it is visible.
|
||||
fn hide_cursor(&mut self) -> Result<(), Error>;
|
||||
/// Prints a character.
|
||||
/// # Panics
|
||||
/// Panics if the character is a special character like `ESC`, `DEL` or `NEWLINE`.
|
||||
fn print(&mut self, char: char) -> Result<(), Error>;
|
||||
/// Moves the cursor to the specified position if it isn't there already.
|
||||
fn move_to(&mut self, x: u16, y: u16) -> Result<(), Error>;
|
||||
/// Returns the size of the terminal.
|
||||
fn size(&self) -> Result<(u16, u16), Error>;
|
||||
/// Sets the foreground color of the terminal.
|
||||
fn set_foreground(&mut self, color: Color) -> Result<(), Error>;
|
||||
/// Clears the terminal content.
|
||||
fn clear(&mut self) -> Result<(), Error>;
|
||||
/// Flushes all queue commands.
|
||||
fn flush(&mut self) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
/// The implementation of [Printer].
|
||||
pub struct PrinterImpl<T> {
|
||||
term: T,
|
||||
position: (u16, u16),
|
||||
cursor: Option<bool>,
|
||||
foreground: Option<Color>,
|
||||
}
|
||||
|
||||
impl<T: Terminal> PrinterImpl<T> {
|
||||
pub fn new(term: T) -> Result<Self, Error> {
|
||||
let position = term.position()?;
|
||||
|
||||
Ok(Self {
|
||||
term,
|
||||
position,
|
||||
cursor: None,
|
||||
foreground: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Terminal> Printer for PrinterImpl<T> {
|
||||
fn show_cursor(&mut self) -> Result<(), Error> {
|
||||
if self.cursor != Some(true) {
|
||||
self.cursor = Some(true);
|
||||
self.term.queue(Show)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn hide_cursor(&mut self) -> Result<(), Error> {
|
||||
if self.cursor != Some(false) {
|
||||
self.cursor = Some(false);
|
||||
self.term.queue(Hide)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print(&mut self, char: char) -> Result<(), Error> {
|
||||
if char < '\u{20}' || char == '\u{7F}' {
|
||||
return Err("Special chars can't be printed.".into());
|
||||
}
|
||||
self.position.0 += 1;
|
||||
self.term.queue(Print(char))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn move_to(&mut self, x: u16, y: u16) -> Result<(), Error> {
|
||||
if self.position != (x, y) {
|
||||
self.position = (x, y);
|
||||
self.term.queue(MoveTo(x, y))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn size(&self) -> Result<(u16, u16), Error> {
|
||||
self.term.size()
|
||||
}
|
||||
|
||||
fn set_foreground(&mut self, color: Color) -> Result<(), Error> {
|
||||
if self.foreground != Some(color) {
|
||||
self.foreground = Some(color);
|
||||
self.term.queue(SetForegroundColor(color))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn clear(&mut self) -> Result<(), Error> {
|
||||
self.term.queue(Clear(ClearType::Purge))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> Result<(), Error> {
|
||||
self.term.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::MockTerminal;
|
||||
use mockall::predicate::eq;
|
||||
|
||||
#[test]
|
||||
fn show_cursor() {
|
||||
let mut mock = MockTerminal::new();
|
||||
mock.expect_position().returning(|| Ok((0, 0)));
|
||||
mock.expect_queue()
|
||||
.with(eq(Show))
|
||||
.once()
|
||||
.returning(|_| Ok(()));
|
||||
|
||||
PrinterImpl::new(mock).unwrap().show_cursor().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn show_cursor_twice_queues_once() {
|
||||
let mut mock = MockTerminal::new();
|
||||
mock.expect_position().returning(|| Ok((0, 0)));
|
||||
mock.expect_queue()
|
||||
.with(eq(Show))
|
||||
.once()
|
||||
.returning(|_| Ok(()));
|
||||
|
||||
let mut printer = PrinterImpl::new(mock).unwrap();
|
||||
|
||||
printer.show_cursor().unwrap();
|
||||
printer.show_cursor().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn show_cursor_after_hiding_queues_show() {
|
||||
let mut mock = MockTerminal::new();
|
||||
mock.expect_position().returning(|| Ok((0, 0)));
|
||||
mock.expect_queue()
|
||||
.with(eq(Show))
|
||||
.once()
|
||||
.returning(|_| Ok(()));
|
||||
mock.expect_queue().with(eq(Hide)).returning(|_| Ok(()));
|
||||
|
||||
let mut printer = PrinterImpl::new(mock).unwrap();
|
||||
|
||||
printer.hide_cursor().unwrap();
|
||||
printer.show_cursor().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hide_cursor() {
|
||||
let mut mock = MockTerminal::new();
|
||||
mock.expect_position().returning(|| Ok((0, 0)));
|
||||
mock.expect_queue()
|
||||
.with(eq(Hide))
|
||||
.once()
|
||||
.returning(|_| Ok(()));
|
||||
|
||||
PrinterImpl::new(mock).unwrap().hide_cursor().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hide_cursor_twice_queues_once() {
|
||||
let mut mock = MockTerminal::new();
|
||||
mock.expect_position().returning(|| Ok((0, 0)));
|
||||
mock.expect_queue()
|
||||
.with(eq(Hide))
|
||||
.once()
|
||||
.returning(|_| Ok(()));
|
||||
|
||||
let mut printer = PrinterImpl::new(mock).unwrap();
|
||||
|
||||
printer.hide_cursor().unwrap();
|
||||
printer.hide_cursor().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hide_cursor_after_showing_queues_hide() {
|
||||
let mut mock = MockTerminal::new();
|
||||
mock.expect_position().returning(|| Ok((0, 0)));
|
||||
mock.expect_queue()
|
||||
.with(eq(Show))
|
||||
.once()
|
||||
.returning(|_| Ok(()));
|
||||
mock.expect_queue().with(eq(Hide)).returning(|_| Ok(()));
|
||||
|
||||
let mut printer = PrinterImpl::new(mock).unwrap();
|
||||
|
||||
printer.show_cursor().unwrap();
|
||||
printer.hide_cursor().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_foreground() {
|
||||
let mut mock = MockTerminal::new();
|
||||
mock.expect_position().returning(|| Ok((0, 0)));
|
||||
mock.expect_queue()
|
||||
.with(eq(SetForegroundColor(Color::Blue)))
|
||||
.once()
|
||||
.returning(|_| Ok(()));
|
||||
|
||||
PrinterImpl::new(mock)
|
||||
.unwrap()
|
||||
.set_foreground(Color::Blue)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_foreground_twice_queues_once() {
|
||||
let mut mock = MockTerminal::new();
|
||||
mock.expect_position().returning(|| Ok((0, 0)));
|
||||
mock.expect_queue()
|
||||
.once()
|
||||
.returning(|_: SetForegroundColor| Ok(()));
|
||||
|
||||
let mut printer = PrinterImpl::new(mock).unwrap();
|
||||
|
||||
printer.set_foreground(Color::Red).unwrap();
|
||||
printer.set_foreground(Color::Red).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_foreground_different_color_queues() {
|
||||
let mut mock = MockTerminal::new();
|
||||
mock.expect_position().returning(|| Ok((0, 0)));
|
||||
mock.expect_queue()
|
||||
.times(3)
|
||||
.returning(|_: SetForegroundColor| Ok(()));
|
||||
|
||||
let mut printer = PrinterImpl::new(mock).unwrap();
|
||||
|
||||
printer.set_foreground(Color::Red).unwrap();
|
||||
printer.set_foreground(Color::Blue).unwrap();
|
||||
printer.set_foreground(Color::Red).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn print() {
|
||||
let mut mock = MockTerminal::new();
|
||||
mock.expect_position().returning(|| Ok((0, 0)));
|
||||
mock.expect_queue()
|
||||
.with(eq(Print('R')))
|
||||
.once()
|
||||
.returning(|_| Ok(()));
|
||||
|
||||
PrinterImpl::new(mock).unwrap().print('R').unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn print_moves_cursor() {
|
||||
let mut mock = MockTerminal::new();
|
||||
mock.expect_position().returning(|| Ok((2, 4)));
|
||||
mock.expect_queue()
|
||||
.times(3)
|
||||
.returning(|_: Print<char>| Ok(()));
|
||||
|
||||
let mut printer = PrinterImpl::new(mock).unwrap();
|
||||
|
||||
printer.print('A').unwrap();
|
||||
printer.print('B').unwrap();
|
||||
printer.print('C').unwrap();
|
||||
printer.move_to(5, 4).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn print_special_char_fails() {
|
||||
let mut mock = MockTerminal::new();
|
||||
mock.expect_position().returning(|| Ok((2, 4)));
|
||||
|
||||
let mut printer = PrinterImpl::new(mock).unwrap();
|
||||
|
||||
assert!(printer.print('\u{0}').is_err());
|
||||
assert!(printer.print('\u{1F}').is_err());
|
||||
assert!(printer.print('\u{7F}').is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn move_to_different_position_queues() {
|
||||
let mut mock = MockTerminal::new();
|
||||
mock.expect_position().returning(|| Ok((7, 2)));
|
||||
mock.expect_queue()
|
||||
.with(eq(MoveTo(5, 4)))
|
||||
.once()
|
||||
.returning(|_| Ok(()));
|
||||
|
||||
PrinterImpl::new(mock).unwrap().move_to(5, 4).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn move_to_same_position_does_not_queue() {
|
||||
let mut mock = MockTerminal::new();
|
||||
mock.expect_position().returning(|| Ok((3, 13)));
|
||||
|
||||
PrinterImpl::new(mock).unwrap().move_to(3, 13).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn size() {
|
||||
let mut mock = MockTerminal::new();
|
||||
mock.expect_position().returning(|| Ok((0, 0)));
|
||||
mock.expect_size().returning(|| Ok((14, 76)));
|
||||
|
||||
assert_eq!((14, 76), PrinterImpl::new(mock).unwrap().size().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clear() {
|
||||
let mut mock = MockTerminal::new();
|
||||
mock.expect_position().returning(|| Ok((0, 0)));
|
||||
mock.expect_queue()
|
||||
.with(eq(Clear(ClearType::Purge)))
|
||||
.once()
|
||||
.returning(|_| Ok(()));
|
||||
|
||||
PrinterImpl::new(mock).unwrap().clear().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flush() {
|
||||
let mut mock = MockTerminal::new();
|
||||
mock.expect_position().returning(|| Ok((0, 0)));
|
||||
mock.expect_flush().once().returning(|| Ok(()));
|
||||
|
||||
PrinterImpl::new(mock).unwrap().flush().unwrap();
|
||||
}
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
use anyhow::Error;
|
||||
use crate::sampler::{Sample, Sampler};
|
||||
use crate::surface::Surface;
|
||||
use crate::Vector;
|
||||
|
||||
use mockall::automock;
|
||||
|
||||
#[automock]
|
||||
pub trait Renderer {
|
||||
fn render(&mut self, step: f32);
|
||||
fn present(&mut self) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
pub struct SamplerRenderer<TSurface, TSampler> {
|
||||
surface: TSurface,
|
||||
sampler: TSampler,
|
||||
}
|
||||
|
||||
impl<T1, T2> SamplerRenderer<T1, T2> {
|
||||
pub fn new(surface: T1, sampler: T2) -> Self {
|
||||
Self { surface, sampler }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T1: Surface, T2: Sampler> Renderer for SamplerRenderer<T1, T2> {
|
||||
fn render(&mut self, step: f32) {
|
||||
for x in 0..self.surface.width() {
|
||||
for y in 0..self.surface.height() {
|
||||
let pos = Vector::from_terminal(x, y);
|
||||
let sample = self.sampler.sample(step, pos);
|
||||
|
||||
match sample {
|
||||
Sample::Keep => (),
|
||||
Sample::Draw { char, color } => self.surface.draw(x, y, char, color),
|
||||
Sample::Clear => self.surface.clear(x, y),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn present(&mut self) -> Result<(), Error> {
|
||||
self.surface.present()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crossterm::style::*;
|
||||
use mockall::predicate::*;
|
||||
use super::*;
|
||||
use crate::surface::MockSurface;
|
||||
use crate::sampler::MockSampler;
|
||||
|
||||
#[test]
|
||||
fn render() {
|
||||
let mut surface = MockSurface::new();
|
||||
let mut sampler = MockSampler::new();
|
||||
|
||||
sampler.expect_sample().withf(|_, pos| pos.x == 0.0 && pos.y == 0.0).returning(|_,_| Sample::Clear);
|
||||
sampler.expect_sample().withf(|_, pos| pos.x == 1.0 && pos.y == 0.0).returning(|_,_| Sample::Keep);
|
||||
sampler.expect_sample().withf(|_, pos| pos.x == 0.0 && pos.y == 2.0).returning(|_,_| Sample::Draw { char: 'a', color: Color::Red });
|
||||
sampler.expect_sample().withf(|_, pos| pos.x == 1.0 && pos.y == 2.0).returning(|_,_| Sample::Keep);
|
||||
sampler.expect_sample().withf(|_, pos| pos.x == 0.0 && pos.y == 4.0).returning(|_,_| Sample::Draw { char: 'x', color: Color::Yellow });
|
||||
sampler.expect_sample().withf(|_, pos| pos.x == 1.0 && pos.y == 4.0).returning(|_,_| Sample::Clear);
|
||||
|
||||
surface.expect_width().return_const(2 as usize);
|
||||
surface.expect_height().return_const(3 as usize);
|
||||
surface.expect_clear().once().with(eq(0), eq(0)).return_const(());
|
||||
surface.expect_draw().once().with(eq(0), eq(1), eq('a'), eq(Color::Red)).return_const(());
|
||||
surface.expect_draw().once().with(eq(0), eq(2), eq('x'), eq(Color::Yellow)).return_const(());
|
||||
surface.expect_clear().once().with(eq(1), eq(2)).return_const(());
|
||||
|
||||
let mut renderer = SamplerRenderer::new(surface, sampler);
|
||||
|
||||
renderer.render(0.5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn present() {
|
||||
let mut surface = MockSurface::new();
|
||||
let sampler = MockSampler::new();
|
||||
|
||||
surface.expect_present().once().returning(|| Ok(()));
|
||||
|
||||
let mut renderer = SamplerRenderer::new(surface, sampler);
|
||||
|
||||
renderer.present().unwrap();
|
||||
}
|
||||
}
|
345
src/renderer.rs
Normal file
345
src/renderer.rs
Normal file
@ -0,0 +1,345 @@
|
||||
use crate::convert::{CharSample, Converter};
|
||||
use crate::pattern::*;
|
||||
use crate::Error;
|
||||
use crate::Printer;
|
||||
use crate::Vector;
|
||||
use crossterm::style::Color;
|
||||
|
||||
/// A renderer for an animation.
|
||||
#[cfg_attr(test, mockall::automock)]
|
||||
pub trait Renderer {
|
||||
/// Renders the current frame and flushes.
|
||||
fn render(&mut self, step: f32) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
/// The implementation of [Renderer].
|
||||
pub struct RendererImpl<T1, T2, T3: Printer> {
|
||||
sampler: T1,
|
||||
converter: T2,
|
||||
printer: T3,
|
||||
}
|
||||
|
||||
impl<T1, T2, T3: Printer> RendererImpl<T1, T2, T3> {
|
||||
pub fn new(sampler: T1, converter: T2, mut printer: T3) -> Result<Self, Error> {
|
||||
printer.hide_cursor()?;
|
||||
|
||||
Ok(Self {
|
||||
sampler,
|
||||
converter,
|
||||
printer,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T1: SamplerFactory, T2: Converter, T3: Printer> Renderer for RendererImpl<T1, T2, T3> {
|
||||
fn render(&mut self, step: f32) -> Result<(), Error> {
|
||||
let (width, height) = self.printer.size()?;
|
||||
let config = Config {
|
||||
step,
|
||||
size: Vector::from_terminal(width, height),
|
||||
};
|
||||
let sampler = self.sampler.create(&config);
|
||||
|
||||
for y in 0..height {
|
||||
for x in 0..width {
|
||||
let pos = Vector::from_terminal(x, y);
|
||||
|
||||
match self.converter.char(sampler.char(pos)) {
|
||||
CharSample::Draw(char) => {
|
||||
let color = self.converter.color(sampler.color(pos));
|
||||
|
||||
self.printer.move_to(x, y)?;
|
||||
self.printer.set_foreground(color)?;
|
||||
self.printer.print(char)?;
|
||||
}
|
||||
CharSample::Clear => {
|
||||
self.printer.move_to(x, y)?;
|
||||
self.printer.print(' ')?;
|
||||
}
|
||||
CharSample::Keep => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
self.printer.flush()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T1, T2, T3: Printer> Drop for RendererImpl<T1, T2, T3> {
|
||||
fn drop(&mut self) {
|
||||
// Errors while dropping the renderer can be safely ignored.
|
||||
self.printer.move_to(0, 0).ok();
|
||||
self.printer.set_foreground(Color::Reset).ok();
|
||||
self.printer.show_cursor().ok();
|
||||
self.printer.clear().ok();
|
||||
self.printer.flush().ok();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::convert::MockConverter;
|
||||
use crate::pattern::MockSampler;
|
||||
use crate::pattern::MockSamplerFactory;
|
||||
use crate::MockPrinter;
|
||||
use crossterm::style::Color;
|
||||
use mockall::predicate::eq;
|
||||
use mockall::Sequence;
|
||||
|
||||
#[test]
|
||||
fn new() {
|
||||
let factory = MockSamplerFactory::new();
|
||||
let converter = MockConverter::new();
|
||||
let mut printer = MockPrinter::new();
|
||||
|
||||
// Constructor
|
||||
printer.expect_hide_cursor().once().returning(|| Ok(()));
|
||||
|
||||
// Drop
|
||||
printer.expect_move_to().returning(|_, _| Ok(()));
|
||||
printer.expect_set_foreground().returning(|_| Ok(()));
|
||||
printer.expect_show_cursor().returning(|| Ok(()));
|
||||
printer.expect_clear().returning(|| Ok(()));
|
||||
printer.expect_flush().returning(|| Ok(()));
|
||||
|
||||
drop(RendererImpl::new(factory, converter, printer));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_config_correct() {
|
||||
let mut sampler = MockSamplerFactory::new();
|
||||
let mut converter = MockConverter::new();
|
||||
let mut printer = MockPrinter::new();
|
||||
|
||||
printer.expect_size().returning(|| Ok((2, 2)));
|
||||
sampler.expect_create().returning(|_| {
|
||||
let mut sampler = MockSampler::new();
|
||||
sampler
|
||||
.expect_char()
|
||||
.with(eq(Vector::new(0.0, 0.0)))
|
||||
.return_const(1.0);
|
||||
sampler
|
||||
.expect_char()
|
||||
.with(eq(Vector::new(1.0, 0.0)))
|
||||
.return_const(2.0);
|
||||
sampler
|
||||
.expect_char()
|
||||
.with(eq(Vector::new(0.0, 2.0)))
|
||||
.return_const(3.0);
|
||||
sampler
|
||||
.expect_char()
|
||||
.with(eq(Vector::new(1.0, 2.0)))
|
||||
.return_const(4.0);
|
||||
sampler
|
||||
.expect_color()
|
||||
.with(eq(Vector::new(0.0, 0.0)))
|
||||
.return_const(5.0);
|
||||
sampler
|
||||
.expect_color()
|
||||
.with(eq(Vector::new(1.0, 0.0)))
|
||||
.return_const(6.0);
|
||||
sampler
|
||||
.expect_color()
|
||||
.with(eq(Vector::new(0.0, 2.0)))
|
||||
.return_const(7.0);
|
||||
sampler
|
||||
.expect_color()
|
||||
.with(eq(Vector::new(1.0, 2.0)))
|
||||
.return_const(8.0);
|
||||
sampler
|
||||
});
|
||||
converter
|
||||
.expect_char()
|
||||
.with(eq(1.0))
|
||||
.return_const(CharSample::Keep);
|
||||
converter
|
||||
.expect_char()
|
||||
.with(eq(2.0))
|
||||
.return_const(CharSample::Clear);
|
||||
converter
|
||||
.expect_char()
|
||||
.with(eq(3.0))
|
||||
.return_const(CharSample::Draw('A'));
|
||||
converter
|
||||
.expect_char()
|
||||
.with(eq(4.0))
|
||||
.return_const(CharSample::Draw('X'));
|
||||
converter
|
||||
.expect_color()
|
||||
.with(eq(7.0))
|
||||
.return_const(Color::Red);
|
||||
converter
|
||||
.expect_color()
|
||||
.with(eq(8.0))
|
||||
.return_const(Color::Blue);
|
||||
|
||||
let seq = &mut Sequence::new();
|
||||
|
||||
// Constructor
|
||||
printer
|
||||
.expect_hide_cursor()
|
||||
.once()
|
||||
.returning(|| Ok(()))
|
||||
.in_sequence(seq);
|
||||
|
||||
// Rendering
|
||||
printer
|
||||
.expect_move_to()
|
||||
.once()
|
||||
.with(eq(1), eq(0))
|
||||
.returning(|_, _| Ok(()))
|
||||
.in_sequence(seq);
|
||||
printer
|
||||
.expect_print()
|
||||
.once()
|
||||
.with(eq(' '))
|
||||
.returning(|_| Ok(()))
|
||||
.in_sequence(seq);
|
||||
printer
|
||||
.expect_move_to()
|
||||
.once()
|
||||
.with(eq(0), eq(1))
|
||||
.returning(|_, _| Ok(()))
|
||||
.in_sequence(seq);
|
||||
printer
|
||||
.expect_set_foreground()
|
||||
.once()
|
||||
.with(eq(Color::Red))
|
||||
.returning(|_| Ok(()))
|
||||
.in_sequence(seq);
|
||||
printer
|
||||
.expect_print()
|
||||
.once()
|
||||
.with(eq('A'))
|
||||
.returning(|_| Ok(()))
|
||||
.in_sequence(seq);
|
||||
printer
|
||||
.expect_move_to()
|
||||
.once()
|
||||
.with(eq(1), eq(1))
|
||||
.returning(|_, _| Ok(()))
|
||||
.in_sequence(seq);
|
||||
printer
|
||||
.expect_set_foreground()
|
||||
.once()
|
||||
.with(eq(Color::Blue))
|
||||
.returning(|_| Ok(()))
|
||||
.in_sequence(seq);
|
||||
printer
|
||||
.expect_print()
|
||||
.once()
|
||||
.with(eq('X'))
|
||||
.returning(|_| Ok(()))
|
||||
.in_sequence(seq);
|
||||
printer
|
||||
.expect_flush()
|
||||
.once()
|
||||
.returning(|| Ok(()))
|
||||
.in_sequence(seq);
|
||||
|
||||
// Drop
|
||||
printer
|
||||
.expect_move_to()
|
||||
.once()
|
||||
.returning(|_, _| Ok(()))
|
||||
.in_sequence(seq);
|
||||
printer
|
||||
.expect_set_foreground()
|
||||
.with(eq(Color::Reset))
|
||||
.once()
|
||||
.returning(|_| Ok(()))
|
||||
.in_sequence(seq);
|
||||
printer
|
||||
.expect_show_cursor()
|
||||
.once()
|
||||
.returning(|| Ok(()))
|
||||
.in_sequence(seq);
|
||||
printer
|
||||
.expect_clear()
|
||||
.once()
|
||||
.returning(|| Ok(()))
|
||||
.in_sequence(seq);
|
||||
printer
|
||||
.expect_flush()
|
||||
.once()
|
||||
.returning(|| Ok(()))
|
||||
.in_sequence(seq);
|
||||
|
||||
let mut renderer = RendererImpl::new(sampler, converter, printer).unwrap();
|
||||
|
||||
renderer.render(0.0).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render() {
|
||||
let mut sampler = MockSamplerFactory::new();
|
||||
let mut converter = MockConverter::new();
|
||||
let mut printer = MockPrinter::new();
|
||||
|
||||
sampler.expect_create().returning(|_| {
|
||||
let mut sampler = MockSampler::new();
|
||||
sampler.expect_char().return_const(0.0);
|
||||
sampler
|
||||
});
|
||||
converter.expect_char().return_const(CharSample::Keep);
|
||||
printer.expect_size().returning(|| Ok((3, 2)));
|
||||
printer.expect_flush().returning(|| Ok(()));
|
||||
|
||||
// Constructor
|
||||
printer.expect_hide_cursor().returning(|| Ok(()));
|
||||
|
||||
// Drop
|
||||
printer.expect_move_to().returning(|_, _| Ok(()));
|
||||
printer.expect_set_foreground().returning(|_| Ok(()));
|
||||
printer.expect_show_cursor().returning(|| Ok(()));
|
||||
printer.expect_clear().returning(|| Ok(()));
|
||||
printer.expect_flush().returning(|| Ok(()));
|
||||
|
||||
let mut renderer = RendererImpl::new(sampler, converter, printer).unwrap();
|
||||
|
||||
renderer.render(0.8).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn end() {
|
||||
let factory = MockSamplerFactory::new();
|
||||
let converter = MockConverter::new();
|
||||
let mut printer = MockPrinter::new();
|
||||
|
||||
let seq = &mut Sequence::new();
|
||||
|
||||
// Constructor
|
||||
printer.expect_hide_cursor().returning(|| Ok(()));
|
||||
|
||||
// Drop
|
||||
printer
|
||||
.expect_move_to()
|
||||
.with(eq(0), eq(0))
|
||||
.once()
|
||||
.returning(|_, _| Ok(()))
|
||||
.in_sequence(seq);
|
||||
printer
|
||||
.expect_set_foreground()
|
||||
.with(eq(Color::Reset))
|
||||
.once()
|
||||
.returning(|_| Ok(()))
|
||||
.in_sequence(seq);
|
||||
printer
|
||||
.expect_show_cursor()
|
||||
.once()
|
||||
.returning(|| Ok(()))
|
||||
.in_sequence(seq);
|
||||
printer
|
||||
.expect_clear()
|
||||
.once()
|
||||
.returning(|| Ok(()))
|
||||
.in_sequence(seq);
|
||||
printer
|
||||
.expect_flush()
|
||||
.once()
|
||||
.returning(|| Ok(()))
|
||||
.in_sequence(seq);
|
||||
|
||||
drop(RendererImpl::new(factory, converter, printer));
|
||||
}
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
use std::time::Duration;
|
||||
use anyhow::Error;
|
||||
use crate::Renderer;
|
||||
use crate::timer::Timer;
|
||||
|
||||
pub struct Runner<TTimer, TRenderer> {
|
||||
timer: TTimer,
|
||||
ticks: u128,
|
||||
renderer: TRenderer,
|
||||
}
|
||||
|
||||
impl<T1: Timer, T2: Renderer> Runner<T1, T2> {
|
||||
pub fn new(duration: Duration,
|
||||
timer: T1,
|
||||
renderer: T2) -> Self {
|
||||
let ticks = duration.as_nanos() / timer.delay().as_nanos();
|
||||
|
||||
Self { timer, ticks, renderer }
|
||||
}
|
||||
|
||||
pub fn run(mut self) -> Result<(), Error> {
|
||||
for i in 0..=self.ticks {
|
||||
let step = i as f32 / self.ticks as f32;
|
||||
|
||||
self.renderer.render(step);
|
||||
self.renderer.present()?;
|
||||
self.timer.sleep();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::time::Duration;
|
||||
use mockall::predicate::*;
|
||||
use mockall::Sequence;
|
||||
use crate::timer::MockTimer;
|
||||
use crate::render::MockRenderer;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn run() {
|
||||
let mut timer = MockTimer::new();
|
||||
let mut renderer = MockRenderer::new();
|
||||
let seq = &mut Sequence::new();
|
||||
|
||||
timer.expect_delay().return_const(Duration::from_secs(2));
|
||||
|
||||
renderer.expect_render().once().with(eq(0.0)).in_sequence(seq).return_const(());
|
||||
renderer.expect_present().once().in_sequence(seq).returning(|| Ok(()));
|
||||
timer.expect_sleep().once().in_sequence(seq).return_const(());
|
||||
|
||||
renderer.expect_render().once().with(eq(0.5)).in_sequence(seq).return_const(());
|
||||
renderer.expect_present().once().in_sequence(seq).returning(|| Ok(()));
|
||||
timer.expect_sleep().once().in_sequence(seq).return_const(());
|
||||
|
||||
renderer.expect_render().once().with(eq(1.0)).in_sequence(seq).return_const(());
|
||||
renderer.expect_present().once().in_sequence(seq).returning(|| Ok(()));
|
||||
timer.expect_sleep().once().in_sequence(seq).return_const(());
|
||||
|
||||
let runner = Runner::new(Duration::from_secs(4), timer, renderer);
|
||||
|
||||
runner.run().unwrap();
|
||||
}
|
||||
}
|
139
src/sampler.rs
139
src/sampler.rs
@ -1,139 +0,0 @@
|
||||
use crossterm::style::Color;
|
||||
use mockall::automock;
|
||||
use crate::animation::Animation;
|
||||
use crate::char::CharSampler;
|
||||
use crate::color::ColorSampler;
|
||||
use crate::fill::FillMode;
|
||||
use crate::vec::Vector;
|
||||
|
||||
pub enum Sample {
|
||||
Keep,
|
||||
Draw { char: char, color: Color },
|
||||
Clear,
|
||||
}
|
||||
|
||||
#[automock]
|
||||
pub trait Sampler {
|
||||
fn sample(&self, step: f32, pos: Vector) -> Sample;
|
||||
}
|
||||
|
||||
pub struct ComposedSampler {
|
||||
animation: Box<dyn Animation>,
|
||||
fill: Box<dyn FillMode>,
|
||||
color: Box<dyn ColorSampler>,
|
||||
char: Box<dyn CharSampler>,
|
||||
}
|
||||
|
||||
impl ComposedSampler {
|
||||
pub fn new(animation: Box<dyn Animation>,
|
||||
fill: Box<dyn FillMode>,
|
||||
color: Box<dyn ColorSampler>,
|
||||
char: Box<dyn CharSampler>) -> Self {
|
||||
Self { animation, fill, color, char }
|
||||
}
|
||||
}
|
||||
|
||||
impl Sampler for ComposedSampler {
|
||||
fn sample(&self, step: f32, pos: Vector) -> Sample {
|
||||
let level = self.animation.sample(step, pos);
|
||||
|
||||
if level >= 1.0 {
|
||||
Sample::Keep
|
||||
} else if level >= 0.0 {
|
||||
let char = self.char.sample(level);
|
||||
let fill = self.fill.sample(level, pos);
|
||||
let color = self.color.sample(fill);
|
||||
|
||||
Sample::Draw { char, color }
|
||||
} else {
|
||||
Sample::Clear
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use mockall::predicate::{always, eq};
|
||||
use super::*;
|
||||
use crate::animation::MockAnimation;
|
||||
use crate::fill::MockFillMode;
|
||||
use crate::color::MockColorSampler;
|
||||
use crate::char::MockCharSampler;
|
||||
|
||||
#[test]
|
||||
fn sample_keep() {
|
||||
let mut anim = Box::new(MockAnimation::new());
|
||||
let fill = Box::new(MockFillMode::new());
|
||||
let color = Box::new(MockColorSampler::new());
|
||||
let char = Box::new(MockCharSampler::new());
|
||||
|
||||
anim.expect_sample().return_const(3.0);
|
||||
|
||||
let sampler = ComposedSampler::new(anim, fill, color, char);
|
||||
|
||||
assert!(matches!(sampler.sample(0.7, Vector::new(0.3, 0.1)), Sample::Keep));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sample_draw() {
|
||||
let mut anim = Box::new(MockAnimation::new());
|
||||
let mut fill = Box::new(MockFillMode::new());
|
||||
let mut color = Box::new(MockColorSampler::new());
|
||||
let mut char = Box::new(MockCharSampler::new());
|
||||
|
||||
anim.expect_sample().once().with(eq(0.2), always()).return_const(0.3);
|
||||
fill.expect_sample().once().with(eq(0.3), always()).return_const(0.8);
|
||||
color.expect_sample().once().with(eq(0.8)).return_const(Color::Blue);
|
||||
char.expect_sample().once().with(eq(0.3)).return_const('Z');
|
||||
|
||||
let sampler = ComposedSampler::new(anim, fill, color, char);
|
||||
|
||||
assert!(matches!(sampler.sample(0.2, Vector::new(0.3, 0.1)), Sample::Draw { char: 'Z', color: Color::Blue }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sample_clear() {
|
||||
let mut anim = Box::new(MockAnimation::new());
|
||||
let fill = Box::new(MockFillMode::new());
|
||||
let color = Box::new(MockColorSampler::new());
|
||||
let char = Box::new(MockCharSampler::new());
|
||||
|
||||
anim.expect_sample().return_const(-0.4);
|
||||
|
||||
let sampler = ComposedSampler::new(anim, fill, color, char);
|
||||
|
||||
assert!(matches!(sampler.sample(0.7, Vector::new(0.3, 0.1)), Sample::Clear));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sample_almost_draw() {
|
||||
let mut anim = Box::new(MockAnimation::new());
|
||||
let fill = Box::new(MockFillMode::new());
|
||||
let color = Box::new(MockColorSampler::new());
|
||||
let char = Box::new(MockCharSampler::new());
|
||||
|
||||
anim.expect_sample().return_const(1.0);
|
||||
|
||||
let sampler = ComposedSampler::new(anim, fill, color, char);
|
||||
|
||||
assert!(matches!(sampler.sample(0.7, Vector::new(0.3, 0.1)), Sample::Keep));
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn sample_almost_clear() {
|
||||
let mut anim = Box::new(MockAnimation::new());
|
||||
let mut fill = Box::new(MockFillMode::new());
|
||||
let mut color = Box::new(MockColorSampler::new());
|
||||
let mut char = Box::new(MockCharSampler::new());
|
||||
|
||||
anim.expect_sample().return_const(0.0);
|
||||
fill.expect_sample().return_const(0.8);
|
||||
color.expect_sample().return_const(Color::Blue);
|
||||
char.expect_sample().return_const('a');
|
||||
|
||||
let sampler = ComposedSampler::new(anim, fill, color, char);
|
||||
|
||||
assert!(matches!(sampler.sample(0.7, Vector::new(0.3, 0.1)), Sample::Draw { .. }));
|
||||
}
|
||||
}
|
193
src/surface.rs
193
src/surface.rs
@ -1,193 +0,0 @@
|
||||
use anyhow::Error;
|
||||
use crossterm::cursor::MoveTo;
|
||||
use crossterm::{ExecutableCommand, QueueableCommand};
|
||||
use crossterm::style::{Color, Print, SetForegroundColor};
|
||||
use crossterm::terminal::{Clear, ClearType};
|
||||
use mockall::automock;
|
||||
use std::io::Write;
|
||||
use crate::array::Array2D;
|
||||
|
||||
#[automock]
|
||||
pub trait Surface {
|
||||
fn width(&self) -> usize;
|
||||
fn height(&self) -> usize;
|
||||
fn draw(&mut self, x: usize, y: usize, char: char, color: Color);
|
||||
fn clear(&mut self, x: usize, y: usize);
|
||||
fn present(&mut self) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
pub struct WriteSurface<T: Write> {
|
||||
out: T,
|
||||
array: Array2D<Cell>,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
enum Cell {
|
||||
Keep,
|
||||
Draw { char: char, color: Color },
|
||||
}
|
||||
|
||||
impl Default for Cell {
|
||||
fn default() -> Self { Cell::Keep }
|
||||
}
|
||||
|
||||
impl<T: Write> WriteSurface<T> {
|
||||
pub fn new(out: T, width: usize, height: usize) -> Self {
|
||||
Self {
|
||||
out,
|
||||
array: Array2D::new(width, height)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Write> Surface for WriteSurface<T> {
|
||||
fn width(&self) -> usize {
|
||||
self.array.width()
|
||||
}
|
||||
|
||||
fn height(&self) -> usize {
|
||||
self.array.height()
|
||||
}
|
||||
|
||||
fn draw(&mut self, x: usize, y: usize, char: char, color: Color) {
|
||||
self.array[(x, y)] = Cell::Draw { char, color };
|
||||
}
|
||||
|
||||
fn clear(&mut self, x: usize, y: usize) {
|
||||
self.array[(x, y)] = Cell::Draw { char: ' ', color: Color::Reset };
|
||||
}
|
||||
|
||||
fn present(&mut self) -> Result<(), Error> {
|
||||
let mut needs_move;
|
||||
let mut last_color = None;
|
||||
|
||||
for y in 0..self.array.height() {
|
||||
needs_move = true;
|
||||
|
||||
for x in 0..self.array.width() {
|
||||
match self.array[(x, y)] {
|
||||
Cell::Keep => {
|
||||
needs_move = true;
|
||||
}
|
||||
Cell::Draw { char, color } => {
|
||||
if needs_move {
|
||||
needs_move = false;
|
||||
self.out.queue(MoveTo(x as u16, y as u16))?;
|
||||
}
|
||||
if last_color.is_none() || last_color.unwrap() != color {
|
||||
last_color = Some(color);
|
||||
self.out.queue(SetForegroundColor(color))?;
|
||||
}
|
||||
self.out.queue(Print(char))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.out.queue(MoveTo(0, 0))?;
|
||||
self.out.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Write> Drop for WriteSurface<T> {
|
||||
fn drop(&mut self) {
|
||||
if let Err(e) = self.out.execute(Clear(ClearType::Purge)) {
|
||||
println!("{}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use super::*;
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
struct Data {
|
||||
flushed: Vec<Vec<u8>>,
|
||||
buffer: Vec<u8>
|
||||
}
|
||||
|
||||
struct MockWrite {
|
||||
data: Rc<RefCell<Data>>
|
||||
}
|
||||
|
||||
impl Data {
|
||||
fn new() -> Rc<RefCell<Self>> {
|
||||
Rc::new(RefCell::new(Self {
|
||||
flushed: Vec::new(),
|
||||
buffer: Vec::new()
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl MockWrite {
|
||||
fn new(data: Rc<RefCell<Data>>) -> Box<dyn Write> {
|
||||
Box::new(Self { data })
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for MockWrite {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
self.data.borrow_mut().buffer.extend_from_slice(buf);
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
let data = self.data.borrow_mut().buffer.drain(..).collect();
|
||||
|
||||
self.data.borrow_mut().flushed.push(data);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn width() {
|
||||
let data = Data::new();
|
||||
let mock = MockWrite::new(data);
|
||||
let renderer = WriteSurface::new(mock, 10, 2);
|
||||
|
||||
assert_eq!(10, renderer.width());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn height() {
|
||||
let data = Data::new();
|
||||
let mock = MockWrite::new(data);
|
||||
let renderer = WriteSurface::new(mock, 5, 8);
|
||||
|
||||
assert_eq!(8, renderer.height());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn present() {
|
||||
// Execute
|
||||
let data = Data::new();
|
||||
let mock = MockWrite::new(data.clone());
|
||||
let mut renderer = WriteSurface::new(mock, 3, 2);
|
||||
|
||||
renderer.draw(0, 0, 'A', Color::Green);
|
||||
renderer.draw(1, 0, 'x', Color::Green);
|
||||
renderer.clear(1, 1);
|
||||
renderer.present().unwrap();
|
||||
|
||||
// Recreate expectation
|
||||
let expected = Data::new();
|
||||
let mut stream = MockWrite::new(expected.clone());
|
||||
|
||||
stream.queue(MoveTo(0, 0)).unwrap();
|
||||
stream.queue(SetForegroundColor(Color::Green)).unwrap();
|
||||
stream.queue(Print('A')).unwrap();
|
||||
stream.queue(Print('x')).unwrap();
|
||||
stream.queue(MoveTo(1, 1)).unwrap();
|
||||
stream.queue(SetForegroundColor(Color::Reset)).unwrap();
|
||||
stream.queue(Print(' ')).unwrap();
|
||||
stream.queue(MoveTo(0, 0)).unwrap();
|
||||
stream.flush().unwrap();
|
||||
|
||||
// Compare
|
||||
assert_eq!(expected, data);
|
||||
}
|
||||
}
|
47
src/term.rs
Normal file
47
src/term.rs
Normal file
@ -0,0 +1,47 @@
|
||||
use crate::Error;
|
||||
use crossterm::{Command, QueueableCommand};
|
||||
use std::io::Write;
|
||||
|
||||
/// A stub for OS calls and crossterm functions.
|
||||
#[cfg_attr(test, mockall::automock)]
|
||||
pub trait Terminal {
|
||||
/// Queues a command for execution.
|
||||
fn queue<T: 'static + Command>(&mut self, cmd: T) -> Result<(), Error>;
|
||||
/// Flushes all queued commands.
|
||||
fn flush(&mut self) -> Result<(), Error>;
|
||||
/// Returns the current size of the terminal.
|
||||
fn size(&self) -> Result<(u16, u16), Error>;
|
||||
/// Returns the current cursor position.
|
||||
fn position(&self) -> Result<(u16, u16), Error>;
|
||||
}
|
||||
|
||||
/// The implementation of [Terminal].
|
||||
pub struct TerminalImpl<T> {
|
||||
out: T,
|
||||
}
|
||||
|
||||
impl<T> TerminalImpl<T> {
|
||||
pub fn new(out: T) -> Self {
|
||||
Self { out }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Write> Terminal for TerminalImpl<T> {
|
||||
fn queue<TCmd: Command>(&mut self, cmd: TCmd) -> Result<(), Error> {
|
||||
self.out.queue(cmd)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> Result<(), Error> {
|
||||
self.out.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn size(&self) -> Result<(u16, u16), Error> {
|
||||
Ok(crossterm::terminal::size()?)
|
||||
}
|
||||
|
||||
fn position(&self) -> Result<(u16, u16), Error> {
|
||||
Ok(crossterm::cursor::position()?)
|
||||
}
|
||||
}
|
39
src/timer.rs
39
src/timer.rs
@ -1,39 +0,0 @@
|
||||
use std::thread::sleep;
|
||||
use std::time::{Duration, Instant};
|
||||
use mockall::automock;
|
||||
|
||||
#[automock]
|
||||
pub trait Timer {
|
||||
fn sleep(&mut self);
|
||||
|
||||
fn delay(&self) -> Duration;
|
||||
}
|
||||
|
||||
pub struct SimpleTimer {
|
||||
delay: Duration,
|
||||
last: Instant
|
||||
}
|
||||
|
||||
impl SimpleTimer {
|
||||
pub fn new(delay: Duration) -> Self {
|
||||
Self {
|
||||
last: Instant::now(),
|
||||
delay,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Timer for SimpleTimer {
|
||||
fn sleep(&mut self) {
|
||||
let now = Instant::now();
|
||||
|
||||
if self.last + self.delay > now {
|
||||
sleep(self.delay - (now - self.last));
|
||||
}
|
||||
self.last = Instant::now();
|
||||
}
|
||||
|
||||
fn delay(&self) -> Duration {
|
||||
self.delay
|
||||
}
|
||||
}
|
90
src/transform/invert.rs
Normal file
90
src/transform/invert.rs
Normal file
@ -0,0 +1,90 @@
|
||||
use crate::pattern::*;
|
||||
use crate::Vector;
|
||||
|
||||
/// A factory for [Invert].
|
||||
///
|
||||
/// Inverts the time of the [Config] for the child [Pattern].
|
||||
#[derive(derive_more::Constructor)]
|
||||
pub struct InvertFactory {
|
||||
child: Box<dyn PatternFactory>,
|
||||
}
|
||||
|
||||
/// Inverts the level of the [Pattern].
|
||||
#[derive(derive_more::Constructor)]
|
||||
pub struct Invert {
|
||||
child: Box<dyn Pattern>,
|
||||
}
|
||||
|
||||
impl PatternFactory for InvertFactory {
|
||||
fn create(&self, config: &Config) -> Box<dyn Pattern> {
|
||||
let mut copy = config.clone();
|
||||
copy.step = 1.0 - config.step;
|
||||
|
||||
Box::new(Invert::new(self.child.create(©)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Pattern for Invert {
|
||||
fn sample(&self, pos: Vector) -> f32 {
|
||||
1.0 - self.child.sample(pos)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::MockPatternFactory;
|
||||
use approx::*;
|
||||
use mockall::predicate::eq;
|
||||
|
||||
#[test]
|
||||
fn create_config_correct() {
|
||||
let input = Config {
|
||||
size: Vector::new(4.0, 2.0),
|
||||
step: 0.4,
|
||||
};
|
||||
let mut output = input.clone();
|
||||
output.step = 0.6;
|
||||
|
||||
let mut child = MockPatternFactory::new();
|
||||
child
|
||||
.expect_create()
|
||||
.with(eq(output))
|
||||
.once()
|
||||
.returning(|_| Box::new(MockPattern::new()));
|
||||
|
||||
InvertFactory::new(Box::new(child)).create(&input);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sample_inverted() {
|
||||
let mut child = MockPatternFactory::new();
|
||||
child.expect_create().returning(|_| {
|
||||
let mut sampler = MockPattern::new();
|
||||
sampler.expect_sample().return_const(0.7);
|
||||
Box::new(sampler)
|
||||
});
|
||||
|
||||
let sampler = InvertFactory::new(Box::new(child)).create(&Config::default());
|
||||
|
||||
assert_abs_diff_eq!(0.3, sampler.sample(Vector::default()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sample_pos_correct() {
|
||||
let mut child = MockPatternFactory::new();
|
||||
child.expect_create().once().returning(|_| {
|
||||
let mut sampler = MockPattern::new();
|
||||
sampler
|
||||
.expect_sample()
|
||||
.with(eq(Vector::new(4.0, 2.0)))
|
||||
.once()
|
||||
.return_const(0.0);
|
||||
Box::new(sampler)
|
||||
});
|
||||
|
||||
let sampler = InvertFactory::new(Box::new(child)).create(&Config::default());
|
||||
|
||||
sampler.sample(Vector::new(4.0, 2.0));
|
||||
}
|
||||
}
|
13
src/transform/mod.rs
Normal file
13
src/transform/mod.rs
Normal file
@ -0,0 +1,13 @@
|
||||
//! Contains transformations to apply on top of patterns.
|
||||
|
||||
mod invert;
|
||||
mod segment;
|
||||
mod shift;
|
||||
mod shrink;
|
||||
mod swap;
|
||||
|
||||
pub use invert::*;
|
||||
pub use segment::*;
|
||||
pub use shift::*;
|
||||
pub use shrink::*;
|
||||
pub use swap::*;
|
160
src/transform/segment.rs
Normal file
160
src/transform/segment.rs
Normal file
@ -0,0 +1,160 @@
|
||||
use crate::pattern::*;
|
||||
use crate::Vector;
|
||||
|
||||
/// A factory for [Segments].
|
||||
#[derive(derive_more::Constructor)]
|
||||
pub struct SegmentsFactory {
|
||||
child: Box<dyn PatternFactory>,
|
||||
segments: f32,
|
||||
}
|
||||
|
||||
/// Converts a pattern to `n` segments each starting with zero and ending with one.
|
||||
#[derive(derive_more::Constructor)]
|
||||
pub struct Segments {
|
||||
child: Box<dyn Pattern>,
|
||||
segments: f32,
|
||||
}
|
||||
|
||||
impl PatternFactory for SegmentsFactory {
|
||||
fn create(&self, config: &Config) -> Box<dyn Pattern> {
|
||||
Box::new(Segments::new(self.child.create(config), self.segments))
|
||||
}
|
||||
}
|
||||
|
||||
impl Pattern for Segments {
|
||||
fn sample(&self, pos: Vector) -> f32 {
|
||||
let sample = self.child.sample(pos);
|
||||
|
||||
if 0.0 <= sample && sample < 1.0 {
|
||||
sample * self.segments % 1.0
|
||||
} else {
|
||||
sample
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::MockPatternFactory;
|
||||
use approx::*;
|
||||
use mockall::predicate::eq;
|
||||
|
||||
#[test]
|
||||
fn create_config_correct() {
|
||||
let config = Config {
|
||||
size: Vector::new(6.0, 3.0),
|
||||
step: 0.4,
|
||||
};
|
||||
let mut child = MockPatternFactory::new();
|
||||
child
|
||||
.expect_create()
|
||||
.with(eq(config))
|
||||
.once()
|
||||
.returning(|_| Box::new(MockPattern::new()));
|
||||
|
||||
SegmentsFactory::new(Box::new(child), 2.0).create(&config);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sample_above_one_untouched() {
|
||||
let mut child = MockPatternFactory::new();
|
||||
child.expect_create().returning(|_| {
|
||||
let mut sampler = MockPattern::new();
|
||||
sampler.expect_sample().return_const(1.1);
|
||||
Box::new(sampler)
|
||||
});
|
||||
|
||||
let sampler = SegmentsFactory::new(Box::new(child), 3.0).create(&Config::default());
|
||||
|
||||
assert_abs_diff_eq!(1.1, sampler.sample(Vector::default()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sample_below_zero_untouched() {
|
||||
let mut child = MockPatternFactory::new();
|
||||
child.expect_create().returning(|_| {
|
||||
let mut sampler = MockPattern::new();
|
||||
sampler.expect_sample().return_const(-0.1);
|
||||
Box::new(sampler)
|
||||
});
|
||||
|
||||
let sampler = SegmentsFactory::new(Box::new(child), 3.0).create(&Config::default());
|
||||
|
||||
assert_abs_diff_eq!(-0.1, sampler.sample(Vector::default()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sample_second_segment_begins_with_one() {
|
||||
let mut child = MockPatternFactory::new();
|
||||
child.expect_create().returning(|_| {
|
||||
let mut sampler = MockPattern::new();
|
||||
sampler.expect_sample().return_const(0.74);
|
||||
Box::new(sampler)
|
||||
});
|
||||
|
||||
let sampler = SegmentsFactory::new(Box::new(child), 4.0).create(&Config::default());
|
||||
|
||||
assert_abs_diff_eq!(0.96, sampler.sample(Vector::default()), epsilon = 0.01);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sample_second_segment_ends_with_zero() {
|
||||
let mut child = MockPatternFactory::new();
|
||||
child.expect_create().returning(|_| {
|
||||
let mut sampler = MockPattern::new();
|
||||
sampler.expect_sample().return_const(0.5);
|
||||
Box::new(sampler)
|
||||
});
|
||||
|
||||
let sampler = SegmentsFactory::new(Box::new(child), 4.0).create(&Config::default());
|
||||
|
||||
assert_abs_diff_eq!(0.0, sampler.sample(Vector::default()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sample_last_segment_begins_with_one() {
|
||||
let mut child = MockPatternFactory::new();
|
||||
child.expect_create().returning(|_| {
|
||||
let mut sampler = MockPattern::new();
|
||||
sampler.expect_sample().return_const(0.24);
|
||||
Box::new(sampler)
|
||||
});
|
||||
|
||||
let sampler = SegmentsFactory::new(Box::new(child), 4.0).create(&Config::default());
|
||||
|
||||
assert_abs_diff_eq!(0.96, sampler.sample(Vector::default()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sample_last_segment_ends_with_zero() {
|
||||
let mut child = MockPatternFactory::new();
|
||||
child.expect_create().returning(|_| {
|
||||
let mut sampler = MockPattern::new();
|
||||
sampler.expect_sample().return_const(0.0);
|
||||
Box::new(sampler)
|
||||
});
|
||||
|
||||
let sampler = SegmentsFactory::new(Box::new(child), 4.0).create(&Config::default());
|
||||
|
||||
assert_abs_diff_eq!(0.0, sampler.sample(Vector::default()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sample_pos_correct() {
|
||||
let mut child = MockPatternFactory::new();
|
||||
child.expect_create().once().returning(|_| {
|
||||
let mut sampler = MockPattern::new();
|
||||
sampler
|
||||
.expect_sample()
|
||||
.with(eq(Vector::new(5.0, 1.0)))
|
||||
.once()
|
||||
.return_const(0.0);
|
||||
Box::new(sampler)
|
||||
});
|
||||
|
||||
let sampler = SegmentsFactory::new(Box::new(child), 3.0).create(&Config::default());
|
||||
|
||||
sampler.sample(Vector::new(5.0, 1.0));
|
||||
}
|
||||
}
|
87
src/transform/shift.rs
Normal file
87
src/transform/shift.rs
Normal file
@ -0,0 +1,87 @@
|
||||
use crate::pattern::*;
|
||||
use crate::Vector;
|
||||
|
||||
/// A factory for [Shift].
|
||||
#[derive(derive_more::Constructor)]
|
||||
pub struct ShiftFactory {
|
||||
child: Box<dyn PatternFactory>,
|
||||
}
|
||||
|
||||
/// Offsets the [Pattern] out of screen, then moves it inside and finally outside the visible area.
|
||||
#[derive(derive_more::Constructor)]
|
||||
pub struct Shift {
|
||||
child: Box<dyn Pattern>,
|
||||
shift: f32,
|
||||
}
|
||||
|
||||
impl PatternFactory for ShiftFactory {
|
||||
fn create(&self, config: &Config) -> Box<dyn Pattern> {
|
||||
Box::new(Shift::new(self.child.create(config), config.step))
|
||||
}
|
||||
}
|
||||
|
||||
impl Pattern for Shift {
|
||||
fn sample(&self, pos: Vector) -> f32 {
|
||||
self.child.sample(pos) + 1.0 - 2.0 * self.shift
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::MockPatternFactory;
|
||||
use approx::*;
|
||||
use mockall::predicate::eq;
|
||||
|
||||
#[test]
|
||||
fn create_config_correct() {
|
||||
let config = Config {
|
||||
size: Vector::new(4.0, 2.0),
|
||||
step: 0.4,
|
||||
};
|
||||
let mut child = MockPatternFactory::new();
|
||||
child
|
||||
.expect_create()
|
||||
.with(eq(config))
|
||||
.once()
|
||||
.returning(|_| Box::new(MockPattern::new()));
|
||||
|
||||
ShiftFactory::new(Box::new(child)).create(&config);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sample_shifted() {
|
||||
let config = Config {
|
||||
step: 0.4,
|
||||
..Config::default()
|
||||
};
|
||||
let mut child = MockPatternFactory::new();
|
||||
child.expect_create().returning(|_| {
|
||||
let mut sampler = MockPattern::new();
|
||||
sampler.expect_sample().return_const(0.6);
|
||||
Box::new(sampler)
|
||||
});
|
||||
|
||||
let sampler = ShiftFactory::new(Box::new(child)).create(&config);
|
||||
|
||||
assert_abs_diff_eq!(0.8, sampler.sample(Vector::default()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sample_pos_correct() {
|
||||
let mut child = MockPatternFactory::new();
|
||||
child.expect_create().once().returning(|_| {
|
||||
let mut sampler = MockPattern::new();
|
||||
sampler
|
||||
.expect_sample()
|
||||
.with(eq(Vector::new(6.0, 7.0)))
|
||||
.once()
|
||||
.return_const(0.0);
|
||||
Box::new(sampler)
|
||||
});
|
||||
|
||||
let sampler = ShiftFactory::new(Box::new(child)).create(&Config::default());
|
||||
|
||||
sampler.sample(Vector::new(6.0, 7.0));
|
||||
}
|
||||
}
|
126
src/transform/shrink.rs
Normal file
126
src/transform/shrink.rs
Normal file
@ -0,0 +1,126 @@
|
||||
use crate::pattern::*;
|
||||
use crate::Vector;
|
||||
|
||||
/// A factory for [Shrink].
|
||||
pub struct ShrinkFactory {
|
||||
child: Box<dyn PatternFactory>,
|
||||
width: f32,
|
||||
rest: f32,
|
||||
}
|
||||
|
||||
/// Reduces the width of the child [Pattern] to one over `n`.
|
||||
#[derive(derive_more::Constructor)]
|
||||
pub struct Shrink {
|
||||
child: Box<dyn Pattern>,
|
||||
width: f32,
|
||||
rest: f32,
|
||||
}
|
||||
|
||||
impl ShrinkFactory {
|
||||
pub fn new(child: Box<dyn PatternFactory>, factor: f32) -> Self {
|
||||
let width = 1.0 / factor;
|
||||
let rest = 1.0 - width;
|
||||
|
||||
Self { child, width, rest }
|
||||
}
|
||||
}
|
||||
|
||||
impl PatternFactory for ShrinkFactory {
|
||||
fn create(&self, config: &Config) -> Box<dyn Pattern> {
|
||||
Box::new(Shrink::new(
|
||||
self.child.create(config),
|
||||
self.width,
|
||||
self.rest,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl Pattern for Shrink {
|
||||
fn sample(&self, pos: Vector) -> f32 {
|
||||
(self.child.sample(pos) - self.rest) / self.width
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::MockPatternFactory;
|
||||
use approx::*;
|
||||
use mockall::predicate::eq;
|
||||
|
||||
#[test]
|
||||
fn create_config_correct() {
|
||||
let config = Config {
|
||||
size: Vector::new(7.0, 3.0),
|
||||
step: 0.2,
|
||||
};
|
||||
let mut child = MockPatternFactory::new();
|
||||
child
|
||||
.expect_create()
|
||||
.with(eq(config))
|
||||
.once()
|
||||
.returning(|_| Box::new(MockPattern::new()));
|
||||
|
||||
ShrinkFactory::new(Box::new(child), 4.0).create(&config);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sample_starts_with_one() {
|
||||
let mut child = MockPatternFactory::new();
|
||||
child.expect_create().returning(|_| {
|
||||
let mut sampler = MockPattern::new();
|
||||
sampler.expect_sample().return_const(1.0);
|
||||
Box::new(sampler)
|
||||
});
|
||||
|
||||
let sampler = ShrinkFactory::new(Box::new(child), 4.0).create(&Config::default());
|
||||
|
||||
assert_abs_diff_eq!(1.0, sampler.sample(Vector::default()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sample_ends_with_zero() {
|
||||
let mut child = MockPatternFactory::new();
|
||||
child.expect_create().returning(|_| {
|
||||
let mut sampler = MockPattern::new();
|
||||
sampler.expect_sample().return_const(0.75);
|
||||
Box::new(sampler)
|
||||
});
|
||||
|
||||
let sampler = ShrinkFactory::new(Box::new(child), 4.0).create(&Config::default());
|
||||
|
||||
assert_abs_diff_eq!(0.0, sampler.sample(Vector::default()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sample_values_beyond_end_are_negative() {
|
||||
let mut child = MockPatternFactory::new();
|
||||
child.expect_create().returning(|_| {
|
||||
let mut sampler = MockPattern::new();
|
||||
sampler.expect_sample().return_const(0.5);
|
||||
Box::new(sampler)
|
||||
});
|
||||
|
||||
let sampler = ShrinkFactory::new(Box::new(child), 4.0).create(&Config::default());
|
||||
|
||||
assert!(sampler.sample(Vector::default()) < 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sample_pos_correct() {
|
||||
let mut child = MockPatternFactory::new();
|
||||
child.expect_create().once().returning(|_| {
|
||||
let mut sampler = MockPattern::new();
|
||||
sampler
|
||||
.expect_sample()
|
||||
.with(eq(Vector::new(3.0, 5.0)))
|
||||
.once()
|
||||
.return_const(0.0);
|
||||
Box::new(sampler)
|
||||
});
|
||||
|
||||
let sampler = ShrinkFactory::new(Box::new(child), 3.0).create(&Config::default());
|
||||
|
||||
sampler.sample(Vector::new(3.0, 5.0));
|
||||
}
|
||||
}
|
90
src/transform/swap.rs
Normal file
90
src/transform/swap.rs
Normal file
@ -0,0 +1,90 @@
|
||||
use crate::pattern::*;
|
||||
use crate::Vector;
|
||||
|
||||
/// A factory for [Swap].
|
||||
///
|
||||
/// Swaps the x-axis and y-axis of terminal size for the contained [Pattern].
|
||||
#[derive(derive_more::Constructor)]
|
||||
pub struct SwapFactory {
|
||||
child: Box<dyn PatternFactory>,
|
||||
}
|
||||
|
||||
/// Swaps the x-axis and y-axis.
|
||||
#[derive(derive_more::Constructor)]
|
||||
pub struct Swap {
|
||||
child: Box<dyn Pattern>,
|
||||
}
|
||||
|
||||
impl PatternFactory for SwapFactory {
|
||||
fn create(&self, config: &Config) -> Box<dyn Pattern> {
|
||||
let mut copy = config.clone();
|
||||
copy.size = config.size.swap();
|
||||
|
||||
Box::new(Swap::new(self.child.create(©)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Pattern for Swap {
|
||||
fn sample(&self, pos: Vector) -> f32 {
|
||||
self.child.sample(pos.swap())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::MockPatternFactory;
|
||||
use approx::*;
|
||||
use mockall::predicate::eq;
|
||||
|
||||
#[test]
|
||||
fn create_config_correct() {
|
||||
let input = Config {
|
||||
size: Vector::new(4.0, 2.0),
|
||||
step: 0.4,
|
||||
};
|
||||
let mut output = input.clone();
|
||||
output.size = Vector::new(2.0, 4.0);
|
||||
|
||||
let mut child = MockPatternFactory::new();
|
||||
child
|
||||
.expect_create()
|
||||
.with(eq(output))
|
||||
.once()
|
||||
.returning(|_| Box::new(MockPattern::new()));
|
||||
|
||||
SwapFactory::new(Box::new(child)).create(&input);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sample_value_correct() {
|
||||
let mut child = MockPatternFactory::new();
|
||||
child.expect_create().returning(|_| {
|
||||
let mut sampler = MockPattern::new();
|
||||
sampler.expect_sample().return_const(0.4);
|
||||
Box::new(sampler)
|
||||
});
|
||||
|
||||
let sampler = SwapFactory::new(Box::new(child)).create(&Config::default());
|
||||
|
||||
assert_abs_diff_eq!(0.4, sampler.sample(Vector::default()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sample_pos_correct() {
|
||||
let mut child = MockPatternFactory::new();
|
||||
child.expect_create().once().returning(|_| {
|
||||
let mut sampler = MockPattern::new();
|
||||
sampler
|
||||
.expect_sample()
|
||||
.with(eq(Vector::new(9.0, 5.0)))
|
||||
.once()
|
||||
.return_const(0.0);
|
||||
Box::new(sampler)
|
||||
});
|
||||
|
||||
let sampler = SwapFactory::new(Box::new(child)).create(&Config::default());
|
||||
|
||||
sampler.sample(Vector::new(5.0, 9.0));
|
||||
}
|
||||
}
|
109
src/vec.rs
109
src/vec.rs
@ -1,106 +1,91 @@
|
||||
use std::ops::Sub;
|
||||
|
||||
/// A vector with a x and y axis.
|
||||
#[derive(Copy, Clone)]
|
||||
/// A vector with a x-axis and y-axis.
|
||||
#[derive(Copy, Clone, PartialEq, Debug, Default, derive_more::Sub)]
|
||||
pub struct Vector {
|
||||
pub x: f32,
|
||||
pub y: f32
|
||||
pub y: f32,
|
||||
}
|
||||
|
||||
impl Vector {
|
||||
/// Creates a new vector.
|
||||
pub fn new(x: f32, y: f32) -> Self {
|
||||
Self { x, y }
|
||||
}
|
||||
|
||||
pub fn center(self) -> Self {
|
||||
Self::new(self.x / 2.0, self.y / 2.0)
|
||||
/// Creates a new vector from terminal coordinates.
|
||||
pub fn from_terminal(x: u16, y: u16) -> Self {
|
||||
Vector::new(x as f32, y as f32 * 2.0)
|
||||
}
|
||||
|
||||
pub fn length(self) -> f32 {
|
||||
/// Returns the length.
|
||||
pub fn len(&self) -> f32 {
|
||||
(self.x * self.x + self.y * self.y).sqrt()
|
||||
}
|
||||
|
||||
pub fn angle(self) -> f32 {
|
||||
self.x.atan2(self.y)
|
||||
}
|
||||
|
||||
pub fn smaller(self) -> f32 {
|
||||
self.x.min(self.y)
|
||||
}
|
||||
|
||||
pub fn abs(self) -> Vector {
|
||||
/// Returns a vector with absolute values.
|
||||
pub fn abs(&self) -> Vector {
|
||||
Self::new(self.x.abs(), self.y.abs())
|
||||
}
|
||||
|
||||
pub fn sum(self) -> f32 {
|
||||
/// Returns the sum of all axis.
|
||||
pub fn sum(&self) -> f32 {
|
||||
self.x + self.y
|
||||
}
|
||||
|
||||
/// Creates a vector with the on screen coordinates based on the terminal coordinates.
|
||||
/// # Arguments
|
||||
/// * `x`: The x axis of the terminal character.
|
||||
/// * `y`: The y axis of the terminal character.
|
||||
pub fn from_terminal(x: usize, y: usize) -> Self {
|
||||
Self::new(x as f32, y as f32 * 2.0)
|
||||
/// Returns the center.
|
||||
pub fn center(&self) -> Vector {
|
||||
Self::new(self.x / 2.0, self.y / 2.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for Vector {
|
||||
type Output = Vector;
|
||||
/// Returns the angle.
|
||||
pub fn angle(&self) -> f32 {
|
||||
self.y.atan2(self.x)
|
||||
}
|
||||
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
Vector::new(self.x - rhs.x, self.y - rhs.y)
|
||||
/// Returns a vector with x and y swapped.
|
||||
pub fn swap(&self) -> Vector {
|
||||
Self::new(self.y, self.x)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use approx::*;
|
||||
|
||||
#[test]
|
||||
fn new() {
|
||||
let vec = Vector::new(3.0, 5.0);
|
||||
let v = Vector::new(4.0, 7.0);
|
||||
assert_abs_diff_eq!(4.0, v.x);
|
||||
assert_abs_diff_eq!(7.0, v.y);
|
||||
}
|
||||
|
||||
assert_eq!(3.0, vec.x);
|
||||
assert_eq!(5.0, vec.y);
|
||||
#[test]
|
||||
fn len() {
|
||||
assert_abs_diff_eq!(8.5, Vector::new(3.0, 8.0).len(), epsilon = 0.1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn abs() {
|
||||
assert_eq!(Vector::new(3.0, 7.0), Vector::new(-3.0, -7.0).abs());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sum() {
|
||||
assert_abs_diff_eq!(11.0, Vector::new(3.0, 8.0).sum());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn center() {
|
||||
let vec = Vector::new(3.0, 8.0);
|
||||
|
||||
assert_eq!(1.5, vec.center().x);
|
||||
assert_eq!(4.0, vec.center().y);
|
||||
assert_eq!(Vector::new(4.0, 9.0), Vector::new(8.0, 18.0).center());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn length() {
|
||||
let vec = Vector::new(3.0, 6.0);
|
||||
|
||||
assert!(6.7 < vec.length() && vec.length() < 6.8);
|
||||
fn angle() {
|
||||
assert_abs_diff_eq!(-1.5, Vector::new(2.0, -20.0).angle(), epsilon = 0.1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smaller() {
|
||||
assert_eq!(4.0, Vector::new(7.0, 4.0).smaller());
|
||||
assert_eq!(2.0, Vector::new(2.0, 9.0).smaller());
|
||||
fn swap() {
|
||||
assert_eq!(Vector::new(7.0, 2.0), Vector::new(2.0, 7.0).swap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_terminal() {
|
||||
let vec = Vector::from_terminal(2, 4);
|
||||
|
||||
assert_eq!(2.0, vec.x);
|
||||
assert_eq!(8.0, vec.y);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sub() {
|
||||
let left = Vector::new(8.0, 15.0);
|
||||
let right = Vector::new(2.0, 4.0);
|
||||
let result = left - right;
|
||||
|
||||
assert_eq!(6.0, result.x);
|
||||
assert_eq!(11.0, result.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user