mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
optimize get_widget_at
This commit is contained in:
@@ -14,6 +14,7 @@ without having to render the entire screen.
|
||||
from __future__ import annotations
|
||||
|
||||
from itertools import chain
|
||||
from collections import defaultdict
|
||||
from operator import itemgetter
|
||||
import sys
|
||||
from typing import Callable, cast, Iterator, Iterable, NamedTuple, TYPE_CHECKING
|
||||
@@ -203,6 +204,8 @@ class Compositor:
|
||||
# Regions that require an update
|
||||
self._dirty_regions: set[Region] = set()
|
||||
|
||||
self._layers_visible: dict[int, list[tuple[Widget, Region]]] | None = None
|
||||
|
||||
@classmethod
|
||||
def _regions_to_spans(
|
||||
cls, regions: Iterable[Region]
|
||||
@@ -257,6 +260,7 @@ class Compositor:
|
||||
"""
|
||||
self._cuts = None
|
||||
self._layers = None
|
||||
self._layers_visible = None
|
||||
self.root = parent
|
||||
self.size = size
|
||||
|
||||
@@ -328,6 +332,7 @@ class Compositor:
|
||||
}
|
||||
return self._visible_widgets
|
||||
|
||||
@timer("arrange")
|
||||
def _arrange_root(
|
||||
self, root: Widget, size: Size
|
||||
) -> tuple[CompositorMap, set[Widget]]:
|
||||
@@ -475,6 +480,27 @@ class Compositor:
|
||||
)
|
||||
return self._layers
|
||||
|
||||
@property
|
||||
def layers_visible(self) -> dict[int, list[tuple[Widget, Region]]]:
|
||||
"""Visible widgets and regions in layers order."""
|
||||
|
||||
if self._layers_visible is None:
|
||||
with timer("CALC LAYERS"):
|
||||
layers_visible: dict[int, list[tuple[Widget, Region]]]
|
||||
screen_region = self.size.region
|
||||
_, screen_height = self.size
|
||||
layers_visible = {y: [] for y in screen_region.line_range}
|
||||
|
||||
visible_intersection = screen_region.intersection
|
||||
|
||||
for widget, region, *_ in self:
|
||||
(_x, y, _width, height) = region
|
||||
if y + height > 0 and y < screen_height:
|
||||
for y in visible_intersection(region).line_range:
|
||||
layers_visible[y].append((widget, region))
|
||||
self._layers_visible = layers_visible
|
||||
return self._layers_visible
|
||||
|
||||
def __iter__(self) -> Iterator[tuple[Widget, Region, Region, Size, Size]]:
|
||||
"""Iterate map with information regarding each widget and is position
|
||||
|
||||
@@ -514,10 +540,16 @@ class Compositor:
|
||||
tuple[Widget, Region]: A tuple of the widget and its region.
|
||||
"""
|
||||
# TODO: Optimize with some line based lookup
|
||||
|
||||
contains = Region.contains
|
||||
for widget, cropped_region, region, *_ in self:
|
||||
if contains(cropped_region, x, y) and widget.visible:
|
||||
for widget, region in self.layers_visible.get(y, []):
|
||||
if contains(region, x, y) and widget.visible:
|
||||
return widget, region
|
||||
|
||||
# contains = Region.contains
|
||||
# for widget, cropped_region, region, *_ in self:
|
||||
# if contains(cropped_region, x, y) and widget.visible:
|
||||
# return widget, region
|
||||
raise errors.NoWidget(f"No widget under screen coordinate ({x}, {y})")
|
||||
|
||||
def get_widgets_at(self, x: int, y: int) -> Iterable[tuple[Widget, Region]]:
|
||||
@@ -531,10 +563,15 @@ class Compositor:
|
||||
Iterable[tuple[Widget, Region]]: Sequence of (WIDGET, REGION) tuples.
|
||||
"""
|
||||
contains = Region.contains
|
||||
for widget, cropped_region, region, *_ in self:
|
||||
if contains(cropped_region, x, y) and widget.visible:
|
||||
for widget, region in self.layers_visible.get(y, []):
|
||||
if contains(region, x, y) and widget.visible:
|
||||
yield widget, region
|
||||
|
||||
# contains = Region.contains
|
||||
# for widget, cropped_region, region, *_ in self:
|
||||
# if contains(cropped_region, x, y) and widget.visible:
|
||||
# yield widget, region
|
||||
|
||||
def get_style_at(self, x: int, y: int) -> Style:
|
||||
"""Get the Style at the given cell or Style.null()
|
||||
|
||||
|
||||
@@ -86,12 +86,13 @@ class Static(Widget):
|
||||
"""
|
||||
return self._renderable
|
||||
|
||||
def update(self, renderable: RenderableType = "") -> None:
|
||||
def update(self, renderable: RenderableType = "", *, layout: bool = False) -> None:
|
||||
"""Update the widget's content area with new text or Rich renderable.
|
||||
|
||||
Args:
|
||||
renderable (RenderableType, optional): A new rich renderable. Defaults to empty renderable;
|
||||
layout (bool, optional): Perform a layout. Defaults to True.
|
||||
"""
|
||||
_check_renderable(renderable)
|
||||
self.renderable = renderable
|
||||
self.refresh(layout=True)
|
||||
self.refresh(layout=layout)
|
||||
|
||||
Reference in New Issue
Block a user