diff --git a/Cargo.lock b/Cargo.lock index 012efc81..45e88ed8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1276,11 +1276,16 @@ dependencies = [ "rand", "ratatui", "symphonia", + "tek_engine", "toml", "uuid", "wavers", ] +[[package]] +name = "tek_engine" +version = "0.2.0" + [[package]] name = "thiserror" version = "1.0.69" diff --git a/Cargo.toml b/Cargo.toml index c1285f0d..ae5af5fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,12 +4,11 @@ edition = "2021" version = "0.2.0" [dependencies] +tek_engine = { path = "./engine" } atomic_float = "1.0.0" backtrace = "0.3.72" -better-panic = "0.3.0" clap = { version = "4.5.4", features = [ "derive" ] } clojure-reader = "0.1.0" -crossterm = "0.27" jack = { path = "./rust-jack" } livi = "0.7.4" midly = "0.5" @@ -17,7 +16,6 @@ once_cell = "1.19.0" palette = { version = "0.7.6", features = [ "random" ] } quanta = "0.12.3" rand = "0.8.5" -ratatui = { version = "0.26.3", features = [ "unstable-widget-ref", "underline-color" ] } symphonia = { version = "0.5.4", features = [ "all" ] } toml = "0.8.12" uuid = { version = "1.10.0", features = [ "v4" ] } diff --git a/engine/Cargo.lock b/engine/Cargo.lock new file mode 100644 index 00000000..4c3144ab --- /dev/null +++ b/engine/Cargo.lock @@ -0,0 +1,666 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "better-panic" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fa9e1d11a268684cbd90ed36370d7577afb6c62d912ddff5c15fc34343e5036" +dependencies = [ + "backtrace", + "console", +] + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "cassowary" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + +[[package]] +name = "castaway" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" +dependencies = [ + "rustversion", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "compact_str" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "ryu", + "static_assertions", +] + +[[package]] +name = "console" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "windows-sys 0.59.0", +] + +[[package]] +name = "crossterm" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" +dependencies = [ + "bitflags", + "crossterm_winapi", + "libc", + "mio", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "foldhash" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "libc" +version = "0.2.169" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "miniz_oxide" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ratatui" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f44c9e68fd46eda15c646fbb85e1040b657a58cdc8c98db1d97a55930d991eef" +dependencies = [ + "bitflags", + "cassowary", + "compact_str", + "crossterm", + "itertools 0.12.1", + "lru", + "paste", + "stability", + "strum", + "unicode-segmentation", + "unicode-truncate", + "unicode-width", +] + +[[package]] +name = "redox_syscall" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustversion" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "stability" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d904e7009df136af5297832a3ace3370cd14ff1546a232f4f185036c2736fcac" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c786062daee0d6db1132800e623df74274a0a87322d8e183338e01b3d98d058" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tek_engine" +version = "0.2.0" +dependencies = [ + "better-panic", + "crossterm", + "ratatui", +] + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-truncate" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" +dependencies = [ + "itertools 0.13.0", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[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-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.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/engine/Cargo.toml b/engine/Cargo.toml new file mode 100644 index 00000000..ab351dc5 --- /dev/null +++ b/engine/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "tek_engine" +edition = "2021" +version = "0.2.0" + +[dependencies] +crossterm = "0.27" +ratatui = { version = "0.26.3", features = [ "unstable-widget-ref", "underline-color" ] } +better-panic = "0.3.0" diff --git a/engine/src/component.rs b/engine/src/component.rs new file mode 100644 index 00000000..3a9cba2b --- /dev/null +++ b/engine/src/component.rs @@ -0,0 +1,27 @@ +use crate::*; + +/// A UI component that can render itself as a [Render], and [Handle] input. +pub trait Component: Render + Handle {} + +/// Everything that implements [Render] and [Handle] is a [Component]. +impl + Handle> Component for C {} + +/// A component that can exit. +pub trait Exit: Send { + //fn exited (&self) -> bool; + //fn exit (&mut self); + //fn boxed (self) -> Box where Self: Sized + 'static { + //Box::new(self) + //} +} + +/// Marker trait for [Component]s that can [Exit]. +pub trait ExitableComponent: Exit + Component where E: Engine { + ///// Perform type erasure for collecting heterogeneous components. + //fn boxed (self) -> Box> where Self: Sized + 'static { + //Box::new(self) + //} +} + +/// All [Components]s that implement [Exit] implement [ExitableComponent]. +impl + Exit> ExitableComponent for C {} diff --git a/engine/src/engine.rs b/engine/src/engine.rs new file mode 100644 index 00000000..59f332a9 --- /dev/null +++ b/engine/src/engine.rs @@ -0,0 +1,80 @@ +use crate::*; +use std::fmt::{Debug, Display}; +use std::ops::{Add, Sub, Mul, Div}; + +/// Platform backend. +pub trait Engine: Send + Sync + Sized { + /// Input event type + type Input: Input; + /// Result of handling input + type Handled; + /// Render target + type Output: Output; + /// Unit of length + type Unit: Coordinate; + /// Rectangle without offset + type Size: Size + From<[Self::Unit;2]> + Debug + Copy; + /// Rectangle with offset + type Area: Area + From<[Self::Unit;4]> + Debug + Copy; + /// Prepare before run + fn setup (&mut self) -> Usually<()> { Ok(()) } + /// True if done + fn exited (&self) -> bool; + /// Clean up after run + fn teardown (&mut self) -> Usually<()> { Ok(()) } +} + +/// A linear coordinate. +pub trait Coordinate: Send + Sync + Copy + + Add + + Sub + + Mul + + Div + + Ord + PartialEq + Eq + + Debug + Display + Default + + From + Into + + Into + + Into +{ + fn minus (self, other: Self) -> Self { + if self >= other { + self - other + } else { + 0.into() + } + } + fn zero () -> Self { + 0.into() + } +} + +pub trait Size { + fn x (&self) -> N; + fn y (&self) -> N; + #[inline] fn w (&self) -> N { self.x() } + #[inline] fn h (&self) -> N { self.y() } + #[inline] fn wh (&self) -> [N;2] { [self.x(), self.y()] } + #[inline] fn clip_w (&self, w: N) -> [N;2] { [self.w().min(w), self.h()] } + #[inline] fn clip_h (&self, h: N) -> [N;2] { [self.w(), self.h().min(h)] } + #[inline] fn expect_min (&self, w: N, h: N) -> Usually<&Self> { + if self.w() < w || self.h() < h { + Err(format!("min {w}x{h}").into()) + } else { + Ok(self) + } + } +} + +pub trait Area: Copy { + fn x (&self) -> N; + fn y (&self) -> N; + fn w (&self) -> N; + fn h (&self) -> N; + #[inline] fn expect_min (&self, w: N, h: N) -> Usually<&Self> { + if self.w() < w || self.h() < h { + Err(format!("min {w}x{h}").into()) + } else { + Ok(self) + } + } +} diff --git a/src/input.rs b/engine/src/input.rs similarity index 98% rename from src/input.rs rename to engine/src/input.rs index 9686a533..b1a3ed34 100644 --- a/src/input.rs +++ b/engine/src/input.rs @@ -1,4 +1,5 @@ use crate::*; +use std::sync::{Arc, Mutex, RwLock}; /// Current input state pub trait Input { @@ -12,11 +13,6 @@ pub trait Input { fn done (&self); } -/// Handle input -pub trait Handle: Send + Sync { - fn handle (&mut self, context: &E::Input) -> Perhaps; -} - #[macro_export] macro_rules! handle { (<$E:ty>|$self:ident:$Struct:ty,$input:ident|$handler:expr) => { impl Handle<$E> for $Struct { @@ -27,6 +23,11 @@ pub trait Handle: Send + Sync { } } +/// Handle input +pub trait Handle: Send + Sync { + fn handle (&mut self, context: &E::Input) -> Perhaps; +} + impl> Handle for &mut H { fn handle (&mut self, context: &E::Input) -> Perhaps { (*self).handle(context) diff --git a/engine/src/lib.rs b/engine/src/lib.rs new file mode 100644 index 00000000..c4078b54 --- /dev/null +++ b/engine/src/lib.rs @@ -0,0 +1,62 @@ +mod component; pub use self::component::*; +mod engine; pub use self::engine::*; +mod input; pub use self::input::*; +mod output; pub use self::output::*; +mod tui; pub use self::tui::*; + +pub use std::error::Error; + +/// Standard result type. +pub type Usually = Result>; + +/// Standard optional result type. +pub type Perhaps = Result, Box>; + +#[cfg(test)] #[test] fn test_stub_engine () -> Usually<()> { + struct TestEngine(bool); + struct TestInput(bool); + struct TestOutput([u16;4]); + enum TestEvent { Test1 } + impl Engine for TestEngine { + type Input = TestInput; + type Handled = (); + type Output = TestOutput; + type Unit = u16; + type Size = [u16;2]; + type Area = [u16;4]; + fn exited (&self) -> bool { + self.0 + } + } + impl Input for TestInput { + type Event = TestEvent; + fn event (&self) -> &Self::Event { + &TestEvent::Test1 + } + fn is_done (&self) -> bool { + self.0 + } + fn done (&self) {} + } + impl Output for TestOutput { + fn area (&self) -> [u16;4] { + self.0 + } + fn area_mut (&mut self) -> &mut [u16;4] { + &mut self.0 + } + fn render_in (&mut self, _: [u16;4], _: &dyn Render) -> Usually<()> { + Ok(()) + } + } + impl Render for String { + fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> { + Ok(Some([self.len() as u16, 1])) + } + } + Ok(()) +} + +#[cfg(test)] #[test] fn test_tui_engine () -> Usually<()> { + Ok(()) +} diff --git a/engine/src/output.rs b/engine/src/output.rs new file mode 100644 index 00000000..c50769bb --- /dev/null +++ b/engine/src/output.rs @@ -0,0 +1,272 @@ +use crate::*; +use std::sync::{Arc, Mutex, RwLock}; + +/// Rendering target +pub trait Output { + /// Current output area + fn area (&self) -> E::Area; + /// Mutable pointer to area + fn area_mut (&mut self) -> &mut E::Area; + /// Render widget in area + fn render_in (&mut self, area: E::Area, widget: &dyn Render) -> Usually<()>; +} + +/// Write content to output buffer. +pub trait Render: Send + Sync { + /// Minimum size to use + fn min_size (&self, _: E::Size) -> Perhaps { + Ok(None) + } + /// Draw to output render target + fn render (&self, _: &mut E::Output) -> Usually<()> { + Ok(()) + } +} + +impl Render for &dyn Render {} +impl Render for &mut dyn Render {} +impl Render for Box> {} +impl> Render for &R {} +impl> Render for &mut R {} +impl> Render for Option {} +impl> Render for Arc {} +impl> Render for Mutex {} +impl> Render for RwLock {} + +/// Something that can be represented by a renderable component. +pub trait Content: Render + Send + Sync { + fn content (&self) -> Option> where (): Render { + None::<()> + } +} + +impl Content for &dyn Render {} +impl Content for &mut dyn Render {} +impl Content for Box> {} +impl> Content for &C {} +impl> Content for &mut C {} +impl> Content for Option {} +impl> Content for Arc {} +impl> Content for Mutex {} +impl> Content for RwLock {} + +/**** + + +impl + Send + Sync> Content for R {} + +//impl> Content for R { + //fn content (&self) -> Option> { + //Some(self) + //} +//} + +/// All implementors of [Content] can be [Render]ed. +impl> Render for C { + /// Minimum size to use + fn min_size (&self, to: E::Size) -> Perhaps { + self.content().map(|content|content.min_size(to)) + .unwrap_or(Ok(None)) + } + /// Draw to output render target + fn render (&self, to: &mut E::Output) -> Usually<()> { + self.content().map(|content|content.render(to)) + .unwrap_or(Ok(())) + } +} + +//impl> Content for &C { + //fn content (&self) -> Option> { + //Some(self) + //} +//} + +//impl> Content for Option { + //fn content (&self) -> Option> { + //Some(self) + //} +//} + + +/// Define custom content for a struct. +#[macro_export] macro_rules! render { + + // Implement for all engines + (|$self:ident:$Struct:ident$(<$($L:lifetime),*E$(,$T:ident$(:$U:path)?)*$(,)?>)?|$cb:expr) => { + impl Content for $Struct $(<$($L,)* E, $($T),*>)? { + fn content (&$self) -> Option> { + Some($cb) + } + } + }; + + // Implement for a specific engine + (<$E:ty>|$self:ident:$Struct:ident$(< + $($($L:lifetime),+)? + $($($T:ident$(:$U:path)?),+)? + >)?|$cb:expr) => { + impl $(< + $($($L),+)? + $($($T$(:$U)?),+)? + >)? Content<$E> for $Struct $(< + $($($L),+)? + $($($T),+)? + >)? { + fn content (&$self) -> Option> { + Some($cb) + } + } + } + +} + +impl> Render for &R { + fn min_size (&self, to: E::Size) -> Perhaps { + (*self).min_size(to) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + (*self).render(to) + } +} + +impl> Render for Option { + fn min_size (&self, to: E::Size) -> Perhaps { + self.map(|content|content.min_size(to)) + .unwrap_or(Ok(None)) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + self.map(|content|content.render(to)) + .unwrap_or(Ok(())) + } +} + +//impl Render for &dyn Render { + //fn min_size (&self, to: E::Size) -> Perhaps { + //(*self).min_size(to) + //} + //fn render (&self, to: &mut E::Output) -> Usually<()> { + //(*self).render(to) + //} +//} + +//impl Render for &mut dyn Render { + //fn min_size (&self, to: E::Size) -> Perhaps { + //(*self).min_size(to) + //} + //fn render (&self, to: &mut E::Output) -> Usually<()> { + //(*self).render(to) + //} +//} + +//impl Render for Box + '_> { + //fn min_size (&self, to: E::Size) -> Perhaps { + //(**self).min_size(to) + //} + //fn render (&self, to: &mut E::Output) -> Usually<()> { + //(**self).render(to) + //} +//} + +//impl> Render for Arc { + //fn min_size (&self, to: E::Size) -> Perhaps { + //self.as_ref().min_size(to) + //} + //fn render (&self, to: &mut E::Output) -> Usually<()> { + //self.as_ref().render(to) + //} +//} + +//impl> Render for Mutex { + //fn min_size (&self, to: E::Size) -> Perhaps { + //self.lock().unwrap().min_size(to) + //} + //fn render (&self, to: &mut E::Output) -> Usually<()> { + //self.lock().unwrap().render(to) + //} +//} + +//impl> Render for RwLock { + //fn min_size (&self, to: E::Size) -> Perhaps { + //self.read().unwrap().min_size(to) + //} + //fn render (&self, to: &mut E::Output) -> Usually<()> { + //self.read().unwrap().render(to) + //} +//} + +//impl> Render for Option { + //fn min_size (&self, to: E::Size) -> Perhaps { + //Ok(self.as_ref().map(|widget|widget.min_size(to)).transpose()?.flatten()) + //} + //fn render (&self, to: &mut E::Output) -> Usually<()> { + //self.as_ref().map(|widget|widget.render(to)).unwrap_or(Ok(())) + //} +//} + +/// Cast to dynamic pointer +pub fn widget (w: &T) -> &dyn Render + where E: Engine, T: Render +{ + w as &dyn Render +} + +/// Ad-hoc widget with custom rendering. +pub fn render (render: F) -> impl Render + where E: Engine, F: Fn(&mut E::Output)->Usually<()>+Send+Sync +{ + Widget::new(|_|Ok(Some([0.into(),0.into()].into())), render) +} + +/// A custom [Render] defined by passing layout and render closures in place. +pub struct Widget< + E: Engine, + L: Send + Sync + Fn(E::Size)->Perhaps, + R: Send + Sync + Fn(&mut E::Output)->Usually<()> +>(L, R, PhantomData); + +impl< + E: Engine, + L: Send + Sync + Fn(E::Size)->Perhaps, + R: Send + Sync + Fn(&mut E::Output)->Usually<()> +> Widget { + pub fn new (layout: L, render: R) -> Self { + Self(layout, render, Default::default()) + } +} + +impl< + E: Engine, + L: Send + Sync + Fn(E::Size)->Perhaps, + R: Send + Sync + Fn(&mut E::Output)->Usually<()> +> Render for Widget { + fn min_size (&self, to: E::Size) -> Perhaps { + self.0(to) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + self.1(to) + } +} + +/// Has static methods for conditional rendering, +/// in unary and binary forms. +pub struct Cond; + +impl Cond { + /// Render `item` when `cond` is true. + pub fn when > (cond: bool, item: A) -> When { + When(cond, item, Default::default()) + } + /// Render `item` if `cond` is true, otherwise render `other`. + pub fn either , B: Render> (cond: bool, item: A, other: B) -> Either { + Either(cond, item, other, Default::default()) + } +} + +/// Renders `self.1` when `self.0` is true. +pub struct When>(bool, A, PhantomData); + +/// Renders `self.1` when `self.0` is true, otherwise renders `self.2` +pub struct Either, B: Render>(bool, A, B, PhantomData); + + +**/ diff --git a/engine/src/tui.rs b/engine/src/tui.rs new file mode 100644 index 00000000..7d6aa090 --- /dev/null +++ b/engine/src/tui.rs @@ -0,0 +1,440 @@ +use crate::*; +use std::sync::{Arc, RwLock, atomic::{AtomicBool, Ordering::*}}; +use std::io::{stdout, Stdout}; +use std::time::Duration; +use std::thread::{spawn, JoinHandle}; + +pub use ::better_panic; +pub(crate) use better_panic::{Settings, Verbosity}; + +pub use ::crossterm; +pub(crate) use crossterm::{ + ExecutableCommand, + event::*, + terminal::{EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode, disable_raw_mode}, + event::{KeyCode, KeyModifiers, KeyEvent, KeyEventKind, KeyEventState}, +}; + +pub use ::ratatui; +pub(crate) use ratatui::{ + prelude::{Color, Style, Buffer}, + style::Modifier, + backend::{Backend, CrosstermBackend, ClearType}, + buffer::Cell +}; + +impl Coordinate for u16 {} + +impl Size for [u16;2] { + fn x (&self) -> u16 { self[0] } + fn y (&self) -> u16 { self[1] } +} + +impl Area for [u16;4] { + fn x (&self) -> u16 { self[0] } + fn y (&self) -> u16 { self[1] } + fn w (&self) -> u16 { self[2] } + fn h (&self) -> u16 { self[3] } +} + +pub struct Tui { + pub exited: Arc, + pub buffer: Buffer, + pub backend: CrosstermBackend, + pub area: [u16;4], // FIXME auto resize +} + +impl Engine for Tui { + type Unit = u16; + type Size = [Self::Unit;2]; + type Area = [Self::Unit;4]; + type Input = TuiInput; + type Handled = bool; + type Output = TuiOutput; + fn exited (&self) -> bool { + self.exited.fetch_and(true, Relaxed) + } + fn setup (&mut self) -> Usually<()> { + let better_panic_handler = Settings::auto().verbosity(Verbosity::Full).create_panic_handler(); + std::panic::set_hook(Box::new(move |info: &std::panic::PanicHookInfo|{ + stdout().execute(LeaveAlternateScreen).unwrap(); + CrosstermBackend::new(stdout()).show_cursor().unwrap(); + disable_raw_mode().unwrap(); + better_panic_handler(info); + })); + stdout().execute(EnterAlternateScreen)?; + self.backend.hide_cursor()?; + enable_raw_mode().map_err(Into::into) + } + fn teardown (&mut self) -> Usually<()> { + stdout().execute(LeaveAlternateScreen)?; + self.backend.show_cursor()?; + disable_raw_mode().map_err(Into::into) + } +} + +impl Tui { + /// Run the main loop. + pub fn run + Sized + 'static> ( + state: Arc> + ) -> Usually>> { + let backend = CrosstermBackend::new(stdout()); + let area = backend.size()?; + let engine = Self { + exited: Arc::new(AtomicBool::new(false)), + buffer: Buffer::empty(area), + area: [area.x, area.y, area.width, area.height], + backend, + }; + let engine = Arc::new(RwLock::new(engine)); + let _input_thread = Self::spawn_input_thread(&engine, &state, Duration::from_millis(100)); + engine.write().unwrap().setup()?; + let render_thread = Self::spawn_render_thread(&engine, &state, Duration::from_millis(10)); + render_thread.join().expect("main thread failed"); + engine.write().unwrap().teardown()?; + Ok(state) + } + fn spawn_input_thread + Sized + 'static> ( + engine: &Arc>, state: &Arc>, poll: Duration + ) -> JoinHandle<()> { + let exited = engine.read().unwrap().exited.clone(); + let state = state.clone(); + spawn(move || loop { + if exited.fetch_and(true, Relaxed) { + break + } + if ::crossterm::event::poll(poll).is_ok() { + let event = TuiEvent::Input(::crossterm::event::read().unwrap()); + match event { + key_pat!(Ctrl-KeyCode::Char('c')) => { + exited.store(true, Relaxed); + }, + _ => { + let exited = exited.clone(); + if let Err(e) = state.write().unwrap().handle(&TuiInput { event, exited }) { + panic!("{e}") + } + } + } + } + }) + } + fn spawn_render_thread + Sized + 'static> ( + engine: &Arc>, state: &Arc>, sleep: Duration + ) -> JoinHandle<()> { + let exited = engine.read().unwrap().exited.clone(); + let engine = engine.clone(); + let state = state.clone(); + let size = engine.read().unwrap().backend.size().expect("get size failed"); + let mut buffer = Buffer::empty(size); + spawn(move || loop { + if exited.fetch_and(true, Relaxed) { + break + } + let size = engine.read().unwrap().backend.size() + .expect("get size failed"); + if let Ok(state) = state.try_read() { + if buffer.area != size { + engine.write().unwrap().backend.clear_region(ClearType::All) + .expect("clear failed"); + buffer.resize(size); + buffer.reset(); + } + let mut output = TuiOutput { + buffer, + area: [size.x, size.y, size.width, size.height] + }; + state.render(&mut output).expect("render failed"); + buffer = engine.write().unwrap().flip(output.buffer, size); + } + std::thread::sleep(sleep); + }) + } + fn flip (&mut self, mut buffer: Buffer, size: ratatui::prelude::Rect) -> Buffer { + if self.buffer.area != size { + self.backend.clear_region(ClearType::All).unwrap(); + self.buffer.resize(size); + self.buffer.reset(); + } + let updates = self.buffer.diff(&buffer); + self.backend.draw(updates.into_iter()).expect("failed to render"); + self.backend.flush().expect("failed to flush output buffer"); + std::mem::swap(&mut self.buffer, &mut buffer); + buffer.reset(); + buffer + } +} + +pub struct TuiInput { + pub(crate) exited: Arc, + pub(crate) event: TuiEvent, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum TuiEvent { + /// Terminal input + Input(Event), +} + +impl Input for TuiInput { + type Event = TuiEvent; + fn event (&self) -> &TuiEvent { + &self.event + } + fn is_done (&self) -> bool { + self.exited.fetch_and(true, Relaxed) + } + fn done (&self) { + self.exited.store(true, Relaxed); + } +} + +#[macro_export] macro_rules! key_pat { + (Ctrl-Alt-$code:pat) => { key_event_pat!($code, KeyModifiers::CONTROL | KeyModifiers::ALT) }; + (Ctrl-$code:pat) => { key_event_pat!($code, KeyModifiers::CONTROL) }; + (Alt-$code:pat) => { key_event_pat!($code, KeyModifiers::ALT) }; + (Shift-$code:pat) => { key_event_pat!($code, KeyModifiers::SHIFT) }; + ($code:pat) => { TuiEvent::Input(crossterm::event::Event::Key(KeyEvent { + code: $code, + modifiers: KeyModifiers::NONE, + kind: KeyEventKind::Press, + state: KeyEventState::NONE + })) }; +} + +#[macro_export] macro_rules! key_event_pat { + ($code:pat) => { + TuiEvent::Input(crossterm::event::Event::Key(KeyEvent { + code: $code, + modifiers: KeyModifiers::NONE, + kind: KeyEventKind::Press, + state: KeyEventState::NONE + })) + }; + ($code:pat, $modifiers: pat) => { + TuiEvent::Input(crossterm::event::Event::Key(KeyEvent { + code: $code, + modifiers: $modifiers, + kind: KeyEventKind::Press, + state: KeyEventState::NONE + })) + }; +} + +#[macro_export] macro_rules! kexp { + (Ctrl-Alt-$code:ident) => { key_event_expr!($code, KeyModifiers::from_bits(0b0000_0110).unwrap()) }; + (Ctrl-$code:ident) => { key_event_expr!($code, KeyModifiers::CONTROL) }; + (Alt-$code:ident) => { key_event_expr!($code, KeyModifiers::ALT) }; + (Shift-$code:ident) => { key_event_expr!($code, KeyModifiers::SHIFT) }; + ($code:ident) => { key_event_expr!($code) }; + ($code:expr) => { key_event_expr!($code) }; +} + +#[macro_export] macro_rules! key_expr { + (Ctrl-Alt-$code:ident) => { key_event_expr!($code, KeyModifiers::CONTROL | KeyModifiers::ALT) }; + (Ctrl-$code:ident) => { key_event_expr!($code, KeyModifiers::CONTROL) }; + (Alt-$code:ident) => { key_event_expr!($code, KeyModifiers::ALT) }; + (Shift-$code:ident) => { key_event_expr!($code, KeyModifiers::SHIFT) }; + ($code:ident) => { key_event_expr!($code) }; +} + +#[macro_export] macro_rules! key_event_expr { + ($code:expr, $modifiers: expr) => { + TuiEvent::Input(crossterm::event::Event::Key(KeyEvent { + code: $code, + modifiers: $modifiers, + kind: KeyEventKind::Press, + state: KeyEventState::NONE + })) + }; + ($code:expr) => { + TuiEvent::Input(crossterm::event::Event::Key(KeyEvent { + code: $code, + modifiers: KeyModifiers::NONE, + kind: KeyEventKind::Press, + state: KeyEventState::NONE + })) + }; +} + +/* +/// Define a key +pub const fn key (code: KeyCode) -> KeyEvent { + let modifiers = KeyModifiers::NONE; + let kind = KeyEventKind::Press; + let state = KeyEventState::NONE; + KeyEvent { code, modifiers, kind, state } +} + +/// Add Ctrl modifier to key +pub const fn ctrl (key: KeyEvent) -> KeyEvent { + KeyEvent { modifiers: key.modifiers.union(KeyModifiers::CONTROL), ..key } +} + +/// Add Alt modifier to key +pub const fn alt (key: KeyEvent) -> KeyEvent { + KeyEvent { modifiers: key.modifiers.union(KeyModifiers::ALT), ..key } +} + +/// Add Shift modifier to key +pub const fn shift (key: KeyEvent) -> KeyEvent { + KeyEvent { modifiers: key.modifiers.union(KeyModifiers::SHIFT), ..key } +} + +/// Define a keymap +#[macro_export] macro_rules! keymap { + ($T:ty { $([$k:ident $(($char:literal))?, $m:ident, $n: literal, $d: literal, $f: expr]),* $(,)? }) => { + &[ + $((KeyCode::$k $(($char))?, KeyModifiers::$m, $n, $d, &$f as KeyHandler<$T>)),* + ] as &'static [KeyBinding<$T>] + } +} + +*/ + +/* + +impl TuiInput { + // TODO remove + pub fn handle_keymap (&self, state: &mut T, keymap: &KeyMap) -> Usually { + match self.event() { + TuiEvent::Input(Key(event)) => { + for (code, modifiers, _, _, command) in keymap.iter() { + if *code == event.code && modifiers.bits() == event.modifiers.bits() { + return command(state) + } + } + }, + _ => {} + }; + Ok(false) + } +} + +pub type KeyHandler = &'static dyn Fn(&mut T)->Usually; + +pub type KeyBinding = (KeyCode, KeyModifiers, &'static str, &'static str, KeyHandler); + +pub type KeyMap = [KeyBinding]; + +*/ + +pub struct TuiOutput { + pub buffer: Buffer, + pub area: [u16;4] +} + +impl Output for TuiOutput { + #[inline] fn area (&self) -> [u16;4] { self.area } + #[inline] fn area_mut (&mut self) -> &mut [u16;4] { &mut self.area } + #[inline] fn render_in (&mut self, area: [u16;4], widget: &dyn Render) -> Usually<()> { + let last = self.area(); + *self.area_mut() = area; + widget.render(self)?; + *self.area_mut() = last; + Ok(()) + } +} + +impl TuiOutput { + pub fn buffer_update (&mut self, area: [u16;4], callback: &impl Fn(&mut Cell, u16, u16)) { + buffer_update(&mut self.buffer, area, callback); + } + pub fn fill_bold (&mut self, area: [u16;4], on: bool) { + if on { + self.buffer_update(area, &|cell,_,_|cell.modifier.insert(Modifier::BOLD)) + } else { + self.buffer_update(area, &|cell,_,_|cell.modifier.remove(Modifier::BOLD)) + } + } + pub fn fill_bg (&mut self, area: [u16;4], color: Color) { + self.buffer_update(area, &|cell,_,_|{cell.set_bg(color);}) + } + pub fn fill_fg (&mut self, area: [u16;4], color: Color) { + self.buffer_update(area, &|cell,_,_|{cell.set_fg(color);}) + } + pub fn fill_ul (&mut self, area: [u16;4], color: Color) { + self.buffer_update(area, &|cell,_,_|{ + cell.modifier = ratatui::prelude::Modifier::UNDERLINED; + cell.underline_color = color; + }) + } + pub fn fill_char (&mut self, area: [u16;4], c: char) { + self.buffer_update(area, &|cell,_,_|{cell.set_char(c);}) + } + pub fn make_dim (&mut self) { + for cell in self.buffer.content.iter_mut() { + cell.bg = ratatui::style::Color::Rgb(30,30,30); + cell.fg = ratatui::style::Color::Rgb(100,100,100); + cell.modifier = ratatui::style::Modifier::DIM; + } + } + pub fn blit ( + &mut self, text: &impl AsRef, x: u16, y: u16, style: Option