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 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 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 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
|
### Changed
|
||||||
|
|
||||||
|
|||||||
@@ -284,6 +284,7 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
|
|||||||
|
|
||||||
BINDINGS: ClassVar[list[BindingType]] = [
|
BINDINGS: ClassVar[list[BindingType]] = [
|
||||||
Binding("enter", "select_cursor", "Select", show=False),
|
Binding("enter", "select_cursor", "Select", show=False),
|
||||||
|
Binding("space", "toggle_node", "Toggle", show=False),
|
||||||
Binding("up", "cursor_up", "Cursor Up", show=False),
|
Binding("up", "cursor_up", "Cursor Up", show=False),
|
||||||
Binding("down", "cursor_down", "Cursor Down", 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 |
|
| Key(s) | Description |
|
||||||
| :- | :- |
|
| :- | :- |
|
||||||
| enter | Select the current item. |
|
| enter | Select the current item. |
|
||||||
|
| space | Toggle the expand/collapsed space of the current item. |
|
||||||
| up | Move the cursor up. |
|
| up | Move the cursor up. |
|
||||||
| down | Move the cursor down. |
|
| down | Move the cursor down. |
|
||||||
"""
|
"""
|
||||||
@@ -971,6 +973,7 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
|
|||||||
self._invalidate()
|
self._invalidate()
|
||||||
|
|
||||||
def action_cursor_up(self) -> None:
|
def action_cursor_up(self) -> None:
|
||||||
|
"""Move the cursor up one node."""
|
||||||
if self.cursor_line == -1:
|
if self.cursor_line == -1:
|
||||||
self.cursor_line = self.last_line
|
self.cursor_line = self.last_line
|
||||||
else:
|
else:
|
||||||
@@ -978,6 +981,7 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
|
|||||||
self.scroll_to_line(self.cursor_line)
|
self.scroll_to_line(self.cursor_line)
|
||||||
|
|
||||||
def action_cursor_down(self) -> None:
|
def action_cursor_down(self) -> None:
|
||||||
|
"""Move the cursor down one node."""
|
||||||
if self.cursor_line == -1:
|
if self.cursor_line == -1:
|
||||||
self.cursor_line = 0
|
self.cursor_line = 0
|
||||||
else:
|
else:
|
||||||
@@ -985,26 +989,50 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
|
|||||||
self.scroll_to_line(self.cursor_line)
|
self.scroll_to_line(self.cursor_line)
|
||||||
|
|
||||||
def action_page_down(self) -> None:
|
def action_page_down(self) -> None:
|
||||||
|
"""Move the cursor down a page's-worth of nodes."""
|
||||||
if self.cursor_line == -1:
|
if self.cursor_line == -1:
|
||||||
self.cursor_line = 0
|
self.cursor_line = 0
|
||||||
self.cursor_line += self.scrollable_content_region.height - 1
|
self.cursor_line += self.scrollable_content_region.height - 1
|
||||||
self.scroll_to_line(self.cursor_line)
|
self.scroll_to_line(self.cursor_line)
|
||||||
|
|
||||||
def action_page_up(self) -> None:
|
def action_page_up(self) -> None:
|
||||||
|
"""Move the cursor up a page's-worth of nodes."""
|
||||||
if self.cursor_line == -1:
|
if self.cursor_line == -1:
|
||||||
self.cursor_line = self.last_line
|
self.cursor_line = self.last_line
|
||||||
self.cursor_line -= self.scrollable_content_region.height - 1
|
self.cursor_line -= self.scrollable_content_region.height - 1
|
||||||
self.scroll_to_line(self.cursor_line)
|
self.scroll_to_line(self.cursor_line)
|
||||||
|
|
||||||
def action_scroll_home(self) -> None:
|
def action_scroll_home(self) -> None:
|
||||||
|
"""Move the cursor to the top of the tree."""
|
||||||
self.cursor_line = 0
|
self.cursor_line = 0
|
||||||
self.scroll_to_line(self.cursor_line)
|
self.scroll_to_line(self.cursor_line)
|
||||||
|
|
||||||
def action_scroll_end(self) -> None:
|
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.cursor_line = self.last_line
|
||||||
self.scroll_to_line(self.cursor_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:
|
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:
|
try:
|
||||||
line = self._tree_lines[self.cursor_line]
|
line = self._tree_lines[self.cursor_line]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
|
|||||||
@@ -48,23 +48,26 @@ async def test_tree_node_selected_message() -> None:
|
|||||||
assert pilot.app.messages == ["NodeExpanded", "NodeSelected"]
|
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:
|
async def test_tree_node_expanded_message() -> None:
|
||||||
"""Expanding a node should result in an expanded message being emitted."""
|
"""Expanding a node should result in an expanded message being emitted."""
|
||||||
async with TreeApp().run_test() as pilot:
|
async with TreeApp().run_test() as pilot:
|
||||||
await pilot.press("enter")
|
await pilot.press("space")
|
||||||
assert pilot.app.messages == ["NodeExpanded", "NodeSelected"]
|
assert pilot.app.messages == ["NodeExpanded"]
|
||||||
|
|
||||||
|
|
||||||
async def test_tree_node_collapsed_message() -> None:
|
async def test_tree_node_collapsed_message() -> None:
|
||||||
"""Collapsing a node should result in a collapsed message being emitted."""
|
"""Collapsing a node should result in a collapsed message being emitted."""
|
||||||
async with TreeApp().run_test() as pilot:
|
async with TreeApp().run_test() as pilot:
|
||||||
await pilot.press("enter", "enter")
|
await pilot.press("space", "space")
|
||||||
assert pilot.app.messages == [
|
assert pilot.app.messages == ["NodeExpanded", "NodeCollapsed"]
|
||||||
"NodeExpanded",
|
|
||||||
"NodeSelected",
|
|
||||||
"NodeCollapsed",
|
|
||||||
"NodeSelected",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
async def test_tree_node_highlighted_message() -> None:
|
async def test_tree_node_highlighted_message() -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user