mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Merge pull request #1014 from Textualize/reactive-always-update
Reactive `always_update`
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -116,3 +116,6 @@ venv.bak/
|
||||
|
||||
# Snapshot testing report output directory
|
||||
tests/snapshot_tests/output
|
||||
|
||||
# Sandbox folder - convenient place for us to develop small test apps without leaving the repo
|
||||
sandbox/
|
||||
|
||||
@@ -165,7 +165,11 @@ If you click the buttons in the above example it will show the current count. Wh
|
||||
|
||||
## Watch methods
|
||||
|
||||
Watch methods are another superpower. Textual will call watch methods when reactive attributes are modified. Watch methods begin with `watch_` followed by the name of the attribute. If the watch method accepts a positional argument, it will be called with the new assigned value. If the watch method accepts *two* positional arguments, it will be called with both the *old* value and the *new* value.
|
||||
Watch methods are another superpower.
|
||||
Textual will call watch methods when reactive attributes are modified.
|
||||
Watch methods begin with `watch_` followed by the name of the attribute.
|
||||
If the watch method accepts a positional argument, it will be called with the new assigned value.
|
||||
If the watch method accepts *two* positional arguments, it will be called with both the *old* value and the *new* value.
|
||||
|
||||
The following app will display any color you type in to the input. Try it with a valid color in Textual CSS. For example `"darkorchid"` or `"#52de44"`.
|
||||
|
||||
@@ -192,6 +196,12 @@ The following app will display any color you type in to the input. Try it with a
|
||||
|
||||
The color is parsed in `on_input_submitted` and assigned to `self.color`. Because `color` is reactive, Textual also calls `watch_color` with the old and new values.
|
||||
|
||||
### When are watch methods called?
|
||||
|
||||
Textual only calls watch methods if the value of a reactive attribute _changes_.
|
||||
If the newly assigned value is the same as the previous value, the watch method is not called.
|
||||
You can override this behaviour by passing `always_update=True` to `reactive`.
|
||||
|
||||
## Compute methods
|
||||
|
||||
Compute methods are the final superpower offered by the `reactive` descriptor. Textual runs compute methods to calculate the value of a reactive attribute. Compute methods begin with `compute_` followed by the name of the reactive value.
|
||||
|
||||
@@ -5,7 +5,6 @@ from inspect import isawaitable
|
||||
from typing import TYPE_CHECKING, Any, Callable, Generic, Type, TypeVar, Union
|
||||
from weakref import WeakSet
|
||||
|
||||
|
||||
from . import events
|
||||
from ._callback import count_parameters, invoke
|
||||
from ._types import MessageTarget
|
||||
@@ -16,7 +15,6 @@ if TYPE_CHECKING:
|
||||
|
||||
Reactable = Union[Widget, App]
|
||||
|
||||
|
||||
ReactiveType = TypeVar("ReactiveType")
|
||||
|
||||
|
||||
@@ -37,7 +35,7 @@ class Reactive(Generic[ReactiveType]):
|
||||
layout (bool, optional): Perform a layout on change. Defaults to False.
|
||||
repaint (bool, optional): Perform a repaint on change. Defaults to True.
|
||||
init (bool, optional): Call watchers on initialize (post mount). Defaults to False.
|
||||
|
||||
always_update(bool, optional): Call watchers even when the new value equals the old value. Defaults to False.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
@@ -47,11 +45,13 @@ class Reactive(Generic[ReactiveType]):
|
||||
layout: bool = False,
|
||||
repaint: bool = True,
|
||||
init: bool = False,
|
||||
always_update: bool = False,
|
||||
) -> None:
|
||||
self._default = default
|
||||
self._layout = layout
|
||||
self._repaint = repaint
|
||||
self._init = init
|
||||
self._always_update = always_update
|
||||
|
||||
@classmethod
|
||||
def init(
|
||||
@@ -60,6 +60,7 @@ class Reactive(Generic[ReactiveType]):
|
||||
*,
|
||||
layout: bool = False,
|
||||
repaint: bool = True,
|
||||
always_update: bool = False,
|
||||
) -> Reactive:
|
||||
"""A reactive variable that calls watchers and compute on initialize (post mount).
|
||||
|
||||
@@ -67,11 +68,17 @@ class Reactive(Generic[ReactiveType]):
|
||||
default (ReactiveType | Callable[[], ReactiveType]): A default value or callable that returns a default.
|
||||
layout (bool, optional): Perform a layout on change. Defaults to False.
|
||||
repaint (bool, optional): Perform a repaint on change. Defaults to True.
|
||||
|
||||
always_update(bool, optional): Call watchers even when the new value equals the old value. Defaults to False.
|
||||
Returns:
|
||||
Reactive: A Reactive instance which calls watchers or initialize.
|
||||
"""
|
||||
return cls(default, layout=layout, repaint=repaint, init=True)
|
||||
return cls(
|
||||
default,
|
||||
layout=layout,
|
||||
repaint=repaint,
|
||||
init=True,
|
||||
always_update=always_update,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def var(
|
||||
@@ -153,7 +160,7 @@ class Reactive(Generic[ReactiveType]):
|
||||
if callable(validate_function) and not first_set:
|
||||
value = validate_function(value)
|
||||
# If the value has changed, or this is the first time setting the value
|
||||
if current_value != value or first_set:
|
||||
if current_value != value or first_set or self._always_update:
|
||||
# Set the first set flag to False
|
||||
setattr(obj, f"__first_set_{self.internal_name}", False)
|
||||
# Store the internal value
|
||||
@@ -259,7 +266,7 @@ class reactive(Reactive[ReactiveType]):
|
||||
layout (bool, optional): Perform a layout on change. Defaults to False.
|
||||
repaint (bool, optional): Perform a repaint on change. Defaults to True.
|
||||
init (bool, optional): Call watchers on initialize (post mount). Defaults to True.
|
||||
|
||||
always_update(bool, optional): Call watchers even when the new value equals the old value. Defaults to False.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
@@ -269,8 +276,15 @@ class reactive(Reactive[ReactiveType]):
|
||||
layout: bool = False,
|
||||
repaint: bool = True,
|
||||
init: bool = True,
|
||||
always_update: bool = False,
|
||||
) -> None:
|
||||
super().__init__(default, layout=layout, repaint=repaint, init=init)
|
||||
super().__init__(
|
||||
default,
|
||||
layout=layout,
|
||||
repaint=repaint,
|
||||
init=init,
|
||||
always_update=always_update,
|
||||
)
|
||||
|
||||
|
||||
class var(Reactive[ReactiveType]):
|
||||
|
||||
Reference in New Issue
Block a user