mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
add animation system
This commit is contained in:
@@ -25,6 +25,7 @@ typing-extensions = { version = "^3.10.0", python = "<3.8" }
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
# rich = {git = "git@github.com:willmcgugan/rich", rev = "pretty-classes"}
|
||||
mypy = "^0.910"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
|
||||
@@ -7,6 +7,13 @@ class Point(NamedTuple):
|
||||
x: int
|
||||
y: int
|
||||
|
||||
def __add__(self, other: object) -> Point:
|
||||
if isinstance(other, Point):
|
||||
_x, _y = self
|
||||
x, y = other
|
||||
return Point(_x + x, _y + y)
|
||||
raise NotImplemented
|
||||
|
||||
|
||||
class Dimensions(NamedTuple):
|
||||
width: int
|
||||
|
||||
@@ -6,7 +6,7 @@ from rich.segment import Segment
|
||||
from rich.style import StyleType
|
||||
|
||||
from .geometry import Dimensions, Point
|
||||
from .widget import Widget
|
||||
from .widget import Widget, Reactive
|
||||
|
||||
|
||||
class PageRender:
|
||||
@@ -81,6 +81,16 @@ class Page(Widget):
|
||||
self._page = PageRender(renderable, style=style)
|
||||
super().__init__(name=name)
|
||||
|
||||
x: Reactive[int] = Reactive(0)
|
||||
y: Reactive[int] = Reactive(0)
|
||||
|
||||
def validate_y(self, value: int) -> int:
|
||||
return max(0, value)
|
||||
|
||||
def update_y(self, old: int, new: int) -> None:
|
||||
x, y = self._page.offset
|
||||
self._page.offset = Point(x, new)
|
||||
|
||||
@property
|
||||
def virtual_size(self) -> Dimensions:
|
||||
return self._page.size
|
||||
|
||||
@@ -142,6 +142,7 @@ class ScrollBar(Widget):
|
||||
virtual_size: Reactive[int] = Reactive(100)
|
||||
window_size: Reactive[int] = Reactive(20)
|
||||
position: Reactive[int] = Reactive(0)
|
||||
mouse_over: Reactive[bool] = Reactive(False)
|
||||
|
||||
def __rich_repr__(self) -> RichReprResult:
|
||||
yield "virtual_size", self.virtual_size
|
||||
@@ -156,8 +157,17 @@ class ScrollBar(Widget):
|
||||
window_size=self.window_size,
|
||||
position=self.position,
|
||||
vertical=self.vertical,
|
||||
style="bright_magenta on #555555"
|
||||
if self.mouse_over
|
||||
else "bright_magenta on #444444",
|
||||
)
|
||||
|
||||
async def on_enter(self, event: events.Enter) -> None:
|
||||
self.mouse_over = True
|
||||
|
||||
async def on_leave(self, event: events.Leave) -> None:
|
||||
self.mouse_over = False
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from rich.console import Console
|
||||
|
||||
@@ -21,6 +21,7 @@ from rich.segment import Segment
|
||||
from rich.style import Style
|
||||
|
||||
from . import events
|
||||
from .animator import Animator
|
||||
from ._context import active_app
|
||||
from ._loop import loop_last
|
||||
from ._line_cache import LineCache
|
||||
@@ -41,7 +42,7 @@ class UpdateMessage(Message):
|
||||
def __init__(
|
||||
self,
|
||||
sender: MessagePump,
|
||||
widget: Widget,
|
||||
widget: WidgetBase,
|
||||
offset_x: int = 0,
|
||||
offset_y: int = 0,
|
||||
):
|
||||
@@ -72,15 +73,15 @@ class Reactive(Generic[ReactiveType]):
|
||||
self._default = default
|
||||
self.validator = validator
|
||||
|
||||
def __set_name__(self, owner: "Widget", name: str) -> None:
|
||||
def __set_name__(self, owner: "WidgetBase", name: str) -> None:
|
||||
self.name = name
|
||||
self.internal_name = f"__{name}"
|
||||
setattr(owner, self.internal_name, self._default)
|
||||
|
||||
def __get__(self, obj: "Widget", obj_type: type[object]) -> ReactiveType:
|
||||
def __get__(self, obj: "WidgetBase", obj_type: type[object]) -> ReactiveType:
|
||||
return getattr(obj, self.internal_name)
|
||||
|
||||
def __set__(self, obj: "Widget", value: ReactiveType) -> None:
|
||||
def __set__(self, obj: "WidgetBase", value: ReactiveType) -> None:
|
||||
if getattr(obj, self.internal_name) != value:
|
||||
log.debug("%s -> %s", self.internal_name, value)
|
||||
|
||||
@@ -115,6 +116,7 @@ class WidgetBase(MessagePump):
|
||||
self._repaint_required = False
|
||||
|
||||
super().__init__()
|
||||
self._animator = Animator(self)
|
||||
# self.disable_messages(events.MouseMove)
|
||||
|
||||
def __init_subclass__(
|
||||
|
||||
@@ -22,40 +22,39 @@ class ScrollView(LayoutView):
|
||||
def __init__(
|
||||
self, renderable: RenderableType, name: str | None = None, style: StyleType = ""
|
||||
) -> None:
|
||||
layout = Layout()
|
||||
layout = Layout(name="outer")
|
||||
layout.split_row(
|
||||
Layout(name="main", ratio=1), Layout(name="vertical_scrollbar", size=1)
|
||||
)
|
||||
layout["main"].split_column(
|
||||
Layout(name="content", ratio=1), Layout(name="horizontal_scrollbar", size=1)
|
||||
Layout(name="content", ratio=1), Layout(name="vertical_scrollbar", size=1)
|
||||
)
|
||||
# layout["main"].split_column(
|
||||
# Layout(name="content", ratio=1), Layout(name="horizontal_scrollbar", size=1)
|
||||
# )
|
||||
self._vertical_scrollbar = ScrollBar(vertical=True)
|
||||
self._horizontal_Scrollbar = ScrollBar(vertical=False)
|
||||
# self._horizontal_Scrollbar = ScrollBar(vertical=False)
|
||||
self._page = Page(renderable, style=style)
|
||||
super().__init__(layout=layout, name=name)
|
||||
|
||||
position_x: Reactive[int] = Reactive(0)
|
||||
position_y: Reactive[int] = Reactive(0)
|
||||
x: Reactive[float] = Reactive(0)
|
||||
y: Reactive[float] = Reactive(0)
|
||||
|
||||
def validate_position_y(self, value: int) -> int:
|
||||
def validate_y(self, value: float) -> int:
|
||||
return max(0, value)
|
||||
|
||||
def update_position_y(self, old_value: int, new_value: int) -> None:
|
||||
self._vertical_scrollbar.position = new_value
|
||||
def update_y(self, old_value: float, new_value: float) -> None:
|
||||
self._page.y = int(new_value)
|
||||
self._vertical_scrollbar.position = int(new_value)
|
||||
|
||||
async def on_mount(self, event: events.Mount) -> None:
|
||||
await self.mount_all(
|
||||
content=self._page,
|
||||
vertical_scrollbar=self._vertical_scrollbar,
|
||||
horizontal_scrollbar=self._horizontal_Scrollbar,
|
||||
# horizontal_scrollbar=self._horizontal_Scrollbar,
|
||||
)
|
||||
|
||||
async def on_idle(self, event: events.Idle) -> None:
|
||||
self._vertical_scrollbar.virtual_size = self._page.virtual_size.height
|
||||
self._vertical_scrollbar.window_size = self.size.height
|
||||
# self._vertical_scrollbar.position = self.position_y
|
||||
log.debug("SCROLLVIEW BAR %r", self._vertical_scrollbar)
|
||||
log.debug("SCROLL SIZE %r", self._page.size)
|
||||
await super().on_idle(event)
|
||||
|
||||
async def on_mouse_scroll_up(self, event: events.MouseScrollUp) -> None:
|
||||
@@ -66,9 +65,10 @@ class ScrollView(LayoutView):
|
||||
|
||||
async def on_key(self, event: events.Key) -> None:
|
||||
if event.key == "down":
|
||||
self.position_y += 1
|
||||
self.y += 1
|
||||
elif event.key == "up":
|
||||
self.position_y -= 1
|
||||
# self._vertical_scrollbar.require_repaint()
|
||||
# await self._vertical_scrollbar.post_message(events.Null(self))
|
||||
# self.require_repaint()
|
||||
self.y -= 1
|
||||
elif event.key == "pagedown":
|
||||
self._animator.animate("y", self.y + self.size.height, duration=0.5)
|
||||
elif event.key == "pageup":
|
||||
self._animator.animate("y", self.y - self.size.height, duration=0.5)
|
||||
|
||||
Reference in New Issue
Block a user