changelog

This commit is contained in:
Will McGugan
2023-02-20 16:16:10 +00:00
parent be850635c2
commit 347b94a0fe
8 changed files with 82 additions and 25 deletions

View File

@@ -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

View File

@@ -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
View 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

View File

@@ -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}"

View File

@@ -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):

View File

@@ -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()

View File

@@ -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(

View File

@@ -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}"