Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

List view #1143

Merged
merged 44 commits into from
Dec 9, 2022
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
9cd6bda
Add ListView and ListItem skeletons
darrenburns Nov 7, 2022
93c7bf3
Add Highlighted and Selected messages to ListView
darrenburns Nov 7, 2022
f6111e7
Add _list_view module and export types
darrenburns Nov 7, 2022
9f46df4
Merge branch 'main' of github.com:Textualize/textual into list-view
darrenburns Nov 7, 2022
bab7889
Adding bindings to ListView
darrenburns Nov 7, 2022
8c9baaf
Fix containers not being focusable
darrenburns Nov 7, 2022
46bc494
Fix tests to allow for focusable containers
darrenburns Nov 7, 2022
d027adf
Add test for non-focusable container with focusable children
darrenburns Nov 7, 2022
58b43ab
Fix a typo in a test
darrenburns Nov 7, 2022
ed06e28
Merge branch 'main' of github.com:Textualize/textual into list-view
darrenburns Nov 7, 2022
ab5d9bd
Merge branch 'support-focusable-containers' into list-view
darrenburns Nov 7, 2022
17dc927
Merge branch 'main' of github.com:Textualize/textual into list-view
darrenburns Nov 8, 2022
6042346
Add list view and docs
darrenburns Nov 8, 2022
fb9502f
Merge branch 'main' of github.com:Textualize/textual into list-view
darrenburns Nov 9, 2022
5bc1606
Merge branch 'main' of github.com:Textualize/textual into list-view
darrenburns Nov 15, 2022
58cbf3a
Switch back to reactives for list view highlighting
darrenburns Nov 15, 2022
d665a6e
Updates to list view, add append and a (currently broken) remove
darrenburns Nov 15, 2022
44cbce1
Add _dom_lock to App
darrenburns Nov 16, 2022
a131ac5
Merge branch 'main' of github.com:Textualize/textual into dom-lock
darrenburns Nov 16, 2022
9572cbd
Remove in a task, lock the DOM
darrenburns Nov 16, 2022
c1590fd
Use async with with lock
darrenburns Nov 16, 2022
8b7670e
Merge branch 'dom-lock' into list-view
darrenburns Nov 16, 2022
cc0a5e3
Merge branch 'main' of github.com:Textualize/textual into list-view
darrenburns Nov 18, 2022
5ac3afd
Remove unused handler
darrenburns Nov 21, 2022
a6c350b
Merge branch 'main' of github.com:Textualize/textual into list-view
darrenburns Nov 21, 2022
b5a1a17
Fix issue where ListView appeared detached from Screen
darrenburns Nov 21, 2022
cbbfcb9
Removing some print debugging
darrenburns Nov 21, 2022
eafb9ec
Adding example for list itemm
darrenburns Nov 21, 2022
b21eb06
Adding list view snapshot test with key presses
darrenburns Nov 21, 2022
afafc05
Update snapshot
darrenburns Nov 21, 2022
c57f6b9
Fix click handler
darrenburns Nov 21, 2022
be31894
Fix snapshot test
darrenburns Nov 21, 2022
4e0c77e
Ensure mkdocs.yml is up to date with listview stuff
darrenburns Nov 21, 2022
1317d19
Some tidy up/changes from code review
darrenburns Nov 23, 2022
eb8c078
Merge branch 'main' of github.com:willmcgugan/textual into list-view
darrenburns Nov 23, 2022
01cbd7b
Update snapshot for ListView - keys no longer shown in footer
darrenburns Nov 23, 2022
853d056
PR feedback
darrenburns Nov 30, 2022
4834807
Update docs/widgets/list_item.md
darrenburns Nov 30, 2022
422f9be
Update docs/widgets/list_view.md
darrenburns Nov 30, 2022
0698bc2
Merge branch 'list-view' of github.com:Textualize/textual into list-view
darrenburns Nov 30, 2022
f0c4023
Merge branch 'main' of github.com:Textualize/textual into list-view
darrenburns Nov 30, 2022
29f4887
Use bindings for handling cursor
darrenburns Nov 30, 2022
cebeafb
Merge branch 'main' into list-view
willmcgugan Dec 9, 2022
24a182c
fix snapshot
willmcgugan Dec 9, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/api/list_item.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
::: textual.widgets.ListItem
1 change: 1 addition & 0 deletions docs/api/list_view.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
::: textual.widgets.ListView
13 changes: 13 additions & 0 deletions docs/examples/widgets/list_view.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Screen {
align: center middle;
}

ListView {
width: 30;
height: auto;
margin: 2 2;
}

Label {
padding: 1 2;
}
17 changes: 17 additions & 0 deletions docs/examples/widgets/list_view.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from textual.app import App, ComposeResult
from textual.widgets import ListView, ListItem, Label, Footer


class ListViewExample(App):
def compose(self) -> ComposeResult:
yield ListView(
ListItem(Label("One")),
ListItem(Label("Two")),
ListItem(Label("Three")),
)
yield Footer()


app = ListViewExample(css_path="list_view.css")
if __name__ == "__main__":
app.run()
40 changes: 40 additions & 0 deletions docs/widgets/list_item.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# List Item

`ListItem` is the type of the elements in a `ListView`.

- [] Focusable
- [] Container

## Example

The example below shows an app with a simple `ListView`, consisting
of multiple `ListItem`s. The arrow keys can be used to navigate the list.

=== "Output"

```{.textual path="docs/examples/widgets/list_view.py"}
```

=== "list_view.py"

```python
--8<-- "docs/examples/widgets/list_view.py"
```

## Reactive Attributes

| Name | Type | Default | Description |
|---------------|--------|---------|--------------------------------------|
| `highlighted` | `bool` | `False` | True if this ListItem is highlighted |

## Messages

#### Attributes

| attribute | type | purpose |
|-----------|------------|-----------------------------|
| `item` | `ListItem` | The item that was selected. |
darrenburns marked this conversation as resolved.
Show resolved Hide resolved

## See Also

* [ListItem](../api/list_item.md) code reference
78 changes: 78 additions & 0 deletions docs/widgets/list_view.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# List View

Displays a vertical list of `ListItem`s which can be highlighted and selected.
Supports keyboard navigation.

- [x] Focusable
- [x] Container

## Example

The example below shows an app with a simple `ListView`.

=== "Output"

```{.textual path="docs/examples/widgets/list_view.py"}
```

=== "list_view.py"

```python
--8<-- "docs/examples/widgets/list_view.py"
```

## Reactive Attributes

| Name | Type | Default | Description |
|---------|-------|---------|---------------------------------|
| `index` | `int` | `0` | The currently highlighted index |

## Messages

### Highlighted

The `ListView.Highlighted` message is emitted when the highlight changes.
This happens when you use the arrow keys on your keyboard and when you
click on a list item.

- [x] Bubbles

#### Attributes

| attribute | type | purpose |
|-----------|------------|--------------------------------|
| `item` | `ListItem` | The item that was highlighted. |

### Selected

The `ListView.Selected` message is emitted when a list item is selected.
You can select a list item by pressing ++enter++ while it is highlighted,
or by clicking on it.

- [x] Bubbles

#### Attributes

| attribute | type | purpose |
|-----------|------------|-----------------------------|
| `item` | `ListItem` | The item that was selected. |
rodrigogiraoserrao marked this conversation as resolved.
Show resolved Hide resolved


### ChildrenUpdated

The `ListView.ChildrenUpdated` message is emitted when the elements in the `ListView`
are changed (e.g. a child is added, or the list is cleared)
darrenburns marked this conversation as resolved.
Show resolved Hide resolved

- [x] Bubbles

#### Attributes

| attribute | type | purpose |
|------------|------------------|---------------------------|
| `children` | `list[ListItem]` | The new ListView children |



## See Also

* [ListView](../api/list_view.md) code reference
4 changes: 4 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ nav:
- "widgets/header.md"
- "widgets/input.md"
- "widgets/label.md"
- "widgets/list_view.md"
- "widgets/list_item.md"
- "widgets/static.md"
- "widgets/tree_control.md"
- API:
Expand All @@ -111,6 +113,8 @@ nav:
- "api/geometry.md"
- "api/header.md"
- "api/label.md"
- "api/list_view.md"
- "api/list_item.md"
- "api/message_pump.md"
- "api/message.md"
- "api/pilot.md"
Expand Down
18 changes: 0 additions & 18 deletions src/textual/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -1918,24 +1918,6 @@ def _detach_from_dom(self, widgets: list[Widget]) -> list[Widget]:
# prune event.
return pruned_remove

async def _on_prune(self, event: events.Prune) -> None:
"""Handle a prune event.

Args:
event (events.Prune): The prune event.
"""

try:
# Prune all the widgets.
for widget in event.widgets:
await self._prune_node(widget)
finally:
# Finally, flag that we're done.
event.finished_flag.set()

# Flag that the layout needs refreshing.
self.refresh(layout=True)

def _walk_children(self, root: Widget) -> Iterable[list[Widget]]:
"""Walk children depth first, generating widgets and a list of their siblings.

Expand Down
2 changes: 1 addition & 1 deletion src/textual/dom.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ def screen(self) -> "Screen":
from .screen import Screen

node = self
while node and not isinstance(node, Screen):
while node is not None and not isinstance(node, Screen):
Copy link
Member Author

@darrenburns darrenburns Nov 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the line that ultimately resulted in those weird NoScreen errors. Because ListView has a __len__, the condition was evaluating as False and terminating the search for the Screen.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah. Good catch!

node = node._parent
if not isinstance(node, Screen):
raise NoScreen("node has no screen")
Expand Down
6 changes: 5 additions & 1 deletion src/textual/widgets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@
from ._footer import Footer
from ._header import Header
from ._label import Label
from ._placeholder import Placeholder
from ._list_view import ListView
from ._list_item import ListItem
from ._pretty import Pretty
from ._placeholder import Placeholder
from ._static import Static
from ._input import Input
from ._text_log import TextLog
Expand All @@ -31,6 +33,8 @@
"DirectoryTree",
"Footer",
"Header",
"ListItem",
"ListView",
"Label",
"Placeholder",
"Pretty",
Expand Down
2 changes: 2 additions & 0 deletions src/textual/widgets/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ from ._directory_tree import DirectoryTree as DirectoryTree
from ._footer import Footer as Footer
from ._header import Header as Header
from ._label import Label as Label
from ._list_view import ListView as ListView
from ._list_item import ListItem as ListItem
from ._placeholder import Placeholder as Placeholder
from ._pretty import Pretty as Pretty
from ._static import Static as Static
Expand Down
39 changes: 39 additions & 0 deletions src/textual/widgets/_list_item.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from textual import events
from textual.message import Message
from textual.reactive import reactive
from textual.widget import Widget


class ListItem(Widget, can_focus=False):
DEFAULT_CSS = """
ListItem {
color: $text;
height: auto;
background: $panel-lighten-1;
overflow: hidden hidden;
}
ListItem > Widget :hover {
background: $boost;
}
ListView ListItem.--highlight {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add a > here? For the (hopefully rare) scenario of nested ListViews

background: $accent 50%;
}
ListView:focus ListItem.--highlight {
background: $accent;
}
ListItem > Widget {
height: auto;
}
"""
highlighted = reactive(False)

def on_click(self, event: events.Click) -> None:
self.emit_no_wait(self._ChildClicked(self))

def watch_highlighted(self, value: bool) -> None:
self.set_class(value, "--highlight")

class _ChildClicked(Message):
"""For informing with the parent ListView that we were clicked"""

pass
Loading