ratatui-core-0.1.0/.cargo_vcs_info.json0000644000000001520000000000100134270ustar { "git": { "sha1": "0a2a7c0363a4806b0cf05c1915bf7cdd438f756c" }, "path_in_vcs": "ratatui-core" }ratatui-core-0.1.0/Cargo.lock0000644000000443200000000000100114070ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "allocator-api2" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "anstyle" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "approx" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" dependencies = [ "num-traits", ] [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bitflags" version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" dependencies = [ "serde_core", ] [[package]] name = "by_address" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" [[package]] name = "castaway" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" dependencies = [ "rustversion", ] [[package]] name = "cfg-if" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "compact_str" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a" dependencies = [ "castaway", "cfg-if", "itoa", "rustversion", "ryu", "serde", "static_assertions", ] [[package]] name = "diff" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" [[package]] name = "document-features" version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" dependencies = [ "litrs", ] [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "fast-srgb8" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" [[package]] name = "foldhash" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" [[package]] name = "futures-core" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-macro" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "futures-task" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-timer" version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" [[package]] name = "futures-util" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", "futures-macro", "futures-task", "pin-project-lite", "pin-utils", "slab", ] [[package]] name = "glob" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "hashbrown" version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" [[package]] name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ "allocator-api2", "equivalent", "foldhash", ] [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "indexmap" version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", "hashbrown 0.15.5", ] [[package]] name = "indoc" version = "2.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" dependencies = [ "rustversion", ] [[package]] name = "itertools" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] [[package]] name = "itertools" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" dependencies = [ "either", ] [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "kasuari" version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fe90c1150662e858c7d5f945089b7517b0a80d8bf7ba4b1b5ffc984e7230a5b" dependencies = [ "hashbrown 0.16.1", "portable-atomic", "portable-atomic-util", "thiserror", ] [[package]] name = "litrs" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" [[package]] name = "lru" version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96051b46fc183dc9cd4a223960ef37b9af631b55191852a8274bfef064cda20f" dependencies = [ "hashbrown 0.16.1", ] [[package]] name = "memchr" version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "palette" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cbf71184cc5ecc2e4e1baccdb21026c20e5fc3dcf63028a086131b3ab00b6e6" dependencies = [ "approx", "fast-srgb8", "palette_derive", "phf", ] [[package]] name = "palette_derive" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5030daf005bface118c096f510ffb781fc28f9ab6a32ab224d8631be6851d30" dependencies = [ "by_address", "proc-macro2", "quote", "syn", ] [[package]] name = "phf" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ "phf_macros", "phf_shared", ] [[package]] name = "phf_generator" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared", "rand", ] [[package]] name = "phf_macros" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" dependencies = [ "phf_generator", "phf_shared", "proc-macro2", "quote", "syn", ] [[package]] name = "phf_shared" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ "siphasher", ] [[package]] name = "pin-project-lite" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "portable-atomic" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" [[package]] name = "portable-atomic-util" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" dependencies = [ "portable-atomic", ] [[package]] name = "pretty_assertions" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" dependencies = [ "diff", "yansi", ] [[package]] name = "proc-macro-crate" version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" dependencies = [ "toml_edit", ] [[package]] name = "proc-macro2" version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "rand_core", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" [[package]] name = "ratatui-core" version = "0.1.0" dependencies = [ "anstyle", "bitflags", "compact_str", "document-features", "hashbrown 0.16.1", "indoc", "itertools 0.14.0", "kasuari", "lru", "palette", "pretty_assertions", "rstest", "serde", "serde_json", "strum", "thiserror", "unicode-segmentation", "unicode-truncate", "unicode-width", ] [[package]] name = "regex" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "relative-path" version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" [[package]] name = "rstest" version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5a3193c063baaa2a95a33f03035c8a72b83d97a54916055ba22d35ed3839d49" dependencies = [ "futures-timer", "futures-util", "rstest_macros", ] [[package]] name = "rstest_macros" version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c845311f0ff7951c5506121a9ad75aec44d083c31583b2ea5a30bcb0b0abba0" dependencies = [ "cfg-if", "glob", "proc-macro-crate", "proc-macro2", "quote", "regex", "relative-path", "rustc_version", "syn", "unicode-ident", ] [[package]] name = "rustc_version" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] [[package]] name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "semver" version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" [[package]] name = "serde" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", ] [[package]] name = "serde_core" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.146" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "217ca874ae0207aac254aa02c957ded05585a90892cc8d87f9e5fa49669dadd8" dependencies = [ "itoa", "memchr", "ryu", "serde", "serde_core", ] [[package]] name = "siphasher" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "slab" version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "strum" version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" dependencies = [ "heck", "proc-macro2", "quote", "syn", ] [[package]] name = "syn" version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "thiserror" version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "toml_datetime" version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" [[package]] name = "toml_edit" version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", "toml_datetime", "winnow", ] [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-segmentation" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-truncate" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fbf03860ff438702f3910ca5f28f8dac63c1c11e7efb5012b8b175493606330" dependencies = [ "itertools 0.13.0", "unicode-segmentation", "unicode-width", ] [[package]] name = "unicode-width" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] name = "winnow" version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" dependencies = [ "memchr", ] [[package]] name = "yansi" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" ratatui-core-0.1.0/Cargo.toml0000644000000077130000000000100114370ustar # 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 = "2024" rust-version = "1.86.0" name = "ratatui-core" version = "0.1.0" authors = [ "Florian Dehau ", "The Ratatui Developers", ] build = false exclude = [ "assets/*", ".github", "Makefile.toml", "CONTRIBUTING.md", "*.log", "tags", ] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = """ Core types and traits for the Ratatui Terminal UI library. Widget libraries should use this crate. Applications should use the main Ratatui crate. """ homepage = "https://ratatui.rs" documentation = "https://docs.rs/ratatui-core" readme = "README.md" keywords = [ "tui", "terminal", "dashboard", ] categories = ["command-line-interface"] license = "MIT" repository = "https://github.com/ratatui/ratatui" resolver = "2" [package.metadata.docs.rs] all-features = true rustdoc-args = [ "--cfg", "docsrs", ] [features] anstyle = ["dep:anstyle"] default = [] layout-cache = ["std"] palette = [ "std", "dep:palette", ] portable-atomic = ["kasuari/portable-atomic"] scrolling-regions = [] serde = [ "std", "dep:serde", "bitflags/serde", "compact_str/serde", ] std = [ "itertools/use_std", "thiserror/std", "kasuari/std", "compact_str/std", "unicode-truncate/std", "strum/std", ] underline-color = [] [lib] name = "ratatui_core" path = "src/lib.rs" [[test]] name = "rect" path = "tests/rect.rs" [dependencies.anstyle] version = "1" optional = true [dependencies.bitflags] version = "2.10" [dependencies.compact_str] version = "0.9" default-features = false [dependencies.document-features] version = "0.2" optional = true [dependencies.hashbrown] version = "0.16" [dependencies.indoc] version = "2" [dependencies.itertools] version = "0.14" features = ["use_alloc"] default-features = false [dependencies.kasuari] version = "0.4" default-features = false [dependencies.lru] version = "0.16" [dependencies.palette] version = "0.7" optional = true [dependencies.serde] version = "1" features = ["derive"] optional = true [dependencies.strum] version = "0.27" features = ["derive"] default-features = false [dependencies.thiserror] version = "2" default-features = false [dependencies.unicode-segmentation] version = "1" [dependencies.unicode-truncate] version = "2" default-features = false [dependencies.unicode-width] version = ">=0.2.0, <=0.2.2" [dev-dependencies.pretty_assertions] version = "1" [dev-dependencies.rstest] version = "0.26" [dev-dependencies.serde_json] version = "1" [lints.clippy] as_underscore = "warn" cast_possible_truncation = "allow" cast_possible_wrap = "allow" cast_precision_loss = "allow" cast_sign_loss = "allow" deref_by_slicing = "warn" else_if_without_else = "warn" empty_line_after_doc_comments = "warn" equatable_if_let = "warn" fn_to_numeric_cast_any = "warn" format_push_string = "warn" implicit_clone = "warn" map_err_ignore = "warn" missing_const_for_fn = "warn" missing_errors_doc = "allow" missing_panics_doc = "allow" mixed_read_write_in_expression = "warn" mod_module_files = "warn" module_inception = "allow" module_name_repetitions = "allow" must_use_candidate = "allow" needless_pass_by_ref_mut = "warn" needless_raw_strings = "warn" or_fun_call = "warn" redundant_type_annotations = "warn" rest_pat_in_fully_bound_structs = "warn" string_lit_chars_any = "warn" string_slice = "warn" unnecessary_self_imports = "warn" use_self = "warn" [lints.clippy.pedantic] level = "warn" priority = -1 [lints.rust] unsafe_code = "forbid" ratatui-core-0.1.0/Cargo.toml.orig000064400000000000000000000047431046102023000151200ustar 00000000000000[package] name = "ratatui-core" version = "0.1.0" description = """ Core types and traits for the Ratatui Terminal UI library. Widget libraries should use this crate. Applications should use the main Ratatui crate. """ documentation = "https://docs.rs/ratatui-core" readme = "README.md" authors.workspace = true repository.workspace = true homepage.workspace = true keywords.workspace = true categories.workspace = true license.workspace = true exclude.workspace = true edition.workspace = true rust-version.workspace = true [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] [features] default = [] ## enables std std = [ "itertools/use_std", "thiserror/std", "kasuari/std", "compact_str/std", "unicode-truncate/std", "strum/std", ] ## enables layout cache layout-cache = ["std"] ## enables conversions to / from colors, modifiers, and styles in the ['anstyle'] crate anstyle = ["dep:anstyle"] ## enables conversions from colors in the [`palette`] crate to [`Color`](crate::style::Color). palette = ["std", "dep:palette"] ## enables portable-atomic integration for targets that don't support atomic types. portable-atomic = ["kasuari/portable-atomic"] ## enables the backend code that sets the underline color. Underline color is only supported by ## the Crossterm backend, and is not supported on Windows 7. underline-color = [] ## Use terminal scrolling regions to make some operations less prone to ## flickering. (i.e. Terminal::insert_before). scrolling-regions = [] ## enables serialization and deserialization of style and color types using the [`serde`] crate. ## This is useful if you want to save themes to a file. serde = ["std", "dep:serde", "bitflags/serde", "compact_str/serde"] [dependencies] anstyle = { workspace = true, optional = true } bitflags.workspace = true compact_str.workspace = true document-features = { workspace = true, optional = true } hashbrown.workspace = true indoc.workspace = true itertools.workspace = true kasuari = { workspace = true, default-features = false } lru.workspace = true palette = { workspace = true, optional = true } serde = { workspace = true, optional = true } strum.workspace = true thiserror = { workspace = true, default-features = false } unicode-segmentation.workspace = true unicode-truncate = { workspace = true, default-features = false } unicode-width.workspace = true [dev-dependencies] pretty_assertions.workspace = true rstest.workspace = true serde_json.workspace = true [lints] workspace = true ratatui-core-0.1.0/README.md000064400000000000000000000050021046102023000134750ustar 00000000000000# Ratatui Core [![Crates.io](https://img.shields.io/crates/v/ratatui-core)](https://crates.io/crates/ratatui-core) [![Documentation](https://docs.rs/ratatui-core/badge.svg)](https://docs.rs/ratatui-core) [![License](https://img.shields.io/crates/l/ratatui-core)](../LICENSE) **ratatui-core** is the core library of the [ratatui] project, providing the essential building blocks for creating rich terminal user interfaces in Rust. [ratatui]: https://github.com/ratatui/ratatui ### Why `ratatui-core`? The `ratatui-core` crate is split from the main [`ratatui`](https://crates.io/crates/ratatui) crate to offer better stability for widget library authors. Widget libraries should generally depend on `ratatui-core`, benefiting from a stable API and reducing the need for frequent updates. Applications, on the other hand, should depend on the main `ratatui` crate, which includes built-in widgets and additional features. ## Installation Add `ratatui-core` to your `Cargo.toml`: ```shell cargo add ratatui-core ``` ## Crate Organization `ratatui-core` is part of the Ratatui workspace that was modularized in version 0.30.0 to improve compilation times, API stability, and dependency management. This crate provides the foundational types and traits that other crates in the workspace depend on. **When to use `ratatui-core`:** - Building widget libraries that implement [`Widget`] or [`StatefulWidget`] - Creating lightweight applications that don't need built-in widgets - You want minimal dependencies and faster compilation times - You need maximum API stability (core types change less frequently) **When to use the main [`ratatui`] crate:** - Building applications that use built-in widgets - You want convenience and don't mind slightly longer compilation times - You need backend implementations and terminal management utilities For detailed information about the workspace organization, see [ARCHITECTURE.md]. [`ratatui`]: https://crates.io/crates/ratatui [`Widget`]: widgets::Widget [`StatefulWidget`]: widgets::StatefulWidget [ARCHITECTURE.md]: https://github.com/ratatui/ratatui/blob/main/ARCHITECTURE.md ## Contributing We welcome contributions from the community! Please see our [CONTRIBUTING](../CONTRIBUTING.md) guide for more details on how to get involved. ### License This project is licensed under the MIT License. See the [LICENSE](../LICENSE) file for details. ratatui-core-0.1.0/src/backend/test.rs000064400000000000000000001104741046102023000157330ustar 00000000000000//! This module provides the `TestBackend` implementation for the [`Backend`] trait. //! It is used in the integration tests to verify the correctness of the library. use alloc::string::String; use alloc::vec; use core::fmt::{self, Write}; use core::iter; use unicode_width::UnicodeWidthStr; use crate::backend::{Backend, ClearType, WindowSize}; use crate::buffer::{Buffer, Cell}; use crate::layout::{Position, Rect, Size}; /// A [`Backend`] implementation used for integration testing that renders to an memory buffer. /// /// Note: that although many of the integration and unit tests in ratatui are written using this /// backend, it is preferable to write unit tests for widgets directly against the buffer rather /// than using this backend. This backend is intended for integration tests that test the entire /// terminal UI. /// /// # Example /// /// ```rust,ignore /// use ratatui::backend::{Backend, TestBackend}; /// /// let mut backend = TestBackend::new(10, 2); /// backend.clear()?; /// backend.assert_buffer_lines([" "; 2]); /// # Result::Ok(()) /// ``` #[derive(Debug, Clone, Eq, PartialEq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct TestBackend { buffer: Buffer, scrollback: Buffer, cursor: bool, pos: (u16, u16), } /// Returns a string representation of the given buffer for debugging purpose. /// /// This function is used to visualize the buffer content in a human-readable format. /// It iterates through the buffer content and appends each cell's symbol to the view string. /// If a cell is hidden by a multi-width symbol, it is added to the overwritten vector and /// displayed at the end of the line. fn buffer_view(buffer: &Buffer) -> String { let mut view = String::with_capacity(buffer.content.len() + buffer.area.height as usize * 3); for cells in buffer.content.chunks(buffer.area.width as usize) { let mut overwritten = vec![]; let mut skip: usize = 0; view.push('"'); for (x, c) in cells.iter().enumerate() { if skip == 0 { view.push_str(c.symbol()); } else { overwritten.push((x, c.symbol())); } skip = core::cmp::max(skip, c.symbol().width()).saturating_sub(1); } view.push('"'); if !overwritten.is_empty() { write!(&mut view, " Hidden by multi-width symbols: {overwritten:?}").unwrap(); } view.push('\n'); } view } impl TestBackend { /// Creates a new `TestBackend` with the specified width and height. pub fn new(width: u16, height: u16) -> Self { Self { buffer: Buffer::empty(Rect::new(0, 0, width, height)), scrollback: Buffer::empty(Rect::new(0, 0, width, 0)), cursor: false, pos: (0, 0), } } /// Creates a new `TestBackend` with the specified lines as the initial screen state. /// /// The backend's screen size is determined from the initial lines. #[must_use] pub fn with_lines<'line, Lines>(lines: Lines) -> Self where Lines: IntoIterator, Lines::Item: Into>, { let buffer = Buffer::with_lines(lines); let scrollback = Buffer::empty(Rect { width: buffer.area.width, ..Rect::ZERO }); Self { buffer, scrollback, cursor: false, pos: (0, 0), } } /// Returns a reference to the internal buffer of the `TestBackend`. pub const fn buffer(&self) -> &Buffer { &self.buffer } /// Returns a reference to the internal scrollback buffer of the `TestBackend`. /// /// The scrollback buffer represents the part of the screen that is currently hidden from view, /// but that could be accessed by scrolling back in the terminal's history. This would normally /// be done using the terminal's scrollbar or an equivalent keyboard shortcut. /// /// The scrollback buffer starts out empty. Lines are appended when they scroll off the top of /// the main buffer. This happens when lines are appended to the bottom of the main buffer /// using [`Backend::append_lines`]. /// /// The scrollback buffer has a maximum height of [`u16::MAX`]. If lines are appended to the /// bottom of the scrollback buffer when it is at its maximum height, a corresponding number of /// lines will be removed from the top. pub const fn scrollback(&self) -> &Buffer { &self.scrollback } /// Resizes the `TestBackend` to the specified width and height. pub fn resize(&mut self, width: u16, height: u16) { self.buffer.resize(Rect::new(0, 0, width, height)); let scrollback_height = self.scrollback.area.height; self.scrollback .resize(Rect::new(0, 0, width, scrollback_height)); } /// Asserts that the `TestBackend`'s buffer is equal to the expected buffer. /// /// This is a shortcut for `assert_eq!(self.buffer(), &expected)`. /// /// # Panics /// /// When they are not equal, a panic occurs with a detailed error message showing the /// differences between the expected and actual buffers. #[expect(deprecated)] #[track_caller] pub fn assert_buffer(&self, expected: &Buffer) { // TODO: use assert_eq!() crate::assert_buffer_eq!(&self.buffer, expected); } /// Asserts that the `TestBackend`'s scrollback buffer is equal to the expected buffer. /// /// This is a shortcut for `assert_eq!(self.scrollback(), &expected)`. /// /// # Panics /// /// When they are not equal, a panic occurs with a detailed error message showing the /// differences between the expected and actual buffers. #[track_caller] pub fn assert_scrollback(&self, expected: &Buffer) { assert_eq!(&self.scrollback, expected); } /// Asserts that the `TestBackend`'s scrollback buffer is empty. /// /// # Panics /// /// When the scrollback buffer is not equal, a panic occurs with a detailed error message /// showing the differences between the expected and actual buffers. pub fn assert_scrollback_empty(&self) { let expected = Buffer { area: Rect { width: self.scrollback.area.width, ..Rect::ZERO }, content: vec![], }; self.assert_scrollback(&expected); } /// Asserts that the `TestBackend`'s buffer is equal to the expected lines. /// /// This is a shortcut for `assert_eq!(self.buffer(), &Buffer::with_lines(expected))`. /// /// # Panics /// /// When they are not equal, a panic occurs with a detailed error message showing the /// differences between the expected and actual buffers. #[track_caller] pub fn assert_buffer_lines<'line, Lines>(&self, expected: Lines) where Lines: IntoIterator, Lines::Item: Into>, { self.assert_buffer(&Buffer::with_lines(expected)); } /// Asserts that the `TestBackend`'s scrollback buffer is equal to the expected lines. /// /// This is a shortcut for `assert_eq!(self.scrollback(), &Buffer::with_lines(expected))`. /// /// # Panics /// /// When they are not equal, a panic occurs with a detailed error message showing the /// differences between the expected and actual buffers. #[track_caller] pub fn assert_scrollback_lines<'line, Lines>(&self, expected: Lines) where Lines: IntoIterator, Lines::Item: Into>, { self.assert_scrollback(&Buffer::with_lines(expected)); } /// Asserts that the `TestBackend`'s cursor position is equal to the expected one. /// /// This is a shortcut for `assert_eq!(self.get_cursor_position().unwrap(), expected)`. /// /// # Panics /// /// When they are not equal, a panic occurs with a detailed error message showing the /// differences between the expected and actual position. #[track_caller] pub fn assert_cursor_position>(&mut self, position: P) { let actual = self.get_cursor_position().unwrap(); assert_eq!(actual, position.into()); } } impl fmt::Display for TestBackend { /// Formats the `TestBackend` for display by calling the `buffer_view` function /// on its internal buffer. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", buffer_view(&self.buffer)) } } type Result = core::result::Result; impl Backend for TestBackend { type Error = core::convert::Infallible; fn draw<'a, I>(&mut self, content: I) -> Result<()> where I: Iterator, { for (x, y, c) in content { self.buffer[(x, y)] = c.clone(); } Ok(()) } fn hide_cursor(&mut self) -> Result<()> { self.cursor = false; Ok(()) } fn show_cursor(&mut self) -> Result<()> { self.cursor = true; Ok(()) } fn get_cursor_position(&mut self) -> Result { Ok(self.pos.into()) } fn set_cursor_position>(&mut self, position: P) -> Result<()> { self.pos = position.into().into(); Ok(()) } fn clear(&mut self) -> Result<()> { self.buffer.reset(); Ok(()) } fn clear_region(&mut self, clear_type: ClearType) -> Result<()> { let region = match clear_type { ClearType::All => return self.clear(), ClearType::AfterCursor => { let index = self.buffer.index_of(self.pos.0, self.pos.1) + 1; &mut self.buffer.content[index..] } ClearType::BeforeCursor => { let index = self.buffer.index_of(self.pos.0, self.pos.1); &mut self.buffer.content[..index] } ClearType::CurrentLine => { let line_start_index = self.buffer.index_of(0, self.pos.1); let line_end_index = self.buffer.index_of(self.buffer.area.width - 1, self.pos.1); &mut self.buffer.content[line_start_index..=line_end_index] } ClearType::UntilNewLine => { let index = self.buffer.index_of(self.pos.0, self.pos.1); let line_end_index = self.buffer.index_of(self.buffer.area.width - 1, self.pos.1); &mut self.buffer.content[index..=line_end_index] } }; for cell in region { cell.reset(); } Ok(()) } /// Inserts n line breaks at the current cursor position. /// /// After the insertion, the cursor x position will be incremented by 1 (unless it's already /// at the end of line). This is a common behaviour of terminals in raw mode. /// /// If the number of lines to append is fewer than the number of lines in the buffer after the /// cursor y position then the cursor is moved down by n rows. /// /// If the number of lines to append is greater than the number of lines in the buffer after /// the cursor y position then that number of empty lines (at most the buffer's height in this /// case but this limit is instead replaced with scrolling in most backend implementations) will /// be added after the current position and the cursor will be moved to the last row. fn append_lines(&mut self, line_count: u16) -> Result<()> { let Position { x: cur_x, y: cur_y } = self.get_cursor_position()?; let Rect { width, height, .. } = self.buffer.area; // the next column ensuring that we don't go past the last column let new_cursor_x = cur_x.saturating_add(1).min(width.saturating_sub(1)); let max_y = height.saturating_sub(1); let lines_after_cursor = max_y.saturating_sub(cur_y); if line_count > lines_after_cursor { // We need to insert blank lines at the bottom and scroll the lines from the top into // scrollback. let scroll_by: usize = (line_count - lines_after_cursor).into(); let width: usize = self.buffer.area.width.into(); let cells_to_scrollback = self.buffer.content.len().min(width * scroll_by); append_to_scrollback( &mut self.scrollback, self.buffer.content.splice( 0..cells_to_scrollback, iter::repeat_with(Default::default).take(cells_to_scrollback), ), ); self.buffer.content.rotate_left(cells_to_scrollback); append_to_scrollback( &mut self.scrollback, iter::repeat_with(Default::default).take(width * scroll_by - cells_to_scrollback), ); } let new_cursor_y = cur_y.saturating_add(line_count).min(max_y); self.set_cursor_position(Position::new(new_cursor_x, new_cursor_y))?; Ok(()) } fn size(&self) -> Result { Ok(self.buffer.area.as_size()) } fn window_size(&mut self) -> Result { // Some arbitrary window pixel size, probably doesn't need much testing. const WINDOW_PIXEL_SIZE: Size = Size { width: 640, height: 480, }; Ok(WindowSize { columns_rows: self.buffer.area.as_size(), pixels: WINDOW_PIXEL_SIZE, }) } fn flush(&mut self) -> Result<()> { Ok(()) } #[cfg(feature = "scrolling-regions")] fn scroll_region_up(&mut self, region: core::ops::Range, scroll_by: u16) -> Result<()> { let width: usize = self.buffer.area.width.into(); let cell_region_start = width * region.start.min(self.buffer.area.height) as usize; let cell_region_end = width * region.end.min(self.buffer.area.height) as usize; let cell_region_len = cell_region_end - cell_region_start; let cells_to_scroll_by = width * scroll_by as usize; // Deal with the simple case where nothing needs to be copied into scrollback. if cell_region_start > 0 { if cells_to_scroll_by >= cell_region_len { // The scroll amount is large enough to clear the whole region. self.buffer.content[cell_region_start..cell_region_end].fill_with(Default::default); } else { // Scroll up by rotating, then filling in the bottom with empty cells. self.buffer.content[cell_region_start..cell_region_end] .rotate_left(cells_to_scroll_by); self.buffer.content[cell_region_end - cells_to_scroll_by..cell_region_end] .fill_with(Default::default); } return Ok(()); } // The rows inserted into the scrollback will first come from the buffer, and if that is // insufficient, will then be blank rows. let cells_from_region = cell_region_len.min(cells_to_scroll_by); append_to_scrollback( &mut self.scrollback, self.buffer.content.splice( 0..cells_from_region, iter::repeat_with(Default::default).take(cells_from_region), ), ); if cells_to_scroll_by < cell_region_len { // Rotate the remaining cells to the front of the region. self.buffer.content[cell_region_start..cell_region_end].rotate_left(cells_from_region); } else { // Splice cleared out the region. Insert empty rows in scrollback. append_to_scrollback( &mut self.scrollback, iter::repeat_with(Default::default).take(cells_to_scroll_by - cell_region_len), ); } Ok(()) } #[cfg(feature = "scrolling-regions")] fn scroll_region_down(&mut self, region: core::ops::Range, scroll_by: u16) -> Result<()> { let width: usize = self.buffer.area.width.into(); let cell_region_start = width * region.start.min(self.buffer.area.height) as usize; let cell_region_end = width * region.end.min(self.buffer.area.height) as usize; let cell_region_len = cell_region_end - cell_region_start; let cells_to_scroll_by = width * scroll_by as usize; if cells_to_scroll_by >= cell_region_len { // The scroll amount is large enough to clear the whole region. self.buffer.content[cell_region_start..cell_region_end].fill_with(Default::default); } else { // Scroll up by rotating, then filling in the top with empty cells. self.buffer.content[cell_region_start..cell_region_end] .rotate_right(cells_to_scroll_by); self.buffer.content[cell_region_start..cell_region_start + cells_to_scroll_by] .fill_with(Default::default); } Ok(()) } } /// Append the provided cells to the bottom of a scrollback buffer. The number of cells must be a /// multiple of the buffer's width. If the scrollback buffer ends up larger than 65535 lines tall, /// then lines will be removed from the top to get it down to size. fn append_to_scrollback(scrollback: &mut Buffer, cells: impl IntoIterator) { scrollback.content.extend(cells); let width = scrollback.area.width as usize; let new_height = (scrollback.content.len() / width).min(u16::MAX as usize); let keep_from = scrollback .content .len() .saturating_sub(width * u16::MAX as usize); scrollback.content.drain(0..keep_from); scrollback.area.height = new_height as u16; } #[cfg(test)] mod tests { use alloc::format; use itertools::Itertools as _; use super::*; #[test] fn new() { assert_eq!( TestBackend::new(10, 2), TestBackend { buffer: Buffer::with_lines([" "; 2]), scrollback: Buffer::empty(Rect::new(0, 0, 10, 0)), cursor: false, pos: (0, 0), } ); } #[test] fn test_buffer_view() { let buffer = Buffer::with_lines(["aaaa"; 2]); assert_eq!(buffer_view(&buffer), "\"aaaa\"\n\"aaaa\"\n"); } #[test] fn buffer_view_with_overwrites() { let multi_byte_char = "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ"; // renders 2 wide let buffer = Buffer::with_lines([multi_byte_char]); assert_eq!( buffer_view(&buffer), format!( r#""{multi_byte_char}" Hidden by multi-width symbols: [(1, " ")] "#, ) ); } #[test] fn buffer() { let backend = TestBackend::new(10, 2); backend.assert_buffer_lines([" "; 2]); } #[test] fn resize() { let mut backend = TestBackend::new(10, 2); backend.resize(5, 5); backend.assert_buffer_lines([" "; 5]); } #[test] fn assert_buffer() { let backend = TestBackend::new(10, 2); backend.assert_buffer_lines([" "; 2]); } #[test] #[should_panic = "buffer contents not equal"] fn assert_buffer_panics() { let backend = TestBackend::new(10, 2); backend.assert_buffer_lines(["aaaaaaaaaa"; 2]); } #[test] #[should_panic = "assertion `left == right` failed"] fn assert_scrollback_panics() { let backend = TestBackend::new(10, 2); backend.assert_scrollback_lines(["aaaaaaaaaa"; 2]); } #[test] fn display() { let backend = TestBackend::new(10, 2); assert_eq!(format!("{backend}"), "\" \"\n\" \"\n"); } #[test] fn draw() { let mut backend = TestBackend::new(10, 2); let cell = Cell::new("a"); backend.draw([(0, 0, &cell)].into_iter()).unwrap(); backend.draw([(0, 1, &cell)].into_iter()).unwrap(); backend.assert_buffer_lines(["a "; 2]); } #[test] fn hide_cursor() { let mut backend = TestBackend::new(10, 2); backend.hide_cursor().unwrap(); assert!(!backend.cursor); } #[test] fn show_cursor() { let mut backend = TestBackend::new(10, 2); backend.show_cursor().unwrap(); assert!(backend.cursor); } #[test] fn get_cursor_position() { let mut backend = TestBackend::new(10, 2); assert_eq!(backend.get_cursor_position().unwrap(), Position::ORIGIN); } #[test] fn assert_cursor_position() { let mut backend = TestBackend::new(10, 2); backend.assert_cursor_position(Position::ORIGIN); } #[test] fn set_cursor_position() { let mut backend = TestBackend::new(10, 10); backend .set_cursor_position(Position { x: 5, y: 5 }) .unwrap(); assert_eq!(backend.pos, (5, 5)); } #[test] fn clear() { let mut backend = TestBackend::new(4, 2); let cell = Cell::new("a"); backend.draw([(0, 0, &cell)].into_iter()).unwrap(); backend.draw([(0, 1, &cell)].into_iter()).unwrap(); backend.clear().unwrap(); backend.assert_buffer_lines([" ", " "]); } #[test] fn clear_region_all() { let mut backend = TestBackend::with_lines([ "aaaaaaaaaa", "aaaaaaaaaa", "aaaaaaaaaa", "aaaaaaaaaa", "aaaaaaaaaa", ]); backend.clear_region(ClearType::All).unwrap(); backend.assert_buffer_lines([ " ", " ", " ", " ", " ", ]); } #[test] fn clear_region_after_cursor() { let mut backend = TestBackend::with_lines([ "aaaaaaaaaa", "aaaaaaaaaa", "aaaaaaaaaa", "aaaaaaaaaa", "aaaaaaaaaa", ]); backend .set_cursor_position(Position { x: 3, y: 2 }) .unwrap(); backend.clear_region(ClearType::AfterCursor).unwrap(); backend.assert_buffer_lines([ "aaaaaaaaaa", "aaaaaaaaaa", "aaaa ", " ", " ", ]); } #[test] fn clear_region_before_cursor() { let mut backend = TestBackend::with_lines([ "aaaaaaaaaa", "aaaaaaaaaa", "aaaaaaaaaa", "aaaaaaaaaa", "aaaaaaaaaa", ]); backend .set_cursor_position(Position { x: 5, y: 3 }) .unwrap(); backend.clear_region(ClearType::BeforeCursor).unwrap(); backend.assert_buffer_lines([ " ", " ", " ", " aaaaa", "aaaaaaaaaa", ]); } #[test] fn clear_region_current_line() { let mut backend = TestBackend::with_lines([ "aaaaaaaaaa", "aaaaaaaaaa", "aaaaaaaaaa", "aaaaaaaaaa", "aaaaaaaaaa", ]); backend .set_cursor_position(Position { x: 3, y: 1 }) .unwrap(); backend.clear_region(ClearType::CurrentLine).unwrap(); backend.assert_buffer_lines([ "aaaaaaaaaa", " ", "aaaaaaaaaa", "aaaaaaaaaa", "aaaaaaaaaa", ]); } #[test] fn clear_region_until_new_line() { let mut backend = TestBackend::with_lines([ "aaaaaaaaaa", "aaaaaaaaaa", "aaaaaaaaaa", "aaaaaaaaaa", "aaaaaaaaaa", ]); backend .set_cursor_position(Position { x: 3, y: 0 }) .unwrap(); backend.clear_region(ClearType::UntilNewLine).unwrap(); backend.assert_buffer_lines([ "aaa ", "aaaaaaaaaa", "aaaaaaaaaa", "aaaaaaaaaa", "aaaaaaaaaa", ]); } #[test] fn append_lines_not_at_last_line() { let mut backend = TestBackend::with_lines([ "aaaaaaaaaa", "bbbbbbbbbb", "cccccccccc", "dddddddddd", "eeeeeeeeee", ]); backend.set_cursor_position(Position::ORIGIN).unwrap(); // If the cursor is not at the last line in the terminal the addition of a // newline simply moves the cursor down and to the right backend.append_lines(1).unwrap(); backend.assert_cursor_position(Position { x: 1, y: 1 }); backend.append_lines(1).unwrap(); backend.assert_cursor_position(Position { x: 2, y: 2 }); backend.append_lines(1).unwrap(); backend.assert_cursor_position(Position { x: 3, y: 3 }); backend.append_lines(1).unwrap(); backend.assert_cursor_position(Position { x: 4, y: 4 }); // As such the buffer should remain unchanged backend.assert_buffer_lines([ "aaaaaaaaaa", "bbbbbbbbbb", "cccccccccc", "dddddddddd", "eeeeeeeeee", ]); backend.assert_scrollback_empty(); } #[test] fn append_lines_at_last_line() { let mut backend = TestBackend::with_lines([ "aaaaaaaaaa", "bbbbbbbbbb", "cccccccccc", "dddddddddd", "eeeeeeeeee", ]); // If the cursor is at the last line in the terminal the addition of a // newline will scroll the contents of the buffer backend .set_cursor_position(Position { x: 0, y: 4 }) .unwrap(); backend.append_lines(1).unwrap(); backend.assert_buffer_lines([ "bbbbbbbbbb", "cccccccccc", "dddddddddd", "eeeeeeeeee", " ", ]); backend.assert_scrollback_lines(["aaaaaaaaaa"]); // It also moves the cursor to the right, as is common of the behaviour of // terminals in raw-mode backend.assert_cursor_position(Position { x: 1, y: 4 }); } #[test] fn append_multiple_lines_not_at_last_line() { let mut backend = TestBackend::with_lines([ "aaaaaaaaaa", "bbbbbbbbbb", "cccccccccc", "dddddddddd", "eeeeeeeeee", ]); backend.set_cursor_position(Position::ORIGIN).unwrap(); // If the cursor is not at the last line in the terminal the addition of multiple // newlines simply moves the cursor n lines down and to the right by 1 backend.append_lines(4).unwrap(); backend.assert_cursor_position(Position { x: 1, y: 4 }); // As such the buffer should remain unchanged backend.assert_buffer_lines([ "aaaaaaaaaa", "bbbbbbbbbb", "cccccccccc", "dddddddddd", "eeeeeeeeee", ]); backend.assert_scrollback_empty(); } #[test] fn append_multiple_lines_past_last_line() { let mut backend = TestBackend::with_lines([ "aaaaaaaaaa", "bbbbbbbbbb", "cccccccccc", "dddddddddd", "eeeeeeeeee", ]); backend .set_cursor_position(Position { x: 0, y: 3 }) .unwrap(); backend.append_lines(3).unwrap(); backend.assert_cursor_position(Position { x: 1, y: 4 }); backend.assert_buffer_lines([ "cccccccccc", "dddddddddd", "eeeeeeeeee", " ", " ", ]); backend.assert_scrollback_lines(["aaaaaaaaaa", "bbbbbbbbbb"]); } #[test] fn append_multiple_lines_where_cursor_at_end_appends_height_lines() { let mut backend = TestBackend::with_lines([ "aaaaaaaaaa", "bbbbbbbbbb", "cccccccccc", "dddddddddd", "eeeeeeeeee", ]); backend .set_cursor_position(Position { x: 0, y: 4 }) .unwrap(); backend.append_lines(5).unwrap(); backend.assert_cursor_position(Position { x: 1, y: 4 }); backend.assert_buffer_lines([ " ", " ", " ", " ", " ", ]); backend.assert_scrollback_lines([ "aaaaaaaaaa", "bbbbbbbbbb", "cccccccccc", "dddddddddd", "eeeeeeeeee", ]); } #[test] fn append_multiple_lines_where_cursor_appends_height_lines() { let mut backend = TestBackend::with_lines([ "aaaaaaaaaa", "bbbbbbbbbb", "cccccccccc", "dddddddddd", "eeeeeeeeee", ]); backend.set_cursor_position(Position::ORIGIN).unwrap(); backend.append_lines(5).unwrap(); backend.assert_cursor_position(Position { x: 1, y: 4 }); backend.assert_buffer_lines([ "bbbbbbbbbb", "cccccccccc", "dddddddddd", "eeeeeeeeee", " ", ]); backend.assert_scrollback_lines(["aaaaaaaaaa"]); } #[test] fn append_multiple_lines_where_cursor_at_end_appends_more_than_height_lines() { let mut backend = TestBackend::with_lines([ "aaaaaaaaaa", "bbbbbbbbbb", "cccccccccc", "dddddddddd", "eeeeeeeeee", ]); backend .set_cursor_position(Position { x: 0, y: 4 }) .unwrap(); backend.append_lines(8).unwrap(); backend.assert_cursor_position(Position { x: 1, y: 4 }); backend.assert_buffer_lines([ " ", " ", " ", " ", " ", ]); backend.assert_scrollback_lines([ "aaaaaaaaaa", "bbbbbbbbbb", "cccccccccc", "dddddddddd", "eeeeeeeeee", " ", " ", " ", ]); } #[test] fn append_lines_truncates_beyond_u16_max() -> Result<()> { let mut backend = TestBackend::new(10, 5); // Fill the scrollback with 65535 + 10 lines. let row_count = u16::MAX as usize + 10; for row in 0..=row_count { if row > 4 { backend.set_cursor_position(Position { x: 0, y: 4 })?; backend.append_lines(1)?; } let cells = format!("{row:>10}").chars().map(Cell::from).collect_vec(); let content = cells .iter() .enumerate() .map(|(column, cell)| (column as u16, 4.min(row) as u16, cell)); backend.draw(content)?; } // check that the buffer contains the last 5 lines appended backend.assert_buffer_lines([ " 65541", " 65542", " 65543", " 65544", " 65545", ]); // TODO: ideally this should be something like: // let lines = (6..=65545).map(|row| format!("{row:>10}")); // backend.assert_scrollback_lines(lines); // but there's some truncation happening in Buffer::with_lines that needs to be fixed assert_eq!( Buffer { area: Rect::new(0, 0, 10, 5), content: backend.scrollback.content[0..10 * 5].to_vec(), }, Buffer::with_lines([ " 6", " 7", " 8", " 9", " 10", ]), "first 5 lines of scrollback should have been truncated" ); assert_eq!( Buffer { area: Rect::new(0, 0, 10, 5), content: backend.scrollback.content[10 * 65530..10 * 65535].to_vec(), }, Buffer::with_lines([ " 65536", " 65537", " 65538", " 65539", " 65540", ]), "last 5 lines of scrollback should have been appended" ); // These checks come after the content checks as otherwise we won't see the failing content // when these checks fail. // Make sure the scrollback is the right size. assert_eq!(backend.scrollback.area.width, 10); assert_eq!(backend.scrollback.area.height, 65535); assert_eq!(backend.scrollback.content.len(), 10 * 65535); Ok(()) } #[test] fn size() { let backend = TestBackend::new(10, 2); assert_eq!(backend.size().unwrap(), Size::new(10, 2)); } #[test] fn flush() { let mut backend = TestBackend::new(10, 2); backend.flush().unwrap(); } #[cfg(feature = "scrolling-regions")] mod scrolling_regions { use rstest::rstest; use super::*; const A: &str = "aaaa"; const B: &str = "bbbb"; const C: &str = "cccc"; const D: &str = "dddd"; const E: &str = "eeee"; const S: &str = " "; #[rstest] #[case([A, B, C, D, E], 0..5, 0, [], [A, B, C, D, E])] #[case([A, B, C, D, E], 0..5, 2, [A, B], [C, D, E, S, S])] #[case([A, B, C, D, E], 0..5, 5, [A, B, C, D, E], [S, S, S, S, S])] #[case([A, B, C, D, E], 0..5, 7, [A, B, C, D, E, S, S], [S, S, S, S, S])] #[case([A, B, C, D, E], 0..3, 0, [], [A, B, C, D, E])] #[case([A, B, C, D, E], 0..3, 2, [A, B], [C, S, S, D, E])] #[case([A, B, C, D, E], 0..3, 3, [A, B, C], [S, S, S, D, E])] #[case([A, B, C, D, E], 0..3, 4, [A, B, C, S], [S, S, S, D, E])] #[case([A, B, C, D, E], 1..4, 0, [], [A, B, C, D, E])] #[case([A, B, C, D, E], 1..4, 2, [], [A, D, S, S, E])] #[case([A, B, C, D, E], 1..4, 3, [], [A, S, S, S, E])] #[case([A, B, C, D, E], 1..4, 4, [], [A, S, S, S, E])] #[case([A, B, C, D, E], 0..0, 0, [], [A, B, C, D, E])] #[case([A, B, C, D, E], 0..0, 2, [S, S], [A, B, C, D, E])] #[case([A, B, C, D, E], 2..2, 0, [], [A, B, C, D, E])] #[case([A, B, C, D, E], 2..2, 2, [], [A, B, C, D, E])] fn scroll_region_up( #[case] initial_screen: [&'static str; L], #[case] range: core::ops::Range, #[case] scroll_by: u16, #[case] expected_scrollback: [&'static str; M], #[case] expected_buffer: [&'static str; N], ) { let mut backend = TestBackend::with_lines(initial_screen); backend.scroll_region_up(range, scroll_by).unwrap(); if expected_scrollback.is_empty() { backend.assert_scrollback_empty(); } else { backend.assert_scrollback_lines(expected_scrollback); } backend.assert_buffer_lines(expected_buffer); } #[rstest] #[case([A, B, C, D, E], 0..5, 0, [A, B, C, D, E])] #[case([A, B, C, D, E], 0..5, 2, [S, S, A, B, C])] #[case([A, B, C, D, E], 0..5, 5, [S, S, S, S, S])] #[case([A, B, C, D, E], 0..5, 7, [S, S, S, S, S])] #[case([A, B, C, D, E], 0..3, 0, [A, B, C, D, E])] #[case([A, B, C, D, E], 0..3, 2, [S, S, A, D, E])] #[case([A, B, C, D, E], 0..3, 3, [S, S, S, D, E])] #[case([A, B, C, D, E], 0..3, 4, [S, S, S, D, E])] #[case([A, B, C, D, E], 1..4, 0, [A, B, C, D, E])] #[case([A, B, C, D, E], 1..4, 2, [A, S, S, B, E])] #[case([A, B, C, D, E], 1..4, 3, [A, S, S, S, E])] #[case([A, B, C, D, E], 1..4, 4, [A, S, S, S, E])] #[case([A, B, C, D, E], 0..0, 0, [A, B, C, D, E])] #[case([A, B, C, D, E], 0..0, 2, [A, B, C, D, E])] #[case([A, B, C, D, E], 2..2, 0, [A, B, C, D, E])] #[case([A, B, C, D, E], 2..2, 2, [A, B, C, D, E])] fn scroll_region_down( #[case] initial_screen: [&'static str; M], #[case] range: core::ops::Range, #[case] scroll_by: u16, #[case] expected_buffer: [&'static str; N], ) { let mut backend = TestBackend::with_lines(initial_screen); backend.scroll_region_down(range, scroll_by).unwrap(); backend.assert_scrollback_empty(); backend.assert_buffer_lines(expected_buffer); } } } ratatui-core-0.1.0/src/backend.rs000064400000000000000000000415551046102023000147570ustar 00000000000000#![warn(missing_docs)] //! This module provides the backend implementations for different terminal libraries. //! //! It defines the [`Backend`] trait which is used to abstract over the specific terminal library //! being used. //! //! Supported terminal backends: //! - [Crossterm]: enable the `crossterm` feature (enabled by default) and use [`CrosstermBackend`] //! - [Termion]: enable the `termion` feature and use [`TermionBackend`] //! - [Termwiz]: enable the `termwiz` feature and use [`TermwizBackend`] //! //! Additionally, a [`TestBackend`] is provided for testing purposes. //! //! See the [Backend Comparison] section of the [Ratatui Website] for more details on the different //! backends. //! //! Each backend supports a number of features, such as [raw mode](#raw-mode), [alternate //! screen](#alternate-screen), and [mouse capture](#mouse-capture). These features are generally //! not enabled by default, and must be enabled by the application before they can be used. See the //! documentation for each backend for more details. //! //! Note: most applications should use the [`Terminal`] struct instead of directly calling methods //! on the backend. //! //! # Example //! //! ```rust,ignore //! use std::io::stdout; //! //! use ratatui::{backend::CrosstermBackend, Terminal}; //! //! let backend = CrosstermBackend::new(stdout()); //! let mut terminal = Terminal::new(backend)?; //! terminal.clear()?; //! terminal.draw(|frame| { //! // -- snip -- //! })?; //! # std::io::Result::Ok(()) //! ``` //! //! See the the [Examples] directory for more examples. //! //! # Raw Mode //! //! Raw mode is a mode where the terminal does not perform any processing or handling of the input //! and output. This means that features such as echoing input characters, line buffering, and //! special character processing (e.g., CTRL-C for SIGINT) are disabled. This is useful for //! applications that want to have complete control over the terminal input and output, processing //! each keystroke themselves. //! //! For example, in raw mode, the terminal will not perform line buffering on the input, so the //! application will receive each key press as it is typed, instead of waiting for the user to //! press enter. This makes it suitable for real-time applications like text editors, //! terminal-based games, and more. //! //! Each backend handles raw mode differently, so the behavior may vary depending on the backend //! being used. Be sure to consult the backend's specific documentation for exact details on how it //! implements raw mode. //! //! # Alternate Screen //! //! The alternate screen is a separate buffer that some terminals provide, distinct from the main //! screen. When activated, the terminal will display the alternate screen, hiding the current //! content of the main screen. Applications can write to this screen as if it were the regular //! terminal display, but when the application exits, the terminal will switch back to the main //! screen, and the contents of the alternate screen will be cleared. This is useful for //! applications like text editors or terminal games that want to use the full terminal window //! without disrupting the command line or other terminal content. //! //! This creates a seamless transition between the application and the regular terminal session, as //! the content displayed before launching the application will reappear after the application //! exits. //! //! Note that not all terminal emulators support the alternate screen, and even those that do may //! handle it differently. As a result, the behavior may vary depending on the backend being used. //! Always consult the specific backend's documentation to understand how it implements the //! alternate screen. //! //! # Mouse Capture //! //! Mouse capture is a mode where the terminal captures mouse events such as clicks, scrolls, and //! movement, and sends them to the application as special sequences or events. This enables the //! application to handle and respond to mouse actions, providing a more interactive and graphical //! user experience within the terminal. It's particularly useful for applications like //! terminal-based games, text editors, or other programs that require more direct interaction from //! the user. //! //! Each backend handles mouse capture differently, with variations in the types of events that can //! be captured and how they are represented. As such, the behavior may vary depending on the //! backend being used, and developers should consult the specific backend's documentation to //! understand how it implements mouse capture. //! //! [`CrosstermBackend`]: https://docs.rs/ratatui/latest/ratatui/backend/struct.CrosstermBackend.html //! [`TermionBackend`]: https://docs.rs/ratatui/latest/ratatui/backend/struct.TermionBackend.html //! [`TermwizBackend`]: https://docs.rs/ratatui/latest/ratatui/backend/struct.TermwizBackend.html //! [`Terminal`]: https://docs.rs/ratatui/latest/ratatui/struct.Terminal.html //! [Crossterm]: https://crates.io/crates/crossterm //! [Termion]: https://crates.io/crates/termion //! [Termwiz]: https://crates.io/crates/termwiz //! [Examples]: https://github.com/ratatui/ratatui/tree/main/ratatui/examples/README.md //! [Backend Comparison]: https://ratatui.rs/concepts/backends/comparison/ //! [Ratatui Website]: https://ratatui.rs use strum::{Display, EnumString}; use crate::buffer::Cell; use crate::layout::{Position, Size}; mod test; pub use self::test::TestBackend; /// Enum representing the different types of clearing operations that can be performed /// on the terminal screen. #[derive(Debug, Display, EnumString, Clone, Copy, Eq, PartialEq, Hash)] pub enum ClearType { /// Clear the entire screen. All, /// Clear everything after the cursor. AfterCursor, /// Clear everything before the cursor. BeforeCursor, /// Clear the current line. CurrentLine, /// Clear everything from the cursor until the next newline. UntilNewLine, } /// The window size in characters (columns / rows) as well as pixels. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] pub struct WindowSize { /// Size of the window in characters (columns / rows). pub columns_rows: Size, /// Size of the window in pixels. /// /// The `pixels` fields may not be implemented by all terminals and return `0,0`. See /// under section "Get and set window /// size" / TIOCGWINSZ where the fields are commented as "unused". pub pixels: Size, } /// The `Backend` trait provides an abstraction over different terminal libraries. It defines the /// methods required to draw content, manipulate the cursor, and clear the terminal screen. /// /// Most applications should not need to interact with the `Backend` trait directly as the /// [`Terminal`] struct provides a higher level interface for interacting with the terminal. /// /// [`Terminal`]: https://docs.rs/ratatui/latest/ratatui/struct.Terminal.html pub trait Backend { /// Error type associated with this Backend. type Error: core::error::Error; /// Draw the given content to the terminal screen. /// /// The content is provided as an iterator over `(u16, u16, &Cell)` tuples, where the first two /// elements represent the x and y coordinates, and the third element is a reference to the /// [`Cell`] to be drawn. fn draw<'a, I>(&mut self, content: I) -> Result<(), Self::Error> where I: Iterator; /// Insert `n` line breaks to the terminal screen. /// /// This method is optional and may not be implemented by all backends. fn append_lines(&mut self, _n: u16) -> Result<(), Self::Error> { Ok(()) } /// Hide the cursor on the terminal screen. /// /// /// See also [`show_cursor`]. /// # Example /// /// ```rust,ignore /// # use ratatui::backend::{TestBackend}; /// # let mut backend = TestBackend::new(80, 25); /// use ratatui::backend::Backend; /// /// backend.hide_cursor()?; /// // do something with hidden cursor /// backend.show_cursor()?; /// # std::io::Result::Ok(()) /// ``` /// /// [`show_cursor`]: Self::show_cursor fn hide_cursor(&mut self) -> Result<(), Self::Error>; /// Show the cursor on the terminal screen. /// /// See [`hide_cursor`] for an example. /// /// [`hide_cursor`]: Self::hide_cursor fn show_cursor(&mut self) -> Result<(), Self::Error>; /// Get the current cursor position on the terminal screen. /// /// The returned tuple contains the x and y coordinates of the cursor. /// The origin (0, 0) is at the top left corner of the screen. /// /// See [`set_cursor_position`] for an example. /// /// [`set_cursor_position`]: Self::set_cursor_position fn get_cursor_position(&mut self) -> Result; /// Set the cursor position on the terminal screen to the given x and y coordinates. /// /// The origin (0, 0) is at the top left corner of the screen. /// /// # Example /// /// ```rust,ignore /// # use ratatui::backend::{TestBackend}; /// # let mut backend = TestBackend::new(80, 25); /// use ratatui::{backend::Backend, layout::Position}; /// /// backend.set_cursor_position(Position { x: 10, y: 20 })?; /// assert_eq!(backend.get_cursor_position()?, Position { x: 10, y: 20 }); /// # std::io::Result::Ok(()) /// ``` fn set_cursor_position>(&mut self, position: P) -> Result<(), Self::Error>; /// Get the current cursor position on the terminal screen. /// /// The returned tuple contains the x and y coordinates of the cursor. The origin /// (0, 0) is at the top left corner of the screen. #[deprecated = "use `get_cursor_position()` instead which returns `Result`"] fn get_cursor(&mut self) -> Result<(u16, u16), Self::Error> { let Position { x, y } = self.get_cursor_position()?; Ok((x, y)) } /// Set the cursor position on the terminal screen to the given x and y coordinates. /// /// The origin (0, 0) is at the top left corner of the screen. #[deprecated = "use `set_cursor_position((x, y))` instead which takes `impl Into`"] fn set_cursor(&mut self, x: u16, y: u16) -> Result<(), Self::Error> { self.set_cursor_position(Position { x, y }) } /// Clears the whole terminal screen /// /// # Example /// /// ```rust,ignore /// # use ratatui::backend::{TestBackend}; /// # let mut backend = TestBackend::new(80, 25); /// use ratatui::backend::Backend; /// /// backend.clear()?; /// # std::io::Result::Ok(()) /// ``` fn clear(&mut self) -> Result<(), Self::Error>; /// Clears a specific region of the terminal specified by the [`ClearType`] parameter /// /// This method is optional and may not be implemented by all backends. The default /// implementation calls [`clear`] if the `clear_type` is [`ClearType::All`] and returns an /// error otherwise. /// /// # Example /// /// ```rust,ignore /// # use ratatui::{backend::{TestBackend}}; /// # let mut backend = TestBackend::new(80, 25); /// use ratatui::backend::{Backend, ClearType}; /// /// backend.clear_region(ClearType::All)?; /// # std::io::Result::Ok(()) /// ``` /// /// # Errors /// /// This method will return an error if the terminal screen could not be cleared. It will also /// return an error if the `clear_type` is not supported by the backend. /// /// [`clear`]: Self::clear fn clear_region(&mut self, clear_type: ClearType) -> Result<(), Self::Error>; /// Get the size of the terminal screen in columns/rows as a [`Size`]. /// /// The returned [`Size`] contains the width and height of the terminal screen. /// /// # Example /// /// ```rust,ignore /// # use ratatui::{backend::{TestBackend}}; /// # let backend = TestBackend::new(80, 25); /// use ratatui::{backend::Backend, layout::Size}; /// /// assert_eq!(backend.size()?, Size::new(80, 25)); /// # Result::Ok(()) /// ``` fn size(&self) -> Result; /// Get the size of the terminal screen in columns/rows and pixels as a [`WindowSize`]. /// /// The reason for this not returning only the pixel size, given the redundancy with the /// `size()` method, is that the underlying backends most likely get both values with one /// syscall, and the user is also most likely to need columns and rows along with pixel size. fn window_size(&mut self) -> Result; /// Flush any buffered content to the terminal screen. fn flush(&mut self) -> Result<(), Self::Error>; /// Scroll a region of the screen upwards, where a region is specified by a (half-open) range /// of rows. /// /// Each row in the region is replaced by the row `line_count` rows below it, except the bottom /// `line_count` rows, which are replaced by empty rows. If `line_count` is equal to or larger /// than the number of rows in the region, then all rows are replaced with empty rows. /// /// If the region includes row 0, then `line_count` rows are copied into the bottom of the /// scrollback buffer. These rows are first taken from the old contents of the region, starting /// from the top. If there aren't sufficient rows in the region, then the remainder are empty /// rows. /// /// The position of the cursor afterwards is undefined. /// /// The behavior is designed to match what ANSI terminals do when scrolling regions are /// established. With ANSI terminals, a scrolling region can be established with the "^[[X;Yr" /// sequence, where X and Y define the lines of the region. The scrolling region can be reset /// to be the whole screen with the "^[[r" sequence. /// /// When a scrolling region is established in an ANSI terminal, various operations' behaviors /// are changed in such a way that the scrolling region acts like a "virtual screen". In /// particular, the scrolling sequence "^[[NS", which scrolls lines up by a count of N. /// /// On an ANSI terminal, this method will probably translate to something like: /// "^[[X;Yr^[[NS^[[r". That is, set the scrolling region, scroll up, then reset the scrolling /// region. /// /// For examples of how this function is expected to work, refer to the tests for /// [`TestBackend::scroll_region_up`]. #[cfg(feature = "scrolling-regions")] fn scroll_region_up( &mut self, region: core::ops::Range, line_count: u16, ) -> Result<(), Self::Error>; /// Scroll a region of the screen downwards, where a region is specified by a (half-open) range /// of rows. /// /// Each row in the region is replaced by the row `line_count` rows above it, except the top /// `line_count` rows, which are replaced by empty rows. If `line_count` is equal to or larger /// than the number of rows in the region, then all rows are replaced with empty rows. /// /// The position of the cursor afterwards is undefined. /// /// See the documentation for [`Self::scroll_region_down`] for more information about how this /// is expected to be implemented for ANSI terminals. All of that applies, except the ANSI /// sequence to scroll down is "^[[NT". /// /// This function is asymmetrical with regards to the scrollback buffer. The reason is that /// this how terminals seem to implement things. /// /// For examples of how this function is expected to work, refer to the tests for /// [`TestBackend::scroll_region_down`]. #[cfg(feature = "scrolling-regions")] fn scroll_region_down( &mut self, region: core::ops::Range, line_count: u16, ) -> Result<(), Self::Error>; } #[cfg(test)] mod tests { use alloc::string::ToString; use strum::ParseError; use super::*; #[test] fn clear_type_tostring() { assert_eq!(ClearType::All.to_string(), "All"); assert_eq!(ClearType::AfterCursor.to_string(), "AfterCursor"); assert_eq!(ClearType::BeforeCursor.to_string(), "BeforeCursor"); assert_eq!(ClearType::CurrentLine.to_string(), "CurrentLine"); assert_eq!(ClearType::UntilNewLine.to_string(), "UntilNewLine"); } #[test] fn clear_type_from_str() { assert_eq!("All".parse::(), Ok(ClearType::All)); assert_eq!( "AfterCursor".parse::(), Ok(ClearType::AfterCursor) ); assert_eq!( "BeforeCursor".parse::(), Ok(ClearType::BeforeCursor) ); assert_eq!( "CurrentLine".parse::(), Ok(ClearType::CurrentLine) ); assert_eq!( "UntilNewLine".parse::(), Ok(ClearType::UntilNewLine) ); assert_eq!("".parse::(), Err(ParseError::VariantNotFound)); } } ratatui-core-0.1.0/src/buffer/assert.rs000064400000000000000000000052771046102023000161430ustar 00000000000000/// Assert that two buffers are equal by comparing their areas and content. /// /// # Panics /// When the buffers differ this method panics and displays the differences similar to /// `assert_eq!()`. #[deprecated = "use `assert_eq!(&actual, &expected)`"] #[macro_export] macro_rules! assert_buffer_eq { ($actual_expr:expr, $expected_expr:expr) => { match (&$actual_expr, &$expected_expr) { (actual, expected) => { assert!( actual.area == expected.area, "buffer areas not equal\nexpected: {expected:?}\nactual: {actual:?}", ); let nice_diff = expected .diff(actual) .into_iter() .enumerate() .map(|(i, (x, y, cell))| { let expected_cell = &expected[(x, y)]; ::alloc::format!("{i}: at ({x}, {y})\n expected: {expected_cell:?}\n actual: {cell:?}") }) .collect::<::alloc::vec::Vec<::alloc::string::String>>() .join("\n"); assert!( nice_diff.is_empty(), "buffer contents not equal\nexpected: {expected:?}\nactual: {actual:?}\ndiff:\n{nice_diff}", ); // shouldn't get here, but this guards against future behavior // that changes equality but not area or content assert_eq!( actual, expected, "buffers are not equal in an unexpected way. Please open an issue about this." ); } } }; } #[expect(deprecated)] #[cfg(test)] mod tests { use crate::buffer::Buffer; use crate::layout::Rect; use crate::style::{Color, Style}; #[test] fn assert_buffer_eq_does_not_panic_on_equal_buffers() { let buffer = Buffer::empty(Rect::new(0, 0, 5, 1)); let other_buffer = Buffer::empty(Rect::new(0, 0, 5, 1)); assert_buffer_eq!(buffer, other_buffer); } #[should_panic = "buffer areas not equal"] #[test] fn assert_buffer_eq_panics_on_unequal_area() { let buffer = Buffer::empty(Rect::new(0, 0, 5, 1)); let other_buffer = Buffer::empty(Rect::new(0, 0, 6, 1)); assert_buffer_eq!(buffer, other_buffer); } #[should_panic = "buffer contents not equal"] #[test] fn assert_buffer_eq_panics_on_unequal_style() { let buffer = Buffer::empty(Rect::new(0, 0, 5, 1)); let mut other_buffer = Buffer::empty(Rect::new(0, 0, 5, 1)); other_buffer.set_string(0, 0, " ", Style::default().fg(Color::Red)); assert_buffer_eq!(buffer, other_buffer); } } ratatui-core-0.1.0/src/buffer/buffer.rs000064400000000000000000001364701046102023000161130ustar 00000000000000use alloc::vec; use alloc::vec::Vec; use core::ops::{Index, IndexMut}; use core::{cmp, fmt}; use unicode_segmentation::UnicodeSegmentation; use unicode_width::UnicodeWidthStr; use crate::buffer::Cell; use crate::layout::{Position, Rect}; use crate::style::Style; use crate::text::{Line, Span}; /// A buffer that maps to the desired content of the terminal after the draw call /// /// No widget in the library interacts directly with the terminal. Instead each of them is required /// to draw their state to an intermediate buffer. It is basically a grid where each cell contains /// a grapheme, a foreground color and a background color. This grid will then be used to output /// the appropriate escape sequences and characters to draw the UI as the user has defined it. /// /// # Examples: /// /// ``` /// use ratatui_core::buffer::{Buffer, Cell}; /// use ratatui_core::layout::{Position, Rect}; /// use ratatui_core::style::{Color, Style}; /// /// # fn foo() -> Option<()> { /// let mut buf = Buffer::empty(Rect { /// x: 0, /// y: 0, /// width: 10, /// height: 5, /// }); /// /// // indexing using Position /// buf[Position { x: 0, y: 0 }].set_symbol("A"); /// assert_eq!(buf[Position { x: 0, y: 0 }].symbol(), "A"); /// /// // indexing using (x, y) tuple (which is converted to Position) /// buf[(0, 1)].set_symbol("B"); /// assert_eq!(buf[(0, 1)].symbol(), "x"); /// /// // getting an Option instead of panicking if the position is outside the buffer /// let cell = buf.cell_mut(Position { x: 0, y: 2 })?; /// cell.set_symbol("C"); /// /// let cell = buf.cell(Position { x: 0, y: 2 })?; /// assert_eq!(cell.symbol(), "C"); /// /// buf.set_string( /// 3, /// 0, /// "string", /// Style::default().fg(Color::Red).bg(Color::White), /// ); /// let cell = &buf[(5, 0)]; // cannot move out of buf, so we borrow it /// assert_eq!(cell.symbol(), "r"); /// assert_eq!(cell.fg, Color::Red); /// assert_eq!(cell.bg, Color::White); /// # Some(()) /// # } /// ``` #[derive(Default, Clone, Eq, PartialEq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Buffer { /// The area represented by this buffer pub area: Rect, /// The content of the buffer. The length of this Vec should always be equal to area.width * /// area.height pub content: Vec, } impl Buffer { /// Returns a Buffer with all cells set to the default one #[must_use] pub fn empty(area: Rect) -> Self { Self::filled(area, Cell::EMPTY) } /// Returns a Buffer with all cells initialized with the attributes of the given Cell #[must_use] pub fn filled(area: Rect, cell: Cell) -> Self { let size = area.area() as usize; let content = vec![cell; size]; Self { area, content } } /// Returns a Buffer containing the given lines #[must_use] pub fn with_lines<'a, Iter>(lines: Iter) -> Self where Iter: IntoIterator, Iter::Item: Into>, { let lines = lines.into_iter().map(Into::into).collect::>(); let height = lines.len() as u16; let width = lines.iter().map(Line::width).max().unwrap_or_default() as u16; let mut buffer = Self::empty(Rect::new(0, 0, width, height)); for (y, line) in lines.iter().enumerate() { buffer.set_line(0, y as u16, line, width); } buffer } /// Returns the content of the buffer as a slice pub fn content(&self) -> &[Cell] { &self.content } /// Returns the area covered by this buffer pub const fn area(&self) -> &Rect { &self.area } /// Returns a reference to the [`Cell`] at the given coordinates /// /// Callers should use [`Buffer[]`](Self::index) or [`Buffer::cell`] instead of this method. /// /// Note: idiomatically methods named `get` usually return `Option<&T>`, but this method panics /// instead. This is kept for backwards compatibility. See [`cell`](Self::cell) for a safe /// alternative. /// /// # Panics /// /// Panics if the index is out of bounds. #[track_caller] #[deprecated = "use `Buffer[(x, y)]` instead. To avoid panicking, use `Buffer::cell((x, y))`. Both methods take `impl Into`."] #[must_use] pub fn get(&self, x: u16, y: u16) -> &Cell { let i = self.index_of(x, y); &self.content[i] } /// Returns a mutable reference to the [`Cell`] at the given coordinates. /// /// Callers should use [`Buffer[]`](Self::index_mut) or [`Buffer::cell_mut`] instead of this /// method. /// /// Note: idiomatically methods named `get_mut` usually return `Option<&mut T>`, but this method /// panics instead. This is kept for backwards compatibility. See [`cell_mut`](Self::cell_mut) /// for a safe alternative. /// /// # Panics /// /// Panics if the position is outside the `Buffer`'s area. #[track_caller] #[deprecated = "use `Buffer[(x, y)]` instead. To avoid panicking, use `Buffer::cell_mut((x, y))`. Both methods take `impl Into`."] #[must_use] pub fn get_mut(&mut self, x: u16, y: u16) -> &mut Cell { let i = self.index_of(x, y); &mut self.content[i] } /// Returns a reference to the [`Cell`] at the given position or [`None`] if the position is /// outside the `Buffer`'s area. /// /// This method accepts any value that can be converted to [`Position`] (e.g. `(x, y)` or /// `Position::new(x, y)`). /// /// For a method that panics when the position is outside the buffer instead of returning /// `None`, use [`Buffer[]`](Self::index). /// /// # Examples /// /// ```rust /// use ratatui_core::buffer::{Buffer, Cell}; /// use ratatui_core::layout::{Position, Rect}; /// /// let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 10)); /// /// assert_eq!(buffer.cell(Position::new(0, 0)), Some(&Cell::default())); /// assert_eq!(buffer.cell(Position::new(10, 10)), None); /// assert_eq!(buffer.cell((0, 0)), Some(&Cell::default())); /// assert_eq!(buffer.cell((10, 10)), None); /// ``` #[must_use] pub fn cell>(&self, position: P) -> Option<&Cell> { let position = position.into(); let index = self.index_of_opt(position)?; self.content.get(index) } /// Returns a mutable reference to the [`Cell`] at the given position or [`None`] if the /// position is outside the `Buffer`'s area. /// /// This method accepts any value that can be converted to [`Position`] (e.g. `(x, y)` or /// `Position::new(x, y)`). /// /// For a method that panics when the position is outside the buffer instead of returning /// `None`, use [`Buffer[]`](Self::index_mut). /// /// # Examples /// /// ```rust /// use ratatui_core::buffer::{Buffer, Cell}; /// use ratatui_core::layout::{Position, Rect}; /// use ratatui_core::style::{Color, Style}; /// let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 10)); /// /// if let Some(cell) = buffer.cell_mut(Position::new(0, 0)) { /// cell.set_symbol("A"); /// } /// if let Some(cell) = buffer.cell_mut((0, 0)) { /// cell.set_style(Style::default().fg(Color::Red)); /// } /// ``` #[must_use] pub fn cell_mut>(&mut self, position: P) -> Option<&mut Cell> { let position = position.into(); let index = self.index_of_opt(position)?; self.content.get_mut(index) } /// Returns the index in the `Vec` for the given global (x, y) coordinates. /// /// Global coordinates are offset by the Buffer's area offset (`x`/`y`). /// /// Usage discouraged, as it exposes `self.content` as a linearly indexable array, which limits /// potential future abstractions. See . /// /// # Examples /// /// ``` /// use ratatui_core::buffer::Buffer; /// use ratatui_core::layout::Rect; /// /// let buffer = Buffer::empty(Rect::new(200, 100, 10, 10)); /// // Global coordinates to the top corner of this buffer's area /// assert_eq!(buffer.index_of(200, 100), 0); /// ``` /// /// # Panics /// /// Panics when given an coordinate that is outside of this Buffer's area. /// /// ```should_panic /// use ratatui_core::buffer::Buffer; /// use ratatui_core::layout::Rect; /// /// let buffer = Buffer::empty(Rect::new(200, 100, 10, 10)); /// // Top coordinate is outside of the buffer in global coordinate space, as the Buffer's area /// // starts at (200, 100). /// buffer.index_of(0, 0); // Panics /// ``` #[track_caller] #[must_use] pub fn index_of(&self, x: u16, y: u16) -> usize { self.index_of_opt(Position { x, y }).unwrap_or_else(|| { panic!( "index outside of buffer: the area is {area:?} but index is ({x}, {y})", area = self.area, ) }) } /// Returns the index in the `Vec` for the given global (x, y) coordinates. /// /// Returns `None` if the given coordinates are outside of the Buffer's area. /// /// Note that this is private because of #[must_use] const fn index_of_opt(&self, position: Position) -> Option { let area = self.area; if !area.contains(position) { return None; } // remove offset let y = (position.y - self.area.y) as usize; let x = (position.x - self.area.x) as usize; let width = self.area.width as usize; Some(y * width + x) } /// Returns the (global) coordinates of a cell given its index. /// /// Global coordinates are offset by the Buffer's area offset (`x`/`y`). /// /// Usage discouraged, as it exposes `self.content` as a linearly indexable array, which limits /// potential future abstractions. See . /// /// # Examples /// /// ``` /// use ratatui_core::buffer::Buffer; /// use ratatui_core::layout::Rect; /// /// let rect = Rect::new(200, 100, 10, 10); /// let buffer = Buffer::empty(rect); /// assert_eq!(buffer.pos_of(0), (200, 100)); /// assert_eq!(buffer.pos_of(14), (204, 101)); /// ``` /// /// # Panics /// /// Panics when given an index that is outside the Buffer's content. /// /// ```should_panic /// use ratatui_core::buffer::Buffer; /// use ratatui_core::layout::Rect; /// /// let rect = Rect::new(0, 0, 10, 10); // 100 cells in total /// let buffer = Buffer::empty(rect); /// // Index 100 is the 101th cell, which lies outside of the area of this Buffer. /// buffer.pos_of(100); // Panics /// ``` #[must_use] pub fn pos_of(&self, index: usize) -> (u16, u16) { debug_assert!( index < self.content.len(), "Trying to get the coords of a cell outside the buffer: i={index} len={}", self.content.len() ); let x = index % self.area.width as usize + self.area.x as usize; let y = index / self.area.width as usize + self.area.y as usize; ( u16::try_from(x).expect("x overflow. This should never happen as area.width is u16"), u16::try_from(y).expect("y overflow. This should never happen as area.height is u16"), ) } /// Print a string, starting at the position (x, y) pub fn set_string(&mut self, x: u16, y: u16, string: T, style: S) where T: AsRef, S: Into //!
//!
//!
//!
//!
C50
//!
C100
//!
C200
//!
C300
//!
C400
//!
C500
//!
C600
//!
C700
//!
C800
//!
C900
//!
A100
//!
A200
//!
A400
//!
A700
//!
//!
//!
//! //! [`RED`]
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//! //! [`PINK`]
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//! //! [`PURPLE`]
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//! //! [`DEEP_PURPLE`]
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//! //! [`INDIGO`]
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//! //! [`BLUE`]
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//! //! [`LIGHT_BLUE`]
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//! //! [`CYAN`]
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//! //! [`TEAL`]
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//! //! [`GREEN`]
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//! //! [`LIGHT_GREEN`]
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//! //! [`LIME`]
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//! //! [`YELLOW`]
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//! //! [`AMBER`]
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//! //! [`ORANGE`]
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//! //! [`DEEP_ORANGE`]
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//! //! [`BROWN`]
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//! //! [`GRAY`]
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//! //! [`BLUE_GRAY`]
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//!
//! //! [`BLACK`]
//!
//!
//!
//!
//! //! [`WHITE`]
//!
//!
//!
//!
//! //! # Example //! //! ```rust //! use ratatui_core::style::Color; //! use ratatui_core::style::palette::material::{BLUE, RED}; //! //! assert_eq!(RED.c500, Color::Rgb(244, 67, 54)); //! assert_eq!(BLUE.c500, Color::Rgb(33, 150, 243)); //! ``` //! //! [`matdesign-color` crate]: https://crates.io/crates/matdesign-color use crate::style::Color; /// A palette of colors for use in Material design with accent colors /// /// This is a collection of colors that are used in Material design. They consist of a set of /// colors from 50 to 900, and a set of accent colors from 100 to 700. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct AccentedPalette { pub c50: Color, pub c100: Color, pub c200: Color, pub c300: Color, pub c400: Color, pub c500: Color, pub c600: Color, pub c700: Color, pub c800: Color, pub c900: Color, pub a100: Color, pub a200: Color, pub a400: Color, pub a700: Color, } /// A palette of colors for use in Material design without accent colors /// /// This is a collection of colors that are used in Material design. They consist of a set of /// colors from 50 to 900. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct NonAccentedPalette { pub c50: Color, pub c100: Color, pub c200: Color, pub c300: Color, pub c400: Color, pub c500: Color, pub c600: Color, pub c700: Color, pub c800: Color, pub c900: Color, } impl AccentedPalette { /// Create a new `AccentedPalette` from the given variants /// /// The variants should be in the format [0x00RRGGBB, ...] pub const fn from_variants(variants: [u32; 14]) -> Self { Self { c50: Color::from_u32(variants[0]), c100: Color::from_u32(variants[1]), c200: Color::from_u32(variants[2]), c300: Color::from_u32(variants[3]), c400: Color::from_u32(variants[4]), c500: Color::from_u32(variants[5]), c600: Color::from_u32(variants[6]), c700: Color::from_u32(variants[7]), c800: Color::from_u32(variants[8]), c900: Color::from_u32(variants[9]), a100: Color::from_u32(variants[10]), a200: Color::from_u32(variants[11]), a400: Color::from_u32(variants[12]), a700: Color::from_u32(variants[13]), } } } impl NonAccentedPalette { /// Create a new `NonAccented` from the given variants /// /// The variants should be in the format [0x00RRGGBB, ...] pub const fn from_variants(variants: [u32; 10]) -> Self { Self { c50: Color::from_u32(variants[0]), c100: Color::from_u32(variants[1]), c200: Color::from_u32(variants[2]), c300: Color::from_u32(variants[3]), c400: Color::from_u32(variants[4]), c500: Color::from_u32(variants[5]), c600: Color::from_u32(variants[6]), c700: Color::from_u32(variants[7]), c800: Color::from_u32(variants[8]), c900: Color::from_u32(variants[9]), } } } // Accented palettes pub const RED: AccentedPalette = AccentedPalette::from_variants(variants::RED); pub const PINK: AccentedPalette = AccentedPalette::from_variants(variants::PINK); pub const PURPLE: AccentedPalette = AccentedPalette::from_variants(variants::PURPLE); pub const DEEP_PURPLE: AccentedPalette = AccentedPalette::from_variants(variants::DEEP_PURPLE); pub const INDIGO: AccentedPalette = AccentedPalette::from_variants(variants::INDIGO); pub const BLUE: AccentedPalette = AccentedPalette::from_variants(variants::BLUE); pub const LIGHT_BLUE: AccentedPalette = AccentedPalette::from_variants(variants::LIGHT_BLUE); pub const CYAN: AccentedPalette = AccentedPalette::from_variants(variants::CYAN); pub const TEAL: AccentedPalette = AccentedPalette::from_variants(variants::TEAL); pub const GREEN: AccentedPalette = AccentedPalette::from_variants(variants::GREEN); pub const LIGHT_GREEN: AccentedPalette = AccentedPalette::from_variants(variants::LIGHT_GREEN); pub const LIME: AccentedPalette = AccentedPalette::from_variants(variants::LIME); pub const YELLOW: AccentedPalette = AccentedPalette::from_variants(variants::YELLOW); pub const AMBER: AccentedPalette = AccentedPalette::from_variants(variants::AMBER); pub const ORANGE: AccentedPalette = AccentedPalette::from_variants(variants::ORANGE); pub const DEEP_ORANGE: AccentedPalette = AccentedPalette::from_variants(variants::DEEP_ORANGE); // Unaccented palettes pub const BROWN: NonAccentedPalette = NonAccentedPalette::from_variants(variants::BROWN); pub const GRAY: NonAccentedPalette = NonAccentedPalette::from_variants(variants::GRAY); pub const BLUE_GRAY: NonAccentedPalette = NonAccentedPalette::from_variants(variants::BLUE_GRAY); // Black and white included for completeness pub const BLACK: Color = Color::from_u32(0x000000); pub const WHITE: Color = Color::from_u32(0xFFFFFF); mod variants { pub const RED: [u32; 14] = [ 0xFFEBEE, 0xFFCDD2, 0xEF9A9A, 0xE57373, 0xEF5350, 0xF44336, 0xE53935, 0xD32F2F, 0xC62828, 0xB71C1C, 0xFF8A80, 0xFF5252, 0xFF1744, 0xD50000, ]; pub const PINK: [u32; 14] = [ 0xFCE4EC, 0xF8BBD0, 0xF48FB1, 0xF06292, 0xEC407A, 0xE91E63, 0xD81B60, 0xC2185B, 0xAD1457, 0x880E4F, 0xFF80AB, 0xFF4081, 0xF50057, 0xC51162, ]; pub const PURPLE: [u32; 14] = [ 0xF3E5F5, 0xE1BEE7, 0xCE93D8, 0xBA68C8, 0xAB47BC, 0x9C27B0, 0x8E24AA, 0x7B1FA2, 0x6A1B9A, 0x4A148C, 0xEA80FC, 0xE040FB, 0xD500F9, 0xAA00FF, ]; pub const DEEP_PURPLE: [u32; 14] = [ 0xEDE7F6, 0xD1C4E9, 0xB39DDB, 0x9575CD, 0x7E57C2, 0x673AB7, 0x5E35B1, 0x512DA8, 0x4527A0, 0x311B92, 0xB388FF, 0x7C4DFF, 0x651FFF, 0x6200EA, ]; pub const INDIGO: [u32; 14] = [ 0xE8EAF6, 0xC5CAE9, 0x9FA8DA, 0x7986CB, 0x5C6BC0, 0x3F51B5, 0x3949AB, 0x303F9F, 0x283593, 0x1A237E, 0x8C9EFF, 0x536DFE, 0x3D5AFE, 0x304FFE, ]; pub const BLUE: [u32; 14] = [ 0xE3F2FD, 0xBBDEFB, 0x90CAF9, 0x64B5F6, 0x42A5F5, 0x2196F3, 0x1E88E5, 0x1976D2, 0x1565C0, 0x0D47A1, 0x82B1FF, 0x448AFF, 0x2979FF, 0x2962FF, ]; pub const LIGHT_BLUE: [u32; 14] = [ 0xE1F5FE, 0xB3E5FC, 0x81D4FA, 0x4FC3F7, 0x29B6F6, 0x03A9F4, 0x039BE5, 0x0288D1, 0x0277BD, 0x01579B, 0x80D8FF, 0x40C4FF, 0x00B0FF, 0x0091EA, ]; pub const CYAN: [u32; 14] = [ 0xE0F7FA, 0xB2EBF2, 0x80DEEA, 0x4DD0E1, 0x26C6DA, 0x00BCD4, 0x00ACC1, 0x0097A7, 0x00838F, 0x006064, 0x84FFFF, 0x18FFFF, 0x00E5FF, 0x00B8D4, ]; pub const TEAL: [u32; 14] = [ 0xE0F2F1, 0xB2DFDB, 0x80CBC4, 0x4DB6AC, 0x26A69A, 0x009688, 0x00897B, 0x00796B, 0x00695C, 0x004D40, 0xA7FFEB, 0x64FFDA, 0x1DE9B6, 0x00BFA5, ]; pub const GREEN: [u32; 14] = [ 0xE8F5E9, 0xC8E6C9, 0xA5D6A7, 0x81C784, 0x66BB6A, 0x4CAF50, 0x43A047, 0x388E3C, 0x2E7D32, 0x1B5E20, 0xB9F6CA, 0x69F0AE, 0x00E676, 0x00C853, ]; pub const LIGHT_GREEN: [u32; 14] = [ 0xF1F8E9, 0xDCEDC8, 0xC5E1A5, 0xAED581, 0x9CCC65, 0x8BC34A, 0x7CB342, 0x689F38, 0x558B2F, 0x33691E, 0xCCFF90, 0xB2FF59, 0x76FF03, 0x64DD17, ]; pub const LIME: [u32; 14] = [ 0xF9FBE7, 0xF0F4C3, 0xE6EE9C, 0xDCE775, 0xD4E157, 0xCDDC39, 0xC0CA33, 0xAFB42B, 0x9E9D24, 0x827717, 0xF4FF81, 0xEEFF41, 0xC6FF00, 0xAEEA00, ]; pub const YELLOW: [u32; 14] = [ 0xFFFDE7, 0xFFF9C4, 0xFFF59D, 0xFFF176, 0xFFEE58, 0xFFEB3B, 0xFDD835, 0xFBC02D, 0xF9A825, 0xF57F17, 0xFFFF8D, 0xFFFF00, 0xFFEA00, 0xFFD600, ]; pub const AMBER: [u32; 14] = [ 0xFFF8E1, 0xFFECB3, 0xFFE082, 0xFFD54F, 0xFFCA28, 0xFFC107, 0xFFB300, 0xFFA000, 0xFF8F00, 0xFF6F00, 0xFFE57F, 0xFFD740, 0xFFC400, 0xFFAB00, ]; pub const ORANGE: [u32; 14] = [ 0xFFF3E0, 0xFFE0B2, 0xFFCC80, 0xFFB74D, 0xFFA726, 0xFF9800, 0xFB8C00, 0xF57C00, 0xEF6C00, 0xE65100, 0xFFD180, 0xFFAB40, 0xFF9100, 0xFF6D00, ]; pub const DEEP_ORANGE: [u32; 14] = [ 0xFBE9E7, 0xFFCCBC, 0xFFAB91, 0xFF8A65, 0xFF7043, 0xFF5722, 0xF4511E, 0xE64A19, 0xD84315, 0xBF360C, 0xFF9E80, 0xFF6E40, 0xFF3D00, 0xDD2C00, ]; pub const BROWN: [u32; 10] = [ 0xEFEBE9, 0xD7CCC8, 0xBCAAA4, 0xA1887F, 0x8D6E63, 0x795548, 0x6D4C41, 0x5D4037, 0x4E342E, 0x3E2723, ]; pub const GRAY: [u32; 10] = [ 0xFAFAFA, 0xF5F5F5, 0xEEEEEE, 0xE0E0E0, 0xBDBDBD, 0x9E9E9E, 0x757575, 0x616161, 0x424242, 0x212121, ]; pub const BLUE_GRAY: [u32; 10] = [ 0xECEFF1, 0xCFD8DC, 0xB0BEC5, 0x90A4AE, 0x78909C, 0x607D8B, 0x546E7A, 0x455A64, 0x37474F, 0x263238, ]; } ratatui-core-0.1.0/src/style/palette/tailwind.rs000064400000000000000000001152761046102023000200030ustar 00000000000000//! Represents the Tailwind CSS [default color palette][palette]. //! //! [palette]: https://tailwindcss.com/docs/customizing-colors#default-color-palette //! //! There are 22 palettes. Each palette has 11 colors, with variants from 50 to 950. Black and White //! are also included for completeness and to avoid being affected by any terminal theme that might //! be in use. //! //! //!
//!
//!
//!
//!
C50
C100
C200
C300
C400
//!
C500
C600
C700
C800
C900
//!
C950
//!
//!
//!
//! //! [`SLATE`]
//!
//!
//!
//!
//!
//!
//!
//!
//!
//! //! [`GRAY`]
//!
//!
//!
//!
//!
//!
//!
//!
//!
//! //! [`ZINC`]
//!
//!
//!
//!
//!
//!
//!
//!
//!
//! //! [`NEUTRAL`]
//!
//!
//!
//!
//!
//!
//!
//!
//!
//! //! [`STONE`]
//!
//!
//!
//!
//!
//!
//!
//!
//!
//! //! [`RED`]
//!
//!
//!
//!
//!
//!
//!
//!
//!
//! //! [`ORANGE`]
//!
//!
//!
//!
//!
//!
//!
//!
//!
//! //! [`AMBER`]
//!
//!
//!
//!
//!
//!
//!
//!
//!
//! //! [`YELLOW`]
//!
//!
//!
//!
//!
//!
//!
//!
//!
//! //! [`LIME`]
//!
//!
//!
//!
//!
//!
//!
//!
//!
//! //! [`GREEN`]
//!
//!
//!
//!
//!
//!
//!
//!
//!
//! //! [`EMERALD`]
//!
//!
//!
//!
//!
//!
//!
//!
//!
//! //! [`TEAL`]
//!
//!
//!
//!
//!
//!
//!
//!
//!
//! //! [`CYAN`]
//!
//!
//!
//!
//!
//!
//!
//!
//!
//! //! [`SKY`]
//!
//!
//!
//!
//!
//!
//!
//!
//!
//! //! [`BLUE`]
//!
//!
//!
//!
//!
//!
//!
//!
//!
//! //! [`INDIGO`]
//!
//!
//!
//!
//!
//!
//!
//!
//!
//! //! [`VIOLET`]
//!
//!
//!
//!
//!
//!
//!
//!
//!
//! //! [`PURPLE`]
//!
//!
//!
//!
//!
//!
//!
//!
//!
//! //! [`FUCHSIA`]
//!
//!
//!
//!
//!
//!
//!
//!
//!
//! //! [`PINK`]
//!
//!
//!
//!
//!
//!
//!
//!
//!
//! //! [`BLACK`]
//!
//!
//!
//!
//! //! [`WHITE`]
//!
//!
//!
//!
//! //! # Example //! //! ```rust //! use ratatui_core::style::Color; //! use ratatui_core::style::palette::tailwind::{BLUE, RED}; //! //! assert_eq!(RED.c500, Color::Rgb(239, 68, 68)); //! assert_eq!(BLUE.c500, Color::Rgb(59, 130, 246)); //! ``` use crate::style::Color; #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Palette { pub c50: Color, pub c100: Color, pub c200: Color, pub c300: Color, pub c400: Color, pub c500: Color, pub c600: Color, pub c700: Color, pub c800: Color, pub c900: Color, pub c950: Color, } #[rustfmt::skip] ///
pub const BLACK: Color = Color::from_u32(0x000000); #[rustfmt::skip] ///
pub const WHITE: Color = Color::from_u32(0xffffff); #[rustfmt::skip] ///
pub const SLATE: Palette = Palette { c50: Color::from_u32(0xf8fafc), c100: Color::from_u32(0xf1f5f9), c200: Color::from_u32(0xe2e8f0), c300: Color::from_u32(0xcbd5e1), c400: Color::from_u32(0x94a3b8), c500: Color::from_u32(0x64748b), c600: Color::from_u32(0x475569), c700: Color::from_u32(0x334155), c800: Color::from_u32(0x1e293b), c900: Color::from_u32(0x0f172a), c950: Color::from_u32(0x020617), }; #[rustfmt::skip] ///
pub const GRAY: Palette = Palette { c50: Color::from_u32(0xf9fafb), c100: Color::from_u32(0xf3f4f6), c200: Color::from_u32(0xe5e7eb), c300: Color::from_u32(0xd1d5db), c400: Color::from_u32(0x9ca3af), c500: Color::from_u32(0x6b7280), c600: Color::from_u32(0x4b5563), c700: Color::from_u32(0x374151), c800: Color::from_u32(0x1f2937), c900: Color::from_u32(0x111827), c950: Color::from_u32(0x030712), }; #[rustfmt::skip] ///
pub const ZINC: Palette = Palette { c50: Color::from_u32(0xfafafa), c100: Color::from_u32(0xf4f4f5), c200: Color::from_u32(0xe4e4e7), c300: Color::from_u32(0xd4d4d8), c400: Color::from_u32(0xa1a1aa), c500: Color::from_u32(0x71717a), c600: Color::from_u32(0x52525b), c700: Color::from_u32(0x3f3f46), c800: Color::from_u32(0x27272a), c900: Color::from_u32(0x18181b), c950: Color::from_u32(0x09090b), }; #[rustfmt::skip] ///
pub const NEUTRAL: Palette = Palette { c50: Color::from_u32(0xfafafa), c100: Color::from_u32(0xf5f5f5), c200: Color::from_u32(0xe5e5e5), c300: Color::from_u32(0xd4d4d4), c400: Color::from_u32(0xa3a3a3), c500: Color::from_u32(0x737373), c600: Color::from_u32(0x525252), c700: Color::from_u32(0x404040), c800: Color::from_u32(0x262626), c900: Color::from_u32(0x171717), c950: Color::from_u32(0x0a0a0a), }; #[rustfmt::skip] ///
pub const STONE: Palette = Palette { c50: Color::from_u32(0xfafaf9), c100: Color::from_u32(0xf5f5f4), c200: Color::from_u32(0xe7e5e4), c300: Color::from_u32(0xd6d3d1), c400: Color::from_u32(0xa8a29e), c500: Color::from_u32(0x78716c), c600: Color::from_u32(0x57534e), c700: Color::from_u32(0x44403c), c800: Color::from_u32(0x292524), c900: Color::from_u32(0x1c1917), c950: Color::from_u32(0x0c0a09), }; #[rustfmt::skip] ///
pub const RED: Palette = Palette { c50: Color::from_u32(0xfef2f2), c100: Color::from_u32(0xfee2e2), c200: Color::from_u32(0xfecaca), c300: Color::from_u32(0xfca5a5), c400: Color::from_u32(0xf87171), c500: Color::from_u32(0xef4444), c600: Color::from_u32(0xdc2626), c700: Color::from_u32(0xb91c1c), c800: Color::from_u32(0x991b1b), c900: Color::from_u32(0x7f1d1d), c950: Color::from_u32(0x450a0a), }; #[rustfmt::skip] ///
pub const ORANGE: Palette = Palette { c50: Color::from_u32(0xfff7ed), c100: Color::from_u32(0xffedd5), c200: Color::from_u32(0xfed7aa), c300: Color::from_u32(0xfdba74), c400: Color::from_u32(0xfb923c), c500: Color::from_u32(0xf97316), c600: Color::from_u32(0xea580c), c700: Color::from_u32(0xc2410c), c800: Color::from_u32(0x9a3412), c900: Color::from_u32(0x7c2d12), c950: Color::from_u32(0x431407), }; #[rustfmt::skip] ///
pub const AMBER: Palette = Palette { c50: Color::from_u32(0xfffbeb), c100: Color::from_u32(0xfef3c7), c200: Color::from_u32(0xfde68a), c300: Color::from_u32(0xfcd34d), c400: Color::from_u32(0xfbbf24), c500: Color::from_u32(0xf59e0b), c600: Color::from_u32(0xd97706), c700: Color::from_u32(0xb45309), c800: Color::from_u32(0x92400e), c900: Color::from_u32(0x78350f), c950: Color::from_u32(0x451a03), }; #[rustfmt::skip] ///
pub const YELLOW: Palette = Palette { c50: Color::from_u32(0xfefce8), c100: Color::from_u32(0xfef9c3), c200: Color::from_u32(0xfef08a), c300: Color::from_u32(0xfde047), c400: Color::from_u32(0xfacc15), c500: Color::from_u32(0xeab308), c600: Color::from_u32(0xca8a04), c700: Color::from_u32(0xa16207), c800: Color::from_u32(0x854d0e), c900: Color::from_u32(0x713f12), c950: Color::from_u32(0x422006), }; #[rustfmt::skip] ///
pub const LIME: Palette = Palette { c50: Color::from_u32(0xf7fee7), c100: Color::from_u32(0xecfccb), c200: Color::from_u32(0xd9f99d), c300: Color::from_u32(0xbef264), c400: Color::from_u32(0xa3e635), c500: Color::from_u32(0x84cc16), c600: Color::from_u32(0x65a30d), c700: Color::from_u32(0x4d7c0f), c800: Color::from_u32(0x3f6212), c900: Color::from_u32(0x365314), c950: Color::from_u32(0x1a2e05), }; #[rustfmt::skip] ///
pub const GREEN: Palette = Palette { c50: Color::from_u32(0xf0fdf4), c100: Color::from_u32(0xdcfce7), c200: Color::from_u32(0xbbf7d0), c300: Color::from_u32(0x86efac), c400: Color::from_u32(0x4ade80), c500: Color::from_u32(0x22c55e), c600: Color::from_u32(0x16a34a), c700: Color::from_u32(0x15803d), c800: Color::from_u32(0x166534), c900: Color::from_u32(0x14532d), c950: Color::from_u32(0x052e16), }; #[rustfmt::skip] ///
pub const EMERALD: Palette = Palette { c50: Color::from_u32(0xecfdf5), c100: Color::from_u32(0xd1fae5), c200: Color::from_u32(0xa7f3d0), c300: Color::from_u32(0x6ee7b7), c400: Color::from_u32(0x34d399), c500: Color::from_u32(0x10b981), c600: Color::from_u32(0x059669), c700: Color::from_u32(0x047857), c800: Color::from_u32(0x065f46), c900: Color::from_u32(0x064e3b), c950: Color::from_u32(0x022c22), }; #[rustfmt::skip] ///
pub const TEAL: Palette = Palette { c50: Color::from_u32(0xf0fdfa), c100: Color::from_u32(0xccfbf1), c200: Color::from_u32(0x99f6e4), c300: Color::from_u32(0x5eead4), c400: Color::from_u32(0x2dd4bf), c500: Color::from_u32(0x14b8a6), c600: Color::from_u32(0x0d9488), c700: Color::from_u32(0x0f766e), c800: Color::from_u32(0x115e59), c900: Color::from_u32(0x134e4a), c950: Color::from_u32(0x042f2e), }; #[rustfmt::skip] ///
pub const CYAN: Palette = Palette { c50: Color::from_u32(0xecfeff), c100: Color::from_u32(0xcffafe), c200: Color::from_u32(0xa5f3fc), c300: Color::from_u32(0x67e8f9), c400: Color::from_u32(0x22d3ee), c500: Color::from_u32(0x06b6d4), c600: Color::from_u32(0x0891b2), c700: Color::from_u32(0x0e7490), c800: Color::from_u32(0x155e75), c900: Color::from_u32(0x164e63), c950: Color::from_u32(0x083344), }; #[rustfmt::skip] ///
pub const SKY: Palette = Palette { c50: Color::from_u32(0xf0f9ff), c100: Color::from_u32(0xe0f2fe), c200: Color::from_u32(0xbae6fd), c300: Color::from_u32(0x7dd3fc), c400: Color::from_u32(0x38bdf8), c500: Color::from_u32(0x0ea5e9), c600: Color::from_u32(0x0284c7), c700: Color::from_u32(0x0369a1), c800: Color::from_u32(0x075985), c900: Color::from_u32(0x0c4a6e), c950: Color::from_u32(0x082f49), }; #[rustfmt::skip] ///
pub const BLUE: Palette = Palette { c50: Color::from_u32(0xeff6ff), c100: Color::from_u32(0xdbeafe), c200: Color::from_u32(0xbfdbfe), c300: Color::from_u32(0x93c5fd), c400: Color::from_u32(0x60a5fa), c500: Color::from_u32(0x3b82f6), c600: Color::from_u32(0x2563eb), c700: Color::from_u32(0x1d4ed8), c800: Color::from_u32(0x1e40af), c900: Color::from_u32(0x1e3a8a), c950: Color::from_u32(0x172554), }; #[rustfmt::skip] ///
pub const INDIGO: Palette = Palette { c50: Color::from_u32(0xeef2ff), c100: Color::from_u32(0xe0e7ff), c200: Color::from_u32(0xc7d2fe), c300: Color::from_u32(0xa5b4fc), c400: Color::from_u32(0x818cf8), c500: Color::from_u32(0x6366f1), c600: Color::from_u32(0x4f46e5), c700: Color::from_u32(0x4338ca), c800: Color::from_u32(0x3730a3), c900: Color::from_u32(0x312e81), c950: Color::from_u32(0x1e1b4b), }; #[rustfmt::skip] ///
pub const VIOLET: Palette = Palette { c50: Color::from_u32(0xf5f3ff), c100: Color::from_u32(0xede9fe), c200: Color::from_u32(0xddd6fe), c300: Color::from_u32(0xc4b5fd), c400: Color::from_u32(0xa78bfa), c500: Color::from_u32(0x8b5cf6), c600: Color::from_u32(0x7c3aed), c700: Color::from_u32(0x6d28d9), c800: Color::from_u32(0x5b21b6), c900: Color::from_u32(0x4c1d95), c950: Color::from_u32(0x2e1065), }; #[rustfmt::skip] ///
pub const PURPLE: Palette = Palette { c50: Color::from_u32(0xfaf5ff), c100: Color::from_u32(0xf3e8ff), c200: Color::from_u32(0xe9d5ff), c300: Color::from_u32(0xd8b4fe), c400: Color::from_u32(0xc084fc), c500: Color::from_u32(0xa855f7), c600: Color::from_u32(0x9333ea), c700: Color::from_u32(0x7e22ce), c800: Color::from_u32(0x6b21a8), c900: Color::from_u32(0x581c87), c950: Color::from_u32(0x3b0764), }; #[rustfmt::skip] ///
pub const FUCHSIA: Palette = Palette { c50: Color::from_u32(0xfdf4ff), c100: Color::from_u32(0xfae8ff), c200: Color::from_u32(0xf5d0fe), c300: Color::from_u32(0xf0abfc), c400: Color::from_u32(0xe879f9), c500: Color::from_u32(0xd946ef), c600: Color::from_u32(0xc026d3), c700: Color::from_u32(0xa21caf), c800: Color::from_u32(0x86198f), c900: Color::from_u32(0x701a75), c950: Color::from_u32(0x4a044e), }; #[rustfmt::skip] ///
pub const PINK: Palette = Palette { c50: Color::from_u32(0xfdf2f8), c100: Color::from_u32(0xfce7f3), c200: Color::from_u32(0xfbcfe8), c300: Color::from_u32(0xf9a8d4), c400: Color::from_u32(0xf472b6), c500: Color::from_u32(0xec4899), c600: Color::from_u32(0xdb2777), c700: Color::from_u32(0xbe185d), c800: Color::from_u32(0x9d174d), c900: Color::from_u32(0x831843), c950: Color::from_u32(0x500724), }; #[rustfmt::skip] ///
pub const ROSE: Palette = Palette { c50: Color::from_u32(0xfff1f2), c100: Color::from_u32(0xffe4e6), c200: Color::from_u32(0xfecdd3), c300: Color::from_u32(0xfda4af), c400: Color::from_u32(0xfb7185), c500: Color::from_u32(0xf43f5e), c600: Color::from_u32(0xe11d48), c700: Color::from_u32(0xbe123c), c800: Color::from_u32(0x9f1239), c900: Color::from_u32(0x881337), c950: Color::from_u32(0x4c0519), }; ratatui-core-0.1.0/src/style/palette.rs000064400000000000000000000001661046102023000161570ustar 00000000000000#![allow(clippy::unreadable_literal)] //! A module for defining color palettes. pub mod material; pub mod tailwind; ratatui-core-0.1.0/src/style/palette_conversion.rs000064400000000000000000000043761046102023000204330ustar 00000000000000//! Conversions from colors in the `palette` crate to [`Color`]. use ::palette::LinSrgb; use ::palette::bool_mask::LazySelect; use ::palette::num::{Arithmetics, MulSub, PartialCmp, Powf, Real}; use palette::Srgb; use palette::stimulus::IntoStimulus; use crate::style::Color; /// Convert an [`palette::Srgb`] color to a [`Color`]. /// /// # Examples /// /// ``` /// use palette::Srgb; /// use ratatui_core::style::Color; /// /// let color = Color::from(Srgb::new(1.0f32, 0.0, 0.0)); /// assert_eq!(color, Color::Rgb(255, 0, 0)); /// ``` impl> From> for Color { fn from(color: Srgb) -> Self { let (red, green, blue) = color.into_format().into_components(); Self::Rgb(red, green, blue) } } /// Convert a [`palette::LinSrgb`] color to a [`Color`]. /// /// Note: this conversion only works for floating point linear sRGB colors. If you have a linear /// sRGB color in another format, you need to convert it to floating point first. /// /// # Examples /// /// ``` /// use palette::LinSrgb; /// use ratatui_core::style::Color; /// /// let color = Color::from(LinSrgb::new(1.0f32, 0.0, 0.0)); /// assert_eq!(color, Color::Rgb(255, 0, 0)); /// ``` impl> From> for Color where T: Real + Powf + MulSub + Arithmetics + PartialCmp + Clone, T::Mask: LazySelect, { fn from(color: LinSrgb) -> Self { let srgb_color = Srgb::::from_linear(color); Self::from(srgb_color) } } #[cfg(test)] mod tests { use super::*; #[test] fn from_srgb() { const RED: Color = Color::Rgb(255, 0, 0); assert_eq!(Color::from(Srgb::new(255u8, 0, 0)), RED); assert_eq!(Color::from(Srgb::new(65535u16, 0, 0)), RED); assert_eq!(Color::from(Srgb::new(1.0f32, 0.0, 0.0)), RED); assert_eq!( Color::from(Srgb::new(0.5f32, 0.5, 0.5)), Color::Rgb(128, 128, 128) ); } #[test] fn from_lin_srgb() { const RED: Color = Color::Rgb(255, 0, 0); assert_eq!(Color::from(LinSrgb::new(1.0f32, 0.0, 0.0)), RED); assert_eq!(Color::from(LinSrgb::new(1.0f64, 0.0, 0.0)), RED); assert_eq!( Color::from(LinSrgb::new(0.5f32, 0.5, 0.5)), Color::Rgb(188, 188, 188) ); } } ratatui-core-0.1.0/src/style/stylize.rs000064400000000000000000000616451046102023000162350ustar 00000000000000use alloc::borrow::Cow; use alloc::string::{String, ToString}; use core::fmt; use crate::style::{Color, Modifier, Style}; use crate::text::Span; /// A trait for objects that have a `Style`. /// /// This trait enables generic code to be written that can interact with any object that has a /// `Style`. This is used by the `Stylize` trait to allow generic code to be written that can /// interact with any object that can be styled. pub trait Styled { type Item; /// Returns the style of the object. fn style(&self) -> Style; /// Sets the style of the object. /// /// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or /// your own type that implements [`Into