Skip to content

Commit

Permalink
fix(MenuWidget): avoid opening an ActionItem's action return valu…
Browse files Browse the repository at this point in the history
…e twice if it is a `PageWidget` class

refactor(MenuWidget): improve opening/closing application logic to avoid race conditions
refactor(PageWidget): add `__repr__` to help debugging and logging `PageWidget`s
refactor(MenuWidget): use `mainthread` decorator in transitions only when necessary
refactor(ItemWidget): increase the length of non-short items (decrease the right margin from 30 to 6)
  • Loading branch information
sassanh committed May 6, 2024
1 parent c788b01 commit ac8f541
Show file tree
Hide file tree
Showing 8 changed files with 100 additions and 51 deletions.
8 changes: 5 additions & 3 deletions .github/workflows/integration_delivery.yml
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ jobs:
- build
runs-on: ubuntu-latest
environment:
name: release
name: pypi
url: https://pypi.org/p/${{ needs.build.outputs.name }}
permissions:
id-token: write
Expand Down Expand Up @@ -186,10 +186,12 @@ jobs:
- lint
- build
- pypi-publish
runs-on: ubuntu-latest
environment:
name: release
url: https://pypi.org/p/${{ needs.build.outputs.name }}
runs-on: ubuntu-latest
url:
https://github.com/${{ github.repository }}/releases/tag/v${{
needs.build.outputs.version }}
permissions:
contents: write
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Changelog

## Version 0.11.5

- fix(MenuWidget): avoid opening an `ActionItem`'s `action` return value twice if
it is a `PageWidget` class
- refactor(MenuWidget): improve opening/closing application logic to avoid race conditions
- refactor(PageWidget): add `__repr__` to help debugging and logging `PageWidget`s
- refactor(MenuWidget): use `mainthread` decorator in transitions only when necessary
- refactor(ItemWidget): increase the length of non-short items (decrease the right
margin from 30 to 6)

## Version 0.11.4

- refactor(NotificationWidget): remove all the logic and make it solely a view
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "ubo-gui"
version = "0.11.4"
version = "0.11.5"
description = "GUI sdk for Ubo Pod"
authors = ["Sassan Haradji <sassanh@gmail.com>"]
license = "Apache-2.0"
Expand Down
2 changes: 1 addition & 1 deletion ubo_gui/animated_slider/animated_slider.kv
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#:kivy 2.3.0
#:import utils kivy.utils

<-AnimatedSlider@Slider>:
<AnimatedSlider>:
background_width: dp(2)
cursor_size: ((dp(13), dp(28)) if self.orientation == 'horizontal' else (dp(28), dp(13)))
padding: min(*self.cursor_size) / 2
Expand Down
82 changes: 54 additions & 28 deletions ubo_gui/menu/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ class MenuWidget(BoxLayout, TransitionsMixin):
widget_subscriptions_lock: threading.Lock
screen_subscriptions: set[Callable[[], None]]
screen_subscriptions_lock: threading.Lock
stack_lock: threading.Lock

_current_menu_items: Sequence[Item]
_current_screen: Screen | None = None
Expand All @@ -127,6 +128,7 @@ def __init__(self: MenuWidget, **kwargs: object) -> None:
self.widget_subscriptions_lock = threading.Lock()
self.screen_subscriptions = set()
self.screen_subscriptions_lock = threading.Lock()
self.stack_lock = threading.Lock()
super().__init__(**kwargs)
self.bind(stack=self.render)

Expand Down Expand Up @@ -229,7 +231,7 @@ def select_action_item(self: MenuWidget, item: ActionItem) -> None:
return
if isinstance(result, type) and issubclass(result, PageWidget):
self.open_application(result())
if isinstance(result, PageWidget):
elif isinstance(result, PageWidget):
self.open_application(result)
elif isinstance(result, Menu) or callable(result):
self.open_menu(result)
Expand Down Expand Up @@ -291,12 +293,11 @@ def select(self: MenuWidget, index: int) -> None:

def go_back(self: MenuWidget) -> None:
"""Go back to the previous menu."""
if self.current_application and self.current_application.go_back():
return
headless_widget = HeadlessWidget.get_instance(self)
if headless_widget:
headless_widget.activate_high_fps_mode()
self.pop()
if self.current_application:
if not self.current_application.go_back():
self.close_application(self.current_application)
elif self.current_menu:
self.pop()

def render_header_menu(self: MenuWidget, menu: HeadedMenu) -> HeaderMenuPageWidget:
"""Render a header menu."""
Expand Down Expand Up @@ -521,22 +522,23 @@ def set_current_screen(self: MenuWidget, screen: Screen) -> bool:

def open_application(self: MenuWidget, application: PageWidget) -> None:
"""Open an application."""
headless_widget = HeadlessWidget.get_instance(self)
if headless_widget:
headless_widget.activate_high_fps_mode()
application.name = uuid.uuid4().hex
application.padding_bottom = self.padding_bottom
application.padding_top = self.padding_top
self.push(
application,
transition=self._swap_transition,
duration=0.2,
direction='left',
)
application.bind(
on_close=self.close_application,
on_leave=self.leave_application,
)
with self.stack_lock:
headless_widget = HeadlessWidget.get_instance(self)
if headless_widget:
headless_widget.activate_high_fps_mode()
application.name = uuid.uuid4().hex
application.padding_bottom = self.padding_bottom
application.padding_top = self.padding_top
self.push(
application,
transition=self._swap_transition,
duration=0.2,
direction='left',
)
application.bind(
on_close=self.close_application,
on_leave=self.leave_application,
)

def clean_application(self: MenuWidget, application: PageWidget) -> None:
"""Clean up the application bounds."""
Expand All @@ -545,11 +547,35 @@ def clean_application(self: MenuWidget, application: PageWidget) -> None:

def close_application(self: MenuWidget, application: PageWidget) -> None:
"""Close an application after its `on_close` event is fired."""
while any(
isinstance(item, StackApplicationItem) and item.application is application
for item in self.stack
):
self.go_back()
# Remove `application` and all applications in the stack with their `root` being
# `application` from stack and clear their bindings and subscriptions.
# If any of these applications are the top of the stack, remove it with `pop` to
# ensure the animation is played.
with self.stack_lock:
if any(
isinstance(item.root, StackApplicationItem)
and item.root.application is application
for item in self.stack
):
to_be_removed = [
cast(StackApplicationItem, item)
for item in self.stack
if isinstance(item.root, StackApplicationItem)
and item.root.application is application
and item is not self.top
]

for item in to_be_removed:
item.clear_subscriptions()
self.clean_application(item.application)

self.stack = [item for item in self.stack if item not in to_be_removed]

if (
isinstance(self.top.root, StackApplicationItem)
and self.top.root.application is application
):
self.pop()

def leave_application(self: MenuWidget, application: PageWidget) -> None:
"""Close an application after its `on_leave` event is fired."""
Expand Down
31 changes: 20 additions & 11 deletions ubo_gui/menu/transitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import threading
from functools import cached_property
from typing import Any
from typing import Any, NotRequired, TypedDict

from headless_kivy_pi import HeadlessWidget
from kivy.clock import mainthread
Expand All @@ -19,6 +19,13 @@
from kivy.uix.widget import Widget


class SwitchParameters(TypedDict):
"""Parameters for switching screens."""

duration: NotRequired[float | None]
direction: NotRequired[str | None]


class TransitionsMixin:
"""Provides easy access to different transitions."""

Expand Down Expand Up @@ -60,14 +67,16 @@ def _handle_transition_complete(
and transition is not self._no_transition
):
duration = 0.08
mainthread(
lambda *_: self.screen_manager.switch_to(
screen,
transition=transition,
**({'duration': duration} if duration else {}),
**({'direction': direction} if direction else {}),
),
)()
switch_parameters: SwitchParameters = {}
if duration:
switch_parameters['duration'] = duration
if direction:
switch_parameters['direction'] = direction
self._perform_switch(
screen,
transition=transition,
**switch_parameters,
)
else:
if isinstance(self, Widget):
headless_widget = HeadlessWidget.get_instance(self)
Expand Down Expand Up @@ -108,7 +117,7 @@ def _perform_switch(
) -> None:
if duration is None:
duration = 0.2
self.screen_manager.switch_to(
mainthread(self.screen_manager.switch_to)(
screen,
transition=transition,
**({'duration': duration} if duration else {}),
Expand All @@ -132,7 +141,7 @@ def _switch_to(
if headless_widget:
headless_widget.activate_high_fps_mode()
self._is_transition_in_progress = transition is not self._no_transition
mainthread(self._perform_switch)(
self._perform_switch(
screen,
transition=transition,
duration=duration,
Expand Down
9 changes: 2 additions & 7 deletions ubo_gui/menu/widgets/item_widget.kv
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#:kivy 2.3.0

<ItemWidget>:
orientation: 'horizontal'
height: dp(UBO_GUI_MENU_ITEM_HEIGHT)
opacity: root.opacity

Expand All @@ -10,7 +11,7 @@

RoundedRectangle:
pos: self.pos
size: (dp(UBO_GUI_SHORT_WIDTH) if self.is_short else self.width - dp(30), self.height) if root.is_set else (0, 0)
size: (dp(UBO_GUI_SHORT_WIDTH) if self.is_short else self.width - dp(6), self.height) if root.is_set else (0, 0)
radius: (0, 0), (dp(26), dp(26)), (dp(26), dp(26)), (0, 0)

orientation: 'horizontal'
Expand All @@ -21,7 +22,6 @@
size_hint: None, 1

Label:
pos: root.pos
color: root.color
font_size: dp(26)
text: root.icon if root.icon is not None else ''
Expand All @@ -31,7 +31,6 @@
markup: True

Label:
pos: root.pos
color: root.color
font_size: dp(18)
text: '' if root.is_short else root.label
Expand All @@ -42,7 +41,3 @@
shorten: True
shorten_from: 'right'
markup: True

Widget:
width: dp(34)
size_hint: (None, 1) if root.is_short else (0, 1)
7 changes: 7 additions & 0 deletions ubo_gui/page/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,10 @@ def get_item(self: PageWidget, index: int) -> Item | None:

def on_close(self: PageWidget) -> None:
"""Signal when the page is closed."""

def __repr__(self: PageWidget) -> str:
"""Return a string representation of the `PageWidget`."""
return (
f'<{self.__class__.__name__}@PageWidget name="{self.name}" '
f'title="{self.title}">'
)

0 comments on commit ac8f541

Please sign in to comment.