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