mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Merge pull request #2102 from Textualize/verb-methods-return-self
Return 'self' in some widget verb methods.
This commit is contained in:
10
CHANGELOG.md
10
CHANGELOG.md
@@ -33,6 +33,16 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
- Breaking change: changed default behaviour of `Vertical` (see `VerticalScroll`) https://github.com/Textualize/textual/issues/1957
|
- Breaking change: changed default behaviour of `Vertical` (see `VerticalScroll`) https://github.com/Textualize/textual/issues/1957
|
||||||
- The default `overflow` style for `Horizontal` was changed to `hidden hidden` https://github.com/Textualize/textual/issues/1957
|
- The default `overflow` style for `Horizontal` was changed to `hidden hidden` https://github.com/Textualize/textual/issues/1957
|
||||||
- `DirectoryTree` also accepts `pathlib.Path` objects as the path to list https://github.com/Textualize/textual/issues/1438
|
- `DirectoryTree` also accepts `pathlib.Path` objects as the path to list https://github.com/Textualize/textual/issues/1438
|
||||||
|
- Some widget methods now return `self` instead of `None` https://github.com/Textualize/textual/pull/2102:
|
||||||
|
- `Widget`: `refresh`, `focus`, `reset_focus`
|
||||||
|
- `Button.press`
|
||||||
|
- `DataTable`: `clear`, `refresh_coordinate`, `refresh_row`, `refresh_column`, `sort`
|
||||||
|
- `Placehoder.cycle_variant`
|
||||||
|
- `Switch.toggle`
|
||||||
|
- `Tabs.clear`
|
||||||
|
- `TextLog`: `write`, `clear`
|
||||||
|
- `TreeNode`: `expand`, `expand_all`, `collapse`, `collapse_all`, `toggle`, `toggle_all`
|
||||||
|
- `Tree`: `clear`, `reset`
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ if TYPE_CHECKING:
|
|||||||
from .css.query import DOMQuery, QueryType
|
from .css.query import DOMQuery, QueryType
|
||||||
from .screen import Screen
|
from .screen import Screen
|
||||||
from .widget import Widget
|
from .widget import Widget
|
||||||
from typing_extensions import TypeAlias
|
from typing_extensions import Self, TypeAlias
|
||||||
|
|
||||||
from typing_extensions import Literal
|
from typing_extensions import Literal
|
||||||
|
|
||||||
@@ -950,5 +950,5 @@ class DOMNode(MessagePump):
|
|||||||
has_pseudo_classes = self.pseudo_classes.issuperset(class_names)
|
has_pseudo_classes = self.pseudo_classes.issuperset(class_names)
|
||||||
return has_pseudo_classes
|
return has_pseudo_classes
|
||||||
|
|
||||||
def refresh(self, *, repaint: bool = True, layout: bool = False) -> None:
|
def refresh(self, *, repaint: bool = True, layout: bool = False) -> Self:
|
||||||
pass
|
return self
|
||||||
|
|||||||
@@ -2560,7 +2560,7 @@ class Widget(DOMNode):
|
|||||||
*regions: Region,
|
*regions: Region,
|
||||||
repaint: bool = True,
|
repaint: bool = True,
|
||||||
layout: bool = False,
|
layout: bool = False,
|
||||||
) -> None:
|
) -> Self:
|
||||||
"""Initiate a refresh of the widget.
|
"""Initiate a refresh of the widget.
|
||||||
|
|
||||||
This method sets an internal flag to perform a refresh, which will be done on the
|
This method sets an internal flag to perform a refresh, which will be done on the
|
||||||
@@ -2576,8 +2576,11 @@ class Widget(DOMNode):
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
*regions: Additional screen regions to mark as dirty.
|
*regions: Additional screen regions to mark as dirty.
|
||||||
repaint: Repaint the widget (will call render() again). Defaults to True.
|
repaint: Repaint the widget (will call render() again).
|
||||||
layout: Also layout widgets in the view. Defaults to False.
|
layout: Also layout widgets in the view.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The `Widget` instance.
|
||||||
"""
|
"""
|
||||||
if layout and not self._layout_required:
|
if layout and not self._layout_required:
|
||||||
self._layout_required = True
|
self._layout_required = True
|
||||||
@@ -2595,6 +2598,7 @@ class Widget(DOMNode):
|
|||||||
self._repaint_required = True
|
self._repaint_required = True
|
||||||
|
|
||||||
self.check_idle()
|
self.check_idle()
|
||||||
|
return self
|
||||||
|
|
||||||
def remove(self) -> AwaitRemove:
|
def remove(self) -> AwaitRemove:
|
||||||
"""Remove the Widget from the DOM (effectively deleting it).
|
"""Remove the Widget from the DOM (effectively deleting it).
|
||||||
@@ -2676,12 +2680,14 @@ class Widget(DOMNode):
|
|||||||
self._layout_required = False
|
self._layout_required = False
|
||||||
screen.post_message(messages.Layout())
|
screen.post_message(messages.Layout())
|
||||||
|
|
||||||
def focus(self, scroll_visible: bool = True) -> None:
|
def focus(self, scroll_visible: bool = True) -> Self:
|
||||||
"""Give focus to this widget.
|
"""Give focus to this widget.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
scroll_visible: Scroll parent to make this widget
|
scroll_visible: Scroll parent to make this widget visible.
|
||||||
visible. Defaults to True.
|
|
||||||
|
Returns:
|
||||||
|
The `Widget` instance.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def set_focus(widget: Widget):
|
def set_focus(widget: Widget):
|
||||||
@@ -2692,13 +2698,19 @@ class Widget(DOMNode):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
self.app.call_later(set_focus, self)
|
self.app.call_later(set_focus, self)
|
||||||
|
return self
|
||||||
|
|
||||||
def reset_focus(self) -> None:
|
def reset_focus(self) -> Self:
|
||||||
"""Reset the focus (move it to the next available widget)."""
|
"""Reset the focus (move it to the next available widget).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The `Widget` instance.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
self.screen._reset_focus(self)
|
self.screen._reset_focus(self)
|
||||||
except NoScreen:
|
except NoScreen:
|
||||||
pass
|
pass
|
||||||
|
return self
|
||||||
|
|
||||||
def capture_mouse(self, capture: bool = True) -> None:
|
def capture_mouse(self, capture: bool = True) -> None:
|
||||||
"""Capture (or release) the mouse.
|
"""Capture (or release) the mouse.
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from typing import cast
|
|
||||||
|
|
||||||
import rich.repr
|
import rich.repr
|
||||||
from rich.text import Text, TextType
|
from rich.text import Text, TextType
|
||||||
from typing_extensions import Literal
|
from typing_extensions import Literal, Self
|
||||||
|
|
||||||
from .. import events
|
from .. import events
|
||||||
from ..css._error_tools import friendly_list
|
from ..css._error_tools import friendly_list
|
||||||
@@ -233,14 +232,18 @@ class Button(Static, can_focus=True):
|
|||||||
event.stop()
|
event.stop()
|
||||||
self.press()
|
self.press()
|
||||||
|
|
||||||
def press(self) -> None:
|
def press(self) -> Self:
|
||||||
"""Respond to a button press."""
|
"""Respond to a button press.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The button instance."""
|
||||||
if self.disabled or not self.display:
|
if self.disabled or not self.display:
|
||||||
return
|
return self
|
||||||
# Manage the "active" effect:
|
# Manage the "active" effect:
|
||||||
self._start_active_affect()
|
self._start_active_affect()
|
||||||
# ...and let other components know that we've just been clicked:
|
# ...and let other components know that we've just been clicked:
|
||||||
self.post_message(Button.Pressed(self))
|
self.post_message(Button.Pressed(self))
|
||||||
|
return self
|
||||||
|
|
||||||
def _start_active_affect(self) -> None:
|
def _start_active_affect(self) -> None:
|
||||||
"""Start a small animation to show the button was clicked."""
|
"""Start a small animation to show the button was clicked."""
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from rich.protocol import is_renderable
|
|||||||
from rich.segment import Segment
|
from rich.segment import Segment
|
||||||
from rich.style import Style
|
from rich.style import Style
|
||||||
from rich.text import Text, TextType
|
from rich.text import Text, TextType
|
||||||
from typing_extensions import Literal, TypeAlias
|
from typing_extensions import Literal, Self, TypeAlias
|
||||||
|
|
||||||
from .. import events
|
from .. import events
|
||||||
from .._cache import LRUCache
|
from .._cache import LRUCache
|
||||||
@@ -1156,11 +1156,14 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
|||||||
full_column_region = Region(x, 0, width, height)
|
full_column_region = Region(x, 0, width, height)
|
||||||
return full_column_region
|
return full_column_region
|
||||||
|
|
||||||
def clear(self, columns: bool = False) -> None:
|
def clear(self, columns: bool = False) -> Self:
|
||||||
"""Clear the table.
|
"""Clear the table.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
columns: Also clear the columns. Defaults to False.
|
columns: Also clear the columns.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The `DataTable` instance.
|
||||||
"""
|
"""
|
||||||
self._clear_caches()
|
self._clear_caches()
|
||||||
self._y_offsets.clear()
|
self._y_offsets.clear()
|
||||||
@@ -1176,6 +1179,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
|||||||
self._label_column = Column(self._label_column_key, Text(), auto_width=True)
|
self._label_column = Column(self._label_column_key, Text(), auto_width=True)
|
||||||
self._labelled_row_exists = False
|
self._labelled_row_exists = False
|
||||||
self.refresh()
|
self.refresh()
|
||||||
|
return self
|
||||||
|
|
||||||
def add_column(
|
def add_column(
|
||||||
self, label: TextType, *, width: int | None = None, key: str | None = None
|
self, label: TextType, *, width: int | None = None, key: str | None = None
|
||||||
@@ -1333,49 +1337,66 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
|||||||
self._updated_cells.clear()
|
self._updated_cells.clear()
|
||||||
self._update_column_widths(updated_columns)
|
self._update_column_widths(updated_columns)
|
||||||
|
|
||||||
def refresh_coordinate(self, coordinate: Coordinate) -> None:
|
def refresh_coordinate(self, coordinate: Coordinate) -> Self:
|
||||||
"""Refresh the cell at a coordinate.
|
"""Refresh the cell at a coordinate.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
coordinate: The coordinate to refresh.
|
coordinate: The coordinate to refresh.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The `DataTable` instance.
|
||||||
"""
|
"""
|
||||||
if not self.is_valid_coordinate(coordinate):
|
if not self.is_valid_coordinate(coordinate):
|
||||||
return
|
return self
|
||||||
region = self._get_cell_region(coordinate)
|
region = self._get_cell_region(coordinate)
|
||||||
self._refresh_region(region)
|
self._refresh_region(region)
|
||||||
|
return self
|
||||||
|
|
||||||
def refresh_row(self, row_index: int) -> None:
|
def refresh_row(self, row_index: int) -> Self:
|
||||||
"""Refresh the row at the given index.
|
"""Refresh the row at the given index.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
row_index: The index of the row to refresh.
|
row_index: The index of the row to refresh.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The `DataTable` instance.
|
||||||
"""
|
"""
|
||||||
if not self.is_valid_row_index(row_index):
|
if not self.is_valid_row_index(row_index):
|
||||||
return
|
return self
|
||||||
|
|
||||||
region = self._get_row_region(row_index)
|
region = self._get_row_region(row_index)
|
||||||
self._refresh_region(region)
|
self._refresh_region(region)
|
||||||
|
return self
|
||||||
|
|
||||||
def refresh_column(self, column_index: int) -> None:
|
def refresh_column(self, column_index: int) -> Self:
|
||||||
"""Refresh the column at the given index.
|
"""Refresh the column at the given index.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
column_index: The index of the column to refresh.
|
column_index: The index of the column to refresh.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The `DataTable` instance.
|
||||||
"""
|
"""
|
||||||
if not self.is_valid_column_index(column_index):
|
if not self.is_valid_column_index(column_index):
|
||||||
return
|
return self
|
||||||
|
|
||||||
region = self._get_column_region(column_index)
|
region = self._get_column_region(column_index)
|
||||||
self._refresh_region(region)
|
self._refresh_region(region)
|
||||||
|
return self
|
||||||
|
|
||||||
def _refresh_region(self, region: Region) -> None:
|
def _refresh_region(self, region: Region) -> Self:
|
||||||
"""Refresh a region of the DataTable, if it's visible within
|
"""Refresh a region of the DataTable, if it's visible within the window.
|
||||||
the window. This method will translate the region to account
|
|
||||||
for scrolling."""
|
This method will translate the region to account for scrolling.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The `DataTable` instance.
|
||||||
|
"""
|
||||||
if not self.window_region.overlaps(region):
|
if not self.window_region.overlaps(region):
|
||||||
return
|
return self
|
||||||
region = region.translate(-self.scroll_offset)
|
region = region.translate(-self.scroll_offset)
|
||||||
self.refresh(region)
|
self.refresh(region)
|
||||||
|
return self
|
||||||
|
|
||||||
def is_valid_row_index(self, row_index: int) -> bool:
|
def is_valid_row_index(self, row_index: int) -> bool:
|
||||||
"""Return a boolean indicating whether the row_index is within table bounds.
|
"""Return a boolean indicating whether the row_index is within table bounds.
|
||||||
@@ -1839,12 +1860,15 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
|||||||
self,
|
self,
|
||||||
*columns: ColumnKey | str,
|
*columns: ColumnKey | str,
|
||||||
reverse: bool = False,
|
reverse: bool = False,
|
||||||
) -> None:
|
) -> Self:
|
||||||
"""Sort the rows in the DataTable by one or more column keys.
|
"""Sort the rows in the `DataTable` by one or more column keys.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
columns: One or more columns to sort by the values in.
|
columns: One or more columns to sort by the values in.
|
||||||
reverse: If True, the sort order will be reversed.
|
reverse: If True, the sort order will be reversed.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The `DataTable` instance.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def sort_by_column_keys(
|
def sort_by_column_keys(
|
||||||
@@ -1862,6 +1886,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
|||||||
)
|
)
|
||||||
self._update_count += 1
|
self._update_count += 1
|
||||||
self.refresh()
|
self.refresh()
|
||||||
|
return self
|
||||||
|
|
||||||
def _scroll_cursor_into_view(self, animate: bool = False) -> None:
|
def _scroll_cursor_into_view(self, animate: bool = False) -> None:
|
||||||
"""When the cursor is at a boundary of the DataTable and moves out
|
"""When the cursor is at a boundary of the DataTable and moves out
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|||||||
from itertools import cycle
|
from itertools import cycle
|
||||||
|
|
||||||
from rich.console import RenderableType
|
from rich.console import RenderableType
|
||||||
from typing_extensions import Literal
|
from typing_extensions import Literal, Self
|
||||||
|
|
||||||
from .. import events
|
from .. import events
|
||||||
from ..css._error_tools import friendly_list
|
from ..css._error_tools import friendly_list
|
||||||
@@ -132,9 +132,14 @@ class Placeholder(Widget):
|
|||||||
"""
|
"""
|
||||||
return self._renderables[self.variant]
|
return self._renderables[self.variant]
|
||||||
|
|
||||||
def cycle_variant(self) -> None:
|
def cycle_variant(self) -> Self:
|
||||||
"""Get the next variant in the cycle."""
|
"""Get the next variant in the cycle.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The `Placeholder` instance.
|
||||||
|
"""
|
||||||
self.variant = next(self._variants_cycle)
|
self.variant = next(self._variants_cycle)
|
||||||
|
return self
|
||||||
|
|
||||||
def watch_variant(
|
def watch_variant(
|
||||||
self, old_variant: PlaceholderVariant, variant: PlaceholderVariant
|
self, old_variant: PlaceholderVariant, variant: PlaceholderVariant
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import ClassVar
|
from typing import TYPE_CHECKING, ClassVar
|
||||||
|
|
||||||
from rich.console import RenderableType
|
from rich.console import RenderableType
|
||||||
|
|
||||||
@@ -11,6 +11,9 @@ from ..reactive import reactive
|
|||||||
from ..scrollbar import ScrollBarRender
|
from ..scrollbar import ScrollBarRender
|
||||||
from ..widget import Widget
|
from ..widget import Widget
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from typing_extensions import Self
|
||||||
|
|
||||||
|
|
||||||
class Switch(Widget, can_focus=True):
|
class Switch(Widget, can_focus=True):
|
||||||
"""A switch widget that represents a boolean value.
|
"""A switch widget that represents a boolean value.
|
||||||
@@ -158,10 +161,14 @@ class Switch(Widget, can_focus=True):
|
|||||||
"""Toggle the state of the switch."""
|
"""Toggle the state of the switch."""
|
||||||
self.toggle()
|
self.toggle()
|
||||||
|
|
||||||
def toggle(self) -> None:
|
def toggle(self) -> Self:
|
||||||
"""Toggle the switch value.
|
"""Toggle the switch value.
|
||||||
|
|
||||||
As a result of the value changing, a `Switch.Changed` message will
|
As a result of the value changing, a `Switch.Changed` message will
|
||||||
be posted.
|
be posted.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The `Switch` instance.
|
||||||
"""
|
"""
|
||||||
self.value = not self.value
|
self.value = not self.value
|
||||||
|
return self
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import ClassVar
|
from typing import TYPE_CHECKING, ClassVar
|
||||||
|
|
||||||
import rich.repr
|
import rich.repr
|
||||||
from rich.style import Style
|
from rich.style import Style
|
||||||
@@ -18,6 +18,9 @@ from ..renderables.underline_bar import UnderlineBar
|
|||||||
from ..widget import Widget
|
from ..widget import Widget
|
||||||
from ..widgets import Static
|
from ..widgets import Static
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from typing_extensions import Self
|
||||||
|
|
||||||
|
|
||||||
class Underline(Widget):
|
class Underline(Widget):
|
||||||
"""The animated underline beneath tabs."""
|
"""The animated underline beneath tabs."""
|
||||||
@@ -316,13 +319,18 @@ class Tabs(Widget, can_focus=True):
|
|||||||
|
|
||||||
self.call_after_refresh(refresh_active)
|
self.call_after_refresh(refresh_active)
|
||||||
|
|
||||||
def clear(self) -> None:
|
def clear(self) -> Self:
|
||||||
"""Clear all the tabs."""
|
"""Clear all the tabs.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The `Tabs` instance.
|
||||||
|
"""
|
||||||
underline = self.query_one(Underline)
|
underline = self.query_one(Underline)
|
||||||
underline.highlight_start = 0
|
underline.highlight_start = 0
|
||||||
underline.highlight_end = 0
|
underline.highlight_end = 0
|
||||||
self.query("#tabs-list > Tab").remove()
|
self.query("#tabs-list > Tab").remove()
|
||||||
self.post_message(self.Cleared(self))
|
self.post_message(self.Cleared(self))
|
||||||
|
return self
|
||||||
|
|
||||||
def remove_tab(self, tab_or_id: Tab | str | None) -> None:
|
def remove_tab(self, tab_or_id: Tab | str | None) -> None:
|
||||||
"""Remove a tab.
|
"""Remove a tab.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Optional, cast
|
from typing import TYPE_CHECKING, Optional, cast
|
||||||
|
|
||||||
from rich.console import RenderableType
|
from rich.console import RenderableType
|
||||||
from rich.highlighter import ReprHighlighter
|
from rich.highlighter import ReprHighlighter
|
||||||
@@ -18,6 +18,9 @@ from ..reactive import var
|
|||||||
from ..scroll_view import ScrollView
|
from ..scroll_view import ScrollView
|
||||||
from ..strip import Strip
|
from ..strip import Strip
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from typing_extensions import Self
|
||||||
|
|
||||||
|
|
||||||
class TextLog(ScrollView, can_focus=True):
|
class TextLog(ScrollView, can_focus=True):
|
||||||
"""A widget for logging text."""
|
"""A widget for logging text."""
|
||||||
@@ -89,7 +92,7 @@ class TextLog(ScrollView, can_focus=True):
|
|||||||
width: int | None = None,
|
width: int | None = None,
|
||||||
expand: bool = False,
|
expand: bool = False,
|
||||||
shrink: bool = True,
|
shrink: bool = True,
|
||||||
) -> None:
|
) -> Self:
|
||||||
"""Write text or a rich renderable.
|
"""Write text or a rich renderable.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -97,6 +100,9 @@ class TextLog(ScrollView, can_focus=True):
|
|||||||
width: Width to render or ``None`` to use optimal width.
|
width: Width to render or ``None`` to use optimal width.
|
||||||
expand: Enable expand to widget width, or ``False`` to use `width`.
|
expand: Enable expand to widget width, or ``False`` to use `width`.
|
||||||
shrink: Enable shrinking of content to fit width.
|
shrink: Enable shrinking of content to fit width.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The `TextLog` instance.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
renderable: RenderableType
|
renderable: RenderableType
|
||||||
@@ -136,7 +142,7 @@ class TextLog(ScrollView, can_focus=True):
|
|||||||
)
|
)
|
||||||
lines = list(Segment.split_lines(segments))
|
lines = list(Segment.split_lines(segments))
|
||||||
if not lines:
|
if not lines:
|
||||||
return
|
return self
|
||||||
|
|
||||||
self.max_width = max(
|
self.max_width = max(
|
||||||
self.max_width,
|
self.max_width,
|
||||||
@@ -154,14 +160,21 @@ class TextLog(ScrollView, can_focus=True):
|
|||||||
self.virtual_size = Size(self.max_width, len(self.lines))
|
self.virtual_size = Size(self.max_width, len(self.lines))
|
||||||
self.scroll_end(animate=False)
|
self.scroll_end(animate=False)
|
||||||
|
|
||||||
def clear(self) -> None:
|
return self
|
||||||
"""Clear the text log."""
|
|
||||||
|
def clear(self) -> Self:
|
||||||
|
"""Clear the text log.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The `TextLog` instance.
|
||||||
|
"""
|
||||||
self.lines.clear()
|
self.lines.clear()
|
||||||
self._line_cache.clear()
|
self._line_cache.clear()
|
||||||
self._start_line = 0
|
self._start_line = 0
|
||||||
self.max_width = 0
|
self.max_width = 0
|
||||||
self.virtual_size = Size(self.max_width, len(self.lines))
|
self.virtual_size = Size(self.max_width, len(self.lines))
|
||||||
self.refresh()
|
self.refresh()
|
||||||
|
return self
|
||||||
|
|
||||||
def render_line(self, y: int) -> Strip:
|
def render_line(self, y: int) -> Strip:
|
||||||
scroll_x, scroll_y = self.scroll_offset
|
scroll_x, scroll_y = self.scroll_offset
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ In particular it provides `Checkbox`, `RadioButton` and `RadioSet`.
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import ClassVar
|
from typing import TYPE_CHECKING, ClassVar
|
||||||
|
|
||||||
from rich.style import Style
|
from rich.style import Style
|
||||||
from rich.text import Text, TextType
|
from rich.text import Text, TextType
|
||||||
@@ -17,6 +17,9 @@ from ..message import Message
|
|||||||
from ..reactive import reactive
|
from ..reactive import reactive
|
||||||
from ._static import Static
|
from ._static import Static
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from typing_extensions import Self
|
||||||
|
|
||||||
|
|
||||||
class ToggleButton(Static, can_focus=True):
|
class ToggleButton(Static, can_focus=True):
|
||||||
"""Base toggle button widget.
|
"""Base toggle button widget.
|
||||||
@@ -201,9 +204,14 @@ class ToggleButton(Static, can_focus=True):
|
|||||||
def get_content_height(self, container: Size, viewport: Size, width: int) -> int:
|
def get_content_height(self, container: Size, viewport: Size, width: int) -> int:
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
def toggle(self) -> None:
|
def toggle(self) -> Self:
|
||||||
"""Toggle the value of the widget."""
|
"""Toggle the value of the widget.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The `ToggleButton` instance.
|
||||||
|
"""
|
||||||
self.value = not self.value
|
self.value = not self.value
|
||||||
|
return self
|
||||||
|
|
||||||
def action_toggle(self) -> None:
|
def action_toggle(self) -> None:
|
||||||
"""Toggle the value of the widget when called as an action.
|
"""Toggle the value of the widget when called as an action.
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ from ..scroll_view import ScrollView
|
|||||||
from ..strip import Strip
|
from ..strip import Strip
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing_extensions import TypeAlias
|
from typing_extensions import Self, TypeAlias
|
||||||
|
|
||||||
NodeID = NewType("NodeID", int)
|
NodeID = NewType("NodeID", int)
|
||||||
"""The type of an ID applied to a [TreeNode][textual.widgets._tree.TreeNode]."""
|
"""The type of an ID applied to a [TreeNode][textual.widgets._tree.TreeNode]."""
|
||||||
@@ -201,15 +201,25 @@ class TreeNode(Generic[TreeDataType]):
|
|||||||
for child in self.children:
|
for child in self.children:
|
||||||
child._expand(expand_all)
|
child._expand(expand_all)
|
||||||
|
|
||||||
def expand(self) -> None:
|
def expand(self) -> Self:
|
||||||
"""Expand the node (show its children)."""
|
"""Expand the node (show its children).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The `TreeNode` instance.
|
||||||
|
"""
|
||||||
self._expand(False)
|
self._expand(False)
|
||||||
self._tree._invalidate()
|
self._tree._invalidate()
|
||||||
|
return self
|
||||||
|
|
||||||
def expand_all(self) -> None:
|
def expand_all(self) -> Self:
|
||||||
"""Expand the node (show its children) and all those below it."""
|
"""Expand the node (show its children) and all those below it.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The `TreeNode` instance.
|
||||||
|
"""
|
||||||
self._expand(True)
|
self._expand(True)
|
||||||
self._tree._invalidate()
|
self._tree._invalidate()
|
||||||
|
return self
|
||||||
|
|
||||||
def _collapse(self, collapse_all: bool) -> None:
|
def _collapse(self, collapse_all: bool) -> None:
|
||||||
"""Mark the node as collapsed (its children are hidden).
|
"""Mark the node as collapsed (its children are hidden).
|
||||||
@@ -223,29 +233,49 @@ class TreeNode(Generic[TreeDataType]):
|
|||||||
for child in self.children:
|
for child in self.children:
|
||||||
child._collapse(collapse_all)
|
child._collapse(collapse_all)
|
||||||
|
|
||||||
def collapse(self) -> None:
|
def collapse(self) -> Self:
|
||||||
"""Collapse the node (hide its children)."""
|
"""Collapse the node (hide its children).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The `TreeNode` instance.
|
||||||
|
"""
|
||||||
self._collapse(False)
|
self._collapse(False)
|
||||||
self._tree._invalidate()
|
self._tree._invalidate()
|
||||||
|
return self
|
||||||
|
|
||||||
def collapse_all(self) -> None:
|
def collapse_all(self) -> Self:
|
||||||
"""Collapse the node (hide its children) and all those below it."""
|
"""Collapse the node (hide its children) and all those below it.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The `TreeNode` instance.
|
||||||
|
"""
|
||||||
self._collapse(True)
|
self._collapse(True)
|
||||||
self._tree._invalidate()
|
self._tree._invalidate()
|
||||||
|
return self
|
||||||
|
|
||||||
def toggle(self) -> None:
|
def toggle(self) -> Self:
|
||||||
"""Toggle the node's expanded state."""
|
"""Toggle the node's expanded state.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The `TreeNode` instance.
|
||||||
|
"""
|
||||||
if self._expanded:
|
if self._expanded:
|
||||||
self.collapse()
|
self.collapse()
|
||||||
else:
|
else:
|
||||||
self.expand()
|
self.expand()
|
||||||
|
return self
|
||||||
|
|
||||||
def toggle_all(self) -> None:
|
def toggle_all(self) -> Self:
|
||||||
"""Toggle the node's expanded state and make all those below it match."""
|
"""Toggle the node's expanded state and make all those below it match.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The `TreeNode` instance.
|
||||||
|
"""
|
||||||
if self._expanded:
|
if self._expanded:
|
||||||
self.collapse_all()
|
self.collapse_all()
|
||||||
else:
|
else:
|
||||||
self.expand_all()
|
self.expand_all()
|
||||||
|
return self
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def label(self) -> TextType:
|
def label(self) -> TextType:
|
||||||
@@ -597,8 +627,12 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
|
|||||||
label = self.render_label(node, NULL_STYLE, NULL_STYLE)
|
label = self.render_label(node, NULL_STYLE, NULL_STYLE)
|
||||||
return label.cell_len
|
return label.cell_len
|
||||||
|
|
||||||
def clear(self) -> None:
|
def clear(self) -> Self:
|
||||||
"""Clear all nodes under root."""
|
"""Clear all nodes under root.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The `Tree` instance.
|
||||||
|
"""
|
||||||
self._line_cache.clear()
|
self._line_cache.clear()
|
||||||
self._tree_lines_cached = None
|
self._tree_lines_cached = None
|
||||||
self._current_id = 0
|
self._current_id = 0
|
||||||
@@ -614,17 +648,22 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
|
|||||||
)
|
)
|
||||||
self._updates += 1
|
self._updates += 1
|
||||||
self.refresh()
|
self.refresh()
|
||||||
|
return self
|
||||||
|
|
||||||
def reset(self, label: TextType, data: TreeDataType | None = None) -> None:
|
def reset(self, label: TextType, data: TreeDataType | None = None) -> Self:
|
||||||
"""Clear the tree and reset the root node.
|
"""Clear the tree and reset the root node.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
label: The label for the root node.
|
label: The label for the root node.
|
||||||
data: Optional data for the root node.
|
data: Optional data for the root node.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The `Tree` instance.
|
||||||
"""
|
"""
|
||||||
self.clear()
|
self.clear()
|
||||||
self.root.label = label
|
self.root.label = label
|
||||||
self.root.data = data
|
self.root.data = data
|
||||||
|
return self
|
||||||
|
|
||||||
def select_node(self, node: TreeNode[TreeDataType] | None) -> None:
|
def select_node(self, node: TreeNode[TreeDataType] | None) -> None:
|
||||||
"""Move the cursor to the given node, or reset cursor.
|
"""Move the cursor to the given node, or reset cursor.
|
||||||
|
|||||||
Reference in New Issue
Block a user