Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 143 additions & 3 deletions streamdeck_ui/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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)
Expand Down
46 changes: 45 additions & 1 deletion streamdeck_ui/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)))

Expand All @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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)

Expand Down
50 changes: 47 additions & 3 deletions streamdeck_ui/main.ui
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,19 @@
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_9">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Information:</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="brightness">
<property name="sizePolicy">
Expand Down Expand Up @@ -218,14 +231,14 @@
<item row="3" column="1">
<widget class="QLineEdit" name="keys"/>
</item>
<item row="6" column="0">
<item row="7" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Write Text:</string>
</property>
</widget>
</item>
<item row="6" column="1">
<item row="7" column="1">
<widget class="QPlainTextEdit" name="write"/>
</item>
<item row="4" column="0">
Expand Down Expand Up @@ -262,6 +275,37 @@
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Information:</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QComboBox" name="information">
<item>
<property name="text">
<string>N/A</string>
</property>
</item>
<item>
<property name="text">
<string>Current Time (H:M:S)</string>
</property>
</item>
<item>
<property name="text">
<string>Current Time (H)</string>
</property>
</item>
<item>
<property name="text">
<string>Current Time (M)</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
</layout>
Expand All @@ -279,7 +323,7 @@
<x>0</x>
<y>0</y>
<width>844</width>
<height>30</height>
<height>22</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
Expand Down
Loading