Some DataTable doc updates, rename Coord -> Coordinate and extract to module

This commit is contained in:
Darren Burns
2023-01-17 11:06:21 +00:00
parent f327f1d2af
commit 23eb13d12d
5 changed files with 110 additions and 91 deletions

View File

@@ -6,7 +6,7 @@ hide:
# Roadmap # 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. 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 * [ ] Integration with screen readers
* [x] Monochrome mode * [x] Monochrome mode
* [ ] High contrast theme * [ ] High contrast theme
* [ ] Color blind themes * [ ] Color-blind themes
- [ ] Command interface - [ ] Command interface
* [ ] Command menu * [ ] Command menu
* [ ] Fuzzy search * [ ] Fuzzy search
- [ ] Configuration (.toml based extensible configuration format) - [ ] Configuration (.toml based extensible configuration format)
- [x] Console - [x] Console
- [ ] Devtools - [ ] Devtools
* [ ] Integrated log * [ ] Integrated log
* [ ] DOM tree view * [ ] DOM tree view
* [ ] REPL * [ ] REPL
- [ ] Reactive state abstraction - [ ] Reactive state abstraction
- [x] Themes - [x] Themes
* [ ] Customize via config * [ ] Customize via config
* [ ] Builtin theme editor * [ ] Builtin theme editor
## Widgets ## 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] Buttons
* [x] Error / warning variants * [x] Error / warning variants
@@ -44,8 +44,8 @@ Widgets are key to making user friendly interfaces. The builtin widgets should c
- [ ] Content switcher - [ ] Content switcher
- [x] DataTable - [x] DataTable
* [x] Cell select * [x] Cell select
* [ ] Row / Column select * [x] Row / Column select
* [ ] API to update cells / rows * [ ] API to update cells / rows
* [ ] Lazy loading API * [ ] Lazy loading API
- [ ] Date picker - [ ] Date picker
- [ ] Drop-down menus - [ ] Drop-down menus
@@ -76,4 +76,3 @@ Widgets are key to making user friendly interfaces. The builtin widgets should c
* [ ] Indentation guides * [ ] Indentation guides
* [ ] Smart features for various languages * [ ] Smart features for various languages
* [ ] Syntax highlighting * [ ] Syntax highlighting

View File

@@ -22,18 +22,32 @@ The example below populates a table with CSV data.
## Reactive Attributes ## Reactive Attributes
| Name | Type | Default | Description | | Name | Type | Default | Description |
|-----------------|---------|---------------|---------------------------------------------------------| |-----------------|--------------|--------------------|---------------------------------------------------------|
| `show_header` | `bool` | `True` | Show the table header | | `show_header` | `bool` | `True` | Show the table header |
| `fixed_rows` | `int` | `0` | Number of fixed rows (rows which do not scroll) | | `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) | | `fixed_columns` | `int` | `0` | Number of fixed columns (columns which do not scroll) |
| `zebra_stripes` | `bool` | `False` | Display alternating colors on rows | | `zebra_stripes` | `bool` | `False` | Display alternating colors on rows |
| `header_height` | `int` | `1` | Height of header row | | `header_height` | `int` | `1` | Height of header row |
| `show_cursor` | `bool` | `True` | Show the cursor | | `show_cursor` | `bool` | `True` | Show the cursor |
| `cursor_type` | `str` | `"cell"` | One of `"cell"`, `"row"`, `"column"`, or `"none"` | | `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 | | `cursor_cell` | `Coordinate` | `Coordinate(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 | | `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 ## See Also

View File

@@ -1774,8 +1774,8 @@ class App(Generic[ReturnType], DOMNode):
"""Get the widget under the given coordinates. """Get the widget under the given coordinates.
Args: Args:
x (int): X Coord. x (int): X Coordinate.
y (int): Y Coord. y (int): Y Coordinate.
Returns: Returns:
tuple[Widget, Region]: The widget and the widget's screen region. tuple[Widget, Region]: The widget and the widget's screen region.

46
src/textual/coordinate.py Normal file
View File

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

View File

@@ -2,7 +2,7 @@ from __future__ import annotations
from dataclasses import dataclass, field from dataclasses import dataclass, field
from itertools import chain, zip_longest 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 import rich.repr
from rich.console import RenderableType from rich.console import RenderableType
@@ -14,6 +14,7 @@ from rich.text import Text, TextType
from .. import events, messages from .. import events, messages
from .._cache import LRUCache from .._cache import LRUCache
from ..coordinate import Coordinate
from .._segment_tools import line_crop from .._segment_tools import line_crop
from .._types import SegmentLines from .._types import SegmentLines
from ..binding import Binding from ..binding import Binding
@@ -78,49 +79,6 @@ class Row:
cell_renderables: list[RenderableType] = field(default_factory=list) 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): class DataTable(ScrollView, Generic[CellType], can_focus=True):
DEFAULT_CSS = """ DEFAULT_CSS = """
App.-dark DataTable { App.-dark DataTable {
@@ -199,10 +157,10 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
show_cursor = Reactive(True) show_cursor = Reactive(True)
cursor_type = Reactive(CELL) cursor_type = Reactive(CELL)
cursor_cell: Reactive[Coord] = Reactive( cursor_cell: Reactive[Coordinate] = Reactive(
Coord(0, 0), repaint=False, always_update=True 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__( def __init__(
self, self,
@@ -263,11 +221,11 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
def cursor_column(self) -> int: def cursor_column(self) -> int:
return self.cursor_cell.column 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. """Get the value from the cell at the given coordinate.
Args: Args:
coordinate (Coord): The coordinate to retrieve the value from. coordinate (Coordinate): The coordinate to retrieve the value from.
Returns: Returns:
CellType: The value of the cell. 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: def watch_zebra_stripes(self, zebra_stripes: bool) -> None:
self._clear_caches() 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(*old)
self.refresh_cell(*value) 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 old_coordinate != new_coordinate:
if self.cursor_type == "cell": if self.cursor_type == "cell":
self.refresh_cell(*old_coordinate) self.refresh_cell(*old_coordinate)
@@ -326,7 +286,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
self.refresh_column(old_coordinate.column) self.refresh_column(old_coordinate.column)
self._highlight_column(new_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) self.refresh_cell(*coordinate)
cell_value = self.get_cell_value(coordinate) cell_value = self.get_cell_value(coordinate)
self.emit_no_wait(DataTable.CellHighlighted(self, 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.refresh_column(column_index)
self.emit_no_wait(DataTable.ColumnHighlighted(self, 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) 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, column = cursor_cell
row = clamp(row, 0, self.row_count - 1) row = clamp(row, 0, self.row_count - 1)
column = clamp(column, self.fixed_columns, len(self.columns) - 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: def watch_cursor_type(self, old: str, value: str) -> None:
self._set_hover_cursor(False) self._set_hover_cursor(False)
@@ -637,8 +597,8 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
row_index: int, row_index: int,
line_no: int, line_no: int,
base_style: Style, base_style: Style,
cursor_location: Coord, cursor_location: Coordinate,
hover_location: Coord, hover_location: Coordinate,
) -> tuple[SegmentLines, SegmentLines]: ) -> tuple[SegmentLines, SegmentLines]:
"""Render a row in to lines for each cell. """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 render_cell = self._render_cell
def _should_highlight( def _should_highlight(
cursor_location: Coord, cursor_location: Coordinate,
cell_location: Coord, cell_location: Coordinate,
cursor_type: CursorType, cursor_type: CursorType,
) -> bool: ) -> bool:
"""Determine whether we should highlight a cell given the location """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_style += Style.from_meta({"fixed": True})
fixed_row = [] fixed_row = []
for column in self.columns[: self.fixed_columns]: 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( fixed_cell_lines = render_cell(
row_index, row_index,
column.index, column.index,
@@ -722,7 +682,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
scrollable_row = [] scrollable_row = []
for column in self.columns: for column in self.columns:
cell_location = Coord(row_index, column.index) cell_location = Coordinate(row_index, column.index)
cell_lines = render_cell( cell_lines = render_cell(
row_index, row_index,
column.index, column.index,
@@ -827,7 +787,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
meta = event.style.meta meta = event.style.meta
if meta and self.show_cursor and self.cursor_type != "none": if meta and self.show_cursor and self.cursor_type != "none":
try: try:
self.hover_cell = Coord(meta["row"], meta["column"]) self.hover_cell = Coordinate(meta["row"], meta["column"])
except KeyError: except KeyError:
pass pass
@@ -872,7 +832,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
self._emit_selected_message() self._emit_selected_message()
meta = self.get_style_at(event.x, event.y).meta meta = self.get_style_at(event.x, event.y).meta
if 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) self._scroll_cursor_into_view(animate=True)
event.stop() event.stop()
@@ -942,11 +902,11 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
Attributes: Attributes:
sender (DataTable): The DataTable the cell was highlighted in. sender (DataTable): The DataTable the cell was highlighted in.
value (CellType): The value in the highlighted cell. 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__( def __init__(
self, sender: DataTable, value: CellType, coordinate: Coord self, sender: DataTable, value: CellType, coordinate: Coordinate
) -> None: ) -> None:
self.value = value self.value = value
self.coordinate = coordinate self.coordinate = coordinate
@@ -964,11 +924,11 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
Attributes: Attributes:
sender (DataTable): The DataTable the cell was selected in. sender (DataTable): The DataTable the cell was selected in.
value (CellType): The value in the cell that was selected. 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__( def __init__(
self, sender: DataTable, value: CellType, coordinate: Coord self, sender: DataTable, value: CellType, coordinate: Coordinate
) -> None: ) -> None:
self.value = value self.value = value
self.coordinate = coordinate self.coordinate = coordinate