Skip to content

Commit f955af2

Browse files
committed
refactored reactive
1 parent b4b8449 commit f955af2

File tree

5 files changed

+83
-61
lines changed

5 files changed

+83
-61
lines changed

src/textual/app.py

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
from .message_pump import MessagePump
2828
from .message import Message
2929
from .view import DockView, View
30-
from .widget import Widget, Widget
30+
from .widget import Widget, Widget, Reactive
3131

3232
log = logging.getLogger("rich")
3333

@@ -216,11 +216,11 @@ async def process_messages(self) -> None:
216216
if traceback is not None:
217217
self.console.print(traceback)
218218

219-
# async def dock(self, *widgets: WidgetBase, z: int = 0) -> None:
219+
def require_repaint(self) -> None:
220+
self.refresh()
220221

221-
# dock = Dock("top", widgets, z)
222-
# self._docks.append(dock)
223-
# await self.view.mount(widgets)
222+
def require_layout(self) -> None:
223+
self.view.require_layout()
224224

225225
async def message_update(self, message: Message) -> None:
226226
self.refresh()
@@ -375,21 +375,20 @@ async def action_bell(self) -> None:
375375
)
376376

377377
class MyApp(App):
378+
"""Just a test app."""
379+
378380
async def on_load(self, event: events.Load) -> None:
379381
await self.bind("q,ctrl+c", "quit")
380382
await self.bind("x", "bang")
381383
await self.bind("b", "toggle_sidebar")
382-
self.side = False
384+
385+
show_bar: Reactive[bool] = Reactive(False)
386+
387+
def update_show_bar(self, show_bar: bool) -> None:
388+
self.animator.animate(self.bar, "layout_offset_x", -40 if show_bar else 0)
383389

384390
async def action_toggle_sidebar(self) -> None:
385-
self.side = not self.side
386-
self.animator.animate(
387-
self.bar,
388-
"layout_offset_x",
389-
-40 if self.side else 0,
390-
speed=60,
391-
easing="in_out_cubic",
392-
)
391+
self.show_bar = not self.show_bar
393392

394393
async def on_startup(self, event: events.Startup) -> None:
395394

src/textual/page.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ def contents_size(self) -> Dimensions:
9191
def validate_y(self, value: int) -> int:
9292
return max(0, value)
9393

94-
def update_y(self, old: int, new: int) -> None:
94+
def update_y(self, new: int) -> None:
9595
x, y = self._page.offset
9696
self._page.offset = Point(x, new)
9797

src/textual/reactive.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
from __future__ import annotations
2+
3+
import sys
4+
from typing import Callable, Generic, TypeVar
5+
6+
from .message_pump import MessagePump
7+
8+
if sys.version_info >= (3, 8):
9+
from typing import Protocol
10+
else:
11+
from typing_extensions import Protocol
12+
13+
14+
class Reactable(Protocol):
15+
def require_layout(self):
16+
...
17+
18+
def require_repaint(self):
19+
...
20+
21+
22+
ReactiveType = TypeVar("ReactiveType")
23+
24+
25+
class Reactive(Generic[ReactiveType]):
26+
"""Reactive descriptor."""
27+
28+
def __init__(
29+
self,
30+
default: ReactiveType,
31+
*,
32+
layout: bool = False,
33+
repaint: bool = True,
34+
) -> None:
35+
self._default = default
36+
self.layout = layout
37+
self.repaint = repaint
38+
39+
def __set_name__(self, owner: Reactable, name: str) -> None:
40+
self.name = name
41+
self.internal_name = f"__{name}"
42+
setattr(owner, self.internal_name, self._default)
43+
44+
def __get__(self, obj: Reactable, obj_type: type[object]) -> ReactiveType:
45+
return getattr(obj, self.internal_name)
46+
47+
def __set__(self, obj: Reactable, value: ReactiveType) -> None:
48+
if getattr(obj, self.internal_name) != value:
49+
50+
current_value = getattr(obj, self.internal_name, None)
51+
validate_function = getattr(obj, f"validate_{self.name}", None)
52+
if callable(validate_function):
53+
value = validate_function(value)
54+
55+
if current_value != value:
56+
57+
update_function = getattr(obj, f"update_{self.name}", None)
58+
if callable(update_function):
59+
update_function(value)
60+
setattr(obj, self.internal_name, value)
61+
62+
if self.layout:
63+
obj.require_layout()
64+
elif self.repaint:
65+
obj.require_repaint()

src/textual/widget.py

Lines changed: 3 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from .messages import UpdateMessage, LayoutMessage
3131
from .message_pump import MessagePump
3232
from .geometry import Point, Dimensions
33+
from .reactive import Reactive
3334

3435

3536
if TYPE_CHECKING:
@@ -42,49 +43,6 @@
4243
log = getLogger("rich")
4344

4445

45-
ReactiveType = TypeVar("ReactiveType")
46-
47-
48-
class Reactive(Generic[ReactiveType]):
49-
def __init__(
50-
self,
51-
default: ReactiveType,
52-
validator: Callable[[object, ReactiveType], ReactiveType] | None = None,
53-
layout: bool = False,
54-
) -> None:
55-
self._default = default
56-
self.validator = validator
57-
self.layout = layout
58-
59-
def __set_name__(self, owner: "Widget", name: str) -> None:
60-
self.name = name
61-
self.internal_name = f"__{name}"
62-
setattr(owner, self.internal_name, self._default)
63-
64-
def __get__(self, obj: "Widget", obj_type: type[object]) -> ReactiveType:
65-
return getattr(obj, self.internal_name)
66-
67-
def __set__(self, obj: "Widget", value: ReactiveType) -> None:
68-
if getattr(obj, self.internal_name) != value:
69-
70-
current_value = getattr(obj, self.internal_name, None)
71-
validate_function = getattr(obj, f"validate_{self.name}", None)
72-
if callable(validate_function):
73-
value = validate_function(value)
74-
75-
if current_value != value:
76-
setattr(obj, self.internal_name, value)
77-
78-
update_function = getattr(obj, f"update_{self.name}", None)
79-
if callable(update_function):
80-
update_function(current_value, value)
81-
82-
if self.layout:
83-
obj.require_layout()
84-
else:
85-
obj.require_repaint()
86-
87-
8846
@rich.repr.auto
8947
class Widget(MessagePump):
9048
_id: ClassVar[int] = 0
@@ -223,7 +181,7 @@ async def on_event(self, event: events.Event) -> None:
223181
await super().on_event(event)
224182

225183
async def on_idle(self, event: events.Idle) -> None:
226-
if self.check_layout():
184+
if self.check_layout():
227185
self.reset_check_repaint()
228186
self.reset_check_layout()
229187
await self.update_layout()
@@ -255,4 +213,4 @@ def __init__(self, renderable: RenderableType, name: str | None = None) -> None:
255213
self.renderable = renderable
256214

257215
def render(self) -> RenderableType:
258-
return self.renderable
216+
return self.renderable

src/textual/widgets/scroll_view.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def validate_y(self, value: float) -> float:
5151
def validate_target_y(self, value: float) -> float:
5252
return clamp(value, 0, self._page.contents_size.height - self.size.height)
5353

54-
def update_y(self, old_value: float, new_value: float) -> None:
54+
def update_y(self, new_value: float) -> None:
5555
self._page.y = round(new_value)
5656
self._vertical_scrollbar.position = round(new_value)
5757

0 commit comments

Comments
 (0)