optimized line rendering and updated render_lines API

This commit is contained in:
Will McGugan
2022-06-15 17:13:56 +01:00
parent 5edefcf29f
commit b0079fce56
9 changed files with 116 additions and 89 deletions

84
poetry.lock generated
View File

@@ -345,7 +345,7 @@ mkdocs = ">=1.1"
[[package]] [[package]]
name = "mkdocs-material" name = "mkdocs-material"
version = "8.3.3" version = "8.3.5"
description = "Documentation that simply works" description = "Documentation that simply works"
category = "dev" category = "dev"
optional = false optional = false
@@ -391,7 +391,7 @@ python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"]
[[package]] [[package]]
name = "mkdocstrings-python" name = "mkdocstrings-python"
version = "0.7.0" version = "0.7.1"
description = "A Python handler for mkdocstrings." description = "A Python handler for mkdocstrings."
category = "dev" category = "dev"
optional = false optional = false
@@ -658,16 +658,21 @@ version = "12.4.4"
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.6.3,<4.0.0" python-versions = "^3.6.3"
develop = true
[package.dependencies] [package.dependencies]
commonmark = ">=0.9.0,<0.10.0" commonmark = "^0.9.0"
pygments = ">=2.6.0,<3.0.0" pygments = "^2.6.0"
typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""} typing-extensions = {version = ">=4.0.0, <5.0", markers = "python_version < \"3.9\""}
[package.extras] [package.extras]
jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"] jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"]
[package.source]
type = "directory"
url = "../rich"
[[package]] [[package]]
name = "six" name = "six"
version = "1.16.0" version = "1.16.0"
@@ -740,7 +745,7 @@ testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)",
[[package]] [[package]]
name = "watchdog" name = "watchdog"
version = "2.1.8" version = "2.1.9"
description = "Filesystem events monitoring" description = "Filesystem events monitoring"
category = "dev" category = "dev"
optional = false optional = false
@@ -777,7 +782,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-
[metadata] [metadata]
lock-version = "1.1" lock-version = "1.1"
python-versions = "^3.7" python-versions = "^3.7"
content-hash = "378a60041202d505cba26ea7084886fe1b01090a0035253b100593b559aed090" content-hash = "4aeef009c7c1f6a34d0dd1c3c16647b53a339f5963dcbe31eef43bb9dac270ab"
[metadata.files] [metadata.files]
aiohttp = [ aiohttp = [
@@ -1122,8 +1127,8 @@ mkdocs-autorefs = [
{file = "mkdocs_autorefs-0.4.1-py3-none-any.whl", hash = "sha256:a2248a9501b29dc0cc8ba4c09f4f47ff121945f6ce33d760f145d6f89d313f5b"}, {file = "mkdocs_autorefs-0.4.1-py3-none-any.whl", hash = "sha256:a2248a9501b29dc0cc8ba4c09f4f47ff121945f6ce33d760f145d6f89d313f5b"},
] ]
mkdocs-material = [ mkdocs-material = [
{file = "mkdocs-material-8.3.3.tar.gz", hash = "sha256:3dd30af894f6d5da3d8a2f8ffc04c90c4d0f1be013e654ec45f608373c131542"}, {file = "mkdocs-material-8.3.5.tar.gz", hash = "sha256:0d7ae82b28fa57a2ad9a4eeb5fd01704cb8fe963eb20c56a68afdd735e52b432"},
{file = "mkdocs_material-8.3.3-py2.py3-none-any.whl", hash = "sha256:4f9564af58f9c96f25c263cb705a40a82c833cb10c2626d6db6ddadedaa5b6c3"}, {file = "mkdocs_material-8.3.5-py2.py3-none-any.whl", hash = "sha256:9190aef365bd6ed43b27f47d1de956d9e67b61f4b9edb444563337ef717cacd3"},
] ]
mkdocs-material-extensions = [ mkdocs-material-extensions = [
{file = "mkdocs-material-extensions-1.0.3.tar.gz", hash = "sha256:bfd24dfdef7b41c312ede42648f9eb83476ea168ec163b613f9abd12bbfddba2"}, {file = "mkdocs-material-extensions-1.0.3.tar.gz", hash = "sha256:bfd24dfdef7b41c312ede42648f9eb83476ea168ec163b613f9abd12bbfddba2"},
@@ -1134,8 +1139,8 @@ mkdocstrings = [
{file = "mkdocstrings-0.19.0.tar.gz", hash = "sha256:efa34a67bad11229d532d89f6836a8a215937548623b64f3698a1df62e01cc3e"}, {file = "mkdocstrings-0.19.0.tar.gz", hash = "sha256:efa34a67bad11229d532d89f6836a8a215937548623b64f3698a1df62e01cc3e"},
] ]
mkdocstrings-python = [ mkdocstrings-python = [
{file = "mkdocstrings-python-0.7.0.tar.gz", hash = "sha256:e54c67890e8bb7dc4604360c8ef5dd214b23b6924de7706f461e3c998d4ea061"}, {file = "mkdocstrings-python-0.7.1.tar.gz", hash = "sha256:c334b382dca202dfa37071c182418a6df5818356a95d54362a2b24822ca3af71"},
{file = "mkdocstrings_python-0.7.0-py3-none-any.whl", hash = "sha256:6964bd92f106766e771ac6cd5bc02643a960602b4d921b95362e31d491e9a6db"}, {file = "mkdocstrings_python-0.7.1-py3-none-any.whl", hash = "sha256:a22060bfa374697678e9af4e62b020d990dad2711c98f7a9fac5c0345bef93c7"},
] ]
msgpack = [ msgpack = [
{file = "msgpack-1.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4ab251d229d10498e9a2f3b1e68ef64cb393394ec477e3370c457f9430ce9250"}, {file = "msgpack-1.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4ab251d229d10498e9a2f3b1e68ef64cb393394ec477e3370c457f9430ce9250"},
@@ -1381,10 +1386,7 @@ pyyaml-env-tag = [
{file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"}, {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"},
{file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"}, {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"},
] ]
rich = [ rich = []
{file = "rich-12.4.4-py3-none-any.whl", hash = "sha256:d2bbd99c320a2532ac71ff6a3164867884357da3e3301f0240090c5d2fdac7ec"},
{file = "rich-12.4.4.tar.gz", hash = "sha256:4c586de507202505346f3e32d1363eb9ed6932f0c2f63184dea88983ff4971e2"},
]
six = [ six = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
@@ -1474,31 +1476,31 @@ virtualenv = [
{file = "virtualenv-20.14.1.tar.gz", hash = "sha256:ef589a79795589aada0c1c5b319486797c03b67ac3984c48c669c0e4f50df3a5"}, {file = "virtualenv-20.14.1.tar.gz", hash = "sha256:ef589a79795589aada0c1c5b319486797c03b67ac3984c48c669c0e4f50df3a5"},
] ]
watchdog = [ watchdog = [
{file = "watchdog-2.1.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:676263bee67b165f16b05abc52acc7a94feac5b5ab2449b491f1a97638a79277"}, {file = "watchdog-2.1.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a735a990a1095f75ca4f36ea2ef2752c99e6ee997c46b0de507ba40a09bf7330"},
{file = "watchdog-2.1.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:aa68d2d9a89d686fae99d28a6edf3b18595e78f5adf4f5c18fbfda549ac0f20c"}, {file = "watchdog-2.1.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b17d302850c8d412784d9246cfe8d7e3af6bcd45f958abb2d08a6f8bedf695d"},
{file = "watchdog-2.1.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5e2e51c53666850c3ecffe9d265fc5d7351db644de17b15e9c685dd3cdcd6f97"}, {file = "watchdog-2.1.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ee3e38a6cc050a8830089f79cbec8a3878ec2fe5160cdb2dc8ccb6def8552658"},
{file = "watchdog-2.1.8-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:7721ac736170b191c50806f43357407138c6748e4eb3e69b071397f7f7aaeedd"}, {file = "watchdog-2.1.9-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:64a27aed691408a6abd83394b38503e8176f69031ca25d64131d8d640a307591"},
{file = "watchdog-2.1.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ce7376aed3da5fd777483fe5ebc8475a440c6d18f23998024f832134b2938e7b"}, {file = "watchdog-2.1.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:195fc70c6e41237362ba720e9aaf394f8178bfc7fa68207f112d108edef1af33"},
{file = "watchdog-2.1.8-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:f9ee4c6bf3a1b2ed6be90a2d78f3f4bbd8105b6390c04a86eb48ed67bbfa0b0b"}, {file = "watchdog-2.1.9-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:bfc4d351e6348d6ec51df007432e6fe80adb53fd41183716017026af03427846"},
{file = "watchdog-2.1.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:68dbe75e0fa1ba4d73ab3f8e67b21770fbed0651d32ce515cd38919a26873266"}, {file = "watchdog-2.1.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8250546a98388cbc00c3ee3cc5cf96799b5a595270dfcfa855491a64b86ef8c3"},
{file = "watchdog-2.1.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0c520009b8cce79099237d810aaa19bc920941c268578436b62013b2f0102320"}, {file = "watchdog-2.1.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:117ffc6ec261639a0209a3252546b12800670d4bf5f84fbd355957a0595fe654"},
{file = "watchdog-2.1.8-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:efcc8cbc1b43902571b3dce7ef53003f5b97fe4f275fe0489565fc6e2ebe3314"}, {file = "watchdog-2.1.9-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:97f9752208f5154e9e7b76acc8c4f5a58801b338de2af14e7e181ee3b28a5d39"},
{file = "watchdog-2.1.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:746e4c197ec1083581bb1f64d07d1136accf03437badb5ff8fcb862565c193b2"}, {file = "watchdog-2.1.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:247dcf1df956daa24828bfea5a138d0e7a7c98b1a47cf1fa5b0c3c16241fcbb7"},
{file = "watchdog-2.1.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1ae17b6be788fb8e4d8753d8d599de948f0275a232416e16436363c682c6f850"}, {file = "watchdog-2.1.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:226b3c6c468ce72051a4c15a4cc2ef317c32590d82ba0b330403cafd98a62cfd"},
{file = "watchdog-2.1.8-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ddde157dc1447d8130cb5b8df102fad845916fe4335e3d3c3f44c16565becbb7"}, {file = "watchdog-2.1.9-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d9820fe47c20c13e3c9dd544d3706a2a26c02b2b43c993b62fcd8011bcc0adb3"},
{file = "watchdog-2.1.8-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4978db33fc0934c92013ee163a9db158ec216099b69fce5aec790aba704da412"}, {file = "watchdog-2.1.9-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:70af927aa1613ded6a68089a9262a009fbdf819f46d09c1a908d4b36e1ba2b2d"},
{file = "watchdog-2.1.8-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b962de4d7d92ff78fb2dbc6a0cb292a679dea879a0eb5568911484d56545b153"}, {file = "watchdog-2.1.9-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ed80a1628cee19f5cfc6bb74e173f1b4189eb532e705e2a13e3250312a62e0c9"},
{file = "watchdog-2.1.8-py3-none-manylinux2014_aarch64.whl", hash = "sha256:1e5d0fdfaa265c29dc12621913a76ae99656cf7587d03950dfeb3595e5a26102"}, {file = "watchdog-2.1.9-py3-none-manylinux2014_aarch64.whl", hash = "sha256:9f05a5f7c12452f6a27203f76779ae3f46fa30f1dd833037ea8cbc2887c60213"},
{file = "watchdog-2.1.8-py3-none-manylinux2014_armv7l.whl", hash = "sha256:036ed15f7cd656351bf4e17244447be0a09a61aaa92014332d50719fc5973bc0"}, {file = "watchdog-2.1.9-py3-none-manylinux2014_armv7l.whl", hash = "sha256:255bb5758f7e89b1a13c05a5bceccec2219f8995a3a4c4d6968fe1de6a3b2892"},
{file = "watchdog-2.1.8-py3-none-manylinux2014_i686.whl", hash = "sha256:2962628a8777650703e8f6f2593065884c602df7bae95759b2df267bd89b2ef5"}, {file = "watchdog-2.1.9-py3-none-manylinux2014_i686.whl", hash = "sha256:d3dda00aca282b26194bdd0adec21e4c21e916956d972369359ba63ade616153"},
{file = "watchdog-2.1.8-py3-none-manylinux2014_ppc64.whl", hash = "sha256:156ec3a94695ea68cfb83454b98754af6e276031ba1ae7ae724dc6bf8973b92a"}, {file = "watchdog-2.1.9-py3-none-manylinux2014_ppc64.whl", hash = "sha256:186f6c55abc5e03872ae14c2f294a153ec7292f807af99f57611acc8caa75306"},
{file = "watchdog-2.1.8-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:47598fe6713fc1fee86b1ca85c9cbe77e9b72d002d6adeab9c3b608f8a5ead10"}, {file = "watchdog-2.1.9-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:083171652584e1b8829581f965b9b7723ca5f9a2cd7e20271edf264cfd7c1412"},
{file = "watchdog-2.1.8-py3-none-manylinux2014_s390x.whl", hash = "sha256:fed4de6e45a4f16e4046ea00917b4fe1700b97244e5d114f594b4a1b9de6bed8"}, {file = "watchdog-2.1.9-py3-none-manylinux2014_s390x.whl", hash = "sha256:b530ae007a5f5d50b7fbba96634c7ee21abec70dc3e7f0233339c81943848dc1"},
{file = "watchdog-2.1.8-py3-none-manylinux2014_x86_64.whl", hash = "sha256:24dedcc3ce75e150f2a1d704661f6879764461a481ba15a57dc80543de46021c"}, {file = "watchdog-2.1.9-py3-none-manylinux2014_x86_64.whl", hash = "sha256:4f4e1c4aa54fb86316a62a87b3378c025e228178d55481d30d857c6c438897d6"},
{file = "watchdog-2.1.8-py3-none-win32.whl", hash = "sha256:6ddf67bc9f413791072e3afb466e46cc72c6799ba73dea18439b412e8f2e3257"}, {file = "watchdog-2.1.9-py3-none-win32.whl", hash = "sha256:5952135968519e2447a01875a6f5fc8c03190b24d14ee52b0f4b1682259520b1"},
{file = "watchdog-2.1.8-py3-none-win_amd64.whl", hash = "sha256:88ef3e8640ef0a64b7ad7394b0f23384f58ac19dd759da7eaa9bc04b2898943f"}, {file = "watchdog-2.1.9-py3-none-win_amd64.whl", hash = "sha256:7a833211f49143c3d336729b0020ffd1274078e94b0ae42e22f596999f50279c"},
{file = "watchdog-2.1.8-py3-none-win_ia64.whl", hash = "sha256:0fb60c7d31474b21acba54079ce9ff0136411183e9a591369417cddb1d7d00d7"}, {file = "watchdog-2.1.9-py3-none-win_ia64.whl", hash = "sha256:ad576a565260d8f99d97f2e64b0f97a48228317095908568a9d5c786c829d428"},
{file = "watchdog-2.1.8.tar.gz", hash = "sha256:6d03149126864abd32715d4e9267d2754cede25a69052901399356ad3bc5ecff"}, {file = "watchdog-2.1.9.tar.gz", hash = "sha256:43ce20ebb36a51f21fa376f76d1d4692452b2527ccd601950d69ed36b9e21609"},
] ]
yarl = [ yarl = [
{file = "yarl-1.7.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f2a8508f7350512434e41065684076f640ecce176d262a7d54f0da41d99c5a95"}, {file = "yarl-1.7.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f2a8508f7350512434e41065684076f640ecce176d262a7d54f0da41d99c5a95"},

View File

@@ -22,8 +22,8 @@ textual = "textual.cli.cli:run"
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "^3.7" python = "^3.7"
rich = "^12.4.3" #rich = "^12.4.3"
#rich = {path="../rich", develop=true} rich = {path="../rich", develop=true}
click = "8.1.2" click = "8.1.2"
importlib-metadata = "^4.11.3" importlib-metadata = "^4.11.3"
typing-extensions = { version = "^4.0.0", python = "<3.8" } typing-extensions = { version = "^4.0.0", python = "<3.8" }

View File

@@ -174,3 +174,6 @@ if __name__ == "__main__":
from textual.css.scalar import Scalar from textual.css.scalar import Scalar
print(Scalar.resolve_dimension.cache_info()) print(Scalar.resolve_dimension.cache_info())
from rich.style import Style
print(Style.__add__.cache_info())

View File

@@ -21,10 +21,14 @@ class TableApp(App):
def on_mount(self): def on_mount(self):
self.bind("d", "toggle_dark") self.bind("d", "toggle_dark")
self.bind("z", "toggle_zebra")
def action_toggle_dark(self) -> None: def action_toggle_dark(self) -> None:
self.app.dark = not self.app.dark self.app.dark = not self.app.dark
def action_toggle_zebra(self) -> None:
self.table.zebra_stripes = not self.table.zebra_stripes
app = TableApp() app = TableApp()
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -445,7 +445,8 @@ class Compositor:
x -= region.x x -= region.x
y -= region.y y -= region.y
lines = widget.render_lines((y, y + 1), (0, region.width)) # lines = widget.render_lines((y, y + 1), (0, region.width))
lines = widget.render_lines(Region(0, y, region.width, 1))
if not lines: if not lines:
return Style.null() return Style.null()
@@ -546,8 +547,7 @@ class Compositor:
continue continue
if region in clip: if region in clip:
yield region, clip, widget.render_lines( yield region, clip, widget.render_lines(
(0, region.height), Region(0, 0, region.width, region.height)
(0, region.width),
) )
elif overlaps(clip, region): elif overlaps(clip, region):
clipped_region = intersection(region, clip) clipped_region = intersection(region, clip)
@@ -556,10 +556,8 @@ class Compositor:
new_x, new_y, new_width, new_height = clipped_region new_x, new_y, new_width, new_height = clipped_region
delta_x = new_x - region.x delta_x = new_x - region.x
delta_y = new_y - region.y delta_y = new_y - region.y
crop_x = delta_x + new_width
lines = widget.render_lines( lines = widget.render_lines(
(delta_y, delta_y + new_height), Region(delta_x, delta_y, new_width, new_height)
(delta_x, crop_x),
) )
yield region, clip, lines yield region, clip, lines

View File

@@ -151,7 +151,7 @@ class App(Generic[ReturnType], DOMNode):
self.console = Console( self.console = Console(
file=(open(os.devnull, "wt") if self.is_headless else sys.__stdout__), file=(open(os.devnull, "wt") if self.is_headless else sys.__stdout__),
markup=True, markup=False,
highlight=False, highlight=False,
emoji=False, emoji=False,
) )

View File

@@ -1,11 +1,9 @@
from __future__ import annotations from __future__ import annotations
from rich.console import RenderableType from rich.console import RenderableType
from rich.segment import Segment
from rich.style import Style
from .geometry import Size from .geometry import Size
from ._types import Lines
from .widget import Widget from .widget import Widget

View File

@@ -877,21 +877,26 @@ class Widget(DOMNode):
self._dirty_regions.clear() self._dirty_regions.clear()
def _crop_lines(self, lines: Lines, x1, x2) -> Lines: def _crop_lines(self, lines: Lines, x1, x2) -> Lines:
if (x1, x2) != (0, self.size.width): width = self.size.width
lines = [line_crop(line, x1, x2, self.size.width) for line in lines] if (x1, x2) != (0, width):
lines = [line_crop(line, x1, x2, width) for line in lines]
return lines return lines
def render_lines( def render_lines(self, crop: Region) -> Lines:
self, line_range: tuple[int, int], column_range: tuple[int, int] """Render the widget in to lines.
) -> Lines:
"""Get segment lines to render the widget.""" Args:
crop (Region): Region within visible area to.
Returns:
Lines: A list of list of segments
"""
if self._dirty_regions: if self._dirty_regions:
self._render_lines() self._render_lines()
y1, y2 = line_range x1, y1, x2, y2 = crop.corners
lines = self._render_cache.lines[y1:y2] lines = self._render_cache.lines[y1:y2]
if column_range is not None: lines = self._crop_lines(lines, x1, x2)
lines = self._crop_lines(lines, *column_range)
return lines return lines
def get_style_at(self, x: int, y: int) -> Style: def get_style_at(self, x: int, y: int) -> Style:

View File

@@ -1,8 +1,9 @@
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass, field
from typing import ClassVar, Generic, TypeVar, cast from typing import ClassVar, Generic, TypeVar, cast
from rich.console import RenderableType
from rich.padding import Padding from rich.padding import Padding
from rich.segment import Segment from rich.segment import Segment
from rich.style import Style from rich.style import Style
@@ -11,8 +12,9 @@ from rich.text import Text, TextType
from .._lru_cache import LRUCache from .._lru_cache import LRUCache
from .._segment_tools import line_crop from .._segment_tools import line_crop
from .._types import Lines from .._types import Lines
from ..geometry import Size from ..geometry import Region, Size
from ..reactive import Reactive from ..reactive import Reactive
from .._profile import timer
from ..scroll_view import ScrollView from ..scroll_view import ScrollView
from ..widget import Widget from ..widget import Widget
@@ -27,6 +29,13 @@ class Column:
index: int = 0 index: int = 0
@dataclass
class Row:
index: int
height: int = 1
cell_renderables: list[RenderableType] = field(default_factory=list)
@dataclass @dataclass
class Cell: class Cell:
value: object value: object
@@ -62,6 +71,10 @@ class DataTable(ScrollView, Generic[CellType]):
background: $primary 10%; background: $primary 10%;
} }
.-dark-mode DataTable > .datatable--even-row {
background: $primary 15%;
}
DataTable > .datatable--highlight { DataTable > .datatable--highlight {
background: $secondary; background: $secondary;
color: $text-secondary; color: $text-secondary;
@@ -84,16 +97,19 @@ class DataTable(ScrollView, Generic[CellType]):
) -> None: ) -> None:
super().__init__(name=name, id=id, classes=classes) super().__init__(name=name, id=id, classes=classes)
self.columns: list[Column] = [] self.columns: list[Column] = []
self.rows: dict[int, Row] = {}
self.data: dict[int, list[CellType]] = {} self.data: dict[int, list[CellType]] = {}
self.row_count = 0 self.row_count = 0
self._cells: dict[int, list[Cell]] = {} self.line_contents: list[str] = []
self._cells: dict[int, list[Cell]] = {}
self._cell_render_cache: dict[tuple[int, int], Lines] = LRUCache(10000) self._cell_render_cache: dict[tuple[int, int], Lines] = LRUCache(10000)
show_header = Reactive(True) show_header = Reactive(True)
fixed_rows = Reactive(1) fixed_rows = Reactive(1)
fixed_columns = Reactive(1) fixed_columns = Reactive(1)
zebra_stripes = Reactive(False)
def _update_dimensions(self) -> None: def _update_dimensions(self) -> None:
max_width = sum(column.width for column in self.columns) max_width = sum(column.width for column in self.columns)
@@ -105,8 +121,17 @@ class DataTable(ScrollView, Generic[CellType]):
self._update_dimensions() self._update_dimensions()
self.refresh() self.refresh()
def add_row(self, *cells: CellType) -> None: def add_row(self, *cells: CellType, height: int = 1) -> None:
self.data[self.row_count] = list(cells) row_index = self.row_count
self.data[row_index] = list(cells)
self.rows[row_index] = Row(
row_index,
height=height,
cell_renderables=[
Text.from_markup(cell) if isinstance(cell, str) else cell
for cell in cells
],
)
self.row_count += 1 self.row_count += 1
self._update_dimensions() self._update_dimensions()
self.refresh() self.refresh()
@@ -122,7 +147,7 @@ class DataTable(ScrollView, Generic[CellType]):
if data is None: if data is None:
return [Text() for column in self.columns] return [Text() for column in self.columns]
else: else:
return data return self.rows[data_offset].cell_renderables
def _render_cell(self, y: int, column: Column) -> Lines: def _render_cell(self, y: int, column: Column) -> Lines:
@@ -151,6 +176,8 @@ class DataTable(ScrollView, Generic[CellType]):
rendered_width += column.width rendered_width += column.width
cell_segments.append(lines[0]) cell_segments.append(lines[0])
base_style = self.rich_style
fixed_style = self.component_styles[ fixed_style = self.component_styles[
"datatable--fixed" "datatable--fixed"
].node.rich_style + Style.from_meta({"fixed": True}) ].node.rich_style + Style.from_meta({"fixed": True})
@@ -165,37 +192,31 @@ class DataTable(ScrollView, Generic[CellType]):
line: list[Segment] = sum(cell_segments, start=[]) line: list[Segment] = sum(cell_segments, start=[])
row_style = Style() row_style = base_style
if y == 0: if y == 0:
segments = fixed + line_crop(line, x1 + fixed_width, x2, width) segments = fixed + line_crop(line, x1 + fixed_width, x2, width)
line = Segment.adjust_line_length(segments, width) line = Segment.adjust_line_length(segments, width)
else: else:
component_row_style = ( if self.zebra_stripes:
"datatable--odd-row" if y % 2 else "datatable--even-row" component_row_style = (
) "datatable--odd-row" if y % 2 else "datatable--even-row"
)
row_style += self.component_styles[component_row_style].node.rich_style row_style = self.component_styles[component_row_style].node.rich_style
line = list(Segment.apply_style(line, row_style)) line = list(Segment.apply_style(line, row_style))
segments = fixed + line_crop(line, x1 + fixed_width, x2, width) segments = fixed + line_crop(line, x1 + fixed_width, x2, width)
line = Segment.adjust_line_length(segments, width) line = Segment.adjust_line_length(segments, width, style=base_style)
if y == 0 and self.show_header: if y == 0 and self.show_header:
line = list(Segment.apply_style(line, header_style)) line = list(Segment.apply_style(line, header_style))
return line return line
def render_lines( @timer("render_lines")
self, line_range: tuple[int, int], column_range: tuple[int, int] def render_lines(self, crop: Region) -> Lines:
) -> Lines:
scroll_x, scroll_y = self.scroll_offset
y1, y2 = line_range
y1 += scroll_y
y2 += scroll_y
x1, x2 = column_range scroll_x, scroll_y = self.scroll_offset
x1 += scroll_x x1, y1, x2, y2 = crop.translate(scroll_x, scroll_y).corners
x2 += scroll_x
fixed_lines = [self._render_line(y, x1, x2) for y in range(0, self.fixed_rows)] fixed_lines = [self._render_line(y, x1, x2) for y in range(0, self.fixed_rows)]
lines = [self._render_line(y, x1, x2) for y in range(y1, y2)] lines = [self._render_line(y, x1, x2) for y in range(y1, y2)]
@@ -204,10 +225,6 @@ class DataTable(ScrollView, Generic[CellType]):
if y - scroll_y == 0: if y - scroll_y == 0:
lines[0] = fixed_line lines[0] = fixed_line
(base_background, base_color), (background, color) = self.colors
style = Style.from_color(color.rich_color, background.rich_color)
lines = [list(Segment.apply_style(line, style)) for line in lines]
return lines return lines
def on_mouse_move(self, event): def on_mouse_move(self, event):