ai-station/.venv/lib/python3.12/site-packages/textual/scroll_view.py

197 lines
6.3 KiB
Python

"""
`ScrollView` is a base class for [Line API](/guide/widgets#line-api) widgets.
"""
from __future__ import annotations
from rich.console import RenderableType
from textual._animator import EasingFunction
from textual._types import AnimationLevel, CallbackType
from textual.containers import ScrollableContainer
from textual.geometry import Region, Size
class ScrollView(ScrollableContainer):
"""
A base class for a Widget that handles its own scrolling (i.e. doesn't rely
on the compositor to render children).
!!! note
This is the typically wrong class for making something scrollable. If you want to make something scroll, set its
`overflow` style to auto or scroll. Or use one of the pre-defined scrolling containers such as [VerticalScroll][textual.containers.VerticalScroll].
"""
ALLOW_MAXIMIZE = True
DEFAULT_CSS = """
ScrollView {
overflow-y: auto;
overflow-x: auto;
}
"""
@property
def is_scrollable(self) -> bool:
"""Always scrollable."""
return True
@property
def is_container(self) -> bool:
"""Since a ScrollView should be a line-api widget, it won't have children,
and therefore isn't a container."""
return False
def watch_scroll_x(self, old_value: float, new_value: float) -> None:
if self.show_horizontal_scrollbar:
self.horizontal_scrollbar.position = new_value
if round(old_value) != round(new_value):
self.refresh(self.size.region)
def watch_scroll_y(self, old_value: float, new_value: float) -> None:
if self.show_vertical_scrollbar:
self.vertical_scrollbar.position = new_value
if round(old_value) != round(new_value):
self.refresh(self.size.region)
def on_mount(self):
self._refresh_scrollbars()
def get_content_width(self, container: Size, viewport: Size) -> int:
"""Gets the width of the content area.
Args:
container: Size of the container (immediate parent) widget.
viewport: Size of the viewport.
Returns:
The optimal width of the content.
"""
return self.virtual_size.width
def get_content_height(self, container: Size, viewport: Size, width: int) -> int:
"""Gets the height (number of lines) in the content area.
Args:
container: Size of the container (immediate parent) widget.
viewport: Size of the viewport.
width: Width of renderable.
Returns:
The height of the content.
"""
return self.virtual_size.height
def _size_updated(
self, size: Size, virtual_size: Size, container_size: Size, layout: bool = True
) -> bool:
"""Called when size is updated.
Args:
size: New size.
virtual_size: New virtual size.
container_size: New container size.
layout: Perform layout if required.
Returns:
True if a resize event should be sent, otherwise False.
"""
if size_changed := self._size != size:
self._set_dirty()
if (
size_changed
or virtual_size != self.virtual_size
or container_size != self.container_size
):
self._scrollbar_changes.clear()
self._size = size
virtual_size = self.virtual_size
self._container_size = size - self.styles.gutter.totals
self._scroll_update(virtual_size)
return size_changed or self._container_size != container_size
def render(self) -> RenderableType:
"""Render the scrollable region (if `render_lines` is not implemented).
Returns:
Renderable object.
"""
from rich.panel import Panel
return Panel(f"{self.scroll_offset} {self.show_vertical_scrollbar}")
# Custom scroll to which doesn't require call_after_refresh
def scroll_to(
self,
x: float | None = None,
y: float | None = None,
*,
animate: bool = True,
speed: float | None = None,
duration: float | None = None,
easing: EasingFunction | str | None = None,
force: bool = False,
on_complete: CallbackType | None = None,
level: AnimationLevel = "basic",
immediate: bool = False,
) -> None:
"""Scroll to a given (absolute) coordinate, optionally animating.
Args:
x: X coordinate (column) to scroll to, or `None` for no change.
y: Y coordinate (row) to scroll to, or `None` for no change.
animate: Animate to new scroll position.
speed: Speed of scroll if `animate` is `True`; or `None` to use `duration`.
duration: Duration of animation, if `animate` is `True` and `speed` is `None`.
easing: An easing method for the scrolling animation.
force: Force scrolling even when prohibited by overflow styling.
on_complete: A callable to invoke when the animation is finished.
level: Minimum level required for the animation to take place (inclusive).
immediate: If `False` the scroll will be deferred until after a screen refresh,
set to `True` to scroll immediately.
"""
self._scroll_to(
x,
y,
animate=animate,
speed=speed,
duration=duration,
easing=easing,
force=force,
on_complete=on_complete,
level=level,
)
def refresh_line(self, y: int) -> None:
"""Refresh a single line.
Args:
y: Coordinate of line.
"""
self.refresh(
Region(
0,
y - self.scroll_offset.y,
max(self.virtual_size.width, self.size.width),
1,
)
)
def refresh_lines(self, y_start: int, line_count: int = 1) -> None:
"""Refresh one or more lines.
Args:
y_start: First line to refresh.
line_count: Total number of lines to refresh.
"""
refresh_region = Region(
0,
y_start - self.scroll_offset.y,
max(self.virtual_size.width, self.size.width),
line_count,
)
self.refresh(refresh_region)