diff --git a/sandbox/will/basic.css b/sandbox/will/basic.css index eed5e0b5e..0a7fa15ec 100644 --- a/sandbox/will/basic.css +++ b/sandbox/will/basic.css @@ -22,6 +22,10 @@ App > Screen { color: $text-surface; } +DataTable { + margin: 2; + height: 12; +} #sidebar { color: $text-primary; diff --git a/sandbox/will/basic.py b/sandbox/will/basic.py index acae838d7..aca946a19 100644 --- a/sandbox/will/basic.py +++ b/sandbox/will/basic.py @@ -6,7 +6,7 @@ from rich.text import Text from textual.app import App from textual.reactive import Reactive from textual.widget import Widget -from textual.widgets import Static +from textual.widgets import Static, DataTable CODE = ''' class Offset(NamedTuple): @@ -101,6 +101,7 @@ class BasicApp(App, css_path="basic.css"): def on_mount(self): """Build layout here.""" + table = DataTable() self.scroll_to_target = Tweet(TweetBody()) self.mount( header=Static( @@ -114,6 +115,7 @@ class BasicApp(App, css_path="basic.css"): Static(Syntax(CODE, "python"), classes="code"), classes="scrollable", ), + table, Error(), Tweet(TweetBody(), classes="scrollbar-size-custom"), Warning(), @@ -135,6 +137,17 @@ class BasicApp(App, css_path="basic.css"): Widget(classes="content"), ), ) + table.add_column("Foo", width=80) + table.add_column("Bar", width=50) + table.add_column("Baz", width=40) + table.zebra_stripes = True + for n in range(100): + table.add_row( + f"{n} This is an example of a [b]DataTable widget[/b] within a larger [bold magenta]Textual UI", + "Cells may contain just about any kind of data", + "Where there is a Will there is a Way", + height=1, + ) async def on_key(self, event) -> None: await self.dispatch_key(event) diff --git a/src/textual/geometry.py b/src/textual/geometry.py index 827632c61..f8fdf8b1c 100644 --- a/src/textual/geometry.py +++ b/src/textual/geometry.py @@ -354,11 +354,6 @@ class Region(NamedTuple): x, y, width, height = self return Offset(x + width, y + height) - @property - def offset(self) -> Offset: - x, y, _, _ = self - return Offset(x, y) - @property def size(self) -> Size: """Get the size of the region.""" diff --git a/src/textual/widgets/_data_table.py b/src/textual/widgets/_data_table.py index e00fdcf6b..4948a8c1e 100644 --- a/src/textual/widgets/_data_table.py +++ b/src/textual/widgets/_data_table.py @@ -157,16 +157,18 @@ class DataTable(ScrollView, Generic[CellType]): self._row_render_cache = LRUCache(1000) self._cell_render_cache: LRUCache[tuple[int, int, Style, bool, bool], Lines] - self._cell_render_cache = LRUCache(1000) + self._cell_render_cache = LRUCache(10000) - self._line_cache: LRUCache[tuple[int, int, int, int, int, int], list[Segment]] + self._line_cache: LRUCache[ + tuple[int, int, int, int, int, int, Style], list[Segment] + ] self._line_cache = LRUCache(1000) self._line_no = 0 show_header = Reactive(True) fixed_rows = Reactive(0) - fixed_columns = Reactive(1) + fixed_columns = Reactive(0) zebra_stripes = Reactive(False) header_height = Reactive(1) show_cursor = Reactive(True) @@ -237,6 +239,8 @@ class DataTable(ScrollView, Generic[CellType]): ) def _get_cell_region(self, row_index: int, column_index: int) -> Region: + if row_index not in self.rows: + return Region(0, 0, 0, 0) row = self.rows[row_index] x = sum(column.width for column in self.columns[:column_index]) width = self.columns[column_index].width @@ -281,9 +285,11 @@ class DataTable(ScrollView, Generic[CellType]): return region = self._get_cell_region(row_index, column_index) region = region.translate_negative(*self.scroll_offset) - refresh_region = self.content_region.intersection(region) - if refresh_region: - self.refresh(refresh_region) + if region: + self.refresh(region) + # refresh_region = self.content_region.intersection(region) + # if refresh_region: + # self.refresh(refresh_region) def _get_row_renderables(self, row_index: int) -> list[RenderableType]: """Get renderables for the given row. @@ -421,6 +427,8 @@ class DataTable(ScrollView, Generic[CellType]): if y < self.header_height: return (-1, y) y -= self.header_height + if y > len(self._y_offsets): + raise LookupError("Y coord {y!r} is greater than total height") return self._y_offsets[y] def _render_line( @@ -439,7 +447,11 @@ class DataTable(ScrollView, Generic[CellType]): """ width = self.region.width - row_index, line_no = self._get_offsets(y) + + try: + row_index, line_no = self._get_offsets(y) + except LookupError: + return [Segment(" " * width, base_style)] cursor_column = ( self.cursor_column if (self.show_cursor and self.cursor_row == row_index) @@ -447,7 +459,7 @@ class DataTable(ScrollView, Generic[CellType]): ) hover_column = self.hover_column if (self.hover_row == row_index) else -1 - cache_key = (y, x1, x2, width, cursor_column, hover_column) + cache_key = (y, x1, x2, width, cursor_column, hover_column, base_style) if cache_key in self._line_cache: return self._line_cache[cache_key] @@ -479,7 +491,6 @@ class DataTable(ScrollView, Generic[CellType]): Returns: Lines: A list of segments for every line within crop region. """ - scroll_x, scroll_y = self.scroll_offset x1, y1, x2, y2 = crop.translate(scroll_x, scroll_y).corners @@ -500,11 +511,10 @@ class DataTable(ScrollView, Generic[CellType]): for line_index, y in enumerate(range(y1, y2)): if y - scroll_y < fixed_top_row_count: lines[line_index] = fixed_lines[line_index] - return lines def on_mouse_move(self, event: events.MouseMove): - meta = self.get_style_at(event.x, event.y).meta + meta = event.style.meta if meta: try: self.hover_cell = Coord(meta["row"], meta["column"]) @@ -524,10 +534,10 @@ class DataTable(ScrollView, Generic[CellType]): left = sum(column.width for column in self.columns[: self.fixed_columns]) return Spacing(top, 0, 0, left) - def _scroll_cursor_in_to_view(self) -> None: + def _scroll_cursor_in_to_view(self, animate: bool = False) -> None: region = self._get_cell_region(self.cursor_row, self.cursor_column) spacing = self._get_cell_border() - self.scroll_to_region(region, animate=False, spacing=spacing) + self.scroll_to_region(region, animate=animate, spacing=spacing) def on_click(self, event: events.Click) -> None: meta = self.get_style_at(event.x, event.y).meta @@ -551,10 +561,10 @@ class DataTable(ScrollView, Generic[CellType]): self.cursor_cell = self.cursor_cell.right() event.stop() event.prevent_default() - self._scroll_cursor_in_to_view() + self._scroll_cursor_in_to_view(animate=True) def key_left(self, event: events.Key): self.cursor_cell = self.cursor_cell.left() event.stop() event.prevent_default() - self._scroll_cursor_in_to_view() + self._scroll_cursor_in_to_view(animate=True)