From 2c19edc3c88950e4287a5afa6a2ba537bcf2f984 Mon Sep 17 00:00:00 2001 From: Jonathan Newell Date: Fri, 24 Oct 2025 09:32:25 -0400 Subject: [PATCH 1/2] Adds Menu widget with popup functionality Introduces a new Menu widget that allows users to display a customizable menu with items in a popup window. The menu can be configured with a label, icon, menu items, and various styling options such as blur, alignment, and animations. It enables users to launch applications or execute commands directly from the bar. --- README.md | 1 + docs/widgets/(Widget)-Menu.md | 160 +++++++++++++ src/core/validation/widgets/yasb/menu.py | 78 +++++++ src/core/widgets/yasb/menu.py | 282 +++++++++++++++++++++++ src/styles.css | 36 +++ 5 files changed, 557 insertions(+) create mode 100644 docs/widgets/(Widget)-Menu.md create mode 100644 src/core/validation/widgets/yasb/menu.py create mode 100644 src/core/widgets/yasb/menu.py diff --git a/README.md b/README.md index be2a4ba8c..9d1f3c754 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,7 @@ https://github.com/user-attachments/assets/aab8d8e8-248f-46a1-919c-9b0601236ac1 - **[Libre Hardware Monitor](https://github.com/amnweb/yasb/wiki/(Widget)-Libre-HW-Monitor)**: Connects to Libre Hardware Monitor to get sensor data. - **[Media](https://github.com/amnweb/yasb/wiki/(Widget)-Media)**: Displays media controls and information. - **[Memory](https://github.com/amnweb/yasb/wiki/(Widget)-Memory)**: Shows current memory usage. +- **[Menu](https://github.com/amnweb/yasb/wiki/(Widget)-Menu)**: A customizable menu widget that displays items in a popup. - **[Microphone](https://github.com/amnweb/yasb/wiki/(Widget)-Microphone)**: Displays the current microphone status. - **[Notifications](https://github.com/amnweb/yasb/wiki/(Widget)-Notifications)**: Shows the number of notifications from Windows. - **[Notes](https://github.com/amnweb/yasb/wiki/(Widget)-Notes)**: A simple notes widget that allows you to add, delete, and view notes. diff --git a/docs/widgets/(Widget)-Menu.md b/docs/widgets/(Widget)-Menu.md new file mode 100644 index 000000000..4f5d28958 --- /dev/null +++ b/docs/widgets/(Widget)-Menu.md @@ -0,0 +1,160 @@ +# Menu Widget Options + +| Option | Type | Default | Description | +|------------|--------|---------|-----------------------------------------------------------------------------| +| `label` | string | `""` | The text label for the menu button. Can be empty if only using an icon. | +| `icon` | string | `""` | The icon for the menu button. Can be a Unicode character, emoji, or path to an image file. Can be empty if only using a label. | +| `class_name` | string | `""` | The CSS class name for styling the widget. Optional. | +| `image_icon_size` | int | `14` | The size of the icon in pixels if the icon is an image (for the button in the bar). | +| `popup_image_icon_size` | int | `16` | The size of the icon in pixels for menu items in the popup. | +| `menu_items` | list | `[]`| Menu items list with icon, launch command, and optional name. | +| `tooltip` | bool | `True`| Enable or disable tooltips. | +| `blur` | bool | `False`| Enable or disable blur effect on the popup window. | +| `alignment` | string | `"left"`| Popup alignment relative to the button: `"left"`, `"right"`, or `"center"`. | +| `direction` | string | `"down"`| Popup direction: `"down"` (below button) or `"up"` (above button). | +| `popup_offset` | dict | `{"top": 0, "left": 0}`| Offset for the popup position in pixels. | +| `animation` | dict | `{'enabled': True, 'type': 'fadeInOut', 'duration': 200}` | Animation settings for menu items. | +| `container_padding` | dict | `{"top": 0, "left": 0, "bottom": 0, "right": 0}`| Padding for the widget container in the bar. | +| `popup_padding` | dict | `{"top": 8, "left": 8, "bottom": 8, "right": 8}`| Padding for the popup window content. | +| `container_shadow` | dict | `None` | Container shadow options. | +| `label_shadow` | dict | `None` | Label shadow options. | + +## Example Configuration + +```yaml +menu: + type: "yasb.menu.MenuWidget" + options: + label: "Menu" + icon: "\uf0c9" # hamburger menu icon + menu_items: + - {icon: "\uf0a2", launch: "notification_center", name: "Notification Center"} + - {icon: "\ueb51", launch: "quick_settings", name: "Quick Settings"} + - {icon: "\uf422", launch: "search", name: "Search"} + - {icon: "\uf489", launch: "wt", name: "Windows Terminal"} + - {icon: "C:\\Users\\marko\\icons\\vscode.png", launch: "C:\\Users\\Username\\AppData\\Local\\Programs\\Microsoft VS Code\\Code.exe", name: "VS Code"} + - {icon: "\udb81\udc4d", launch: "\"C:\\Program Files\\Mozilla Firefox\\firefox.exe\" -new-tab www.reddit.com", name: "Reddit"} + blur: true + alignment: "left" + direction: "down" + popup_offset: + top: 4 + left: 0 + label_shadow: + enabled: true + color: "black" + radius: 3 + offset: [1, 1] +``` + +## Example with Icon Only + +```yaml +menu_icon_only: + type: "yasb.menu.MenuWidget" + options: + icon: "\uf013" # settings icon + menu_items: + - {icon: "\uf013", launch: "ms-settings:", name: "Settings"} + - {icon: "\uf0c7", launch: "control", name: "Control Panel"} + - {icon: "\uf108", launch: "taskmgr", name: "Task Manager"} + popup_image_icon_size: 20 + blur: true +``` + +## Example with Label Only + +```yaml +menu_label_only: + type: "yasb.menu.MenuWidget" + options: + label: "Apps" + menu_items: + - {icon: "\uf489", launch: "wt", name: "Terminal"} + - {icon: "\uf07c", launch: "explorer", name: "File Explorer"} + - {icon: "\uf108", launch: "notepad", name: "Notepad"} +``` + +## Description of Options + +- **label:** The text label displayed on the menu button in the bar. Can be empty if you only want to show an icon. +- **icon:** The icon displayed on the menu button in the bar. Can be a Unicode character (e.g., `\uf0c9`), emoji, or an image file path (e.g., `C:\\path\\to\\icon.png`). Can be empty if you only want to show a label. +- **class_name:** The CSS class name for styling the widget. Optional. +- **image_icon_size:** The size in pixels of the icon on the menu button (if using an image file). +- **popup_image_icon_size:** The size in pixels of the icons for menu items in the popup window. +- **menu_items:** A list of menu items to display in the popup. Each item is a dictionary with the following keys: + - **icon:** The icon for the menu item. This can be a Unicode character (e.g., `\uf0a2`), an image path (e.g., `C:\\path\\to\\icon.png`), or emoji. + - **launch:** The command to execute when the menu item is clicked. This can include arguments and should be properly quoted if necessary. + - **name:** (Optional) The name of the menu item to display next to the icon and as a tooltip. +- **tooltip:** Enable or disable tooltips when hovering over the menu button and items. +- **blur:** Enable blur effect on the popup window background (Windows 10: Acrylic, Windows 11: Mica). +- **alignment:** How the popup is aligned relative to the menu button. Options: `"left"`, `"right"`, or `"center"`. +- **direction:** Whether the popup appears below (`"down"`) or above (`"up"`) the menu button. +- **popup_offset:** Fine-tune the popup position with pixel offsets: + - **top:** Vertical offset in pixels. + - **left:** Horizontal offset in pixels. +- **animation:** Animation settings when clicking menu items. Contains: + - **enabled:** Enable or disable animation. + - **type:** Animation type (e.g., `fadeInOut`). + - **duration:** Animation duration in milliseconds. +- **container_padding:** Padding around the widget container in the bar. +- **popup_padding:** Padding around the content inside the popup window. +- **container_shadow:** Shadow options for the widget container in the bar. +- **label_shadow:** Shadow options for the menu button label/icon. + +## CSS Styling + +The menu widget can be styled using CSS classes: + +```css +/* Menu button in the bar */ +.menu-widget .widget-container { + background-color: #1e1e1e; + border-radius: 4px; +} + +.menu-widget .icon { + color: #ffffff; + font-size: 14px; +} + +.menu-widget .label { + color: #ffffff; + padding: 0 8px; +} + +/* Popup window */ +.menu-popup { + background-color: #2d2d2d; + border-radius: 8px; + border: 1px solid #404040; +} + +/* Individual menu items */ +.menu-item { + background-color: transparent; + border-radius: 4px; + margin: 2px 0; +} + +.menu-item:hover { + background-color: #3d3d3d; +} + +.menu-item .icon { + color: #ffffff; + font-size: 16px; + min-width: 24px; +} + +.menu-item .label { + color: #ffffff; + font-size: 13px; +} +``` + +> [!NOTE] +> - You must specify at least one of `label` or `icon` for the menu button. You can use both together if desired. +> - Menu items automatically close the popup after being clicked. +> - The popup automatically closes when clicking outside of it. +> - Commands in `launch` support the same function map as the applications widget (e.g., `notification_center`, `quick_settings`, etc.). diff --git a/src/core/validation/widgets/yasb/menu.py b/src/core/validation/widgets/yasb/menu.py new file mode 100644 index 000000000..578b5f6b1 --- /dev/null +++ b/src/core/validation/widgets/yasb/menu.py @@ -0,0 +1,78 @@ +DEFAULTS = { + "animation": {"enabled": True, "type": "fadeInOut", "duration": 200}, + "tooltip": True, + "container_padding": {"top": 0, "left": 0, "bottom": 0, "right": 0}, + "popup_padding": {"top": 8, "left": 8, "bottom": 8, "right": 8}, + "popup_offset": {"top": 0, "left": 0}, + "alignment": "left", + "direction": "down", +} + +VALIDATION_SCHEMA = { + "label": {"type": "string", "required": False, "default": ""}, + "icon": {"type": "string", "required": False, "default": ""}, + "class_name": {"type": "string", "required": False, "default": ""}, + "image_icon_size": {"type": "integer", "required": False, "default": 14}, + "popup_image_icon_size": {"type": "integer", "required": False, "default": 16}, + "menu_items": { + "type": "list", + "required": True, + "schema": { + "type": "dict", + "schema": { + "icon": {"type": "string"}, + "launch": {"type": "string"}, + "name": {"type": "string", "required": False}, + }, + }, + }, + "animation": { + "type": "dict", + "required": False, + "schema": { + "enabled": {"type": "boolean", "default": DEFAULTS["animation"]["enabled"]}, + "type": {"type": "string", "default": DEFAULTS["animation"]["type"]}, + "duration": {"type": "integer", "default": DEFAULTS["animation"]["duration"]}, + }, + "default": DEFAULTS["animation"], + }, + "tooltip": {"type": "boolean", "default": True, "required": False}, + "blur": {"type": "boolean", "default": False, "required": False}, + "alignment": { + "type": "string", + "default": DEFAULTS["alignment"], + "required": False, + "allowed": ["left", "right", "center"], + }, + "direction": { + "type": "string", + "default": DEFAULTS["direction"], + "required": False, + "allowed": ["up", "down"], + }, + "popup_offset": {"type": "dict", "default": DEFAULTS["popup_offset"], "required": False}, + "label_shadow": { + "type": "dict", + "required": False, + "schema": { + "enabled": {"type": "boolean", "default": False}, + "color": {"type": "string", "default": "black"}, + "offset": {"type": "list", "default": [1, 1]}, + "radius": {"type": "integer", "default": 3}, + }, + "default": {"enabled": False, "color": "black", "offset": [1, 1], "radius": 3}, + }, + "container_shadow": { + "type": "dict", + "required": False, + "schema": { + "enabled": {"type": "boolean", "default": False}, + "color": {"type": "string", "default": "black"}, + "offset": {"type": "list", "default": [1, 1]}, + "radius": {"type": "integer", "default": 3}, + }, + "default": {"enabled": False, "color": "black", "offset": [1, 1], "radius": 3}, + }, + "container_padding": {"type": "dict", "default": DEFAULTS["container_padding"], "required": False}, + "popup_padding": {"type": "dict", "default": DEFAULTS["popup_padding"], "required": False}, +} diff --git a/src/core/widgets/yasb/menu.py b/src/core/widgets/yasb/menu.py new file mode 100644 index 000000000..017518e77 --- /dev/null +++ b/src/core/widgets/yasb/menu.py @@ -0,0 +1,282 @@ +import logging +import os +import subprocess + +from PyQt6.QtCore import Qt, pyqtSignal +from PyQt6.QtGui import QCursor, QPixmap +from PyQt6.QtWidgets import QFrame, QHBoxLayout, QLabel, QVBoxLayout, QWidget + +from core.utils.tooltip import set_tooltip +from core.utils.utilities import PopupWidget, add_shadow, is_valid_qobject +from core.utils.widgets.animation_manager import AnimationManager +from core.utils.win32.system_function import function_map +from core.validation.widgets.yasb.menu import VALIDATION_SCHEMA +from core.widgets.base import BaseWidget + + +class MenuWidget(BaseWidget): + validation_schema = VALIDATION_SCHEMA + + def __init__( + self, + label: str, + class_name: str, + menu_items: list[dict[str]], + icon: str, + image_icon_size: int, + popup_image_icon_size: int, + animation: dict[str, str], + tooltip: bool, + container_padding: dict[str, int], + popup_padding: dict[str, int], + blur: bool, + popup_offset: dict[str, int], + alignment: str, + direction: str, + label_shadow: dict = None, + container_shadow: dict = None, + ): + super().__init__(class_name=f"menu-widget {class_name}") + self._label = label + self._icon = icon + self._menu_items = menu_items + self._padding = container_padding + self._popup_padding = popup_padding + self._image_icon_size = image_icon_size + self._popup_image_icon_size = popup_image_icon_size + self._animation = animation + self._tooltip = tooltip + self._blur = blur + self._popup_offset = popup_offset + self._alignment = alignment + self._direction = direction + self._label_shadow = label_shadow + self._container_shadow = container_shadow + self._popup = None + + # Construct container + self._widget_container_layout = QHBoxLayout() + self._widget_container_layout.setSpacing(0) + self._widget_container_layout.setContentsMargins( + self._padding["left"], self._padding["top"], self._padding["right"], self._padding["bottom"] + ) + + # Initialize container + self._widget_container = QFrame() + self._widget_container.setLayout(self._widget_container_layout) + self._widget_container.setProperty("class", "widget-container") + add_shadow(self._widget_container, self._container_shadow) + + # Create the menu button container + self._button_container = QWidget() + self._button_layout = QHBoxLayout(self._button_container) + self._button_layout.setSpacing(4) + self._button_layout.setContentsMargins(0, 0, 0, 0) + + # Create icon label if icon is provided + if self._icon: + self._icon_label = ClickableLabel(self) + self._icon_label.setCursor(QCursor(Qt.CursorShape.PointingHandCursor)) + self._icon_label.setProperty("class", "icon") + self._icon_label.clicked.connect(self._toggle_popup) + + if os.path.isfile(self._icon): + pixmap = QPixmap(self._icon).scaled( + self._image_icon_size, + self._image_icon_size, + Qt.AspectRatioMode.KeepAspectRatio, + Qt.TransformationMode.SmoothTransformation, + ) + self._icon_label.setPixmap(pixmap) + else: + self._icon_label.setText(self._icon) + + add_shadow(self._icon_label, self._label_shadow) + self._button_layout.addWidget(self._icon_label) + + # Create text label if label is provided + if self._label: + self._text_label = ClickableLabel(self) + self._text_label.setCursor(QCursor(Qt.CursorShape.PointingHandCursor)) + self._text_label.setProperty("class", "label") + self._text_label.setText(self._label) + self._text_label.clicked.connect(self._toggle_popup) + + add_shadow(self._text_label, self._label_shadow) + self._button_layout.addWidget(self._text_label) + + # Set tooltip + if self._tooltip: + tooltip_text = self._label if self._label else "Menu" + set_tooltip(self._button_container, tooltip_text, 0) + + # Add button container to widget container + self._widget_container_layout.addWidget(self._button_container) + + # Add the container to the main widget layout + self.widget_layout.addWidget(self._widget_container) + + def _toggle_popup(self): + """Toggle the menu popup visibility.""" + try: + if self._popup and is_valid_qobject(self._popup) and self._popup.isVisible(): + self._popup.hide_animated() + else: + self._show_popup() + except RuntimeError: + # Popup was deleted, create a new one + self._show_popup() + + def _show_popup(self): + """Create and show the popup menu.""" + # Close existing popup if any + try: + if self._popup and is_valid_qobject(self._popup): + self._popup.hide() + except RuntimeError: + pass + + # Create new popup + self._popup = PopupWidget( + parent=self._widget_container, + blur=self._blur, + round_corners=True, + round_corners_type="normal", + border_color="None", + ) + self._popup.setProperty("class", "menu-popup") + # Prevent click-through to windows behind the popup + self._popup.setAttribute(Qt.WidgetAttribute.WA_NoMouseReplay) + + # Create popup layout directly on the PopupWidget (like media.py does) + popup_layout = QVBoxLayout(self._popup) + popup_layout.setSpacing(0) + popup_layout.setContentsMargins( + self._popup_padding["left"], + self._popup_padding["top"], + self._popup_padding["right"], + self._popup_padding["bottom"], + ) + + # Add menu items directly to the popup layout + if isinstance(self._menu_items, list): + for item_data in self._menu_items: + if "icon" in item_data and "launch" in item_data: + # Create menu item + item = MenuItemWidget( + parent=self, + icon=item_data["icon"], + label=item_data.get("name", ""), + launch=item_data["launch"], + icon_size=self._popup_image_icon_size, + animation=self._animation, + tooltip=self._tooltip, + ) + popup_layout.addWidget(item) + + # Adjust popup size to content + self._popup.adjustSize() + + # Force Qt to apply the stylesheet to the popup and its children + self._popup.style().unpolish(self._popup) + self._popup.style().polish(self._popup) + self._popup._popup_content.style().unpolish(self._popup._popup_content) + self._popup._popup_content.style().polish(self._popup._popup_content) + + # Position and show popup + self._popup.setPosition( + alignment=self._alignment, + direction=self._direction, + offset_left=self._popup_offset["left"], + offset_top=self._popup_offset["top"], + ) + self._popup.show() + + def execute_code(self, data): + """Execute the command associated with a menu item.""" + try: + if data in function_map: + function_map[data]() + else: + try: + if not any(param in data for param in ["-new-tab", "-new-window", "-private-window"]): + data = data.split() + subprocess.Popen(data, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, shell=True) + except Exception as e: + logging.error(f"Error starting app: {str(e)}") + + # Close popup after executing command + try: + if self._popup and is_valid_qobject(self._popup) and self._popup.isVisible(): + self._popup.hide_animated() + except RuntimeError: + pass + except Exception as e: + logging.error(f'Exception occurred: {str(e)} "{data}"') + + +class ClickableLabel(QLabel): + """A label that emits a signal when clicked.""" + + clicked = pyqtSignal() + + def __init__(self, parent=None): + super().__init__(parent) + self.parent_widget = parent + + def mousePressEvent(self, event): + if event.button() == Qt.MouseButton.LeftButton: + self.clicked.emit() + + +class MenuItemWidget(QFrame): + """A single menu item in the popup.""" + + def __init__(self, parent, icon, label, launch, icon_size, animation, tooltip): + super().__init__() + self.parent_widget = parent + self._launch = launch + self._animation = animation + + self.setProperty("class", "menu-item") + self.setCursor(QCursor(Qt.CursorShape.PointingHandCursor)) + + # Create layout + layout = QHBoxLayout(self) + layout.setSpacing(8) + layout.setContentsMargins(8, 8, 8, 8) + + # Create icon label + self._icon_label = QLabel() + self._icon_label.setProperty("class", "icon") + self._icon_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + + if os.path.isfile(icon): + pixmap = QPixmap(icon).scaled( + icon_size, + icon_size, + Qt.AspectRatioMode.KeepAspectRatio, + Qt.TransformationMode.SmoothTransformation, + ) + self._icon_label.setPixmap(pixmap) + else: + self._icon_label.setText(icon) + + layout.addWidget(self._icon_label) + + # Create text label + self._text_label = QLabel(label) + self._text_label.setProperty("class", "label") + layout.addWidget(self._text_label, stretch=1) + + if tooltip and label: + set_tooltip(self, label, 0) + + def mousePressEvent(self, event): + if event.button() == Qt.MouseButton.LeftButton: + event.accept() # Accept the event to prevent propagation + if self._animation["enabled"]: + AnimationManager.animate(self, self._animation["type"], self._animation["duration"]) + self.parent_widget.execute_code(self._launch) + else: + super().mousePressEvent(event) diff --git a/src/styles.css b/src/styles.css index 4d277d242..d9d855a0b 100644 --- a/src/styles.css +++ b/src/styles.css @@ -197,6 +197,42 @@ For more information about configuration options, please visit the Wiki https:// .power-menu-popup .button.cancel.hover .label { color: rgb(255, 255, 255) } + +/* Menu Widget Popup */ +.menu-popup { + background-color: var(--bg-color1); + border-radius: 8px; + padding: 4px; +} + +.menu-popup .menu-item { + background-color: transparent; + border-radius: 4px; + padding: 8px; + margin: 2px 0; +} + +.menu-popup .menu-item:hover { + background-color: var(--bg-color2); +} + +.menu-popup .menu-item .icon { + color: var(--text2); + font-size: 16px; + min-width: 20px; +} + +.menu-popup .menu-item .label { + color: var(--text1); + font-size: 13px; + font-weight: 400; +} + +.menu-popup .menu-item:hover .icon, +.menu-popup .menu-item:hover .label { + color: var(--text0); +} + .uptime { font-size: 14px; margin-bottom: 10px; From b0dc385df1412ee7e7f4077668284e89bc006268 Mon Sep 17 00:00:00 2001 From: Jonathan Newell Date: Fri, 24 Oct 2025 10:49:27 -0400 Subject: [PATCH 2/2] Guards against None values in shadow and style Addresses potential errors by checking for None values before applying shadows and styles to the menu widget. This prevents crashes when shadow configurations or styles are not defined. Also, clarifies type hints for menu items and shadow attributes to improve code readability. --- src/core/widgets/yasb/menu.py | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/src/core/widgets/yasb/menu.py b/src/core/widgets/yasb/menu.py index 017518e77..59d3f5e9a 100644 --- a/src/core/widgets/yasb/menu.py +++ b/src/core/widgets/yasb/menu.py @@ -21,7 +21,7 @@ def __init__( self, label: str, class_name: str, - menu_items: list[dict[str]], + menu_items: list[dict[str, str]], icon: str, image_icon_size: int, popup_image_icon_size: int, @@ -33,8 +33,8 @@ def __init__( popup_offset: dict[str, int], alignment: str, direction: str, - label_shadow: dict = None, - container_shadow: dict = None, + label_shadow: dict | None = None, + container_shadow: dict | None = None, ): super().__init__(class_name=f"menu-widget {class_name}") self._label = label @@ -65,7 +65,8 @@ def __init__( self._widget_container = QFrame() self._widget_container.setLayout(self._widget_container_layout) self._widget_container.setProperty("class", "widget-container") - add_shadow(self._widget_container, self._container_shadow) + if self._container_shadow is not None: + add_shadow(self._widget_container, self._container_shadow) # Create the menu button container self._button_container = QWidget() @@ -91,7 +92,8 @@ def __init__( else: self._icon_label.setText(self._icon) - add_shadow(self._icon_label, self._label_shadow) + if self._label_shadow is not None: + add_shadow(self._icon_label, self._label_shadow) self._button_layout.addWidget(self._icon_label) # Create text label if label is provided @@ -102,7 +104,8 @@ def __init__( self._text_label.setText(self._label) self._text_label.clicked.connect(self._toggle_popup) - add_shadow(self._text_label, self._label_shadow) + if self._label_shadow is not None: + add_shadow(self._text_label, self._label_shadow) self._button_layout.addWidget(self._text_label) # Set tooltip @@ -177,11 +180,18 @@ def _show_popup(self): # Adjust popup size to content self._popup.adjustSize() - # Force Qt to apply the stylesheet to the popup and its children - self._popup.style().unpolish(self._popup) - self._popup.style().polish(self._popup) - self._popup._popup_content.style().unpolish(self._popup._popup_content) - self._popup._popup_content.style().polish(self._popup._popup_content) + # Force Qt to apply the stylesheet to the popup and its children (guard against None) + popup_style = self._popup.style() + if popup_style is not None: + popup_style.unpolish(self._popup) + popup_style.polish(self._popup) + + popup_content = getattr(self._popup, "_popup_content", None) + if popup_content is not None: + content_style = popup_content.style() + if content_style is not None: + content_style.unpolish(popup_content) + content_style.polish(popup_content) # Position and show popup self._popup.setPosition(