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 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()

View File

@@ -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)