From 23eb13d12d61e49dc0ac71dbe0a0789bb5bc933c Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 17 Jan 2023 11:06:21 +0000 Subject: [PATCH] Some DataTable doc updates, rename Coord -> Coordinate and extract to module --- docs/roadmap.md | 21 ++++--- docs/widgets/data_table.md | 36 ++++++++---- src/textual/app.py | 4 +- src/textual/coordinate.py | 46 +++++++++++++++ src/textual/widgets/_data_table.py | 94 +++++++++--------------------- 5 files changed, 110 insertions(+), 91 deletions(-) create mode 100644 src/textual/coordinate.py diff --git a/docs/roadmap.md b/docs/roadmap.md index a1b0c9347..7589d5c33 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -6,7 +6,7 @@ hide: # Roadmap -We ([textualize.io](https://www.textualize.io/)) are actively building and maintaining Textual. +We ([textualize.io](https://www.textualize.io/)) are actively building and maintaining Textual. We have many new features in the pipeline. This page will keep track of that work. @@ -18,24 +18,24 @@ High-level features we plan on implementing. * [ ] Integration with screen readers * [x] Monochrome mode * [ ] High contrast theme - * [ ] Color blind themes + * [ ] Color-blind themes - [ ] Command interface * [ ] Command menu * [ ] Fuzzy search - [ ] Configuration (.toml based extensible configuration format) -- [x] Console +- [x] Console - [ ] Devtools - * [ ] Integrated log - * [ ] DOM tree view + * [ ] Integrated log + * [ ] DOM tree view * [ ] REPL - [ ] Reactive state abstraction -- [x] Themes - * [ ] Customize via config +- [x] Themes + * [ ] Customize via config * [ ] Builtin theme editor ## Widgets -Widgets are key to making user friendly interfaces. The builtin widgets should cover many common (and some uncommon) use-cases. The following is a list of the widgets we have built or are planning to build. +Widgets are key to making user-friendly interfaces. The builtin widgets should cover many common (and some uncommon) use-cases. The following is a list of the widgets we have built or are planning to build. - [x] Buttons * [x] Error / warning variants @@ -44,8 +44,8 @@ Widgets are key to making user friendly interfaces. The builtin widgets should c - [ ] Content switcher - [x] DataTable * [x] Cell select - * [ ] Row / Column select - * [ ] API to update cells / rows + * [x] Row / Column select + * [ ] API to update cells / rows * [ ] Lazy loading API - [ ] Date picker - [ ] Drop-down menus @@ -76,4 +76,3 @@ Widgets are key to making user friendly interfaces. The builtin widgets should c * [ ] Indentation guides * [ ] Smart features for various languages * [ ] Syntax highlighting - diff --git a/docs/widgets/data_table.md b/docs/widgets/data_table.md index 4e4afd5f9..c687b5522 100644 --- a/docs/widgets/data_table.md +++ b/docs/widgets/data_table.md @@ -22,18 +22,32 @@ The example below populates a table with CSV data. ## Reactive Attributes -| Name | Type | Default | Description | -|-----------------|---------|---------------|---------------------------------------------------------| -| `show_header` | `bool` | `True` | Show the table header | -| `fixed_rows` | `int` | `0` | Number of fixed rows (rows which do not scroll) | -| `fixed_columns` | `int` | `0` | Number of fixed columns (columns which do not scroll) | -| `zebra_stripes` | `bool` | `False` | Display alternating colors on rows | -| `header_height` | `int` | `1` | Height of header row | -| `show_cursor` | `bool` | `True` | Show the cursor | -| `cursor_type` | `str` | `"cell"` | One of `"cell"`, `"row"`, `"column"`, or `"none"` | -| `cursor_cell` | `Coord` | `Coord(0, 0)` | The coordinates of the cell the cursor is currently on | -| `hover_cell` | `Coord` | `Coord(0, 0)` | The coordinates of the cell the _mouse_ cursor is above | +| Name | Type | Default | Description | +|-----------------|--------------|--------------------|---------------------------------------------------------| +| `show_header` | `bool` | `True` | Show the table header | +| `fixed_rows` | `int` | `0` | Number of fixed rows (rows which do not scroll) | +| `fixed_columns` | `int` | `0` | Number of fixed columns (columns which do not scroll) | +| `zebra_stripes` | `bool` | `False` | Display alternating colors on rows | +| `header_height` | `int` | `1` | Height of header row | +| `show_cursor` | `bool` | `True` | Show the cursor | +| `cursor_type` | `str` | `"cell"` | One of `"cell"`, `"row"`, `"column"`, or `"none"` | +| `cursor_cell` | `Coordinate` | `Coordinate(0, 0)` | The coordinates of the cell the cursor is currently on | +| `hover_cell` | `Coordinate` | `Coordinate(0, 0)` | The coordinates of the cell the _mouse_ cursor is above | +## Messages + +### CellHighlighted + +The `DataTable.CellHighlighted` message is emitted by the `DataTable` widget when the cursor moves +to highlight a new cell. It's also emitted when the cell cursor is re-enabled (by setting `show_cursor=True`), +and when the cursor type is changed to `"cell"`. + +#### Attributes + +| Attribute | Type | Description | +|--------------|----------------------------------------------|----------------------------------| +| `value` | `CellType` | The value contained in the cell. | +| `coordinate` | [Coordinate][textual.coordinates.Coordinate] | The coordinate of the cell. | ## See Also diff --git a/src/textual/app.py b/src/textual/app.py index c3332ba82..6b99ac48f 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -1774,8 +1774,8 @@ class App(Generic[ReturnType], DOMNode): """Get the widget under the given coordinates. Args: - x (int): X Coord. - y (int): Y Coord. + x (int): X Coordinate. + y (int): Y Coordinate. Returns: tuple[Widget, Region]: The widget and the widget's screen region. diff --git a/src/textual/coordinate.py b/src/textual/coordinate.py new file mode 100644 index 000000000..9429cc5b6 --- /dev/null +++ b/src/textual/coordinate.py @@ -0,0 +1,46 @@ +from __future__ import annotations + +from typing import NamedTuple + + +class Coordinate(NamedTuple): + """An object representing a row/column coordinate.""" + + row: int + column: int + + def left(self) -> Coordinate: + """Get coordinate to the left. + + Returns: + Coordinate: The coordinate. + """ + row, column = self + return Coordinate(row, column - 1) + + def right(self) -> Coordinate: + """Get coordinate to the right. + + Returns: + Coordinate: The coordinate. + """ + row, column = self + return Coordinate(row, column + 1) + + def up(self) -> Coordinate: + """Get coordinate above. + + Returns: + Coordinate: The coordinate. + """ + row, column = self + return Coordinate(row - 1, column) + + def down(self) -> Coordinate: + """Get coordinate below. + + Returns: + Coordinate: The coordinate. + """ + row, column = self + return Coordinate(row + 1, column) diff --git a/src/textual/widgets/_data_table.py b/src/textual/widgets/_data_table.py index ab20f44ad..fcf2021ba 100644 --- a/src/textual/widgets/_data_table.py +++ b/src/textual/widgets/_data_table.py @@ -2,7 +2,7 @@ from __future__ import annotations from dataclasses import dataclass, field from itertools import chain, zip_longest -from typing import ClassVar, Generic, Iterable, NamedTuple, TypeVar, cast +from typing import ClassVar, Generic, Iterable, TypeVar, cast import rich.repr from rich.console import RenderableType @@ -14,6 +14,7 @@ from rich.text import Text, TextType from .. import events, messages from .._cache import LRUCache +from ..coordinate import Coordinate from .._segment_tools import line_crop from .._types import SegmentLines from ..binding import Binding @@ -78,49 +79,6 @@ class Row: cell_renderables: list[RenderableType] = field(default_factory=list) -class Coord(NamedTuple): - """An object to represent the coordinate of a cell within the data table.""" - - row: int - column: int - - def left(self) -> Coord: - """Get coordinate to the left. - - Returns: - Coord: The coordinate. - """ - row, column = self - return Coord(row, column - 1) - - def right(self) -> Coord: - """Get coordinate to the right. - - Returns: - Coord: The coordinate. - """ - row, column = self - return Coord(row, column + 1) - - def up(self) -> Coord: - """Get coordinate above. - - Returns: - Coord: The coordinate. - """ - row, column = self - return Coord(row - 1, column) - - def down(self) -> Coord: - """Get coordinate below. - - Returns: - Coord: The coordinate. - """ - row, column = self - return Coord(row + 1, column) - - class DataTable(ScrollView, Generic[CellType], can_focus=True): DEFAULT_CSS = """ App.-dark DataTable { @@ -199,10 +157,10 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True): show_cursor = Reactive(True) cursor_type = Reactive(CELL) - cursor_cell: Reactive[Coord] = Reactive( - Coord(0, 0), repaint=False, always_update=True + cursor_cell: Reactive[Coordinate] = Reactive( + Coordinate(0, 0), repaint=False, always_update=True ) - hover_cell: Reactive[Coord] = Reactive(Coord(0, 0), repaint=False) + hover_cell: Reactive[Coordinate] = Reactive(Coordinate(0, 0), repaint=False) def __init__( self, @@ -263,11 +221,11 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True): def cursor_column(self) -> int: return self.cursor_cell.column - def get_cell_value(self, coordinate: Coord) -> CellType: + def get_cell_value(self, coordinate: Coordinate) -> CellType: """Get the value from the cell at the given coordinate. Args: - coordinate (Coord): The coordinate to retrieve the value from. + coordinate (Coordinate): The coordinate to retrieve the value from. Returns: CellType: The value of the cell. @@ -310,11 +268,13 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True): def watch_zebra_stripes(self, zebra_stripes: bool) -> None: self._clear_caches() - def watch_hover_cell(self, old: Coord, value: Coord) -> None: + def watch_hover_cell(self, old: Coordinate, value: Coordinate) -> None: self.refresh_cell(*old) self.refresh_cell(*value) - def watch_cursor_cell(self, old_coordinate: Coord, new_coordinate: Coord) -> None: + def watch_cursor_cell( + self, old_coordinate: Coordinate, new_coordinate: Coordinate + ) -> None: if old_coordinate != new_coordinate: if self.cursor_type == "cell": self.refresh_cell(*old_coordinate) @@ -326,7 +286,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True): self.refresh_column(old_coordinate.column) self._highlight_column(new_coordinate.column) - def _highlight_cell(self, coordinate: Coord) -> None: + def _highlight_cell(self, coordinate: Coordinate) -> None: self.refresh_cell(*coordinate) cell_value = self.get_cell_value(coordinate) self.emit_no_wait(DataTable.CellHighlighted(self, cell_value, coordinate)) @@ -339,14 +299,14 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True): self.refresh_column(column_index) self.emit_no_wait(DataTable.ColumnHighlighted(self, column_index)) - def validate_cursor_cell(self, value: Coord) -> Coord: + def validate_cursor_cell(self, value: Coordinate) -> Coordinate: return self._clamp_cursor_cell(value) - def _clamp_cursor_cell(self, cursor_cell: Coord) -> Coord: + def _clamp_cursor_cell(self, cursor_cell: Coordinate) -> Coordinate: row, column = cursor_cell row = clamp(row, 0, self.row_count - 1) column = clamp(column, self.fixed_columns, len(self.columns) - 1) - return Coord(row, column) + return Coordinate(row, column) def watch_cursor_type(self, old: str, value: str) -> None: self._set_hover_cursor(False) @@ -637,8 +597,8 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True): row_index: int, line_no: int, base_style: Style, - cursor_location: Coord, - hover_location: Coord, + cursor_location: Coordinate, + hover_location: Coordinate, ) -> tuple[SegmentLines, SegmentLines]: """Render a row in to lines for each cell. @@ -669,8 +629,8 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True): render_cell = self._render_cell def _should_highlight( - cursor_location: Coord, - cell_location: Coord, + cursor_location: Coordinate, + cell_location: Coordinate, cursor_type: CursorType, ) -> bool: """Determine whether we should highlight a cell given the location @@ -694,7 +654,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True): fixed_style += Style.from_meta({"fixed": True}) fixed_row = [] for column in self.columns[: self.fixed_columns]: - cell_location = Coord(row_index, column.index) + cell_location = Coordinate(row_index, column.index) fixed_cell_lines = render_cell( row_index, column.index, @@ -722,7 +682,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True): scrollable_row = [] for column in self.columns: - cell_location = Coord(row_index, column.index) + cell_location = Coordinate(row_index, column.index) cell_lines = render_cell( row_index, column.index, @@ -827,7 +787,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True): meta = event.style.meta if meta and self.show_cursor and self.cursor_type != "none": try: - self.hover_cell = Coord(meta["row"], meta["column"]) + self.hover_cell = Coordinate(meta["row"], meta["column"]) except KeyError: pass @@ -872,7 +832,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True): self._emit_selected_message() meta = self.get_style_at(event.x, event.y).meta if meta: - self.cursor_cell = Coord(meta["row"], meta["column"]) + self.cursor_cell = Coordinate(meta["row"], meta["column"]) self._scroll_cursor_into_view(animate=True) event.stop() @@ -942,11 +902,11 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True): Attributes: sender (DataTable): The DataTable the cell was highlighted in. value (CellType): The value in the highlighted cell. - coordinate (Coord): The coordinate of the highlighted cell. + coordinate (Coordinate): The coordinate of the highlighted cell. """ def __init__( - self, sender: DataTable, value: CellType, coordinate: Coord + self, sender: DataTable, value: CellType, coordinate: Coordinate ) -> None: self.value = value self.coordinate = coordinate @@ -964,11 +924,11 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True): Attributes: sender (DataTable): The DataTable the cell was selected in. value (CellType): The value in the cell that was selected. - coordinate (Coord): The coordinate of the cell that was selected. + coordinate (Coordinate): The coordinate of the cell that was selected. """ def __init__( - self, sender: DataTable, value: CellType, coordinate: Coord + self, sender: DataTable, value: CellType, coordinate: Coordinate ) -> None: self.value = value self.coordinate = coordinate