mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
compositor and cache optimizations
This commit is contained in:
21
examples/pride.py
Normal file
21
examples/pride.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
from textual.app import App
|
||||||
|
from textual.widgets import Static
|
||||||
|
|
||||||
|
|
||||||
|
class PrideApp(App):
|
||||||
|
"""Displays a pride flag."""
|
||||||
|
|
||||||
|
COLORS = ["red", "orange", "yellow", "green", "blue", "purple"]
|
||||||
|
|
||||||
|
def compose(self):
|
||||||
|
for color in self.COLORS:
|
||||||
|
stripe = Static()
|
||||||
|
stripe.styles.height = "1fr"
|
||||||
|
stripe.styles.background = color
|
||||||
|
yield stripe
|
||||||
|
|
||||||
|
|
||||||
|
app = PrideApp()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run()
|
||||||
32
poetry.lock
generated
32
poetry.lock
generated
@@ -345,7 +345,7 @@ mkdocs = ">=1.1"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mkdocs-material"
|
name = "mkdocs-material"
|
||||||
version = "8.3.2"
|
version = "8.3.3"
|
||||||
description = "Documentation that simply works"
|
description = "Documentation that simply works"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
@@ -533,7 +533,7 @@ python-versions = ">=3.6"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pymdown-extensions"
|
name = "pymdown-extensions"
|
||||||
version = "9.4"
|
version = "9.5"
|
||||||
description = "Extension pack for Python Markdown."
|
description = "Extension pack for Python Markdown."
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
@@ -658,21 +658,16 @@ version = "12.4.4"
|
|||||||
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
|
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "^3.6.3"
|
python-versions = ">=3.6.3,<4.0.0"
|
||||||
develop = true
|
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
commonmark = "^0.9.0"
|
commonmark = ">=0.9.0,<0.10.0"
|
||||||
pygments = "^2.6.0"
|
pygments = ">=2.6.0,<3.0.0"
|
||||||
typing-extensions = {version = ">=4.0.0, <5.0", markers = "python_version < \"3.9\""}
|
typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""}
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"]
|
jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"]
|
||||||
|
|
||||||
[package.source]
|
|
||||||
type = "directory"
|
|
||||||
url = "../rich"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "six"
|
name = "six"
|
||||||
version = "1.16.0"
|
version = "1.16.0"
|
||||||
@@ -782,7 +777,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 = "4aeef009c7c1f6a34d0dd1c3c16647b53a339f5963dcbe31eef43bb9dac270ab"
|
content-hash = "378a60041202d505cba26ea7084886fe1b01090a0035253b100593b559aed090"
|
||||||
|
|
||||||
[metadata.files]
|
[metadata.files]
|
||||||
aiohttp = [
|
aiohttp = [
|
||||||
@@ -1127,8 +1122,8 @@ mkdocs-autorefs = [
|
|||||||
{file = "mkdocs_autorefs-0.4.1-py3-none-any.whl", hash = "sha256:a2248a9501b29dc0cc8ba4c09f4f47ff121945f6ce33d760f145d6f89d313f5b"},
|
{file = "mkdocs_autorefs-0.4.1-py3-none-any.whl", hash = "sha256:a2248a9501b29dc0cc8ba4c09f4f47ff121945f6ce33d760f145d6f89d313f5b"},
|
||||||
]
|
]
|
||||||
mkdocs-material = [
|
mkdocs-material = [
|
||||||
{file = "mkdocs-material-8.3.2.tar.gz", hash = "sha256:d203ba166063ccd0ff83893a54f46d98e2fb3961babd7c0176cb3f95ac62df20"},
|
{file = "mkdocs-material-8.3.3.tar.gz", hash = "sha256:3dd30af894f6d5da3d8a2f8ffc04c90c4d0f1be013e654ec45f608373c131542"},
|
||||||
{file = "mkdocs_material-8.3.2-py2.py3-none-any.whl", hash = "sha256:57d910ccb6d98d0d1281cbb34a9c8ae6d820b2f5caad1d1246d8ed49eec10a50"},
|
{file = "mkdocs_material-8.3.3-py2.py3-none-any.whl", hash = "sha256:4f9564af58f9c96f25c263cb705a40a82c833cb10c2626d6db6ddadedaa5b6c3"},
|
||||||
]
|
]
|
||||||
mkdocs-material-extensions = [
|
mkdocs-material-extensions = [
|
||||||
{file = "mkdocs-material-extensions-1.0.3.tar.gz", hash = "sha256:bfd24dfdef7b41c312ede42648f9eb83476ea168ec163b613f9abd12bbfddba2"},
|
{file = "mkdocs-material-extensions-1.0.3.tar.gz", hash = "sha256:bfd24dfdef7b41c312ede42648f9eb83476ea168ec163b613f9abd12bbfddba2"},
|
||||||
@@ -1319,8 +1314,8 @@ pygments = [
|
|||||||
{file = "Pygments-2.12.0.tar.gz", hash = "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb"},
|
{file = "Pygments-2.12.0.tar.gz", hash = "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb"},
|
||||||
]
|
]
|
||||||
pymdown-extensions = [
|
pymdown-extensions = [
|
||||||
{file = "pymdown_extensions-9.4-py3-none-any.whl", hash = "sha256:5b7432456bf555ce2b0ab3c2439401084cda8110f24f6b3ecef952b8313dfa1b"},
|
{file = "pymdown_extensions-9.5-py3-none-any.whl", hash = "sha256:ec141c0f4983755349f0c8710416348d1a13753976c028186ed14f190c8061c4"},
|
||||||
{file = "pymdown_extensions-9.4.tar.gz", hash = "sha256:1baa22a60550f731630474cad28feb0405c8101f1a7ddc3ec0ed86ee510bcc43"},
|
{file = "pymdown_extensions-9.5.tar.gz", hash = "sha256:3ef2d998c0d5fa7eb09291926d90d69391283561cf6306f85cd588a5eb5befa0"},
|
||||||
]
|
]
|
||||||
pyparsing = [
|
pyparsing = [
|
||||||
{file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
|
{file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
|
||||||
@@ -1386,7 +1381,10 @@ pyyaml-env-tag = [
|
|||||||
{file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"},
|
{file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"},
|
||||||
{file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"},
|
{file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"},
|
||||||
]
|
]
|
||||||
rich = []
|
rich = [
|
||||||
|
{file = "rich-12.4.4-py3-none-any.whl", hash = "sha256:d2bbd99c320a2532ac71ff6a3164867884357da3e3301f0240090c5d2fdac7ec"},
|
||||||
|
{file = "rich-12.4.4.tar.gz", hash = "sha256:4c586de507202505346f3e32d1363eb9ed6932f0c2f63184dea88983ff4971e2"},
|
||||||
|
]
|
||||||
six = [
|
six = [
|
||||||
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
|
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
|
||||||
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
|
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ textual = "textual.cli.cli:run"
|
|||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.7"
|
python = "^3.7"
|
||||||
#rich = "^12.4.3"
|
rich = "^12.4.3"
|
||||||
rich = {path="../rich", develop=true}
|
#rich = {path="../rich", develop=true}
|
||||||
click = "8.1.2"
|
click = "8.1.2"
|
||||||
importlib-metadata = "^4.11.3"
|
importlib-metadata = "^4.11.3"
|
||||||
typing-extensions = { version = "^4.0.0", python = "<3.8" }
|
typing-extensions = { version = "^4.0.0", python = "<3.8" }
|
||||||
|
|||||||
@@ -76,8 +76,8 @@ App > Screen {
|
|||||||
|
|
||||||
|
|
||||||
Tweet {
|
Tweet {
|
||||||
height: 12;
|
height:12;
|
||||||
width: 80;
|
width: 100%;
|
||||||
|
|
||||||
margin: 1 3;
|
margin: 1 3;
|
||||||
background: $panel;
|
background: $panel;
|
||||||
@@ -94,9 +94,9 @@ Tweet {
|
|||||||
|
|
||||||
|
|
||||||
.scrollable {
|
.scrollable {
|
||||||
width: 80;
|
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
max-width:80;
|
margin: 1 2;
|
||||||
height: 20;
|
height: 20;
|
||||||
align-horizontal: center;
|
align-horizontal: center;
|
||||||
layout: vertical;
|
layout: vertical;
|
||||||
@@ -105,7 +105,7 @@ Tweet {
|
|||||||
.code {
|
.code {
|
||||||
|
|
||||||
height: 34;
|
height: 34;
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,7 +189,7 @@ OptionItem:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Error {
|
Error {
|
||||||
width: 80;
|
width: 100%;
|
||||||
height:3;
|
height:3;
|
||||||
background: $error;
|
background: $error;
|
||||||
color: $text-error;
|
color: $text-error;
|
||||||
@@ -202,7 +202,7 @@ Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Warning {
|
Warning {
|
||||||
width: 80;
|
width: 100%;
|
||||||
height:3;
|
height:3;
|
||||||
background: $warning;
|
background: $warning;
|
||||||
color: $text-warning-fade-1;
|
color: $text-warning-fade-1;
|
||||||
@@ -214,7 +214,7 @@ Warning {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Success {
|
Success {
|
||||||
width: 80;
|
width: 100%;
|
||||||
height:3;
|
height:3;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
background: $success-lighten-3;
|
background: $success-lighten-3;
|
||||||
|
|||||||
@@ -117,6 +117,11 @@ class BasicApp(App, css_path="basic.css"):
|
|||||||
Warning(),
|
Warning(),
|
||||||
Tweet(TweetBody(), classes="scroll-horizontal"),
|
Tweet(TweetBody(), classes="scroll-horizontal"),
|
||||||
Success(),
|
Success(),
|
||||||
|
Tweet(TweetBody(), classes="scroll-horizontal"),
|
||||||
|
Tweet(TweetBody(), classes="scroll-horizontal"),
|
||||||
|
Tweet(TweetBody(), classes="scroll-horizontal"),
|
||||||
|
Tweet(TweetBody(), classes="scroll-horizontal"),
|
||||||
|
Tweet(TweetBody(), classes="scroll-horizontal"),
|
||||||
),
|
),
|
||||||
footer=Widget(),
|
footer=Widget(),
|
||||||
sidebar=Widget(
|
sidebar=Widget(
|
||||||
@@ -154,3 +159,15 @@ app = BasicApp()
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app.run()
|
app.run()
|
||||||
|
|
||||||
|
from textual.geometry import Region
|
||||||
|
from textual.color import Color
|
||||||
|
|
||||||
|
print(Region.intersection.cache_info())
|
||||||
|
print(Region.overlaps.cache_info())
|
||||||
|
print(Region.union.cache_info())
|
||||||
|
print(Region.split_vertical.cache_info())
|
||||||
|
print(Region.__contains__.cache_info())
|
||||||
|
from textual.css.scalar import Scalar
|
||||||
|
|
||||||
|
print(Scalar.resolve_dimension.cache_info())
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ class MapGeometry(NamedTuple):
|
|||||||
CompositorMap: TypeAlias = "dict[Widget, MapGeometry]"
|
CompositorMap: TypeAlias = "dict[Widget, MapGeometry]"
|
||||||
|
|
||||||
|
|
||||||
@rich.repr.auto
|
@rich.repr.auto(angular=True)
|
||||||
class LayoutUpdate:
|
class LayoutUpdate:
|
||||||
"""A renderable containing the result of a render for a given region."""
|
"""A renderable containing the result of a render for a given region."""
|
||||||
|
|
||||||
@@ -89,11 +89,7 @@ class LayoutUpdate:
|
|||||||
yield new_line
|
yield new_line
|
||||||
|
|
||||||
def __rich_repr__(self) -> rich.repr.Result:
|
def __rich_repr__(self) -> rich.repr.Result:
|
||||||
x, y, width, height = self.region
|
yield self.region
|
||||||
yield "x", x
|
|
||||||
yield "y", y
|
|
||||||
yield "width", width
|
|
||||||
yield "height", height
|
|
||||||
|
|
||||||
|
|
||||||
@rich.repr.auto(angular=True)
|
@rich.repr.auto(angular=True)
|
||||||
@@ -558,7 +554,10 @@ class Compositor:
|
|||||||
delta_y = new_y - region.y
|
delta_y = new_y - region.y
|
||||||
crop_x = delta_x + new_width
|
crop_x = delta_x + new_width
|
||||||
lines = widget.get_render_lines(delta_y, delta_y + new_height)
|
lines = widget.get_render_lines(delta_y, delta_y + new_height)
|
||||||
lines = [line_crop(line, delta_x, crop_x) for line in lines]
|
if (delta_x, crop_x) != (0, region.width):
|
||||||
|
lines = [
|
||||||
|
line_crop(line, delta_x, crop_x, region.width) for line in lines
|
||||||
|
]
|
||||||
yield region, clip, lines
|
yield region, clip, lines
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -600,7 +599,7 @@ class Compositor:
|
|||||||
is_rendered_line = lambda y: True
|
is_rendered_line = lambda y: True
|
||||||
elif update_regions:
|
elif update_regions:
|
||||||
# Create a crop regions that surrounds all updates
|
# Create a crop regions that surrounds all updates
|
||||||
crop = Region.from_union(list(update_regions)).intersection(screen_region)
|
crop = Region.from_union(update_regions).intersection(screen_region)
|
||||||
spans = list(self._regions_to_spans(update_regions))
|
spans = list(self._regions_to_spans(update_regions))
|
||||||
is_rendered_line = {y for y, _, _ in spans}.__contains__
|
is_rendered_line = {y for y, _, _ in spans}.__contains__
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -7,14 +7,16 @@ from __future__ import annotations
|
|||||||
from rich.segment import Segment
|
from rich.segment import Segment
|
||||||
|
|
||||||
|
|
||||||
def line_crop(segments: list[Segment], start: int, end: int) -> list[Segment]:
|
def line_crop(
|
||||||
|
segments: list[Segment], start: int, end: int, total: int
|
||||||
|
) -> list[Segment]:
|
||||||
"""Crops a list of segments between two cell offsets.
|
"""Crops a list of segments between two cell offsets.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
segments (list[Segment]): A list of Segments for a line.
|
segments (list[Segment]): A list of Segments for a line.
|
||||||
start (int): Start offset
|
start (int): Start offset
|
||||||
end (int): End offset
|
end (int): End offset
|
||||||
|
total (int): Total cell length of segments.
|
||||||
Returns:
|
Returns:
|
||||||
list[Segment]: A new shorter list of segments
|
list[Segment]: A new shorter list of segments
|
||||||
"""
|
"""
|
||||||
@@ -35,6 +37,13 @@ def line_crop(segments: list[Segment], start: int, end: int) -> list[Segment]:
|
|||||||
else:
|
else:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
if end >= total:
|
||||||
|
# The end crop is the end of the segments, so we can collect all remaining segments
|
||||||
|
if segment:
|
||||||
|
add_segment(segment)
|
||||||
|
output_segments.extend(iter_segments)
|
||||||
|
return output_segments
|
||||||
|
|
||||||
pos = start
|
pos = start
|
||||||
while segment is not None:
|
while segment is not None:
|
||||||
end_pos = pos + segment.cell_length
|
end_pos = pos + segment.cell_length
|
||||||
|
|||||||
@@ -231,7 +231,6 @@ class Color(NamedTuple):
|
|||||||
r, g, b, _ = self
|
r, g, b, _ = self
|
||||||
return Color(r, g, b, alpha)
|
return Color(r, g, b, alpha)
|
||||||
|
|
||||||
@lru_cache(maxsize=2048)
|
|
||||||
def blend(self, destination: Color, factor: float) -> Color:
|
def blend(self, destination: Color, factor: float) -> Color:
|
||||||
"""Generate a new color between two colors.
|
"""Generate a new color between two colors.
|
||||||
|
|
||||||
@@ -418,6 +417,7 @@ class ColorPair(NamedTuple):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=1024)
|
||||||
def rgb_to_lab(rgb: Color) -> Lab:
|
def rgb_to_lab(rgb: Color) -> Lab:
|
||||||
"""Convert an RGB color to the CIE-L*ab format.
|
"""Convert an RGB color to the CIE-L*ab format.
|
||||||
|
|
||||||
@@ -444,6 +444,7 @@ def rgb_to_lab(rgb: Color) -> Lab:
|
|||||||
return Lab(116 * y - 16, 500 * (x - y), 200 * (y - z))
|
return Lab(116 * y - 16, 500 * (x - y), 200 * (y - z))
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=1024)
|
||||||
def lab_to_rgb(lab: Lab) -> Color:
|
def lab_to_rgb(lab: Lab) -> Color:
|
||||||
"""Convert a CIE-L*ab color to RGB.
|
"""Convert a CIE-L*ab color to RGB.
|
||||||
|
|
||||||
|
|||||||
@@ -223,6 +223,14 @@ class Scalar(NamedTuple):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_number(cls, value: float) -> Scalar:
|
def from_number(cls, value: float) -> Scalar:
|
||||||
|
"""Create a scalar with cells unit.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value (float): A number of cells.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Scalar: New Scalar.
|
||||||
|
"""
|
||||||
return cls(float(value), Unit.CELLS, Unit.WIDTH)
|
return cls(float(value), Unit.CELLS, Unit.WIDTH)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@@ -6,9 +6,13 @@ Functions and classes to manage terminal geometry (anything involving coordinate
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any, cast, NamedTuple, Sequence, Tuple, Union, TypeVar
|
from functools import lru_cache
|
||||||
|
|
||||||
SpacingDimensions = Union[int, Tuple[int], Tuple[int, int], Tuple[int, int, int, int]]
|
from typing import Any, cast, Collection, NamedTuple, Tuple, TypeAlias, Union, TypeVar
|
||||||
|
|
||||||
|
SpacingDimensions: TypeAlias = Union[
|
||||||
|
int, Tuple[int], Tuple[int, int], Tuple[int, int, int, int]
|
||||||
|
]
|
||||||
|
|
||||||
T = TypeVar("T", int, float)
|
T = TypeVar("T", int, float)
|
||||||
|
|
||||||
@@ -182,7 +186,7 @@ class Region(NamedTuple):
|
|||||||
height: int = 0
|
height: int = 0
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_union(cls, regions: Sequence[Region]) -> Region:
|
def from_union(cls, regions: Collection[Region]) -> Region:
|
||||||
"""Create a Region from the union of other regions.
|
"""Create a Region from the union of other regions.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -356,6 +360,7 @@ class Region(NamedTuple):
|
|||||||
height + expand_height * 2,
|
height + expand_height * 2,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@lru_cache(maxsize=1024)
|
||||||
def overlaps(self, other: Region) -> bool:
|
def overlaps(self, other: Region) -> bool:
|
||||||
"""Check if another region overlaps this region.
|
"""Check if another region overlaps this region.
|
||||||
|
|
||||||
@@ -433,6 +438,7 @@ class Region(NamedTuple):
|
|||||||
self_x, self_y, width, height = self
|
self_x, self_y, width, height = self
|
||||||
return Region(self_x + x, self_y + y, width, height)
|
return Region(self_x + x, self_y + y, width, height)
|
||||||
|
|
||||||
|
@lru_cache(maxsize=4096)
|
||||||
def __contains__(self, other: Any) -> bool:
|
def __contains__(self, other: Any) -> bool:
|
||||||
"""Check if a point is in this region."""
|
"""Check if a point is in this region."""
|
||||||
if isinstance(other, Region):
|
if isinstance(other, Region):
|
||||||
@@ -483,6 +489,7 @@ class Region(NamedTuple):
|
|||||||
height=max(0, height - top - bottom),
|
height=max(0, height - top - bottom),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@lru_cache(maxsize=4096)
|
||||||
def intersection(self, region: Region) -> Region:
|
def intersection(self, region: Region) -> Region:
|
||||||
"""Get the overlapping portion of the two regions.
|
"""Get the overlapping portion of the two regions.
|
||||||
|
|
||||||
@@ -507,6 +514,7 @@ class Region(NamedTuple):
|
|||||||
|
|
||||||
return Region(rx1, ry1, rx2 - rx1, ry2 - ry1)
|
return Region(rx1, ry1, rx2 - rx1, ry2 - ry1)
|
||||||
|
|
||||||
|
@lru_cache(maxsize=4096)
|
||||||
def union(self, region: Region) -> Region:
|
def union(self, region: Region) -> Region:
|
||||||
"""Get a new region that contains both regions.
|
"""Get a new region that contains both regions.
|
||||||
|
|
||||||
@@ -524,6 +532,7 @@ class Region(NamedTuple):
|
|||||||
)
|
)
|
||||||
return union_region
|
return union_region
|
||||||
|
|
||||||
|
@lru_cache(maxsize=1024)
|
||||||
def split(self, cut_x: int, cut_y: int) -> tuple[Region, Region, Region, Region]:
|
def split(self, cut_x: int, cut_y: int) -> tuple[Region, Region, Region, Region]:
|
||||||
"""Split a region in to 4 from given x and y offsets (cuts).
|
"""Split a region in to 4 from given x and y offsets (cuts).
|
||||||
|
|
||||||
@@ -563,6 +572,7 @@ class Region(NamedTuple):
|
|||||||
_Region(x + cut_x, y + cut_y, width - cut_x, height - cut_y),
|
_Region(x + cut_x, y + cut_y, width - cut_x, height - cut_y),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@lru_cache(maxsize=1024)
|
||||||
def split_vertical(self, cut: int) -> tuple[Region, Region]:
|
def split_vertical(self, cut: int) -> tuple[Region, Region]:
|
||||||
"""Split a region in to two, from a given x offset.
|
"""Split a region in to two, from a given x offset.
|
||||||
|
|
||||||
@@ -591,6 +601,7 @@ class Region(NamedTuple):
|
|||||||
Region(x + cut, y, width - cut, height),
|
Region(x + cut, y, width - cut, height),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@lru_cache(maxsize=1024)
|
||||||
def split_horizontal(self, cut: int) -> tuple[Region, Region]:
|
def split_horizontal(self, cut: int) -> tuple[Region, Region]:
|
||||||
"""Split a region in to two, from a given x offset.
|
"""Split a region in to two, from a given x offset.
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class VerticalLayout(Layout):
|
|||||||
|
|
||||||
box_models = [
|
box_models = [
|
||||||
widget.get_box_model(size, parent_size, fraction_unit)
|
widget.get_box_model(size, parent_size, fraction_unit)
|
||||||
for widget in cast("list[Widget]", parent.children)
|
for widget in parent.children
|
||||||
]
|
]
|
||||||
|
|
||||||
margins = [
|
margins = [
|
||||||
|
|||||||
Reference in New Issue
Block a user