diff --git a/CHANGELOG.md b/CHANGELOG.md index da29be9..9ee4e3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## Version 0.9.2 + +- fix: queue transitions instead of letting the last transition interrupt the active + one + ## Version 0.9.1 - hotfix: remove debug background rectangle diff --git a/pyproject.toml b/pyproject.toml index 6ba58ab..2f2037f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ubo-gui" -version = "0.9.1" +version = "0.9.2" description = "GUI sdk for Ubo Pod" authors = ["Sassan Haradji "] license = "Apache-2.0" diff --git a/ubo_gui/menu/__init__.py b/ubo_gui/menu/__init__.py index e338ade..6e4ac92 100644 --- a/ubo_gui/menu/__init__.py +++ b/ubo_gui/menu/__init__.py @@ -162,7 +162,7 @@ def go_down(self: MenuWidget) -> None: return self.page_index = (self.page_index + 1) % self.pages self.render_items() - self.screen_manager.switch_to( + self._switch_to( self.current_screen, transition=self._slide_transition, direction='up', @@ -181,7 +181,7 @@ def go_up(self: MenuWidget) -> None: return self.page_index = (self.page_index - 1) % self.pages self.render_items() - self.screen_manager.switch_to( + self._switch_to( self.current_screen, transition=self._slide_transition, direction='down', @@ -371,7 +371,7 @@ def handle_items_change(items: Sequence[Item]) -> None: self.current_menu_items = items self.render_items() if last_items: - self.screen_manager.switch_to( + self._switch_to( self.current_screen, transition=self._no_transition, ) @@ -459,7 +459,7 @@ def replace( parent=self.top.parent, ) self.top.subscriptions = subscriptions - self.screen_manager.switch_to( + self._switch_to( self.current_screen, transition=self._no_transition, ) @@ -485,11 +485,12 @@ def push( # noqa: PLR0913 *self.stack, StackApplicationItem(application=item, parent=parent), ] - self.screen_manager.switch_to( + + self._switch_to( self.current_screen, transition=transition, - **({'duration': duration} if duration else {}), - **({'direction': direction} if direction else {}), + duration=duration, + direction=direction, ) def pop( @@ -516,11 +517,11 @@ def pop( elif self.current_application: self.clean_application(self.current_application) transition_ = self._swap_transition - self.screen_manager.switch_to( + self._switch_to( self.current_screen, transition=transition or transition_, - **({'duration': duration} if duration else {}), - **({'direction': direction} if direction else {}), + duration=duration, + direction=direction, ) def get_is_scrollbar_visible(self: MenuWidget) -> bool: diff --git a/ubo_gui/menu/transitions.py b/ubo_gui/menu/transitions.py index 7a711eb..b252338 100644 --- a/ubo_gui/menu/transitions.py +++ b/ubo_gui/menu/transitions.py @@ -1,11 +1,16 @@ """Provides easy access to different transitions.""" from __future__ import annotations +import threading from functools import cached_property +from typing import Any from headless_kivy_pi import HeadlessWidget +from kivy.clock import Clock from kivy.uix.screenmanager import ( NoTransition, + Screen, + ScreenManager, SlideTransition, SwapTransition, TransitionBase, @@ -15,13 +20,24 @@ class TransitionsMixin: """Provides easy access to different transitions.""" + transition_queue: list[ + tuple[Screen | None, TransitionBase, str | None, float | None] + ] + screen_manager: ScreenManager + _is_transition_in_progress: bool = False + _transition_progress_lock: threading.Lock + + def __init__(self: TransitionsMixin, **kwargs: dict[str, Any]) -> None: + """Initialize the transitions mixin.""" + _ = kwargs + self._transition_progress_lock = threading.Lock() + self.transition_queue = [] + def _handle_transition_progress( self: TransitionsMixin, transition: TransitionBase, progression: float, ) -> None: - if progression is 0: # noqa: F632 - float 0.0 is not accepted, we are looking for int 0 - HeadlessWidget.activate_high_fps_mode() transition.screen_out.opacity = 1 - progression transition.screen_in.opacity = progression @@ -31,7 +47,28 @@ def _handle_transition_complete( ) -> None: transition.screen_out.opacity = 0 transition.screen_in.opacity = 1 - HeadlessWidget.activate_low_fps_mode() + with self._transition_progress_lock: + if self.transition_queue: + ( + (screen, transition, direction, duration), + *self.transition_queue, + ) = self.transition_queue + if ( + len(self.transition_queue) > 1 + and transition is not self._no_transition + ): + duration = 0.1 + Clock.schedule_once( + lambda *_: self.screen_manager.switch_to( + screen, + transition=transition, + **({'duration': duration} if duration else {}), + **({'direction': direction} if direction else {}), + ), + ) + else: + HeadlessWidget.activate_low_fps_mode() + self._is_transition_in_progress = False def _setup_transition(self: TransitionsMixin, transition: TransitionBase) -> None: transition.bind(on_progress=self._handle_transition_progress) @@ -54,3 +91,31 @@ def _swap_transition(self: TransitionsMixin) -> SwapTransition: transition = SwapTransition() self._setup_transition(transition) return transition + + def _switch_to( + self: TransitionsMixin, + screen: Screen | None, + /, + *, + transition: TransitionBase, + duration: float | None = None, + direction: str | None = None, + ) -> None: + """Switch to a new screen.""" + with self._transition_progress_lock: + if not self._is_transition_in_progress: + HeadlessWidget.activate_high_fps_mode() + self._is_transition_in_progress = transition is not self._no_transition + Clock.schedule_once( + lambda *_: self.screen_manager.switch_to( + screen, + transition=transition, + **({'duration': duration} if duration else {}), + **({'direction': direction} if direction else {}), + ), + ) + else: + self.transition_queue = [ + *self.transition_queue, + (screen, transition, direction, duration), + ]