|
60 | 60 | from textual.actions import SkipAction |
61 | 61 | from textual.await_remove import AwaitRemove |
62 | 62 | from textual.box_model import BoxModel |
63 | | -from textual.cache import FIFOCache |
| 63 | +from textual.cache import FIFOCache, LRUCache |
64 | 64 | from textual.color import Color |
65 | 65 | from textual.compose import compose |
66 | 66 | from textual.content import Content, ContentType |
@@ -427,6 +427,7 @@ def __init__( |
427 | 427 | self._size = _null_size |
428 | 428 | self._container_size = _null_size |
429 | 429 | self._layout_required = False |
| 430 | + self._layout_updates = 0 |
430 | 431 | self._repaint_required = False |
431 | 432 | self._scroll_required = False |
432 | 433 | self._recompose_required = False |
@@ -455,6 +456,8 @@ def __init__( |
455 | 456 | # Regions which need to be transferred from cache to screen |
456 | 457 | self._repaint_regions: set[Region] = set() |
457 | 458 |
|
| 459 | + self._box_model_cache: LRUCache[object, BoxModel] = LRUCache(16) |
| 460 | + |
458 | 461 | # Cache the auto content dimensions |
459 | 462 | self._content_width_cache: tuple[object, int] = (None, 0) |
460 | 463 | self._content_height_cache: tuple[object, int] = (None, 0) |
@@ -1642,6 +1645,19 @@ def _get_box_model( |
1642 | 1645 | Returns: |
1643 | 1646 | The size and margin for this widget. |
1644 | 1647 | """ |
| 1648 | + cache_key = ( |
| 1649 | + container, |
| 1650 | + viewport, |
| 1651 | + width_fraction, |
| 1652 | + height_fraction, |
| 1653 | + constrain_width, |
| 1654 | + greedy, |
| 1655 | + self._layout_updates, |
| 1656 | + self.styles._cache_key, |
| 1657 | + ) |
| 1658 | + if cached_box_model := self._box_model_cache.get(cache_key): |
| 1659 | + return cached_box_model |
| 1660 | + |
1645 | 1661 | styles = self.styles |
1646 | 1662 | is_border_box = styles.box_sizing == "border-box" |
1647 | 1663 | gutter = styles.gutter # Padding plus border |
@@ -1750,6 +1766,7 @@ def _get_box_model( |
1750 | 1766 | model = BoxModel( |
1751 | 1767 | content_width + gutter.width, content_height + gutter.height, margin |
1752 | 1768 | ) |
| 1769 | + self._box_model_cache[cache_key] = model |
1753 | 1770 | return model |
1754 | 1771 |
|
1755 | 1772 | def get_content_width(self, container: Size, viewport: Size) -> int: |
@@ -3610,7 +3627,7 @@ def scroll_visible( |
3610 | 3627 | """ |
3611 | 3628 | parent = self.parent |
3612 | 3629 | if isinstance(parent, Widget): |
3613 | | - if self.region: |
| 3630 | + if self._size: |
3614 | 3631 | self.screen.scroll_to_widget( |
3615 | 3632 | self, |
3616 | 3633 | animate=animate, |
@@ -4200,14 +4217,9 @@ def refresh( |
4200 | 4217 | Returns: |
4201 | 4218 | The `Widget` instance. |
4202 | 4219 | """ |
4203 | | - if layout: |
| 4220 | + if layout and not self._layout_required: |
4204 | 4221 | self._layout_required = True |
4205 | | - for ancestor in self.ancestors: |
4206 | | - if not isinstance(ancestor, Widget): |
4207 | | - break |
4208 | | - ancestor._clear_arrangement_cache() |
4209 | | - if not ancestor.styles.auto_dimensions: |
4210 | | - break |
| 4222 | + self._layout_updates += 1 |
4211 | 4223 |
|
4212 | 4224 | if recompose: |
4213 | 4225 | self._recompose_required = True |
@@ -4422,7 +4434,14 @@ def _check_refresh(self) -> None: |
4422 | 4434 | screen.post_message(messages.Update(self)) |
4423 | 4435 | if self._layout_required: |
4424 | 4436 | self._layout_required = False |
4425 | | - screen.post_message(messages.Layout()) |
| 4437 | + for ancestor in self.ancestors: |
| 4438 | + if not isinstance(ancestor, Widget): |
| 4439 | + break |
| 4440 | + ancestor._clear_arrangement_cache() |
| 4441 | + ancestor._layout_updates += 1 |
| 4442 | + if not ancestor.styles.auto_dimensions: |
| 4443 | + break |
| 4444 | + screen.post_message(messages.Layout(self)) |
4426 | 4445 |
|
4427 | 4446 | def focus(self, scroll_visible: bool = True) -> Self: |
4428 | 4447 | """Give focus to this widget. |
|
0 commit comments