mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Merge pull request #1700 from davep/tree-select-expand-divorce
Enhance `Tree` so that there is a expand/collapse action that is separate from select
This commit is contained in:
@@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
- Added more keyboard actions and related bindings to `Input` https://github.com/Textualize/textual/pull/1676
|
||||
- Added App.scroll_sensitivity_x and App.scroll_sensitivity_y to adjust how many lines the scroll wheel moves the scroll position https://github.com/Textualize/textual/issues/928
|
||||
- Added Shift+scroll wheel and ctrl+scroll wheel to scroll horizontally
|
||||
- Added `Tree.action_toggle_node` to toggle a node without selecting, and bound it to <kbd>Space</kbd> https://github.com/Textualize/textual/issues/1433
|
||||
|
||||
### Changed
|
||||
|
||||
|
||||
@@ -284,6 +284,7 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
|
||||
|
||||
BINDINGS: ClassVar[list[BindingType]] = [
|
||||
Binding("enter", "select_cursor", "Select", show=False),
|
||||
Binding("space", "toggle_node", "Toggle", show=False),
|
||||
Binding("up", "cursor_up", "Cursor Up", show=False),
|
||||
Binding("down", "cursor_down", "Cursor Down", show=False),
|
||||
]
|
||||
@@ -291,6 +292,7 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
|
||||
| Key(s) | Description |
|
||||
| :- | :- |
|
||||
| enter | Select the current item. |
|
||||
| space | Toggle the expand/collapsed space of the current item. |
|
||||
| up | Move the cursor up. |
|
||||
| down | Move the cursor down. |
|
||||
"""
|
||||
@@ -971,6 +973,7 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
|
||||
self._invalidate()
|
||||
|
||||
def action_cursor_up(self) -> None:
|
||||
"""Move the cursor up one node."""
|
||||
if self.cursor_line == -1:
|
||||
self.cursor_line = self.last_line
|
||||
else:
|
||||
@@ -978,6 +981,7 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
|
||||
self.scroll_to_line(self.cursor_line)
|
||||
|
||||
def action_cursor_down(self) -> None:
|
||||
"""Move the cursor down one node."""
|
||||
if self.cursor_line == -1:
|
||||
self.cursor_line = 0
|
||||
else:
|
||||
@@ -985,26 +989,50 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
|
||||
self.scroll_to_line(self.cursor_line)
|
||||
|
||||
def action_page_down(self) -> None:
|
||||
"""Move the cursor down a page's-worth of nodes."""
|
||||
if self.cursor_line == -1:
|
||||
self.cursor_line = 0
|
||||
self.cursor_line += self.scrollable_content_region.height - 1
|
||||
self.scroll_to_line(self.cursor_line)
|
||||
|
||||
def action_page_up(self) -> None:
|
||||
"""Move the cursor up a page's-worth of nodes."""
|
||||
if self.cursor_line == -1:
|
||||
self.cursor_line = self.last_line
|
||||
self.cursor_line -= self.scrollable_content_region.height - 1
|
||||
self.scroll_to_line(self.cursor_line)
|
||||
|
||||
def action_scroll_home(self) -> None:
|
||||
"""Move the cursor to the top of the tree."""
|
||||
self.cursor_line = 0
|
||||
self.scroll_to_line(self.cursor_line)
|
||||
|
||||
def action_scroll_end(self) -> None:
|
||||
"""Move the cursor to the bottom of the tree.
|
||||
|
||||
Note:
|
||||
Here bottom means vertically, not branch depth.
|
||||
"""
|
||||
self.cursor_line = self.last_line
|
||||
self.scroll_to_line(self.cursor_line)
|
||||
|
||||
def action_toggle_node(self) -> None:
|
||||
"""Toggle the expanded state of the target node."""
|
||||
try:
|
||||
line = self._tree_lines[self.cursor_line]
|
||||
except IndexError:
|
||||
pass
|
||||
else:
|
||||
self._toggle_node(line.path[-1])
|
||||
|
||||
def action_select_cursor(self) -> None:
|
||||
"""Cause a select event for the target node.
|
||||
|
||||
Note:
|
||||
If `auto_expand` is `True` use of this action on a non-leaf node
|
||||
will cause both an expand/collapse event to occour, as well as a
|
||||
selected event.
|
||||
"""
|
||||
try:
|
||||
line = self._tree_lines[self.cursor_line]
|
||||
except IndexError:
|
||||
|
||||
@@ -48,23 +48,26 @@ async def test_tree_node_selected_message() -> None:
|
||||
assert pilot.app.messages == ["NodeExpanded", "NodeSelected"]
|
||||
|
||||
|
||||
async def test_tree_node_selected_message_no_auto() -> None:
|
||||
"""Selecting a node should result in only a selected message being emitted."""
|
||||
async with TreeApp().run_test() as pilot:
|
||||
pilot.app.query_one(MyTree).auto_expand = False
|
||||
await pilot.press("enter")
|
||||
assert pilot.app.messages == ["NodeSelected"]
|
||||
|
||||
|
||||
async def test_tree_node_expanded_message() -> None:
|
||||
"""Expanding a node should result in an expanded message being emitted."""
|
||||
async with TreeApp().run_test() as pilot:
|
||||
await pilot.press("enter")
|
||||
assert pilot.app.messages == ["NodeExpanded", "NodeSelected"]
|
||||
await pilot.press("space")
|
||||
assert pilot.app.messages == ["NodeExpanded"]
|
||||
|
||||
|
||||
async def test_tree_node_collapsed_message() -> None:
|
||||
"""Collapsing a node should result in a collapsed message being emitted."""
|
||||
async with TreeApp().run_test() as pilot:
|
||||
await pilot.press("enter", "enter")
|
||||
assert pilot.app.messages == [
|
||||
"NodeExpanded",
|
||||
"NodeSelected",
|
||||
"NodeCollapsed",
|
||||
"NodeSelected",
|
||||
]
|
||||
await pilot.press("space", "space")
|
||||
assert pilot.app.messages == ["NodeExpanded", "NodeCollapsed"]
|
||||
|
||||
|
||||
async def test_tree_node_highlighted_message() -> None:
|
||||
|
||||
Reference in New Issue
Block a user