mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
changelog
This commit is contained in:
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Changed
|
||||
|
||||
- Added alternative method of composing Widgets
|
||||
|
||||
## [0.11.1] - 2023-02-17
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -38,10 +38,9 @@ class CodeBrowser(App):
|
||||
"""Compose our UI."""
|
||||
path = "./" if len(sys.argv) < 2 else sys.argv[1]
|
||||
yield Header()
|
||||
yield Container(
|
||||
DirectoryTree(path, id="tree-view"),
|
||||
Vertical(Static(id="code", expand=True), id="code-view"),
|
||||
)
|
||||
with Container():
|
||||
yield DirectoryTree(path, id="tree-view")
|
||||
yield Vertical(Static(id="code", expand=True), id="code-view")
|
||||
yield Footer()
|
||||
|
||||
def on_mount(self, event: events.Mount) -> None:
|
||||
|
||||
32
src/textual/_compose.py
Normal file
32
src/textual/_compose.py
Normal file
@@ -0,0 +1,32 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .app import App
|
||||
from .widget import Widget
|
||||
|
||||
|
||||
def compose(node: App | Widget) -> list[Widget]:
|
||||
"""Compose child widgets.
|
||||
|
||||
Args:
|
||||
node: The parent node.
|
||||
|
||||
Returns:
|
||||
A list of widgets.
|
||||
"""
|
||||
app = node.app
|
||||
nodes: list[Widget] = []
|
||||
for child in node.compose():
|
||||
if app._composed:
|
||||
nodes.extend(app._composed)
|
||||
app._composed.clear()
|
||||
if app._compose_stack:
|
||||
app._compose_stack[-1]._nodes._append(child)
|
||||
else:
|
||||
nodes.append(child)
|
||||
if app._composed:
|
||||
nodes.extend(app._composed)
|
||||
app._composed.clear()
|
||||
return nodes
|
||||
@@ -46,6 +46,7 @@ from ._animator import DEFAULT_EASING, Animatable, Animator, EasingFunction
|
||||
from ._ansi_sequences import SYNC_END, SYNC_START
|
||||
from ._asyncio import create_task
|
||||
from ._callback import invoke
|
||||
from ._compose import compose
|
||||
from ._context import active_app
|
||||
from ._event_broker import NoHandler, extract_handler_actions
|
||||
from ._path import _make_path_object_relative
|
||||
@@ -388,6 +389,9 @@ class App(Generic[ReturnType], DOMNode):
|
||||
self._installed_screens: dict[str, Screen | Callable[[], Screen]] = {}
|
||||
self._installed_screens.update(**self.SCREENS)
|
||||
|
||||
self._compose_stack: list[Widget] = []
|
||||
self._composed: list[Widget] = []
|
||||
|
||||
self.devtools: DevtoolsClient | None = None
|
||||
if "devtools" in self.features:
|
||||
try:
|
||||
@@ -1606,7 +1610,7 @@ class App(Generic[ReturnType], DOMNode):
|
||||
|
||||
async def _on_compose(self) -> None:
|
||||
try:
|
||||
widgets = list(self.compose())
|
||||
widgets = compose(self)
|
||||
except TypeError as error:
|
||||
raise TypeError(
|
||||
f"{self!r} compose() returned an invalid response; {error}"
|
||||
|
||||
@@ -41,18 +41,16 @@ class ColorsView(Vertical):
|
||||
]
|
||||
|
||||
for color_name in ColorSystem.COLOR_NAMES:
|
||||
items: list[Widget] = [Label(f'"{color_name}"')]
|
||||
for level in LEVELS:
|
||||
color = f"{color_name}-{level}" if level else color_name
|
||||
item = ColorItem(
|
||||
ColorBar(f"${color}", classes="text label"),
|
||||
ColorBar("$text-muted", classes="muted"),
|
||||
ColorBar("$text-disabled", classes="disabled"),
|
||||
classes=color,
|
||||
)
|
||||
items.append(item)
|
||||
|
||||
yield ColorGroup(*items, id=f"group-{color_name}")
|
||||
with ColorGroup(id=f"group-{color_name}"):
|
||||
yield Label(f'"{color_name}"')
|
||||
for level in LEVELS:
|
||||
color = f"{color_name}-{level}" if level else color_name
|
||||
yield ColorItem(
|
||||
ColorBar(f"${color}", classes="text label"),
|
||||
ColorBar("$text-muted", classes="muted"),
|
||||
ColorBar("$text-disabled", classes="disabled"),
|
||||
classes=color,
|
||||
)
|
||||
|
||||
|
||||
class ColorsApp(App):
|
||||
|
||||
@@ -73,16 +73,15 @@ class EasingApp(App):
|
||||
)
|
||||
|
||||
yield EasingButtons()
|
||||
yield Vertical(
|
||||
Horizontal(
|
||||
with Vertical():
|
||||
yield Horizontal(
|
||||
Label("Animation Duration:", id="label"), duration_input, id="inputs"
|
||||
),
|
||||
Horizontal(
|
||||
)
|
||||
yield Horizontal(
|
||||
self.animated_bar,
|
||||
Container(self.opacity_widget, id="other"),
|
||||
),
|
||||
Footer(),
|
||||
)
|
||||
)
|
||||
yield Footer()
|
||||
|
||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||
self.bell()
|
||||
|
||||
@@ -134,6 +134,7 @@ class DOMNode(MessagePump):
|
||||
self._classes.update(_classes)
|
||||
|
||||
self._nodes: NodeList = NodeList()
|
||||
self._composing: bool = False
|
||||
self._css_styles: Styles = Styles(self)
|
||||
self._inline_styles: Styles = Styles(self)
|
||||
self.styles: RenderStyles = RenderStyles(
|
||||
|
||||
@@ -5,6 +5,7 @@ from collections import Counter
|
||||
from fractions import Fraction
|
||||
from itertools import islice
|
||||
from operator import attrgetter
|
||||
from types import TracebackType
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
ClassVar,
|
||||
@@ -38,6 +39,7 @@ from . import errors, events, messages
|
||||
from ._animator import DEFAULT_EASING, Animatable, BoundAnimator, EasingFunction
|
||||
from ._arrange import DockArrangeResult, arrange
|
||||
from ._asyncio import create_task
|
||||
from ._compose import compose
|
||||
from ._context import active_app
|
||||
from ._easing import DEFAULT_SCROLL_EASING
|
||||
from ._layout import Layout
|
||||
@@ -363,6 +365,22 @@ class Widget(DOMNode):
|
||||
def offset(self, offset: Offset) -> None:
|
||||
self.styles.offset = ScalarOffset.from_offset(offset)
|
||||
|
||||
def __enter__(self) -> None:
|
||||
self.app._compose_stack.append(self)
|
||||
|
||||
def __exit__(
|
||||
self,
|
||||
exc_type: type[BaseException] | None,
|
||||
exc_val: BaseException | None,
|
||||
exc_tb: TracebackType | None,
|
||||
) -> None:
|
||||
compose_stack = self.app._compose_stack
|
||||
composed = compose_stack.pop()
|
||||
if compose_stack:
|
||||
compose_stack[-1]._nodes._append(composed)
|
||||
else:
|
||||
self.app._composed.append(composed)
|
||||
|
||||
ExpectType = TypeVar("ExpectType", bound="Widget")
|
||||
|
||||
@overload
|
||||
@@ -2444,7 +2462,7 @@ class Widget(DOMNode):
|
||||
|
||||
async def _on_compose(self) -> None:
|
||||
try:
|
||||
widgets = list(self.compose())
|
||||
widgets = compose(self)
|
||||
except TypeError as error:
|
||||
raise TypeError(
|
||||
f"{self!r} compose() returned an invalid response; {error}"
|
||||
|
||||
Reference in New Issue
Block a user