Skip to content

Commit 5ed98ba

Browse files
committed
add animation system
1 parent 2be8bf9 commit 5ed98ba

File tree

6 files changed

+54
-24
lines changed

6 files changed

+54
-24
lines changed

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ typing-extensions = { version = "^3.10.0", python = "<3.8" }
2525

2626
[tool.poetry.dev-dependencies]
2727
# rich = {git = "[email protected]:willmcgugan/rich", rev = "pretty-classes"}
28+
mypy = "^0.910"
2829

2930
[build-system]
3031
requires = ["poetry-core>=1.0.0"]

src/textual/geometry.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ class Point(NamedTuple):
77
x: int
88
y: int
99

10+
def __add__(self, other: object) -> Point:
11+
if isinstance(other, Point):
12+
_x, _y = self
13+
x, y = other
14+
return Point(_x + x, _y + y)
15+
raise NotImplemented
16+
1017

1118
class Dimensions(NamedTuple):
1219
width: int

src/textual/page.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from rich.style import StyleType
77

88
from .geometry import Dimensions, Point
9-
from .widget import Widget
9+
from .widget import Widget, Reactive
1010

1111

1212
class PageRender:
@@ -81,6 +81,16 @@ def __init__(
8181
self._page = PageRender(renderable, style=style)
8282
super().__init__(name=name)
8383

84+
x: Reactive[int] = Reactive(0)
85+
y: Reactive[int] = Reactive(0)
86+
87+
def validate_y(self, value: int) -> int:
88+
return max(0, value)
89+
90+
def update_y(self, old: int, new: int) -> None:
91+
x, y = self._page.offset
92+
self._page.offset = Point(x, new)
93+
8494
@property
8595
def virtual_size(self) -> Dimensions:
8696
return self._page.size

src/textual/scrollbar.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ def __init__(self, vertical: bool = True, name: str | None = None) -> None:
142142
virtual_size: Reactive[int] = Reactive(100)
143143
window_size: Reactive[int] = Reactive(20)
144144
position: Reactive[int] = Reactive(0)
145+
mouse_over: Reactive[bool] = Reactive(False)
145146

146147
def __rich_repr__(self) -> RichReprResult:
147148
yield "virtual_size", self.virtual_size
@@ -156,8 +157,17 @@ def render(self) -> RenderableType:
156157
window_size=self.window_size,
157158
position=self.position,
158159
vertical=self.vertical,
160+
style="bright_magenta on #555555"
161+
if self.mouse_over
162+
else "bright_magenta on #444444",
159163
)
160164

165+
async def on_enter(self, event: events.Enter) -> None:
166+
self.mouse_over = True
167+
168+
async def on_leave(self, event: events.Leave) -> None:
169+
self.mouse_over = False
170+
161171

162172
if __name__ == "__main__":
163173
from rich.console import Console

src/textual/widget.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from rich.style import Style
2222

2323
from . import events
24+
from .animator import Animator
2425
from ._context import active_app
2526
from ._loop import loop_last
2627
from ._line_cache import LineCache
@@ -41,7 +42,7 @@ class UpdateMessage(Message):
4142
def __init__(
4243
self,
4344
sender: MessagePump,
44-
widget: Widget,
45+
widget: WidgetBase,
4546
offset_x: int = 0,
4647
offset_y: int = 0,
4748
):
@@ -72,15 +73,15 @@ def __init__(
7273
self._default = default
7374
self.validator = validator
7475

75-
def __set_name__(self, owner: "Widget", name: str) -> None:
76+
def __set_name__(self, owner: "WidgetBase", name: str) -> None:
7677
self.name = name
7778
self.internal_name = f"__{name}"
7879
setattr(owner, self.internal_name, self._default)
7980

80-
def __get__(self, obj: "Widget", obj_type: type[object]) -> ReactiveType:
81+
def __get__(self, obj: "WidgetBase", obj_type: type[object]) -> ReactiveType:
8182
return getattr(obj, self.internal_name)
8283

83-
def __set__(self, obj: "Widget", value: ReactiveType) -> None:
84+
def __set__(self, obj: "WidgetBase", value: ReactiveType) -> None:
8485
if getattr(obj, self.internal_name) != value:
8586
log.debug("%s -> %s", self.internal_name, value)
8687

@@ -115,6 +116,7 @@ def __init__(self, name: str | None = None) -> None:
115116
self._repaint_required = False
116117

117118
super().__init__()
119+
self._animator = Animator(self)
118120
# self.disable_messages(events.MouseMove)
119121

120122
def __init_subclass__(

src/textual/widgets/scroll_view.py

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,40 +22,39 @@ class ScrollView(LayoutView):
2222
def __init__(
2323
self, renderable: RenderableType, name: str | None = None, style: StyleType = ""
2424
) -> None:
25-
layout = Layout()
25+
layout = Layout(name="outer")
2626
layout.split_row(
27-
Layout(name="main", ratio=1), Layout(name="vertical_scrollbar", size=1)
28-
)
29-
layout["main"].split_column(
30-
Layout(name="content", ratio=1), Layout(name="horizontal_scrollbar", size=1)
27+
Layout(name="content", ratio=1), Layout(name="vertical_scrollbar", size=1)
3128
)
29+
# layout["main"].split_column(
30+
# Layout(name="content", ratio=1), Layout(name="horizontal_scrollbar", size=1)
31+
# )
3232
self._vertical_scrollbar = ScrollBar(vertical=True)
33-
self._horizontal_Scrollbar = ScrollBar(vertical=False)
33+
# self._horizontal_Scrollbar = ScrollBar(vertical=False)
3434
self._page = Page(renderable, style=style)
3535
super().__init__(layout=layout, name=name)
3636

37-
position_x: Reactive[int] = Reactive(0)
38-
position_y: Reactive[int] = Reactive(0)
37+
x: Reactive[float] = Reactive(0)
38+
y: Reactive[float] = Reactive(0)
3939

40-
def validate_position_y(self, value: int) -> int:
40+
def validate_y(self, value: float) -> int:
4141
return max(0, value)
4242

43-
def update_position_y(self, old_value: int, new_value: int) -> None:
44-
self._vertical_scrollbar.position = new_value
43+
def update_y(self, old_value: float, new_value: float) -> None:
44+
self._page.y = int(new_value)
45+
self._vertical_scrollbar.position = int(new_value)
4546

4647
async def on_mount(self, event: events.Mount) -> None:
4748
await self.mount_all(
4849
content=self._page,
4950
vertical_scrollbar=self._vertical_scrollbar,
50-
horizontal_scrollbar=self._horizontal_Scrollbar,
51+
# horizontal_scrollbar=self._horizontal_Scrollbar,
5152
)
5253

5354
async def on_idle(self, event: events.Idle) -> None:
5455
self._vertical_scrollbar.virtual_size = self._page.virtual_size.height
5556
self._vertical_scrollbar.window_size = self.size.height
5657
# self._vertical_scrollbar.position = self.position_y
57-
log.debug("SCROLLVIEW BAR %r", self._vertical_scrollbar)
58-
log.debug("SCROLL SIZE %r", self._page.size)
5958
await super().on_idle(event)
6059

6160
async def on_mouse_scroll_up(self, event: events.MouseScrollUp) -> None:
@@ -66,9 +65,10 @@ async def on_mouse_scroll_down(self, event: events.MouseScrollUp) -> None:
6665

6766
async def on_key(self, event: events.Key) -> None:
6867
if event.key == "down":
69-
self.position_y += 1
68+
self.y += 1
7069
elif event.key == "up":
71-
self.position_y -= 1
72-
# self._vertical_scrollbar.require_repaint()
73-
# await self._vertical_scrollbar.post_message(events.Null(self))
74-
# self.require_repaint()
70+
self.y -= 1
71+
elif event.key == "pagedown":
72+
self._animator.animate("y", self.y + self.size.height, duration=0.5)
73+
elif event.key == "pageup":
74+
self._animator.animate("y", self.y - self.size.height, duration=0.5)

0 commit comments

Comments
 (0)