Reactivity improvements

This commit is contained in:
Will McGugan
2023-02-09 09:35:01 +00:00
parent b86882ed0c
commit c66c8b6ad6
6 changed files with 24 additions and 52 deletions

View File

@@ -414,6 +414,10 @@ class App(Generic[ReturnType], DOMNode):
"""ReturnType | None: The return type of the app."""
return self._return_value
def _post_mount(self):
"""Called after the object has been mounted."""
Reactive._initialize_object(self)
def animate(
self,
attribute: str,

View File

@@ -5,7 +5,7 @@ from textual._easing import EASING
from textual.app import App, ComposeResult
from textual.cli.previews.borders import TEXT
from textual.containers import Container, Horizontal, Vertical
from textual.reactive import Reactive
from textual.reactive import reactive, var
from textual.scrollbar import ScrollBarRender
from textual.widget import Widget
from textual.widgets import Button, Footer, Label, Input
@@ -23,8 +23,8 @@ class EasingButtons(Widget):
class Bar(Widget):
position = Reactive.init(START_POSITION)
animation_running = Reactive(False)
position = reactive(START_POSITION)
animation_running = reactive(False)
DEFAULT_CSS = """
@@ -53,8 +53,8 @@ class Bar(Widget):
class EasingApp(App):
position = Reactive.init(START_POSITION)
duration = Reactive.var(1.0)
position = reactive(START_POSITION)
duration = var(1.0)
def on_load(self):
self.bind(

View File

@@ -358,7 +358,10 @@ class MessagePump(metaclass=MessagePumpMeta):
finally:
# This is critical, mount may be waiting
self._mounted_event.set()
Reactive._initialize_object(self)
self._post_mount()
def _post_mount(self):
"""Called after the object has been mounted."""
async def _process_messages_loop(self) -> None:
"""Process messages until the queue is closed."""

View File

@@ -7,6 +7,7 @@ from typing import (
Any,
Awaitable,
Callable,
ClassVar,
Generic,
Type,
TypeVar,
@@ -16,7 +17,7 @@ from typing import (
import rich.repr
from . import events
from ._callback import count_parameters, invoke
from ._callback import count_parameters
from ._types import MessageTarget
if TYPE_CHECKING:
@@ -28,15 +29,6 @@ if TYPE_CHECKING:
ReactiveType = TypeVar("ReactiveType")
class _NotSet:
pass
_NOT_SET = _NotSet()
T = TypeVar("T")
@rich.repr.auto
class Reactive(Generic[ReactiveType]):
"""Reactive descriptor.
@@ -50,7 +42,7 @@ class Reactive(Generic[ReactiveType]):
compute: Run compute methods when attribute is changed. Defaults to True.
"""
_reactives: TypeVar[dict[str, object]] = {}
_reactives: ClassVar[dict[str, object]] = {}
def __init__(
self,
@@ -77,37 +69,6 @@ class Reactive(Generic[ReactiveType]):
yield "always_update", self._always_update
yield "compute", self._run_compute
@classmethod
def init(
cls,
default: ReactiveType | Callable[[], ReactiveType],
*,
layout: bool = False,
repaint: bool = True,
always_update: bool = False,
compute: bool = True,
) -> Reactive:
"""A reactive variable that calls watchers and compute on initialize (post mount).
Args:
default: A default value or callable that returns a default.
layout: Perform a layout on change. Defaults to False.
repaint: Perform a repaint on change. Defaults to True.
always_update: Call watchers even when the new value equals the old value. Defaults to False.
compute: Run compute methods when attribute is changed. Defaults to True.
Returns:
A Reactive instance which calls watchers or initialize.
"""
return cls(
default,
layout=layout,
repaint=repaint,
init=True,
always_update=always_update,
compute=compute,
)
@classmethod
def var(
cls,

View File

@@ -360,6 +360,10 @@ class Widget(DOMNode):
def offset(self, offset: Offset) -> None:
self.styles.offset = ScalarOffset.from_offset(offset)
def _post_mount(self):
"""Called after the object has been mounted."""
Reactive._initialize_object(self)
ExpectType = TypeVar("ExpectType", bound="Widget")
@overload

View File

@@ -11,7 +11,7 @@ from rich.text import Text, TextType
from .. import events
from ..css._error_tools import friendly_list
from ..message import Message
from ..reactive import Reactive
from ..reactive import reactive
from ..widgets import Static
@@ -151,13 +151,13 @@ class Button(Static, can_focus=True):
ACTIVE_EFFECT_DURATION = 0.3
"""When buttons are clicked they get the `-active` class for this duration (in seconds)"""
label: Reactive[RenderableType] = Reactive("")
label: reactive[RenderableType] = reactive("")
"""The text label that appears within the button."""
variant = Reactive.init("default")
variant = reactive("default")
"""The variant name for the button."""
disabled = Reactive(False)
disabled = reactive(False)
"""The disabled state of the button; `True` if disabled, `False` if not."""
class Pressed(Message, bubble=True):