mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Move DataTable cursor with page up/down, home, end (#2228)
* Add pageup and pagedown actions to DataTable, with no impls * Pagedown moves DataTable cursor now * Account for header height in pagedown action * Page Up support in the DataTable * Fix and off-by-1, ensure page up/down works on col cursor * Add placeholder scroll home/end action handlers to datatable * Add scroll home and scroll end * Hide hover cursor when home or end is used * Ensure home and end work correctly with all curosrs * Testing home/end/pagedown/pageup cursor movement in DataTable * Docstrings for new datatable actions * Fix a broken unit test for the DataTable
This commit is contained in:
@@ -203,6 +203,8 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
||||
Binding("down", "cursor_down", "Cursor Down", show=False),
|
||||
Binding("right", "cursor_right", "Cursor Right", show=False),
|
||||
Binding("left", "cursor_left", "Cursor Left", show=False),
|
||||
Binding("pageup", "page_up", "Page Up", show=False),
|
||||
Binding("pagedown", "page_down", "Page Down", show=False),
|
||||
]
|
||||
"""
|
||||
| Key(s) | Description |
|
||||
@@ -247,7 +249,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
||||
background: $surface ;
|
||||
color: $text;
|
||||
height: auto;
|
||||
max-height: 100vh
|
||||
max-height: 100vh;
|
||||
}
|
||||
DataTable > .datatable--header {
|
||||
text-style: bold;
|
||||
@@ -1976,6 +1978,76 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
||||
self._scroll_cursor_into_view(animate=True)
|
||||
event.stop()
|
||||
|
||||
def action_page_down(self) -> None:
|
||||
"""Move the cursor one page down."""
|
||||
self._set_hover_cursor(False)
|
||||
cursor_type = self.cursor_type
|
||||
if self.show_cursor and (cursor_type == "cell" or cursor_type == "row"):
|
||||
height = self.size.height - self.header_height if self.show_header else 0
|
||||
|
||||
# Determine how many rows constitutes a "page"
|
||||
offset = 0
|
||||
rows_to_scroll = 0
|
||||
row_index, column_index = self.cursor_coordinate
|
||||
for ordered_row in self.ordered_rows[row_index:]:
|
||||
offset += ordered_row.height
|
||||
if offset > height:
|
||||
break
|
||||
rows_to_scroll += 1
|
||||
|
||||
self.cursor_coordinate = Coordinate(
|
||||
row_index + rows_to_scroll - 1, column_index
|
||||
)
|
||||
self._scroll_cursor_into_view()
|
||||
else:
|
||||
super().action_page_down()
|
||||
|
||||
def action_page_up(self) -> None:
|
||||
"""Move the cursor one page up."""
|
||||
self._set_hover_cursor(False)
|
||||
cursor_type = self.cursor_type
|
||||
if self.show_cursor and (cursor_type == "cell" or cursor_type == "row"):
|
||||
height = self.size.height - self.header_height if self.show_header else 0
|
||||
|
||||
# Determine how many rows constitutes a "page"
|
||||
offset = 0
|
||||
rows_to_scroll = 0
|
||||
row_index, column_index = self.cursor_coordinate
|
||||
for ordered_row in self.ordered_rows[: row_index + 1]:
|
||||
offset += ordered_row.height
|
||||
if offset > height:
|
||||
break
|
||||
rows_to_scroll += 1
|
||||
|
||||
self.cursor_coordinate = Coordinate(
|
||||
row_index - rows_to_scroll + 1, column_index
|
||||
)
|
||||
self._scroll_cursor_into_view()
|
||||
else:
|
||||
super().action_page_up()
|
||||
|
||||
def action_scroll_home(self) -> None:
|
||||
"""Scroll to the top of the data table."""
|
||||
self._set_hover_cursor(False)
|
||||
cursor_type = self.cursor_type
|
||||
if self.show_cursor and (cursor_type == "cell" or cursor_type == "row"):
|
||||
row_index, column_index = self.cursor_coordinate
|
||||
self.cursor_coordinate = Coordinate(0, column_index)
|
||||
self._scroll_cursor_into_view()
|
||||
else:
|
||||
super().action_scroll_home()
|
||||
|
||||
def action_scroll_end(self) -> None:
|
||||
"""Scroll to the bottom of the data table."""
|
||||
self._set_hover_cursor(False)
|
||||
cursor_type = self.cursor_type
|
||||
if self.show_cursor and (cursor_type == "cell" or cursor_type == "row"):
|
||||
row_index, column_index = self.cursor_coordinate
|
||||
self.cursor_coordinate = Coordinate(self.row_count - 1, column_index)
|
||||
self._scroll_cursor_into_view()
|
||||
else:
|
||||
super().action_scroll_end()
|
||||
|
||||
def action_cursor_up(self) -> None:
|
||||
self._set_hover_cursor(False)
|
||||
cursor_type = self.cursor_type
|
||||
|
||||
@@ -167,10 +167,36 @@ async def test_datatable_message_emission():
|
||||
async def test_empty_table_interactions():
|
||||
app = DataTableApp()
|
||||
async with app.run_test() as pilot:
|
||||
await pilot.press("enter", "up", "down", "left", "right")
|
||||
await pilot.press(
|
||||
"enter", "up", "down", "left", "right", "home", "end", "pagedown", "pageup"
|
||||
)
|
||||
assert app.message_names == []
|
||||
|
||||
|
||||
async def test_cursor_movement_with_home_pagedown_etc():
|
||||
app = DataTableApp()
|
||||
|
||||
async with app.run_test() as pilot:
|
||||
table = app.query_one(DataTable)
|
||||
table.add_columns("A", "B")
|
||||
table.add_rows(ROWS)
|
||||
await pilot.press("right", "pagedown")
|
||||
await pilot.pause()
|
||||
assert table.cursor_coordinate == Coordinate(2, 1)
|
||||
|
||||
await pilot.press("pageup")
|
||||
await pilot.pause()
|
||||
assert table.cursor_coordinate == Coordinate(0, 1)
|
||||
|
||||
await pilot.press("end")
|
||||
await pilot.pause()
|
||||
assert table.cursor_coordinate == Coordinate(2, 1)
|
||||
|
||||
await pilot.press("home")
|
||||
await pilot.pause()
|
||||
assert table.cursor_coordinate == Coordinate(0, 1)
|
||||
|
||||
|
||||
async def test_add_rows():
|
||||
app = DataTableApp()
|
||||
async with app.run_test():
|
||||
|
||||
Reference in New Issue
Block a user