cursive-0.20.0/.cargo_vcs_info.json0000644000000001450000000000100125730ustar { "git": { "sha1": "e09e62a3d59cc2a230f047862f9630779e89e692" }, "path_in_vcs": "cursive" }cursive-0.20.0/Cargo.lock0000644000000554040000000000100105560ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "ahash" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57e6e951cfbb2db8de1828d49073a113a29fd7117b1596caa781a258c7e38d72" dependencies = [ "cfg-if", "getrandom", "once_cell", "version_check 0.9.4", ] [[package]] name = "ansi-parser" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcb2392079bf27198570d6af79ecbd9ec7d8f16d3ec6b60933922fdb66287127" dependencies = [ "heapless", "nom", ] [[package]] name = "as-slice" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45403b49e3954a4b8428a0ac21a4b7afadccf92bfd96273f1a58cd4812496ae0" dependencies = [ "generic-array 0.12.4", "generic-array 0.13.3", "generic-array 0.14.6", "stable_deref_trait", ] [[package]] name = "atty" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi", "libc", "winapi", ] [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "bear-lib-terminal" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "315549f695a3fef8ad1e1ebad093a7c38d135fab85e10067841744901f907116" dependencies = [ "bear-lib-terminal-sys", ] [[package]] name = "bear-lib-terminal-sys" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a04c83b2c02f07128177fa98ea2d241f85d351d376dcee70694fcb6960a279f" dependencies = [ "libc", ] [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "byteorder" version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "cc" version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "crossbeam-channel" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" dependencies = [ "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" dependencies = [ "cfg-if", "once_cell", ] [[package]] name = "crossterm" version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" 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 = "cursive" version = "0.20.0" dependencies = [ "ahash", "atty", "bear-lib-terminal", "cfg-if", "crossbeam-channel", "crossterm", "cursive_core", "lazy_static", "libc", "log", "maplit", "ncurses", "pancurses", "pretty-bytes", "rand", "signal-hook", "term_size", "termion", "unicode-segmentation", "unicode-width", ] [[package]] name = "cursive_core" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8c04587ebe2dc513de481bfaadd99edef636269b3b722ad11db6a12307aecbe" dependencies = [ "ahash", "ansi-parser", "crossbeam-channel", "enum-map", "enumset", "lazy_static", "log", "num", "owning_ref", "pulldown-cmark", "time", "toml", "unicode-segmentation", "unicode-width", "xi-unicode", ] [[package]] name = "darling" version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" dependencies = [ "darling_core", "darling_macro", ] [[package]] name = "darling_core" version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "syn", ] [[package]] name = "darling_macro" version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" dependencies = [ "darling_core", "quote", "syn", ] [[package]] name = "enum-map" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5a56d54c8dd9b3ad34752ed197a4eb2a6601bc010808eb097a04a58ae4c43e1" dependencies = [ "enum-map-derive", ] [[package]] name = "enum-map-derive" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9045e2676cd5af83c3b167d917b0a5c90a4d8e266e2683d6631b235c457fc27" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "enumset" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4799cdb24d48f1f8a7a98d06b7fde65a85a2d1e42b25a889f5406aa1fbefe074" dependencies = [ "enumset_derive", ] [[package]] name = "enumset_derive" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea83a3fbdc1d999ccfbcbee717eab36f8edf2d71693a23ce0d7cca19e085304c" dependencies = [ "darling", "proc-macro2", "quote", "syn", ] [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "generic-array" version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" dependencies = [ "typenum", ] [[package]] name = "generic-array" version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f797e67af32588215eaaab8327027ee8e71b9dd0b2b26996aedf20c030fce309" dependencies = [ "typenum", ] [[package]] name = "generic-array" version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" dependencies = [ "typenum", "version_check 0.9.4", ] [[package]] name = "getopts" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" dependencies = [ "unicode-width", ] [[package]] name = "getrandom" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "hash32" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4041af86e63ac4298ce40e5cca669066e75b6f1aa3390fe2561ffa5e1d9f4cc" dependencies = [ "byteorder", ] [[package]] name = "heapless" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74911a68a1658cfcfb61bc0ccfbd536e3b6e906f8c2f7883ee50157e3e2184f1" dependencies = [ "as-slice", "generic-array 0.13.3", "hash32", "stable_deref_trait", ] [[package]] name = "hermit-abi" version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" dependencies = [ "libc", ] [[package]] name = "ident_case" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "itoa" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" [[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.132" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" [[package]] name = "lock_api" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f80bf5aacaf25cbfc8210d1cfb718f2bf3b11c4c54e5afe36c236853a8ec390" 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 = "maplit" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" [[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.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" dependencies = [ "libc", "log", "wasi", "windows-sys", ] [[package]] name = "ncurses" version = "5.101.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e2c5d34d72657dc4b638a1c25d40aae81e4f1c699062f72f467237920752032" dependencies = [ "cc", "libc", "pkg-config", ] [[package]] name = "nom" version = "4.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" dependencies = [ "memchr", "version_check 0.1.5", ] [[package]] name = "num" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" dependencies = [ "num-complex", "num-integer", "num-iter", "num-rational", "num-traits", ] [[package]] name = "num-complex" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" dependencies = [ "num-traits", ] [[package]] name = "num-integer" version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ "autocfg", "num-traits", ] [[package]] name = "num-iter" version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" dependencies = [ "autocfg", "num-integer", "num-traits", ] [[package]] name = "num-rational" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" dependencies = [ "autocfg", "num-integer", "num-traits", ] [[package]] name = "num-traits" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", ] [[package]] name = "num_threads" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" dependencies = [ "libc", ] [[package]] name = "numtoa" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" [[package]] name = "once_cell" version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0" [[package]] name = "owning_ref" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ff55baddef9e4ad00f88b6c743a2a8062d4c6ade126c2a528644b8e444d52ce" dependencies = [ "stable_deref_trait", ] [[package]] name = "pancurses" version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0352975c36cbacb9ee99bfb709b9db818bed43af57751797f8633649759d13db" dependencies = [ "libc", "log", "ncurses", "pdcurses-sys", "winreg", ] [[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.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-sys", ] [[package]] name = "pdcurses-sys" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "084dd22796ff60f1225d4eb6329f33afaf4c85419d51d440ab6b8c6f4529166b" dependencies = [ "cc", "libc", ] [[package]] name = "pkg-config" version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" [[package]] name = "ppv-lite86" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" [[package]] name = "pretty-bytes" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "009d6edd2c1dbf2e1c0cd48a2f7766e03498d49ada7109a01c6911815c685316" dependencies = [ "atty", "getopts", ] [[package]] name = "proc-macro2" version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" dependencies = [ "unicode-ident", ] [[package]] name = "pulldown-cmark" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d9cc634bc78768157b5cbfe988ffcd1dcba95cd2b2f03a88316c08c6d00ed63" dependencies = [ "bitflags", "memchr", "unicase", ] [[package]] name = "quote" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" 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.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" 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 = "redox_termios" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f" dependencies = [ "redox_syscall", ] [[package]] name = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "serde" version = "1.0.144" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" [[package]] name = "signal-hook" version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d" 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.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" dependencies = [ "libc", ] [[package]] name = "smallvec" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" [[package]] name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "syn" version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "term_size" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9" dependencies = [ "libc", "winapi", ] [[package]] name = "termion" version = "1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e" dependencies = [ "libc", "numtoa", "redox_syscall", "redox_termios", ] [[package]] name = "time" version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c3f9a28b618c3a6b9251b6908e9c99e04b9e5c02e6581ccbb67d59c34ef7f9b" dependencies = [ "itoa", "libc", "num_threads", ] [[package]] name = "toml" version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" dependencies = [ "serde", ] [[package]] name = "typenum" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "unicase" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" dependencies = [ "version_check 0.9.4", ] [[package]] name = "unicode-ident" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" [[package]] name = "unicode-segmentation" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" [[package]] name = "unicode-width" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" [[package]] name = "version_check" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[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.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" dependencies = [ "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_msvc" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" [[package]] name = "windows_i686_gnu" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" [[package]] name = "windows_i686_msvc" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" [[package]] name = "windows_x86_64_gnu" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" [[package]] name = "windows_x86_64_msvc" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" [[package]] name = "winreg" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a27a759395c1195c4cc5cda607ef6f8f6498f64e78f7900f5de0a127a424704a" dependencies = [ "winapi", ] [[package]] name = "xi-unicode" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a67300977d3dc3f8034dae89778f502b6ba20b269527b3223ba59c0cf393bb8a" cursive-0.20.0/Cargo.toml0000644000000047760000000000100106070ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "cursive" version = "0.20.0" authors = ["Alexandre Bury "] description = "A TUI (Text User Interface) library focused on ease-of-use." documentation = "https://docs.rs/cursive" readme = "Readme.md" keywords = [ "ncurses", "TUI", "UI", ] categories = [ "command-line-interface", "gui", ] license = "MIT" repository = "https://github.com/gyscos/cursive" resolver = "1" [package.metadata.docs.rs] all-features = true [lib] name = "cursive" [[example]] name = "theme" required-features = ["toml"] [[example]] name = "ansi" required-features = ["ansi"] [dependencies.ahash] version = "0.8" [dependencies.bear-lib-terminal] version = "2" optional = true [dependencies.cfg-if] version = "1" [dependencies.crossbeam-channel] version = "0.5" [dependencies.crossterm] version = "0.25" optional = true [dependencies.cursive_core] version = "0.3.0" [dependencies.lazy_static] version = "1" [dependencies.libc] version = "0.2" [dependencies.log] version = "0.4" [dependencies.maplit] version = "1.0" optional = true [dependencies.ncurses] version = "5.99.0" features = ["wide"] optional = true [dependencies.pancurses] version = "0.17" features = ["wide"] optional = true [dependencies.term_size] version = "0.3" optional = true [dependencies.termion] version = "1" optional = true [dependencies.unicode-segmentation] version = "1" [dependencies.unicode-width] version = "0.1" [dev-dependencies.atty] version = "0.2" [dev-dependencies.pretty-bytes] version = "0.2" [dev-dependencies.rand] version = "0.8" [features] ansi = ["cursive_core/ansi"] blt-backend = ["bear-lib-terminal"] crossterm-backend = ["crossterm"] default = ["ncurses-backend"] doc-cfg = ["cursive_core/doc-cfg"] markdown = ["cursive_core/markdown"] ncurses-backend = [ "ncurses", "maplit", "term_size", ] pancurses-backend = [ "pancurses", "maplit", "term_size", ] termion-backend = ["termion"] toml = ["cursive_core/toml"] unstable_scroll = [] [target."cfg(unix)".dependencies.signal-hook] version = "0.3" cursive-0.20.0/Cargo.toml.orig000064400000000000000000000035051046102023000142550ustar 00000000000000[package] authors = ["Alexandre Bury "] categories = ["command-line-interface", "gui"] description = "A TUI (Text User Interface) library focused on ease-of-use." documentation = "https://docs.rs/cursive" keywords = ["ncurses", "TUI", "UI"] license = "MIT" name = "cursive" readme = "Readme.md" repository = "https://github.com/gyscos/cursive" version = "0.20.0" edition = "2021" [package.metadata.docs.rs] all-features = true [dependencies] cursive_core = { path = "../cursive-core", version= "0.3.0"} crossbeam-channel = "0.5" cfg-if = "1" unicode-segmentation = "1" unicode-width = "0.1" lazy_static = "1" libc = "0.2" term_size = { version = "0.3", optional = true } maplit = { version = "1.0", optional = true } log = "0.4" ahash = "0.8" [dependencies.bear-lib-terminal] optional = true version = "2" [dependencies.ncurses] features = ["wide"] optional = true version = "5.99.0" [dependencies.pancurses] features = ["wide"] optional = true version = "0.17" [dependencies.termion] optional = true version = "1" [dependencies.crossterm] optional = true version = "0.25" [features] doc-cfg = ["cursive_core/doc-cfg"] # Enable doc_cfg, a nightly-only doc feature. blt-backend = ["bear-lib-terminal"] default = ["ncurses-backend"] ncurses-backend = ["ncurses", "maplit", "term_size"] pancurses-backend = ["pancurses", "maplit", "term_size"] termion-backend = ["termion"] crossterm-backend = ["crossterm"] markdown = ["cursive_core/markdown"] ansi = ["cursive_core/ansi"] unstable_scroll = [] # Deprecated feature, remove in next version toml = ["cursive_core/toml"] [lib] name = "cursive" [target.'cfg(unix)'.dependencies] signal-hook = "0.3" [[example]] name = "theme" required-features = ["toml"] [[example]] name = "ansi" required-features = ["ansi"] [dev-dependencies] rand = "0.8" atty = "0.2" pretty-bytes = "0.2" cursive-0.20.0/Readme.md000064400000000000000000000171021046102023000131030ustar 00000000000000# Cursive [![crates.io](https://img.shields.io/crates/v/cursive.svg)](https://crates.io/crates/cursive) [![Rust](https://github.com/gyscos/cursive/actions/workflows/rust.yml/badge.svg?branch=main)](https://github.com/gyscos/cursive/actions/workflows/rust.yml) [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE) [![Gitter chat](https://badges.gitter.im/gyscos/cursive.png)](https://gitter.im/cursive-rs/cursive) Cursive is a TUI (Text User Interface) library for rust. It uses ncurses by default, but [other backends are available](https://github.com/gyscos/cursive/wiki/Backends). It allows you to build rich user interfaces for terminal applications. # [Documentation](http://docs.rs/cursive) It is designed to be safe and easy to use: ```toml [dependencies] cursive = "0.19" ``` Or to use the latest git version: ```toml [dependencies] cursive = { git = "https://github.com/gyscos/cursive" } ``` ([You will also need ncurses installed.](https://github.com/gyscos/cursive/wiki/Install-ncurses)) ```rust,no_run use cursive::views::{Dialog, TextView}; fn main() { // Creates the cursive root - required for every application. let mut siv = cursive::default(); // Creates a dialog with a single "Quit" button siv.add_layer(Dialog::around(TextView::new("Hello Dialog!")) .title("Cursive") .button("Quit", |s| s.quit())); // Starts the event loop. siv.run(); } ``` [![Cursive dialog example](https://raw.githubusercontent.com/gyscos/cursive/main/doc/cursive_example.png)](cursive/examples/dialog.rs) Check out the other [examples](https://github.com/gyscos/cursive/tree/main/cursive/examples) to get these results, and more:
lorem.rs example menubar.rs example select.rs example mines example theme_manual.rs example syntect example
_(Colors may depend on your terminal configuration.)_ ## Tutorials These tutorials may help you get started with cursive: * [Starting with cursive: (1/3)](https://github.com/gyscos/cursive/tree/main/doc/tutorial_1.md) * [Starting with cursive: (2/3)](https://github.com/gyscos/cursive/tree/main/doc/tutorial_2.md) * [Starting with cursive: (3/3)](https://github.com/gyscos/cursive/tree/main/doc/tutorial_3.md) ## Third-party views Here are a few crates implementing new views for you to use: * [cursive-aligned-view](https://github.com/deinstapel/cursive-aligned-view): A view wrapper for gyscos/cursive views which aligns child views. * [cursive-async-view](https://github.com/deinstapel/cursive-async-view): A loading-screen wrapper. * [cursive-flexi-logger-view](https://github.com/deinstapel/cursive-flexi-logger-view): An alternative debug view using `emabee/flexi_logger`. * [cursive-markup](https://sr.ht/~ireas/cursive-markup-rs): A view that renders HTML or other markup. * [cursive-multiplex](https://github.com/deinstapel/cursive-multiplex): A tmux like multiplexer. * [cursive-spinner-view](https://github.com/otov4its/cursive-spinner-view): A spinner view. * [cursive-tabs](https://github.com/deinstapel/cursive-tabs): Tabs. * [cursive_calendar_view](https://github.com/BonsaiDen/cursive_calendar_view): A basic calendar view implementation. * [cursive_hexview](https://github.com/hellow554/cursive_hexview): A simple hexview. * [cursive_table_view](https://github.com/BonsaiDen/cursive_table_view): A basic table view component. * [cursive_tree_view](https://github.com/BonsaiDen/cursive_tree_view): A tree view implementation. ## Showcases Here are some cool applications using cursive: * [RustyChat](https://github.com/SambaDialloB/RustyChat): Chat client made using Rust and Cursive. * [clock-cli](https://github.com/TianyiShi2001/clock-cli-rs): A clock with stopwatch and countdown timer functionalities. * [fui](https://github.com/xliiv/fui): Add CLI & form interface to your program. * [git-branchless](https://github.com/arxanas/git-branchless): Branchless workflow for Git. * [grin-tui](https://github.com/mimblewimble/grin): Minimal implementation of the MimbleWimble protocol. * [kakikun](https://github.com/file-acomplaint/kakikun): A paint and ASCII art application for the terminal. * [launchk](https://github.com/mach-kernel/launchk): Manage launchd agents and daemons on macOS. * [mythra](https://github.com/deven96/mythra): CLI to search for music. * [ncspot](https://github.com/hrkfdn/ncspot): Cross-platform ncurses Spotify client. * [rbmenu-tui](https://github.com/DevHyperCoder/rbmenu-tui): A TUI for bookmark management. * [ripasso](https://github.com/cortex/ripasso): A simple password manager written in Rust. * [rusty-man](https://sr.ht/~ireas/rusty-man): Browse rustdoc documentation. * [saci-rs](https://gitlab.com/ihercowitz/saci-rs): Simple API Client Interface. * [so](https://github.com/samtay/so): A terminal interface for Stack Overflow. * [sudoku-tui](https://github.com/TianyiShi2001/sudoku-tui): Play sudoku on the command line. * [wiki-tui](https://github.com/Builditluc/wiki-tui): A simple and easy to use Wikipedia Text User Interface ## Goals * **Ease of use.** Simple apps should be simple. Complex apps should be manageable. * **Linux TTY Compatibility.** Colors may suffer, and UTF-8 may be too much, but most features *must* work properly on a Linux TTY. * **Flexibility.** This library should be able to handle simple UI scripts, complex real-time applications, or even games. * In particular, it tries to have enough features to recreate these kind of tools: * [menuconfig](http://en.wikipedia.org/wiki/Menuconfig#/media/File:Linux_x86_3.10.0-rc2_Kernel_Configuration.png) * [nmtui](https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/Networking_Guide/sec-Configure_a_Network_Team_Using_the_Text_User_Interface_nmtui.html) ## Compatibility First off, terminals are messy. A small set of features is standard, but beyond that, almost every terminal has its own implementation. ### Output * **Colors**: the basic 8-colors palette should be broadly supported. User-defined colors is not supported in the raw linux TTY, but should work in most terminals, although it's still kinda experimental. * **UTF-8**: Currently Cursive really expects a UTF-8 locale. It may eventually get patched to support window borders on other locales, but it's not a priority. There is initial support for [wide characters](https://en.wikipedia.org/wiki/CJK_characters). [RTL](https://en.wikipedia.org/wiki/Right-to-left) support [is planned](https://github.com/gyscos/cursive/issues/31), but still very early. ### Input * The `key_codes` example can be a useful tool to see how the library reacts to various key presses. * Keep in mind that if the terminal has shortcuts registered, they probably won't be transmitted to the app. * UTF-8 input should work fine in a unicode-enabled terminal emulator, but raw linux TTY may be more capricious. ## [Contributing](CONTRIBUTING.md) ## Alternatives See also [tui-rs](https://github.com/fdehau/tui-rs) - and a small [comparison page](https://github.com/gyscos/cursive/wiki/Cursive-vs-tui%E2%80%90rs). cursive-0.20.0/examples/Readme.md000064400000000000000000000057021046102023000147240ustar 00000000000000# Cursive Examples Here are example programs using Cursive to help you getting familiar with the various aspects of the library. To run an example, use `cargo run --example EXAMPLE_NAME`. To use a specific cursive backend, you can do, for example: ``` cargo run --example EXAMPLE_NAME --features crossterm-backend --no-default-features ``` ## [`hello_world`](./hello_world.rs) Simplest example possible, it will show you the starting point of a basic Cursive application. ## [`dialog`](./dialog.rs) This example wraps the text in a `Dialog` view, showing the basic idea of view composition. ## [`lorem`](./lorem.rs) This example loads a large text file to show scrolling behaviour. It also includes greek and japanese characters to show non-ascii support. ## [`edit`](./edit.rs) Here we have an `EditView` to get input from the user, and use that input in the next view. It shows how to identify a view with an name and refer to it later. ## [`mutation`](./mutation.rs) This example modifies the content of an existing view. ## [`linear`](./linear.rs) This example uses a `LinearView` to put multiple views side-by-side. ## [`menubar`](./menubar.rs) Here we learn how to create a menubar at the top of the screen, and populate it with static and dynamic entried. ## [`logs`](./logs.rs) This example defines a custom view to display asynchronous input from a channel. ## [`key_codes`](./key_codes.rs) This example uses a custom view to print any input received. Can be used as a debugging tool to see what input the application is receiving. ## [`select`](./select.rs) This example uses a `SelectView` to have the user pick a city from a long list. ## [`list_view`](./list_view.rs) This shows a use of a `ListView`, used to build simple forms. ## [`text_area`](./text_area.rs) This example uses a `TextArea`, where the user can input a block of text. ## [`markup`](./markup.rs) This example prints a text with markup decorations. ## [`theme`](./theme.rs) This loads a theme file at runtime to change default colors. ## [`theme_manual`](./theme_manual.rs) Instead of loading a theme file, this manually sets various theme settings. ## [`terminal_default`](./terminal_default.rs) This example shows the effect of the `Color::TerminalDefault` setting. ## [`colors`](./colors.rs) This example draws a colorful square to show off true color support. ## [`refcell_view`](./refcell_view.rs) Here we show how to access multiple views concurently through their name. ## [`progress`](./progress.rs) This shows how to send information from an asynchronous task (like a download or slow computation) to update a progress bar. ## [`radio`](./radio.rs) This shows how to use `RadioGroup` and `RadioButton`. ## [`slider`](./slider.rs) This is a demonstration of the `SliderView`. ## [`mines`](./mines) (**Work in progress**) A larger example showing an implementation of minesweeper. ## [`window_title`](./window_title.rs) This shows how to change the terminal window title. cursive-0.20.0/examples/advanced_user_data.rs000064400000000000000000000110301046102023000173360ustar 00000000000000use cursive::view::{Nameable, Resizable}; use cursive::views::{ Dialog, EditView, LinearLayout, ListView, RadioGroup, SliderView, }; use cursive::With; #[derive(Clone, Debug, Default)] struct UserData { boolean: bool, string: String, number: usize, } // This is an example with a more complex use of user_data. // Here we prepare some state (UserData) and make it available to be used // elsewhere via user_data. fn main() { let mut siv = cursive::default(); siv.set_user_data(UserData::default()); siv.add_layer( Dialog::text("Some stuff happens here.") .title("Main") .button("UserData", |s| { let mut boolean_group: RadioGroup = RadioGroup::new(); // Get current user_data to draw the correct current status. let current_data = s .with_user_data(|user_data: &mut UserData| { user_data.clone() }) .unwrap(); s.add_layer( Dialog::new() .title("UserData") .content( ListView::new() .child( "String: ", EditView::new() .content(current_data.string.clone()) .with_name("string") .fixed_width(18), ) .child( "Number: ", SliderView::horizontal(18) .value(current_data.number) .with_name("number"), ) .child( "Boolean: ", LinearLayout::horizontal() .child( boolean_group .button(false, "False") .fixed_width(10), ) .child( boolean_group .button(true, "True") .with_if( current_data.boolean, |button| { button.select(); }, ) .fixed_width(10), ) .with(|layout| { if current_data.boolean { layout .set_focus_index(1) .unwrap(); } }), ), ) .button("Done", move |s| { // Save selected user_data as user_data let string = s .call_on_name( "string", |view: &mut EditView| view.get_content(), ) .unwrap(); let number = s .call_on_name( "number", |view: &mut SliderView| view.get_value(), ) .unwrap(); s.with_user_data(|user_data: &mut UserData| { user_data.boolean = *boolean_group.selection(); user_data.string = string.to_string(); user_data.number = number; }) .unwrap(); s.pop_layer(); }), ); }), ); siv.run(); } cursive-0.20.0/examples/ansi.rs000064400000000000000000000010431046102023000144770ustar 00000000000000fn main() { // Start with some text content that includes ANSI codes. // Often this could be the output of another command meant for humans. let content = include_str!("text_with_ansi_codes.txt"); // Parse the content as ANSI-decorated text. let styled = cursive::utils::markup::ansi::parse(content); // Just give this to `TextView` let text_view = cursive::views::TextView::new(styled); // And make a minimal app around that. let mut siv = cursive::default(); siv.add_layer(text_view); siv.run(); } cursive-0.20.0/examples/assets/cities.txt000064400000000000000000000043721046102023000165320ustar 00000000000000Abidjan Abu Dhabi Abuja Accra Adamstown Addis Ababa Algiers Alofi Amman Amsterdam Andorra la Vella Ankara Antananarivo Apia Arbil Ashgabat Asmara Astana Asunción Athens Avarua Baghdad Baku Bamako Bandar Seri Begawan Bangkok Bangui Banjul Basseterre Beijing 北京 Beirut Belgrade Belmopan Berlin Bern Bishkek Bissau Bogotá Brasília Bratislava Brazzaville Bridgetown Brussels Bucharest Budapest Buenos Aires Bujumbura Cairo Canberra Caracas Cardiff Castries Cayenne Charlotte Amalie Chișinău Cockburn Town Colombo Conakry Copenhagen Dakar Damascus Dhaka Dili Djibouti Dodoma Doha Douglas Dublin Dushanbe Edinburgh Fort-de-France Freetown Funafuti Funchal Gaborone Garoowe Gaza Georgetown Georgetown George Town Gibraltar Grozny Guatemala City Hagatna Hamilton Hanoi Harare Hargeisa Havana Helsinki Honiara Islamabad Jakarta Jamestown Jerusalem Jerusalem Kabul Kampala Kathmandu Khartoum Kiev Kigali Kilinochchi Kingston Kingston Kingstown Kinshasa Kuala Lumpur Kuwait City La Paz Las Palmas Libreville Lilongwe Lima Lisbon Ljubljana Lomé London Luanda Lusaka Luxembourg Madrid Majuro Malabo Malé Mamoudzou Managua Manama Manila Maputo Maseru Mata-Utu Mbabane Melekeok Mexico City Minsk Mogadishu Monaco Monrovia Montevideo Moroni Moscow Muscat Nairobi Nassau Naypyidaw N'Djamena New Delhi Niamey Nicosia none Nouakchott Nouméa Nuku'alofa Nuuk Oranjestad Oslo Ottawa Ouagadougou Pago Pago Palikir Panama City Papeete Paramaribo Paris Phnom Penh Plymouth Podgorica Ponta Delgada Port-au-Prince Port Louis Port Moresby Port of Spain Porto-Novo Port Vila Prague Praia Pretoria Priština Putrajaya Pyongyang Quito Rabat Ramallah Reykjavík Riga Riyadh Road Town Rome Roseau Saint-Denis Saint Helier Saint-Pierre Saipan Sanaa San Juan San Marino San Salvador Santiago Sant José Santo Domingo São Tomé Sarajevo Seoul Singapore Skopje Sofia Stanley Stepanakert St. George's St. John's Stockholm St Peter Port Sucre Sukhumi Suva Taipei Tallinn Tarawa Tashkent Tbilisi Tegucigalpa Tehran The Settlement The Valley Thimphu Tirana Tiraspol Tokyo 東京 Tórshavn Tripoli Tskhinvali Tunis Ulaanbaatar Vaduz Valletta Valparaíso Vatican City Victoria Vienna Vientiane Vilnius Vitoria-Gasteiz Warsaw Washington, D.C. Wellington West Island Willemstad Windhoek Yamoussoukro Yaoundé Yaren Yerevan Zagreb cursive-0.20.0/examples/assets/lorem.txt000064400000000000000000000362341046102023000163720ustar 00000000000000Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus sit amet porttitor ex. Vivamus ultricies nec massa eget fringilla. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Duis et luctus odio. Phasellus et lacus dolor. Praesent ut nulla quis magna auctor consequat ornare quis orci. Nam quis tempus massa. Mauris cursus interdum risus et vulputate. Nunc et scelerisque tellus, vitae condimentum nulla. Suspendisse tincidunt ipsum sit amet eros auctor ultricies. Vestibulum non diam quam. In sem nunc, gravida sagittis rutrum eget, tincidunt ut mi. Vivamus quam quam, mattis sed rutrum quis, rhoncus ac enim. Aliquam rutrum ac ligula et pellentesque. Donec consequat, nisi eget volutpat gravida, velit sem dapibus libero, vel hendrerit mi lorem et risus. Nunc iaculis convallis bibendum. Morbi sit amet lectus rhoncus, euismod tortor eu, cursus justo. Quisque non sagittis sapien. Fusce ornare scelerisque ante a egestas. Nam quis nulla nunc. Ut lacinia eleifend leo eu accumsan. Aenean et ex quis velit accumsan fringilla. Praesent lobortis nisl augue, non sollicitudin magna tempus a. Donec varius lorem justo, sed fermentum sem tempor non. Sed varius blandit urna, sed sodales est porttitor ac. Nunc sollicitudin consequat sodales. Aliquam et auctor nulla. Fusce sit amet risus gravida, porta eros eget, luctus eros. In eu malesuada metus, eu tempus libero. Phasellus sit amet nibh bibendum, faucibus est in, luctus turpis. Sed lobortis eget massa a facilisis. Cras venenatis, nulla ut tempor ornare, erat velit tempus leo, eget sodales libero nibh non nisl. Mauris eget fringilla mi. Vivamus accumsan non erat ut malesuada. Duis vehicula cursus ex. Nam quis lorem non dolor luctus tristique. Fusce et sapien egestas, fringilla nulla et, ullamcorper quam. Etiam vel tellus erat. Vestibulum aliquet vulputate erat in elementum. Sed tempor aliquam sollicitudin. Aenean porttitor justo et lorem pharetra molestie. Ut neque erat, aliquet eget auctor in, congue at augue. Suspendisse eget metus lorem. Mauris ex neque, luctus sed mauris sed, ultricies ultrices tellus. Vestibulum sit amet porttitor ante, eu lacinia urna. Nulla nec tristique nisi. Morbi sodales risus eu tincidunt iaculis. Donec fringilla est odio, sit amet aliquet neque finibus non. Pellentesque interdum ante eget orci sagittis, ut pulvinar nulla faucibus. Fusce aliquam id ipsum luctus vehicula. Ut vitae dui a nisi aliquam vestibulum id vitae nunc. Nulla et sodales urna. Curabitur lectus mi, lobortis vitae rhoncus at, blandit eu dui. Maecenas nisl ante, faucibus tristique laoreet sed, blandit at justo. Mauris id elementum ex. Donec sit amet volutpat turpis. Phasellus a congue urna. Pellentesque nec commodo libero. Fusce dapibus egestas suscipit. Vivamus sed mattis libero, a tristique nulla. Vivamus nec dui eget nibh eleifend imperdiet. Curabitur efficitur egestas sapien, ut tempus nulla mattis sit amet. Duis molestie aliquam metus et eleifend. Quisque in vestibulum purus. Curabitur maximus lorem a nisl cursus convallis. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Praesent dui lectus, commodo eget nulla ut, maximus facilisis ligula. Aliquam ultricies augue ac pellentesque pellentesque. Nunc maximus euismod dolor, ac dignissim metus facilisis non. Aliquam feugiat dui sed tortor vehicula, a suscipit odio posuere. In et volutpat mi, non rhoncus diam. Donec interdum sem justo, id aliquet elit ornare at. Nunc vel blandit lectus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed posuere ipsum, nec mattis nisl. In risus nunc, volutpat at faucibus id, rhoncus sit amet tortor. Sed porta pellentesque nibh in ornare. Nam porttitor tortor nec risus bibendum, et sodales nibh faucibus. Aliquam erat volutpat. Maecenas laoreet vestibulum purus, pretium tristique velit elementum nec. Sed at nulla sed lorem tincidunt mattis quis ac lorem. Nunc placerat gravida congue. Etiam diam neque, dictum in eros ac, lacinia gravida orci. Cras in nisi augue. Fusce condimentum vestibulum nisl convallis lacinia. Mauris ligula diam, facilisis quis nulla ut, luctus feugiat eros. Duis ac consequat nisl. Nulla facilisi. Integer euismod mauris a feugiat gravida. Nulla consectetur est vitae lectus semper porttitor. Cras pellentesque tincidunt lacus, id sagittis lectus tincidunt eu. Mauris pellentesque lobortis aliquet. Mauris nec est bibendum, cursus metus eget, maximus nunc. Donec fermentum eros quis dolor imperdiet accumsan. Sed vitae rhoncus velit. Proin eu luctus libero. Interdum et malesuada fames ac ante ipsum primis in faucibus. Aenean imperdiet diam vitae eros vehicula mattis. Sed auctor erat et sapien mattis, sit amet faucibus risus tempus. Praesent ut commodo dui. Pellentesque et sodales purus. Nullam mollis sed urna eget aliquet. Μελιορε ιμπερδιετ αδ φιξ, δωλορε θριθανι ασυσαμυς ευ πρι. Ευ ναμ αδχυς σριπτορεμ. Δισερετ ωπωρθεαθ δεφινιτιωνες υθ σεα, φιδιτ φιθαε κυαλισκυε ετ εσθ, ιυς δισπυθανδο ινστρυσθιορ εα. Νε σεδ ρεγιονε οσυρρερεθ. Ατ συμο υλλυμ ηομερω φελ, αεκυε πραεσενθ ρεπυδιαρε ιυς αν. Εσθ δισπυθανδο μεδιοσριθαθεμ ιν, φις ευ δισιθ αυδιαμ μενθιτυμ. Νο περ δισαμ τρασθατος, ιδ ιγνωθα ποπυλω ειρμωδ ιυς, ιδ δισαντ δεσερυισε μεα. Αδ παρτεμ φεριθυς πρω. Φιδιτ δοσθυς ρεπρεχενδυντ νες νο, ιλλυδ αθκυι εξπλισαρι υθ εως, πρω ερος φαλλι ινιμισυς νο. Νο φιμ νυλλα σεντεντιαε συσιπιαντυρ, αδ νοσθρω ιμπεδιτ φασιλις σεδ, αδ ευμ δισο προμπτα ιμπερδιετ. Ιδ ηις μαγνα ωμιτθαμ θχεωπηραστυς, οδιο παθριοκυε ηας ιν, εξ ιυς ινθεγρε αλθερυμ περσεσυτι. Ιδ ανιμαλ τεμποριβυς δεφινιτιωνες φιξ. Σεδ ευ πλασεραθ σωνσεπθαμ, αδ κυωδ ειυς σομπλεσθιθυρ ιυς, εσεντ δολορες ετ κυι. Νο σεδ σολετ σωνσεθεθυρ αδιπισινγ. Νο σιθ φαστιδιι περφεσθο σαπιενθεμ, εα ινερμις ινδοστυμ δισεντιας φελ, υσυ ει υθροκυε μαλυισετ. Μει κυαεστιο μνεσαρσχυμ νο. Εα δομινγ αεθερνο αλθερυμ δυο, ευ σεα σασε μολλις, λωρεμ ωμνιυμ ιισκυε σεδ νε. Μεα θε δισαθ φαλλι οφφισιις. Ναμ δυις ιριυρε αδ. Φασιλις σεθερος αλβυσιυς ατ μει, μελ υθ ρεπυδιαρε ασεντιορ ινσιδεριντ. Σαεπε πρωβατυς ηις αν, φευγιαθ υλλαμσορπερ ατ μει, ει κυο ριδενς αππαρεατ διγνισιμ. Αλιενυμ ελαβοραρετ σονσλυσιονεμκυε κυι αν, παυλω μεδιοσρεμ υσυ συ, συ γραεσι λυπταθυμ σωνσεκυαθ σιθ. Σιθ θε ηαβεο φοσιβυς σονστιθυαμ, περ ετ φιθαε εξερσι μολλις. Ναμ αθκυι αυδιαμ αδ. Ευ κυαεστιο φυλπυτατε εσθ. Υθ σολυτα σομμοδο δετρασθο δυο, εα σιθ δελισατα σωνσεπθαμ. Ευμ υτιναμ ασομμοδαρε ει, σολεατ ιριυρε ιν συμ. Φιδερερ σεθερος δεφινιεβας ιυς θε. Δεσωρε πωσιθ φιφενδυμ εα φιμ, κυωδ σονσυλατυ σοτιδιεκυε φελ αδ. Ιδ ναμ δισαθ ταντας, δισαντ φασιλις ηις υθ. Κυεμ εραντ δισερε σεδ ει, εα φιρθυθε ωπωρθεαθ σενσεριτ ευμ. Μελ θαλε αππελλανθυρ ιδ. Ιδ κυι σαλε περσιπιθ. Ιδ σεδ λαβιθυρ σωνσεκυαθ φορενσιβυς, εξ στετ δελενιτι σιθ. Αδ μοδυς νοφυμ σωνσλυδαθυρκυε συμ, φιμ ευ χινς δεβετ, φιξ ιν αλια ριδενς παθριοκυε. Δυο απεριαμ σομπλεσθιθυρ ιδ, νο μανδαμυς παρτιενδω συμ, φιδιτ λιβρις φαβυλας μεα νε. Θαλε μυτατ πωνδερυμ ευ περ, ριδενς οπωρθερε περ αδ, εα φασερ ερρωρ πρω. Υσυ ατ φελιτ λαορεεθ. Μεα πορρω επισυρι εξπλισαρι ιδ, βωνορυμ δολωρυμ νυσκυαμ εαμ αν, παυλω λυσιλιυς ερροριβυς μεα υθ. Ει φελ αμετ νιηιλ σριπτορεμ, σοπιωσαε ινφενιρε σεα νο. Νε σολεατ περπετυα υσυ. Εα πρω δελεσθυς θχεωπηραστυς, θαθιων σεθερος φις θε, ιυς ετ σαεπε φολυτπατ μαιεσθατις. Ιυς ιν διαμ μαλυισετ σορρυμπιθ. Οδιο σπλενδιδε σιθ ατ, νες εραθ λατινε ατ. Νες εξ ποσιμ δεσερυισε. Ευμ εξ τιβικυε νωμιναφι αππαρεατ, ωφφενδιθ ευριπιδις ιδ ιυς, σιθ σαεπε μολεστιε εα. Φις χαρυμ κυοδσι ευ. Περτινασια λιβεραφισε σεα ιδ. Φιξ ηαβεο εραντ φιθαε υθ, δολορ δοσθυς ιν κυι, φιξ ατ σινθ σεμπερ δεφινιθιονεμ. Ταντας νομινατι σωνσεθεθυρ ει ναμ. Ιδ φιξ πωσε λαβιθυρ νομινατι, πρινσιπες ιρασυνδια υσυ υθ, ποπυλω σονσλυσιονεμκυε φελ ιν. Αλικυανδο νεσεσιταθιβυς μελ θε, συμ ετ πριμα ρεφορμιδανς, κυοδσι περσεκυερις ετ νες. Εκυιδεμ φιφενδω πρωβατυς ηας ετ. Νε σεδ ταντας σολυτα ανιμαλ. 私は十一月きっとそうした注意通りという事の時が云ったませ。 けっしてその間を話地ももうこの話なななどのなっていますには自失しですませて、ちょっとにはしなましでだ。がたが解らたのはもう今日をほとんどたますましょ。ちょうど大森さんをふり徳義また指導からしない尻馬その博奕私か返事からというご反対ですたましですが、どんな将来はそれか去就自己からつかて、大森さんの方へ我の私に勢いご矛盾と炙っがこれ三つにお危くにあっようによくご任命になっますんて、ようやくことに入会がするたて行くますものをなくなっですでし。だからだからごいくらをしのはこう低級と掘りうで、その人をは突き破るなてという英語にやっば下さいたです。どんな一方がたの時その個人も彼ら中に上げでかと木下さんを返っでん、相手の直接ですというご焦燥うでたけれども、本立の後が他人が当時かもの壁がたくさんおくからみるて、ずいぶんの以前に見えるてそんなついでにもちろん比べるうでとなるたらのましし、ないたですて全くお漫然考えでのなですまし。 そうして個人か新たか留学がするますて、昨日中嚢をしからならた後がご参考の事実をかかるだまし。今にはたしか飽いが云いありないだならて、もしちゃんとせよと妨害もとてもありがたいませはずなら。しかし肝#「を云っては来ならものましば、教場では、けっしてあなたかさからなりれるないたいうられるたでと見えるて、別はもつてもらいたなけれ。もしとうていはいよいよ同年輩といういるたて、私からも将来上ぐらいあなたのお意味はなく立てるいるなけれな。 彼らはもし享有の点からご話はあるているですだたでて、四二の比喩にどう会っましといった教育だろて、しかしながらその申の文章に云われば、あなたかを私の権力を影響をしてくれたものないないと評見るて独立起し得るんなけれ。根柢にところが岡田さんにそうして少々なるあり点まいましです。大森君はたった主義にあるておくたらのませであっ。 (だから平気に当るためありずでけれどもたは合ううますて、)さらにありゃまし先を、三井の順々でも移ろて食わせろという、学校の経験は事実のところなど移ろある事にさないて参考方読むているでというご不行届で気でしょ。ここはいかに精神でするたようにしからいけますのながまたもともと吉利自分現われたた。すなわち実際一円は兄をして、前にとうていきまっないなと合って、ないですたばしかしご病気を用いれたなけれ。 外国の今を、こうした人間に今日を云いでも、時間上を当然すべて二一一行で考えじゃの自分に、どこか叱らまし専攻にありでほかはもとより立てるられものますて、とうとう当然生徒にやかましいから、そのものに考えものに貧乏んないありゃなな。しかしようやく当時三二二円に行かくらいはさたに対する自由たお話が罹っと、権力でその時その所を掴むていうのです。 同時にに会に教場くれた万一篇平生に考えば、私かよっながみですという点がすぐなりでしょのなけれて、はなはだ持っものが心丈夫たから、どうも呑にしからしながらおきありでしょ。自分を出れと行くてこれか若い事で行くようにありかも断わらましありて、そうして仕方も若い事に云わて、それより秋刀魚でしいるて三年が三年も万人もさきほど進まていくらいならものた。 事実なますかし先生がするて、そんな英語は十分えらい重宝なくとしなくのたも行けれだない、若いがたのためが説きませ人た生きとしていたのますた。例えば私も結構でてするでしょものなは面白い、心的でしょから考えたのなと上って私の本位のいくらがどんな方角が留学してなりたた。腹にも上手ます常に待っからなられれた結果に個人に食わせと、余裕になりとか、だから左に儲けたりしよ理由に致し筋、正直ですので、同時に気に入らて広い遅まきへすありと示そから、教師へもっと時代ほど自分までに計ら取消はさらしく。 また不思議にはその主義の変責任に場合にあります所に思うからじっと通用眺めるからくる時間になっのまい。もっとも私はこのところに来できるのです、お話の金力に説明するた仕方でも云うなましと大きくはあるました。 ことにそれはその不愉快です申と与えかもない、尊重の先生にまるで与えでに考えていでのだろ。 cursive-0.20.0/examples/assets/style.toml000064400000000000000000000023221046102023000165370ustar 00000000000000# Every field in a theme file is optional. shadow = false borders = "outset" # Alternatives are "none" and "simple" # Base colors are red, green, blue, # cyan, magenta, yellow, white and black. [colors] # There are 3 ways to select a color: # - The 16 base colors are selected by name: # "blue", "light red", "magenta", ... # - Low-resolution colors use 3 characters, each <= 5: # "541", "003", ... # - Full-resolution colors start with '#' and can be 3 or 6 hex digits: # "#1A6", "#123456", ... # If the value is an array, the first valid # and supported color will be used. background = ["#cdf6cd", "454", "magenta"] # If the terminal doesn't support custom color (like the linux TTY), # non-base colors will be skipped. shadow = ["#222288", "blue"] view = "111" # An array with a single value has the same effect as a simple value. primary = ["white"] secondary = "#EEEEEE" tertiary = "#252521" # Hex values can use lower or uppercase. # (base color MUST be lowercase) title_primary = ["BLUE", "yellow"] # `BLUE` will be skipped. title_secondary = "#ffff55" # Lower precision values can use only 3 digits. highlight = "#F88" highlight_inactive = "#5555FF" cursive-0.20.0/examples/autocomplete.rs000064400000000000000000000061551046102023000162570ustar 00000000000000use cursive::align::HAlign; use cursive::traits::Scrollable; use cursive::view::{Nameable, Resizable}; use cursive::views::{Dialog, EditView, LinearLayout, SelectView, TextView}; use cursive::Cursive; use lazy_static::lazy_static; // This example shows a way to implement a (Google-like) autocomplete search box. // Try entering "tok"! lazy_static! { static ref CITIES: &'static str = include_str!("assets/cities.txt"); } fn main() { let mut siv = cursive::default(); siv.add_layer( Dialog::around( LinearLayout::vertical() // the query box is on the top .child( EditView::new() // update results every time the query changes .on_edit(on_edit) // submit the focused (first) item of the matches .on_submit(on_submit) .with_name("query"), ) // search results below the input .child( SelectView::new() // shows all cities by default .with_all_str(CITIES.lines()) // Sets the callback for when "Enter" is pressed. .on_submit(show_next_window) // Center the text horizontally .h_align(HAlign::Center) .with_name("matches") .scrollable(), ) .fixed_height(10), ) .button("Quit", Cursive::quit) .title("Where are you from?"), ); siv.run(); } // Update results according to the query fn on_edit(siv: &mut Cursive, query: &str, _cursor: usize) { let matches = search_fn(CITIES.lines(), query); // Update the `matches` view with the filtered array of cities siv.call_on_name("matches", |v: &mut SelectView| { v.clear(); v.add_all_str(matches); }); } // Filter cities with names containing query string. You can implement your own logic here! fn search_fn<'a, 'b, T: std::iter::IntoIterator>( items: T, query: &'b str, ) -> Vec<&'a str> { items .into_iter() .filter(|&item| { let item = item.to_lowercase(); let query = query.to_lowercase(); item.contains(&query) }) .collect() } fn on_submit(siv: &mut Cursive, query: &str) { let matches = siv.find_name::("matches").unwrap(); if matches.is_empty() { // not all people live in big cities. If none of the cities in the list matches, use the value of the query. show_next_window(siv, query); } else { // pressing "Enter" without moving the focus into the `matches` view will submit the first match result let city = &*matches.selection().unwrap(); show_next_window(siv, city); }; } fn show_next_window(siv: &mut Cursive, city: &str) { siv.pop_layer(); let text = format!("{} is a great city!", city); siv.add_layer( Dialog::around(TextView::new(text)).button("Quit", |s| s.quit()), ); } cursive-0.20.0/examples/colors.rs000064400000000000000000000041701046102023000150520ustar 00000000000000use cursive::theme::{Color, ColorStyle}; use cursive::view::Resizable; use cursive::views::Canvas; use cursive::Printer; // This example will draw a colored square with a gradient. // // We'll use a Canvas, which lets us only define a draw method. // // We will combine 2 gradients: one for the background, // and one for the foreground. // // Note: color reproduction is not as good on all backends. // termion can do full 16M true colors, but ncurses is currently limited to // 256 colors. fn main() { // Start as usual let mut siv = cursive::default(); siv.add_global_callback('q', |s| s.quit()); // Canvas lets us easily override any method. // Canvas can have states, but we don't need any here, so we use `()`. siv.add_layer(Canvas::new(()).with_draw(draw).fixed_size((20, 10))); siv.run(); } /// Method used to draw the cube. /// /// This takes as input the Canvas state and a printer. fn draw(_: &(), p: &Printer) { // We use the view size to calibrate the color let x_max = p.size.x as u8; let y_max = p.size.y as u8; // Print each cell individually for x in 0..x_max { for y in 0..y_max { // We'll use a different style for each cell let style = ColorStyle::new( front_color(x, y, x_max, y_max), back_color(x, y, x_max, y_max), ); p.with_color(style, |printer| { printer.print((x, y), "+"); }); } } } // Gradient for the front color fn front_color(x: u8, y: u8, x_max: u8, y_max: u8) -> Color { // We return a full 24-bits RGB color, but some backends // will project it to a 256-colors palette. Color::Rgb( x * (255 / x_max), y * (255 / y_max), (x + 2 * y) * (255 / (x_max + 2 * y_max)), ) } // Gradient for the background color fn back_color(x: u8, y: u8, x_max: u8, y_max: u8) -> Color { // Let's try to have a gradient in a different direction than the front color. Color::Rgb( 128 + (2 * y_max + x - 2 * y) * (128 / (x_max + 2 * y_max)), 255 - y * (255 / y_max), 255 - x * (255 / x_max), ) } cursive-0.20.0/examples/ctrl_c.rs000064400000000000000000000010171046102023000150140ustar 00000000000000use cursive::views; fn main() { let mut siv = cursive::default(); siv.clear_global_callbacks(cursive::event::Event::CtrlChar('c')); siv.set_on_pre_event(cursive::event::Event::CtrlChar('c'), |s| { s.add_layer( views::Dialog::text("Do you want to quit?") .button("Yes", |s| s.quit()) .button("No", |s| { s.pop_layer(); }), ); }); siv.add_layer(views::Dialog::text("Try pressing Ctrl-C!")); siv.run(); } cursive-0.20.0/examples/debug_console.rs000064400000000000000000000012301046102023000163530ustar 00000000000000fn main() { // Initialize the cursive logger. cursive::logger::init(); // Use some logging macros from the `log` crate. log::error!("Something serious probably happened!"); log::warn!("Or did it?"); log::debug!("Logger initialized."); log::info!("Starting!"); let mut siv = cursive::default(); siv.add_layer(cursive::views::Dialog::text("Press ~ to open the console.\nPress l to generate logs.\nPress q to quit.")); siv.add_global_callback('q', cursive::Cursive::quit); siv.add_global_callback('~', cursive::Cursive::toggle_debug_console); siv.add_global_callback('l', |_| log::trace!("Wooo")); siv.run(); } cursive-0.20.0/examples/dialog.rs000064400000000000000000000011451046102023000150070ustar 00000000000000use cursive::{ views::{CircularFocus, Dialog, TextView}, With as _, }; fn main() { // Creates the cursive root - required for every application. let mut siv = cursive::default(); // Creates a dialog with a single "Quit" button siv.add_layer( // Most views can be configured in a chainable way Dialog::around(TextView::new("Hello Dialog!")) .title("Cursive") .button("Foo", |_s| ()) .button("Quit", |s| s.quit()) .wrap_with(CircularFocus::new) .wrap_tab(), ); // Starts the event loop. siv.run(); } cursive-0.20.0/examples/edit.rs000064400000000000000000000041501046102023000144740ustar 00000000000000use cursive::traits::*; use cursive::views::{Dialog, EditView, TextView}; use cursive::Cursive; fn main() { let mut siv = cursive::default(); // Create a dialog with an edit text and a button. // The user can either hit the button, // or press Enter on the edit text. siv.add_layer( Dialog::new() .title("Enter your name") // Padding is (left, right, top, bottom) .padding_lrtb(1, 1, 1, 0) .content( EditView::new() // Call `show_popup` when the user presses `Enter` .on_submit(show_popup) // Give the `EditView` a name so we can refer to it later. .with_name("name") // Wrap this in a `ResizedView` with a fixed width. // Do this _after_ `with_name` or the name will point to the // `ResizedView` instead of `EditView`! .fixed_width(20), ) .button("Ok", |s| { // This will run the given closure, *ONLY* if a view with the // correct type and the given name is found. let name = s .call_on_name("name", |view: &mut EditView| { // We can return content from the closure! view.get_content() }) .unwrap(); // Run the next step show_popup(s, &name); }), ); siv.run(); } // This will replace the current layer with a new popup. // If the name is empty, we'll show an error message instead. fn show_popup(s: &mut Cursive, name: &str) { if name.is_empty() { // Try again as many times as we need! s.add_layer(Dialog::info("Please enter a name!")); } else { let content = format!("Hello {}!", name); // Remove the initial popup s.pop_layer(); // And put a new one instead s.add_layer( Dialog::around(TextView::new(content)) .button("Quit", |s| s.quit()), ); } } cursive-0.20.0/examples/fixed_layout.rs000064400000000000000000000013601046102023000162430ustar 00000000000000use cursive::{ views::{Button, FixedLayout, TextView}, Rect, }; fn main() { let mut siv = cursive::default(); siv.add_layer( cursive::views::Dialog::around( FixedLayout::new() .child(Rect::from_size((0, 0), (1, 1)), TextView::new("/")) .child(Rect::from_size((14, 0), (1, 1)), TextView::new(r"\")) .child(Rect::from_size((0, 2), (1, 1)), TextView::new(r"\")) .child(Rect::from_size((14, 2), (1, 1)), TextView::new("/")) .child( Rect::from_size((2, 1), (11, 1)), Button::new("Click me!", |s| s.quit()), ), ) .button("Quit", |s| s.quit()), ); siv.run(); } cursive-0.20.0/examples/focus.rs000064400000000000000000000030561046102023000146720ustar 00000000000000use cursive::traits::*; fn main() { let mut siv = cursive::default(); siv.add_layer( cursive::views::Dialog::new().content( cursive::views::LinearLayout::vertical() .child( cursive::views::TextView::new("Focused").with_name("text"), ) .child( cursive::views::EditView::new() .wrap_with(cursive::views::FocusTracker::new) .on_focus(|_| { cursive::event::EventResult::with_cb(|s| { s.call_on_name( "text", |v: &mut cursive::views::TextView| { v.set_content("Focused"); }, ); }) }) .on_focus_lost(|_| { cursive::event::EventResult::with_cb(|s| { s.call_on_name( "text", |v: &mut cursive::views::TextView| { v.set_content("Focus lost"); }, ); }) }), ) .child(cursive::views::Button::new("Quit", |s| s.quit())) .fixed_width(20), ), ); siv.run(); } cursive-0.20.0/examples/hello_world.rs000064400000000000000000000005601046102023000160620ustar 00000000000000use cursive::views::TextView; use cursive::Cursive; fn main() { let mut siv = cursive::default(); // We can quit by pressing `q` siv.add_global_callback('q', Cursive::quit); // Add a simple view siv.add_layer(TextView::new( "Hello World!\n\ Press q to quit the application.", )); // Run the event loop siv.run(); } cursive-0.20.0/examples/key_codes.rs000064400000000000000000000025541046102023000155220ustar 00000000000000use cursive::event::{Event, EventResult}; use cursive::traits::*; use cursive::Printer; // This example define a custom view that prints any event it receives. // This is a handy way to check the input received by cursive. fn main() { let mut siv = cursive::default(); siv.add_layer(KeyCodeView::new(10).full_width().fixed_height(10)); siv.run(); } // Our view will have a small history of the last events. struct KeyCodeView { history: Vec, size: usize, } impl KeyCodeView { fn new(size: usize) -> Self { KeyCodeView { history: Vec::new(), size, } } } // Let's implement the `View` trait. // `View` contains many methods, but only a few are required. impl View for KeyCodeView { fn draw(&self, printer: &Printer) { // We simply draw every event from the history. for (y, line) in self.history.iter().enumerate() { printer.print((0, y), line); } } fn on_event(&mut self, event: Event) -> EventResult { // Each line will be a debug-format of the event. let line = format!("{:?}", event); self.history.push(line); // Keep a fixed-sized history. while self.history.len() > self.size { self.history.remove(0); } // No need to return any callback. EventResult::Consumed(None) } } cursive-0.20.0/examples/linear.rs000064400000000000000000000025141046102023000150230ustar 00000000000000use cursive::align::HAlign; use cursive::traits::*; use cursive::views::{Dialog, DummyView, LinearLayout, TextView}; // This example uses a LinearLayout to stick multiple views next to each other. fn main() { let mut siv = cursive::default(); // Some description text. We want it to be long, but not _too_ long. let text = "This is a very simple example of linear layout. Two views \ are present, a short title above, and this text. The text \ has a fixed width, and the title is centered horizontally."; // We'll create a dialog with a TextView serving as a title siv.add_layer( Dialog::around( LinearLayout::vertical() .child(TextView::new("Title").h_align(HAlign::Center)) // Use a DummyView as spacer .child(DummyView.fixed_height(1)) // Disabling scrollable means the view cannot shrink. .child(TextView::new(text)) // The other views will share the remaining space. .child(TextView::new(text).scrollable()) .child(TextView::new(text).scrollable()) .child(TextView::new(text).scrollable()) .fixed_width(30), ) .button("Quit", |s| s.quit()) .h_align(HAlign::Center), ); siv.run(); } cursive-0.20.0/examples/list_view.rs000064400000000000000000000062331046102023000155600ustar 00000000000000use cursive::{ traits::*, views::{ Checkbox, Dialog, EditView, LinearLayout, ListView, SelectView, TextArea, TextView, }, }; // This example uses a ListView. // // ListView can be used to build forms, with a list of inputs. fn main() { let mut siv = cursive::default(); siv.add_layer( Dialog::new() .title("Please fill out this form") .button("Ok", |s| s.quit()) .content( ListView::new() // Each child is a single-line view with a label .child("Name", EditView::new().fixed_width(10)) .child("Presentation", TextArea::new().min_height(4)) .child( "Receive spam?", Checkbox::new().on_change(|s, checked| { // Enable/Disable the next field depending on this checkbox for name in &["email1", "email2"] { s.call_on_name(name, |view: &mut EditView| { view.set_enabled(checked) }); if checked { s.focus_name("email1").unwrap(); } } }), ) .child( "Email", // Each child must have a height of 1 line, // but we can still combine multiple views! LinearLayout::horizontal() .child( EditView::new() .disabled() .with_name("email1") .fixed_width(15), ) .child(TextView::new("@")) .child( EditView::new() .disabled() .with_name("email2") .fixed_width(10), ), ) // Delimiter currently are just a blank line .delimiter() .child( "Age", // Popup-mode SelectView are small enough to fit here SelectView::new() .popup() .item_str("0-18") .item_str("19-30") .item_str("31-40") .item_str("41+"), ) .with(|list| { // We can also add children procedurally for i in 0..50 { list.add_child( &format!("Item {}", i), EditView::new(), ); } }) .scrollable(), ), ); siv.run(); } cursive-0.20.0/examples/logs.rs000064400000000000000000000052451046102023000145210ustar 00000000000000use cursive::traits::*; use cursive::Vec2; use cursive::{Cursive, Printer}; use std::collections::VecDeque; use std::sync::mpsc; use std::thread; use std::time::Duration; // This example will print a stream of logs generated from a separate thread. // // We will use a custom view using a channel to receive data asynchronously. fn main() { // As usual, create the Cursive root let mut siv = cursive::default(); let cb_sink = siv.cb_sink().clone(); // We want to refresh the page even when no input is given. siv.add_global_callback('q', |s| s.quit()); // A channel will communicate data from our running task to the UI. let (tx, rx) = mpsc::channel(); // Generate data in a separate thread. thread::spawn(move || { generate_logs(&tx, cb_sink); }); // And sets the view to read from the other end of the channel. siv.add_layer(BufferView::new(200, rx).full_screen()); siv.run(); } // We will only simulate log generation here. // In real life, this may come from a running task, a separate process, ... fn generate_logs(tx: &mpsc::Sender, cb_sink: cursive::CbSink) { let mut i = 1; loop { let line = format!("Interesting log line {}", i); i += 1; // The send will fail when the other side is dropped. // (When the application ends). if tx.send(line).is_err() { return; } cb_sink.send(Box::new(Cursive::noop)).unwrap(); thread::sleep(Duration::from_millis(30)); } } // Let's define a buffer view, that shows the last lines from a stream. struct BufferView { // We'll use a ring buffer buffer: VecDeque, // Receiving end of the stream rx: mpsc::Receiver, } impl BufferView { // Creates a new view with the given buffer size fn new(size: usize, rx: mpsc::Receiver) -> Self { let mut buffer = VecDeque::new(); buffer.resize(size, String::new()); BufferView { buffer, rx } } // Reads available data from the stream into the buffer fn update(&mut self) { // Add each available line to the end of the buffer. while let Ok(line) = self.rx.try_recv() { self.buffer.push_back(line); self.buffer.pop_front(); } } } impl View for BufferView { fn layout(&mut self, _: Vec2) { // Before drawing, we'll want to update the buffer self.update(); } fn draw(&self, printer: &Printer) { // Print the end of the buffer for (i, line) in self.buffer.iter().rev().take(printer.size.y).enumerate() { printer.print((0, printer.size.y - 1 - i), line); } } } cursive-0.20.0/examples/lorem.rs000064400000000000000000000035521046102023000146720ustar 00000000000000use cursive::{ align::HAlign, event::{EventResult, Key}, traits::With, view::{scroll::Scroller, Scrollable}, views::{Dialog, OnEventView, Panel, TextView}, }; fn main() { // Read some long text from a file. let content = include_str!("assets/lorem.txt"); let mut siv = cursive::default(); // We can quit by pressing q siv.add_global_callback('q', |s| s.quit()); // The text is too long to fit on a line, so the view will wrap lines, // and will adapt to the terminal size. siv.add_fullscreen_layer( Dialog::around(Panel::new( TextView::new(content) .scrollable() .wrap_with(OnEventView::new) .on_pre_event_inner(Key::PageUp, |v, _| { let scroller = v.get_scroller_mut(); if scroller.can_scroll_up() { scroller.scroll_up( scroller.last_outer_size().y.saturating_sub(1), ); } Some(EventResult::Consumed(None)) }) .on_pre_event_inner(Key::PageDown, |v, _| { let scroller = v.get_scroller_mut(); if scroller.can_scroll_down() { scroller.scroll_down( scroller.last_outer_size().y.saturating_sub(1), ); } Some(EventResult::Consumed(None)) }), )) .title("Unicode and wide-character support") // This is the alignment for the button .h_align(HAlign::Center) .button("Quit", |s| s.quit()), ); // Show a popup on top of the view. siv.add_layer(Dialog::info( "Try resizing the terminal!\n(Press 'q' to \ quit when you're done.)", )); siv.run(); } cursive-0.20.0/examples/markup.rs000064400000000000000000000013031046102023000150430ustar 00000000000000use cursive::theme::BaseColor; use cursive::theme::Color; use cursive::theme::Effect; use cursive::theme::Style; use cursive::utils::markup::StyledString; use cursive::views::{Dialog, TextView}; fn main() { let mut siv = cursive::default(); let mut styled = StyledString::plain("Isn't "); styled.append(StyledString::styled("that ", Color::Dark(BaseColor::Red))); styled.append(StyledString::styled( "cool?", Style::from(Color::Light(BaseColor::Blue)).combine(Effect::Bold), )); // TextView can natively accept StyledString. siv.add_layer( Dialog::around(TextView::new(styled)) .button("Hell yeah!", |s| s.quit()), ); siv.run(); } cursive-0.20.0/examples/menubar.rs000064400000000000000000000065021046102023000152030ustar 00000000000000use cursive::{event::Key, menu, traits::*, views::Dialog}; use std::sync::atomic::{AtomicUsize, Ordering}; // This examples shows how to configure and use a menubar at the top of the // application. fn main() { let mut siv = cursive::default(); // We'll use a counter to name new files. let counter = AtomicUsize::new(1); // The menubar is a list of (label, menu tree) pairs. siv.menubar() // We add a new "File" tree .add_subtree( "File", menu::Tree::new() // Trees are made of leaves, with are directly actionable... .leaf("New", move |s| { // Here we use the counter to add an entry // in the list of "Recent" items. let i = counter.fetch_add(1, Ordering::Relaxed); let filename = format!("New {}", i); s.menubar() .find_subtree("File") .unwrap() .find_subtree("Recent") .unwrap() .insert_leaf(0, filename, |_| ()); s.add_layer(Dialog::info("New file!")); }) // ... and of sub-trees, which open up when selected. .subtree( "Recent", // The `.with()` method can help when running loops // within builder patterns. menu::Tree::new().with(|tree| { for i in 1..100 { // We don't actually do anything here, // but you could! tree.add_item(menu::Item::leaf(format!("Item {}", i), |_| ()).with(|s| { if i % 5 == 0 { s.disable(); } })) } }), ) // Delimiter are simple lines between items, // and cannot be selected. .delimiter() .with(|tree| { for i in 1..10 { tree.add_leaf(format!("Option {}", i), |_| ()); } }), ) .add_subtree( "Help", menu::Tree::new() .subtree( "Help", menu::Tree::new() .leaf("General", |s| { s.add_layer(Dialog::info("Help message!")) }) .leaf("Online", |s| { let text = "Google it yourself!\n\ Kids, these days..."; s.add_layer(Dialog::info(text)) }), ) .leaf("About", |s| { s.add_layer(Dialog::info("Cursive v0.0.0")) }), ) .add_delimiter() .add_leaf("Quit", |s| s.quit()); // When `autohide` is on (default), the menu only appears when active. // Turning it off will leave the menu always visible. // Try uncommenting this line! // siv.set_autohide_menu(false); siv.add_global_callback(Key::Esc, |s| s.select_menubar()); siv.add_layer(Dialog::text("Hit to show the menu!")); siv.run(); } cursive-0.20.0/examples/mines/Readme.md000064400000000000000000000002231046102023000160300ustar 00000000000000This is a slightly larger example, showing an implementation of Minesweeper. *This is a work in progress; many features are not implemented yet.* cursive-0.20.0/examples/mines/game.rs000064400000000000000000000042321046102023000155740ustar 00000000000000use cursive::Vec2; use rand::{thread_rng, Rng}; // use std::cmp::max; #[derive(Clone, Copy)] pub struct Options { pub size: Vec2, pub mines: usize, } #[derive(Clone, Copy)] pub enum Cell { Bomb, Free(usize), } pub struct Board { pub size: Vec2, pub cells: Vec, } impl Board { pub fn new(options: Options) -> Self { let n_cells = options.size.x * options.size.y; if options.mines > n_cells { // Something is wrong here... // Use different options instead. return Board::new(Options { size: options.size, mines: n_cells, }); } let mut board = Board { size: options.size, cells: vec![Cell::Free(0); n_cells], }; for _ in 0..options.mines { // Find a free cell to put a bomb let i = loop { let i = thread_rng().gen_range(0..n_cells); if let Cell::Bomb = board.cells[i] { continue; } break i; }; // We know we'll go through since that's how we picked i... board.cells[i] = Cell::Bomb; // Increase count on adjacent cells let pos = Vec2::new(i % options.size.x, i / options.size.x); for p in board.neighbours(pos) { if let Some(&mut Cell::Free(ref mut n)) = board.get_mut(p) { *n += 1; } } } board } fn get_mut(&mut self, pos: Vec2) -> Option<&mut Cell> { self.cell_id(pos).map(move |i| &mut self.cells[i]) } pub fn cell_id(&self, pos: Vec2) -> Option { if pos < self.size { Some(pos.x + pos.y * self.size.x) } else { None } } pub fn neighbours(&self, pos: Vec2) -> Vec { let pos_min = pos.saturating_sub((1, 1)); let pos_max = (pos + (2, 2)).or_min(self.size); (pos_min.x..pos_max.x) .flat_map(|x| (pos_min.y..pos_max.y).map(move |y| Vec2::new(x, y))) .filter(|&p| p != pos) .collect() } } cursive-0.20.0/examples/mines/main.rs000064400000000000000000000216021046102023000156070ustar 00000000000000mod game; use cursive::{ direction::Direction, event::{Event, EventResult, MouseButton, MouseEvent}, theme::{BaseColor, Color, ColorStyle}, view::CannotFocus, views::{Button, Dialog, LinearLayout, Panel, SelectView}, Cursive, Printer, Vec2, }; fn main() { let mut siv = cursive::default(); siv.add_layer( Dialog::new() .title("Minesweeper") .padding_lrtb(2, 2, 1, 1) .content( LinearLayout::vertical() .child(Button::new_raw(" New game ", show_options)) .child(Button::new_raw(" Best scores ", |s| { s.add_layer(Dialog::info("Not yet!").title("Scores")) })) .child(Button::new_raw(" Exit ", |s| s.quit())), ), ); siv.run(); } fn show_options(siv: &mut Cursive) { siv.add_layer( Dialog::new() .title("Select difficulty") .content( SelectView::new() .item( "Easy: 8x8, 10 mines", game::Options { size: Vec2::new(8, 8), mines: 10, }, ) .item( "Medium: 16x16, 40 mines", game::Options { size: Vec2::new(16, 16), mines: 40, }, ) .item( "Difficult: 24x24, 99 mines", game::Options { size: Vec2::new(24, 24), mines: 99, }, ) .on_submit(|s, option| { s.pop_layer(); new_game(s, *option); }), ) .dismiss_button("Back"), ); } #[derive(Clone, Copy, PartialEq)] enum Cell { Visible(usize), Flag, Unknown, } struct BoardView { // Actual board, unknown to the player. board: game::Board, // Visible board overlay: Vec, focused: Option, _missing_mines: usize, } impl BoardView { pub fn new(options: game::Options) -> Self { let overlay = vec![Cell::Unknown; options.size.x * options.size.y]; let board = game::Board::new(options); BoardView { board, overlay, focused: None, _missing_mines: options.mines, } } fn get_cell(&self, mouse_pos: Vec2, offset: Vec2) -> Option { mouse_pos .checked_sub(offset) .map(|pos| pos.map_x(|x| x / 2)) .and_then(|pos| { if pos.fits_in(self.board.size) { Some(pos) } else { None } }) } fn flag(&mut self, pos: Vec2) { if let Some(i) = self.board.cell_id(pos) { let new_cell = match self.overlay[i] { Cell::Unknown => Cell::Flag, Cell::Flag => Cell::Unknown, other => other, }; self.overlay[i] = new_cell; } } fn reveal(&mut self, pos: Vec2) -> EventResult { if let Some(i) = self.board.cell_id(pos) { if self.overlay[i] != Cell::Unknown { return EventResult::Consumed(None); } // Action! match self.board.cells[i] { game::Cell::Bomb => { return EventResult::with_cb(|s| { s.add_layer(Dialog::text("BOOOM").button("Ok", |s| { s.pop_layer(); s.pop_layer(); })); }) } game::Cell::Free(n) => { self.overlay[i] = Cell::Visible(n); if n == 0 { // Reveal all surrounding cells for p in self.board.neighbours(pos) { self.reveal(p); } } } } } EventResult::Consumed(None) } fn auto_reveal(&mut self, pos: Vec2) -> EventResult { if let Some(i) = self.board.cell_id(pos) { if let Cell::Visible(n) = self.overlay[i] { // First: is every possible cell tagged? let neighbours = self.board.neighbours(pos); let tagged = neighbours .iter() .filter_map(|&pos| self.board.cell_id(pos)) .map(|i| self.overlay[i]) .filter(|&cell| cell == Cell::Flag) .count(); if tagged != n { return EventResult::Consumed(None); } for p in neighbours { let result = self.reveal(p); if result.has_callback() { return result; } } } } EventResult::Consumed(None) } } impl cursive::view::View for BoardView { fn draw(&self, printer: &Printer) { for (i, cell) in self.overlay.iter().enumerate() { let x = (i % self.board.size.x) * 2; let y = i / self.board.size.x; let text = match *cell { Cell::Unknown => "[]", Cell::Flag => "()", Cell::Visible(n) => { [" ", " 1", " 2", " 3", " 4", " 5", " 6", " 7", " 8"][n] } }; let color = match *cell { Cell::Unknown => Color::RgbLowRes(3, 3, 3), Cell::Flag => Color::RgbLowRes(4, 4, 2), Cell::Visible(1) => Color::RgbLowRes(3, 5, 3), Cell::Visible(2) => Color::RgbLowRes(5, 5, 3), Cell::Visible(3) => Color::RgbLowRes(5, 4, 3), Cell::Visible(4) => Color::RgbLowRes(5, 3, 3), Cell::Visible(5) => Color::RgbLowRes(5, 2, 2), Cell::Visible(6) => Color::RgbLowRes(5, 0, 1), Cell::Visible(7) => Color::RgbLowRes(5, 0, 2), Cell::Visible(8) => Color::RgbLowRes(5, 0, 3), _ => Color::Dark(BaseColor::White), }; printer.with_color( ColorStyle::new(Color::Dark(BaseColor::Black), color), |printer| printer.print((x, y), text), ); } } fn take_focus( &mut self, _: Direction, ) -> Result { Ok(EventResult::Consumed(None)) } fn on_event(&mut self, event: Event) -> EventResult { match event { Event::Mouse { offset, position, event: MouseEvent::Press(_btn), } => { // Get cell for position if let Some(pos) = self.get_cell(position, offset) { self.focused = Some(pos); return EventResult::Consumed(None); } } Event::Mouse { offset, position, event: MouseEvent::Release(btn), } => { // Get cell for position if let Some(pos) = self.get_cell(position, offset) { if self.focused == Some(pos) { // We got a click here! match btn { MouseButton::Left => return self.reveal(pos), MouseButton::Right => { self.flag(pos); return EventResult::Consumed(None); } MouseButton::Middle => { return self.auto_reveal(pos); } _ => (), } } self.focused = None; } } _ => (), } EventResult::Ignored } fn required_size(&mut self, _: Vec2) -> Vec2 { self.board.size.map_x(|x| 2 * x) } } fn new_game(siv: &mut Cursive, options: game::Options) { let _board = game::Board::new(options); siv.add_layer( Dialog::new() .title("Minesweeper") .content( LinearLayout::horizontal() .child(Panel::new(BoardView::new(options))), ) .button("Quit game", |s| { s.pop_layer(); }), ); siv.add_layer(Dialog::info( "Controls: Reveal cell: left click Mark as mine: right-click Reveal nearby unmarked cells: middle-click", )); } cursive-0.20.0/examples/mutation.rs000064400000000000000000000032551046102023000154140ustar 00000000000000use cursive::traits::*; use cursive::view::{Offset, Position}; use cursive::views::{Dialog, OnEventView, TextView}; use cursive::Cursive; // This example modifies a view after creation. fn main() { let mut siv = cursive::default(); let content = "Press Q to quit the application.\n\nPress P to open the \ popup."; siv.add_global_callback('q', |s| s.quit()); // Let's wrap the view to give it a recognizable name, so we can look for it. // We add the P callback on the textview only (and not globally), // so that we can't call it when the popup is already visible. siv.add_layer( OnEventView::new(TextView::new(content).with_name("text")) .on_event('p', |s| show_popup(s)), ); siv.run(); } fn show_popup(siv: &mut Cursive) { // Let's center the popup horizontally, but offset it down a few rows, // so the user can see both the popup and the view underneath. siv.screen_mut().add_layer_at( Position::new(Offset::Center, Offset::Parent(5)), Dialog::around(TextView::new("Tak!")) .button("Change", |s| { // Look for a view tagged "text". // We _know_ it's there, so unwrap it. s.call_on_name("text", |view: &mut TextView| { let content = reverse(view.get_content().source()); view.set_content(content); }); }) .dismiss_button("Ok"), ); } // This just reverses each character // // Note: it would be more correct to iterate on graphemes instead. // Check the unicode_segmentation crate! fn reverse(text: &str) -> String { text.chars().rev().collect() } cursive-0.20.0/examples/panels.rs000064400000000000000000000015741046102023000150400ustar 00000000000000use cursive::traits::*; use cursive::views::{Button, LinearLayout, Panel}; fn main() { let mut siv = cursive::default(); siv.add_layer( LinearLayout::vertical() .child( Panel::new(Button::new("Quit", |s| s.quit())) .title("Panel 1") .fixed_width(20), ) .child( Panel::new(Button::new("Quit", |s| s.quit())) .title("Panel 2") .fixed_width(15), ) .child( Panel::new(Button::new("Quit", |s| s.quit())) .title("Panel 3") .fixed_width(10), ) .child( Panel::new(Button::new("Quit", |s| s.quit())) .title("Panel 4") .fixed_width(5), ), ); siv.run(); } cursive-0.20.0/examples/pause.rs000064400000000000000000000013241046102023000146640ustar 00000000000000use cursive::{self, views}; fn main() { let mut siv = cursive::default(); siv.add_layer( views::Dialog::text("Please write your message.") .button("Ok", |s| s.quit()), ); siv.run(); // At this point the terminal is cleaned up. // We can write to stdout like any CLI program. // You could also start $EDITOR, or run other commands. println!("Enter your message here:"); let mut line = String::new(); std::io::stdin().read_line(&mut line).unwrap(); // And we can start another event loop later on. siv.add_layer( views::Dialog::text(format!("Your message was:\n{}", line)) .button("I guess?", |s| s.quit()), ); siv.run(); } cursive-0.20.0/examples/position.rs000064400000000000000000000023271046102023000154170ustar 00000000000000use cursive::{ view::Position, views::{LayerPosition, TextView}, Cursive, }; /// Moves top layer by the specified amount fn move_top(c: &mut Cursive, x_in: isize, y_in: isize) { // Step 1. Get the current position of the layer. let s = c.screen_mut(); let l = LayerPosition::FromFront(0); // Step 2. add the specifed amount let pos = s .layer_offset(LayerPosition::FromFront(0)) .unwrap() .saturating_add((x_in, y_in)); // convert the new x and y into a position let p = Position::absolute(pos); // Step 3. Apply the new position s.reposition_layer(l, p); } fn main() { let mut siv = cursive::default(); // We can quit by pressing `q` siv.add_global_callback('q', Cursive::quit); // Next Gen FPS Controls. siv.add_global_callback('w', |s| move_top(s, 0, -1)); siv.add_global_callback('a', |s| move_top(s, -1, 0)); siv.add_global_callback('s', |s| move_top(s, 0, 1)); siv.add_global_callback('d', |s| move_top(s, 1, 0)); // Add window to fly around. siv.add_layer(TextView::new( "Press w,a,s,d to move the window.\n\ Press q to quit the application.", )); // Run the event loop siv.run(); } cursive-0.20.0/examples/progress.rs000064400000000000000000000075751046102023000154310ustar 00000000000000use cursive::traits::*; use cursive::utils::Counter; use cursive::views::{Button, Dialog, LinearLayout, ProgressBar, TextView}; use cursive::Cursive; use rand::Rng; use std::cmp::min; use std::thread; use std::time::Duration; // This example shows a ProgressBar reporting the status from an asynchronous // job. // // It works by sharing a counter with the job thread. This counter can be // "ticked" to indicate progress. fn main() { let mut siv = cursive::default(); // We'll start slowly with a simple start button... siv.add_layer( Dialog::new() .title("Progress bar example") .padding_lrtb(0, 0, 1, 1) .content(Button::new("Start", phase_1)), ); siv.run(); } fn phase_1(s: &mut Cursive) { // Phase 1 is easy: a simple pre-loading. // Number of ticks let n_max = 1000; // This is the callback channel let cb = s.cb_sink().clone(); s.pop_layer(); s.add_layer(Dialog::around( ProgressBar::new() // We need to know how many ticks represent a full bar. .range(0, n_max) .with_task(move |counter| { // This closure will be called in a separate thread. fake_load(n_max, &counter); // When we're done, send a callback through the channel cb.send(Box::new(coffee_break)).unwrap(); }) .full_width(), )); s.set_autorefresh(true); } fn coffee_break(s: &mut Cursive) { // A little break before things get serious. s.set_autorefresh(false); s.pop_layer(); s.add_layer( Dialog::new() .title("Preparation complete") .content(TextView::new("Now, the real deal!").center()) .button("Again??", phase_2), ); } fn phase_2(s: &mut Cursive) { // Now, we'll run N tasks // (It could be downloading a file, extracting an archive, // reticulating sprites, ...) let n_bars = 10; // Each task will have its own shiny counter let counters: Vec<_> = (0..n_bars).map(|_| Counter::new(0)).collect(); // To make things more interesting, we'll give a random speed to each bar let speeds: Vec<_> = (0..n_bars) .map(|_| rand::thread_rng().gen_range(50..150)) .collect(); let n_max = 100_000; let cb = s.cb_sink().clone(); // Let's prepare the progress bars... let mut linear = LinearLayout::vertical(); for c in &counters { linear.add_child(ProgressBar::new().max(n_max).with_value(c.clone())); } s.pop_layer(); s.add_layer(Dialog::around(linear.full_width()).title("Just a moment...")); // And we start the worker thread. thread::spawn(move || { loop { thread::sleep(Duration::from_millis(5)); let mut done = true; for (c, s) in counters.iter().zip(&speeds) { let ticks = min(n_max - c.get(), *s); c.tick(ticks); if c.get() < n_max { done = false; } } if done { break; } } cb.send(Box::new(final_step)).unwrap(); }); s.set_autorefresh(true); } fn final_step(s: &mut Cursive) { // A little break before things get serious. s.set_autorefresh(false); s.pop_layer(); s.add_layer( Dialog::new() .title("Report") .content( TextView::new( "Time travel was a success!\n\ We went forward a few seconds!!", ) .center(), ) .button("That's it?", |s| s.quit()), ); } // Function to simulate a long process. fn fake_load(n_max: usize, counter: &Counter) { for _ in 0..n_max { thread::sleep(Duration::from_millis(5)); // The `counter.tick()` method increases the progress value counter.tick(1); } } cursive-0.20.0/examples/radio.rs000064400000000000000000000041221046102023000146440ustar 00000000000000use cursive::views::{Dialog, DummyView, LinearLayout, RadioGroup}; // This example uses radio buttons. fn main() { let mut siv = cursive::default(); // We need to pre-create the groups for our RadioButtons. let mut color_group: RadioGroup = RadioGroup::new(); let mut size_group: RadioGroup = RadioGroup::new(); siv.add_layer( Dialog::new() .title("Make your selection") // We'll have two columns side-by-side .content( LinearLayout::horizontal() .child( LinearLayout::vertical() // The color group uses the label itself as stored value // By default, the first item is selected. .child(color_group.button_str("Red")) .child(color_group.button_str("Green")) .child(color_group.button_str("Blue")), ) // A DummyView is used as a spacer .child(DummyView) .child( LinearLayout::vertical() // For the size, we store a number separately .child(size_group.button(5, "Small")) // The initial selection can also be overriden .child(size_group.button(15, "Medium").selected()) // The large size is out of stock, sorry! .child(size_group.button(25, "Large").disabled()), ), ) .button("Ok", move |s| { // We retrieve the stored value for both group. let color = color_group.selection(); let size = size_group.selection(); s.pop_layer(); // And we simply print the result. let text = format!("Color: {}\nSize: {}cm", color, size); s.add_layer(Dialog::text(text).button("Ok", |s| s.quit())); }), ); siv.run(); } cursive-0.20.0/examples/refcell_view.rs000064400000000000000000000026451046102023000162240ustar 00000000000000use cursive::view::{Nameable, Resizable}; use cursive::views::{Dialog, EditView, LinearLayout, TextView}; use cursive::Cursive; // This example shows a way to access multiple views at the same time. fn main() { let mut siv = cursive::default(); // Create a dialog with 2 edit fields, and a text view. // The text view indicates when the 2 fields content match. siv.add_layer( Dialog::around( LinearLayout::vertical() .child(EditView::new().on_edit(on_edit).with_name("1")) .child(EditView::new().on_edit(on_edit).with_name("2")) .child(TextView::new("match").with_name("match")) .fixed_width(10), ) .button("Quit", Cursive::quit), ); siv.run(); } // Compare the content of the two edit views, // and update the TextView accordingly. // // We'll ignore the `content` and `cursor` arguments, // and directly retrieve the content from the `Cursive` root. fn on_edit(siv: &mut Cursive, _content: &str, _cursor: usize) { // Get handles for each view. let edit_1 = siv.find_name::("1").unwrap(); let edit_2 = siv.find_name::("2").unwrap(); // Directly compare references to edit_1 and edit_2. let matches = edit_1.get_content() == edit_2.get_content(); siv.call_on_name("match", |v: &mut TextView| { v.set_content(if matches { "match" } else { "no match" }) }); } cursive-0.20.0/examples/scroll.rs000064400000000000000000000015671046102023000150560ustar 00000000000000use cursive::traits::Resizable; use cursive::view::Scrollable; use cursive::views::{Button, Canvas, Dialog, LinearLayout}; use cursive::Printer; fn main() { let mut siv = cursive::default(); siv.add_layer( Dialog::around( LinearLayout::vertical() .child(Button::new("Foo", |s| s.add_layer(Dialog::info("Ah")))) .child(Canvas::new(()).with_draw(draw).fixed_size((120, 40))) .child(Button::new("Bar", |s| s.add_layer(Dialog::info("Uh")))) .scrollable() .scroll_x(true), ) .fixed_size((60, 30)), ); siv.add_global_callback('q', |s| s.quit()); siv.run(); } fn draw(_: &(), p: &Printer) { for x in 0..p.size.x { for y in 0..p.size.y { let c = (x + 6 * y) % 10; p.print((x, y), &format!("{}", c)); } } } cursive-0.20.0/examples/select.rs000064400000000000000000000034611046102023000150320ustar 00000000000000use cursive::align::HAlign; use cursive::event::EventResult; use cursive::traits::*; use cursive::views::{Dialog, OnEventView, SelectView, TextView}; use cursive::Cursive; // We'll use a SelectView here. // // A SelectView is a scrollable list of items, from which the user can select // one. fn main() { let mut select = SelectView::new() // Center the text horizontally .h_align(HAlign::Center) // Use keyboard to jump to the pressed letters .autojump(); // Read the list of cities from separate file, and fill the view with it. // (We include the file at compile-time to avoid runtime read errors.) let content = include_str!("assets/cities.txt"); select.add_all_str(content.lines()); // Sets the callback for when "Enter" is pressed. select.set_on_submit(show_next_window); // Let's override the `j` and `k` keys for navigation let select = OnEventView::new(select) .on_pre_event_inner('k', |s, _| { let cb = s.select_up(1); Some(EventResult::Consumed(Some(cb))) }) .on_pre_event_inner('j', |s, _| { let cb = s.select_down(1); Some(EventResult::Consumed(Some(cb))) }); let mut siv = cursive::default(); // Let's add a ResizedView to keep the list at a reasonable size // (it can scroll anyway). siv.add_layer( Dialog::around(select.scrollable().fixed_size((20, 10))) .title("Where are you from?"), ); siv.run(); } // Let's put the callback in a separate function to keep it clean, // but it's not required. fn show_next_window(siv: &mut Cursive, city: &str) { siv.pop_layer(); let text = format!("{} is a great city!", city); siv.add_layer( Dialog::around(TextView::new(text)).button("Quit", |s| s.quit()), ); } cursive-0.20.0/examples/select_test.rs000064400000000000000000000104421046102023000160660ustar 00000000000000// We'll do some automated tests on interface identical to one in select.rs // // To run this example call: // cargo test --example select_test -- --nocapture fn main() { print!( "To run this example call:\n$ cargo test --bin select_test -- --nocapture\n" ); } #[cfg(test)] pub mod tests { extern crate cursive; use cursive::align::HAlign; use cursive::backends::puppet::observed::ObservedScreen; use cursive::event::Event; use cursive::event::EventResult; use cursive::event::Key; use cursive::traits::*; use cursive::views::*; use cursive::*; use std::cell::RefCell; pub struct BasicSetup { siv: CursiveRunner, screen_stream: crossbeam_channel::Receiver, input: crossbeam_channel::Sender>, last_screen: RefCell>, } impl BasicSetup { pub fn new() -> Self { let mut select = SelectView::new() // Center the text horizontally .h_align(HAlign::Center) // Use keyboard to jump to the pressed letters .autojump(); // Read the list of cities from separate file, and fill the view with it. // (We include the file at compile-time to avoid runtime read errors.) let content = include_str!("assets/cities.txt"); select.add_all_str(content.lines()); // Sets the callback for when "Enter" is pressed. select.set_on_submit(show_next_window); // Let's override the `j` and `k` keys for navigation let select = OnEventView::new(select) .on_pre_event_inner('k', |s, _| { s.select_up(1); Some(EventResult::Consumed(None)) }) .on_pre_event_inner('j', |s, _| { s.select_down(1); Some(EventResult::Consumed(None)) }); let size = Vec2::new(80, 16); let backend = backends::puppet::Backend::init(Some(size)); let sink = backend.stream(); let input = backend.input(); let mut siv = Cursive::new().into_runner(backend); // Let's add a ResizedView to keep the list at a reasonable size // (it can scroll anyway). siv.add_layer( Dialog::around(select.scrollable().fixed_size((20, 10))) .title("Where are you from?"), ); input.send(Some(Event::Refresh)).unwrap(); siv.step(); BasicSetup { siv, screen_stream: sink, input, last_screen: RefCell::new(None), } } pub fn last_screen(&self) -> Option { while let Ok(screen) = self.screen_stream.try_recv() { self.last_screen.replace(Some(screen)); } self.last_screen.borrow().clone() } pub fn dump_debug(&self) { self.last_screen().as_ref().map(|s| s.print_stdout()); } pub fn hit_keystroke(&mut self, key: Key) { self.input.send(Some(Event::Key(key))).unwrap(); self.siv.step(); } } // Let's put the callback in a separate function to keep it clean, // but it's not required. fn show_next_window(siv: &mut Cursive, city: &str) { siv.pop_layer(); let text = format!("{} is a great city!", city); siv.add_layer( Dialog::around(TextView::new(text)).button("Quit", |s| s.quit()), ); } #[test] fn displays() { let s = BasicSetup::new(); let screen = s.last_screen().unwrap(); s.dump_debug(); assert_eq!(screen.find_occurences("Where are you from").len(), 1); assert_eq!(screen.find_occurences("Some random string").len(), 0); } #[test] fn interacts() { let mut s = BasicSetup::new(); s.hit_keystroke(Key::Down); s.hit_keystroke(Key::Enter); let screen = s.last_screen().unwrap(); s.dump_debug(); assert_eq!( screen.find_occurences("Abu Dhabi is a great city!").len(), 1 ); assert_eq!(screen.find_occurences("Abidjan").len(), 0); } } cursive-0.20.0/examples/slider.rs000064400000000000000000000022141046102023000150300ustar 00000000000000use cursive::traits::*; use cursive::views::{Dialog, SliderView}; use cursive::Cursive; fn main() { let mut siv = cursive::default(); siv.add_global_callback('q', |s| s.quit()); // Let's add a simple slider in a dialog. // Moving the slider will update the dialog's title. // And pressing "Enter" will show a new dialog. siv.add_layer( Dialog::around( // We give the number of steps in the constructor SliderView::horizontal(15) // Sets the initial value .value(7) .on_change(|s, v| { let title = format!("[ {} ]", v); s.call_on_name("dialog", |view: &mut Dialog| { view.set_title(title) }); }) .on_enter(|s, v| { s.pop_layer(); s.add_layer( Dialog::text(format!("Lucky number {}!", v)) .button("Ok", Cursive::quit), ); }), ) .title("[ 7 ]") .with_name("dialog"), ); siv.run(); } cursive-0.20.0/examples/stopwatch.rs000064400000000000000000000051021046102023000155610ustar 00000000000000//! A simple stopwatch implementation. //! Also check out [`clock-cli`](https://github.com/TianyiShi2001/cl!ock-cli-rs), //! which aims to implement a fully-fledged clock with stopwatch, countdown //! timer, and possibly more functionalities. use cursive::traits::{Nameable, Resizable}; use cursive::views::{Button, Canvas, Dialog, LinearLayout}; use std::time::{Duration, Instant}; fn main() { let mut siv = cursive::default(); siv.add_layer( Dialog::new() .title("Stopwatch") .content( LinearLayout::horizontal() .child( Canvas::new(Watch { last_started: Instant::now(), last_elapsed: Duration::default(), running: true, }) .with_draw(|s, printer| { printer.print( (0, 1), &format!("{:.2?}", s.elapsed()), ); }) .with_name("stopwatch") .fixed_size((8, 3)), ) .child( LinearLayout::vertical() .child(Button::new("Start", run(Watch::start))) .child(Button::new("Pause", run(Watch::pause))) .child(Button::new("Stop", run(Watch::stop))), ), ) .button("Quit", |s| s.quit()) .h_align(cursive::align::HAlign::Center), ); siv.set_fps(20); siv.run(); } struct Watch { last_started: Instant, last_elapsed: Duration, running: bool, } impl Watch { fn start(&mut self) { self.running = true; self.last_started = Instant::now(); } fn elapsed(&self) -> Duration { self.last_elapsed + if self.running { Instant::now() - self.last_started } else { Duration::default() } } fn pause(&mut self) { self.last_elapsed = self.elapsed(); self.running = false; } fn stop(&mut self) { self.running = false; self.last_elapsed = Duration::default(); } } // Helper function to find the stopwatch view and run a closure on it. fn run(f: F) -> impl Fn(&mut cursive::Cursive) where F: Fn(&mut Watch), { move |s| { s.call_on_name("stopwatch", |c: &mut Canvas| { f(c.state_mut()); }); } } cursive-0.20.0/examples/tcp_server.rs000064400000000000000000000147271046102023000157360ustar 00000000000000use cursive::traits::Resizable; use cursive::traits::*; use cursive::views; use std::io::{Read as _, Write as _}; use std::sync::{Arc, Mutex}; // This example builds a simple TCP server with some parameters and some output. // It then builds a TUI to control the parameters and display the output. fn main() { let mut siv = cursive::default(); // Build a shared model let model = Arc::new(Mutex::new(ModelData { offset: 10, logs: Vec::new(), cb_sink: siv.cb_sink().clone(), })); // Start the TCP server in a thread start_server(Arc::clone(&model)); // Build the UI from the model siv.add_layer( views::Dialog::around(build_ui(Arc::clone(&model))) .button("Quit", |s| s.quit()), ); siv.run(); } struct ModelData { /// The offset will be controlled by the UI and used in the server offset: u8, /// Logs will be filled by the server and displayed on the UI logs: Vec, /// A callback sink is used to control the UI from the server /// (eg. force refresh, error popups) cb_sink: cursive::CbSink, } // Here we use a single mutex, but bigger models might // prefer individual mutexes for different variables. type Model = Arc>; #[derive(Clone, Copy)] struct LogEntry { input: u8, output: u8, } /// Starts serving on a separate thread, and show a popup on error. fn start_server(model: Model) { std::thread::spawn(move || { if let Err(err) = serve(Arc::clone(&model)) { let model = model.lock().unwrap(); model .cb_sink .send(Box::new(move |s: &mut cursive::Cursive| { s.add_layer( views::Dialog::text(format!("{:?}", err)) .title("Error in TCP server") .button("Quit", |s| s.quit()), ); })) .unwrap(); } }); } /// Starts a simple, single-threaded TCP server. /// Adds a configurable offset to each byte received and sent it back. fn serve(model: Model) -> std::io::Result<()> { // Bind on some local address let listener = std::net::TcpListener::bind("localhost:1234")?; // Handle each connection sequentially for stream in listener.incoming() { let stream = stream?; // Process each byte according to the current model. for byte in (&stream).bytes() { let byte = byte?; let mut model = model.lock().unwrap(); let response = byte.wrapping_add(model.offset); (&stream).write_all(&[response])?; // Save processed jobs model.logs.push(LogEntry { input: byte, output: response, }); // Send a noop to refresh the display model .cb_sink .send(Box::new(cursive::Cursive::noop)) .unwrap(); } } Ok(()) } /// Build the UI for the given model. fn build_ui(model: Model) -> impl cursive::view::View { // Build the UI in 3 parts, stacked together in a LinearLayout. views::LinearLayout::vertical() .child(build_selector(Arc::clone(&model))) .child(build_tester(Arc::clone(&model))) .child(views::DummyView.fixed_height(1)) .child(build_log_viewer(Arc::clone(&model))) } /// Build a view that shows processed jobs from the model. fn build_log_viewer(model: Model) -> impl cursive::view::View { views::Canvas::new(model) .with_draw(|model, printer| { let model = model.lock().unwrap(); for (i, &log) in model.logs.iter().enumerate() { printer.print( (0, i), &format!( "{:3} '{}' -> {:3} '{}'", log.input, readable_char(log.input), log.output, readable_char(log.output), ), ); } }) .with_required_size(|model, _req| { let model = model.lock().unwrap(); cursive::Vec2::new(20, model.logs.len()) }) .scrollable() } /// Pretty print an ascii u8 if possible. fn readable_char(byte: u8) -> char { if byte.is_ascii_control() { '�' } else { byte as char } } /// Build a view that can update the model. fn build_selector(model: Model) -> impl cursive::view::View { let offset = model.lock().unwrap().offset; views::LinearLayout::horizontal() .child( views::EditView::new() .content(format!("{}", offset)) .with_name("edit") .min_width(5), ) .child(views::DummyView.fixed_width(1)) .child(views::Button::new("Update", move |s| { if let Some(n) = s .call_on_name("edit", |edit: &mut views::EditView| { edit.get_content() }) .and_then(|content| content.parse().ok()) { model.lock().unwrap().offset = n; } else { s.add_layer(views::Dialog::info( "Could not parse offset as u8", )); } })) } /// Build a view that can run test connections. fn build_tester(model: Model) -> impl cursive::view::View { views::LinearLayout::horizontal() .child(views::TextView::new("Current value:")) .child(views::DummyView.fixed_width(1)) .child( views::Canvas::new(model) .with_draw(|model, printer| { printer.print( (0, 0), &format!("{}", model.lock().unwrap().offset), ) }) .with_required_size(|_, _| cursive::Vec2::new(3, 1)), ) .child(views::DummyView.fixed_width(1)) .child(views::Button::new("Test", |s| { if let Err(err) = test_server() { s.add_layer( views::Dialog::info(format!("{:?}", err)) .title("Error running test."), ); } })) } /// Run a test connection. fn test_server() -> std::io::Result<()> { let mut stream = std::net::TcpStream::connect("localhost:1234")?; for &byte in b"cursive123" { let mut buf = [0]; stream.write_all(&[byte])?; stream.read_exact(&mut buf)?; } Ok(()) } cursive-0.20.0/examples/terminal_default.rs000064400000000000000000000015131046102023000170660ustar 00000000000000use cursive::theme::{Color, PaletteColor, Theme}; use cursive::views::TextView; use cursive::Cursive; // This example sets the background color to the terminal default. // // This way, it looks more natural. fn main() { let mut siv = cursive::default(); let theme = custom_theme_from_cursive(&siv); siv.set_theme(theme); // We can quit by pressing `q` siv.add_global_callback('q', Cursive::quit); siv.add_layer(TextView::new( "Hello World with default terminal background color!\n\ Press q to quit the application.", )); siv.run(); } fn custom_theme_from_cursive(siv: &Cursive) -> Theme { // We'll return the current theme with a small modification. let mut theme = siv.current_theme().clone(); theme.palette[PaletteColor::Background] = Color::TerminalDefault; theme } cursive-0.20.0/examples/text_area.rs000064400000000000000000000045301046102023000155250ustar 00000000000000use cursive::event::{Event, Key}; use cursive::traits::*; use cursive::views::{Dialog, EditView, OnEventView, TextArea}; use cursive::Cursive; fn main() { let mut siv = cursive::default(); // The main dialog will just have a textarea. // Its size expand automatically with the content. siv.add_layer( Dialog::new() .title("Describe your issue") .padding_lrtb(1, 1, 1, 0) .content(TextArea::new().with_name("text")) .button("Ok", Cursive::quit), ); // We'll add a find feature! siv.add_layer(Dialog::info("Hint: press Ctrl-F to find in text!")); siv.add_global_callback(Event::CtrlChar('f'), |s| { // When Ctrl-F is pressed, show the Find popup. // Pressing the Escape key will discard it. s.add_layer( OnEventView::new( Dialog::new() .title("Find") .content( EditView::new() .on_submit(find) .with_name("edit") .min_width(10), ) .button("Ok", |s| { let text = s .call_on_name("edit", |view: &mut EditView| { view.get_content() }) .unwrap(); find(s, &text); }) .dismiss_button("Cancel"), ) .on_event(Event::Key(Key::Esc), |s| { s.pop_layer(); }), ) }); siv.run(); } fn find(siv: &mut Cursive, text: &str) { // First, remove the find popup siv.pop_layer(); let res = siv.call_on_name("text", |v: &mut TextArea| { // Find the given text from the text area content // Possible improvement: search after the current cursor. if let Some(i) = v.get_content().find(text) { // If we found it, move the cursor v.set_cursor(i); Ok(()) } else { // Otherwise, return an error so we can show a warning. Err(()) } }); if let Some(Err(())) = res { // If we didn't find anything, tell the user! siv.add_layer(Dialog::info(format!("`{}` not found", text))); } } cursive-0.20.0/examples/text_with_ansi_codes.txt000064400000000000000000000013531046102023000201520ustar 00000000000000I love cursive and I cannot lie cursive-0.20.0/examples/theme.rs000064400000000000000000000011241046102023000146470ustar 00000000000000use cursive::views::{Dialog, TextView}; fn main() { let mut siv = cursive::default(); // You can load a theme from a file at runtime for fast development. siv.load_theme_file("examples/assets/style.toml").unwrap(); // Or you can directly load it from a string for easy deployment. siv.load_toml(include_str!("assets/style.toml")).unwrap(); siv.add_layer( Dialog::around(TextView::new( "This application uses a \ custom theme!", )) .title("Themed dialog") .button("Quit", |s| s.quit()), ); siv.run(); } cursive-0.20.0/examples/theme_editor.rs000064400000000000000000000265251046102023000162310ustar 00000000000000use cursive::theme::{ BaseColor, BorderStyle, Color, Palette, PaletteColor, Theme, }; use cursive::traits::{Finder, Nameable, Resizable, With}; #[derive(Clone, Copy)] enum ColorKind { TerminalDefault, Base, Rgb, } fn set_color_subtree( v: &mut cursive::views::LinearLayout, kind: &ColorKind, title: &str, ) { // First, clean out all child views here. while v.len() > 1 { v.remove_child(1); } match kind { ColorKind::TerminalDefault => { // No need for any extra child there. } ColorKind::Base => { v.add_child(cursive::views::DummyView.fixed_width(2)); v.add_child( cursive::views::SelectView::new() .popup() .with_all( BaseColor::all() .map(|color| (format!("{color:?}"), color)), ) .on_submit(|s, _| apply(s)) .with_name(format!("{title}_base")), ); v.add_child(cursive::views::DummyView.fixed_width(2)); v.add_child(cursive::views::TextView::new("Light:")); v.add_child(cursive::views::DummyView); v.add_child( cursive::views::Checkbox::new() .on_change(|s, _| apply(s)) .with_name(format!("{title}_light")), ) } ColorKind::Rgb => { v.add_child(cursive::views::DummyView.fixed_width(2)); for primary in ["red", "green", "blue"] { v.add_child( cursive::views::EditView::new() .max_content_width(3) .on_edit(|s, _, _| apply(s)) .with_name(format!("{title}_{primary}")) .fixed_width(4), ); v.add_child(cursive::views::DummyView); } v.add_child(cursive::views::DummyView); v.add_child(cursive::views::TextView::new("Low Res:")); v.add_child(cursive::views::DummyView); v.add_child( cursive::views::Checkbox::new() .on_change(|s, _| apply(s)) .with_name(format!("{title}_lowres")), ) } } } fn make_color_selection( title: &str, starting_color: Color, ) -> impl cursive::View { cursive::views::LinearLayout::horizontal() .child( cursive::views::SelectView::new() .popup() .item("Terminal default", ColorKind::TerminalDefault) .item("Base color", ColorKind::Base) .item("RGB", ColorKind::Rgb) .on_submit({ let title = title.to_string(); move |s, kind| { let title = &title; s.call_on_name( title, move |v: &mut cursive::views::LinearLayout| { set_color_subtree(v, kind, title); }, ) .unwrap(); apply(s); } }) .selected(match starting_color { Color::TerminalDefault => 0, Color::Dark(_) | Color::Light(_) => 1, Color::Rgb(..) | Color::RgbLowRes(..) => 2, }) .with_name(format!("{title}_kind")), ) .with(|mut v| match starting_color { Color::TerminalDefault => (), Color::Rgb(r, g, b) | Color::RgbLowRes(r, g, b) => { let is_lowres = matches!(starting_color, Color::RgbLowRes(..)); set_color_subtree(&mut v, &ColorKind::Rgb, title); for (primary, value) in [("red", r), ("green", g), ("blue", b)] { v.call_on_name( &format!("{title}_{primary}"), |e: &mut cursive::views::EditView| { e.set_content(format!("{value}")); }, ) .unwrap(); } v.call_on_name( &format!("{title}_lowres"), |c: &mut cursive::views::Checkbox| { c.set_checked(is_lowres); }, ) .unwrap(); } Color::Light(color) | Color::Dark(color) => { let is_light = matches!(starting_color, Color::Light(_)); set_color_subtree(&mut v, &ColorKind::Base, title); v.call_on_name( &format!("{title}_base"), |s: &mut cursive::views::SelectView| { s.set_selection(color as usize); }, ) .unwrap(); v.call_on_name( &format!("{title}_light"), |c: &mut cursive::views::Checkbox| { c.set_checked(is_light); }, ) .unwrap(); } }) .with_name(title) } fn make_dialog(theme: &Theme) -> impl cursive::View { cursive::views::Dialog::new() .title("Theme editor") .button("Quit", |s| s.quit()) .content( cursive::views::LinearLayout::vertical() .child( cursive::views::ListView::new() .child( "Shadows", cursive::views::Checkbox::new() .with_checked(theme.shadow) .on_change(|s, _| apply(s)) .with_name("shadows"), ) .child( "Borders", cursive::views::SelectView::new() .popup() .with_all( BorderStyle::all() .map(|b| (format!("{b:?}"), b)), ) .selected(theme.borders as usize) .on_submit(|s, _| apply(s)) .with_name("borders") .max_width(10), ) .child( "Palette", cursive::views::ListView::new().with(|l| { for color in PaletteColor::all() { let current_color = theme.palette[color]; let color = format!("{color:?}"); l.add_child( &color, make_color_selection( &color, current_color, ), ); } }), ), ) .child(cursive::views::DummyView) .child( cursive::views::TextView::new( "Press R to reset the theme.", ) .style(BaseColor::Red.light()) .h_align(cursive::align::HAlign::Center) .with_name("status"), ), ) .fixed_width(80) } fn main() { let mut siv = cursive::default(); siv.add_global_callback('r', |s| { let theme = Theme::default(); s.pop_layer(); s.add_layer(make_dialog(&theme)); s.set_theme(theme); }); let theme = siv.current_theme().clone(); siv.add_layer(make_dialog(&theme)); siv.run(); } fn get_edit_text(siv: &mut cursive::Cursive, name: &str) -> Option { siv.call_on_name(name, |e: &mut cursive::views::EditView| { e.get_content().parse() }) .unwrap() .ok() } fn get_checkbox(siv: &mut cursive::Cursive, name: &str) -> bool { siv.call_on_name(name, |s: &mut cursive::views::Checkbox| s.is_checked()) .unwrap() } fn get_selection( siv: &mut cursive::Cursive, name: &str, ) -> T { siv.call_on_name(name, |s: &mut cursive::views::SelectView| { *s.selection().unwrap() }) .unwrap() } fn find_color(siv: &mut cursive::Cursive, title: &str) -> Option { // First, the kind Some(match get_selection(siv, &format!("{title}_kind")) { ColorKind::TerminalDefault => Color::TerminalDefault, ColorKind::Base => { let base_color: BaseColor = get_selection(siv, &format!("{title}_base")); let light = get_checkbox(siv, &format!("{title}_light")); if light { base_color.light() } else { base_color.dark() } } ColorKind::Rgb => { let r = get_edit_text(siv, &format!("{title}_red")); let g = get_edit_text(siv, &format!("{title}_green")); let b = get_edit_text(siv, &format!("{title}_blue")); let lowres = get_checkbox(siv, &format!("{title}_lowres")); match (r, g, b) { (Some(r), Some(g), Some(b)) => { if lowres { if let Some(color) = Color::low_res(r, g, b) { color } else { siv.call_on_name("status", |t: &mut cursive::views::TextView| t.set_content( format!("Low-resolution values can only be <= 5 for {title}.") )).unwrap(); return None; } } else { Color::Rgb(r, g, b) } } _ => { // invalid! siv.call_on_name( "status", |t: &mut cursive::views::TextView| { t.set_content(format!( "Invalid R/G/B values for {title}." )) }, ); return None; } } } }) } fn apply(siv: &mut cursive::Cursive) { siv.call_on_name("status", |t: &mut cursive::views::TextView| { t.set_content("Press R to reset the theme.") }) .unwrap(); let shadow = siv .call_on_name("shadows", |c: &mut cursive::views::Checkbox| { c.is_checked() }) .unwrap(); let borders = siv .call_on_name( "borders", |c: &mut cursive::views::SelectView| { *c.selection().unwrap() }, ) .unwrap(); let mut palette = Palette::default(); for color in PaletteColor::all() { if let Some(c) = find_color(siv, &format!("{color:?}")) { palette[color] = c; } } siv.set_theme(Theme { shadow, borders, palette, }) } cursive-0.20.0/examples/theme_manual.rs000064400000000000000000000031151046102023000162060ustar 00000000000000use cursive::theme::{BaseColor, BorderStyle, Color, ColorStyle, Palette}; use cursive::traits::With; use cursive::views::{Dialog, EditView, LinearLayout, TextView}; use cursive::Cursive; fn main() { let mut siv = cursive::default(); siv.set_theme(cursive::theme::Theme { shadow: true, borders: BorderStyle::Simple, palette: Palette::default().with(|palette| { use cursive::theme::BaseColor::*; use cursive::theme::Color::*; use cursive::theme::PaletteColor::*; palette[Background] = TerminalDefault; palette[View] = TerminalDefault; palette[Primary] = White.dark(); palette[TitlePrimary] = Blue.light(); palette[Secondary] = Blue.light(); palette[Highlight] = Blue.dark(); }), }); let layout = LinearLayout::vertical() .child(TextView::new("This is a dynamic theme example!")) .child(EditView::new().content("Woo! colors!")); siv.add_layer( Dialog::around(layout) .title("Theme example") .button("Change", |s| { let mut theme = s.current_theme().clone(); theme.shadow = !theme.shadow; theme.borders = match theme.borders { BorderStyle::Simple => BorderStyle::Outset, BorderStyle::Outset => BorderStyle::None, BorderStyle::None => BorderStyle::Simple, }; s.set_theme(theme); }) .button("Quit", Cursive::quit), ); siv.run(); } cursive-0.20.0/examples/themed_view.rs000064400000000000000000000020311046102023000160430ustar 00000000000000use cursive::{self, theme, views, With}; fn main() { let mut cursive = cursive::default(); cursive.add_layer( views::Dialog::text("Open a themed dialog?") .button("Open", show_dialog) .button("Quit", |s| s.quit()), ); cursive.run(); } fn show_dialog(s: &mut cursive::Cursive) { // Let's build a green theme let theme = s.current_theme().clone().with(|theme| { theme.palette[theme::PaletteColor::View] = theme::Color::Dark(theme::BaseColor::Black); theme.palette[theme::PaletteColor::Primary] = theme::Color::Light(theme::BaseColor::Green); theme.palette[theme::PaletteColor::TitlePrimary] = theme::Color::Light(theme::BaseColor::Green); theme.palette[theme::PaletteColor::Highlight] = theme::Color::Dark(theme::BaseColor::Green); }); s.add_layer(views::ThemedView::new( theme, views::Layer::new( views::Dialog::info("Colors!").title("Themed Dialog"), ), )); } cursive-0.20.0/examples/user_data.rs000064400000000000000000000025141046102023000155200ustar 00000000000000//! This example shows the usage of user data. //! //! This lets you attach data to the main `Cursive` root, which can simplify //! communication in simple applications. //! //! `Cursive::set_user_data` is used to store or update the user data, while //! `Cursive::user_data` and `Cursive::with_user_data` can access it, if they //! know the exact type. use cursive::views::Dialog; use cursive::Cursive; struct Data { counter: u32, } fn main() { let mut siv = cursive::default(); // `Cursive::set_user_data` accepts any `T: Any`, which includes most // owned types siv.set_user_data(Data { counter: 0 }); siv.add_layer( Dialog::text("This uses some user data!") .title("User data example") .button("Increment", |s| { // `Cursive::with_user_data()` is an easy way to run a closure // on the data. s.with_user_data(|data: &mut Data| { data.counter += 1; }); }) .button("Show", |s| { // `Cursive::user_data()` returns a reference to the data. let value = s.user_data::().unwrap().counter; s.add_layer(Dialog::info(format!("Current value: {}", value))); }) .button("Quit", Cursive::quit), ); siv.run(); } cursive-0.20.0/examples/vpv.rs000064400000000000000000000126431046102023000143700ustar 00000000000000use std::io; use cursive::traits::{Resizable, With}; use cursive::utils; use cursive::views::{Canvas, Dialog, LinearLayout, ProgressBar}; use cursive::Cursive; use pretty_bytes::converter::convert; use std::thread; use std::time; // This example is a visual version of the `pv` tool. fn main() { let mut siv = cursive::default(); // We'll use this channel to signal the end of the transfer let cb_sink = siv.cb_sink().clone(); // Use a counter to track progress let counter = utils::Counter::new(0); let counter_copy = counter.clone(); let start = time::Instant::now(); // If an argument is given, it is the file we'll read from. // Otherwise, read from stdin. let (source, len) = match std::env::args().nth(1) { Some(source) => { let meta = std::fs::metadata(&source).unwrap(); // If possible, read the file size to have a progress bar. let len = meta.len(); (Some(source), if len > 0 { Some(len) } else { None }) } None => (None, None), }; // Add a single view: progress status siv.add_layer( Dialog::new() .title("Copying...") .content( LinearLayout::vertical() .child( Canvas::new(counter.clone()) .with_draw(move |c, printer| { let ticks = c.get() as f64; let now = time::Instant::now(); let duration = now - start; let seconds = duration.as_secs() as f64 + f64::from(duration.subsec_nanos()) * 1e-9; let speed = ticks / seconds; // Print ETA if we have a file size // Otherwise prints elapsed time. if let Some(len) = len { let remaining = (len as f64 - ticks) / speed; printer.print( (0, 0), &format!( "ETA: {:.1} seconds", remaining ), ); } else { printer.print( (0, 0), &format!( "Elapsed: {:.1} seconds", seconds ), ); } printer.print( (0, 1), &format!("Copied: {}", convert(ticks)), ); printer.print( (0, 2), &format!("Speed: {}/s", convert(speed)), ); }) .fixed_size((25, 3)), ) .with(|l| { // If we have a file length, add a progress bar if let Some(len) = len { l.add_child( ProgressBar::new() .max(len as usize) .with_value(counter.clone()), ); } }), ) .button("Abort", Cursive::quit), ); if source.is_none() && atty::is(atty::Stream::Stdin) { siv.add_layer( Dialog::text( "Please specify an input file or redirect a file to stdin. cargo run --example vpv /dev/null", ) .button("Quit", Cursive::quit), ); } else { // Start the copy in a separate thread thread::spawn(move || { // Copy to stdout - lock it for better performance. let stdout = io::stdout(); let mut stdout = stdout.lock(); match source { None => { // Copy from stdin - lock it for better performance. let stdin = io::stdin(); let stdin = stdin.lock(); let mut reader = utils::ProgressReader::new(counter_copy, stdin); // And copy! io::copy(&mut reader, &mut stdout).unwrap(); } Some(source) => { // Copy from stdin - lock it for better performance. let input = std::fs::File::open(source).unwrap(); let mut reader = utils::ProgressReader::new(counter_copy, input); // And copy! io::copy(&mut reader, &mut stdout).unwrap(); } } // When we're done, shut down the application cb_sink.send(Box::new(Cursive::quit)).unwrap(); }); siv.set_autorefresh(true); } siv.run(); } cursive-0.20.0/examples/window_title.rs000064400000000000000000000006251046102023000162620ustar 00000000000000fn main() { let mut siv = cursive::default(); siv.add_layer( cursive::views::Dialog::new() .title("Write yourself a new title!") .content(cursive::views::EditView::new().on_edit( |s, content, _| { s.set_window_title(content); }, )) .button("Quit", |s| s.quit()), ); siv.run(); } cursive-0.20.0/src/backends/blt.rs000064400000000000000000000426011046102023000150560ustar 00000000000000//! Backend using BearLibTerminal //! //! Requires the `blt-backend` feature. #![cfg(feature = "bear-lib-terminal")] #![cfg_attr(feature = "doc-cfg", doc(cfg(feature = "blt-backend")))] pub use bear_lib_terminal; use bear_lib_terminal::geometry::Size; use bear_lib_terminal::terminal::{self, state, Event as BltEvent, KeyCode}; use bear_lib_terminal::Color as BltColor; use crate::backend; use crate::event::{Event, Key, MouseButton, MouseEvent}; use crate::theme::{BaseColor, Color, ColorPair, Effect}; use crate::Vec2; // Use AHash instead of the slower SipHash type HashSet = std::collections::HashSet; enum ColorRole { Foreground, Background, } /// Backend using BearLibTerminal pub struct Backend { buttons_pressed: HashSet, mouse_position: Vec2, } impl Backend { /// Creates a new BearLibTerminal-based backend. pub fn init() -> Box { // TODO: Add some error handling? terminal::open("Cursive", 80, 24); terminal::set(terminal::config::Window::empty().resizeable(true)); terminal::set(vec![ terminal::config::InputFilter::Group { group: terminal::config::InputFilterGroup::Keyboard, both: false, }, terminal::config::InputFilter::Group { group: terminal::config::InputFilterGroup::Mouse, both: true, }, ]); let c = Backend { buttons_pressed: HashSet::default(), mouse_position: Vec2::zero(), }; Box::new(c) } fn parse_next(&mut self) -> Option { // TODO: we could add backend-specific controls here. // Ex: ctrl+mouse wheel cause window cellsize to change terminal::read_event().map(|ev| { match ev { BltEvent::Close => Event::Exit, BltEvent::Resize { .. } => Event::WindowResize, // TODO: mouse support BltEvent::MouseMove { x, y } => { self.mouse_position = Vec2::new(x as usize, y as usize); // TODO: find out if a button is pressed? match self.buttons_pressed.iter().next() { None => Event::Refresh, Some(btn) => Event::Mouse { event: MouseEvent::Hold(*btn), position: self.mouse_position, offset: Vec2::zero(), }, } } BltEvent::MouseScroll { delta } => Event::Mouse { event: if delta < 0 { MouseEvent::WheelUp } else { MouseEvent::WheelDown }, position: self.mouse_position, offset: Vec2::zero(), }, BltEvent::KeyPressed { key, ctrl, shift } => { self.blt_keycode_to_ev(key, shift, ctrl) } // TODO: there's no Key::Shift/Ctrl for w/e reason BltEvent::ShiftPressed => Event::Refresh, BltEvent::ControlPressed => Event::Refresh, // TODO: what should we do here? BltEvent::KeyReleased { key, .. } => { // It's probably a mouse key. blt_keycode_to_mouse_button(key) .map(|btn| { self.buttons_pressed.remove(&btn); Event::Mouse { event: MouseEvent::Release(btn), position: self.mouse_position, offset: Vec2::zero(), } }) .unwrap_or_else(|| Event::Unknown(vec![])) } BltEvent::ShiftReleased | BltEvent::ControlReleased => { Event::Refresh } _ => Event::Unknown(vec![]), } }) } fn blt_keycode_to_ev( &mut self, kc: KeyCode, shift: bool, ctrl: bool, ) -> Event { match kc { KeyCode::F1 | KeyCode::F2 | KeyCode::F3 | KeyCode::F4 | KeyCode::F5 | KeyCode::F6 | KeyCode::F7 | KeyCode::F8 | KeyCode::F9 | KeyCode::F10 | KeyCode::F11 | KeyCode::F12 | KeyCode::NumEnter | KeyCode::Enter | KeyCode::Escape | KeyCode::Backspace | KeyCode::Tab | KeyCode::Pause | KeyCode::Insert | KeyCode::Home | KeyCode::PageUp | KeyCode::Delete | KeyCode::End | KeyCode::PageDown | KeyCode::Right | KeyCode::Left | KeyCode::Down | KeyCode::Up => match (shift, ctrl) { (true, true) => Event::CtrlShift(blt_keycode_to_key(kc)), (true, false) => Event::Shift(blt_keycode_to_key(kc)), (false, true) => Event::Ctrl(blt_keycode_to_key(kc)), (false, false) => Event::Key(blt_keycode_to_key(kc)), }, // TODO: mouse support KeyCode::MouseLeft | KeyCode::MouseRight | KeyCode::MouseMiddle | KeyCode::MouseFourth | KeyCode::MouseFifth => blt_keycode_to_mouse_button(kc) .map(|btn| { self.buttons_pressed.insert(btn); Event::Mouse { event: MouseEvent::Press(btn), position: self.mouse_position, offset: Vec2::zero(), } }) .unwrap_or_else(|| Event::Unknown(vec![])), KeyCode::A | KeyCode::B | KeyCode::C | KeyCode::D | KeyCode::E | KeyCode::F | KeyCode::G | KeyCode::H | KeyCode::I | KeyCode::J | KeyCode::K | KeyCode::L | KeyCode::M | KeyCode::N | KeyCode::O | KeyCode::P | KeyCode::Q | KeyCode::R | KeyCode::S | KeyCode::T | KeyCode::U | KeyCode::V | KeyCode::W | KeyCode::X | KeyCode::Y | KeyCode::Z | KeyCode::Row1 | KeyCode::Row2 | KeyCode::Row3 | KeyCode::Row4 | KeyCode::Row5 | KeyCode::Row6 | KeyCode::Row7 | KeyCode::Row8 | KeyCode::Row9 | KeyCode::Row0 | KeyCode::Grave | KeyCode::Minus | KeyCode::Equals | KeyCode::LeftBracket | KeyCode::RightBracket | KeyCode::Backslash | KeyCode::Semicolon | KeyCode::Apostrophe | KeyCode::Comma | KeyCode::Period | KeyCode::Slash | KeyCode::Space | KeyCode::NumDivide | KeyCode::NumMultiply | KeyCode::NumMinus | KeyCode::NumPlus | KeyCode::NumPeriod | KeyCode::Num1 | KeyCode::Num2 | KeyCode::Num3 | KeyCode::Num4 | KeyCode::Num5 | KeyCode::Num6 | KeyCode::Num7 | KeyCode::Num8 | KeyCode::Num9 | KeyCode::Num0 => { if ctrl { Event::CtrlChar(blt_keycode_to_char(kc, shift)) } else { Event::Char(blt_keycode_to_char(kc, shift)) } } } } } impl Drop for Backend { fn drop(&mut self) { terminal::close(); } } impl backend::Backend for Backend { fn name(&self) -> &str { "bear-lib-terminal" } fn set_title(&mut self, title: String) { terminal::set(terminal::config::Window::empty().title(title)); } fn set_color(&self, color: ColorPair) -> ColorPair { let current = ColorPair { front: blt_colour_to_colour(state::foreground()), back: blt_colour_to_colour(state::background()), }; let fg = colour_to_blt_colour(color.front, ColorRole::Foreground); let bg = colour_to_blt_colour(color.back, ColorRole::Background); terminal::set_colors(fg, bg); current } fn set_effect(&self, effect: Effect) { match effect { // TODO: does BLT support bold/italic/strikethrough/underline? Effect::Dim | Effect::Bold | Effect::Italic | Effect::Underline | Effect::Strikethrough | Effect::Blink | Effect::Simple => {} // TODO: how to do this correctly?` // BLT itself doesn't do this kind of thing, // we'd need the colours in our position, // but `f()` can do whatever Effect::Reverse => { terminal::set_colors(state::background(), state::foreground()) } } } fn unset_effect(&self, effect: Effect) { match effect { // TODO: does BLT support bold/italic/strikethrough/underline? Effect::Dim | Effect::Bold | Effect::Italic | Effect::Underline | Effect::Strikethrough | Effect::Blink | Effect::Simple => {} // The process of reversing is the same as unreversing Effect::Reverse => { terminal::set_colors(state::background(), state::foreground()) } } } fn has_colors(&self) -> bool { true } fn screen_size(&self) -> Vec2 { let Size { width, height } = terminal::state::size(); (width, height).into() } fn clear(&self, color: Color) { terminal::set_background(colour_to_blt_colour( color, ColorRole::Background, )); terminal::clear(None); } fn refresh(&mut self) { terminal::refresh(); } fn print_at(&self, pos: Vec2, text: &str) { terminal::print_xy(pos.x as i32, pos.y as i32, text); } fn poll_event(&mut self) -> Option { self.parse_next() } } fn blt_colour_to_colour(c: BltColor) -> Color { Color::Rgb(c.red, c.green, c.blue) } fn colour_to_blt_colour(clr: Color, role: ColorRole) -> BltColor { let (r, g, b) = match clr { Color::TerminalDefault => { let clr = match role { ColorRole::Foreground => state::foreground(), ColorRole::Background => state::background(), }; return clr; } // Colours taken from // https://en.wikipedia.org/wiki/ANSI_escape_code#Colors Color::Dark(BaseColor::Black) => (0, 0, 0), Color::Dark(BaseColor::Red) => (170, 0, 0), Color::Dark(BaseColor::Green) => (0, 170, 0), Color::Dark(BaseColor::Yellow) => (170, 85, 0), Color::Dark(BaseColor::Blue) => (0, 0, 170), Color::Dark(BaseColor::Magenta) => (170, 0, 170), Color::Dark(BaseColor::Cyan) => (0, 170, 170), Color::Dark(BaseColor::White) => (170, 170, 170), Color::Light(BaseColor::Black) => (85, 85, 85), Color::Light(BaseColor::Red) => (255, 85, 85), Color::Light(BaseColor::Green) => (85, 255, 85), Color::Light(BaseColor::Yellow) => (255, 255, 85), Color::Light(BaseColor::Blue) => (85, 85, 255), Color::Light(BaseColor::Magenta) => (255, 85, 255), Color::Light(BaseColor::Cyan) => (85, 255, 255), Color::Light(BaseColor::White) => (255, 255, 255), Color::Rgb(r, g, b) => (r, g, b), Color::RgbLowRes(r, g, b) => ( (f32::from(r) / 5.0 * 255.0) as u8, (f32::from(g) / 5.0 * 255.0) as u8, (f32::from(b) / 5.0 * 255.0) as u8, ), }; BltColor::from_rgb(r, g, b) } fn blt_keycode_to_char(kc: KeyCode, shift: bool) -> char { match bear_lib_terminal::terminal::state::char() { '\u{0}' => blt_keycode_to_char_impl(kc, shift), c => c, } } #[allow(clippy::cognitive_complexity)] fn blt_keycode_to_char_impl(kc: KeyCode, shift: bool) -> char { match kc { KeyCode::A if shift => 'A', KeyCode::A => 'a', KeyCode::B if shift => 'B', KeyCode::B => 'b', KeyCode::C if shift => 'C', KeyCode::C => 'c', KeyCode::D if shift => 'D', KeyCode::D => 'd', KeyCode::E if shift => 'E', KeyCode::E => 'e', KeyCode::F if shift => 'F', KeyCode::F => 'f', KeyCode::G if shift => 'G', KeyCode::G => 'g', KeyCode::H if shift => 'H', KeyCode::H => 'h', KeyCode::I if shift => 'I', KeyCode::I => 'i', KeyCode::J if shift => 'J', KeyCode::J => 'j', KeyCode::K if shift => 'K', KeyCode::K => 'k', KeyCode::L if shift => 'L', KeyCode::L => 'l', KeyCode::M if shift => 'M', KeyCode::M => 'm', KeyCode::N if shift => 'N', KeyCode::N => 'n', KeyCode::O if shift => 'O', KeyCode::O => 'o', KeyCode::P if shift => 'P', KeyCode::P => 'p', KeyCode::Q if shift => 'Q', KeyCode::Q => 'q', KeyCode::R if shift => 'R', KeyCode::R => 'r', KeyCode::S if shift => 'S', KeyCode::S => 's', KeyCode::T if shift => 'T', KeyCode::T => 't', KeyCode::U if shift => 'U', KeyCode::U => 'u', KeyCode::V if shift => 'V', KeyCode::V => 'v', KeyCode::W if shift => 'W', KeyCode::W => 'w', KeyCode::X if shift => 'X', KeyCode::X => 'x', KeyCode::Y if shift => 'Y', KeyCode::Y => 'y', KeyCode::Z if shift => 'Z', KeyCode::Z => 'z', KeyCode::Row1 if shift => '!', KeyCode::Row1 => '1', KeyCode::Row2 if shift => '@', KeyCode::Row2 => '2', KeyCode::Row3 if shift => '#', KeyCode::Row3 => '3', KeyCode::Row4 if shift => '$', KeyCode::Row4 => '4', KeyCode::Row5 if shift => '%', KeyCode::Row5 => '5', KeyCode::Row6 if shift => '^', KeyCode::Row6 => '6', KeyCode::Row7 if shift => '&', KeyCode::Row7 => '7', KeyCode::Row8 if shift => '*', KeyCode::Row8 => '8', KeyCode::Row9 if shift => '(', KeyCode::Row9 => '9', KeyCode::Row0 if shift => ')', KeyCode::Row0 => '0', KeyCode::Grave if shift => '~', KeyCode::Grave => '`', KeyCode::Minus if shift => '_', KeyCode::Minus => '-', KeyCode::Equals if shift => '+', KeyCode::Equals => '=', KeyCode::LeftBracket if shift => '{', KeyCode::LeftBracket => '[', KeyCode::RightBracket if shift => '}', KeyCode::RightBracket => ']', KeyCode::Backslash if shift => '|', KeyCode::Backslash => '\\', KeyCode::Semicolon if shift => ':', KeyCode::Semicolon => ';', KeyCode::Apostrophe if shift => '"', KeyCode::Apostrophe => '\'', KeyCode::Comma if shift => '<', KeyCode::Comma => ',', KeyCode::Period if shift => '>', KeyCode::Period => '.', KeyCode::Slash if shift => '?', KeyCode::Slash => '/', KeyCode::Space => ' ', KeyCode::NumDivide => '/', KeyCode::NumMultiply => '*', KeyCode::NumMinus => '-', KeyCode::NumPlus => '+', KeyCode::NumPeriod => '.', KeyCode::Num1 => '1', KeyCode::Num2 => '2', KeyCode::Num3 => '3', KeyCode::Num4 => '4', KeyCode::Num5 => '5', KeyCode::Num6 => '6', KeyCode::Num7 => '7', KeyCode::Num8 => '8', KeyCode::Num9 => '9', KeyCode::Num0 => '0', _ => unreachable!("Found unknown input: {:?}", kc), } } fn blt_keycode_to_key(kc: KeyCode) -> Key { match kc { KeyCode::F1 => Key::F1, KeyCode::F2 => Key::F2, KeyCode::F3 => Key::F3, KeyCode::F4 => Key::F4, KeyCode::F5 => Key::F5, KeyCode::F6 => Key::F6, KeyCode::F7 => Key::F7, KeyCode::F8 => Key::F8, KeyCode::F9 => Key::F9, KeyCode::F10 => Key::F10, KeyCode::F11 => Key::F11, KeyCode::F12 => Key::F12, KeyCode::NumEnter | KeyCode::Enter => Key::Enter, KeyCode::Escape => Key::Esc, KeyCode::Backspace => Key::Backspace, KeyCode::Tab => Key::Tab, KeyCode::Pause => Key::PauseBreak, KeyCode::Insert => Key::Ins, KeyCode::Home => Key::Home, KeyCode::PageUp => Key::PageUp, KeyCode::Delete => Key::Del, KeyCode::End => Key::End, KeyCode::PageDown => Key::PageDown, KeyCode::Right => Key::Right, KeyCode::Left => Key::Left, KeyCode::Down => Key::Down, KeyCode::Up => Key::Up, _ => unreachable!(), } } fn blt_keycode_to_mouse_button(kc: KeyCode) -> Option { Some(match kc { KeyCode::MouseLeft => MouseButton::Left, KeyCode::MouseRight => MouseButton::Right, KeyCode::MouseMiddle => MouseButton::Middle, _ => return None, }) } cursive-0.20.0/src/backends/crossterm.rs000064400000000000000000000322411046102023000163150ustar 00000000000000//! Backend using the pure-rust crossplatform crossterm library. //! //! Requires the `crossterm-backend` feature. #![cfg(feature = "crossterm")] #![cfg_attr(feature = "doc-cfg", doc(cfg(feature = "crossterm-backend")))] use std::{ cell::{Cell, RefCell, RefMut}, io::{self, BufWriter, Write}, time::Duration, }; pub use crossterm; use crossterm::{ cursor::{Hide, MoveTo, Show}, event::{ poll, read, DisableMouseCapture, EnableMouseCapture, Event as CEvent, KeyCode, KeyEvent as CKeyEvent, KeyModifiers, MouseButton as CMouseButton, MouseEvent as CMouseEvent, MouseEventKind, }, execute, queue, style::{ Attribute, Color, Print, SetAttribute, SetBackgroundColor, SetForegroundColor, }, terminal::{ self, disable_raw_mode, enable_raw_mode, Clear, ClearType, EnterAlternateScreen, LeaveAlternateScreen, }, }; use crate::{ backend, event::{Event, Key, MouseButton, MouseEvent}, theme, Vec2, }; #[cfg(windows)] type Stdout = io::Stdout; #[cfg(unix)] type Stdout = std::fs::File; /// Backend using crossterm pub struct Backend { current_style: Cell, stdout: RefCell>, } fn translate_button(button: CMouseButton) -> MouseButton { match button { CMouseButton::Left => MouseButton::Left, CMouseButton::Right => MouseButton::Right, CMouseButton::Middle => MouseButton::Middle, } } fn translate_key(code: KeyCode) -> Option { Some(match code { KeyCode::Esc => Key::Esc, KeyCode::Backspace => Key::Backspace, KeyCode::Left => Key::Left, KeyCode::Right => Key::Right, KeyCode::Up => Key::Up, KeyCode::Down => Key::Down, KeyCode::Home => Key::Home, KeyCode::End => Key::End, KeyCode::PageUp => Key::PageUp, KeyCode::PageDown => Key::PageDown, KeyCode::Delete => Key::Del, KeyCode::Insert => Key::Ins, KeyCode::Enter => Key::Enter, KeyCode::Tab => Key::Tab, KeyCode::F(n) => Key::from_f(n), KeyCode::BackTab => Key::Tab, /* not supported */ // These should never occur. _ => return None, }) } fn translate_event(event: CKeyEvent) -> Option { const CTRL_ALT: KeyModifiers = KeyModifiers::from_bits_truncate( KeyModifiers::CONTROL.bits() | KeyModifiers::ALT.bits(), ); const CTRL_SHIFT: KeyModifiers = KeyModifiers::from_bits_truncate( KeyModifiers::CONTROL.bits() | KeyModifiers::SHIFT.bits(), ); const ALT_SHIFT: KeyModifiers = KeyModifiers::from_bits_truncate( KeyModifiers::ALT.bits() | KeyModifiers::SHIFT.bits(), ); Some(match event { // Handle Char + modifier. CKeyEvent { modifiers: KeyModifiers::CONTROL, code: KeyCode::Char(c), .. } => Event::CtrlChar(c), CKeyEvent { modifiers: KeyModifiers::ALT, code: KeyCode::Char(c), .. } => Event::AltChar(c), CKeyEvent { modifiers: KeyModifiers::SHIFT, code: KeyCode::Char(c), .. } => Event::Char(c), CKeyEvent { code: KeyCode::Char(c), .. } => Event::Char(c), // From now on, assume the key is never a `Char`. // Explicitly handle 'backtab' since crossterm does not sent SHIFT alongside the back tab key. CKeyEvent { code: KeyCode::BackTab, .. } => Event::Shift(Key::Tab), // Handle key + multiple modifiers CKeyEvent { modifiers: CTRL_ALT, code, .. } => Event::CtrlAlt(translate_key(code)?), CKeyEvent { modifiers: CTRL_SHIFT, code, .. } => Event::CtrlShift(translate_key(code)?), CKeyEvent { modifiers: ALT_SHIFT, code, .. } => Event::AltShift(translate_key(code)?), // Handle key + single modifier CKeyEvent { modifiers: KeyModifiers::CONTROL, code, .. } => Event::Ctrl(translate_key(code)?), CKeyEvent { modifiers: KeyModifiers::ALT, code, .. } => Event::Alt(translate_key(code)?), CKeyEvent { modifiers: KeyModifiers::SHIFT, code, .. } => Event::Shift(translate_key(code)?), // All other keys. CKeyEvent { code, .. } => Event::Key(translate_key(code)?), }) } fn translate_color(base_color: theme::Color) -> Color { match base_color { theme::Color::Dark(theme::BaseColor::Black) => Color::Black, theme::Color::Dark(theme::BaseColor::Red) => Color::DarkRed, theme::Color::Dark(theme::BaseColor::Green) => Color::DarkGreen, theme::Color::Dark(theme::BaseColor::Yellow) => Color::DarkYellow, theme::Color::Dark(theme::BaseColor::Blue) => Color::DarkBlue, theme::Color::Dark(theme::BaseColor::Magenta) => Color::DarkMagenta, theme::Color::Dark(theme::BaseColor::Cyan) => Color::DarkCyan, theme::Color::Dark(theme::BaseColor::White) => Color::Grey, theme::Color::Light(theme::BaseColor::Black) => Color::DarkGrey, theme::Color::Light(theme::BaseColor::Red) => Color::Red, theme::Color::Light(theme::BaseColor::Green) => Color::Green, theme::Color::Light(theme::BaseColor::Yellow) => Color::Yellow, theme::Color::Light(theme::BaseColor::Blue) => Color::Blue, theme::Color::Light(theme::BaseColor::Magenta) => Color::Magenta, theme::Color::Light(theme::BaseColor::Cyan) => Color::Cyan, theme::Color::Light(theme::BaseColor::White) => Color::White, theme::Color::Rgb(r, g, b) => Color::Rgb { r, g, b }, theme::Color::RgbLowRes(r, g, b) => { debug_assert!(r <= 5, "Red color fragment (r = {}) is out of bound. Make sure r ≤ 5.", r); debug_assert!(g <= 5, "Green color fragment (g = {}) is out of bound. Make sure g ≤ 5.", g); debug_assert!(b <= 5, "Blue color fragment (b = {}) is out of bound. Make sure b ≤ 5.", b); Color::AnsiValue(16 + 36 * r + 6 * g + b) } theme::Color::TerminalDefault => Color::Reset, } } impl Backend { /// Creates a new crossterm backend. pub fn init() -> Result, crossterm::ErrorKind> where Self: Sized, { enable_raw_mode()?; // TODO: Use the stdout we define down there execute!( io::stdout(), EnterAlternateScreen, EnableMouseCapture, Hide )?; #[cfg(unix)] let stdout = RefCell::new(BufWriter::new(std::fs::File::create("/dev/tty")?)); #[cfg(windows)] let stdout = RefCell::new(BufWriter::new(io::stdout())); Ok(Box::new(Backend { current_style: Cell::new(theme::ColorPair::from_256colors(0, 0)), stdout, })) } fn apply_colors(&self, colors: theme::ColorPair) { self.with_stdout(|stdout| { queue!( stdout, SetForegroundColor(translate_color(colors.front)), SetBackgroundColor(translate_color(colors.back)) ) .unwrap() }); } fn stdout_mut(&self) -> RefMut> { self.stdout.borrow_mut() } fn with_stdout(&self, f: impl FnOnce(&mut BufWriter)) { f(&mut *self.stdout_mut()); } fn set_attr(&self, attr: Attribute) { self.with_stdout(|stdout| queue!(stdout, SetAttribute(attr)).unwrap()); } fn map_key(&mut self, event: CEvent) -> Option { Some(match event { CEvent::Key(key_event) => translate_event(key_event)?, CEvent::Mouse(CMouseEvent { kind, column, row, modifiers: _, }) => { let position = (column, row).into(); let event = match kind { MouseEventKind::Down(button) => { MouseEvent::Press(translate_button(button)) } MouseEventKind::Up(button) => { MouseEvent::Release(translate_button(button)) } MouseEventKind::Drag(button) => { MouseEvent::Hold(translate_button(button)) } MouseEventKind::Moved => { return None; } MouseEventKind::ScrollDown => MouseEvent::WheelDown, MouseEventKind::ScrollUp => MouseEvent::WheelUp, }; Event::Mouse { event, position, offset: Vec2::zero(), } } CEvent::Resize(_, _) => Event::WindowResize, CEvent::Paste(_) => { unreachable!("Did not enable bracketed paste.") } CEvent::FocusGained | CEvent::FocusLost => return None, }) } } impl Drop for Backend { fn drop(&mut self) { // We have to execute the show cursor command at the `stdout`. self.with_stdout(|stdout| { execute!(stdout, LeaveAlternateScreen, DisableMouseCapture, Show) .expect("Can not disable mouse capture or show cursor.") }); disable_raw_mode().unwrap(); } } impl backend::Backend for Backend { fn poll_event(&mut self) -> Option { match poll(Duration::from_millis(1)) { Ok(true) => match read() { Ok(event) => match self.map_key(event) { Some(event) => Some(event), None => self.poll_event(), }, Err(e) => panic!("{:?}", e), }, _ => None, } } fn set_title(&mut self, title: String) { self.with_stdout(|stdout| { execute!(stdout, terminal::SetTitle(title)).unwrap() }); } fn refresh(&mut self) { self.with_stdout(|stdout| stdout.flush().unwrap()); } fn has_colors(&self) -> bool { // TODO: color support detection? true } fn screen_size(&self) -> Vec2 { let size = terminal::size().unwrap_or((1, 1)); Vec2::from(size) } fn print_at(&self, pos: Vec2, text: &str) { self.with_stdout(|stdout| { queue!(stdout, MoveTo(pos.x as u16, pos.y as u16), Print(text)) .unwrap() }); } fn print_at_rep(&self, pos: Vec2, repetitions: usize, text: &str) { if repetitions > 0 { self.with_stdout(|out| { queue!(out, MoveTo(pos.x as u16, pos.y as u16)).unwrap(); out.write_all(text.as_bytes()).unwrap(); let mut dupes_left = repetitions - 1; while dupes_left > 0 { out.write_all(text.as_bytes()).unwrap(); dupes_left -= 1; } }); } } fn clear(&self, color: theme::Color) { self.apply_colors(theme::ColorPair { front: color, back: color, }); self.with_stdout(|stdout| { queue!(stdout, Clear(ClearType::All)).unwrap() }); } fn set_color(&self, color: theme::ColorPair) -> theme::ColorPair { let current_style = self.current_style.get(); if current_style != color { self.apply_colors(color); self.current_style.set(color); } current_style } fn set_effect(&self, effect: theme::Effect) { match effect { theme::Effect::Simple => (), theme::Effect::Reverse => self.set_attr(Attribute::Reverse), theme::Effect::Dim => self.set_attr(Attribute::Dim), theme::Effect::Bold => self.set_attr(Attribute::Bold), theme::Effect::Blink => self.set_attr(Attribute::SlowBlink), theme::Effect::Italic => self.set_attr(Attribute::Italic), theme::Effect::Strikethrough => { self.set_attr(Attribute::CrossedOut) } theme::Effect::Underline => self.set_attr(Attribute::Underlined), } } fn unset_effect(&self, effect: theme::Effect) { match effect { theme::Effect::Simple => (), theme::Effect::Reverse => self.set_attr(Attribute::NoReverse), theme::Effect::Dim | theme::Effect::Bold => { self.set_attr(Attribute::NormalIntensity) } theme::Effect::Blink => self.set_attr(Attribute::NoBlink), theme::Effect::Italic => self.set_attr(Attribute::NoItalic), theme::Effect::Strikethrough => { self.set_attr(Attribute::NotCrossedOut) } theme::Effect::Underline => self.set_attr(Attribute::NoUnderline), } } fn name(&self) -> &str { "crossterm" } } cursive-0.20.0/src/backends/curses/mod.rs000064400000000000000000000112401046102023000163530ustar 00000000000000//! Common module for the ncurses and pancurses backends. //! //! Requires either of `ncurses-backend` or `pancurses-backend`. #![cfg(any(feature = "ncurses-backend", feature = "pancurses-backend"))] use crate::event::{Event, Key}; use crate::theme::{BaseColor, Color, ColorPair}; use maplit::hashmap; pub mod n; pub mod pan; // Use AHash instead of the slower SipHash type HashMap = std::collections::HashMap; /// Split a i32 into individual bytes, little endian (least significant byte first). fn split_i32(code: i32) -> Vec { (0..4).map(|i| ((code >> (8 * i)) & 0xFF) as u8).collect() } fn fill_key_codes(target: &mut HashMap, f: F) where F: Fn(i32) -> Option, { let key_names = hashmap! { "DC" => Key::Del, "DN" => Key::Down, "END" => Key::End, "HOM" => Key::Home, "IC" => Key::Ins, "LFT" => Key::Left, "NXT" => Key::PageDown, "PRV" => Key::PageUp, "RIT" => Key::Right, "UP" => Key::Up, }; for code in 512..1024 { let name = match f(code) { Some(name) => name, None => continue, }; if !name.starts_with('k') { continue; } let (key_name, modifier) = name[1..].split_at(name.len() - 2); let key = match key_names.get(key_name) { Some(&key) => key, None => continue, }; let event = match modifier { "3" => Event::Alt(key), "4" => Event::AltShift(key), "5" => Event::Ctrl(key), "6" => Event::CtrlShift(key), "7" => Event::CtrlAlt(key), _ => continue, }; target.insert(code, event); } } fn find_closest_pair(pair: ColorPair, max_colors: i16) -> (i16, i16) { ( find_closest(pair.front, max_colors), find_closest(pair.back, max_colors), ) } /// Finds the closest index in the 256-color palette. /// /// If `max_colors` is less than 256 (like 8 or 16), the color will be /// downgraded to the closest one available. fn find_closest(color: Color, max_colors: i16) -> i16 { let max_colors = std::cmp::max(max_colors, 8); match color { Color::TerminalDefault => -1, Color::Dark(BaseColor::Black) => 0, Color::Dark(BaseColor::Red) => 1, Color::Dark(BaseColor::Green) => 2, Color::Dark(BaseColor::Yellow) => 3, Color::Dark(BaseColor::Blue) => 4, Color::Dark(BaseColor::Magenta) => 5, Color::Dark(BaseColor::Cyan) => 6, Color::Dark(BaseColor::White) => 7, Color::Light(BaseColor::Black) => 8 % max_colors, Color::Light(BaseColor::Red) => 9 % max_colors, Color::Light(BaseColor::Green) => 10 % max_colors, Color::Light(BaseColor::Yellow) => 11 % max_colors, Color::Light(BaseColor::Blue) => 12 % max_colors, Color::Light(BaseColor::Magenta) => 13 % max_colors, Color::Light(BaseColor::Cyan) => 14 % max_colors, Color::Light(BaseColor::White) => 15 % max_colors, Color::Rgb(r, g, b) if max_colors >= 256 => { // If r = g = b, it may be a grayscale value! // Grayscale colors have a bit higher resolution than the rest of // the palette, so if we can use it we should! // // r=g=b < 8 should go to pure black instead. // r=g=b >= 247 should go to pure white. // TODO: project almost-gray colors as well? if r == g && g == b && (8..247).contains(&r) { // The grayscale palette says the colors 232+n are: // (r = g = b) = 8 + 10 * n // With 0 <= n <= 23. This gives: // (r - 8) / 10 = n let n = (r - 8) / 10; i16::from(232 + n) } else { // Generic RGB let r = 6 * u16::from(r) / 256; let g = 6 * u16::from(g) / 256; let b = 6 * u16::from(b) / 256; (16 + 36 * r + 6 * g + b) as i16 } } Color::Rgb(r, g, b) => { // Have to hack it down to 8 colors. let r = if r > 127 { 1 } else { 0 }; let g = if g > 127 { 1 } else { 0 }; let b = if b > 127 { 1 } else { 0 }; (r + 2 * g + 4 * b) as i16 } Color::RgbLowRes(r, g, b) if max_colors >= 256 => { i16::from(16 + 36 * r + 6 * g + b) } Color::RgbLowRes(r, g, b) => { let r = if r > 2 { 1 } else { 0 }; let g = if g > 2 { 1 } else { 0 }; let b = if b > 2 { 1 } else { 0 }; (r + 2 * g + 4 * b) as i16 } } } cursive-0.20.0/src/backends/curses/n.rs000064400000000000000000000456351046102023000160500ustar 00000000000000//! Ncurses-specific backend. #![cfg(feature = "ncurses-backend")] #![cfg_attr(feature = "doc-cfg", doc(cfg(feature = "ncurses-backend")))] pub use ncurses; use log::{debug, warn}; use ncurses::mmask_t; use std::cell::{Cell, RefCell}; use std::ffi::CString; use std::fs::File; use std::io; use std::io::Write; use crate::backend; use crate::event::{Event, Key, MouseButton, MouseEvent}; use crate::theme::{Color, ColorPair, Effect}; use crate::utf8; use crate::Vec2; use super::split_i32; // Use AHash instead of the slower SipHash type HashMap = std::collections::HashMap; /// Backend using ncurses. pub struct Backend { current_style: Cell, // Maps (front, back) ncurses colors to ncurses pairs pairs: RefCell>, // Pre-computed map of ncurses codes to parsed Event key_codes: HashMap, // Remember the last pressed button to correctly feed Released Event last_mouse_button: Option, // Sometimes a code from ncurses should be split in two Events. // // So remember the one we didn't return. input_buffer: Option, } fn find_closest_pair(pair: ColorPair) -> (i16, i16) { super::find_closest_pair(pair, ncurses::COLORS() as i16) } /// Writes some bytes directly to `/dev/tty` /// /// Since this is not going to be used often, we can afford to re-open the /// file every time. fn write_to_tty(bytes: &[u8]) -> io::Result<()> { let mut tty_output = File::create("/dev/tty").expect("cursive can only run with a tty"); tty_output.write_all(bytes)?; // tty_output will be flushed automatically at the end of the function. Ok(()) } impl Backend { /// Creates a new ncurses-based backend. /// /// Uses `/dev/tty` for input/output. pub fn init() -> io::Result> { Self::init_with_files("/dev/tty", "/dev/tty") } /// Creates a new ncurses-based backend. /// /// Uses stdin/stdout for input/output. pub fn init_stdio() -> io::Result> { Self::init_with_files("/dev/stdin", "/dev/stdout") } /// Creates a new ncurses-based backend using the given files for input/output. pub fn init_with_files( input_path: &str, output_path: &str, ) -> io::Result> { // Check the $TERM variable. if std::env::var("TERM") .map(|var| var.is_empty()) .unwrap_or(true) { return Err(io::Error::new( io::ErrorKind::Other, "$TERM is unset. Cannot initialize ncurses interface.", )); } // Change the locale. // For some reasons it's mandatory to get some UTF-8 support. let buf = CString::new("").unwrap(); unsafe { libc::setlocale(libc::LC_ALL, buf.as_ptr()) }; // The delay is the time ncurses wait after pressing ESC // to see if it's an escape sequence. // Default delay is way too long. 25 is imperceptible yet works fine. ::std::env::set_var("ESCDELAY", "25"); // Don't output to standard IO, directly feed into /dev/tty // This leaves stdin and stdout usable for other purposes. let input = { let mode = CString::new("r").unwrap(); let path = CString::new(input_path).unwrap(); unsafe { libc::fopen(path.as_ptr(), mode.as_ptr()) } }; let output = { let mode = CString::new("w").unwrap(); let path = CString::new(output_path).unwrap(); unsafe { libc::fopen(path.as_ptr(), mode.as_ptr()) } }; ncurses::newterm(None, output, input); // Enable keypad (like arrows) ncurses::keypad(ncurses::stdscr(), true); // This disables mouse click detection, // and provides 0-delay access to mouse presses. ncurses::mouseinterval(0); // Listen to all mouse events. ncurses::mousemask( (ncurses::ALL_MOUSE_EVENTS | ncurses::REPORT_MOUSE_POSITION) as mmask_t, None, ); // Enable non-blocking input, so getch() immediately returns. ncurses::timeout(0); // Don't echo user input, we'll take care of that ncurses::noecho(); // This disables buffering and some input processing. ncurses::raw(); // This enables color support. ncurses::start_color(); // Pick up background and text color from the terminal theme. ncurses::use_default_colors(); // Don't print cursors. ncurses::curs_set(ncurses::CURSOR_VISIBILITY::CURSOR_INVISIBLE); // This asks the terminal to provide us with mouse drag events // (Mouse move when a button is pressed). // Replacing 1002 with 1003 would give us ANY mouse move. write_to_tty(b"\x1B[?1002h")?; let c = Backend { current_style: Cell::new(ColorPair::from_256colors(0, 0)), pairs: RefCell::new(HashMap::default()), key_codes: initialize_keymap(), last_mouse_button: None, input_buffer: None, }; Ok(Box::new(c)) } /// Save a new color pair. fn insert_color( &self, pairs: &mut HashMap<(i16, i16), i16>, (front, back): (i16, i16), ) -> i16 { let n = 1 + pairs.len() as i16; let target = if ncurses::COLOR_PAIRS() > i32::from(n) { // We still have plenty of space for everyone. n } else { // The world is too small for both of us. let target = n - 1; // Remove the mapping to n-1 pairs.retain(|_, &mut v| v != target); target }; pairs.insert((front, back), target); ncurses::init_pair(target, front, back); target } /// Checks the pair in the cache, or re-define a color if needed. fn get_or_create(&self, pair: ColorPair) -> i16 { let mut pairs = self.pairs.borrow_mut(); // Find if we have this color in stock let result = find_closest_pair(pair); let lookup = pairs.get(&result).copied(); lookup.unwrap_or_else(|| self.insert_color(&mut pairs, result)) } fn set_colors(&self, pair: ColorPair) { let i = self.get_or_create(pair); self.current_style.set(pair); let style = ncurses::COLOR_PAIR(i); ncurses::attron(style); } fn parse_next(&mut self) -> Option { if let Some(event) = self.input_buffer.take() { return Some(event); } let ch: i32 = ncurses::getch(); // Non-blocking input will return -1 as long as no input is available. if ch == -1 { return None; } // Is it a UTF-8 starting point? let event = if (32..=255).contains(&ch) && ch != 127 { utf8::read_char(ch as u8, || Some(ncurses::getch() as u8)) .map(Event::Char) .unwrap_or_else(|e| { warn!("Error reading input: {}", e); Event::Unknown(vec![ch as u8]) }) } else { self.parse_ncurses_char(ch) }; Some(event) } fn parse_ncurses_char(&mut self, ch: i32) -> Event { // eprintln!("Found {:?}", ncurses::keyname(ch)); if ch == ncurses::KEY_MOUSE { self.parse_mouse_event() } else { self.key_codes .get(&ch) .cloned() .unwrap_or_else(|| Event::Unknown(split_i32(ch))) } } fn parse_mouse_event(&mut self) -> Event { let mut mevent = ncurses::MEVENT { id: 0, x: 0, y: 0, z: 0, bstate: 0, }; if ncurses::getmouse(&mut mevent as *mut ncurses::MEVENT) == ncurses::OK { // Currently unused let _ctrl = (mevent.bstate & ncurses::BUTTON_CTRL as mmask_t) != 0; let _shift = (mevent.bstate & ncurses::BUTTON_SHIFT as mmask_t) != 0; let _alt = (mevent.bstate & ncurses::BUTTON_ALT as mmask_t) != 0; // Keep the base state, without the modifiers mevent.bstate &= !(ncurses::BUTTON_SHIFT | ncurses::BUTTON_ALT | ncurses::BUTTON_CTRL) as mmask_t; // This makes a full `Event` from a `MouseEvent`. let make_event = |event| Event::Mouse { offset: Vec2::zero(), position: Vec2::new(mevent.x as usize, mevent.y as usize), event, }; if mevent.bstate == ncurses::REPORT_MOUSE_POSITION as mmask_t { // The event is either a mouse drag event, // or a weird double-release event. :S self.last_mouse_button .map(MouseEvent::Hold) .or_else(|| { // In legacy mode, some buttons overlap, // so we need to disambiguate. (mevent.bstate == ncurses::BUTTON5_DOUBLE_CLICKED as mmask_t) .then_some(MouseEvent::WheelDown) }) .map(make_event) .unwrap_or_else(|| Event::Unknown(vec![])) } else { // Identify the button let mut bare_event = mevent.bstate & ((1 << 25) - 1); let mut event = None; // ncurses encodes multiple events in the same value. while bare_event != 0 { let single_event = 1 << bare_event.trailing_zeros(); bare_event ^= single_event; // Process single_event on_mouse_event(single_event as i32, |e| { // Keep one event for later, // send the rest through the channel. if event.is_none() { event = Some(e); } else { self.input_buffer = Some(make_event(e)); } }); } if let Some(event) = event { match event { MouseEvent::Press(btn) => { self.last_mouse_button = Some(btn); } MouseEvent::Release(_) => { self.last_mouse_button = None; } _ => (), } make_event(event) } else { debug!("No event parsed?..."); Event::Unknown(vec![]) } } } else { debug!("Ncurses event not recognized."); Event::Unknown(vec![]) } } } impl Drop for Backend { fn drop(&mut self) { write_to_tty(b"\x1B[?1002l").unwrap(); ncurses::endwin(); } } impl backend::Backend for Backend { fn name(&self) -> &str { "ncurses" } fn set_title(&mut self, title: String) { write_to_tty(format!("\x1B]0;{}\x07", title).as_bytes()).unwrap(); } fn screen_size(&self) -> Vec2 { let mut x: i32 = 0; let mut y: i32 = 0; ncurses::getmaxyx(ncurses::stdscr(), &mut y, &mut x); (x, y).into() } fn has_colors(&self) -> bool { ncurses::has_colors() } fn poll_event(&mut self) -> Option { self.parse_next() } fn set_color(&self, colors: ColorPair) -> ColorPair { // eprintln!("Color used: {:?}", colors); let current = self.current_style.get(); if current != colors { self.set_colors(colors); } current } fn set_effect(&self, effect: Effect) { let style = match effect { Effect::Reverse => ncurses::A_REVERSE(), Effect::Simple => ncurses::A_NORMAL(), Effect::Dim => ncurses::A_DIM(), Effect::Bold => ncurses::A_BOLD(), Effect::Blink => ncurses::A_BLINK(), Effect::Italic => ncurses::A_ITALIC(), Effect::Strikethrough => ncurses::A_NORMAL(), Effect::Underline => ncurses::A_UNDERLINE(), }; ncurses::attron(style); } fn unset_effect(&self, effect: Effect) { let style = match effect { Effect::Reverse => ncurses::A_REVERSE(), Effect::Simple => ncurses::A_NORMAL(), Effect::Dim => ncurses::A_DIM(), Effect::Bold => ncurses::A_BOLD(), Effect::Blink => ncurses::A_BLINK(), Effect::Italic => ncurses::A_ITALIC(), Effect::Strikethrough => ncurses::A_NORMAL(), Effect::Underline => ncurses::A_UNDERLINE(), }; ncurses::attroff(style); } fn clear(&self, color: Color) { let id = self.get_or_create(ColorPair { front: color, back: color, }); ncurses::wbkgd(ncurses::stdscr(), ncurses::COLOR_PAIR(id)); ncurses::clear(); } fn refresh(&mut self) { ncurses::refresh(); } fn print_at(&self, pos: Vec2, text: &str) { ncurses::mvaddstr(pos.y as i32, pos.x as i32, text); } fn print_at_rep(&self, pos: Vec2, repetitions: usize, text: &str) { if repetitions > 0 { ncurses::mvaddstr(pos.y as i32, pos.x as i32, text); let mut dupes_left = repetitions - 1; while dupes_left > 0 { ncurses::addstr(text); dupes_left -= 1; } } } } /// Returns the Key enum corresponding to the given ncurses event. fn get_mouse_button(bare_event: i32) -> MouseButton { match bare_event { ncurses::BUTTON1_RELEASED | ncurses::BUTTON1_PRESSED | ncurses::BUTTON1_CLICKED | ncurses::BUTTON1_DOUBLE_CLICKED | ncurses::BUTTON1_TRIPLE_CLICKED => MouseButton::Left, ncurses::BUTTON2_RELEASED | ncurses::BUTTON2_PRESSED | ncurses::BUTTON2_CLICKED | ncurses::BUTTON2_DOUBLE_CLICKED | ncurses::BUTTON2_TRIPLE_CLICKED => MouseButton::Middle, ncurses::BUTTON3_RELEASED | ncurses::BUTTON3_PRESSED | ncurses::BUTTON3_CLICKED | ncurses::BUTTON3_DOUBLE_CLICKED | ncurses::BUTTON3_TRIPLE_CLICKED => MouseButton::Right, ncurses::BUTTON4_RELEASED | ncurses::BUTTON4_PRESSED | ncurses::BUTTON4_CLICKED | ncurses::BUTTON4_DOUBLE_CLICKED | ncurses::BUTTON4_TRIPLE_CLICKED => MouseButton::Button4, ncurses::BUTTON5_RELEASED | ncurses::BUTTON5_PRESSED | ncurses::BUTTON5_CLICKED | ncurses::BUTTON5_DOUBLE_CLICKED | ncurses::BUTTON5_TRIPLE_CLICKED => MouseButton::Button5, _ => MouseButton::Other, } } /// Parse the given code into one or more event. /// /// If the given event code should expend into multiple events /// (for instance click expends into PRESS + RELEASE), /// the returned Vec will include those queued events. /// /// The main event is returned separately to avoid allocation in most cases. fn on_mouse_event(bare_event: i32, mut f: F) where F: FnMut(MouseEvent), { let button = get_mouse_button(bare_event); match bare_event { ncurses::BUTTON1_RELEASED | ncurses::BUTTON2_RELEASED | ncurses::BUTTON3_RELEASED => f(MouseEvent::Release(button)), ncurses::BUTTON1_PRESSED | ncurses::BUTTON2_PRESSED | ncurses::BUTTON3_PRESSED => f(MouseEvent::Press(button)), ncurses::BUTTON4_PRESSED => f(MouseEvent::WheelUp), ncurses::BUTTON5_PRESSED => f(MouseEvent::WheelDown), // BUTTON4_RELEASED? BUTTON5_RELEASED? // Do they ever happen? _ => debug!("Unknown event: {:032b}", bare_event), } } fn add_fn(start: i32, with_key: F, map: &mut HashMap) where F: Fn(Key) -> Event, { for i in 0..12 { map.insert(start + i, with_key(Key::from_f((i + 1) as u8))); } } fn initialize_keymap() -> HashMap { // First, define the static mappings. let mut map = HashMap::default(); // Value sent by ncurses when nothing happens map.insert(-1, Event::Refresh); // Values under 256 are chars and control values // Tab is '\t' map.insert(9, Event::Key(Key::Tab)); // Treat '\n' and the numpad Enter the same map.insert(10, Event::Key(Key::Enter)); map.insert(ncurses::KEY_ENTER, Event::Key(Key::Enter)); // This is the escape key when pressed by itself. // When used for control sequences, // it should have been caught earlier. map.insert(27, Event::Key(Key::Esc)); // `Backspace` sends 127, but Ctrl-H sends `Backspace` map.insert(127, Event::Key(Key::Backspace)); map.insert(ncurses::KEY_BACKSPACE, Event::Key(Key::Backspace)); map.insert(410, Event::WindowResize); map.insert(ncurses::KEY_B2, Event::Key(Key::NumpadCenter)); map.insert(ncurses::KEY_DC, Event::Key(Key::Del)); map.insert(ncurses::KEY_IC, Event::Key(Key::Ins)); map.insert(ncurses::KEY_BTAB, Event::Shift(Key::Tab)); map.insert(ncurses::KEY_SLEFT, Event::Shift(Key::Left)); map.insert(ncurses::KEY_SRIGHT, Event::Shift(Key::Right)); map.insert(ncurses::KEY_LEFT, Event::Key(Key::Left)); map.insert(ncurses::KEY_RIGHT, Event::Key(Key::Right)); map.insert(ncurses::KEY_UP, Event::Key(Key::Up)); map.insert(ncurses::KEY_DOWN, Event::Key(Key::Down)); map.insert(ncurses::KEY_SR, Event::Shift(Key::Up)); map.insert(ncurses::KEY_SF, Event::Shift(Key::Down)); map.insert(ncurses::KEY_PPAGE, Event::Key(Key::PageUp)); map.insert(ncurses::KEY_NPAGE, Event::Key(Key::PageDown)); map.insert(ncurses::KEY_HOME, Event::Key(Key::Home)); map.insert(ncurses::KEY_END, Event::Key(Key::End)); map.insert(ncurses::KEY_SHOME, Event::Shift(Key::Home)); map.insert(ncurses::KEY_SEND, Event::Shift(Key::End)); map.insert(ncurses::KEY_SDC, Event::Shift(Key::Del)); map.insert(ncurses::KEY_SNEXT, Event::Shift(Key::PageDown)); map.insert(ncurses::KEY_SPREVIOUS, Event::Shift(Key::PageUp)); // Then add some dynamic ones for c in 1..=26 { let event = match c { // Ctrl-i and Ctrl-j are special, they use the same codes as Tab // and Enter respecively. There's just no way to detect them. :( 9 => Event::Key(Key::Tab), 10 => Event::Key(Key::Enter), other => Event::CtrlChar((b'a' - 1 + other as u8) as char), }; map.insert(c, event); } // Ncurses provides a F1 variable, but no modifiers add_fn(ncurses::KEY_F1, Event::Key, &mut map); add_fn(277, Event::Shift, &mut map); add_fn(289, Event::Ctrl, &mut map); add_fn(301, Event::CtrlShift, &mut map); add_fn(313, Event::Alt, &mut map); // Those codes actually vary between ncurses versions... super::fill_key_codes(&mut map, ncurses::keyname); map } cursive-0.20.0/src/backends/curses/pan.rs000064400000000000000000000541721046102023000163650ustar 00000000000000//! Pancuses-specific backend. #![cfg(feature = "pancurses-backend")] #![cfg_attr(feature = "doc-cfg", doc(cfg(feature = "pancurses-backend")))] pub use pancurses; use log::{debug, warn}; use std::cell::{Cell, RefCell}; use std::io::{stdout, Write}; use crate::backend; use crate::event::{Event, Key, MouseButton, MouseEvent}; use crate::theme::{Color, ColorPair, Effect}; use crate::Vec2; use super::split_i32; use pancurses::mmask_t; // Use AHash instead of the slower SipHash type HashMap = std::collections::HashMap; /// Backend using pancurses. pub struct Backend { // Used current_style: Cell, pairs: RefCell>, // pancurses needs a handle to the current window. window: pancurses::Window, key_codes: HashMap, last_mouse_button: Option, input_buffer: Option, } fn find_closest_pair(pair: ColorPair) -> (i16, i16) { super::find_closest_pair(pair, pancurses::COLORS() as i16) } impl Backend { /// Creates a new pancurses-based backend. pub fn init() -> std::io::Result> { // Check the $TERM variable (at least on unix). // Otherwise we'll just abort. // TODO: On windows, is there anything to check? if cfg!(unix) && std::env::var("TERM") .map(|var| var.is_empty()) .unwrap_or(true) { return Err(std::io::Error::new( std::io::ErrorKind::Other, "$TERM is unset. Cannot initialize pancurses interface.", )); } ::std::env::set_var("ESCDELAY", "25"); if cfg!(unix) { let buf = std::ffi::CString::new("").unwrap(); unsafe { libc::setlocale(libc::LC_ALL, buf.as_ptr()) }; } // TODO: use pancurses::newterm() let window = pancurses::initscr(); window.keypad(true); window.timeout(0); pancurses::noecho(); pancurses::raw(); pancurses::start_color(); pancurses::use_default_colors(); pancurses::curs_set(0); pancurses::mouseinterval(0); pancurses::mousemask( pancurses::ALL_MOUSE_EVENTS | pancurses::REPORT_MOUSE_POSITION, None, ); // This asks the terminal to provide us with mouse drag events // (Mouse move when a button is pressed). // Replacing 1002 with 1003 would give us ANY mouse move. #[cfg(not(windows))] print!("\x1B[?1002h"); stdout().flush()?; let c = Backend { current_style: Cell::new(ColorPair::from_256colors(0, 0)), pairs: RefCell::new(HashMap::default()), key_codes: initialize_keymap(), last_mouse_button: None, input_buffer: None, window, }; Ok(Box::new(c)) } /// Save a new color pair. fn insert_color( &self, pairs: &mut HashMap<(i16, i16), i32>, (front, back): (i16, i16), ) -> i32 { let n = 1 + pairs.len() as i32; // TODO: when COLORS_PAIRS is available... let target = if 256 > n { // We still have plenty of space for everyone. n } else { // The world is too small for both of us. let target = n - 1; // Remove the mapping to n-1 pairs.retain(|_, &mut v| v != target); target }; pairs.insert((front, back), target); pancurses::init_pair(target as i16, front, back); target } /// Checks the pair in the cache, or re-define a color if needed. fn get_or_create(&self, pair: ColorPair) -> i32 { let mut pairs = self.pairs.borrow_mut(); let pair = find_closest_pair(pair); // Find if we have this color in stock if pairs.contains_key(&pair) { // We got it! pairs[&pair] } else { self.insert_color(&mut *pairs, pair) } } fn set_colors(&self, pair: ColorPair) { let i = self.get_or_create(pair); self.current_style.set(pair); let style = pancurses::COLOR_PAIR(i as pancurses::chtype); self.window.attron(style); } fn parse_next(&mut self) -> Option { if let Some(event) = self.input_buffer.take() { return Some(event); } if let Some(ev) = self.window.getch() { Some(match ev { pancurses::Input::Character('\n') | pancurses::Input::Character('\r') => Event::Key(Key::Enter), // TODO: wait for a very short delay. If more keys are // pipelined, it may be an escape sequence. pancurses::Input::Character('\u{7f}') | pancurses::Input::Character('\u{8}') => { Event::Key(Key::Backspace) } pancurses::Input::Character('\u{9}') => Event::Key(Key::Tab), pancurses::Input::Character('\u{1b}') => Event::Key(Key::Esc), // Ctrl+C pancurses::Input::Character(c) if (c as u32) <= 26 => { Event::CtrlChar((b'a' - 1 + c as u8) as char) } pancurses::Input::Character(c) => Event::Char(c), // TODO: Some key combos are not recognized by pancurses, // but are sent as Unknown. We could still parse them here. pancurses::Input::Unknown(code) => self .key_codes // pancurses does some weird keycode mapping .get(&(code + 256 + 48)) .cloned() .unwrap_or_else(|| { warn!("Unknown: {}", code); Event::Unknown(split_i32(code)) }), // TODO: I honestly have no fucking idea what KeyCodeYes is pancurses::Input::KeyCodeYes => Event::Refresh, pancurses::Input::KeyBreak => Event::Key(Key::PauseBreak), pancurses::Input::KeyDown => Event::Key(Key::Down), pancurses::Input::KeyUp => Event::Key(Key::Up), pancurses::Input::KeyLeft => Event::Key(Key::Left), pancurses::Input::KeyRight => Event::Key(Key::Right), pancurses::Input::KeyHome => Event::Key(Key::Home), pancurses::Input::KeyBackspace => Event::Key(Key::Backspace), pancurses::Input::KeyF0 => Event::Key(Key::F0), pancurses::Input::KeyF1 => Event::Key(Key::F1), pancurses::Input::KeyF2 => Event::Key(Key::F2), pancurses::Input::KeyF3 => Event::Key(Key::F3), pancurses::Input::KeyF4 => Event::Key(Key::F4), pancurses::Input::KeyF5 => Event::Key(Key::F5), pancurses::Input::KeyF6 => Event::Key(Key::F6), pancurses::Input::KeyF7 => Event::Key(Key::F7), pancurses::Input::KeyF8 => Event::Key(Key::F8), pancurses::Input::KeyF9 => Event::Key(Key::F9), pancurses::Input::KeyF10 => Event::Key(Key::F10), pancurses::Input::KeyF11 => Event::Key(Key::F11), pancurses::Input::KeyF12 => Event::Key(Key::F12), pancurses::Input::KeyF13 => Event::Shift(Key::F1), pancurses::Input::KeyF14 => Event::Shift(Key::F2), pancurses::Input::KeyF15 => Event::Shift(Key::F3), pancurses::Input::KeyDL => Event::Refresh, pancurses::Input::KeyIL => Event::Refresh, pancurses::Input::KeyDC => Event::Key(Key::Del), pancurses::Input::KeyIC => Event::Key(Key::Ins), pancurses::Input::KeyEIC => Event::Refresh, pancurses::Input::KeyClear => Event::Refresh, pancurses::Input::KeyEOS => Event::Refresh, pancurses::Input::KeyEOL => Event::Refresh, pancurses::Input::KeySF => Event::Shift(Key::Down), pancurses::Input::KeySR => Event::Shift(Key::Up), pancurses::Input::KeyNPage => Event::Key(Key::PageDown), pancurses::Input::KeyPPage => Event::Key(Key::PageUp), pancurses::Input::KeySTab => Event::Shift(Key::Tab), pancurses::Input::KeyCTab => Event::Ctrl(Key::Tab), pancurses::Input::KeyCATab => Event::CtrlAlt(Key::Tab), pancurses::Input::KeyEnter => Event::Key(Key::Enter), pancurses::Input::KeySReset => Event::Refresh, pancurses::Input::KeyReset => Event::Refresh, pancurses::Input::KeyPrint => Event::Refresh, pancurses::Input::KeyLL => Event::Refresh, pancurses::Input::KeyAbort => Event::Refresh, pancurses::Input::KeySHelp => Event::Refresh, pancurses::Input::KeyLHelp => Event::Refresh, pancurses::Input::KeyBTab => Event::Shift(Key::Tab), pancurses::Input::KeyBeg => Event::Refresh, pancurses::Input::KeyCancel => Event::Refresh, pancurses::Input::KeyClose => Event::Refresh, pancurses::Input::KeyCommand => Event::Refresh, pancurses::Input::KeyCopy => Event::Refresh, pancurses::Input::KeyCreate => Event::Refresh, pancurses::Input::KeyEnd => Event::Key(Key::End), pancurses::Input::KeyExit => Event::Refresh, pancurses::Input::KeyFind => Event::Refresh, pancurses::Input::KeyHelp => Event::Refresh, pancurses::Input::KeyMark => Event::Refresh, pancurses::Input::KeyMessage => Event::Refresh, pancurses::Input::KeyMove => Event::Refresh, pancurses::Input::KeyNext => Event::Refresh, pancurses::Input::KeyOpen => Event::Refresh, pancurses::Input::KeyOptions => Event::Refresh, pancurses::Input::KeyPrevious => Event::Refresh, pancurses::Input::KeyRedo => Event::Refresh, pancurses::Input::KeyReference => Event::Refresh, pancurses::Input::KeyRefresh => Event::Refresh, pancurses::Input::KeyReplace => Event::Refresh, pancurses::Input::KeyRestart => Event::Refresh, pancurses::Input::KeyResume => Event::Refresh, pancurses::Input::KeySave => Event::Refresh, pancurses::Input::KeySBeg => Event::Refresh, pancurses::Input::KeySCancel => Event::Refresh, pancurses::Input::KeySCommand => Event::Refresh, pancurses::Input::KeySCopy => Event::Refresh, pancurses::Input::KeySCreate => Event::Refresh, pancurses::Input::KeySDC => Event::Shift(Key::Del), pancurses::Input::KeySDL => Event::Refresh, pancurses::Input::KeySelect => Event::Refresh, pancurses::Input::KeySEnd => Event::Shift(Key::End), pancurses::Input::KeySEOL => Event::Refresh, pancurses::Input::KeySExit => Event::Refresh, pancurses::Input::KeySFind => Event::Refresh, pancurses::Input::KeySHome => Event::Shift(Key::Home), pancurses::Input::KeySIC => Event::Shift(Key::Ins), pancurses::Input::KeySLeft => Event::Shift(Key::Left), pancurses::Input::KeySMessage => Event::Refresh, pancurses::Input::KeySMove => Event::Refresh, pancurses::Input::KeySNext => Event::Shift(Key::PageDown), pancurses::Input::KeySOptions => Event::Refresh, pancurses::Input::KeySPrevious => Event::Shift(Key::PageUp), pancurses::Input::KeySPrint => Event::Refresh, pancurses::Input::KeySRedo => Event::Refresh, pancurses::Input::KeySReplace => Event::Refresh, pancurses::Input::KeySRight => Event::Shift(Key::Right), pancurses::Input::KeySResume => Event::Refresh, pancurses::Input::KeySSave => Event::Refresh, pancurses::Input::KeySSuspend => Event::Refresh, pancurses::Input::KeySUndo => Event::Refresh, pancurses::Input::KeySuspend => Event::Refresh, pancurses::Input::KeyUndo => Event::Refresh, pancurses::Input::KeyResize => { // Let pancurses adjust their structures when the // window is resized. // Do it for Windows only, as 'resize_term' is not // implemented for Unix if cfg!(target_os = "windows") { pancurses::resize_term(0, 0); } Event::WindowResize } pancurses::Input::KeyEvent => Event::Refresh, // TODO: mouse support pancurses::Input::KeyMouse => self.parse_mouse_event(), pancurses::Input::KeyA1 => Event::Refresh, pancurses::Input::KeyA3 => Event::Refresh, pancurses::Input::KeyB2 => Event::Key(Key::NumpadCenter), pancurses::Input::KeyC1 => Event::Refresh, pancurses::Input::KeyC3 => Event::Refresh, }) } else { None } } fn parse_mouse_event(&mut self) -> Event { let mut mevent = match pancurses::getmouse() { Err(code) => return Event::Unknown(split_i32(code)), Ok(event) => event, }; let _shift = (mevent.bstate & pancurses::BUTTON_SHIFT as mmask_t) != 0; let _alt = (mevent.bstate & pancurses::BUTTON_ALT as mmask_t) != 0; let _ctrl = (mevent.bstate & pancurses::BUTTON_CTRL as mmask_t) != 0; mevent.bstate &= !(pancurses::BUTTON_SHIFT | pancurses::BUTTON_ALT | pancurses::BUTTON_CTRL) as mmask_t; let make_event = |event| Event::Mouse { offset: Vec2::zero(), position: Vec2::new(mevent.x as usize, mevent.y as usize), event, }; if mevent.bstate == pancurses::REPORT_MOUSE_POSITION as mmask_t { // The event is either a mouse drag event, // or a weird double-release event. :S self.last_mouse_button .map(MouseEvent::Hold) .or_else(|| { // In legacy mode, some buttons overlap, // so we need to disambiguate. (mevent.bstate == pancurses::BUTTON5_DOUBLE_CLICKED as mmask_t) .then(|| MouseEvent::WheelDown) }) .map(&make_event) .unwrap_or_else(|| { debug!("We got a mouse drag, but no last mouse pressed?"); Event::Unknown(vec![]) }) } else { // Identify the button let mut bare_event = mevent.bstate & ((1 << 25) - 1); let mut event = None; while bare_event != 0 { let single_event = 1 << bare_event.trailing_zeros(); bare_event ^= single_event; // Process single_event on_mouse_event(single_event, |e| { if event.is_none() { event = Some(e); } else { self.input_buffer = Some(make_event(e)); } }); } if let Some(event) = event { if let Some(btn) = event.button() { self.last_mouse_button = Some(btn); } make_event(event) } else { debug!("No event parsed?..."); Event::Unknown(vec![]) } } } } impl Drop for Backend { fn drop(&mut self) { print!("\x1B[?1002l"); stdout().flush().expect("could not flush stdout"); pancurses::endwin(); } } impl backend::Backend for Backend { fn name(&self) -> &str { "pancurses" } fn set_title(&mut self, title: String) { print!("\x1B]0;{}\x07", title); stdout().flush().expect("could not flush stdout"); } fn screen_size(&self) -> Vec2 { // Coordinates are reversed here let (y, x) = self.window.get_max_yx(); (x, y).into() } fn has_colors(&self) -> bool { pancurses::has_colors() } fn set_color(&self, colors: ColorPair) -> ColorPair { let current = self.current_style.get(); if current != colors { self.set_colors(colors); } current } fn set_effect(&self, effect: Effect) { let style = match effect { Effect::Simple => pancurses::Attribute::Normal, Effect::Reverse => pancurses::Attribute::Reverse, Effect::Dim => pancurses::Attribute::Dim, Effect::Bold => pancurses::Attribute::Bold, Effect::Blink => pancurses::Attribute::Blink, Effect::Italic => pancurses::Attribute::Italic, Effect::Strikethrough => pancurses::Attribute::Strikeout, Effect::Underline => pancurses::Attribute::Underline, }; self.window.attron(style); } fn unset_effect(&self, effect: Effect) { let style = match effect { Effect::Simple => pancurses::Attribute::Normal, Effect::Reverse => pancurses::Attribute::Reverse, Effect::Dim => pancurses::Attribute::Dim, Effect::Bold => pancurses::Attribute::Bold, Effect::Blink => pancurses::Attribute::Blink, Effect::Italic => pancurses::Attribute::Italic, Effect::Strikethrough => pancurses::Attribute::Strikeout, Effect::Underline => pancurses::Attribute::Underline, }; self.window.attroff(style); } fn clear(&self, color: Color) { let id = self.get_or_create(ColorPair { front: color, back: color, }); self.window.bkgd(pancurses::ColorPair(id as u8)); self.window.clear(); } fn refresh(&mut self) { self.window.refresh(); } fn print_at(&self, pos: Vec2, text: &str) { self.window.mvaddstr(pos.y as i32, pos.x as i32, text); } fn print_at_rep(&self, pos: Vec2, repetitions: usize, text: &str) { if repetitions > 0 { self.window.mvaddstr(pos.y as i32, pos.x as i32, text); let mut dupes_left = repetitions - 1; while dupes_left > 0 { self.window.addstr(text); dupes_left -= 1; } } } fn poll_event(&mut self) -> Option { self.parse_next() } } /// Parse the given code into one or more event. /// /// If the given event code should expend into multiple events /// (for instance click expends into PRESS + RELEASE), /// the returned Vec will include those queued events. /// /// The main event is returned separately to avoid allocation in most cases. fn on_mouse_event(bare_event: mmask_t, mut f: F) where F: FnMut(MouseEvent), { let button = get_mouse_button(bare_event); match bare_event { pancurses::BUTTON4_PRESSED => f(MouseEvent::WheelUp), pancurses::BUTTON5_PRESSED => f(MouseEvent::WheelDown), pancurses::BUTTON1_RELEASED | pancurses::BUTTON2_RELEASED | pancurses::BUTTON3_RELEASED | pancurses::BUTTON4_RELEASED | pancurses::BUTTON5_RELEASED => f(MouseEvent::Release(button)), pancurses::BUTTON1_PRESSED | pancurses::BUTTON2_PRESSED | pancurses::BUTTON3_PRESSED => f(MouseEvent::Press(button)), pancurses::BUTTON1_CLICKED | pancurses::BUTTON2_CLICKED | pancurses::BUTTON3_CLICKED | pancurses::BUTTON4_CLICKED | pancurses::BUTTON5_CLICKED => { f(MouseEvent::Press(button)); f(MouseEvent::Release(button)); } // Well, we disabled click detection pancurses::BUTTON1_DOUBLE_CLICKED | pancurses::BUTTON2_DOUBLE_CLICKED | pancurses::BUTTON3_DOUBLE_CLICKED | pancurses::BUTTON4_DOUBLE_CLICKED | pancurses::BUTTON5_DOUBLE_CLICKED => { for _ in 0..2 { f(MouseEvent::Press(button)); f(MouseEvent::Release(button)); } } pancurses::BUTTON1_TRIPLE_CLICKED | pancurses::BUTTON2_TRIPLE_CLICKED | pancurses::BUTTON3_TRIPLE_CLICKED | pancurses::BUTTON4_TRIPLE_CLICKED | pancurses::BUTTON5_TRIPLE_CLICKED => { for _ in 0..3 { f(MouseEvent::Press(button)); f(MouseEvent::Release(button)); } } _ => debug!("Unknown event: {:032b}", bare_event), } } /// Returns the Key enum corresponding to the given pancurses event. fn get_mouse_button(bare_event: mmask_t) -> MouseButton { match bare_event { pancurses::BUTTON1_RELEASED | pancurses::BUTTON1_PRESSED | pancurses::BUTTON1_CLICKED | pancurses::BUTTON1_DOUBLE_CLICKED | pancurses::BUTTON1_TRIPLE_CLICKED => MouseButton::Left, pancurses::BUTTON2_RELEASED | pancurses::BUTTON2_PRESSED | pancurses::BUTTON2_CLICKED | pancurses::BUTTON2_DOUBLE_CLICKED | pancurses::BUTTON2_TRIPLE_CLICKED => MouseButton::Middle, pancurses::BUTTON3_RELEASED | pancurses::BUTTON3_PRESSED | pancurses::BUTTON3_CLICKED | pancurses::BUTTON3_DOUBLE_CLICKED | pancurses::BUTTON3_TRIPLE_CLICKED => MouseButton::Right, pancurses::BUTTON4_RELEASED | pancurses::BUTTON4_PRESSED | pancurses::BUTTON4_CLICKED | pancurses::BUTTON4_DOUBLE_CLICKED | pancurses::BUTTON4_TRIPLE_CLICKED => MouseButton::Button4, pancurses::BUTTON5_RELEASED | pancurses::BUTTON5_PRESSED | pancurses::BUTTON5_CLICKED | pancurses::BUTTON5_DOUBLE_CLICKED | pancurses::BUTTON5_TRIPLE_CLICKED => MouseButton::Button5, _ => MouseButton::Other, } } fn initialize_keymap() -> HashMap { let mut map = HashMap::default(); super::fill_key_codes(&mut map, pancurses::keyname); map } cursive-0.20.0/src/backends/mod.rs000064400000000000000000000032111046102023000150460ustar 00000000000000//! Define backends using common libraries. //! //! Cursive doesn't print anything by itself: it delegates this job to a //! backend library, which handles all actual input and output. //! //! This module defines the [`Backend`] trait, as well as a few implementations //! using some common libraries. Each of those included backends needs a //! corresonding feature to be enabled. //! //! [`Backend`]: ../backend/trait.Backend.html #[cfg(unix)] mod resize; pub mod blt; pub mod crossterm; pub mod curses; pub mod puppet; pub mod termion; #[allow(dead_code)] fn boxed(e: impl std::error::Error + 'static) -> Box { Box::new(e) } /// Tries to initialize the default backend. /// /// Will use the first backend enabled from the list: /// * BearLibTerminal /// * Termion /// * Crossterm /// * Pancurses /// * Ncurses /// * Dummy pub fn try_default( ) -> Result, Box> { cfg_if::cfg_if! { if #[cfg(feature = "blt-backend")] { Ok(blt::Backend::init()) } else if #[cfg(feature = "termion-backend")] { termion::Backend::init().map_err(boxed) } else if #[cfg(feature = "crossterm-backend")] { crossterm::Backend::init().map_err(boxed) } else if #[cfg(feature = "pancurses-backend")] { curses::pan::Backend::init().map_err(boxed) } else if #[cfg(feature = "ncurses-backend")] { curses::n::Backend::init().map_err(boxed) } else { log::warn!("No built-it backend, falling back to Dummy backend."); Ok(cursive_core::backend::Dummy::init()) } } } cursive-0.20.0/src/backends/puppet/mod.rs000064400000000000000000000113231046102023000163660ustar 00000000000000//! Puppet backend use crossbeam_channel::{self, Receiver, Sender, TryRecvError}; use self::observed::ObservedCell; use self::observed::ObservedScreen; use self::observed::ObservedStyle; use crate::backend; use crate::event::Event; use crate::theme; use crate::Vec2; use std::cell::{Cell, RefCell}; use std::rc::Rc; use unicode_segmentation::UnicodeSegmentation; use unicode_width::UnicodeWidthStr; pub mod observed; pub mod observed_screen_view; mod static_values; use static_values::*; /// Puppet backend for testing. pub struct Backend { inner_sender: Sender>, inner_receiver: Receiver>, prev_frame: RefCell>, current_frame: RefCell, size: Cell, current_style: RefCell>, screen_channel: (Sender, Receiver), } impl Backend { /// Creates new Puppet backend of given or default size. pub fn init(size_op: Option) -> Box where Self: Sized, { let (inner_sender, inner_receiver) = crossbeam_channel::unbounded(); let size = size_op.unwrap_or(*DEFAULT_SIZE); let mut backend = Backend { inner_sender, inner_receiver, prev_frame: RefCell::new(None), current_frame: RefCell::new(ObservedScreen::new(size)), size: Cell::new(size), current_style: RefCell::new(Rc::new( DEFAULT_OBSERVED_STYLE.clone(), )), screen_channel: crossbeam_channel::unbounded(), }; { use backend::Backend; backend.refresh(); } Box::new(backend) } /// Returns current ObservedStyle pub fn current_style(&self) -> Rc { self.current_style.borrow().clone() } /// Ouput stream of consecutive frames rendered by Puppet backend pub fn stream(&self) -> Receiver { self.screen_channel.1.clone() } /// Input stream to inject artificial input to Puppet backend. pub fn input(&self) -> Sender> { self.inner_sender.clone() } } impl backend::Backend for Backend { fn poll_event(&mut self) -> Option { match self.inner_receiver.try_recv() { Ok(event) => event, Err(TryRecvError::Empty) => None, Err(e) => panic!("{}", e), } } fn set_title(&mut self, _title: String) {} fn refresh(&mut self) { let size = self.size.get(); let current_frame = self.current_frame.replace(ObservedScreen::new(size)); self.prev_frame.replace(Some(current_frame.clone())); self.screen_channel.0.send(current_frame).unwrap(); } fn has_colors(&self) -> bool { true } fn screen_size(&self) -> Vec2 { self.size.get() } fn print_at(&self, pos: Vec2, text: &str) { let style = self.current_style.borrow().clone(); let mut screen = self.current_frame.borrow_mut(); let mut offset: usize = 0; for (idx, grapheme) in text.graphemes(true).enumerate() { let cpos = pos + Vec2::new(idx + offset, 0); screen[cpos] = Some(ObservedCell::new( cpos, style.clone(), Some(grapheme.to_string()), )); for _ in 0..grapheme.width() - 1 { offset += 1; let spos = pos + Vec2::new(idx + offset, 0); screen[spos] = Some(ObservedCell::new(spos, style.clone(), None)); } } } fn clear(&self, clear_color: theme::Color) { let mut cloned_style = (*self.current_style()).clone(); let mut screen = self.current_frame.borrow_mut(); cloned_style.colors.back = clear_color; screen.clear(&Rc::new(cloned_style)) } // This sets the Colours and returns the previous colours // to allow you to set them back when you're done. fn set_color(&self, new_colors: theme::ColorPair) -> theme::ColorPair { let mut copied_style = (*self.current_style()).clone(); let old_colors = copied_style.colors; copied_style.colors = new_colors; self.current_style.replace(Rc::new(copied_style)); old_colors } fn set_effect(&self, effect: theme::Effect) { let mut copied_style = (*self.current_style()).clone(); copied_style.effects.insert(effect); self.current_style.replace(Rc::new(copied_style)); } fn unset_effect(&self, effect: theme::Effect) { let mut copied_style = (*self.current_style()).clone(); copied_style.effects.remove(effect); self.current_style.replace(Rc::new(copied_style)); } } cursive-0.20.0/src/backends/puppet/observed.rs000064400000000000000000000426631046102023000174330ustar 00000000000000//! Structs representing output of puppet backend use crate::reexports::enumset::EnumSet; use crate::theme::ColorPair; use crate::theme::Effect; use crate::Vec2; use std::ops::Index; use std::ops::IndexMut; use std::rc::Rc; use std::string::ToString; use std::{fmt, fmt::Display, fmt::Formatter}; use unicode_segmentation::UnicodeSegmentation; use unicode_width::UnicodeWidthStr; /// Style of observed cell #[derive(Debug, Clone, Eq, PartialEq)] pub struct ObservedStyle { /// Colors: front and back pub colors: ColorPair, /// Effects enabled on observed cell pub effects: EnumSet, } /// Contents of observed cell #[derive(Debug, Clone, Eq, PartialEq)] pub enum GraphemePart { /// Represents begin of wide character Begin(String), /// Represents a cell that is filled with continuation of some character that begun in cell with lower x-index. Continuation, } impl GraphemePart { /// Returns true iff GraphemePart is Continuation pub fn is_continuation(&self) -> bool { matches!(*self, GraphemePart::Continuation) } /// Returns Some(String) if GraphemePart is Begin(String), else None. pub fn as_option(&self) -> Option<&String> { match *self { GraphemePart::Begin(ref string) => Some(string), GraphemePart::Continuation => None, } } /// Returns String if GraphemePart is Begin(String), panics otherwise. pub fn unwrap(&self) -> String { match *self { GraphemePart::Begin(ref s) => s.clone(), _ => panic!("unwrapping GraphemePart::Continuation"), } } } /// Represents a single cell of terminal. #[derive(Debug, Clone, Eq, PartialEq)] pub struct ObservedCell { /// Absolute position pub pos: Vec2, /// Style pub style: Rc, /// Part of grapheme - either it's beginning or continuation when character is multi-cell long. pub letter: GraphemePart, } impl ObservedCell { /// Constructor pub fn new( pos: Vec2, style: Rc, letter: Option, ) -> Self { let letter: GraphemePart = match letter { Some(s) => GraphemePart::Begin(s), None => GraphemePart::Continuation, }; ObservedCell { pos, style, letter } } } /// Puppet backend output /// /// Represents single frame. #[derive(Debug, Clone, Eq, PartialEq)] pub struct ObservedScreen { /// Size size: Vec2, /// Contents. Each cell can be set or empty. contents: Vec>, } impl Display for ObservedScreen { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { writeln!(f, "captured piece:")?; write!(f, "x")?; for x in 0..self.size().x { write!(f, "{}", x % 10)?; } writeln!(f, "x")?; for y in 0..self.size().y { write!(f, "{}", y % 10)?; for x in 0..self.size().x { let pos = Vec2::new(x, y); let cell_op: &Option = &self[pos]; if cell_op.is_some() { let cell = cell_op.as_ref().unwrap(); if cell.letter.is_continuation() { write!(f, "c")?; continue; } else { let letter = cell.letter.unwrap(); if letter == " " { write!(f, " ")?; } else { write!(f, "{}", letter)?; } } } else { write!(f, ".")?; } } writeln!(f, "|")?; } write!(f, "x")?; for _x in 0..self.size().x { write!(f, "-")?; } writeln!(f, "x")?; Ok(()) } } impl ObservedScreen { /// Creates empty ObservedScreen pub fn new(size: Vec2) -> Self { let contents: Vec> = vec![None; size.x * size.y]; ObservedScreen { size, contents } } fn flatten_index(&self, index: Vec2) -> usize { assert!(index.x < self.size.x); assert!(index.y < self.size.y); index.y * self.size.x + index.x } fn unflatten_index(&self, index: usize) -> Vec2 { assert!(index < self.contents.len()); Vec2::new(index / self.size.x, index % self.size.x) } /// Sets all cells to empty cells with given style pub fn clear(&mut self, style: &Rc) { for idx in 0..self.contents.len() { self.contents[idx] = Some(ObservedCell::new( self.unflatten_index(idx), style.clone(), None, )) } } /// Size pub fn size(&self) -> Vec2 { self.size } /// Returns a rectangular subset of observed screen. pub fn piece(&self, min: Vec2, max: Vec2) -> ObservedPiece { ObservedPiece::new(self, min, max) } /// Prints the piece to stdout. pub fn print_stdout(&self) { println!("{}", self) } /// Returns occurences of given string pattern pub fn find_occurences(&self, pattern: &str) -> Vec { // TODO(njskalski): test for two-cell letters. // TODO(njskalski): fails with whitespaces like "\t". let mut hits: Vec = vec![]; for y in self.min().y..self.max().y { 'x: for x in self.min().x..self.max().x { // check candidate. if pattern.len() > self.size().x - x { continue; } let mut pattern_cursor: usize = 0; let mut pos_cursor: usize = 0; loop { let pattern_symbol = pattern .graphemes(true) .nth(pattern_cursor) .unwrap_or_else(|| { panic!( "Found no char at cursor {} in {}", pattern_cursor, &pattern ) }); let pos_it = Vec2::new(x + pos_cursor, y); let found_symbol: Option<&String> = if let Some(ref cell) = self[pos_it] { cell.letter.as_option() } else { None }; match found_symbol { Some(screen_symbol) => { if pattern_symbol == screen_symbol { pattern_cursor += 1; pos_cursor += screen_symbol.width(); } else { continue 'x; } } None => { if pattern_symbol == " " { pattern_cursor += 1; pos_cursor += 1; } else { continue 'x; } } }; if pattern_cursor == pattern.graphemes(true).count() { break; }; } if pattern_cursor == pattern.graphemes(true).count() { hits.push(ObservedLine::new( self, Vec2::new(x, y), pos_cursor, )); } } } hits } } /// Represents rectangular piece of observed screen (Puppet backend output) pub trait ObservedPieceInterface { /// Minimums of coordinates fn min(&self) -> Vec2; /// Maximums of coordinates fn max(&self) -> Vec2; /// Reference of ObservablePiece this one is a subsection of or Self fn parent(&self) -> &ObservedScreen; /// Size of piece fn size(&self) -> Vec2 { self.max() - self.min() } /// Returns a string representation of consecutive lines of this piece. fn as_strings(&self) -> Vec { let mut v: Vec = vec![]; for y in self.min().y..self.max().y { let mut s = String::new(); for x in self.min().x..self.max().x { match &self.parent()[Vec2::new(x, y)] { None => s.push(' '), Some(cell) => { if let GraphemePart::Begin(ref lex) = cell.letter { s.push_str(lex); } } } } v.push(s); } v } /// Returns expanded sibling of this piece /// /// Asserts if request can be satisfied. fn expanded(&self, up_left: Vec2, down_right: Vec2) -> ObservedPiece { assert!(self.min().x >= up_left.x); assert!(self.min().y >= up_left.y); assert!(self.max().x + down_right.x <= self.parent().size.x); assert!(self.max().y + down_right.y <= self.parent().size.y); ObservedPiece::new( self.parent(), self.min() - up_left, self.max() + down_right, ) } } /// Represents a piece or whole of observed screen. pub struct ObservedPiece<'a> { min: Vec2, max: Vec2, parent: &'a ObservedScreen, } impl<'a> ObservedPiece<'a> { fn new(parent: &'a ObservedScreen, min: Vec2, max: Vec2) -> Self { ObservedPiece { min, max, parent } } } impl ObservedPieceInterface for ObservedScreen { fn min(&self) -> Vec2 { Vec2::new(0, 0) } fn max(&self) -> Vec2 { self.size } fn parent(&self) -> &ObservedScreen { self } } impl<'a> ObservedPieceInterface for ObservedPiece<'a> { fn min(&self) -> Vec2 { self.min } fn max(&self) -> Vec2 { self.max } fn parent(&self) -> &ObservedScreen { self.parent } } /// Represents a single line of observed screen. pub struct ObservedLine<'a> { line_start: Vec2, line_len: usize, parent: &'a ObservedScreen, } impl<'a> ObservedLine<'a> { fn new( parent: &'a ObservedScreen, line_start: Vec2, line_len: usize, ) -> Self { ObservedLine { line_start, line_len, parent, } } /// Returns the same line, but expanded. /// /// Asserts whether request can be satisfied #[must_use] pub fn expanded_line(&self, left: usize, right: usize) -> Self { assert!(left <= self.line_start.x); assert!( self.line_start.x + self.line_len + right <= self.parent.size.x ); ObservedLine { line_start: Vec2::new(self.line_start.x - left, self.line_start.y), line_len: self.line_len + left + right, parent: self.parent, } } } impl<'a> ObservedPieceInterface for ObservedLine<'a> { fn min(&self) -> Vec2 { self.line_start } fn max(&self) -> Vec2 { self.line_start + (self.line_len, 1) } fn parent(&self) -> &ObservedScreen { self.parent } } impl<'a> ToString for ObservedLine<'a> { fn to_string(&self) -> String { self.as_strings().remove(0) } } impl Index for dyn ObservedPieceInterface { type Output = Option; fn index(&self, index: Vec2) -> &Self::Output { assert!(self.max() - self.min() > index); let parent_index = self.min() + index; &self.parent()[parent_index] } } impl Index for ObservedScreen { type Output = Option; fn index(&self, index: Vec2) -> &Self::Output { let idx = self.flatten_index(index); &self.contents[idx] } } impl IndexMut for ObservedScreen { fn index_mut(&mut self, index: Vec2) -> &mut Option { let idx = self.flatten_index(index); &mut self.contents[idx] } } #[cfg(test)] mod tests { use super::*; use crate::backends::puppet::DEFAULT_OBSERVED_STYLE; /// Expecting fake_screen to be square, # will be replaced with blank. fn get_observed_screen(fake_screen: &Vec<&str>) -> ObservedScreen { let observed_style: Rc = Rc::new(DEFAULT_OBSERVED_STYLE.clone()); let height = fake_screen.len(); let width = fake_screen[0].width(); let size = Vec2::new(width, height); let mut os = ObservedScreen::new(size); for y in 0..fake_screen.len() { let mut x: usize = 0; for letter in fake_screen[y].graphemes(true) { let idx = os.flatten_index(Vec2::new(x, y)); os.contents[idx] = if letter == "#" { None } else { Some(ObservedCell::new( Vec2::new(x, y), observed_style.clone(), Some(letter.to_owned()), )) }; x += letter.width(); } } os } #[test] fn test_test() { let fake_screen: Vec<&'static str> = vec!["..hello***", "!!##$$$$$*", ".hello^^^^"]; let os = get_observed_screen(&fake_screen); assert_eq!( os[Vec2::new(0, 0)].as_ref().unwrap().letter.as_option(), Some(&".".to_owned()) ); assert_eq!(os[Vec2::new(2, 1)], None); } #[test] fn find_occurrences_no_blanks() { let fake_screen: Vec<&'static str> = vec!["..hello***", "!!##$$$$$*", ".hello^^^^"]; let os = get_observed_screen(&fake_screen); let hits = os.find_occurences("hello"); assert_eq!(hits.len(), 2); assert_eq!(hits[0].size(), Vec2::new(5, 1)); assert_eq!(hits[1].size(), Vec2::new(5, 1)); assert_eq!(hits[0].to_string(), "hello"); assert_eq!(hits[1].to_string(), "hello"); assert_eq!(hits[0].min(), Vec2::new(2, 0)); assert_eq!(hits[0].max(), Vec2::new(7, 1)); assert_eq!(hits[1].min(), Vec2::new(1, 2)); assert_eq!(hits[1].max(), Vec2::new(6, 3)); } #[test] fn find_occurrences_some_blanks() { let fake_screen: Vec<&'static str> = vec!["__hello world_", "hello!world___", "___hello#world"]; let os = get_observed_screen(&fake_screen); let hits = os.find_occurences("hello world"); assert_eq!(hits.len(), 2); assert_eq!(hits[0].size(), Vec2::new(11, 1)); assert_eq!(hits[1].size(), Vec2::new(11, 1)); assert_eq!(hits[0].to_string(), "hello world"); assert_eq!(hits[1].to_string(), "hello world"); assert_eq!(hits[0].min(), Vec2::new(2, 0)); assert_eq!(hits[0].max(), Vec2::new(13, 1)); assert_eq!(hits[1].min(), Vec2::new(3, 2)); assert_eq!(hits[1].max(), Vec2::new(14, 3)); } #[test] fn test_expand_lines() { let fake_screen: Vec<&'static str> = vec!["abc hello#efg"]; let os = get_observed_screen(&fake_screen); let hits = os.find_occurences("hello"); assert_eq!(hits.len(), 1); let hit = hits.first().unwrap(); assert_eq!(hit.size(), Vec2::new(5, 1)); let expanded_left = hit.expanded_line(3, 0); assert_eq!(expanded_left.size(), Vec2::new(8, 1)); assert_eq!(expanded_left.to_string(), "bc hello"); let expanded_left = hit.expanded_line(4, 0); assert_eq!(expanded_left.size(), Vec2::new(9, 1)); assert_eq!(expanded_left.to_string(), "abc hello"); let expanded_right = hit.expanded_line(0, 2); assert_eq!(expanded_right.size(), Vec2::new(7, 1)); assert_eq!(expanded_right.to_string(), "hello e"); let expanded_right = hit.expanded_line(0, 4); assert_eq!(expanded_right.size(), Vec2::new(9, 1)); assert_eq!(expanded_right.to_string(), "hello efg"); } #[test] fn test_expand_lines_weird_symbol_1() { let fake_screen: Vec<&'static str> = vec!["abc ▸ #efg"]; let os = get_observed_screen(&fake_screen); let hits = os.find_occurences("root"); assert_eq!(hits.len(), 1); let hit = hits.first().unwrap(); assert_eq!(hit.size(), Vec2::new(4, 1)); let expanded_left = hit.expanded_line(3, 0); assert_eq!(expanded_left.size(), Vec2::new(7, 1)); assert_eq!(expanded_left.to_string(), "▸ efg"); } #[test] fn test_expand_lines_weird_symbol_2() { let fake_screen: Vec<&'static str> = vec!["abc ▸ #efg"]; let os = get_observed_screen(&fake_screen); let hits = os.find_occurences("▸"); assert_eq!(hits.len(), 1); let hit = hits.first().unwrap(); assert_eq!(hit.size(), Vec2::new(1, 1)); let expanded_left = hit.expanded_line(3, 0); assert_eq!(expanded_left.size(), Vec2::new(4, 1)); assert_eq!(expanded_left.to_string(), "bc ▸"); let expanded_right = hit.expanded_line(0, 9); assert_eq!(expanded_right.size(), Vec2::new(10, 1)); assert_eq!(expanded_right.to_string(), "▸ e"); } } cursive-0.20.0/src/backends/puppet/observed_screen_view.rs000064400000000000000000000031441046102023000220130ustar 00000000000000//! View visualizing a captured PuppetBackend outputs use crate::backends::puppet::observed::ObservedCell; use crate::backends::puppet::observed::ObservedScreen; use crate::theme::ColorStyle; use crate::theme::ColorType; use crate::view::View; use crate::Printer; use crate::Vec2; /// A view that visualize observed screen pub struct ObservedScreenView { screen: ObservedScreen, } impl ObservedScreenView { /// Constructor pub fn new(obs: ObservedScreen) -> Self { ObservedScreenView { screen: obs } } } impl View for ObservedScreenView { fn draw(&self, printer: &Printer) { for x in 0..self.screen.size().x { for y in 0..self.screen.size().y { let pos = Vec2::new(x, y); let cell_op: &Option = &self.screen[pos]; if cell_op.is_none() { continue; } let cell = cell_op.as_ref().unwrap(); if cell.letter.is_continuation() { continue; } printer.with_effects(cell.style.effects, |printer| { let color_style = ColorStyle { front: ColorType::Color(cell.style.colors.front), back: ColorType::Color(cell.style.colors.back), }; printer.with_color(color_style, |printer| { printer.print(pos, &cell.letter.unwrap()); }); }); } } } fn required_size(&mut self, _: Vec2) -> Vec2 { self.screen.size() } } cursive-0.20.0/src/backends/puppet/static_values.rs000064400000000000000000000012571046102023000204620ustar 00000000000000/// Some default values to Puppet backend. use lazy_static::lazy_static; use crate::reexports::enumset::EnumSet; use crate::theme::ColorPair; use crate::theme::{Color, Effect}; use crate::Vec2; use crate::XY; use crate::backends::puppet::observed::*; lazy_static! { /// Default size for the puppet terminal. pub static ref DEFAULT_SIZE: Vec2 = XY:: { x: 120, y: 80 }; /// Default style for the puppet terminal. pub static ref DEFAULT_OBSERVED_STYLE: ObservedStyle = ObservedStyle { colors: ColorPair { front: Color::TerminalDefault, back: Color::TerminalDefault, }, effects: EnumSet::::empty(), }; } cursive-0.20.0/src/backends/resize.rs000064400000000000000000000013771046102023000156030ustar 00000000000000use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::thread; use crossbeam_channel::Sender; use signal_hook::iterator::Signals; /// This starts a new thread to listen for SIGWINCH signals #[allow(unused)] pub fn start_resize_thread( resize_sender: Sender<()>, resize_running: Arc, ) { let mut signals = Signals::new([libc::SIGWINCH]).unwrap(); thread::spawn(move || { // This thread will listen to SIGWINCH events and report them. while resize_running.load(Ordering::Relaxed) { // We know it will only contain SIGWINCH signals, so no need to check. if signals.wait().count() > 0 && resize_sender.send(()).is_err() { return; } } }); } cursive-0.20.0/src/backends/termion.rs000064400000000000000000000343621046102023000157570ustar 00000000000000//! Backend using the pure-rust termion library. //! //! Requires the `termion-backend` feature. #![cfg(feature = "termion")] #![cfg_attr(feature = "doc-cfg", doc(cfg(feature = "termion-backend")))] pub use termion; use crossbeam_channel::{self, Receiver}; use termion::color as tcolor; use termion::event::Event as TEvent; use termion::event::Key as TKey; use termion::event::MouseButton as TMouseButton; use termion::event::MouseEvent as TMouseEvent; use termion::input::{Events, MouseTerminal, TermRead}; use termion::raw::{IntoRawMode, RawTerminal}; use termion::screen::AlternateScreen; use termion::style as tstyle; use crate::backend; use crate::backends; use crate::event::{Event, Key, MouseButton, MouseEvent}; use crate::theme; use crate::Vec2; use std::cell::{Cell, RefCell}; use std::fs::File; use std::io::{BufWriter, Write}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; /// Backend using termion pub struct Backend { // Do we want to make this generic on the writer? terminal: RefCell>>>>, current_style: Cell, // Inner state required to parse input last_button: Option, events: Events, // Raw input file descriptor, to fix the file on exit, since we can't // (currently) get it from events. #[cfg(unix)] input_fd: std::os::unix::io::RawFd, resize_receiver: Receiver<()>, running: Arc, } /// Set the given file to be read in non-blocking mode. That is, attempting a /// read on the given file may return 0 bytes. /// /// Copied from private function at https://docs.rs/nonblock/0.1.0/nonblock/. /// /// The MIT License (MIT) /// /// Copyright (c) 2016 Anthony Nowell /// /// Permission is hereby granted, free of charge, to any person obtaining a copy /// of this software and associated documentation files (the "Software"), to deal /// in the Software without restriction, including without limitation the rights /// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell /// copies of the Software, and to permit persons to whom the Software is /// furnished to do so, subject to the following conditions: /// /// The above copyright notice and this permission notice shall be included in all /// copies or substantial portions of the Software. /// /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR /// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, /// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE /// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER /// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, /// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE /// SOFTWARE. #[cfg(unix)] fn set_blocking( fd: std::os::unix::io::RawFd, blocking: bool, ) -> std::io::Result<()> { use libc::{fcntl, F_GETFL, F_SETFL, O_NONBLOCK}; let flags = unsafe { fcntl(fd, F_GETFL, 0) }; if flags < 0 { return Err(std::io::Error::last_os_error()); } let flags = if blocking { flags & !O_NONBLOCK } else { flags | O_NONBLOCK }; let res = unsafe { fcntl(fd, F_SETFL, flags) }; if res != 0 { return Err(std::io::Error::last_os_error()); } Ok(()) } impl Backend { /// Creates a new termion-based backend. /// /// Uses `/dev/tty` for input and output. pub fn init() -> std::io::Result> { Self::init_with_files( File::open("/dev/tty")?, File::create("/dev/tty")?, ) } /// Creates a new termion-based backend. /// /// Uses `stdin` and `stdout` for input/output. pub fn init_stdio() -> std::io::Result> { Self::init_with_files( File::open("/dev/stdin")?, File::create("/dev/stdout")?, ) } /// Creates a new termion-based backend using the given input and output files. pub fn init_with_files( input_file: File, output_file: File, ) -> std::io::Result> { #[cfg(unix)] use std::os::unix::io::AsRawFd; #[cfg(unix)] let input_fd = input_file.as_raw_fd(); #[cfg(unix)] set_blocking(input_fd, false)?; // Use a ~8MB buffer // Should be enough for a single screen most of the time. let terminal = RefCell::new(AlternateScreen::from(MouseTerminal::from( BufWriter::with_capacity(8_000_000, output_file) .into_raw_mode()?, ))); write!(terminal.borrow_mut(), "{}", termion::cursor::Hide)?; let (resize_sender, resize_receiver) = crossbeam_channel::bounded(0); let running = Arc::new(AtomicBool::new(true)); #[cfg(unix)] backends::resize::start_resize_thread( resize_sender, Arc::clone(&running), ); let c = Backend { terminal, current_style: Cell::new(theme::ColorPair::from_256colors(0, 0)), last_button: None, events: input_file.events(), #[cfg(unix)] input_fd, resize_receiver, running, }; Ok(Box::new(c)) } fn apply_colors(&self, colors: theme::ColorPair) { with_color(colors.front, |c| self.write(tcolor::Fg(c))); with_color(colors.back, |c| self.write(tcolor::Bg(c))); } fn map_key(&mut self, event: TEvent) -> Event { match event { TEvent::Unsupported(bytes) => Event::Unknown(bytes), TEvent::Key(TKey::Esc) => Event::Key(Key::Esc), TEvent::Key(TKey::Backspace) => Event::Key(Key::Backspace), TEvent::Key(TKey::Left) => Event::Key(Key::Left), TEvent::Key(TKey::Right) => Event::Key(Key::Right), TEvent::Key(TKey::Up) => Event::Key(Key::Up), TEvent::Key(TKey::Down) => Event::Key(Key::Down), TEvent::Key(TKey::Home) => Event::Key(Key::Home), TEvent::Key(TKey::End) => Event::Key(Key::End), TEvent::Key(TKey::PageUp) => Event::Key(Key::PageUp), TEvent::Key(TKey::PageDown) => Event::Key(Key::PageDown), TEvent::Key(TKey::Delete) => Event::Key(Key::Del), TEvent::Key(TKey::Insert) => Event::Key(Key::Ins), TEvent::Key(TKey::F(i)) if i < 12 => Event::Key(Key::from_f(i)), TEvent::Key(TKey::F(j)) => Event::Unknown(vec![j]), TEvent::Key(TKey::Char('\n')) => Event::Key(Key::Enter), TEvent::Key(TKey::Char('\t')) => Event::Key(Key::Tab), TEvent::Key(TKey::Char(c)) => Event::Char(c), TEvent::Key(TKey::Ctrl(c)) => Event::CtrlChar(c), TEvent::Key(TKey::Alt(c)) => Event::AltChar(c), TEvent::Mouse(TMouseEvent::Press(btn, x, y)) => { let position = (x - 1, y - 1).into(); let event = match btn { TMouseButton::Left => MouseEvent::Press(MouseButton::Left), TMouseButton::Middle => { MouseEvent::Press(MouseButton::Middle) } TMouseButton::Right => { MouseEvent::Press(MouseButton::Right) } TMouseButton::WheelUp => MouseEvent::WheelUp, TMouseButton::WheelDown => MouseEvent::WheelDown, }; if let MouseEvent::Press(btn) = event { self.last_button = Some(btn); } Event::Mouse { event, position, offset: Vec2::zero(), } } TEvent::Mouse(TMouseEvent::Release(x, y)) if self.last_button.is_some() => { let event = MouseEvent::Release(self.last_button.unwrap()); let position = (x - 1, y - 1).into(); Event::Mouse { event, position, offset: Vec2::zero(), } } TEvent::Mouse(TMouseEvent::Hold(x, y)) if self.last_button.is_some() => { let event = MouseEvent::Hold(self.last_button.unwrap()); let position = (x - 1, y - 1).into(); Event::Mouse { event, position, offset: Vec2::zero(), } } _ => Event::Unknown(vec![]), } } fn write(&self, content: T) where T: std::fmt::Display, { write!(self.terminal.borrow_mut(), "{}", content).unwrap(); } } impl Drop for Backend { fn drop(&mut self) { self.running.store(false, Ordering::Relaxed); #[cfg(unix)] set_blocking(self.input_fd, true).unwrap(); write!( self.terminal.get_mut(), "{}{}", termion::cursor::Show, termion::cursor::Goto(1, 1) ) .unwrap(); write!( self.terminal.get_mut(), "{}[49m{}[39m{}", 27 as char, 27 as char, termion::clear::All ) .unwrap(); } } impl backend::Backend for Backend { fn name(&self) -> &str { "termion" } fn set_title(&mut self, title: String) { write!(self.terminal.get_mut(), "\x1B]0;{}\x07", title).unwrap(); } fn set_color(&self, color: theme::ColorPair) -> theme::ColorPair { let current_style = self.current_style.get(); if current_style != color { self.apply_colors(color); self.current_style.set(color); } current_style } fn set_effect(&self, effect: theme::Effect) { match effect { theme::Effect::Simple => (), theme::Effect::Reverse => self.write(tstyle::Invert), theme::Effect::Dim => self.write(tstyle::Faint), theme::Effect::Bold => self.write(tstyle::Bold), theme::Effect::Blink => self.write(tstyle::Blink), theme::Effect::Italic => self.write(tstyle::Italic), theme::Effect::Strikethrough => self.write(tstyle::CrossedOut), theme::Effect::Underline => self.write(tstyle::Underline), } } fn unset_effect(&self, effect: theme::Effect) { match effect { theme::Effect::Simple => (), theme::Effect::Reverse => self.write(tstyle::NoInvert), theme::Effect::Dim | theme::Effect::Bold => { self.write(tstyle::NoFaint) } theme::Effect::Blink => self.write(tstyle::NoBlink), theme::Effect::Italic => self.write(tstyle::NoItalic), theme::Effect::Strikethrough => self.write(tstyle::NoCrossedOut), theme::Effect::Underline => self.write(tstyle::NoUnderline), } } fn has_colors(&self) -> bool { // TODO: color support detection? true } fn screen_size(&self) -> Vec2 { // TODO: termion::terminal_size currently requires stdout. // When available, we should try to use self.terminal or something instead. let (x, y) = termion::terminal_size().unwrap_or((1, 1)); (x, y).into() } fn clear(&self, color: theme::Color) { self.apply_colors(theme::ColorPair { front: color, back: color, }); self.write(termion::clear::All); } fn refresh(&mut self) { self.terminal.get_mut().flush().unwrap(); } fn print_at(&self, pos: Vec2, text: &str) { write!( self.terminal.borrow_mut(), "{}{}", termion::cursor::Goto(1 + pos.x as u16, 1 + pos.y as u16), text ) .unwrap(); } fn print_at_rep(&self, pos: Vec2, repetitions: usize, text: &str) { if repetitions > 0 { let mut out = self.terminal.borrow_mut(); write!( out, "{}{}", termion::cursor::Goto(1 + pos.x as u16, 1 + pos.y as u16), text ) .unwrap(); let mut dupes_left = repetitions - 1; while dupes_left > 0 { write!(out, "{}", text).unwrap(); dupes_left -= 1; } } } fn poll_event(&mut self) -> Option { if let Some(Ok(event)) = self.events.next() { Some(self.map_key(event)) } else if let Ok(()) = self.resize_receiver.try_recv() { Some(Event::WindowResize) } else { None } } } fn with_color(clr: theme::Color, f: F) -> R where F: FnOnce(&dyn tcolor::Color) -> R, { match clr { theme::Color::TerminalDefault => f(&tcolor::Reset), theme::Color::Dark(theme::BaseColor::Black) => f(&tcolor::Black), theme::Color::Dark(theme::BaseColor::Red) => f(&tcolor::Red), theme::Color::Dark(theme::BaseColor::Green) => f(&tcolor::Green), theme::Color::Dark(theme::BaseColor::Yellow) => f(&tcolor::Yellow), theme::Color::Dark(theme::BaseColor::Blue) => f(&tcolor::Blue), theme::Color::Dark(theme::BaseColor::Magenta) => f(&tcolor::Magenta), theme::Color::Dark(theme::BaseColor::Cyan) => f(&tcolor::Cyan), theme::Color::Dark(theme::BaseColor::White) => f(&tcolor::White), theme::Color::Light(theme::BaseColor::Black) => f(&tcolor::LightBlack), theme::Color::Light(theme::BaseColor::Red) => f(&tcolor::LightRed), theme::Color::Light(theme::BaseColor::Green) => f(&tcolor::LightGreen), theme::Color::Light(theme::BaseColor::Yellow) => { f(&tcolor::LightYellow) } theme::Color::Light(theme::BaseColor::Blue) => f(&tcolor::LightBlue), theme::Color::Light(theme::BaseColor::Magenta) => { f(&tcolor::LightMagenta) } theme::Color::Light(theme::BaseColor::Cyan) => f(&tcolor::LightCyan), theme::Color::Light(theme::BaseColor::White) => f(&tcolor::LightWhite), theme::Color::Rgb(r, g, b) => f(&tcolor::Rgb(r, g, b)), theme::Color::RgbLowRes(r, g, b) => { f(&tcolor::AnsiValue::rgb(r, g, b)) } } } cursive-0.20.0/src/cursive_ext.rs000064400000000000000000000101601046102023000150560ustar 00000000000000/// Extension trait for the `Cursive` root to simplify initialization. /// /// It brings backend-specific methods to initialize a `Cursive` root. /// /// # Examples /// /// ```rust,no_run /// use cursive::{Cursive, CursiveExt}; /// /// let mut siv = Cursive::new(); /// /// // Use `CursiveExt::run()` to pick one of the enabled backends, /// // depending on cargo features. /// siv.run(); /// /// // Or explicitly use a specific backend /// #[cfg(feature = "ncurses-backend")] /// siv.run_ncurses().unwrap(); /// #[cfg(feature = "panncurses-backend")] /// siv.run_pancurses().unwrap(); /// #[cfg(feature = "termion-backend")] /// siv.run_termion().unwrap(); /// #[cfg(feature = "crossterm-backend")] /// siv.run_crossterm().unwrap(); /// #[cfg(feature = "blt-backend")] /// siv.run_blt(); /// ``` pub trait CursiveExt { /// Tries to use one of the enabled backends. /// /// Will fallback to the dummy backend if no other backend feature is enabled. /// /// # Panics /// /// If the backend initialization fails. fn run(&mut self); /// Creates a new Cursive root using a ncurses backend. #[cfg(feature = "ncurses-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "ncurses-backend")))] fn run_ncurses(&mut self) -> std::io::Result<()>; /// Creates a new Cursive root using a pancurses backend. #[cfg(feature = "pancurses-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "pancurses-backend")))] fn run_pancurses(&mut self) -> std::io::Result<()>; /// Creates a new Cursive root using a termion backend. #[cfg(feature = "termion-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "termion-backend")))] fn run_termion(&mut self) -> std::io::Result<()>; /// Creates a new Cursive root using a crossterm backend. #[cfg(feature = "crossterm-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "crossterm-backend")))] fn run_crossterm(&mut self) -> Result<(), crossterm::ErrorKind>; /// Creates a new Cursive root using a bear-lib-terminal backend. #[cfg(feature = "blt-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "blt-backend")))] fn run_blt(&mut self); } impl CursiveExt for cursive_core::Cursive { fn run(&mut self) { cfg_if::cfg_if! { if #[cfg(feature = "blt-backend")] { self.run_blt() } else if #[cfg(feature = "termion-backend")] { self.run_termion().unwrap() } else if #[cfg(feature = "crossterm-backend")] { self.run_crossterm().unwrap() } else if #[cfg(feature = "pancurses-backend")] { self.run_pancurses().unwrap() } else if #[cfg(feature = "ncurses-backend")] { self.run_ncurses().unwrap() } else { log::warn!("No built-it backend, falling back to Cursive::dummy()."); self.run_dummy() } } } #[cfg(feature = "ncurses-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "curses-backend")))] fn run_ncurses(&mut self) -> std::io::Result<()> { self.try_run_with(crate::backends::curses::n::Backend::init) } #[cfg(feature = "pancurses-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "pancurses-backend")))] fn run_pancurses(&mut self) -> std::io::Result<()> { self.try_run_with(crate::backends::curses::pan::Backend::init) } #[cfg(feature = "termion-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "termion-backend")))] fn run_termion(&mut self) -> std::io::Result<()> { self.try_run_with(crate::backends::termion::Backend::init) } #[cfg(feature = "crossterm-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "crossterm-backend")))] fn run_crossterm(&mut self) -> Result<(), crossterm::ErrorKind> { self.try_run_with(crate::backends::crossterm::Backend::init) } #[cfg(feature = "blt-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "blt-backend")))] fn run_blt(&mut self) { self.run_with(crate::backends::blt::Backend::init) } } cursive-0.20.0/src/cursive_runnable.rs000064400000000000000000000145411046102023000160730ustar 00000000000000use crate::{backend, backends, Cursive, CursiveRunner}; type Initializer = dyn FnMut() -> Result, Box>; /// A runnable wrapper around `Cursive`, bundling the backend initializer. /// /// This struct embeds both `Cursive` and a backend-initializer /// (`FnMut() -> Result`), to provide a simple `.run()` method. /// /// This lets you pick the backend when creating the Cursive root, rather than /// when running it. /// /// It implements `DerefMut`, so you can use it just like a /// regular `Cursive` object. pub struct CursiveRunnable { siv: Cursive, backend_init: Box, } impl std::ops::Deref for CursiveRunnable { type Target = Cursive; fn deref(&self) -> &Cursive { &self.siv } } impl std::ops::DerefMut for CursiveRunnable { fn deref_mut(&mut self) -> &mut Cursive { &mut self.siv } } impl std::borrow::Borrow for CursiveRunnable { fn borrow(&self) -> &Cursive { self } } impl std::borrow::BorrowMut for CursiveRunnable { fn borrow_mut(&mut self) -> &mut Cursive { self } } /// Helper function to help type inference when `Box::new` would not work. fn boxed(e: impl std::error::Error + 'static) -> Box { Box::new(e) } impl CursiveRunnable { /// Creates a new Cursive wrapper using the given boxed backend initializer. fn with_initializer(backend_init: Box) -> Self { let siv = Cursive::new(); Self { siv, backend_init } } /// Creates a new Cursive wrapper, using the given backend. pub fn new(mut backend_init: F) -> Self where E: std::error::Error + 'static, F: FnMut() -> Result, E> + 'static, { Self::with_initializer(Box::new(move || backend_init().map_err(boxed))) } /// Runs the event loop with the registered backend initializer. /// /// # Panics /// /// If the backend initialization fails. pub fn run(&mut self) { self.try_run().unwrap(); } /// Runs the event loop with the registered backend initializer. pub fn try_run(&mut self) -> Result<(), Box> { self.siv.try_run_with(&mut self.backend_init) } /// Gets a runner with the registered backend. /// /// Used to manually control the event loop. In most cases, running /// `run()` will be easier. /// /// The runner will borrow `self`; when dropped, it will clear out the /// terminal, and the cursive instance will be ready for another run if /// needed. pub fn try_runner( &mut self, ) -> Result, Box> { Ok(self.siv.runner((self.backend_init)()?)) } /// Gets a runner with the registered backend. /// /// # Panics /// /// If the backend initialization fails. pub fn runner(&mut self) -> CursiveRunner<&mut Cursive> { self.try_runner().unwrap() } /// Returns a new runner on the registered backend. /// /// Used to manually control the event loop. In most cases, running /// `run()` will be easier. /// /// The runner will embed `self`; when dropped, it will clear out the /// terminal, and the cursive instance will be dropped as well. pub fn try_into_runner( mut self, ) -> Result, Box> { let backend = (self.backend_init)()?; Ok(CursiveRunner::new(self, backend)) } /// Returns a new runner on the registered backend. /// /// Used to manually control the event loop. In most cases, running /// `run()` will be easier. /// /// The runner will embed `self`; when dropped, it will clear out the /// terminal, and the cursive instance will be dropped as well. /// /// # Panics /// /// If the backend initialization fails. pub fn into_runner(self) -> CursiveRunner { self.try_into_runner().unwrap() } /// Creates a new Cursive wrapper using the dummy backend. /// /// Nothing will actually be output when calling `.run()`. pub fn dummy() -> Self { Self::new::(|| { Ok(cursive_core::backend::Dummy::init()) }) } /// Creates a new Cursive wrapper using the ncurses backend. /// /// _Requires the `ncurses-backend` feature._ #[cfg(feature = "ncurses-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "ncurses-backend")))] pub fn ncurses() -> Self { Self::new(backends::curses::n::Backend::init) } /// Creates a new Cursive wrapper using the panncurses backend. /// /// _Requires the `panncurses-backend` feature._ #[cfg(feature = "pancurses-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "pancurses-backend")))] pub fn pancurses() -> Self { Self::new(backends::curses::pan::Backend::init) } /// Creates a new Cursive wrapper using the termion backend. /// /// _Requires the `termion-backend` feature._ #[cfg(feature = "termion-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "termion-backend")))] pub fn termion() -> Self { Self::new(backends::termion::Backend::init) } /// Creates a new Cursive wrapper using the crossterm backend. /// /// _Requires the `crossterm-backend` feature._ #[cfg(feature = "crossterm-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "crossterm-backend")))] pub fn crossterm() -> Self { Self::new(backends::crossterm::Backend::init) } /// Creates a new Cursive wrapper using the bear-lib-terminal backend. /// /// _Requires the `blt-backend` feature._ #[cfg(feature = "blt-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "blt-backend")))] pub fn blt() -> Self { Self::new::(|| { Ok(backends::blt::Backend::init()) }) } /// Creates a new Cursive wrapper using one of the available backends. /// /// Picks the first backend enabled from the list: /// * BearLibTerminal /// * Termion /// * Crossterm /// * Pancurses /// * Ncurses /// * Dummy pub fn default() -> Self { Self::with_initializer(Box::new(backends::try_default)) } } cursive-0.20.0/src/lib.rs000064400000000000000000000100121046102023000132600ustar 00000000000000//! # Cursive //! //! [Cursive] is a [TUI] library - it lets you easily build rich interfaces //! for use in a terminal. //! //! [Cursive]: https://github.com/gyscos/cursive //! [TUI]: https://en.wikipedia.org/wiki/Text-based_user_interface //! //! ## Getting started //! //! * Every application should start with a [`Cursive`] object. It is the main //! entry-point to the library. //! * A declarative phase then describes the structure of the UI by adding //! views and configuring their behaviours. //! * Finally, the event loop is started by calling [`Cursive::run`]. //! //! ## Examples //! //! ```rust,no_run //! use cursive::views::TextView; //! use cursive::{Cursive, CursiveExt}; //! //! let mut siv = Cursive::new(); //! //! siv.add_layer(TextView::new("Hello World!\nPress q to quit.")); //! //! siv.add_global_callback('q', |s| s.quit()); //! //! siv.run(); //! ``` //! //! ## Views //! //! Views are the main components of a cursive interface. //! The [`views`] module contains many views to use in your //! application; if you don't find what you need, you may also implement the //! [`View`] trait and build your own. //! //! ## Callbacks //! //! Cursive is callback-driven: it reacts to events generated by user input. //! //! During the declarative phase, callbacks are set to trigger on specific //! events. These functions usually take an `&mut Cursive` argument, allowing //! them to modify the view tree at will. //! //! ## Debugging //! //! The `Cursive` root initializes the terminal on creation, and does cleanups //! on drop. While it is alive, printing to the terminal will not work //! as expected, making debugging a bit harder. //! //! One solution is to redirect stderr to a file when running the application, //! and log to it instead of stdout. //! //! Or you can use gdb as usual. //! //! ## Themes //! //! Cursive supports configuring the feels and looks of your application with //! custom themes and colors. For details see documentation of the //! [`cursive::theme`] module. //! //! [`cursive::theme`]: ./theme/index.html #![deny(missing_docs)] #![cfg_attr(feature = "doc-cfg", feature(doc_cfg))] pub use cursive_core::*; mod utf8; pub mod backends; mod cursive_ext; mod cursive_runnable; pub use cursive_ext::CursiveExt; pub use cursive_runnable::CursiveRunnable; /// Creates a new Cursive root using one of the enabled backends. /// /// Will use the first available backend from this list: /// * BearLibTerminal /// * Termion /// * Crossterm /// * Pancurses /// * Ncurses /// /// If none of these is enabled, it will default to a dummy backend. /// /// # Panics /// /// If the backend initialization fails. pub fn default() -> CursiveRunnable { CursiveRunnable::default() } /// Creates a new Cursive root using a ncurses backend. #[cfg(feature = "ncurses-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "ncurses-backend")))] pub fn ncurses() -> CursiveRunnable { CursiveRunnable::ncurses() } /// Creates a new Cursive root using a pancurses backend. #[cfg(feature = "pancurses-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "pancurses-backend")))] pub fn pancurses() -> CursiveRunnable { CursiveRunnable::pancurses() } /// Creates a new Cursive root using a termion backend. #[cfg(feature = "termion-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "termion-backend")))] pub fn termion() -> CursiveRunnable { CursiveRunnable::termion() } /// Creates a new Cursive root using a crossterm backend. #[cfg(feature = "crossterm-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "crossterm-backend")))] pub fn crossterm() -> CursiveRunnable { CursiveRunnable::crossterm() } /// Creates a new Cursive root using a bear-lib-terminal backend. #[cfg(feature = "blt-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "blt-backend")))] pub fn blt() -> CursiveRunnable { CursiveRunnable::blt() } /// Creates a new Cursive root using a dummy backend. /// /// Nothing will be output. This is mostly here for tests. pub fn dummy() -> CursiveRunnable { CursiveRunnable::dummy() } cursive-0.20.0/src/utf8.rs000064400000000000000000000033441046102023000134120ustar 00000000000000use std::char::from_u32; /// Reads a potentially multi-bytes utf8 codepoint. /// /// Reads the given first byte, and uses the given /// function to get more if needed. /// /// Returns an error if the stream is invalid utf-8. #[allow(dead_code)] pub fn read_char(first: u8, next: F) -> Result where F: Fn() -> Option, { if first < 0x80 { return Ok(first as char); } // Number of leading 1s determines the number of bytes we'll have to read let n_bytes = match (!first).leading_zeros() { n @ 2..=6 => n as usize, 1 => return Err("First byte is continuation byte.".to_string()), 7..=8 => return Err("WTF is this byte??".to_string()), _ => unreachable!(), }; let mut res = 0_u32; // First, get the data - only the few last bits res |= u32::from(first & make_mask(7 - n_bytes)); // We already have one byte, now read the others. for _ in 1..n_bytes { let byte = next().ok_or_else(|| "Missing UTF-8 byte".to_string())?; if byte & 0xC0 != 0x80 { return Err(format!( "Found non-continuation byte after leading: \ {}", byte )); } // We have 6 fresh new bits to read, make room. res <<= 6; // 0x3F is 00111111, so we keep the last 6 bits res |= u32::from(byte & 0x3F); } // from_u32 could return an error if we gave it invalid utf-8. // But we're probably safe since we respected the rules when building it. Ok(from_u32(res).unwrap()) } // Returns a simple bitmask with n 1s to the right. #[allow(dead_code)] fn make_mask(n: usize) -> u8 { let mut r = 0_u8; for i in 0..n { r |= 1 << i; } r }