mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
ws
This commit is contained in:
233
poetry.lock
generated
233
poetry.lock
generated
@@ -72,7 +72,7 @@ python-versions = ">=3.6.1"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "click"
|
name = "click"
|
||||||
version = "8.0.3"
|
version = "8.0.4"
|
||||||
description = "Composable command line interface toolkit"
|
description = "Composable command line interface toolkit"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
@@ -103,7 +103,7 @@ test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "coverage"
|
name = "coverage"
|
||||||
version = "6.3.1"
|
version = "6.3.2"
|
||||||
description = "Code coverage measurement for Python"
|
description = "Code coverage measurement for Python"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
@@ -148,7 +148,7 @@ dev = ["twine", "markdown", "flake8", "wheel"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "identify"
|
name = "identify"
|
||||||
version = "2.4.10"
|
version = "2.4.11"
|
||||||
description = "File identification library for Python"
|
description = "File identification library for Python"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
@@ -304,21 +304,21 @@ pytkdocs = ">=0.14.0"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mypy"
|
name = "mypy"
|
||||||
version = "0.910"
|
version = "0.931"
|
||||||
description = "Optional static typing for Python"
|
description = "Optional static typing for Python"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.5"
|
python-versions = ">=3.6"
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
mypy-extensions = ">=0.4.3,<0.5.0"
|
mypy-extensions = ">=0.4.3"
|
||||||
toml = "*"
|
tomli = ">=1.1.0"
|
||||||
typed-ast = {version = ">=1.4.0,<1.5.0", markers = "python_version < \"3.8\""}
|
typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""}
|
||||||
typing-extensions = ">=3.7.4"
|
typing-extensions = ">=3.10"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
dmypy = ["psutil (>=4.0)"]
|
dmypy = ["psutil (>=4.0)"]
|
||||||
python2 = ["typed-ast (>=1.4.0,<1.5.0)"]
|
python2 = ["typed-ast (>=1.4.0,<2)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mypy-extensions"
|
name = "mypy-extensions"
|
||||||
@@ -357,7 +357,7 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "platformdirs"
|
name = "platformdirs"
|
||||||
version = "2.5.0"
|
version = "2.5.1"
|
||||||
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
@@ -488,11 +488,11 @@ six = ">=1.5"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pytkdocs"
|
name = "pytkdocs"
|
||||||
version = "0.15.0"
|
version = "0.16.0"
|
||||||
description = "Load Python objects documentation."
|
description = "Load Python objects documentation."
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.6.2"
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
astunparse = {version = ">=1.6", markers = "python_version < \"3.9\""}
|
astunparse = {version = ">=1.6", markers = "python_version < \"3.9\""}
|
||||||
@@ -564,11 +564,11 @@ python-versions = ">=3.7"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typed-ast"
|
name = "typed-ast"
|
||||||
version = "1.4.3"
|
version = "1.5.2"
|
||||||
description = "a fork of Python 2 and 3 ast modules with type comment support"
|
description = "a fork of Python 2 and 3 ast modules with type comment support"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = ">=3.6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typing-extensions"
|
name = "typing-extensions"
|
||||||
@@ -580,7 +580,7 @@ python-versions = "*"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "virtualenv"
|
name = "virtualenv"
|
||||||
version = "20.13.1"
|
version = "20.13.2"
|
||||||
description = "Virtual Python Environment builder"
|
description = "Virtual Python Environment builder"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
@@ -623,7 +623,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 = "3e7523fa7dbaa4f8c78acc4189fae9bee5b5ecdcca4d437027c7068283634121"
|
content-hash = "017cd9bd80c2432f3de493d190c162e98894d9bb1db0482502658e2bc9231887"
|
||||||
|
|
||||||
[metadata.files]
|
[metadata.files]
|
||||||
astunparse = [
|
astunparse = [
|
||||||
@@ -672,8 +672,8 @@ cfgv = [
|
|||||||
{file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"},
|
{file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"},
|
||||||
]
|
]
|
||||||
click = [
|
click = [
|
||||||
{file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"},
|
{file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"},
|
||||||
{file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"},
|
{file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"},
|
||||||
]
|
]
|
||||||
colorama = [
|
colorama = [
|
||||||
{file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
|
{file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
|
||||||
@@ -684,47 +684,47 @@ commonmark = [
|
|||||||
{file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"},
|
{file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"},
|
||||||
]
|
]
|
||||||
coverage = [
|
coverage = [
|
||||||
{file = "coverage-6.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeffd96882d8c06d31b65dddcf51db7c612547babc1c4c5db6a011abe9798525"},
|
{file = "coverage-6.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9b27d894748475fa858f9597c0ee1d4829f44683f3813633aaf94b19cb5453cf"},
|
||||||
{file = "coverage-6.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:621f6ea7260ea2ffdaec64fe5cb521669984f567b66f62f81445221d4754df4c"},
|
{file = "coverage-6.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37d1141ad6b2466a7b53a22e08fe76994c2d35a5b6b469590424a9953155afac"},
|
||||||
{file = "coverage-6.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84f2436d6742c01136dd940ee158bfc7cf5ced3da7e4c949662b8703b5cd8145"},
|
{file = "coverage-6.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9987b0354b06d4df0f4d3e0ec1ae76d7ce7cbca9a2f98c25041eb79eec766f1"},
|
||||||
{file = "coverage-6.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de73fca6fb403dd72d4da517cfc49fcf791f74eee697d3219f6be29adf5af6ce"},
|
{file = "coverage-6.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:26e2deacd414fc2f97dd9f7676ee3eaecd299ca751412d89f40bc01557a6b1b4"},
|
||||||
{file = "coverage-6.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78fbb2be068a13a5d99dce9e1e7d168db880870f7bc73f876152130575bd6167"},
|
{file = "coverage-6.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4dd8bafa458b5c7d061540f1ee9f18025a68e2d8471b3e858a9dad47c8d41903"},
|
||||||
{file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f5a4551dfd09c3bd12fca8144d47fe7745275adf3229b7223c2f9e29a975ebda"},
|
{file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:46191097ebc381fbf89bdce207a6c107ac4ec0890d8d20f3360345ff5976155c"},
|
||||||
{file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7bff3a98f63b47464480de1b5bdd80c8fade0ba2832c9381253c9b74c4153c27"},
|
{file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6f89d05e028d274ce4fa1a86887b071ae1755082ef94a6740238cd7a8178804f"},
|
||||||
{file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a06c358f4aed05fa1099c39decc8022261bb07dfadc127c08cfbd1391b09689e"},
|
{file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:58303469e9a272b4abdb9e302a780072c0633cdcc0165db7eec0f9e32f901e05"},
|
||||||
{file = "coverage-6.3.1-cp310-cp310-win32.whl", hash = "sha256:9fff3ff052922cb99f9e52f63f985d4f7a54f6b94287463bc66b7cdf3eb41217"},
|
{file = "coverage-6.3.2-cp310-cp310-win32.whl", hash = "sha256:2fea046bfb455510e05be95e879f0e768d45c10c11509e20e06d8fcaa31d9e39"},
|
||||||
{file = "coverage-6.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:276b13cc085474e482566c477c25ed66a097b44c6e77132f3304ac0b039f83eb"},
|
{file = "coverage-6.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:a2a8b8bcc399edb4347a5ca8b9b87e7524c0967b335fbb08a83c8421489ddee1"},
|
||||||
{file = "coverage-6.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:56c4a409381ddd7bbff134e9756077860d4e8a583d310a6f38a2315b9ce301d0"},
|
{file = "coverage-6.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f1555ea6d6da108e1999b2463ea1003fe03f29213e459145e70edbaf3e004aaa"},
|
||||||
{file = "coverage-6.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9eb494070aa060ceba6e4bbf44c1bc5fa97bfb883a0d9b0c9049415f9e944793"},
|
{file = "coverage-6.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5f4e1edcf57ce94e5475fe09e5afa3e3145081318e5fd1a43a6b4539a97e518"},
|
||||||
{file = "coverage-6.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5e15d424b8153756b7c903bde6d4610be0c3daca3986173c18dd5c1a1625e4cd"},
|
{file = "coverage-6.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a15dc0a14008f1da3d1ebd44bdda3e357dbabdf5a0b5034d38fcde0b5c234b7"},
|
||||||
{file = "coverage-6.3.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61d47a897c1e91f33f177c21de897267b38fbb45f2cd8e22a710bcef1df09ac1"},
|
{file = "coverage-6.3.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21b7745788866028adeb1e0eca3bf1101109e2dc58456cb49d2d9b99a8c516e6"},
|
||||||
{file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:25e73d4c81efa8ea3785274a2f7f3bfbbeccb6fcba2a0bdd3be9223371c37554"},
|
{file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8ce257cac556cb03be4a248d92ed36904a59a4a5ff55a994e92214cde15c5bad"},
|
||||||
{file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:fac0bcc5b7e8169bffa87f0dcc24435446d329cbc2b5486d155c2e0f3b493ae1"},
|
{file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b0be84e5a6209858a1d3e8d1806c46214e867ce1b0fd32e4ea03f4bd8b2e3359"},
|
||||||
{file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:72128176fea72012063200b7b395ed8a57849282b207321124d7ff14e26988e8"},
|
{file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:acf53bc2cf7282ab9b8ba346746afe703474004d9e566ad164c91a7a59f188a4"},
|
||||||
{file = "coverage-6.3.1-cp37-cp37m-win32.whl", hash = "sha256:1bc6d709939ff262fd1432f03f080c5042dc6508b6e0d3d20e61dd045456a1a0"},
|
{file = "coverage-6.3.2-cp37-cp37m-win32.whl", hash = "sha256:8bdde1177f2311ee552f47ae6e5aa7750c0e3291ca6b75f71f7ffe1f1dab3dca"},
|
||||||
{file = "coverage-6.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:618eeba986cea7f621d8607ee378ecc8c2504b98b3fdc4952b30fe3578304687"},
|
{file = "coverage-6.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b31651d018b23ec463e95cf10070d0b2c548aa950a03d0b559eaa11c7e5a6fa3"},
|
||||||
{file = "coverage-6.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d5ed164af5c9078596cfc40b078c3b337911190d3faeac830c3f1274f26b8320"},
|
{file = "coverage-6.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:07e6db90cd9686c767dcc593dff16c8c09f9814f5e9c51034066cad3373b914d"},
|
||||||
{file = "coverage-6.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:352c68e233409c31048a3725c446a9e48bbff36e39db92774d4f2380d630d8f8"},
|
{file = "coverage-6.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2c6dbb42f3ad25760010c45191e9757e7dce981cbfb90e42feef301d71540059"},
|
||||||
{file = "coverage-6.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:448d7bde7ceb6c69e08474c2ddbc5b4cd13c9e4aa4a717467f716b5fc938a734"},
|
{file = "coverage-6.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c76aeef1b95aff3905fb2ae2d96e319caca5b76fa41d3470b19d4e4a3a313512"},
|
||||||
{file = "coverage-6.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9fde6b90889522c220dd56a670102ceef24955d994ff7af2cb786b4ba8fe11e4"},
|
{file = "coverage-6.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cf5cfcb1521dc3255d845d9dca3ff204b3229401994ef8d1984b32746bb45ca"},
|
||||||
{file = "coverage-6.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e647a0be741edbb529a72644e999acb09f2ad60465f80757da183528941ff975"},
|
{file = "coverage-6.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fbbdc8d55990eac1b0919ca69eb5a988a802b854488c34b8f37f3e2025fa90d"},
|
||||||
{file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a5cdc3adb4f8bb8d8f5e64c2e9e282bc12980ef055ec6da59db562ee9bdfefa"},
|
{file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ec6bc7fe73a938933d4178c9b23c4e0568e43e220aef9472c4f6044bfc6dd0f0"},
|
||||||
{file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2dd70a167843b4b4b2630c0c56f1b586fe965b4f8ac5da05b6690344fd065c6b"},
|
{file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9baff2a45ae1f17c8078452e9e5962e518eab705e50a0aa8083733ea7d45f3a6"},
|
||||||
{file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9ad0a117b8dc2061ce9461ea4c1b4799e55edceb236522c5b8f958ce9ed8fa9a"},
|
{file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd9e830e9d8d89b20ab1e5af09b32d33e1a08ef4c4e14411e559556fd788e6b2"},
|
||||||
{file = "coverage-6.3.1-cp38-cp38-win32.whl", hash = "sha256:e92c7a5f7d62edff50f60a045dc9542bf939758c95b2fcd686175dd10ce0ed10"},
|
{file = "coverage-6.3.2-cp38-cp38-win32.whl", hash = "sha256:f7331dbf301b7289013175087636bbaf5b2405e57259dd2c42fdcc9fcc47325e"},
|
||||||
{file = "coverage-6.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:482fb42eea6164894ff82abbcf33d526362de5d1a7ed25af7ecbdddd28fc124f"},
|
{file = "coverage-6.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:68353fe7cdf91f109fc7d474461b46e7f1f14e533e911a2a2cbb8b0fc8613cf1"},
|
||||||
{file = "coverage-6.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c5b81fb37db76ebea79aa963b76d96ff854e7662921ce742293463635a87a78d"},
|
{file = "coverage-6.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b78e5afb39941572209f71866aa0b206c12f0109835aa0d601e41552f9b3e620"},
|
||||||
{file = "coverage-6.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a4f923b9ab265136e57cc14794a15b9dcea07a9c578609cd5dbbfff28a0d15e6"},
|
{file = "coverage-6.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4e21876082ed887baed0146fe222f861b5815455ada3b33b890f4105d806128d"},
|
||||||
{file = "coverage-6.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56d296cbc8254a7dffdd7bcc2eb70be5a233aae7c01856d2d936f5ac4e8ac1f1"},
|
{file = "coverage-6.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34626a7eee2a3da12af0507780bb51eb52dca0e1751fd1471d0810539cefb536"},
|
||||||
{file = "coverage-6.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1245ab82e8554fa88c4b2ab1e098ae051faac5af829efdcf2ce6b34dccd5567c"},
|
{file = "coverage-6.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1ebf730d2381158ecf3dfd4453fbca0613e16eaa547b4170e2450c9707665ce7"},
|
||||||
{file = "coverage-6.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f2b05757c92ad96b33dbf8e8ec8d4ccb9af6ae3c9e9bd141c7cc44d20c6bcba"},
|
{file = "coverage-6.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd6fe30bd519694b356cbfcaca9bd5c1737cddd20778c6a581ae20dc8c04def2"},
|
||||||
{file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9e3dd806f34de38d4c01416344e98eab2437ac450b3ae39c62a0ede2f8b5e4ed"},
|
{file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:96f8a1cb43ca1422f36492bebe63312d396491a9165ed3b9231e778d43a7fca4"},
|
||||||
{file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d651fde74a4d3122e5562705824507e2f5b2d3d57557f1916c4b27635f8fbe3f"},
|
{file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:dd035edafefee4d573140a76fdc785dc38829fe5a455c4bb12bac8c20cfc3d69"},
|
||||||
{file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:704f89b87c4f4737da2860695a18c852b78ec7279b24eedacab10b29067d3a38"},
|
{file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5ca5aeb4344b30d0bec47481536b8ba1181d50dbe783b0e4ad03c95dc1296684"},
|
||||||
{file = "coverage-6.3.1-cp39-cp39-win32.whl", hash = "sha256:2aed4761809640f02e44e16b8b32c1a5dee5e80ea30a0ff0912158bde9c501f2"},
|
{file = "coverage-6.3.2-cp39-cp39-win32.whl", hash = "sha256:f5fa5803f47e095d7ad8443d28b01d48c0359484fec1b9d8606d0e3282084bc4"},
|
||||||
{file = "coverage-6.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:9976fb0a5709988778ac9bc44f3d50fccd989987876dfd7716dee28beed0a9fa"},
|
{file = "coverage-6.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:9548f10d8be799551eb3a9c74bbf2b4934ddb330e08a73320123c07f95cc2d92"},
|
||||||
{file = "coverage-6.3.1-pp36.pp37.pp38-none-any.whl", hash = "sha256:463e52616ea687fd323888e86bf25e864a3cc6335a043fad6bbb037dbf49bbe2"},
|
{file = "coverage-6.3.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:18d520c6860515a771708937d2f78f63cc47ab3b80cb78e86573b0a760161faf"},
|
||||||
{file = "coverage-6.3.1.tar.gz", hash = "sha256:6c3f6158b02ac403868eea390930ae64e9a9a2a5bbfafefbb920d29258d9f2f8"},
|
{file = "coverage-6.3.2.tar.gz", hash = "sha256:03e2a7826086b91ef345ff18742ee9fc47a6839ccd517061ef8fa1976e652ce9"},
|
||||||
]
|
]
|
||||||
distlib = [
|
distlib = [
|
||||||
{file = "distlib-0.3.4-py2.py3-none-any.whl", hash = "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b"},
|
{file = "distlib-0.3.4-py2.py3-none-any.whl", hash = "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b"},
|
||||||
@@ -739,8 +739,8 @@ ghp-import = [
|
|||||||
{file = "ghp_import-2.0.2-py3-none-any.whl", hash = "sha256:5f8962b30b20652cdffa9c5a9812f7de6bcb56ec475acac579807719bf242c46"},
|
{file = "ghp_import-2.0.2-py3-none-any.whl", hash = "sha256:5f8962b30b20652cdffa9c5a9812f7de6bcb56ec475acac579807719bf242c46"},
|
||||||
]
|
]
|
||||||
identify = [
|
identify = [
|
||||||
{file = "identify-2.4.10-py2.py3-none-any.whl", hash = "sha256:7d10baf6ba6f1912a0a49f4c1c2c49fa1718765c3a37d72d13b07779567c5b85"},
|
{file = "identify-2.4.11-py2.py3-none-any.whl", hash = "sha256:fd906823ed1db23c7a48f9b176a1d71cb8abede1e21ebe614bac7bdd688d9213"},
|
||||||
{file = "identify-2.4.10.tar.gz", hash = "sha256:e12b2aea3cf108de73ae055c2260783bde6601de09718f6768cf8e9f6f6322a6"},
|
{file = "identify-2.4.11.tar.gz", hash = "sha256:2986942d3974c8f2e5019a190523b0b0e2a07cb8e89bf236727fb4b26f27f8fd"},
|
||||||
]
|
]
|
||||||
importlib-metadata = [
|
importlib-metadata = [
|
||||||
{file = "importlib_metadata-4.11.1-py3-none-any.whl", hash = "sha256:e0bc84ff355328a4adfc5240c4f211e0ab386f80aa640d1b11f0618a1d282094"},
|
{file = "importlib_metadata-4.11.1-py3-none-any.whl", hash = "sha256:e0bc84ff355328a4adfc5240c4f211e0ab386f80aa640d1b11f0618a1d282094"},
|
||||||
@@ -825,29 +825,26 @@ mkdocstrings = [
|
|||||||
{file = "mkdocstrings-0.17.0.tar.gz", hash = "sha256:75b5cfa2039aeaf3a5f5cf0aa438507b0330ce76c8478da149d692daa7213a98"},
|
{file = "mkdocstrings-0.17.0.tar.gz", hash = "sha256:75b5cfa2039aeaf3a5f5cf0aa438507b0330ce76c8478da149d692daa7213a98"},
|
||||||
]
|
]
|
||||||
mypy = [
|
mypy = [
|
||||||
{file = "mypy-0.910-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:a155d80ea6cee511a3694b108c4494a39f42de11ee4e61e72bc424c490e46457"},
|
{file = "mypy-0.931-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3c5b42d0815e15518b1f0990cff7a705805961613e701db60387e6fb663fe78a"},
|
||||||
{file = "mypy-0.910-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b94e4b785e304a04ea0828759172a15add27088520dc7e49ceade7834275bedb"},
|
{file = "mypy-0.931-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c89702cac5b302f0c5d33b172d2b55b5df2bede3344a2fbed99ff96bddb2cf00"},
|
||||||
{file = "mypy-0.910-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:088cd9c7904b4ad80bec811053272986611b84221835e079be5bcad029e79dd9"},
|
{file = "mypy-0.931-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:300717a07ad09525401a508ef5d105e6b56646f7942eb92715a1c8d610149714"},
|
||||||
{file = "mypy-0.910-cp35-cp35m-win_amd64.whl", hash = "sha256:adaeee09bfde366d2c13fe6093a7df5df83c9a2ba98638c7d76b010694db760e"},
|
{file = "mypy-0.931-cp310-cp310-win_amd64.whl", hash = "sha256:7b3f6f557ba4afc7f2ce6d3215d5db279bcf120b3cfd0add20a5d4f4abdae5bc"},
|
||||||
{file = "mypy-0.910-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ecd2c3fe726758037234c93df7e98deb257fd15c24c9180dacf1ef829da5f921"},
|
{file = "mypy-0.931-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:1bf752559797c897cdd2c65f7b60c2b6969ffe458417b8d947b8340cc9cec08d"},
|
||||||
{file = "mypy-0.910-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d9dd839eb0dc1bbe866a288ba3c1afc33a202015d2ad83b31e875b5905a079b6"},
|
{file = "mypy-0.931-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4365c60266b95a3f216a3047f1d8e3f895da6c7402e9e1ddfab96393122cc58d"},
|
||||||
{file = "mypy-0.910-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:3e382b29f8e0ccf19a2df2b29a167591245df90c0b5a2542249873b5c1d78212"},
|
{file = "mypy-0.931-cp36-cp36m-win_amd64.whl", hash = "sha256:1b65714dc296a7991000b6ee59a35b3f550e0073411ac9d3202f6516621ba66c"},
|
||||||
{file = "mypy-0.910-cp36-cp36m-win_amd64.whl", hash = "sha256:53fd2eb27a8ee2892614370896956af2ff61254c275aaee4c230ae771cadd885"},
|
{file = "mypy-0.931-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e839191b8da5b4e5d805f940537efcaa13ea5dd98418f06dc585d2891d228cf0"},
|
||||||
{file = "mypy-0.910-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b6fb13123aeef4a3abbcfd7e71773ff3ff1526a7d3dc538f3929a49b42be03f0"},
|
{file = "mypy-0.931-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:50c7346a46dc76a4ed88f3277d4959de8a2bd0a0fa47fa87a4cde36fe247ac05"},
|
||||||
{file = "mypy-0.910-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e4dab234478e3bd3ce83bac4193b2ecd9cf94e720ddd95ce69840273bf44f6de"},
|
{file = "mypy-0.931-cp37-cp37m-win_amd64.whl", hash = "sha256:d8f1ff62f7a879c9fe5917b3f9eb93a79b78aad47b533911b853a757223f72e7"},
|
||||||
{file = "mypy-0.910-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:7df1ead20c81371ccd6091fa3e2878559b5c4d4caadaf1a484cf88d93ca06703"},
|
{file = "mypy-0.931-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f9fe20d0872b26c4bba1c1be02c5340de1019530302cf2dcc85c7f9fc3252ae0"},
|
||||||
{file = "mypy-0.910-cp37-cp37m-win_amd64.whl", hash = "sha256:0aadfb2d3935988ec3815952e44058a3100499f5be5b28c34ac9d79f002a4a9a"},
|
{file = "mypy-0.931-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1b06268df7eb53a8feea99cbfff77a6e2b205e70bf31743e786678ef87ee8069"},
|
||||||
{file = "mypy-0.910-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ec4e0cd079db280b6bdabdc807047ff3e199f334050db5cbb91ba3e959a67504"},
|
{file = "mypy-0.931-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8c11003aaeaf7cc2d0f1bc101c1cc9454ec4cc9cb825aef3cafff8a5fdf4c799"},
|
||||||
{file = "mypy-0.910-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:119bed3832d961f3a880787bf621634ba042cb8dc850a7429f643508eeac97b9"},
|
{file = "mypy-0.931-cp38-cp38-win_amd64.whl", hash = "sha256:d9d2b84b2007cea426e327d2483238f040c49405a6bf4074f605f0156c91a47a"},
|
||||||
{file = "mypy-0.910-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:866c41f28cee548475f146aa4d39a51cf3b6a84246969f3759cb3e9c742fc072"},
|
{file = "mypy-0.931-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ff3bf387c14c805ab1388185dd22d6b210824e164d4bb324b195ff34e322d166"},
|
||||||
{file = "mypy-0.910-cp38-cp38-win_amd64.whl", hash = "sha256:ceb6e0a6e27fb364fb3853389607cf7eb3a126ad335790fa1e14ed02fba50811"},
|
{file = "mypy-0.931-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5b56154f8c09427bae082b32275a21f500b24d93c88d69a5e82f3978018a0266"},
|
||||||
{file = "mypy-0.910-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1a85e280d4d217150ce8cb1a6dddffd14e753a4e0c3cf90baabb32cefa41b59e"},
|
{file = "mypy-0.931-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8ca7f8c4b1584d63c9a0f827c37ba7a47226c19a23a753d52e5b5eddb201afcd"},
|
||||||
{file = "mypy-0.910-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:42c266ced41b65ed40a282c575705325fa7991af370036d3f134518336636f5b"},
|
{file = "mypy-0.931-cp39-cp39-win_amd64.whl", hash = "sha256:74f7eccbfd436abe9c352ad9fb65872cc0f1f0a868e9d9c44db0893440f0c697"},
|
||||||
{file = "mypy-0.910-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:3c4b8ca36877fc75339253721f69603a9c7fdb5d4d5a95a1a1b899d8b86a4de2"},
|
{file = "mypy-0.931-py3-none-any.whl", hash = "sha256:1171f2e0859cfff2d366da2c7092b06130f232c636a3f7301e3feb8b41f6377d"},
|
||||||
{file = "mypy-0.910-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:c0df2d30ed496a08de5daed2a9ea807d07c21ae0ab23acf541ab88c24b26ab97"},
|
{file = "mypy-0.931.tar.gz", hash = "sha256:0038b21890867793581e4cb0d810829f5fd4441aa75796b53033af3aa30430ce"},
|
||||||
{file = "mypy-0.910-cp39-cp39-win_amd64.whl", hash = "sha256:c6c2602dffb74867498f86e6129fd52a2770c48b7cd3ece77ada4fa38f94eba8"},
|
|
||||||
{file = "mypy-0.910-py3-none-any.whl", hash = "sha256:ef565033fa5a958e62796867b1df10c40263ea9ded87164d67572834e57a174d"},
|
|
||||||
{file = "mypy-0.910.tar.gz", hash = "sha256:704098302473cb31a218f1775a873b376b30b4c18229421e9e9dc8916fd16150"},
|
|
||||||
]
|
]
|
||||||
mypy-extensions = [
|
mypy-extensions = [
|
||||||
{file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
|
{file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
|
||||||
@@ -866,8 +863,8 @@ pathspec = [
|
|||||||
{file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
|
{file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
|
||||||
]
|
]
|
||||||
platformdirs = [
|
platformdirs = [
|
||||||
{file = "platformdirs-2.5.0-py3-none-any.whl", hash = "sha256:30671902352e97b1eafd74ade8e4a694782bd3471685e78c32d0fdfd3aa7e7bb"},
|
{file = "platformdirs-2.5.1-py3-none-any.whl", hash = "sha256:bcae7cab893c2d310a711b70b24efb93334febe65f8de776ee320b517471e227"},
|
||||||
{file = "platformdirs-2.5.0.tar.gz", hash = "sha256:8ec11dfba28ecc0715eb5fb0147a87b1bf325f349f3da9aab2cd6b50b96b692b"},
|
{file = "platformdirs-2.5.1.tar.gz", hash = "sha256:7535e70dfa32e84d4b34996ea99c5e432fa29a708d0f4e394bbcb2a8faa4f16d"},
|
||||||
]
|
]
|
||||||
pluggy = [
|
pluggy = [
|
||||||
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
|
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
|
||||||
@@ -906,8 +903,8 @@ python-dateutil = [
|
|||||||
{file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
|
{file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
|
||||||
]
|
]
|
||||||
pytkdocs = [
|
pytkdocs = [
|
||||||
{file = "pytkdocs-0.15.0-py3-none-any.whl", hash = "sha256:d6b2aec34448ec89acb8c1c25062cc1e70c6b26395d46fc7ee753b7e5a4e736a"},
|
{file = "pytkdocs-0.16.0-py3-none-any.whl", hash = "sha256:098405ff347797cfb887a4d78f161372109bfbfffa0f8ee8daa5e2b8a8caba4b"},
|
||||||
{file = "pytkdocs-0.15.0.tar.gz", hash = "sha256:4b45af89d6fa5fa50f979b0f9f54539286b84e245c81991bb838149aa2d9d9c9"},
|
{file = "pytkdocs-0.16.0.tar.gz", hash = "sha256:274407bcefec58d7e411adb03b84da49120107912b1697d17d4a3aea0583f388"},
|
||||||
]
|
]
|
||||||
pyyaml = [
|
pyyaml = [
|
||||||
{file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
|
{file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
|
||||||
@@ -965,36 +962,30 @@ tomli = [
|
|||||||
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
|
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
|
||||||
]
|
]
|
||||||
typed-ast = [
|
typed-ast = [
|
||||||
{file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"},
|
{file = "typed_ast-1.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:183b183b7771a508395d2cbffd6db67d6ad52958a5fdc99f450d954003900266"},
|
||||||
{file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"},
|
{file = "typed_ast-1.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:676d051b1da67a852c0447621fdd11c4e104827417bf216092ec3e286f7da596"},
|
||||||
{file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"},
|
{file = "typed_ast-1.5.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc2542e83ac8399752bc16e0b35e038bdb659ba237f4222616b4e83fb9654985"},
|
||||||
{file = "typed_ast-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428"},
|
{file = "typed_ast-1.5.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74cac86cc586db8dfda0ce65d8bcd2bf17b58668dfcc3652762f3ef0e6677e76"},
|
||||||
{file = "typed_ast-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3"},
|
{file = "typed_ast-1.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:18fe320f354d6f9ad3147859b6e16649a0781425268c4dde596093177660e71a"},
|
||||||
{file = "typed_ast-1.4.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f"},
|
{file = "typed_ast-1.5.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:31d8c6b2df19a777bc8826770b872a45a1f30cfefcfd729491baa5237faae837"},
|
||||||
{file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341"},
|
{file = "typed_ast-1.5.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:963a0ccc9a4188524e6e6d39b12c9ca24cc2d45a71cfdd04a26d883c922b4b78"},
|
||||||
{file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace"},
|
{file = "typed_ast-1.5.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0eb77764ea470f14fcbb89d51bc6bbf5e7623446ac4ed06cbd9ca9495b62e36e"},
|
||||||
{file = "typed_ast-1.4.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f"},
|
{file = "typed_ast-1.5.2-cp36-cp36m-win_amd64.whl", hash = "sha256:294a6903a4d087db805a7656989f613371915fc45c8cc0ddc5c5a0a8ad9bea4d"},
|
||||||
{file = "typed_ast-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363"},
|
{file = "typed_ast-1.5.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:26a432dc219c6b6f38be20a958cbe1abffcc5492821d7e27f08606ef99e0dffd"},
|
||||||
{file = "typed_ast-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7"},
|
{file = "typed_ast-1.5.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7407cfcad702f0b6c0e0f3e7ab876cd1d2c13b14ce770e412c0c4b9728a0f88"},
|
||||||
{file = "typed_ast-1.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266"},
|
{file = "typed_ast-1.5.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f30ddd110634c2d7534b2d4e0e22967e88366b0d356b24de87419cc4410c41b7"},
|
||||||
{file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e"},
|
{file = "typed_ast-1.5.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8c08d6625bb258179b6e512f55ad20f9dfef019bbfbe3095247401e053a3ea30"},
|
||||||
{file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04"},
|
{file = "typed_ast-1.5.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:90904d889ab8e81a956f2c0935a523cc4e077c7847a836abee832f868d5c26a4"},
|
||||||
{file = "typed_ast-1.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899"},
|
{file = "typed_ast-1.5.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bbebc31bf11762b63bf61aaae232becb41c5bf6b3461b80a4df7e791fabb3aca"},
|
||||||
{file = "typed_ast-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c"},
|
{file = "typed_ast-1.5.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c29dd9a3a9d259c9fa19d19738d021632d673f6ed9b35a739f48e5f807f264fb"},
|
||||||
{file = "typed_ast-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805"},
|
{file = "typed_ast-1.5.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:58ae097a325e9bb7a684572d20eb3e1809802c5c9ec7108e85da1eb6c1a3331b"},
|
||||||
{file = "typed_ast-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a"},
|
{file = "typed_ast-1.5.2-cp38-cp38-win_amd64.whl", hash = "sha256:da0a98d458010bf4fe535f2d1e367a2e2060e105978873c04c04212fb20543f7"},
|
||||||
{file = "typed_ast-1.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff"},
|
{file = "typed_ast-1.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:33b4a19ddc9fc551ebabca9765d54d04600c4a50eda13893dadf67ed81d9a098"},
|
||||||
{file = "typed_ast-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41"},
|
{file = "typed_ast-1.5.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1098df9a0592dd4c8c0ccfc2e98931278a6c6c53cb3a3e2cf7e9ee3b06153344"},
|
||||||
{file = "typed_ast-1.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39"},
|
{file = "typed_ast-1.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42c47c3b43fe3a39ddf8de1d40dbbfca60ac8530a36c9b198ea5b9efac75c09e"},
|
||||||
{file = "typed_ast-1.4.3-cp38-cp38-win32.whl", hash = "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927"},
|
{file = "typed_ast-1.5.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f290617f74a610849bd8f5514e34ae3d09eafd521dceaa6cf68b3f4414266d4e"},
|
||||||
{file = "typed_ast-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40"},
|
{file = "typed_ast-1.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:df05aa5b241e2e8045f5f4367a9f6187b09c4cdf8578bb219861c4e27c443db5"},
|
||||||
{file = "typed_ast-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3"},
|
{file = "typed_ast-1.5.2.tar.gz", hash = "sha256:525a2d4088e70a9f75b08b3f87a51acc9cde640e19cc523c7e41aa355564ae27"},
|
||||||
{file = "typed_ast-1.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4"},
|
|
||||||
{file = "typed_ast-1.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0"},
|
|
||||||
{file = "typed_ast-1.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3"},
|
|
||||||
{file = "typed_ast-1.4.3-cp39-cp39-win32.whl", hash = "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808"},
|
|
||||||
{file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"},
|
|
||||||
{file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"},
|
|
||||||
]
|
]
|
||||||
typing-extensions = [
|
typing-extensions = [
|
||||||
{file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"},
|
{file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"},
|
||||||
@@ -1002,8 +993,8 @@ typing-extensions = [
|
|||||||
{file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"},
|
{file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"},
|
||||||
]
|
]
|
||||||
virtualenv = [
|
virtualenv = [
|
||||||
{file = "virtualenv-20.13.1-py2.py3-none-any.whl", hash = "sha256:45e1d053cad4cd453181ae877c4ffc053546ae99e7dd049b9ff1d9be7491abf7"},
|
{file = "virtualenv-20.13.2-py2.py3-none-any.whl", hash = "sha256:e7b34c9474e6476ee208c43a4d9ac1510b041c68347eabfe9a9ea0c86aa0a46b"},
|
||||||
{file = "virtualenv-20.13.1.tar.gz", hash = "sha256:e0621bcbf4160e4e1030f05065c8834b4e93f4fcc223255db2a823440aca9c14"},
|
{file = "virtualenv-20.13.2.tar.gz", hash = "sha256:01f5f80744d24a3743ce61858123488e91cb2dd1d3bdf92adaf1bba39ffdedf0"},
|
||||||
]
|
]
|
||||||
watchdog = [
|
watchdog = [
|
||||||
{file = "watchdog-2.1.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9693f35162dc6208d10b10ddf0458cc09ad70c30ba689d9206e02cd836ce28a3"},
|
{file = "watchdog-2.1.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9693f35162dc6208d10b10ddf0458cc09ad70c30ba689d9206e02cd836ce28a3"},
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ typing-extensions = { version = "^3.10.0", python = "<3.8" }
|
|||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.dev-dependencies]
|
||||||
pytest = "^6.2.3"
|
pytest = "^6.2.3"
|
||||||
black = "^22.1.0"
|
black = "^22.1.0"
|
||||||
mypy = "^0.910"
|
mypy = "^0.931"
|
||||||
pytest-cov = "^2.12.1"
|
pytest-cov = "^2.12.1"
|
||||||
mkdocs = "^1.2.3"
|
mkdocs = "^1.2.3"
|
||||||
mkdocstrings = "^0.17.0"
|
mkdocstrings = "^0.17.0"
|
||||||
|
|||||||
477
src/textual/_arrangement.py
Normal file
477
src/textual/_arrangement.py
Normal file
@@ -0,0 +1,477 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from itertools import chain
|
||||||
|
from operator import attrgetter, itemgetter
|
||||||
|
import sys
|
||||||
|
from typing import Iterator, Iterable, NamedTuple, TYPE_CHECKING
|
||||||
|
|
||||||
|
import rich.repr
|
||||||
|
from rich.console import Console, ConsoleOptions, RenderResult
|
||||||
|
from rich.control import Control
|
||||||
|
from rich.segment import Segment, SegmentLines
|
||||||
|
from rich.style import Style
|
||||||
|
|
||||||
|
from . import log
|
||||||
|
from .geometry import Region, Offset, Size
|
||||||
|
|
||||||
|
from .layout import WidgetPlacement
|
||||||
|
from ._loop import loop_last
|
||||||
|
from ._types import Lines
|
||||||
|
|
||||||
|
if sys.version_info >= (3, 10):
|
||||||
|
from typing import TypeAlias
|
||||||
|
else: # pragma: no cover
|
||||||
|
from typing_extensions import TypeAlias
|
||||||
|
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .widget import Widget
|
||||||
|
|
||||||
|
|
||||||
|
class NoWidget(Exception):
|
||||||
|
"""Raised when there is no widget at the requested coordinate."""
|
||||||
|
|
||||||
|
|
||||||
|
class ReflowResult(NamedTuple):
|
||||||
|
"""The result of a reflow operation. Describes the chances to widgets."""
|
||||||
|
|
||||||
|
hidden: set[Widget]
|
||||||
|
shown: set[Widget]
|
||||||
|
resized: set[Widget]
|
||||||
|
|
||||||
|
|
||||||
|
class RenderRegion(NamedTuple):
|
||||||
|
"""Defines the absolute location of a Widget."""
|
||||||
|
|
||||||
|
region: Region
|
||||||
|
order: tuple[int, ...]
|
||||||
|
clip: Region
|
||||||
|
|
||||||
|
|
||||||
|
RenderRegionMap: TypeAlias = dict[Widget, RenderRegion]
|
||||||
|
|
||||||
|
|
||||||
|
@rich.repr.auto
|
||||||
|
class LayoutUpdate:
|
||||||
|
"""A renderable containing the result of a render for a given region."""
|
||||||
|
|
||||||
|
def __init__(self, lines: Lines, region: Region) -> None:
|
||||||
|
self.lines = lines
|
||||||
|
self.region = region
|
||||||
|
|
||||||
|
def __rich_console__(
|
||||||
|
self, console: Console, options: ConsoleOptions
|
||||||
|
) -> RenderResult:
|
||||||
|
yield Control.home()
|
||||||
|
x = self.region.x
|
||||||
|
new_line = Segment.line()
|
||||||
|
move_to = Control.move_to
|
||||||
|
for last, (y, line) in loop_last(enumerate(self.lines, self.region.y)):
|
||||||
|
yield move_to(x, y)
|
||||||
|
yield from line
|
||||||
|
if not last:
|
||||||
|
yield new_line
|
||||||
|
|
||||||
|
def __rich_repr__(self) -> rich.repr.Result:
|
||||||
|
x, y, width, height = self.region
|
||||||
|
yield "x", x
|
||||||
|
yield "y", y
|
||||||
|
yield "width", width
|
||||||
|
yield "height", height
|
||||||
|
|
||||||
|
|
||||||
|
@rich.repr.auto(angular=True)
|
||||||
|
class Arrangement:
|
||||||
|
"""Responsible for storing information regarding the relative positions of Widgets and rendering them."""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
# A mapping of Widget on to its "render location" (absolute position / depth)
|
||||||
|
self.map: RenderRegionMap = {}
|
||||||
|
|
||||||
|
# All widgets considered in the arrangement
|
||||||
|
# Not this may be a supperset of self.map.keys() as some widgets may be invisible for various reasons
|
||||||
|
self.widgets: set[Widget] = set()
|
||||||
|
|
||||||
|
# Dimensions of the arrangement
|
||||||
|
self.width = 0
|
||||||
|
self.height = 0
|
||||||
|
|
||||||
|
self.regions: dict[Widget, tuple[Region, Region]] = {}
|
||||||
|
self._cuts: list[list[int]] | None = None
|
||||||
|
self._require_update: bool = True
|
||||||
|
self.background = ""
|
||||||
|
|
||||||
|
def __rich_repr__(self) -> rich.repr.Result:
|
||||||
|
yield "width", self.width
|
||||||
|
yield "height", self.height
|
||||||
|
yield "widgets", self.widgets
|
||||||
|
|
||||||
|
def check_update(self) -> bool:
|
||||||
|
return self._require_update
|
||||||
|
|
||||||
|
def require_update(self) -> None:
|
||||||
|
self._require_update = True
|
||||||
|
self.reset()
|
||||||
|
self.map.clear()
|
||||||
|
self.widgets.clear()
|
||||||
|
|
||||||
|
def reset_update(self) -> None:
|
||||||
|
self._require_update = False
|
||||||
|
|
||||||
|
def reset(self) -> None:
|
||||||
|
self._cuts = None
|
||||||
|
|
||||||
|
def reflow(self, parent: Widget, size: Size) -> ReflowResult:
|
||||||
|
"""Reflow (layout) widget and its children.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
parent (Widget): The root widget.
|
||||||
|
size (Size): Size of the area to be filled.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ReflowResult: Hidden shown and resized widgets
|
||||||
|
"""
|
||||||
|
self.reset()
|
||||||
|
|
||||||
|
self.width = size.width
|
||||||
|
self.height = size.height
|
||||||
|
|
||||||
|
map, virtual_size = self._arrange_root(parent)
|
||||||
|
|
||||||
|
self._require_update = False
|
||||||
|
|
||||||
|
old_widgets = set(self.map.keys())
|
||||||
|
new_widgets = set(map.keys())
|
||||||
|
# Newly visible widgets
|
||||||
|
shown_widgets = new_widgets - old_widgets
|
||||||
|
# Newly hidden widgets
|
||||||
|
hidden_widgets = old_widgets - new_widgets
|
||||||
|
|
||||||
|
self._layout_map = map
|
||||||
|
|
||||||
|
# Copy renders if the size hasn't changed
|
||||||
|
new_renders = {
|
||||||
|
widget: (region, clip) for widget, (region, _order, clip) in map.items()
|
||||||
|
}
|
||||||
|
self.regions = new_renders
|
||||||
|
|
||||||
|
# Widgets with changed size
|
||||||
|
resized_widgets = {
|
||||||
|
widget
|
||||||
|
for widget, (region, *_) in map.items()
|
||||||
|
if widget in old_widgets and widget.size != region.size
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.virtual_size = virtual_size
|
||||||
|
|
||||||
|
return ReflowResult(
|
||||||
|
hidden=hidden_widgets, shown=shown_widgets, resized=resized_widgets
|
||||||
|
)
|
||||||
|
|
||||||
|
def _arrange_root(self, root: Widget) -> tuple[RenderRegionMap, Size]:
|
||||||
|
"""Arrange a widgets children based on its layout attribute.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
root (Widget): Top level widget.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
map[dict[Widget, RenderRegion], Size]: A mapping of widget on to render region
|
||||||
|
and the "virtual size" (scrollable reason)
|
||||||
|
"""
|
||||||
|
size = Size(self.width, self.height)
|
||||||
|
ORIGIN = Offset(0, 0)
|
||||||
|
|
||||||
|
map: dict[Widget, RenderRegion] = {}
|
||||||
|
|
||||||
|
def add_widget(
|
||||||
|
widget,
|
||||||
|
region: Region,
|
||||||
|
order: tuple[int, ...],
|
||||||
|
clip: Region,
|
||||||
|
):
|
||||||
|
widgets: set[Widget] = set()
|
||||||
|
styles_offset = widget.styles.offset
|
||||||
|
total_region = region
|
||||||
|
layout_offset = (
|
||||||
|
styles_offset.resolve(region.size, clip.size)
|
||||||
|
if styles_offset
|
||||||
|
else ORIGIN
|
||||||
|
)
|
||||||
|
|
||||||
|
map[widget] = RenderRegion(region + layout_offset, order, clip)
|
||||||
|
|
||||||
|
if widget.layout is not None:
|
||||||
|
scroll = widget.scroll
|
||||||
|
total_region = region.size.region
|
||||||
|
sub_clip = clip.intersection(region)
|
||||||
|
|
||||||
|
placements: list[WidgetPlacement] = []
|
||||||
|
add_placement = placements.append
|
||||||
|
iter_arrange = iter(widget.layout.arrange(widget, region.size, scroll))
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
add_placement(iter_arrange.__next__())
|
||||||
|
except StopIteration as stop_iteration:
|
||||||
|
widgets.update(stop_iteration.value)
|
||||||
|
|
||||||
|
placements = sorted(
|
||||||
|
[
|
||||||
|
placement.apply_margin()
|
||||||
|
for placement in widget.layout.arrange(
|
||||||
|
widget, region.size, scroll
|
||||||
|
)
|
||||||
|
],
|
||||||
|
key=attrgetter("order"),
|
||||||
|
)
|
||||||
|
for sub_region, sub_widget, z in placements:
|
||||||
|
total_region = total_region.union(sub_region)
|
||||||
|
if sub_widget is not None:
|
||||||
|
add_widget(
|
||||||
|
sub_widget,
|
||||||
|
sub_region + region.origin - scroll,
|
||||||
|
sub_widget.z + (z,),
|
||||||
|
sub_clip,
|
||||||
|
)
|
||||||
|
return total_region.size
|
||||||
|
|
||||||
|
virtual_size = add_widget(root, size.region, (), size.region)
|
||||||
|
return map, virtual_size
|
||||||
|
|
||||||
|
async def mount_all(self, view: "View") -> None:
|
||||||
|
view.mount(*self.widgets)
|
||||||
|
|
||||||
|
def __iter__(self) -> Iterator[tuple[Widget, Region, Region]]:
|
||||||
|
layers = sorted(self.map.items(), key=lambda item: item[1].order, reverse=True)
|
||||||
|
for widget, (region, order, clip) in layers:
|
||||||
|
yield widget, region.intersection(clip), region
|
||||||
|
|
||||||
|
def get_offset(self, widget: Widget) -> Offset:
|
||||||
|
"""Get the offset of a widget."""
|
||||||
|
try:
|
||||||
|
return self.map[widget].region.origin
|
||||||
|
except KeyError:
|
||||||
|
raise NoWidget("Widget is not in layout")
|
||||||
|
|
||||||
|
def get_widget_at(self, x: int, y: int) -> tuple[Widget, Region]:
|
||||||
|
"""Get the widget under the given point or None."""
|
||||||
|
for widget, cropped_region, region in self:
|
||||||
|
if widget.is_visual and cropped_region.contains(x, y):
|
||||||
|
return widget, region
|
||||||
|
raise NoWidget(f"No widget under screen coordinate ({x}, {y})")
|
||||||
|
|
||||||
|
def get_style_at(self, x: int, y: int) -> Style:
|
||||||
|
"""Get the Style at the given cell or Style.null()
|
||||||
|
|
||||||
|
Args:
|
||||||
|
x (int): X position within the Layout
|
||||||
|
y (int): Y position within the Layout
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Style: The Style at the cell (x, y) within the Layout
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
widget, region = self.get_widget_at(x, y)
|
||||||
|
except NoWidget:
|
||||||
|
return Style.null()
|
||||||
|
if widget not in self.regions:
|
||||||
|
return Style.null()
|
||||||
|
lines = widget._get_lines()
|
||||||
|
x -= region.x
|
||||||
|
y -= region.y
|
||||||
|
line = lines[y]
|
||||||
|
end = 0
|
||||||
|
for segment in line:
|
||||||
|
end += segment.cell_length
|
||||||
|
if x < end:
|
||||||
|
return segment.style or Style.null()
|
||||||
|
return Style.null()
|
||||||
|
|
||||||
|
def get_widget_region(self, widget: Widget) -> Region:
|
||||||
|
"""Get the Region of a Widget contained in this Layout.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
widget (Widget): The Widget in this layout you wish to know the Region of.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
NoWidget: If the Widget is not contained in this Layout.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Region: The Region of the Widget.
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
region, *_ = self.map[widget]
|
||||||
|
except KeyError:
|
||||||
|
raise NoWidget("Widget is not in layout")
|
||||||
|
else:
|
||||||
|
return region
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cuts(self) -> list[list[int]]:
|
||||||
|
"""Get vertical cuts.
|
||||||
|
|
||||||
|
A cut is every point on a line where a widget starts or ends.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list[list[int]]: A list of cuts for every line.
|
||||||
|
"""
|
||||||
|
if self._cuts is not None:
|
||||||
|
return self._cuts
|
||||||
|
width = self.width
|
||||||
|
height = self.height
|
||||||
|
screen_region = Region(0, 0, width, height)
|
||||||
|
cuts_sets = [{0, width} for _ in range(height)]
|
||||||
|
|
||||||
|
if self.map is not None:
|
||||||
|
for region, order, clip in self.map.values():
|
||||||
|
region = region.intersection(clip)
|
||||||
|
if region and (region in screen_region):
|
||||||
|
region_cuts = region.x_extents
|
||||||
|
for y in region.y_range:
|
||||||
|
cuts_sets[y].update(region_cuts)
|
||||||
|
|
||||||
|
# Sort the cuts for each line
|
||||||
|
self._cuts = [sorted(cut_set) for cut_set in cuts_sets]
|
||||||
|
return self._cuts
|
||||||
|
|
||||||
|
def _get_renders(self, console: Console) -> Iterable[tuple[Region, Region, Lines]]:
|
||||||
|
_rich_traceback_guard = True
|
||||||
|
layout_map = self.map
|
||||||
|
|
||||||
|
if layout_map:
|
||||||
|
widget_regions = sorted(
|
||||||
|
(
|
||||||
|
(widget, region, order, clip)
|
||||||
|
for widget, (region, order, clip) in layout_map.items()
|
||||||
|
),
|
||||||
|
key=itemgetter(2),
|
||||||
|
reverse=True,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
widget_regions = []
|
||||||
|
|
||||||
|
for widget, region, _order, clip in widget_regions:
|
||||||
|
|
||||||
|
if not (widget.is_visual and widget.visible):
|
||||||
|
continue
|
||||||
|
|
||||||
|
lines = widget._get_lines()
|
||||||
|
|
||||||
|
if region in clip:
|
||||||
|
yield region, clip, lines
|
||||||
|
elif clip.overlaps(region):
|
||||||
|
new_region = region.intersection(clip)
|
||||||
|
delta_x = new_region.x - region.x
|
||||||
|
delta_y = new_region.y - region.y
|
||||||
|
splits = [delta_x, delta_x + new_region.width]
|
||||||
|
lines = lines[delta_y : delta_y + new_region.height]
|
||||||
|
divide = Segment.divide
|
||||||
|
lines = [list(divide(line, splits))[1] for line in lines]
|
||||||
|
yield region, clip, lines
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _assemble_chops(
|
||||||
|
cls, chops: list[dict[int, list[Segment] | None]]
|
||||||
|
) -> Iterable[Iterable[Segment]]:
|
||||||
|
|
||||||
|
from_iterable = chain.from_iterable
|
||||||
|
for bucket in chops:
|
||||||
|
yield from_iterable(
|
||||||
|
line for _, line in sorted(bucket.items()) if line is not None
|
||||||
|
)
|
||||||
|
|
||||||
|
def render(
|
||||||
|
self,
|
||||||
|
console: Console,
|
||||||
|
*,
|
||||||
|
crop: Region = None,
|
||||||
|
) -> SegmentLines:
|
||||||
|
"""Render a layout.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
console (Console): Console instance.
|
||||||
|
clip (Optional[Region]): Region to clip to.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
SegmentLines: A renderable
|
||||||
|
"""
|
||||||
|
width = self.width
|
||||||
|
height = self.height
|
||||||
|
screen = Region(0, 0, width, height)
|
||||||
|
|
||||||
|
crop_region = crop.intersection(screen) if crop else screen
|
||||||
|
|
||||||
|
_Segment = Segment
|
||||||
|
divide = _Segment.divide
|
||||||
|
|
||||||
|
# Maps each cut on to a list of segments
|
||||||
|
cuts = self.cuts
|
||||||
|
chops: list[dict[int, list[Segment] | None]] = [
|
||||||
|
{cut: None for cut in cut_set} for cut_set in cuts
|
||||||
|
]
|
||||||
|
|
||||||
|
# TODO: Provide an option to update the background
|
||||||
|
background_style = console.get_style(self.background)
|
||||||
|
background_render = [
|
||||||
|
[_Segment(" " * width, background_style)] for _ in range(height)
|
||||||
|
]
|
||||||
|
# Go through all the renders in reverse order and fill buckets with no render
|
||||||
|
renders = list(self._get_renders(console))
|
||||||
|
|
||||||
|
for region, clip, lines in chain(
|
||||||
|
renders, [(screen, screen, background_render)]
|
||||||
|
):
|
||||||
|
render_region = region.intersection(clip)
|
||||||
|
for y, line in zip(render_region.y_range, lines):
|
||||||
|
|
||||||
|
first_cut, last_cut = render_region.x_extents
|
||||||
|
final_cuts = [cut for cut in cuts[y] if (last_cut >= cut >= first_cut)]
|
||||||
|
|
||||||
|
if len(final_cuts) == 2:
|
||||||
|
cut_segments = [line]
|
||||||
|
else:
|
||||||
|
render_x = render_region.x
|
||||||
|
relative_cuts = [cut - render_x for cut in final_cuts]
|
||||||
|
_, *cut_segments = divide(line, relative_cuts)
|
||||||
|
for cut, segments in zip(final_cuts, cut_segments):
|
||||||
|
if chops[y][cut] is None:
|
||||||
|
chops[y][cut] = segments
|
||||||
|
|
||||||
|
# Assemble the cut renders in to lists of segments
|
||||||
|
crop_x, crop_y, crop_x2, crop_y2 = crop_region.corners
|
||||||
|
output_lines = self._assemble_chops(chops[crop_y:crop_y2])
|
||||||
|
|
||||||
|
def width_view(line: list[Segment]) -> list[Segment]:
|
||||||
|
if line:
|
||||||
|
div_lines = list(divide(line, [crop_x, crop_x2]))
|
||||||
|
line = div_lines[1] if len(div_lines) > 1 else div_lines[0]
|
||||||
|
return line
|
||||||
|
|
||||||
|
if crop is not None and (crop_x, crop_x2) != (0, self.width):
|
||||||
|
render_lines = [width_view(line) for line in output_lines]
|
||||||
|
else:
|
||||||
|
render_lines = list(output_lines)
|
||||||
|
|
||||||
|
return SegmentLines(render_lines, new_lines=True)
|
||||||
|
|
||||||
|
def __rich_console__(
|
||||||
|
self, console: Console, options: ConsoleOptions
|
||||||
|
) -> RenderResult:
|
||||||
|
yield self.render(console)
|
||||||
|
|
||||||
|
def update_widget(self, console: Console, widget: Widget) -> LayoutUpdate | None:
|
||||||
|
if widget not in self.regions:
|
||||||
|
return None
|
||||||
|
|
||||||
|
region, clip = self.regions[widget]
|
||||||
|
|
||||||
|
if not region.size:
|
||||||
|
return None
|
||||||
|
|
||||||
|
widget.clear_render_cache()
|
||||||
|
|
||||||
|
update_region = region.intersection(clip)
|
||||||
|
update_lines = self.render(console, crop=update_region).lines
|
||||||
|
update = LayoutUpdate(update_lines, update_region)
|
||||||
|
log(update)
|
||||||
|
return update
|
||||||
@@ -662,7 +662,7 @@ class App(DOMNode):
|
|||||||
|
|
||||||
async def handle_layout(self, message: messages.Layout) -> None:
|
async def handle_layout(self, message: messages.Layout) -> None:
|
||||||
message.stop()
|
message.stop()
|
||||||
await self.view.refresh_layout()
|
# await self.view.refresh_layout()
|
||||||
self.app.refresh()
|
self.app.refresh()
|
||||||
|
|
||||||
async def on_key(self, event: events.Key) -> None:
|
async def on_key(self, event: events.Key) -> None:
|
||||||
|
|||||||
@@ -1,24 +1,10 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from itertools import chain
|
from typing import ClassVar, Generator, Iterable, NamedTuple, TYPE_CHECKING
|
||||||
from operator import itemgetter
|
|
||||||
from typing import ClassVar, Iterable, Iterator, NamedTuple, TYPE_CHECKING
|
|
||||||
|
|
||||||
import rich.repr
|
|
||||||
from rich.console import Console, ConsoleOptions, RenderResult
|
|
||||||
from rich.control import Control
|
|
||||||
from rich.segment import Segment, SegmentLines
|
|
||||||
from rich.style import Style
|
|
||||||
|
|
||||||
from . import log
|
|
||||||
from ._loop import loop_last
|
|
||||||
from ._types import Lines
|
|
||||||
from .geometry import Region, Offset, Size
|
from .geometry import Region, Offset, Size
|
||||||
from .layout_map import LayoutMap
|
|
||||||
|
|
||||||
PY38 = sys.version_info >= (3, 8)
|
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -26,18 +12,6 @@ if TYPE_CHECKING:
|
|||||||
from .view import View
|
from .view import View
|
||||||
|
|
||||||
|
|
||||||
class NoWidget(Exception):
|
|
||||||
"""Raised when there is no widget at the requested coordinate."""
|
|
||||||
|
|
||||||
|
|
||||||
class ReflowResult(NamedTuple):
|
|
||||||
"""The result of a reflow operation. Describes the chances to widgets."""
|
|
||||||
|
|
||||||
hidden: set[Widget]
|
|
||||||
shown: set[Widget]
|
|
||||||
resized: set[Widget]
|
|
||||||
|
|
||||||
|
|
||||||
class WidgetPlacement(NamedTuple):
|
class WidgetPlacement(NamedTuple):
|
||||||
"""The position, size, and relative order of a widget within its parent."""
|
"""The position, size, and relative order of a widget within its parent."""
|
||||||
|
|
||||||
@@ -66,364 +40,22 @@ class WidgetPlacement(NamedTuple):
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
||||||
@rich.repr.auto
|
|
||||||
class LayoutUpdate:
|
|
||||||
"""A renderable containing the result of a render for a given region."""
|
|
||||||
|
|
||||||
def __init__(self, lines: Lines, region: Region) -> None:
|
|
||||||
self.lines = lines
|
|
||||||
self.region = region
|
|
||||||
|
|
||||||
def __rich_console__(
|
|
||||||
self, console: Console, options: ConsoleOptions
|
|
||||||
) -> RenderResult:
|
|
||||||
yield Control.home()
|
|
||||||
x = self.region.x
|
|
||||||
new_line = Segment.line()
|
|
||||||
move_to = Control.move_to
|
|
||||||
for last, (y, line) in loop_last(enumerate(self.lines, self.region.y)):
|
|
||||||
yield move_to(x, y)
|
|
||||||
yield from line
|
|
||||||
if not last:
|
|
||||||
yield new_line
|
|
||||||
|
|
||||||
def __rich_repr__(self) -> rich.repr.Result:
|
|
||||||
x, y, width, height = self.region
|
|
||||||
yield "x", x
|
|
||||||
yield "y", y
|
|
||||||
yield "width", width
|
|
||||||
yield "height", height
|
|
||||||
|
|
||||||
|
|
||||||
class Layout(ABC):
|
class Layout(ABC):
|
||||||
"""Responsible for arranging Widgets in a view and rendering them."""
|
"""Responsible for arranging Widgets in a view and rendering them."""
|
||||||
|
|
||||||
name: ClassVar[str] = ""
|
name: ClassVar[str] = ""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
self._layout_map: LayoutMap | None = None
|
|
||||||
self.width = 0
|
|
||||||
self.height = 0
|
|
||||||
self.regions: dict[Widget, tuple[Region, Region]] = {}
|
|
||||||
self._cuts: list[list[int]] | None = None
|
|
||||||
self._require_update: bool = True
|
|
||||||
self.background = ""
|
|
||||||
|
|
||||||
def check_update(self) -> bool:
|
|
||||||
return self._require_update
|
|
||||||
|
|
||||||
def require_update(self) -> None:
|
|
||||||
self._require_update = True
|
|
||||||
self.reset()
|
|
||||||
self._layout_map = None
|
|
||||||
|
|
||||||
def reset_update(self) -> None:
|
|
||||||
self._require_update = False
|
|
||||||
|
|
||||||
def reset(self) -> None:
|
|
||||||
self._cuts = None
|
|
||||||
|
|
||||||
def reflow(self, view: View, size: Size) -> ReflowResult:
|
|
||||||
self.reset()
|
|
||||||
|
|
||||||
self.width = size.width
|
|
||||||
self.height = size.height
|
|
||||||
|
|
||||||
map = LayoutMap(size)
|
|
||||||
map.add_widget(view, size.region, (), size.region)
|
|
||||||
|
|
||||||
self._require_update = False
|
|
||||||
|
|
||||||
old_widgets = set() if self.map is None else set(self.map.keys())
|
|
||||||
new_widgets = set(map.keys())
|
|
||||||
# Newly visible widgets
|
|
||||||
shown_widgets = new_widgets - old_widgets
|
|
||||||
# Newly hidden widgets
|
|
||||||
hidden_widgets = old_widgets - new_widgets
|
|
||||||
|
|
||||||
self._layout_map = map
|
|
||||||
|
|
||||||
# Copy renders if the size hasn't changed
|
|
||||||
new_renders = {
|
|
||||||
widget: (region, clip) for widget, (region, _order, clip) in map.items()
|
|
||||||
}
|
|
||||||
self.regions = new_renders
|
|
||||||
|
|
||||||
# Widgets with changed size
|
|
||||||
resized_widgets = {
|
|
||||||
widget
|
|
||||||
for widget, (region, *_) in map.items()
|
|
||||||
if widget in old_widgets and widget.size != region.size
|
|
||||||
}
|
|
||||||
|
|
||||||
return ReflowResult(
|
|
||||||
hidden=hidden_widgets, shown=shown_widgets, resized=resized_widgets
|
|
||||||
)
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def get_widgets(self, view: View) -> Iterable[Widget]:
|
|
||||||
...
|
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def arrange(
|
def arrange(
|
||||||
self, view: View, size: Size, scroll: Offset
|
self, parent: View, size: Size, scroll: Offset
|
||||||
) -> Iterable[WidgetPlacement]:
|
) -> Generator[WidgetPlacement, None, set[Widget]]:
|
||||||
"""Generate a layout map that defines where on the screen the widgets will be drawn.
|
"""Generate a layout map that defines where on the screen the widgets will be drawn.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
view (View): The View instance.
|
parent (Widget): Parent widget.
|
||||||
size (Size): Size of container.
|
size (Size): Size of container.
|
||||||
scroll (Offset): Offset to apply to the Widget placements.
|
scroll (Offset): Offset to apply to the Widget placements.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Iterable[WidgetPlacement]: An iterable of widget location
|
Iterable[WidgetPlacement]: An iterable of widget location
|
||||||
"""
|
"""
|
||||||
|
|
||||||
async def mount_all(self, view: "View") -> None:
|
|
||||||
widgets = list(self.get_widgets(view))
|
|
||||||
if widgets:
|
|
||||||
view.mount(*widgets)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def map(self) -> LayoutMap:
|
|
||||||
assert self._layout_map is not None
|
|
||||||
return self._layout_map
|
|
||||||
|
|
||||||
def __iter__(self) -> Iterator[tuple[Widget, Region, Region]]:
|
|
||||||
if self.map is not None:
|
|
||||||
layers = sorted(
|
|
||||||
self.map.widgets.items(), key=lambda item: item[1].order, reverse=True
|
|
||||||
)
|
|
||||||
for widget, (region, order, clip) in layers:
|
|
||||||
yield widget, region.intersection(clip), region
|
|
||||||
|
|
||||||
def get_offset(self, widget: Widget) -> Offset:
|
|
||||||
"""Get the offset of a widget."""
|
|
||||||
try:
|
|
||||||
return self.map[widget].region.origin
|
|
||||||
except KeyError:
|
|
||||||
raise NoWidget("Widget is not in layout")
|
|
||||||
|
|
||||||
def get_widget_at(self, x: int, y: int) -> tuple[Widget, Region]:
|
|
||||||
"""Get the widget under the given point or None."""
|
|
||||||
for widget, cropped_region, region in self:
|
|
||||||
if widget.is_visual and cropped_region.contains(x, y):
|
|
||||||
return widget, region
|
|
||||||
raise NoWidget(f"No widget under screen coordinate ({x}, {y})")
|
|
||||||
|
|
||||||
def get_style_at(self, x: int, y: int) -> Style:
|
|
||||||
"""Get the Style at the given cell or Style.null()
|
|
||||||
|
|
||||||
Args:
|
|
||||||
x (int): X position within the Layout
|
|
||||||
y (int): Y position within the Layout
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Style: The Style at the cell (x, y) within the Layout
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
widget, region = self.get_widget_at(x, y)
|
|
||||||
except NoWidget:
|
|
||||||
return Style.null()
|
|
||||||
if widget not in self.regions:
|
|
||||||
return Style.null()
|
|
||||||
lines = widget._get_lines()
|
|
||||||
x -= region.x
|
|
||||||
y -= region.y
|
|
||||||
line = lines[y]
|
|
||||||
end = 0
|
|
||||||
for segment in line:
|
|
||||||
end += segment.cell_length
|
|
||||||
if x < end:
|
|
||||||
return segment.style or Style.null()
|
|
||||||
return Style.null()
|
|
||||||
|
|
||||||
def get_widget_region(self, widget: Widget) -> Region:
|
|
||||||
"""Get the Region of a Widget contained in this Layout.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
widget (Widget): The Widget in this layout you wish to know the Region of.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
NoWidget: If the Widget is not contained in this Layout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Region: The Region of the Widget.
|
|
||||||
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
region, *_ = self.map[widget]
|
|
||||||
except KeyError:
|
|
||||||
raise NoWidget("Widget is not in layout")
|
|
||||||
else:
|
|
||||||
return region
|
|
||||||
|
|
||||||
@property
|
|
||||||
def cuts(self) -> list[list[int]]:
|
|
||||||
"""Get vertical cuts.
|
|
||||||
|
|
||||||
A cut is every point on a line where a widget starts or ends.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
list[list[int]]: A list of cuts for every line.
|
|
||||||
"""
|
|
||||||
if self._cuts is not None:
|
|
||||||
return self._cuts
|
|
||||||
width = self.width
|
|
||||||
height = self.height
|
|
||||||
screen_region = Region(0, 0, width, height)
|
|
||||||
cuts_sets = [{0, width} for _ in range(height)]
|
|
||||||
|
|
||||||
if self.map is not None:
|
|
||||||
for region, order, clip in self.map.values():
|
|
||||||
region = region.intersection(clip)
|
|
||||||
if region and (region in screen_region):
|
|
||||||
region_cuts = region.x_extents
|
|
||||||
for y in region.y_range:
|
|
||||||
cuts_sets[y].update(region_cuts)
|
|
||||||
|
|
||||||
# Sort the cuts for each line
|
|
||||||
self._cuts = [sorted(cut_set) for cut_set in cuts_sets]
|
|
||||||
return self._cuts
|
|
||||||
|
|
||||||
def _get_renders(self, console: Console) -> Iterable[tuple[Region, Region, Lines]]:
|
|
||||||
_rich_traceback_guard = True
|
|
||||||
layout_map = self.map
|
|
||||||
|
|
||||||
if layout_map:
|
|
||||||
widget_regions = sorted(
|
|
||||||
(
|
|
||||||
(widget, region, order, clip)
|
|
||||||
for widget, (region, order, clip) in layout_map.items()
|
|
||||||
),
|
|
||||||
key=itemgetter(2),
|
|
||||||
reverse=True,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
widget_regions = []
|
|
||||||
|
|
||||||
for widget, region, _order, clip in widget_regions:
|
|
||||||
|
|
||||||
if not (widget.is_visual and widget.visible):
|
|
||||||
continue
|
|
||||||
|
|
||||||
lines = widget._get_lines()
|
|
||||||
|
|
||||||
if region in clip:
|
|
||||||
yield region, clip, lines
|
|
||||||
elif clip.overlaps(region):
|
|
||||||
new_region = region.intersection(clip)
|
|
||||||
delta_x = new_region.x - region.x
|
|
||||||
delta_y = new_region.y - region.y
|
|
||||||
splits = [delta_x, delta_x + new_region.width]
|
|
||||||
lines = lines[delta_y : delta_y + new_region.height]
|
|
||||||
divide = Segment.divide
|
|
||||||
lines = [list(divide(line, splits))[1] for line in lines]
|
|
||||||
yield region, clip, lines
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _assemble_chops(
|
|
||||||
cls, chops: list[dict[int, list[Segment] | None]]
|
|
||||||
) -> Iterable[Iterable[Segment]]:
|
|
||||||
|
|
||||||
from_iterable = chain.from_iterable
|
|
||||||
for bucket in chops:
|
|
||||||
yield from_iterable(
|
|
||||||
line for _, line in sorted(bucket.items()) if line is not None
|
|
||||||
)
|
|
||||||
|
|
||||||
def render(
|
|
||||||
self,
|
|
||||||
console: Console,
|
|
||||||
*,
|
|
||||||
crop: Region = None,
|
|
||||||
) -> SegmentLines:
|
|
||||||
"""Render a layout.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
console (Console): Console instance.
|
|
||||||
clip (Optional[Region]): Region to clip to.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
SegmentLines: A renderable
|
|
||||||
"""
|
|
||||||
width = self.width
|
|
||||||
height = self.height
|
|
||||||
screen = Region(0, 0, width, height)
|
|
||||||
|
|
||||||
crop_region = crop.intersection(screen) if crop else screen
|
|
||||||
|
|
||||||
_Segment = Segment
|
|
||||||
divide = _Segment.divide
|
|
||||||
|
|
||||||
# Maps each cut on to a list of segments
|
|
||||||
cuts = self.cuts
|
|
||||||
chops: list[dict[int, list[Segment] | None]] = [
|
|
||||||
{cut: None for cut in cut_set} for cut_set in cuts
|
|
||||||
]
|
|
||||||
|
|
||||||
# TODO: Provide an option to update the background
|
|
||||||
background_style = console.get_style(self.background)
|
|
||||||
background_render = [
|
|
||||||
[_Segment(" " * width, background_style)] for _ in range(height)
|
|
||||||
]
|
|
||||||
# Go through all the renders in reverse order and fill buckets with no render
|
|
||||||
renders = list(self._get_renders(console))
|
|
||||||
|
|
||||||
for region, clip, lines in chain(
|
|
||||||
renders, [(screen, screen, background_render)]
|
|
||||||
):
|
|
||||||
render_region = region.intersection(clip)
|
|
||||||
for y, line in zip(render_region.y_range, lines):
|
|
||||||
|
|
||||||
first_cut, last_cut = render_region.x_extents
|
|
||||||
final_cuts = [cut for cut in cuts[y] if (last_cut >= cut >= first_cut)]
|
|
||||||
|
|
||||||
if len(final_cuts) == 2:
|
|
||||||
cut_segments = [line]
|
|
||||||
else:
|
|
||||||
render_x = render_region.x
|
|
||||||
relative_cuts = [cut - render_x for cut in final_cuts]
|
|
||||||
_, *cut_segments = divide(line, relative_cuts)
|
|
||||||
for cut, segments in zip(final_cuts, cut_segments):
|
|
||||||
if chops[y][cut] is None:
|
|
||||||
chops[y][cut] = segments
|
|
||||||
|
|
||||||
# Assemble the cut renders in to lists of segments
|
|
||||||
crop_x, crop_y, crop_x2, crop_y2 = crop_region.corners
|
|
||||||
output_lines = self._assemble_chops(chops[crop_y:crop_y2])
|
|
||||||
|
|
||||||
def width_view(line: list[Segment]) -> list[Segment]:
|
|
||||||
if line:
|
|
||||||
div_lines = list(divide(line, [crop_x, crop_x2]))
|
|
||||||
line = div_lines[1] if len(div_lines) > 1 else div_lines[0]
|
|
||||||
return line
|
|
||||||
|
|
||||||
if crop is not None and (crop_x, crop_x2) != (0, self.width):
|
|
||||||
render_lines = [width_view(line) for line in output_lines]
|
|
||||||
else:
|
|
||||||
render_lines = list(output_lines)
|
|
||||||
|
|
||||||
return SegmentLines(render_lines, new_lines=True)
|
|
||||||
|
|
||||||
def __rich_console__(
|
|
||||||
self, console: Console, options: ConsoleOptions
|
|
||||||
) -> RenderResult:
|
|
||||||
yield self.render(console)
|
|
||||||
|
|
||||||
def update_widget(self, console: Console, widget: Widget) -> LayoutUpdate | None:
|
|
||||||
if widget not in self.regions:
|
|
||||||
return None
|
|
||||||
|
|
||||||
region, clip = self.regions[widget]
|
|
||||||
|
|
||||||
if not region.size:
|
|
||||||
return None
|
|
||||||
|
|
||||||
widget.clear_render_cache()
|
|
||||||
|
|
||||||
update_region = region.intersection(clip)
|
|
||||||
update_lines = self.render(console, crop=update_region).lines
|
|
||||||
update = LayoutUpdate(update_lines, update_region)
|
|
||||||
log(update)
|
|
||||||
return update
|
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
"""
|
||||||
|
|
||||||
|
Planned for deprecation
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
||||||
@@ -10,12 +16,16 @@ from .widget import Widget
|
|||||||
|
|
||||||
|
|
||||||
class RenderRegion(NamedTuple):
|
class RenderRegion(NamedTuple):
|
||||||
|
"""Defines the absolute location of a Widget."""
|
||||||
|
|
||||||
region: Region
|
region: Region
|
||||||
order: tuple[int, ...]
|
order: tuple[int, ...]
|
||||||
clip: Region
|
clip: Region
|
||||||
|
|
||||||
|
|
||||||
class LayoutMap:
|
class LayoutMap:
|
||||||
|
"""A container that maps widgets on to their absolute location."""
|
||||||
|
|
||||||
def __init__(self, size: Size) -> None:
|
def __init__(self, size: Size) -> None:
|
||||||
self.size = size
|
self.size = size
|
||||||
self.widgets: dict[Widget, RenderRegion] = {}
|
self.widgets: dict[Widget, RenderRegion] = {}
|
||||||
@@ -53,6 +63,8 @@ class LayoutMap:
|
|||||||
|
|
||||||
self.widgets[widget] = RenderRegion(region + layout_offset, order, clip)
|
self.widgets[widget] = RenderRegion(region + layout_offset, order, clip)
|
||||||
|
|
||||||
|
# TODO: replace with widget.layout
|
||||||
|
|
||||||
if isinstance(widget, View):
|
if isinstance(widget, View):
|
||||||
view: View = widget
|
view: View = widget
|
||||||
scroll = view.scroll
|
scroll = view.scroll
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|||||||
import sys
|
import sys
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Iterable, TYPE_CHECKING, NamedTuple, Sequence
|
from typing import Generator, Iterable, TYPE_CHECKING, NamedTuple, Sequence
|
||||||
|
|
||||||
from .._layout_resolve import layout_resolve
|
from .._layout_resolve import layout_resolve
|
||||||
from ..css.types import Edge
|
from ..css.types import Edge
|
||||||
@@ -47,31 +47,27 @@ class DockLayout(Layout):
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<DockLayout>"
|
return "<DockLayout>"
|
||||||
|
|
||||||
def get_docks(self, view: View) -> list[Dock]:
|
def get_docks(self, parent: Widget) -> list[Dock]:
|
||||||
groups: dict[str, list[Widget]] = defaultdict(list)
|
groups: dict[str, list[Widget]] = defaultdict(list)
|
||||||
for child in view.children:
|
for child in parent.children:
|
||||||
assert isinstance(child, Widget)
|
assert isinstance(child, Widget)
|
||||||
if child.display:
|
if child.display:
|
||||||
groups[child.styles.dock].append(child)
|
groups[child.styles.dock].append(child)
|
||||||
docks: list[Dock] = []
|
docks: list[Dock] = []
|
||||||
append_dock = docks.append
|
append_dock = docks.append
|
||||||
for name, edge, z in view.styles.docks:
|
for name, edge, z in parent.styles.docks:
|
||||||
append_dock(Dock(edge, groups[name], z))
|
append_dock(Dock(edge, groups[name], z))
|
||||||
return docks
|
return docks
|
||||||
|
|
||||||
def get_widgets(self, view: View) -> Iterable[Widget]:
|
|
||||||
for dock in self.get_docks(view):
|
|
||||||
yield from dock.widgets
|
|
||||||
|
|
||||||
def arrange(
|
def arrange(
|
||||||
self, view: View, size: Size, scroll: Offset
|
self, parent: Widget, size: Size, scroll: Offset
|
||||||
) -> Iterable[WidgetPlacement]:
|
) -> Generator[WidgetPlacement, None, set[Widget]]:
|
||||||
|
|
||||||
width, height = size
|
width, height = size
|
||||||
layout_region = Region(0, 0, width, height)
|
layout_region = Region(0, 0, width, height)
|
||||||
layers: dict[int, Region] = defaultdict(lambda: layout_region)
|
layers: dict[int, Region] = defaultdict(lambda: layout_region)
|
||||||
|
|
||||||
docks = self.get_docks(view)
|
docks = self.get_docks(parent)
|
||||||
|
|
||||||
def make_dock_options(widget, edge: Edge) -> DockOptions:
|
def make_dock_options(widget, edge: Edge) -> DockOptions:
|
||||||
styles = widget.styles
|
styles = widget.styles
|
||||||
@@ -93,11 +89,14 @@ class DockLayout(Layout):
|
|||||||
|
|
||||||
Placement = WidgetPlacement
|
Placement = WidgetPlacement
|
||||||
|
|
||||||
|
arranged_widgets: set[Widget] = set()
|
||||||
|
|
||||||
for edge, widgets, z in docks:
|
for edge, widgets, z in docks:
|
||||||
|
|
||||||
|
arranged_widgets.update(widgets)
|
||||||
dock_options = [make_dock_options(widget, edge) for widget in widgets]
|
dock_options = [make_dock_options(widget, edge) for widget in widgets]
|
||||||
region = layers[z]
|
region = layers[z]
|
||||||
if not region:
|
if not region.area:
|
||||||
# No space left
|
# No space left
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -168,3 +167,5 @@ class DockLayout(Layout):
|
|||||||
region = Region(x, y, width - total, height)
|
region = Region(x, y, width - total, height)
|
||||||
|
|
||||||
layers[z] = region
|
layers[z] = region
|
||||||
|
|
||||||
|
return arranged_widgets
|
||||||
|
|||||||
@@ -1,250 +1,17 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Callable, Iterable
|
from .widget import Widget
|
||||||
|
|
||||||
import rich.repr
|
import rich.repr
|
||||||
from rich.console import RenderableType
|
|
||||||
from rich.style import Style
|
|
||||||
|
|
||||||
from . import errors, events, messages
|
|
||||||
from .geometry import Size, Offset, Region
|
|
||||||
from .layout import Layout, NoWidget, WidgetPlacement
|
|
||||||
from .reactive import Reactive, watch
|
|
||||||
from .widget import Widget
|
|
||||||
|
|
||||||
|
|
||||||
@rich.repr.auto
|
@rich.repr.auto
|
||||||
class View(Widget):
|
class View(Widget):
|
||||||
|
"""A widget for the root of the app."""
|
||||||
|
|
||||||
DEFAULT_STYLES = """
|
DEFAULT_STYLES = """
|
||||||
layout: dock;
|
|
||||||
|
layout: dock
|
||||||
docks: _default=top;
|
docks: _default=top;
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, name: str | None = None, id: str | None = None) -> None:
|
|
||||||
self.mouse_over: Widget | None = None
|
|
||||||
self._mouse_style: Style = Style()
|
|
||||||
self._mouse_widget: Widget | None = None
|
|
||||||
|
|
||||||
self._cached_arrangement: tuple[Size, Offset, list[WidgetPlacement]] = (
|
|
||||||
Size(),
|
|
||||||
Offset(),
|
|
||||||
[],
|
|
||||||
)
|
|
||||||
super().__init__(name=name, id=id)
|
|
||||||
|
|
||||||
background: Reactive[str] = Reactive("")
|
|
||||||
scroll_x: Reactive[int] = Reactive(0)
|
|
||||||
scroll_y: Reactive[int] = Reactive(0)
|
|
||||||
virtual_size = Reactive(Size(0, 0))
|
|
||||||
|
|
||||||
async def watch_background(self, value: str) -> None:
|
|
||||||
self.layout.background = value
|
|
||||||
self.app.refresh()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def layout(self) -> Layout | None:
|
|
||||||
"""Convenience property for accessing ``self.styles.layout``.
|
|
||||||
|
|
||||||
Returns: The Layout associated with this view
|
|
||||||
"""
|
|
||||||
|
|
||||||
return self.styles.layout
|
|
||||||
|
|
||||||
@layout.setter
|
|
||||||
def layout(self, new_value: Layout) -> None:
|
|
||||||
"""Convenience property setter for setting ``view.styles.layout``.
|
|
||||||
Args:
|
|
||||||
new_value:
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
None
|
|
||||||
"""
|
|
||||||
self.styles.layout = new_value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def scroll(self) -> Offset:
|
|
||||||
return Offset(self.scroll_x, self.scroll_y)
|
|
||||||
|
|
||||||
def __rich_repr__(self) -> rich.repr.Result:
|
|
||||||
yield "name", self.name
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_visual(self) -> bool:
|
|
||||||
return False
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_root_view(self) -> bool:
|
|
||||||
return bool(self._parent and self.parent is self.app)
|
|
||||||
|
|
||||||
def is_mounted(self, widget: Widget) -> bool:
|
|
||||||
return self.app.is_mounted(widget)
|
|
||||||
|
|
||||||
def render(self) -> RenderableType:
|
|
||||||
return self.layout or ""
|
|
||||||
|
|
||||||
def get_offset(self, widget: Widget) -> Offset:
|
|
||||||
return self.layout.get_offset(widget)
|
|
||||||
|
|
||||||
def get_arrangement(self, size: Size, scroll: Offset) -> Iterable[WidgetPlacement]:
|
|
||||||
cached_size, cached_scroll, arrangement = self._cached_arrangement
|
|
||||||
if cached_size == size and cached_scroll == scroll:
|
|
||||||
return arrangement
|
|
||||||
|
|
||||||
placements = [
|
|
||||||
placement.apply_margin()
|
|
||||||
for placement in self.layout.arrange(self, size, scroll)
|
|
||||||
]
|
|
||||||
|
|
||||||
self._cached_arrangement = (size, scroll, placements)
|
|
||||||
return placements
|
|
||||||
|
|
||||||
async def handle_update(self, message: messages.Update) -> None:
|
|
||||||
if self.is_root_view:
|
|
||||||
message.stop()
|
|
||||||
widget = message.widget
|
|
||||||
assert isinstance(widget, Widget)
|
|
||||||
|
|
||||||
display_update = self.layout.update_widget(self.console, widget)
|
|
||||||
if display_update is not None:
|
|
||||||
self.app.display(display_update)
|
|
||||||
|
|
||||||
async def handle_layout(self, message: messages.Layout) -> None:
|
|
||||||
await self.refresh_layout()
|
|
||||||
if self.is_root_view:
|
|
||||||
message.stop()
|
|
||||||
self.app.refresh()
|
|
||||||
|
|
||||||
def mount(self, *anon_widgets: Widget, **widgets: Widget) -> None:
|
|
||||||
self.app.register(self, *anon_widgets, **widgets)
|
|
||||||
self.refresh()
|
|
||||||
|
|
||||||
async def refresh_layout(self) -> None:
|
|
||||||
self._cached_arrangement = (Size(), Offset(), [])
|
|
||||||
try:
|
|
||||||
await self.layout.mount_all(self)
|
|
||||||
if not self.is_root_view:
|
|
||||||
await self.app.view.refresh_layout()
|
|
||||||
return
|
|
||||||
|
|
||||||
if not self.size:
|
|
||||||
return
|
|
||||||
|
|
||||||
hidden, shown, resized = self.layout.reflow(self, Size(*self.console.size))
|
|
||||||
assert self.layout.map is not None
|
|
||||||
|
|
||||||
for widget in hidden:
|
|
||||||
widget.post_message_no_wait(events.Hide(self))
|
|
||||||
for widget in shown:
|
|
||||||
widget.post_message_no_wait(events.Show(self))
|
|
||||||
|
|
||||||
send_resize = shown
|
|
||||||
send_resize.update(resized)
|
|
||||||
|
|
||||||
for widget, region, unclipped_region in self.layout:
|
|
||||||
widget._update_size(unclipped_region.size)
|
|
||||||
if widget in send_resize:
|
|
||||||
widget.post_message_no_wait(
|
|
||||||
events.Resize(self, unclipped_region.size)
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
self.app.panic()
|
|
||||||
|
|
||||||
async def on_resize(self, event: events.Resize) -> None:
|
|
||||||
self._update_size(event.size)
|
|
||||||
if self.is_root_view:
|
|
||||||
await self.refresh_layout()
|
|
||||||
self.app.refresh()
|
|
||||||
event.stop()
|
|
||||||
|
|
||||||
def get_widget_at(self, x: int, y: int) -> tuple[Widget, Region]:
|
|
||||||
return self.layout.get_widget_at(x, y)
|
|
||||||
|
|
||||||
def get_style_at(self, x: int, y: int) -> Style:
|
|
||||||
return self.layout.get_style_at(x, y)
|
|
||||||
|
|
||||||
def get_widget_region(self, widget: Widget) -> Region:
|
|
||||||
return self.layout.get_widget_region(widget)
|
|
||||||
|
|
||||||
async def on_mount(self, event: events.Mount) -> None:
|
|
||||||
async def watch_background(value: str) -> None:
|
|
||||||
self.background = value
|
|
||||||
|
|
||||||
watch(self.app, "background", watch_background)
|
|
||||||
|
|
||||||
async def on_idle(self, event: events.Idle) -> None:
|
|
||||||
if self.layout is None:
|
|
||||||
return
|
|
||||||
if self.layout.check_update():
|
|
||||||
self.layout.reset_update()
|
|
||||||
await self.refresh_layout()
|
|
||||||
|
|
||||||
async def _on_mouse_move(self, event: events.MouseMove) -> None:
|
|
||||||
|
|
||||||
try:
|
|
||||||
if self.app.mouse_captured:
|
|
||||||
widget = self.app.mouse_captured
|
|
||||||
region = self.get_widget_region(widget)
|
|
||||||
else:
|
|
||||||
widget, region = self.get_widget_at(event.x, event.y)
|
|
||||||
except NoWidget:
|
|
||||||
await self.app.set_mouse_over(None)
|
|
||||||
else:
|
|
||||||
await self.app.set_mouse_over(widget)
|
|
||||||
await widget.forward_event(
|
|
||||||
events.MouseMove(
|
|
||||||
self,
|
|
||||||
event.x - region.x,
|
|
||||||
event.y - region.y,
|
|
||||||
event.delta_x,
|
|
||||||
event.delta_y,
|
|
||||||
event.button,
|
|
||||||
event.shift,
|
|
||||||
event.meta,
|
|
||||||
event.ctrl,
|
|
||||||
screen_x=event.screen_x,
|
|
||||||
screen_y=event.screen_y,
|
|
||||||
style=event.style,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
async def forward_event(self, event: events.Event) -> None:
|
|
||||||
event.set_forwarded()
|
|
||||||
if isinstance(event, (events.Enter, events.Leave)):
|
|
||||||
await self.post_message(event)
|
|
||||||
|
|
||||||
elif isinstance(event, events.MouseMove):
|
|
||||||
event.style = self.get_style_at(event.screen_x, event.screen_y)
|
|
||||||
await self._on_mouse_move(event)
|
|
||||||
|
|
||||||
elif isinstance(event, events.MouseEvent):
|
|
||||||
try:
|
|
||||||
if self.app.mouse_captured:
|
|
||||||
widget = self.app.mouse_captured
|
|
||||||
region = self.get_widget_region(widget)
|
|
||||||
else:
|
|
||||||
widget, region = self.get_widget_at(event.x, event.y)
|
|
||||||
except NoWidget:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
if isinstance(event, events.MouseDown) and widget.can_focus:
|
|
||||||
await self.app.set_focus(widget)
|
|
||||||
event.style = self.get_style_at(event.screen_x, event.screen_y)
|
|
||||||
await widget.forward_event(event.offset(-region.x, -region.y))
|
|
||||||
|
|
||||||
elif isinstance(event, (events.MouseScrollDown, events.MouseScrollUp)):
|
|
||||||
try:
|
|
||||||
widget, _region = self.get_widget_at(event.x, event.y)
|
|
||||||
except NoWidget:
|
|
||||||
return
|
|
||||||
scroll_widget = widget
|
|
||||||
if scroll_widget is not None:
|
|
||||||
await scroll_widget.forward_event(event)
|
|
||||||
else:
|
|
||||||
self.log("view.forwarded", event)
|
|
||||||
await self.post_message(event)
|
|
||||||
|
|
||||||
async def action_toggle(self, name: str) -> None:
|
|
||||||
widget = self[name]
|
|
||||||
widget.visible = not widget.display
|
|
||||||
await self.post_message(messages.Layout(self))
|
|
||||||
|
|||||||
253
src/textual/viewX.py
Normal file
253
src/textual/viewX.py
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Callable, Iterable
|
||||||
|
|
||||||
|
import rich.repr
|
||||||
|
from rich.console import RenderableType
|
||||||
|
from rich.style import Style
|
||||||
|
|
||||||
|
from . import errors, events, messages
|
||||||
|
from ._arrangement import Arrangement
|
||||||
|
from .geometry import Size, Offset, Region
|
||||||
|
from .layout import Layout, NoWidget, WidgetPlacement
|
||||||
|
from .reactive import Reactive, watch
|
||||||
|
from .widget import Widget
|
||||||
|
|
||||||
|
|
||||||
|
@rich.repr.auto
|
||||||
|
class View(Widget):
|
||||||
|
|
||||||
|
DEFAULT_STYLES = """
|
||||||
|
layout: dock;
|
||||||
|
docks: _default=top;
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, name: str | None = None, id: str | None = None) -> None:
|
||||||
|
self.mouse_over: Widget | None = None
|
||||||
|
self._mouse_style: Style = Style()
|
||||||
|
self._mouse_widget: Widget | None = None
|
||||||
|
|
||||||
|
self._arrangement = Arrangement()
|
||||||
|
|
||||||
|
self._cached_arrangement: tuple[Size, Offset, list[WidgetPlacement]] = (
|
||||||
|
Size(),
|
||||||
|
Offset(),
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
super().__init__(name=name, id=id)
|
||||||
|
|
||||||
|
background: Reactive[str] = Reactive("")
|
||||||
|
scroll_x: Reactive[int] = Reactive(0)
|
||||||
|
scroll_y: Reactive[int] = Reactive(0)
|
||||||
|
virtual_size = Reactive(Size(0, 0))
|
||||||
|
|
||||||
|
async def watch_background(self, value: str) -> None:
|
||||||
|
self._arrangement.background = value
|
||||||
|
self.app.refresh()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def layout(self) -> Layout | None:
|
||||||
|
"""Convenience property for accessing ``self.styles.layout``.
|
||||||
|
|
||||||
|
Returns: The Layout associated with this view
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self.styles.layout
|
||||||
|
|
||||||
|
@layout.setter
|
||||||
|
def layout(self, new_value: Layout) -> None:
|
||||||
|
"""Convenience property setter for setting ``view.styles.layout``.
|
||||||
|
Args:
|
||||||
|
new_value:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
self.styles.layout = new_value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def scroll(self) -> Offset:
|
||||||
|
return Offset(self.scroll_x, self.scroll_y)
|
||||||
|
|
||||||
|
def __rich_repr__(self) -> rich.repr.Result:
|
||||||
|
yield "name", self.name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_visual(self) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_root_view(self) -> bool:
|
||||||
|
return bool(self._parent and self.parent is self.app)
|
||||||
|
|
||||||
|
def is_mounted(self, widget: Widget) -> bool:
|
||||||
|
return self.app.is_mounted(widget)
|
||||||
|
|
||||||
|
def render(self) -> RenderableType:
|
||||||
|
return self._arrangement
|
||||||
|
|
||||||
|
def get_offset(self, widget: Widget) -> Offset:
|
||||||
|
return self._arrangement.get_offset(widget)
|
||||||
|
|
||||||
|
def get_arrangement(self, size: Size, scroll: Offset) -> Iterable[WidgetPlacement]:
|
||||||
|
cached_size, cached_scroll, arrangement = self._cached_arrangement
|
||||||
|
if cached_size == size and cached_scroll == scroll:
|
||||||
|
return arrangement
|
||||||
|
|
||||||
|
placements = [
|
||||||
|
placement.apply_margin()
|
||||||
|
for placement in self.layout.arrange(self, size, scroll)
|
||||||
|
]
|
||||||
|
|
||||||
|
self._cached_arrangement = (size, scroll, placements)
|
||||||
|
return placements
|
||||||
|
|
||||||
|
async def handle_update(self, message: messages.Update) -> None:
|
||||||
|
if self.is_root_view:
|
||||||
|
message.stop()
|
||||||
|
widget = message.widget
|
||||||
|
assert isinstance(widget, Widget)
|
||||||
|
|
||||||
|
display_update = self.layout.update_widget(self.console, widget)
|
||||||
|
if display_update is not None:
|
||||||
|
self.app.display(display_update)
|
||||||
|
|
||||||
|
async def handle_layout(self, message: messages.Layout) -> None:
|
||||||
|
await self.refresh_layout()
|
||||||
|
if self.is_root_view:
|
||||||
|
message.stop()
|
||||||
|
self.app.refresh()
|
||||||
|
|
||||||
|
def mount(self, *anon_widgets: Widget, **widgets: Widget) -> None:
|
||||||
|
self.app.register(self, *anon_widgets, **widgets)
|
||||||
|
self.refresh()
|
||||||
|
|
||||||
|
async def refresh_layout(self) -> None:
|
||||||
|
self._cached_arrangement = (Size(), Offset(), [])
|
||||||
|
try:
|
||||||
|
await self.layout.mount_all(self)
|
||||||
|
if not self.is_root_view:
|
||||||
|
await self.app.view.refresh_layout()
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self.size:
|
||||||
|
return
|
||||||
|
|
||||||
|
hidden, shown, resized = self.layout.reflow(self, Size(*self.console.size))
|
||||||
|
assert self.layout.map is not None
|
||||||
|
|
||||||
|
for widget in hidden:
|
||||||
|
widget.post_message_no_wait(events.Hide(self))
|
||||||
|
for widget in shown:
|
||||||
|
widget.post_message_no_wait(events.Show(self))
|
||||||
|
|
||||||
|
send_resize = shown
|
||||||
|
send_resize.update(resized)
|
||||||
|
|
||||||
|
for widget, region, unclipped_region in self.layout:
|
||||||
|
widget._update_size(unclipped_region.size)
|
||||||
|
if widget in send_resize:
|
||||||
|
widget.post_message_no_wait(
|
||||||
|
events.Resize(self, unclipped_region.size)
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
self.app.panic()
|
||||||
|
|
||||||
|
async def on_resize(self, event: events.Resize) -> None:
|
||||||
|
self._update_size(event.size)
|
||||||
|
if self.is_root_view:
|
||||||
|
await self.refresh_layout()
|
||||||
|
self.app.refresh()
|
||||||
|
event.stop()
|
||||||
|
|
||||||
|
def get_widget_at(self, x: int, y: int) -> tuple[Widget, Region]:
|
||||||
|
return self.layout.get_widget_at(x, y)
|
||||||
|
|
||||||
|
def get_style_at(self, x: int, y: int) -> Style:
|
||||||
|
return self.layout.get_style_at(x, y)
|
||||||
|
|
||||||
|
def get_widget_region(self, widget: Widget) -> Region:
|
||||||
|
return self.layout.get_widget_region(widget)
|
||||||
|
|
||||||
|
async def on_mount(self, event: events.Mount) -> None:
|
||||||
|
async def watch_background(value: str) -> None:
|
||||||
|
self.background = value
|
||||||
|
|
||||||
|
watch(self.app, "background", watch_background)
|
||||||
|
|
||||||
|
async def on_idle(self, event: events.Idle) -> None:
|
||||||
|
if self.layout is None:
|
||||||
|
return
|
||||||
|
if self.layout.check_update():
|
||||||
|
self.layout.reset_update()
|
||||||
|
await self.refresh_layout()
|
||||||
|
|
||||||
|
async def _on_mouse_move(self, event: events.MouseMove) -> None:
|
||||||
|
|
||||||
|
try:
|
||||||
|
if self.app.mouse_captured:
|
||||||
|
widget = self.app.mouse_captured
|
||||||
|
region = self.get_widget_region(widget)
|
||||||
|
else:
|
||||||
|
widget, region = self.get_widget_at(event.x, event.y)
|
||||||
|
except NoWidget:
|
||||||
|
await self.app.set_mouse_over(None)
|
||||||
|
else:
|
||||||
|
await self.app.set_mouse_over(widget)
|
||||||
|
await widget.forward_event(
|
||||||
|
events.MouseMove(
|
||||||
|
self,
|
||||||
|
event.x - region.x,
|
||||||
|
event.y - region.y,
|
||||||
|
event.delta_x,
|
||||||
|
event.delta_y,
|
||||||
|
event.button,
|
||||||
|
event.shift,
|
||||||
|
event.meta,
|
||||||
|
event.ctrl,
|
||||||
|
screen_x=event.screen_x,
|
||||||
|
screen_y=event.screen_y,
|
||||||
|
style=event.style,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
async def forward_event(self, event: events.Event) -> None:
|
||||||
|
event.set_forwarded()
|
||||||
|
if isinstance(event, (events.Enter, events.Leave)):
|
||||||
|
await self.post_message(event)
|
||||||
|
|
||||||
|
elif isinstance(event, events.MouseMove):
|
||||||
|
event.style = self.get_style_at(event.screen_x, event.screen_y)
|
||||||
|
await self._on_mouse_move(event)
|
||||||
|
|
||||||
|
elif isinstance(event, events.MouseEvent):
|
||||||
|
try:
|
||||||
|
if self.app.mouse_captured:
|
||||||
|
widget = self.app.mouse_captured
|
||||||
|
region = self.get_widget_region(widget)
|
||||||
|
else:
|
||||||
|
widget, region = self.get_widget_at(event.x, event.y)
|
||||||
|
except NoWidget:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if isinstance(event, events.MouseDown) and widget.can_focus:
|
||||||
|
await self.app.set_focus(widget)
|
||||||
|
event.style = self.get_style_at(event.screen_x, event.screen_y)
|
||||||
|
await widget.forward_event(event.offset(-region.x, -region.y))
|
||||||
|
|
||||||
|
elif isinstance(event, (events.MouseScrollDown, events.MouseScrollUp)):
|
||||||
|
try:
|
||||||
|
widget, _region = self.get_widget_at(event.x, event.y)
|
||||||
|
except NoWidget:
|
||||||
|
return
|
||||||
|
scroll_widget = widget
|
||||||
|
if scroll_widget is not None:
|
||||||
|
await scroll_widget.forward_event(event)
|
||||||
|
else:
|
||||||
|
self.log("view.forwarded", event)
|
||||||
|
await self.post_message(event)
|
||||||
|
|
||||||
|
async def action_toggle(self, name: str) -> None:
|
||||||
|
widget = self[name]
|
||||||
|
widget.visible = not widget.display
|
||||||
|
await self.post_message(messages.Layout(self))
|
||||||
@@ -16,6 +16,7 @@ import rich.repr
|
|||||||
from rich.align import Align
|
from rich.align import Align
|
||||||
from rich.console import Console, RenderableType
|
from rich.console import Console, RenderableType
|
||||||
from rich.padding import Padding
|
from rich.padding import Padding
|
||||||
|
from rich.pretty import Pretty
|
||||||
from rich.style import Style
|
from rich.style import Style
|
||||||
from rich.styled import Styled
|
from rich.styled import Styled
|
||||||
from rich.text import Text
|
from rich.text import Text
|
||||||
@@ -28,10 +29,11 @@ from ._callback import invoke
|
|||||||
from ._context import active_app
|
from ._context import active_app
|
||||||
from ._types import Lines
|
from ._types import Lines
|
||||||
from .dom import DOMNode
|
from .dom import DOMNode
|
||||||
from .geometry import Size, Spacing
|
from .geometry import Offset, Size
|
||||||
from .message import Message
|
from .message import Message
|
||||||
from .messages import Layout, Update
|
from . import messages
|
||||||
from .reactive import watch
|
from .layout import Layout
|
||||||
|
from .reactive import Reactive, watch
|
||||||
from .renderables.opacity import Opacity
|
from .renderables.opacity import Opacity
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -79,6 +81,10 @@ class Widget(DOMNode):
|
|||||||
|
|
||||||
super().__init__(name=name, id=id)
|
super().__init__(name=name, id=id)
|
||||||
|
|
||||||
|
scroll_x = Reactive(0)
|
||||||
|
scroll_y = Reactive(0)
|
||||||
|
virtual_size = Reactive(Size(0, 0))
|
||||||
|
|
||||||
def __init_subclass__(cls, can_focus: bool = True) -> None:
|
def __init_subclass__(cls, can_focus: bool = True) -> None:
|
||||||
super().__init_subclass__()
|
super().__init_subclass__()
|
||||||
cls.can_focus = can_focus
|
cls.can_focus = can_focus
|
||||||
@@ -145,6 +151,10 @@ class Widget(DOMNode):
|
|||||||
def size(self) -> Size:
|
def size(self) -> Size:
|
||||||
return self._size
|
return self._size
|
||||||
|
|
||||||
|
@property
|
||||||
|
def scroll(self) -> Offset:
|
||||||
|
return Offset(self.scroll_x, self.scroll_y)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_visual(self) -> bool:
|
def is_visual(self) -> bool:
|
||||||
return True
|
return True
|
||||||
@@ -166,6 +176,10 @@ class Widget(DOMNode):
|
|||||||
assert self._animate is not None
|
assert self._animate is not None
|
||||||
return self._animate
|
return self._animate
|
||||||
|
|
||||||
|
@property
|
||||||
|
def layout(self) -> Layout | None:
|
||||||
|
return self.styles.layout
|
||||||
|
|
||||||
def on_style_change(self) -> None:
|
def on_style_change(self) -> None:
|
||||||
self.clear_render_cache()
|
self.clear_render_cache()
|
||||||
|
|
||||||
@@ -237,7 +251,11 @@ class Widget(DOMNode):
|
|||||||
Returns:
|
Returns:
|
||||||
RenderableType: Any renderable
|
RenderableType: Any renderable
|
||||||
"""
|
"""
|
||||||
return Align.center(Text(f"#{self.id}"), vertical="middle")
|
|
||||||
|
# Default displays a pretty repr in the center of the screen
|
||||||
|
return Align.center(
|
||||||
|
Pretty(self, no_wrap=True, overflow="ellipsis"), vertical="middle"
|
||||||
|
)
|
||||||
|
|
||||||
async def action(self, action: str, *params) -> None:
|
async def action(self, action: str, *params) -> None:
|
||||||
await self.app.action(action, self)
|
await self.app.action(action, self)
|
||||||
@@ -258,11 +276,11 @@ class Widget(DOMNode):
|
|||||||
# self.render_cache = None
|
# self.render_cache = None
|
||||||
self.reset_check_repaint()
|
self.reset_check_repaint()
|
||||||
self.reset_check_layout()
|
self.reset_check_layout()
|
||||||
await self.emit(Layout(self))
|
await self.emit(messages.Layout(self))
|
||||||
elif repaint or self.check_repaint():
|
elif repaint or self.check_repaint():
|
||||||
# self.render_cache = None
|
# self.render_cache = None
|
||||||
self.reset_check_repaint()
|
self.reset_check_repaint()
|
||||||
await self.emit(Update(self, self))
|
await self.emit(messages.Update(self, self))
|
||||||
|
|
||||||
async def focus(self) -> None:
|
async def focus(self) -> None:
|
||||||
await self.app.set_focus(self)
|
await self.app.set_focus(self)
|
||||||
|
|||||||
Reference in New Issue
Block a user