Skip to content

Commit

Permalink
Merge pull request #1143 from Textualize/list-view
Browse files Browse the repository at this point in the history
List view
  • Loading branch information
willmcgugan authored Dec 9, 2022
2 parents d91ea42 + 24a182c commit 54a2e3e
Show file tree
Hide file tree
Showing 15 changed files with 525 additions and 20 deletions.
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()
46 changes: 46 additions & 0 deletions docs/widgets/list_item.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# 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

### Selected

The `ListItem.Selected` message is sent when the item is selected.

- [x] Bubbles

#### Attributes

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

## 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. |


### 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).

- [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 @@ -99,6 +99,8 @@ nav:
- "widgets/index.md"
- "widgets/input.md"
- "widgets/label.md"
- "widgets/list_view.md"
- "widgets/list_item.md"
- "widgets/placeholder.md"
- "widgets/static.md"
- "widgets/tree.md"
Expand All @@ -119,6 +121,8 @@ nav:
- "api/header.md"
- "api/input.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 @@ -1955,24 +1955,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 @@ -289,7 +289,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):
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 @@ -8,7 +8,6 @@
# but also to the `__init__.pyi` file in this same folder - otherwise text editors and type checkers won't
# be able to "see" them.
if typing.TYPE_CHECKING:

from ._button import Button
from ._checkbox import Checkbox
from ._data_table import DataTable
Expand All @@ -17,6 +16,8 @@
from ._header import Header
from ._input import Input
from ._label import Label
from ._list_item import ListItem
from ._list_view import ListView
from ._placeholder import Placeholder
from ._pretty import Pretty
from ._static import Static
Expand All @@ -26,6 +27,7 @@
from ._welcome import Welcome
from ..widget import Widget


__all__ = [
"Button",
"Checkbox",
Expand All @@ -35,6 +37,8 @@
"Header",
"Input",
"Label",
"ListItem",
"ListView",
"Placeholder",
"Pretty",
"Static",
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 {
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

0 comments on commit 54a2e3e

Please sign in to comment.