diff --git a/streamdeck_ui/api.py b/streamdeck_ui/api.py index 379e82ca..e461b2a9 100644 --- a/streamdeck_ui/api.py +++ b/streamdeck_ui/api.py @@ -19,6 +19,8 @@ decks: Dict[str, StreamDeck.StreamDeck] = {} state: Dict[str, Dict[str, Union[int, Dict[int, Dict[int, Dict[str, str]]]]]] = {} +live_functions: List = [] + def _key_change_callback(deck_id: str, _deck: StreamDeck.StreamDeck, key: int, state: bool) -> None: if state: @@ -139,6 +141,114 @@ def _button_state(deck_id: str, page: int, button: int) -> dict: return buttons_state.setdefault(button, {}) # type: ignore +class LiveFunction: + + def __init__(self, deck_id: str, page: int, button: int, function_to_run, args): + self.deck_id = deck_id + self.page = page + self.button = button + self.function = function_to_run + self.function_args = args + + def __eq__(self, other): + if self.deck_id != other.deck_id: + return False + + if self.page != other.page: + return False + + if self.button != other.button: + return False + + if self.function != other.function: + return False + + if self.function_args != other.function_args: + return False + + return True + + def __hash__(self): + return hash(f"{self.deck_id}{self.page}{self.button}") + + def remove_all_from_btn(self): + lf_to_remove = [] + for live_function in live_functions: + if self.deck_id == live_function.deck_id and self.page == live_function.page and self.button == live_function.button: + lf_to_remove.append(live_function) + + for lf in lf_to_remove: + live_functions.remove(lf) + + def btn_has_diff_function_running(self): + return any(self.deck_id == f.deck_id and self.page == f.page and self.button == f.button and (self.function != f.function or self.function_args != f.function_args) for f in live_functions) + + +def _set_button_live_info(deck_id: str, page: int, button: int, start: bool, func, *args): + import threading + + live_function = LiveFunction(deck_id, page, button, func, *args) + + if not start: + live_function.remove_all_from_btn() + + # Clear Text + set_button_info(deck_id, page, button, "") + return + + if live_function.btn_has_diff_function_running(): + live_function.remove_all_from_btn() + + # Already registered, skip and carry on + if live_function in live_functions: + return + + live_functions.append(live_function) + + # Ensure we don't kick off multiple threads at once + thread_name = "live_updater" + if any(thread.name == thread_name for thread in threading.enumerate()): + return + + thread = threading.Thread(name=thread_name, target=_start_live_updater) + thread.daemon = True + thread.start() + + +def set_button_live_time(deck_id: str, page: int, button: int, start: bool) -> None: + """Set the button to display live time every second""" + _set_button_live_info(deck_id, page, button, start, _get_current_time, ["%H:%M:%S"]) + + +def _get_current_time(date_format: str): + from datetime import datetime + return datetime.now().strftime(date_format) + + +def set_button_live_hour(deck_id: str, page: int, button: int, start: bool) -> None: + """Set the button to display the current hour""" + # Set Font + _button_state(deck_id, page, button)["font_size"] = 48 + _set_button_live_info(deck_id, page, button, start, _get_current_time, ["%H"]) + + +def set_button_live_minute(deck_id: str, page: int, button: int, start: bool) -> None: + """Set the button to display the current minute""" + _button_state(deck_id, page, button)["font_size"] = 48 + _set_button_live_info(deck_id, page, button, start, _get_current_time, ["%M"]) + + +def _start_live_updater(): + import time + + while len(live_functions) > 0: + for live_function in live_functions: + result = live_function.function(*live_function.function_args) + set_button_info(live_function.deck_id, live_function.page, live_function.button, result) + + time.sleep(1) + + def set_button_text(deck_id: str, page: int, button: int, text: str) -> None: """Set the text associated with a button""" _button_state(deck_id, page, button)["text"] = text @@ -165,6 +275,19 @@ def get_button_icon(deck_id: str, page: int, button: int) -> str: return _button_state(deck_id, page, button).get("icon", "") +def set_button_info(deck_id: str, page: int, button: int, info: str) -> None: + """Set the information associated with a button""" + _button_state(deck_id, page, button)["information"] = info + image_cache.pop(f"{deck_id}.{page}.{button}", None) + render() + _save_state() + + +def get_button_info(deck_id: str, page: int, button: int) -> str: + """Returns the information set for the specified button""" + return _button_state(deck_id, page, button).get("information", "") + + def set_button_change_brightness(deck_id: str, page: int, button: int, amount: int) -> None: """Sets the brightness changing associated with a button""" _button_state(deck_id, page, button)["brightness_change"] = amount @@ -199,6 +322,17 @@ def get_button_switch_page(deck_id: str, page: int, button: int) -> int: return _button_state(deck_id, page, button).get("switch_page", 0) +def set_button_information_index(deck_id: str, page: int, button: int, info_index: int) -> None: + """Sets the Information index for the given button""" + _button_state(deck_id, page, button)["information_index"] = info_index + _save_state() + + +def get_button_information_index(deck_id: str, page: int, button: int) -> int: + """Returns the index of the 'Information' dropdown for the specified button.""" + return _button_state(deck_id, page, button).get("information_index", 0) + + def set_button_keys(deck_id: str, page: int, button: int, keys: str) -> None: """Sets the keys associated with the button""" _button_state(deck_id, page, button)["keys"] = keys @@ -271,11 +405,17 @@ def render() -> None: deck.set_key_image(button_id, image) -def _render_key_image(deck, icon: str = "", text: str = "", font: str = DEFAULT_FONT, **kwargs): +def _render_key_image(deck, icon: str = "", text: str = "", information: str = "", font: str = DEFAULT_FONT, **kwargs): """Renders an individual key image""" image = ImageHelpers.PILHelper.create_image(deck) draw = ImageDraw.Draw(image) + font_size = kwargs.get("font_size") if kwargs.get("font_size") else 14 + + # Give information priority over text + if information: + text = information + if icon: rgba_icon = Image.open(icon).convert("RGBA") else: @@ -290,12 +430,12 @@ def _render_key_image(deck, icon: str = "", text: str = "", font: str = DEFAULT_ image.paste(rgba_icon, icon_pos, rgba_icon) if text: - true_font = ImageFont.truetype(os.path.join(FONTS_PATH, font), 14) + true_font = ImageFont.truetype(os.path.join(FONTS_PATH, font), font_size) label_w, label_h = draw.textsize(text, font=true_font) if icon: label_pos = ((image.width - label_w) // 2, image.height - 20) else: - label_pos = ((image.width - label_w) // 2, (image.height // 2) - 7) + label_pos = ((image.width - label_w) // 2, ((image.height - label_h) // 2)) draw.text(label_pos, text=text, font=true_font, fill="white") return ImageHelpers.PILHelper.to_native_format(deck, image) diff --git a/streamdeck_ui/gui.py b/streamdeck_ui/gui.py index f3631121..905ecd0b 100644 --- a/streamdeck_ui/gui.py +++ b/streamdeck_ui/gui.py @@ -96,6 +96,12 @@ def redraw_buttons(ui) -> None: current_tab = ui.pages.currentWidget() buttons = current_tab.findChildren(QtWidgets.QToolButton) for button in buttons: + # Give "info" priority + info = api.get_button_info(deck_id, _page(ui), button.index) + if info: + button.setText(info) + continue + button.setText(api.get_button_text(deck_id, _page(ui), button.index)) button.setIcon(QIcon(api.get_button_icon(deck_id, _page(ui), button.index))) @@ -105,6 +111,31 @@ def set_brightness(ui, value: int) -> None: api.set_brightness(deck_id, value) +def set_information(ui, index: int, button=None, build: bool=False) -> None: + if not button: + button = selected_button + deck_id = _deck_id(ui) + prev_information_index = api.get_button_information_index(deck_id, _page(ui), button.index) + if prev_information_index == index and not build: + return + + api.set_button_information_index(deck_id, _page(ui), button.index, index) + + if index == 1: + # Current Time (H:M:S) + api.set_button_live_time(deck_id, _page(ui), button.index, True) + elif index == 2: + # Current Time (H) + api.set_button_live_hour(deck_id, _page(ui), button.index, True) + elif index == 3: + # Current Time (M) + api.set_button_live_minute(deck_id, _page(ui), button.index, True) + else: + api.set_button_live_time(deck_id, _page(ui), button.index, False) + + ui.text.setText(api.get_button_text(deck_id, _page(ui), button.index)) + + def button_clicked(ui, clicked_button, buttons) -> None: global selected_button selected_button = clicked_button @@ -116,13 +147,21 @@ def button_clicked(ui, clicked_button, buttons) -> None: deck_id = _deck_id(ui) button_id = selected_button.index - ui.text.setText(api.get_button_text(deck_id, _page(ui), button_id)) + text = api.get_button_text(deck_id, _page(ui), button_id) + ui.text.setText(text) ui.command.setText(api.get_button_command(deck_id, _page(ui), button_id)) ui.keys.setText(api.get_button_keys(deck_id, _page(ui), button_id)) ui.write.setPlainText(api.get_button_write(deck_id, _page(ui), button_id)) ui.change_brightness.setValue(api.get_button_change_brightness(deck_id, _page(ui), button_id)) ui.switch_page.setValue(api.get_button_switch_page(deck_id, _page(ui), button_id)) + info_index = api.get_button_information_index(deck_id, _page(ui), button_id) + if info_index == 0: + api.set_button_info(deck_id, _page(ui), button_id, "") + ui.information.setCurrentIndex(info_index) + + redraw_buttons(ui) + def build_buttons(ui, tab) -> None: deck_id = _deck_id(ui) @@ -160,6 +199,10 @@ def build_buttons(ui, tab) -> None: lambda button=button, buttons=buttons: button_clicked(ui, button, buttons) ) + info_index = api.get_button_information_index(deck_id, _page(ui), button.index) + if info_index != 0: + set_information(ui, info_index, button, build=True) + redraw_buttons(ui) tab.hide() tab.show() @@ -263,6 +306,7 @@ def start(_exit: bool = False) -> None: ui.switch_page.valueChanged.connect(partial(update_switch_page, ui)) ui.imageButton.clicked.connect(partial(select_image, main_window)) ui.brightness.valueChanged.connect(partial(set_brightness, ui)) + ui.information.currentIndexChanged.connect(partial(set_information, ui)) for deck_id, deck in api.open_decks().items(): ui.device_list.addItem(f"{deck['type']} - {deck_id}", userData=deck_id) diff --git a/streamdeck_ui/main.ui b/streamdeck_ui/main.ui index a7e458be..abfb0c67 100644 --- a/streamdeck_ui/main.ui +++ b/streamdeck_ui/main.ui @@ -55,6 +55,19 @@ + + + + + 0 + 0 + + + + Information: + + + @@ -218,14 +231,14 @@ - + Write Text: - + @@ -262,6 +275,37 @@ + + + + Information: + + + + + + + + N/A + + + + + Current Time (H:M:S) + + + + + Current Time (H) + + + + + Current Time (M) + + + + @@ -279,7 +323,7 @@ 0 0 844 - 30 + 22 diff --git a/streamdeck_ui/ui_main.py b/streamdeck_ui/ui_main.py index 8ff1c4f5..def2e904 100644 --- a/streamdeck_ui/ui_main.py +++ b/streamdeck_ui/ui_main.py @@ -3,8 +3,8 @@ # Form implementation generated from reading ui file 'streamdeck_ui/main.ui', # licensing of 'streamdeck_ui/main.ui' applies. # -# Created: Sun Oct 6 02:46:55 2019 -# by: pyside2-uic running on PySide2 5.13.1 +# Created: Sat Oct 31 08:17:32 2020 +# by: pyside2-uic running on PySide2 5.13.2 # # WARNING! All changes made in this file will be lost! @@ -27,24 +27,26 @@ def setupUi(self, MainWindow): self.device_list.setMinimumSize(QtCore.QSize(400, 0)) self.device_list.setObjectName("device_list") self.horizontalLayout_3.addWidget(self.device_list) - spacerItem = QtWidgets.QSpacerItem( - 40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum - ) + spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout_3.addItem(spacerItem) self.label_4 = QtWidgets.QLabel(self.centralwidget) - sizePolicy = QtWidgets.QSizePolicy( - QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred - ) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_4.sizePolicy().hasHeightForWidth()) self.label_4.setSizePolicy(sizePolicy) self.label_4.setObjectName("label_4") self.horizontalLayout_3.addWidget(self.label_4) + self.label_9 = QtWidgets.QLabel(self.centralwidget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label_9.sizePolicy().hasHeightForWidth()) + self.label_9.setSizePolicy(sizePolicy) + self.label_9.setObjectName("label_9") + self.horizontalLayout_3.addWidget(self.label_9) self.brightness = QtWidgets.QSlider(self.centralwidget) - sizePolicy = QtWidgets.QSizePolicy( - QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed - ) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.brightness.sizePolicy().hasHeightForWidth()) @@ -146,10 +148,10 @@ def setupUi(self, MainWindow): self.formLayout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.keys) self.label_6 = QtWidgets.QLabel(self.groupBox) self.label_6.setObjectName("label_6") - self.formLayout.setWidget(6, QtWidgets.QFormLayout.LabelRole, self.label_6) + self.formLayout.setWidget(7, QtWidgets.QFormLayout.LabelRole, self.label_6) self.write = QtWidgets.QPlainTextEdit(self.groupBox) self.write.setObjectName("write") - self.formLayout.setWidget(6, QtWidgets.QFormLayout.FieldRole, self.write) + self.formLayout.setWidget(7, QtWidgets.QFormLayout.FieldRole, self.write) self.label_8 = QtWidgets.QLabel(self.groupBox) self.label_8.setObjectName("label_8") self.formLayout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.label_8) @@ -166,13 +168,23 @@ def setupUi(self, MainWindow): self.change_brightness.setMinimum(-99) self.change_brightness.setObjectName("change_brightness") self.formLayout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.change_brightness) + self.label_91 = QtWidgets.QLabel(self.groupBox) + self.label_91.setObjectName("label_91") + self.formLayout.setWidget(6, QtWidgets.QFormLayout.LabelRole, self.label_91) + self.information = QtWidgets.QComboBox(self.groupBox) + self.information.setObjectName("information") + self.information.addItem("") + self.information.addItem("") + self.information.addItem("") + self.information.addItem("") + self.formLayout.setWidget(6, QtWidgets.QFormLayout.FieldRole, self.information) self.verticalLayout_3.addLayout(self.formLayout) self.horizontalLayout.addWidget(self.groupBox) self.verticalLayout.addLayout(self.horizontalLayout) self.verticalLayout_2.addLayout(self.verticalLayout) MainWindow.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(MainWindow) - self.menubar.setGeometry(QtCore.QRect(0, 0, 844, 30)) + self.menubar.setGeometry(QtCore.QRect(0, 0, 844, 22)) self.menubar.setObjectName("menubar") self.menuFile = QtWidgets.QMenu(self.menubar) self.menuFile.setObjectName("menuFile") @@ -193,75 +205,34 @@ def setupUi(self, MainWindow): QtCore.QMetaObject.connectSlotsByName(MainWindow) def retranslateUi(self, MainWindow): - MainWindow.setWindowTitle( - QtWidgets.QApplication.translate("MainWindow", "MainWindow", None, -1) - ) - self.label_4.setText( - QtWidgets.QApplication.translate("MainWindow", "Brightness:", None, -1) - ) - self.pages.setTabText( - self.pages.indexOf(self.page_1), - QtWidgets.QApplication.translate("MainWindow", "Page 1", None, -1), - ) - self.pages.setTabText( - self.pages.indexOf(self.page_2), - QtWidgets.QApplication.translate("MainWindow", "2", None, -1), - ) - self.pages.setTabText( - self.pages.indexOf(self.page_3), - QtWidgets.QApplication.translate("MainWindow", "3", None, -1), - ) - self.pages.setTabText( - self.pages.indexOf(self.page_4), - QtWidgets.QApplication.translate("MainWindow", "4", None, -1), - ) - self.pages.setTabText( - self.pages.indexOf(self.page_5), - QtWidgets.QApplication.translate("MainWindow", "5", None, -1), - ) - self.pages.setTabText( - self.pages.indexOf(self.page_6), - QtWidgets.QApplication.translate("MainWindow", "6", None, -1), - ) - self.pages.setTabText( - self.pages.indexOf(self.page_7), - QtWidgets.QApplication.translate("MainWindow", "7", None, -1), - ) - self.pages.setTabText( - self.pages.indexOf(self.page_8), - QtWidgets.QApplication.translate("MainWindow", "8", None, -1), - ) - self.pages.setTabText( - self.pages.indexOf(self.page_9), - QtWidgets.QApplication.translate("MainWindow", "9", None, -1), - ) - self.pages.setTabText( - self.pages.indexOf(self.tab_10), - QtWidgets.QApplication.translate("MainWindow", "10", None, -1), - ) - self.groupBox.setTitle( - QtWidgets.QApplication.translate("MainWindow", "Configure Button", None, -1) - ) + MainWindow.setWindowTitle(QtWidgets.QApplication.translate("MainWindow", "MainWindow", None, -1)) + self.label_4.setText(QtWidgets.QApplication.translate("MainWindow", "Brightness:", None, -1)) + self.label_9.setText(QtWidgets.QApplication.translate("MainWindow", "Information:", None, -1)) + self.pages.setTabText(self.pages.indexOf(self.page_1), QtWidgets.QApplication.translate("MainWindow", "Page 1", None, -1)) + self.pages.setTabText(self.pages.indexOf(self.page_2), QtWidgets.QApplication.translate("MainWindow", "2", None, -1)) + self.pages.setTabText(self.pages.indexOf(self.page_3), QtWidgets.QApplication.translate("MainWindow", "3", None, -1)) + self.pages.setTabText(self.pages.indexOf(self.page_4), QtWidgets.QApplication.translate("MainWindow", "4", None, -1)) + self.pages.setTabText(self.pages.indexOf(self.page_5), QtWidgets.QApplication.translate("MainWindow", "5", None, -1)) + self.pages.setTabText(self.pages.indexOf(self.page_6), QtWidgets.QApplication.translate("MainWindow", "6", None, -1)) + self.pages.setTabText(self.pages.indexOf(self.page_7), QtWidgets.QApplication.translate("MainWindow", "7", None, -1)) + self.pages.setTabText(self.pages.indexOf(self.page_8), QtWidgets.QApplication.translate("MainWindow", "8", None, -1)) + self.pages.setTabText(self.pages.indexOf(self.page_9), QtWidgets.QApplication.translate("MainWindow", "9", None, -1)) + self.pages.setTabText(self.pages.indexOf(self.tab_10), QtWidgets.QApplication.translate("MainWindow", "10", None, -1)) + self.groupBox.setTitle(QtWidgets.QApplication.translate("MainWindow", "Configure Button", None, -1)) self.label.setText(QtWidgets.QApplication.translate("MainWindow", "Image:", None, -1)) self.imageButton.setText(QtWidgets.QApplication.translate("MainWindow", "Choose", None, -1)) self.label_2.setText(QtWidgets.QApplication.translate("MainWindow", "Text:", None, -1)) self.label_3.setText(QtWidgets.QApplication.translate("MainWindow", "Command:", None, -1)) - self.label_5.setText( - QtWidgets.QApplication.translate("MainWindow", "Press Keys:", None, -1) - ) - self.label_6.setText( - QtWidgets.QApplication.translate("MainWindow", "Write Text:", None, -1) - ) - self.label_8.setText( - QtWidgets.QApplication.translate("MainWindow", "Switch Page:", None, -1) - ) - self.label_7.setText( - QtWidgets.QApplication.translate("MainWindow", "Brightness +/-:", None, -1) - ) + self.label_5.setText(QtWidgets.QApplication.translate("MainWindow", "Press Keys:", None, -1)) + self.label_6.setText(QtWidgets.QApplication.translate("MainWindow", "Write Text:", None, -1)) + self.label_8.setText(QtWidgets.QApplication.translate("MainWindow", "Switch Page:", None, -1)) + self.label_7.setText(QtWidgets.QApplication.translate("MainWindow", "Brightness +/-:", None, -1)) + self.label_91.setText(QtWidgets.QApplication.translate("MainWindow", "Information:", None, -1)) + self.information.setItemText(0, QtWidgets.QApplication.translate("MainWindow", "N/A", None, -1)) + self.information.setItemText(1, QtWidgets.QApplication.translate("MainWindow", "Current Time (H:M:S)", None, -1)) + self.information.setItemText(2, QtWidgets.QApplication.translate("MainWindow", "Current Time (H)", None, -1)) + self.information.setItemText(3, QtWidgets.QApplication.translate("MainWindow", "Current Time (M)", None, -1)) self.menuFile.setTitle(QtWidgets.QApplication.translate("MainWindow", "File", None, -1)) - self.actionImport.setText( - QtWidgets.QApplication.translate("MainWindow", "Import", None, -1) - ) - self.actionExport.setText( - QtWidgets.QApplication.translate("MainWindow", "Export", None, -1) - ) + self.actionImport.setText(QtWidgets.QApplication.translate("MainWindow", "Import", None, -1)) + self.actionExport.setText(QtWidgets.QApplication.translate("MainWindow", "Export", None, -1)) +