diff --git a/src/textual/scroll_view.py b/src/textual/scroll_view.py index 2161c216f0..e5ce0443f2 100644 --- a/src/textual/scroll_view.py +++ b/src/textual/scroll_view.py @@ -117,6 +117,7 @@ def scroll_to( duration: float | None = None, easing: EasingFunction | str | None = None, force: bool = False, + on_complete: CallbackType | None = None, ) -> None: """Scroll to a given (absolute) coordinate, optionally animating. @@ -128,6 +129,7 @@ def scroll_to( 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. """ self._scroll_to( @@ -138,4 +140,5 @@ def scroll_to( duration=duration, easing=easing, force=force, + on_complete=on_complete, ) diff --git a/src/textual/widget.py b/src/textual/widget.py index ff4c58bc8d..9c066a9820 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -45,6 +45,7 @@ from ._arrange import DockArrangeResult, arrange from ._asyncio import create_task from ._cache import FIFOCache +from ._callback import invoke from ._compose import compose from ._context import NoActiveAppError, active_app from ._easing import DEFAULT_SCROLL_EASING @@ -1697,6 +1698,7 @@ def _scroll_to( duration: float | None = None, easing: EasingFunction | str | None = None, force: bool = False, + on_complete: CallbackType | None = None, ) -> bool: """Scroll to a given (absolute) coordinate, optionally animating. @@ -1708,6 +1710,7 @@ def _scroll_to( 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. Returns: `True` if the scroll position changed, otherwise `False`. @@ -1734,6 +1737,7 @@ def _scroll_to( speed=speed, duration=duration, easing=easing, + on_complete=on_complete, ) scrolled_x = True if maybe_scroll_y: @@ -1746,6 +1750,7 @@ def _scroll_to( speed=speed, duration=duration, easing=easing, + on_complete=on_complete, ) scrolled_y = True @@ -1761,6 +1766,9 @@ def _scroll_to( self.scroll_target_y = self.scroll_y = y scrolled_y = scroll_y != self.scroll_y + if on_complete is not None: + self.call_after_refresh(on_complete) + return scrolled_x or scrolled_y def scroll_to( @@ -1773,6 +1781,7 @@ def scroll_to( duration: float | None = None, easing: EasingFunction | str | None = None, force: bool = False, + on_complete: CallbackType | None = None, ) -> None: """Scroll to a given (absolute) coordinate, optionally animating. @@ -1784,6 +1793,7 @@ def scroll_to( 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. Note: The call to scroll is made after the next refresh. @@ -1797,6 +1807,7 @@ def scroll_to( duration=duration, easing=easing, force=force, + on_complete=on_complete, ) def scroll_relative( @@ -1809,6 +1820,7 @@ def scroll_relative( duration: float | None = None, easing: EasingFunction | str | None = None, force: bool = False, + on_complete: CallbackType | None = None, ) -> None: """Scroll relative to current position. @@ -1820,6 +1832,7 @@ def scroll_relative( 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. """ self.scroll_to( None if x is None else (self.scroll_x + x), @@ -1829,6 +1842,7 @@ def scroll_relative( duration=duration, easing=easing, force=force, + on_complete=on_complete, ) def scroll_home( @@ -1839,6 +1853,7 @@ def scroll_home( duration: float | None = None, easing: EasingFunction | str | None = None, force: bool = False, + on_complete: CallbackType | None = None, ) -> None: """Scroll to home position. @@ -1848,6 +1863,7 @@ def scroll_home( 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. """ if speed is None and duration is None: duration = 1.0 @@ -1859,6 +1875,7 @@ def scroll_home( duration=duration, easing=easing, force=force, + on_complete=on_complete, ) def scroll_end( @@ -1869,6 +1886,7 @@ def scroll_end( duration: float | None = None, easing: EasingFunction | str | None = None, force: bool = False, + on_complete: CallbackType | None = None, ) -> None: """Scroll to the end of the container. @@ -1878,6 +1896,7 @@ def scroll_end( 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. """ if speed is None and duration is None: duration = 1.0 @@ -1898,6 +1917,7 @@ def _lazily_scroll_end() -> None: duration=duration, easing=easing, force=force, + on_complete=on_complete, ) self.call_after_refresh(_lazily_scroll_end) @@ -1910,6 +1930,7 @@ def scroll_left( duration: float | None = None, easing: EasingFunction | str | None = None, force: bool = False, + on_complete: CallbackType | None = None, ) -> None: """Scroll one cell left. @@ -1919,6 +1940,7 @@ def scroll_left( 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. """ self.scroll_to( x=self.scroll_target_x - 1, @@ -1927,6 +1949,7 @@ def scroll_left( duration=duration, easing=easing, force=force, + on_complete=on_complete, ) def _scroll_left_for_pointer( @@ -1937,6 +1960,7 @@ def _scroll_left_for_pointer( duration: float | None = None, easing: EasingFunction | str | None = None, force: bool = False, + on_complete: CallbackType | None = None, ) -> bool: """Scroll left one position, taking scroll sensitivity into account. @@ -1946,6 +1970,7 @@ def _scroll_left_for_pointer( 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. Returns: `True` if any scrolling was done. @@ -1961,6 +1986,7 @@ def _scroll_left_for_pointer( duration=duration, easing=easing, force=force, + on_complete=on_complete, ) def scroll_right( @@ -1971,6 +1997,7 @@ def scroll_right( duration: float | None = None, easing: EasingFunction | str | None = None, force: bool = False, + on_complete: CallbackType | None = None, ) -> None: """Scroll one cell right. @@ -1980,6 +2007,7 @@ def scroll_right( 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. """ self.scroll_to( x=self.scroll_target_x + 1, @@ -1988,6 +2016,7 @@ def scroll_right( duration=duration, easing=easing, force=force, + on_complete=on_complete, ) def _scroll_right_for_pointer( @@ -1998,6 +2027,7 @@ def _scroll_right_for_pointer( duration: float | None = None, easing: EasingFunction | str | None = None, force: bool = False, + on_complete: CallbackType | None = None, ) -> bool: """Scroll right one position, taking scroll sensitivity into account. @@ -2007,6 +2037,7 @@ def _scroll_right_for_pointer( 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. Returns: `True` if any scrolling was done. @@ -2022,6 +2053,7 @@ def _scroll_right_for_pointer( duration=duration, easing=easing, force=force, + on_complete=on_complete, ) def scroll_down( @@ -2032,6 +2064,7 @@ def scroll_down( duration: float | None = None, easing: EasingFunction | str | None = None, force: bool = False, + on_complete: CallbackType | None = None, ) -> None: """Scroll one line down. @@ -2041,6 +2074,7 @@ def scroll_down( 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. """ self.scroll_to( y=self.scroll_target_y + 1, @@ -2049,6 +2083,7 @@ def scroll_down( duration=duration, easing=easing, force=force, + on_complete=on_complete, ) def _scroll_down_for_pointer( @@ -2059,6 +2094,7 @@ def _scroll_down_for_pointer( duration: float | None = None, easing: EasingFunction | str | None = None, force: bool = False, + on_complete: CallbackType | None = None, ) -> bool: """Scroll down one position, taking scroll sensitivity into account. @@ -2068,6 +2104,7 @@ def _scroll_down_for_pointer( 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. Returns: `True` if any scrolling was done. @@ -2083,6 +2120,7 @@ def _scroll_down_for_pointer( duration=duration, easing=easing, force=force, + on_complete=on_complete, ) def scroll_up( @@ -2093,6 +2131,7 @@ def scroll_up( duration: float | None = None, easing: EasingFunction | str | None = None, force: bool = False, + on_complete: CallbackType | None = None, ) -> None: """Scroll one line up. @@ -2102,6 +2141,7 @@ def scroll_up( 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. """ self.scroll_to( y=self.scroll_target_y - 1, @@ -2110,6 +2150,7 @@ def scroll_up( duration=duration, easing=easing, force=force, + on_complete=on_complete, ) def _scroll_up_for_pointer( @@ -2120,6 +2161,7 @@ def _scroll_up_for_pointer( duration: float | None = None, easing: EasingFunction | str | None = None, force: bool = False, + on_complete: CallbackType | None = None, ) -> bool: """Scroll up one position, taking scroll sensitivity into account. @@ -2129,6 +2171,7 @@ def _scroll_up_for_pointer( 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. Returns: `True` if any scrolling was done. @@ -2144,6 +2187,7 @@ def _scroll_up_for_pointer( duration=duration, easing=easing, force=force, + on_complete=on_complete, ) def scroll_page_up( @@ -2154,6 +2198,7 @@ def scroll_page_up( duration: float | None = None, easing: EasingFunction | str | None = None, force: bool = False, + on_complete: CallbackType | None = None, ) -> None: """Scroll one page up. @@ -2163,6 +2208,7 @@ def scroll_page_up( 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. """ self.scroll_to( y=self.scroll_y - self.container_size.height, @@ -2171,6 +2217,7 @@ def scroll_page_up( duration=duration, easing=easing, force=force, + on_complete=on_complete, ) def scroll_page_down( @@ -2181,6 +2228,7 @@ def scroll_page_down( duration: float | None = None, easing: EasingFunction | str | None = None, force: bool = False, + on_complete: CallbackType | None = None, ) -> None: """Scroll one page down. @@ -2190,6 +2238,7 @@ def scroll_page_down( 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. """ self.scroll_to( y=self.scroll_y + self.container_size.height, @@ -2198,6 +2247,7 @@ def scroll_page_down( duration=duration, easing=easing, force=force, + on_complete=on_complete, ) def scroll_page_left( @@ -2208,6 +2258,7 @@ def scroll_page_left( duration: float | None = None, easing: EasingFunction | str | None = None, force: bool = False, + on_complete: CallbackType | None = None, ) -> None: """Scroll one page left. @@ -2217,6 +2268,7 @@ def scroll_page_left( 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. """ if speed is None and duration is None: duration = 0.3 @@ -2227,6 +2279,7 @@ def scroll_page_left( duration=duration, easing=easing, force=force, + on_complete=on_complete, ) def scroll_page_right( @@ -2237,6 +2290,7 @@ def scroll_page_right( duration: float | None = None, easing: EasingFunction | str | None = None, force: bool = False, + on_complete: CallbackType | None = None, ) -> None: """Scroll one page right. @@ -2246,6 +2300,7 @@ def scroll_page_right( 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. """ if speed is None and duration is None: duration = 0.3 @@ -2256,6 +2311,7 @@ def scroll_page_right( duration=duration, easing=easing, force=force, + on_complete=on_complete, ) def scroll_to_widget( @@ -2270,6 +2326,7 @@ def scroll_to_widget( top: bool = False, origin_visible: bool = True, force: bool = False, + on_complete: CallbackType | None = None, ) -> bool: """Scroll scrolling to bring a widget in to view. @@ -2282,6 +2339,7 @@ def scroll_to_widget( top: Scroll widget to top of container. origin_visible: Ensure that the top left of the widget is within the window. force: Force scrolling even when prohibited by overflow styling. + on_complete: A callable to invoke when the animation is finished. Returns: `True` if any scrolling has occurred in any descendant, otherwise `False`. @@ -2307,6 +2365,7 @@ def scroll_to_widget( easing=easing, origin_visible=origin_visible, force=force, + on_complete=on_complete, ) if scroll_offset: scrolled = True @@ -2340,6 +2399,7 @@ def scroll_to_region( top: bool = False, origin_visible: bool = True, force: bool = False, + on_complete: CallbackType | None = None, ) -> Offset: """Scrolls a given region in to view, if required. @@ -2356,6 +2416,7 @@ def scroll_to_region( top: Scroll `region` to top of container. origin_visible: Ensure that the top left of the widget is within the window. force: Force scrolling even when prohibited by overflow styling. + on_complete: A callable to invoke when the animation is finished. Returns: The distance that was scrolled. @@ -2401,6 +2462,7 @@ def scroll_to_region( duration=duration, easing=easing, force=force, + on_complete=on_complete, ) return delta @@ -2413,6 +2475,7 @@ def scroll_visible( top: bool = False, easing: EasingFunction | str | None = None, force: bool = False, + on_complete: CallbackType | None = None, ) -> None: """Scroll the container to make this widget visible. @@ -2423,6 +2486,7 @@ def scroll_visible( top: Scroll to top of container. 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. """ parent = self.parent if isinstance(parent, Widget): @@ -2435,6 +2499,7 @@ def scroll_visible( top=top, easing=easing, force=force, + on_complete=on_complete, ) def scroll_to_center( @@ -2447,6 +2512,7 @@ def scroll_to_center( easing: EasingFunction | str | None = None, force: bool = False, origin_visible: bool = True, + on_complete: CallbackType | None = None, ) -> None: """Scroll this widget to the center of self. @@ -2460,6 +2526,7 @@ def scroll_to_center( easing: An easing method for the scrolling animation. force: Force scrolling even when prohibited by overflow styling. origin_visible: Ensure that the top left corner of the widget remains visible after the scroll. + on_complete: A callable to invoke when the animation is finished. """ self.call_after_refresh( @@ -2472,6 +2539,7 @@ def scroll_to_center( force=force, center=True, origin_visible=origin_visible, + on_complete=on_complete, ) def can_view(self, widget: Widget) -> bool: