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