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("down", "cursor_down", "Cursor Down", show=False),
|
||||||
Binding("right", "cursor_right", "Cursor Right", show=False),
|
Binding("right", "cursor_right", "Cursor Right", show=False),
|
||||||
Binding("left", "cursor_left", "Cursor Left", 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 |
|
| Key(s) | Description |
|
||||||
@@ -247,7 +249,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
|||||||
background: $surface ;
|
background: $surface ;
|
||||||
color: $text;
|
color: $text;
|
||||||
height: auto;
|
height: auto;
|
||||||
max-height: 100vh
|
max-height: 100vh;
|
||||||
}
|
}
|
||||||
DataTable > .datatable--header {
|
DataTable > .datatable--header {
|
||||||
text-style: bold;
|
text-style: bold;
|
||||||
@@ -1976,6 +1978,76 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
|||||||
self._scroll_cursor_into_view(animate=True)
|
self._scroll_cursor_into_view(animate=True)
|
||||||
event.stop()
|
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:
|
def action_cursor_up(self) -> None:
|
||||||
self._set_hover_cursor(False)
|
self._set_hover_cursor(False)
|
||||||
cursor_type = self.cursor_type
|
cursor_type = self.cursor_type
|
||||||
|
|||||||
@@ -167,10 +167,36 @@ async def test_datatable_message_emission():
|
|||||||
async def test_empty_table_interactions():
|
async def test_empty_table_interactions():
|
||||||
app = DataTableApp()
|
app = DataTableApp()
|
||||||
async with app.run_test() as pilot:
|
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 == []
|
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():
|
async def test_add_rows():
|
||||||
app = DataTableApp()
|
app = DataTableApp()
|
||||||
async with app.run_test():
|
async with app.run_test():
|
||||||
|
|||||||
Reference in New Issue
Block a user