diff --git a/gremlin/ui/action_image_generator.py b/gremlin/ui/action_image_generator.py index a575926e..fdec8d5c 100644 --- a/gremlin/ui/action_image_generator.py +++ b/gremlin/ui/action_image_generator.py @@ -14,6 +14,8 @@ QtQuick, ) +from gremlin.ui.util import ColorInformation + class ActionSummaryImageProvider(QtQuick.QQuickImageProvider): @@ -76,7 +78,10 @@ def requestImage( self._cache.move_to_end(image_id) return self._cache[image_id] - image = self._render(image_id) + # A "?" suffix, bumped on a theme change, keeps the id (and thus + # the cache entry) distinct per theme; it is not part of the action + # string, so strip it before rendering. + image = self._render(image_id.split("?", 1)[0]) with self._lock: if len(self._cache) >= self._max_cache_size: @@ -101,7 +106,7 @@ def _render(self, action_string: str) -> QtGui.QImage: try: painter.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing) painter.setRenderHint(QtGui.QPainter.RenderHint.TextAntialiasing) - painter.setPen(QtGui.QColor(0, 0, 0)) + painter.setPen(ColorInformation().foreground) x_offset = 0 for token in tokens: diff --git a/gremlin/ui/backend.py b/gremlin/ui/backend.py index c1787752..0d9cd1cc 100644 --- a/gremlin/ui/backend.py +++ b/gremlin/ui/backend.py @@ -73,6 +73,7 @@ class UIState(QtCore.QObject): inputChanged = Signal() modeChanged = Signal() tabChanged = Signal() + themeRevisionChanged = Signal() selectIndex = Signal(int) def __init__(self, parent: ta.OQO=None) -> None: @@ -82,6 +83,7 @@ def __init__(self, parent: ta.OQO=None) -> None: self._current_input = {} self._current_mode = "Default" self._current_tab = "physical" + self._theme_revision = 0 event_handler.EventListener().device_change_event.connect( self._device_change @@ -135,6 +137,11 @@ def setCurrentTab(self, tab: str) -> None: self._current_tab = tab self.tabChanged.emit() + @Slot() + def bumpThemeRevision(self) -> None: + self._theme_revision += 1 + self.themeRevisionChanged.emit() + @Property(str, notify=deviceChanged) def currentDevice(self) -> str: return str(self._current_device).upper() @@ -161,6 +168,15 @@ def currentMode(self) -> str: def currentTab(self) -> str: return self._current_tab + @Property(int, notify=themeRevisionChanged) + def themeRevision(self) -> int: + """Counter bumped whenever the theme colours change. + + Image sources that bake in a theme colour append it so a theme change + alters the URL and the image is re-requested in the new colour. + """ + return self._theme_revision + def __str__(self) -> str: cur_input = self._current_input.get( self._current_device, diff --git a/joystick_gremlin.py b/joystick_gremlin.py index 63a55463..d2bf4550 100644 --- a/joystick_gremlin.py +++ b/joystick_gremlin.py @@ -357,15 +357,30 @@ def __init__(self, argv: List[str]) -> None: "Failed to find color information object in QML." ) gremlin.ui.util.ColorInformation().update_colors(self.color_information_object) - self.color_information_object.isDarkThemeChanged.connect( - lambda: gremlin.ui.util.ColorInformation().update_colors( - self.color_information_object) - ) + # Coalesce the colour change signals into a single update call that + # triggers only once the current event queue is empty. + self._theme_refresh_timer = QtCore.QTimer() + self._theme_refresh_timer.setSingleShot(True) + self._theme_refresh_timer.setInterval(0) + self._theme_refresh_timer.timeout.connect(self._on_theme_colors_changed) + for changed in ( + self.color_information_object.foregroundChanged, + self.color_information_object.backgroundChanged, + self.color_information_object.accentChanged, + ): + changed.connect(self._theme_refresh_timer.start) # Run UI. self.syslog.info("Gremlin UI launching") self.aboutToQuit.connect(shutdown_cleanup) + def _on_theme_colors_changed(self) -> None: + """Refreshes the cached theme colours and asks QML to redraw.""" + gremlin.ui.util.ColorInformation().update_colors( + self.color_information_object + ) + self.backend.ui_state.bumpThemeRevision() + def process_cmd_args(self, args: argparse.Namespace) -> None: # Load the profile specified by the user on the command line, otherwise # attempt to load the previously loaded profile diff --git a/qml/InputButton.qml b/qml/InputButton.qml index 02dd4693..c19f4301 100644 --- a/qml/InputButton.qml +++ b/qml/InputButton.qml @@ -153,6 +153,7 @@ Button { sourceComponent: Image { source: "image://action_summary/" + actionSequenceDescriptor + + "?r=" + uiState.themeRevision asynchronous: false cache: false clip: true