Skip to content

Commit

Permalink
Merge pull request #1700 from davep/tree-select-expand-divorce
Browse files Browse the repository at this point in the history
Enhance `Tree` so that there is a expand/collapse action that is separate from select
  • Loading branch information
davep authored Jan 31, 2023
2 parents fc22576 + 6db5217 commit ff76157
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 9 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
28 changes: 28 additions & 0 deletions src/textual/widgets/_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,13 +284,15 @@ 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),
]
"""
| 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. |
"""
Expand Down Expand Up @@ -971,40 +973,66 @@ def _on_styles_updated(self) -> None:
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:
self.cursor_line -= 1
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:
self.cursor_line += 1
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:
Expand Down
21 changes: 12 additions & 9 deletions tests/tree/test_tree_messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down

0 comments on commit ff76157

Please sign in to comment.