optimize get_widget_at

This commit is contained in:
Will McGugan
2022-09-30 12:04:25 +01:00
parent c966650df4
commit aec4da985e
2 changed files with 44 additions and 6 deletions

View File

@@ -14,6 +14,7 @@ without having to render the entire screen.
from __future__ import annotations from __future__ import annotations
from itertools import chain from itertools import chain
from collections import defaultdict
from operator import itemgetter from operator import itemgetter
import sys import sys
from typing import Callable, cast, Iterator, Iterable, NamedTuple, TYPE_CHECKING from typing import Callable, cast, Iterator, Iterable, NamedTuple, TYPE_CHECKING
@@ -203,6 +204,8 @@ class Compositor:
# Regions that require an update # Regions that require an update
self._dirty_regions: set[Region] = set() self._dirty_regions: set[Region] = set()
self._layers_visible: dict[int, list[tuple[Widget, Region]]] | None = None
@classmethod @classmethod
def _regions_to_spans( def _regions_to_spans(
cls, regions: Iterable[Region] cls, regions: Iterable[Region]
@@ -257,6 +260,7 @@ class Compositor:
""" """
self._cuts = None self._cuts = None
self._layers = None self._layers = None
self._layers_visible = None
self.root = parent self.root = parent
self.size = size self.size = size
@@ -328,6 +332,7 @@ class Compositor:
} }
return self._visible_widgets return self._visible_widgets
@timer("arrange")
def _arrange_root( def _arrange_root(
self, root: Widget, size: Size self, root: Widget, size: Size
) -> tuple[CompositorMap, set[Widget]]: ) -> tuple[CompositorMap, set[Widget]]:
@@ -475,6 +480,27 @@ class Compositor:
) )
return self._layers 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]]: def __iter__(self) -> Iterator[tuple[Widget, Region, Region, Size, Size]]:
"""Iterate map with information regarding each widget and is position """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. tuple[Widget, Region]: A tuple of the widget and its region.
""" """
# TODO: Optimize with some line based lookup # TODO: Optimize with some line based lookup
contains = Region.contains contains = Region.contains
for widget, cropped_region, region, *_ in self: for widget, region in self.layers_visible.get(y, []):
if contains(cropped_region, x, y) and widget.visible: if contains(region, x, y) and widget.visible:
return widget, region 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})") raise errors.NoWidget(f"No widget under screen coordinate ({x}, {y})")
def get_widgets_at(self, x: int, y: int) -> Iterable[tuple[Widget, Region]]: 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. Iterable[tuple[Widget, Region]]: Sequence of (WIDGET, REGION) tuples.
""" """
contains = Region.contains contains = Region.contains
for widget, cropped_region, region, *_ in self: for widget, region in self.layers_visible.get(y, []):
if contains(cropped_region, x, y) and widget.visible: if contains(region, x, y) and widget.visible:
yield widget, region 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: def get_style_at(self, x: int, y: int) -> Style:
"""Get the Style at the given cell or Style.null() """Get the Style at the given cell or Style.null()

View File

@@ -86,12 +86,13 @@ class Static(Widget):
""" """
return self._renderable 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. """Update the widget's content area with new text or Rich renderable.
Args: Args:
renderable (RenderableType, optional): A new rich renderable. Defaults to empty renderable; renderable (RenderableType, optional): A new rich renderable. Defaults to empty renderable;
layout (bool, optional): Perform a layout. Defaults to True.
""" """
_check_renderable(renderable) _check_renderable(renderable)
self.renderable = renderable self.renderable = renderable
self.refresh(layout=True) self.refresh(layout=layout)