From 6076d13a704894a0b9d5212e49c8a9fa1dc04d62 Mon Sep 17 00:00:00 2001 From: Charles Date: Tue, 26 Nov 2024 22:24:13 -0800 Subject: [PATCH] add: realm boilerplate, copy of demo --- Cargo.lock | 764 ++++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 + src/app/mod.rs | 7 + src/app/model.rs | 190 ++++++++++ src/components/clock.rs | 120 ++++++ src/components/counter.rs | 273 ++++++++++++++ src/components/label.rs | 124 +++++++ src/components/mod.rs | 33 ++ src/main.rs | 78 +++- 9 files changed, 1589 insertions(+), 2 deletions(-) create mode 100644 src/app/mod.rs create mode 100644 src/app/model.rs create mode 100644 src/components/clock.rs create mode 100644 src/components/counter.rs create mode 100644 src/components/label.rs create mode 100644 src/components/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 2f90a37..b215338 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,770 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[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.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6050c3a16ddab2e412160b31f2c871015704239bca62f72f6e5f0be631d3f644" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "static_assertions", +] + +[[package]] +name = "crossterm" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +dependencies = [ + "bitflags", + "crossterm_winapi", + "mio", + "parking_lot", + "rustix", + "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 = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" + +[[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 = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indoc" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" + +[[package]] +name = "instability" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b829f37dead9dc39df40c2d3376c179fdfd2ac771f53f55d3c30dc096a3c0c6e" +dependencies = [ + "darling", + "indoc", + "pretty_assertions", + "proc-macro2", + "quote", + "syn", +] + +[[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 = "lazy-regex" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d8e41c97e6bc7ecb552016274b99fbb5d035e8de288c582d9b933af6677bfda" +dependencies = [ + "lazy-regex-proc_macros", + "once_cell", + "regex", +] + +[[package]] +name = "lazy-regex-proc_macros" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76e1d8b05d672c53cb9c7b920bbba8783845ae4f0b076e02a3db1d02c81b4163" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn", +] + +[[package]] +name = "libc" +version = "0.2.166" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2ccc108bbc0b1331bd061864e7cd823c0cab660bbe6970e66e2c0614decde36" + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[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 = "mio" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi", + "libc", + "log", + "wasi", + "windows-sys", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + [[package]] name = "orangepunk-tui" version = "0.1.0" +dependencies = [ + "tui-realm-stdlib", + "tuirealm", +] + +[[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", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + +[[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.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ratatui" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdef7f9be5c0122f890d58bdf4d964349ba6a6161f705907526d891efabba57d" +dependencies = [ + "bitflags", + "cassowary", + "compact_str", + "crossterm", + "instability", + "itertools", + "lru", + "paste", + "strum", + "strum_macros", + "unicode-segmentation", + "unicode-truncate", + "unicode-width 0.1.14", +] + +[[package]] +name = "redox_syscall" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rustix" +version = "0.38.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rustversion" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" + +[[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 = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + +[[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.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[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.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "textwrap" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width 0.1.14", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tui-realm-stdlib" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78a24d06b8403c57b32a3d3fdac795adf4ef8610e5f9650a3629efc8a9d6c0bb" +dependencies = [ + "textwrap", + "tuirealm", + "unicode-width 0.2.0", +] + +[[package]] +name = "tuirealm" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d3865c9f0e84c07c991c24c853f095bb764859fa3806fe6e00b559f40f1253f" +dependencies = [ + "bitflags", + "crossterm", + "lazy-regex", + "ratatui", + "thiserror", + "tuirealm_derive", +] + +[[package]] +name = "tuirealm_derive" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa8b0560f4245acc0bbe0e1d76e1f6a308145dd6e107befce4cf29e7fe32662" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[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", + "unicode-segmentation", + "unicode-width 0.1.14", +] + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + +[[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.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[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.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[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.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[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.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" diff --git a/Cargo.toml b/Cargo.toml index bd841b6..63489af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,3 +4,5 @@ version = "0.1.0" edition = "2021" [dependencies] +tui-realm-stdlib = "2.0.1" +tuirealm = "2.0.3" diff --git a/src/app/mod.rs b/src/app/mod.rs new file mode 100644 index 0000000..865d5a5 --- /dev/null +++ b/src/app/mod.rs @@ -0,0 +1,7 @@ +//! ## Terminal +//! +//! terminal helper + +pub use super::*; + +pub mod model; diff --git a/src/app/model.rs b/src/app/model.rs new file mode 100644 index 0000000..aa9e317 --- /dev/null +++ b/src/app/model.rs @@ -0,0 +1,190 @@ +//! ## Model +//! +//! app model + +use std::time::{Duration, SystemTime}; + +use tuirealm::event::NoUserEvent; +use tuirealm::props::{Alignment, Color, TextModifiers}; +use tuirealm::ratatui::layout::{Constraint, Direction, Layout}; +use tuirealm::terminal::{CrosstermTerminalAdapter, TerminalAdapter, TerminalBridge}; +use tuirealm::{ + Application, AttrValue, Attribute, EventListenerCfg, Sub, SubClause, SubEventClause, Update, +}; + +use super::components::{Clock, DigitCounter, Label, LetterCounter}; +use super::{Id, Msg}; + +pub struct Model +where + T: TerminalAdapter, +{ + /// Application + pub app: Application, + /// Indicates that the application must quit + pub quit: bool, + /// Tells whether to redraw interface + pub redraw: bool, + /// Used to draw to terminal + pub terminal: TerminalBridge, +} + +impl Default for Model { + fn default() -> Self { + Self { + app: Self::init_app(), + quit: false, + redraw: true, + terminal: TerminalBridge::init_crossterm().expect("Cannot initialize terminal"), + } + } +} + +impl Model +where + T: TerminalAdapter, +{ + pub fn view(&mut self) { + assert!(self + .terminal + .draw(|f| { + let chunks = Layout::default() + .direction(Direction::Vertical) + .margin(1) + .constraints( + [ + Constraint::Length(3), // Clock + Constraint::Length(3), // Letter Counter + Constraint::Length(3), // Digit Counter + Constraint::Length(1), // Label + ] + .as_ref(), + ) + .split(f.area()); + self.app.view(&Id::Clock, f, chunks[0]); + self.app.view(&Id::LetterCounter, f, chunks[1]); + self.app.view(&Id::DigitCounter, f, chunks[2]); + self.app.view(&Id::Label, f, chunks[3]); + }) + .is_ok()); + } + + fn init_app() -> Application { + // Setup application + // NOTE: NoUserEvent is a shorthand to tell tui-realm we're not going to use any custom user event + // NOTE: the event listener is configured to use the default crossterm input listener and to raise a Tick event each second + // which we will use to update the clock + + let mut app: Application = Application::init( + EventListenerCfg::default() + .crossterm_input_listener(Duration::from_millis(20), 3) + .poll_timeout(Duration::from_millis(10)) + .tick_interval(Duration::from_secs(1)), + ); + // Mount components + assert!(app + .mount( + Id::Label, + Box::new( + Label::default() + .text("Waiting for a Msg...") + .alignment(Alignment::Left) + .background(Color::Reset) + .foreground(Color::LightYellow) + .modifiers(TextModifiers::BOLD), + ), + Vec::default(), + ) + .is_ok()); + // Mount clock, subscribe to tick + assert!(app + .mount( + Id::Clock, + Box::new( + Clock::new(SystemTime::now()) + .alignment(Alignment::Center) + .background(Color::Reset) + .foreground(Color::Cyan) + .modifiers(TextModifiers::BOLD) + ), + vec![Sub::new(SubEventClause::Tick, SubClause::Always)] + ) + .is_ok()); + // Mount counters + assert!(app + .mount( + Id::LetterCounter, + Box::new(LetterCounter::new(0)), + Vec::new() + ) + .is_ok()); + assert!(app + .mount( + Id::DigitCounter, + Box::new(DigitCounter::new(5)), + Vec::default() + ) + .is_ok()); + // Active letter counter + assert!(app.active(&Id::LetterCounter).is_ok()); + app + } +} + +// Let's implement Update for model + +impl Update for Model +where + T: TerminalAdapter, +{ + fn update(&mut self, msg: Option) -> Option { + if let Some(msg) = msg { + // Set redraw + self.redraw = true; + // Match message + match msg { + Msg::AppClose => { + self.quit = true; // Terminate + None + } + Msg::Clock => None, + Msg::DigitCounterBlur => { + // Give focus to letter counter + assert!(self.app.active(&Id::LetterCounter).is_ok()); + None + } + Msg::DigitCounterChanged(v) => { + // Update label + assert!(self + .app + .attr( + &Id::Label, + Attribute::Text, + AttrValue::String(format!("DigitCounter has now value: {}", v)) + ) + .is_ok()); + None + } + Msg::LetterCounterBlur => { + // Give focus to digit counter + assert!(self.app.active(&Id::DigitCounter).is_ok()); + None + } + Msg::LetterCounterChanged(v) => { + // Update label + assert!(self + .app + .attr( + &Id::Label, + Attribute::Text, + AttrValue::String(format!("LetterCounter has now value: {}", v)) + ) + .is_ok()); + None + } + } + } else { + None + } + } +} diff --git a/src/components/clock.rs b/src/components/clock.rs new file mode 100644 index 0000000..86d6a8f --- /dev/null +++ b/src/components/clock.rs @@ -0,0 +1,120 @@ +//! ## Label +//! +//! label component + +use std::ops::Add; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + +use tuirealm::command::{Cmd, CmdResult}; +use tuirealm::props::{Alignment, Color, TextModifiers}; +use tuirealm::ratatui::layout::Rect; +use tuirealm::{ + AttrValue, Attribute, Component, Event, Frame, MockComponent, NoUserEvent, State, StateValue, +}; + +use super::{Label, Msg}; + +/// Simple clock component which displays current time +pub struct Clock { + component: Label, + states: OwnStates, +} + +impl Clock { + pub fn new(initial_time: SystemTime) -> Self { + Self { + component: Label::default(), + states: OwnStates::new(initial_time), + } + } + + pub fn alignment(mut self, a: Alignment) -> Self { + self.component + .attr(Attribute::TextAlign, AttrValue::Alignment(a)); + self + } + + pub fn foreground(mut self, c: Color) -> Self { + self.component + .attr(Attribute::Foreground, AttrValue::Color(c)); + self + } + + pub fn background(mut self, c: Color) -> Self { + self.component + .attr(Attribute::Background, AttrValue::Color(c)); + self + } + + pub fn modifiers(mut self, m: TextModifiers) -> Self { + self.attr(Attribute::TextProps, AttrValue::TextModifiers(m)); + self + } + + fn time_to_str(&self) -> String { + let since_the_epoch = self.get_epoch_time(); + let hours = (since_the_epoch / 3600) % 24; + let minutes = (since_the_epoch / 60) % 60; + let seconds = since_the_epoch % 60; + format!("{:02}:{:02}:{:02}", hours, minutes, seconds) + } + + fn get_epoch_time(&self) -> u64 { + self.states + .time + .duration_since(UNIX_EPOCH) + .expect("Time went backwards") + .as_secs() + } +} + +impl MockComponent for Clock { + fn view(&mut self, frame: &mut Frame, area: Rect) { + // Render + self.component.view(frame, area); + } + + fn query(&self, attr: Attribute) -> Option { + self.component.query(attr) + } + + fn attr(&mut self, attr: Attribute, value: AttrValue) { + self.component.attr(attr, value); + } + + fn state(&self) -> State { + // Return current time + State::One(StateValue::U64(self.get_epoch_time())) + } + + fn perform(&mut self, cmd: Cmd) -> CmdResult { + self.component.perform(cmd) + } +} + +impl Component for Clock { + fn on(&mut self, ev: Event) -> Option { + if let Event::Tick = ev { + self.states.tick(); + // Set text + self.attr(Attribute::Text, AttrValue::String(self.time_to_str())); + Some(Msg::Clock) + } else { + None + } + } +} + +struct OwnStates { + time: SystemTime, +} + +impl OwnStates { + pub fn new(time: SystemTime) -> Self { + Self { time } + } + + pub fn tick(&mut self) { + self.time = self.time.add(Duration::from_secs(1)); + } +} diff --git a/src/components/counter.rs b/src/components/counter.rs new file mode 100644 index 0000000..e9a218d --- /dev/null +++ b/src/components/counter.rs @@ -0,0 +1,273 @@ +//! ## Label +//! +//! label component + +use tuirealm::command::{Cmd, CmdResult}; +use tuirealm::event::{Key, KeyEvent, KeyModifiers}; +use tuirealm::props::{Alignment, Borders, Color, Style, TextModifiers}; +use tuirealm::ratatui::layout::Rect; +use tuirealm::ratatui::widgets::{BorderType, Paragraph}; +use tuirealm::{ + AttrValue, Attribute, Component, Event, Frame, MockComponent, NoUserEvent, Props, State, + StateValue, +}; + +use super::{get_block, Msg}; + +/// Counter which increments its value on Submit +struct Counter { + props: Props, + states: OwnStates, +} + +impl Default for Counter { + fn default() -> Self { + Self { + props: Props::default(), + states: OwnStates::default(), + } + } +} + +impl Counter { + pub fn label(mut self, label: S) -> Self + where + S: AsRef, + { + self.attr( + Attribute::Title, + AttrValue::Title((label.as_ref().to_string(), Alignment::Center)), + ); + self + } + + pub fn value(mut self, n: isize) -> Self { + self.attr(Attribute::Value, AttrValue::Number(n)); + self + } + + pub fn alignment(mut self, a: Alignment) -> Self { + self.attr(Attribute::TextAlign, AttrValue::Alignment(a)); + self + } + + pub fn foreground(mut self, c: Color) -> Self { + self.attr(Attribute::Foreground, AttrValue::Color(c)); + self + } + + pub fn background(mut self, c: Color) -> Self { + self.attr(Attribute::Background, AttrValue::Color(c)); + self + } + + pub fn modifiers(mut self, m: TextModifiers) -> Self { + self.attr(Attribute::TextProps, AttrValue::TextModifiers(m)); + self + } + + pub fn borders(mut self, b: Borders) -> Self { + self.attr(Attribute::Borders, AttrValue::Borders(b)); + self + } +} + +impl MockComponent for Counter { + fn view(&mut self, frame: &mut Frame, area: Rect) { + // Check if visible + if self.props.get_or(Attribute::Display, AttrValue::Flag(true)) == AttrValue::Flag(true) { + // Get properties + let text = self.states.counter.to_string(); + let alignment = self + .props + .get_or(Attribute::TextAlign, AttrValue::Alignment(Alignment::Left)) + .unwrap_alignment(); + let foreground = self + .props + .get_or(Attribute::Foreground, AttrValue::Color(Color::Reset)) + .unwrap_color(); + let background = self + .props + .get_or(Attribute::Background, AttrValue::Color(Color::Reset)) + .unwrap_color(); + let modifiers = self + .props + .get_or( + Attribute::TextProps, + AttrValue::TextModifiers(TextModifiers::empty()), + ) + .unwrap_text_modifiers(); + let title = self + .props + .get_or( + Attribute::Title, + AttrValue::Title((String::default(), Alignment::Center)), + ) + .unwrap_title(); + let borders = self + .props + .get_or(Attribute::Borders, AttrValue::Borders(Borders::default())) + .unwrap_borders(); + let focus = self + .props + .get_or(Attribute::Focus, AttrValue::Flag(false)) + .unwrap_flag(); + frame.render_widget( + Paragraph::new(text) + .block(get_block(borders, title, focus)) + .style( + Style::default() + .fg(foreground) + .bg(background) + .add_modifier(modifiers), + ) + .alignment(alignment), + area, + ); + } + } + + fn query(&self, attr: Attribute) -> Option { + self.props.get(attr) + } + + fn attr(&mut self, attr: Attribute, value: AttrValue) { + self.props.set(attr, value); + } + + fn state(&self) -> State { + State::One(StateValue::Isize(self.states.counter)) + } + + fn perform(&mut self, cmd: Cmd) -> CmdResult { + match cmd { + Cmd::Submit => { + self.states.incr(); + CmdResult::Changed(self.state()) + } + _ => CmdResult::None, + } + } +} + +struct OwnStates { + counter: isize, +} + +impl Default for OwnStates { + fn default() -> Self { + Self { counter: 0 } + } +} + +impl OwnStates { + fn incr(&mut self) { + self.counter += 1; + } +} + +// -- Counter components + +#[derive(MockComponent)] +pub struct LetterCounter { + component: Counter, +} + +impl LetterCounter { + pub fn new(initial_value: isize) -> Self { + Self { + component: Counter::default() + .alignment(Alignment::Center) + .background(Color::Reset) + .borders( + Borders::default() + .color(Color::LightGreen) + .modifiers(BorderType::Rounded), + ) + .foreground(Color::LightGreen) + .modifiers(TextModifiers::BOLD) + .value(initial_value) + .label("Letter counter"), + } + } +} + +impl Component for LetterCounter { + fn on(&mut self, ev: Event) -> Option { + // Get command + let cmd = match ev { + Event::Keyboard(KeyEvent { + code: Key::Char(ch), + modifiers: KeyModifiers::NONE, + }) if ch.is_alphabetic() => Cmd::Submit, + Event::Keyboard(KeyEvent { + code: Key::Tab, + modifiers: KeyModifiers::NONE, + }) => return Some(Msg::LetterCounterBlur), // Return focus lost + Event::Keyboard(KeyEvent { + code: Key::Esc, + modifiers: KeyModifiers::NONE, + }) => return Some(Msg::AppClose), + _ => Cmd::None, + }; + // perform + match self.perform(cmd) { + CmdResult::Changed(State::One(StateValue::Isize(c))) => { + Some(Msg::LetterCounterChanged(c)) + } + _ => None, + } + } +} + +#[derive(MockComponent)] +pub struct DigitCounter { + component: Counter, +} + +impl DigitCounter { + pub fn new(initial_value: isize) -> Self { + Self { + component: Counter::default() + .alignment(Alignment::Center) + .background(Color::Reset) + .borders( + Borders::default() + .color(Color::Yellow) + .modifiers(BorderType::Rounded), + ) + .foreground(Color::Yellow) + .modifiers(TextModifiers::BOLD) + .value(initial_value) + .label("Digit counter"), + } + } +} + +impl Component for DigitCounter { + fn on(&mut self, ev: Event) -> Option { + // Get command + let cmd = match ev { + Event::Keyboard(KeyEvent { + code: Key::Char(ch), + modifiers: KeyModifiers::NONE, + }) if ch.is_digit(10) => Cmd::Submit, + Event::Keyboard(KeyEvent { + code: Key::Tab, + modifiers: KeyModifiers::NONE, + }) => return Some(Msg::DigitCounterBlur), // Return focus lost + Event::Keyboard(KeyEvent { + code: Key::Esc, + modifiers: KeyModifiers::NONE, + }) => return Some(Msg::AppClose), + _ => Cmd::None, + }; + // perform + match self.perform(cmd) { + CmdResult::Changed(State::One(StateValue::Isize(c))) => { + Some(Msg::DigitCounterChanged(c)) + } + _ => None, + } + } +} diff --git a/src/components/label.rs b/src/components/label.rs new file mode 100644 index 0000000..8bab2cd --- /dev/null +++ b/src/components/label.rs @@ -0,0 +1,124 @@ +//! ## Label +//! +//! label component + +use tuirealm::command::{Cmd, CmdResult}; +use tuirealm::props::{Alignment, Color, Style, TextModifiers}; +use tuirealm::ratatui::layout::Rect; +use tuirealm::ratatui::widgets::Paragraph; +use tuirealm::{ + AttrValue, Attribute, Component, Event, Frame, MockComponent, NoUserEvent, Props, State, +}; + +use super::Msg; + +/// Simple label component; just renders a text +/// NOTE: since I need just one label, I'm not going to use different object; I will directly implement Component for Label. +/// This is not ideal actually and in a real app you should differentiate Mock Components from Application Components. +pub struct Label { + props: Props, +} + +impl Default for Label { + fn default() -> Self { + Self { + props: Props::default(), + } + } +} + +impl Label { + pub fn text(mut self, s: S) -> Self + where + S: AsRef, + { + self.attr(Attribute::Text, AttrValue::String(s.as_ref().to_string())); + self + } + + pub fn alignment(mut self, a: Alignment) -> Self { + self.attr(Attribute::TextAlign, AttrValue::Alignment(a)); + self + } + + pub fn foreground(mut self, c: Color) -> Self { + self.attr(Attribute::Foreground, AttrValue::Color(c)); + self + } + + pub fn background(mut self, c: Color) -> Self { + self.attr(Attribute::Background, AttrValue::Color(c)); + self + } + + pub fn modifiers(mut self, m: TextModifiers) -> Self { + self.attr(Attribute::TextProps, AttrValue::TextModifiers(m)); + self + } +} + +impl MockComponent for Label { + fn view(&mut self, frame: &mut Frame, area: Rect) { + // Check if visible + if self.props.get_or(Attribute::Display, AttrValue::Flag(true)) == AttrValue::Flag(true) { + // Get properties + let text = self + .props + .get_or(Attribute::Text, AttrValue::String(String::default())) + .unwrap_string(); + let alignment = self + .props + .get_or(Attribute::TextAlign, AttrValue::Alignment(Alignment::Left)) + .unwrap_alignment(); + let foreground = self + .props + .get_or(Attribute::Foreground, AttrValue::Color(Color::Reset)) + .unwrap_color(); + let background = self + .props + .get_or(Attribute::Background, AttrValue::Color(Color::Reset)) + .unwrap_color(); + let modifiers = self + .props + .get_or( + Attribute::TextProps, + AttrValue::TextModifiers(TextModifiers::empty()), + ) + .unwrap_text_modifiers(); + frame.render_widget( + Paragraph::new(text) + .style( + Style::default() + .fg(foreground) + .bg(background) + .add_modifier(modifiers), + ) + .alignment(alignment), + area, + ); + } + } + + fn query(&self, attr: Attribute) -> Option { + self.props.get(attr) + } + + fn attr(&mut self, attr: Attribute, value: AttrValue) { + self.props.set(attr, value); + } + + fn state(&self) -> State { + State::None + } + + fn perform(&mut self, _: Cmd) -> CmdResult { + CmdResult::None + } +} + +impl Component for Label { + fn on(&mut self, _: Event) -> Option { + // Does nothing + None + } +} diff --git a/src/components/mod.rs b/src/components/mod.rs new file mode 100644 index 0000000..794c432 --- /dev/null +++ b/src/components/mod.rs @@ -0,0 +1,33 @@ +//! ## Components +//! +//! demo example components + +use tuirealm::props::{Alignment, Borders, Color, Style}; +use tuirealm::ratatui::widgets::Block; + +use super::Msg; + +// -- modules +mod clock; +mod counter; +mod label; + +// -- export +pub use clock::Clock; +pub use counter::{DigitCounter, LetterCounter}; +pub use label::Label; + +/// ### get_block +/// +/// Get block +pub(crate) fn get_block<'a>(props: Borders, title: (String, Alignment), focus: bool) -> Block<'a> { + Block::default() + .borders(props.sides) + .border_style(match focus { + true => props.style(), + false => Style::default().fg(Color::Reset).bg(Color::Reset), + }) + .border_type(props.modifiers) + .title(title.0) + .title_alignment(title.1) +} diff --git a/src/main.rs b/src/main.rs index e7a11a9..665aba1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,77 @@ -fn main() { - println!("Hello, world!"); +//! ## Demo +//! +//! `Demo` shows how to use tui-realm in a real case + +extern crate tuirealm; + +use tuirealm::application::PollStrategy; +use tuirealm::{AttrValue, Attribute, Update}; +// -- internal +mod app; +mod components; +use app::model::Model; + +// Let's define the messages handled by our app. NOTE: it must derive `PartialEq` +#[derive(Debug, PartialEq)] +pub enum Msg { + AppClose, + Clock, + DigitCounterChanged(isize), + DigitCounterBlur, + LetterCounterChanged(isize), + LetterCounterBlur, +} + +// Let's define the component ids for our application +#[derive(Debug, Eq, PartialEq, Clone, Hash)] +pub enum Id { + Clock, + DigitCounter, + LetterCounter, + Label, +} + +fn main() { + // Setup model + let mut model = Model::default(); + // Enter alternate screen + let _ = model.terminal.enter_alternate_screen(); + let _ = model.terminal.enable_raw_mode(); + // Main loop + // NOTE: loop until quit; quit is set in update if AppClose is received from counter + while !model.quit { + // Tick + match model.app.tick(PollStrategy::Once) { + Err(err) => { + assert!(model + .app + .attr( + &Id::Label, + Attribute::Text, + AttrValue::String(format!("Application error: {}", err)), + ) + .is_ok()); + } + Ok(messages) if messages.len() > 0 => { + // NOTE: redraw if at least one msg has been processed + model.redraw = true; + for msg in messages.into_iter() { + let mut msg = Some(msg); + while msg.is_some() { + msg = model.update(msg); + } + } + } + _ => {} + } + // Redraw + if model.redraw { + model.view(); + model.redraw = false; + } + } + // Terminate terminal + let _ = model.terminal.leave_alternate_screen(); + let _ = model.terminal.disable_raw_mode(); + let _ = model.terminal.clear_screen(); }