Implemented GUI for syng #2

Merged
christofsteel merged 18 commits from gui into main 2023-12-18 19:48:28 +01:00
2 changed files with 90 additions and 38 deletions
Showing only changes of commit 785354032e - Show all commits

View file

@ -13,7 +13,7 @@ syng-server = "syng.server:main"
syng-shell = "syng.webclientmockup:main" syng-shell = "syng.webclientmockup:main"
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "^3.7" python = "^3.8"
pytube = "*" pytube = "*"
aiohttp = "^3.8.3" aiohttp = "^3.8.3"
python-socketio = "^5.7.2" python-socketio = "^5.7.2"
@ -26,6 +26,7 @@ customtkinter = "^5.2.1"
qrcode = "^7.4.2" qrcode = "^7.4.2"
pymediainfo = "^6.1.0" pymediainfo = "^6.1.0"
pyyaml = "^6.0.1" pyyaml = "^6.0.1"
async-tkinter-loop = "^0.9.2"
[build-system] [build-system]
requires = ["poetry-core"] requires = ["poetry-core"]

View file

@ -1,20 +1,25 @@
import asyncio
import builtins import builtins
from functools import partial
import webbrowser
from yaml import load, Loader from yaml import load, Loader
import customtkinter import customtkinter
import qrcode import qrcode
import secrets import secrets
import string import string
from tkinter import filedialog from tkinter import filedialog
from async_tkinter_loop import async_handler, async_mainloop
from async_tkinter_loop.mixins import AsyncCTk
from syng.client import default_config from .client import default_config, start_client
from .sources import available_sources from .sources import available_sources
class OptionFrame(customtkinter.CTkFrame): class OptionFrame(customtkinter.CTkScrollableFrame):
def add_option_label(self, text): def add_option_label(self, text):
customtkinter.CTkLabel(self, text=text, justify="left").grid( customtkinter.CTkLabel(self, text=text, justify="left").grid(
column=0, row=self.number_of_options, padx=5, pady=5 column=0, row=self.number_of_options, padx=5, pady=5, sticky="ne"
) )
def add_bool_option(self, name, description, value=False): def add_bool_option(self, name, description, value=False):
@ -29,7 +34,7 @@ class OptionFrame(customtkinter.CTkFrame):
self.bool_options[name].select() self.bool_options[name].select()
else: else:
self.bool_options[name].deselect() self.bool_options[name].deselect()
self.bool_options[name].grid(column=1, row=self.number_of_options) self.bool_options[name].grid(column=1, row=self.number_of_options, sticky="EW")
self.number_of_options += 1 self.number_of_options += 1
def add_string_option(self, name, description, value="", callback=None): def add_string_option(self, name, description, value="", callback=None):
@ -40,33 +45,68 @@ class OptionFrame(customtkinter.CTkFrame):
self.string_options[name] = customtkinter.CTkTextbox( self.string_options[name] = customtkinter.CTkTextbox(
self, wrap="none", height=1 self, wrap="none", height=1
) )
self.string_options[name].grid(column=1, row=self.number_of_options) self.string_options[name].grid(
column=1, row=self.number_of_options, sticky="EW"
)
self.string_options[name].insert("0.0", value) self.string_options[name].insert("0.0", value)
if callback is not None: if callback is not None:
self.string_options[name].bind("<KeyRelease>", callback) self.string_options[name].bind("<KeyRelease>", callback)
self.string_options[name].bind("<ButtonRelease>", callback) self.string_options[name].bind("<ButtonRelease>", callback)
self.number_of_options += 1 self.number_of_options += 1
def del_list_element(self, name, element, frame):
self.list_options[name].remove(element)
frame.destroy()
def add_list_element(self, name, frame, init, callback):
input_and_minus = customtkinter.CTkFrame(frame)
input_and_minus.pack(side="top", fill="x", expand=True)
input_field = customtkinter.CTkTextbox(input_and_minus, wrap="none", height=1)
input_field.pack(side="left", fill="x", expand=True)
input_field.insert("0.0", init)
if callback is not None:
input_field.bind("<KeyRelease>", callback)
input_field.bind("<ButtonRelease>", callback)
minus_button = customtkinter.CTkButton(
input_and_minus,
text="-",
width=40,
command=partial(self.del_list_element, name, input_field, input_and_minus),
)
minus_button.pack(side="right")
self.list_options[name].append(input_field)
def add_list_option(self, name, description, value=[], callback=None): def add_list_option(self, name, description, value=[], callback=None):
self.add_option_label(description) self.add_option_label(description)
self.list_options[name] = customtkinter.CTkTextbox(self, wrap="none", height=1) frame = customtkinter.CTkFrame(self)
self.list_options[name].grid(column=1, row=self.number_of_options) frame.grid(column=1, row=self.number_of_options, sticky="EW")
self.list_options[name].insert("0.0", ", ".join(value))
if callback is not None: self.list_options[name] = []
self.list_options[name].bind("<KeyRelease>", callback) for v in value:
self.list_options[name].bind("<ButtonRelease>", callback) self.add_list_element(name, frame, v, callback)
plus_button = customtkinter.CTkButton(
frame,
text="+",
command=partial(self.add_list_element, name, frame, "", callback),
)
plus_button.pack(side="bottom", fill="x", expand=True)
self.number_of_options += 1 self.number_of_options += 1
def add_choose_option(self, name, description, values, value=""): def add_choose_option(self, name, description, values, value=""):
self.add_option_label(description) self.add_option_label(description)
self.choose_options[name] = customtkinter.CTkOptionMenu(self, values=values) self.choose_options[name] = customtkinter.CTkOptionMenu(self, values=values)
self.choose_options[name].grid(column=1, row=self.number_of_options) self.choose_options[name].grid(
column=1, row=self.number_of_options, sticky="EW"
)
self.choose_options[name].set(value) self.choose_options[name].set(value)
self.number_of_options += 1 self.number_of_options += 1
def __init__(self, parent): def __init__(self, parent):
super().__init__(parent) super().__init__(parent)
self.columnconfigure((1,), weight=1)
self.number_of_options = 0 self.number_of_options = 0
self.string_options = {} self.string_options = {}
self.choose_options = {} self.choose_options = {}
@ -84,10 +124,10 @@ class OptionFrame(customtkinter.CTkFrame):
for name, checkbox in self.bool_options.items(): for name, checkbox in self.bool_options.items():
config[name] = checkbox.get() == 1 config[name] = checkbox.get() == 1
for name, textbox in self.list_options.items(): for name, textboxes in self.list_options.items():
config[name] = [ config[name] = []
v.strip() for v in textbox.get("0.0", "end").strip().split(",") for textbox in textboxes:
] config[name].append(textbox.get("0.0", "end").strip())
return config return config
@ -109,9 +149,7 @@ class SourceTab(OptionFrame):
super().__init__(parent) super().__init__(parent)
source = available_sources[source_name] source = available_sources[source_name]
self.vars: dict[str, str | bool | list[str]] = {} self.vars: dict[str, str | bool | list[str]] = {}
for row, (name, (typ, desc, default)) in enumerate( for name, (typ, desc, default) in source.config_schema.items():
source.config_schema.items()
):
value = config[name] if name in config else default value = config[name] if name in config else default
match typ: match typ:
case builtins.bool: case builtins.bool:
@ -133,20 +171,15 @@ class GeneralConfig(OptionFrame):
"waiting_room_policy", "waiting_room_policy",
"Waiting room policy", "Waiting room policy",
["forced", "optional", "none"], ["forced", "optional", "none"],
config["waiting_room_policy"], str(config["waiting_room_policy"]).lower(),
)
self.add_string_option(
"last_song", "Time of last song\nin ISO-8601", config["last_song"]
) )
self.add_string_option("last_song", "Time of last song", config["last_song"])
self.add_string_option( self.add_string_option(
"preview_duration", "Preview Duration", config["preview_duration"] "preview_duration", "Preview Duration", config["preview_duration"]
) )
for name, textbox in self.string_options.items():
if config[name]:
textbox.insert("0.0", config[name])
for name, optionmenu in self.choose_options.items():
optionmenu.set(str(config[name]).lower())
def get_config(self): def get_config(self):
config = super().get_config() config = super().get_config()
try: try:
@ -157,7 +190,7 @@ class GeneralConfig(OptionFrame):
return config return config
class SyngGui(customtkinter.CTk): class SyngGui(customtkinter.CTk, AsyncCTk):
def loadConfig(self): def loadConfig(self):
filedialog.askopenfilename() filedialog.askopenfilename()
@ -177,6 +210,7 @@ class SyngGui(customtkinter.CTk):
self.wm_title("Syng") self.wm_title("Syng")
# Buttons
fileframe = customtkinter.CTkFrame(self) fileframe = customtkinter.CTkFrame(self)
fileframe.pack(side="bottom") fileframe.pack(side="bottom")
@ -192,10 +226,16 @@ class SyngGui(customtkinter.CTk):
) )
startbutton.pack(side="right") startbutton.pack(side="right")
open_web_button = customtkinter.CTkButton(
fileframe, text="Open Web", command=self.open_web
)
open_web_button.pack(side="left")
# Tabs and QR Code
frm = customtkinter.CTkFrame(self) frm = customtkinter.CTkFrame(self)
frm.pack(ipadx=10, padx=10, fill="both", expand=True) frm.pack(ipadx=10, padx=10, fill="both", expand=True)
tabview = customtkinter.CTkTabview(frm) tabview = customtkinter.CTkTabview(frm, width=600, height=500)
tabview.pack(side="right", padx=10, pady=10, fill="both", expand=True) tabview.pack(side="right", padx=10, pady=10, fill="both", expand=True)
tabview.add("General") tabview.add("General")
@ -204,12 +244,12 @@ class SyngGui(customtkinter.CTk):
tabview.set("General") tabview.set("General")
self.qrlabel = customtkinter.CTkLabel(frm, text="") self.qrlabel = customtkinter.CTkLabel(frm, text="")
self.qrlabel.pack(side="left") self.qrlabel.pack(side="left", anchor="n", padx=10, pady=10)
self.general_config = GeneralConfig( self.general_config = GeneralConfig(
tabview.tab("General"), config["config"], self.updateQr tabview.tab("General"), config["config"], self.updateQr
) )
self.general_config.pack(ipadx=10, fill="y") self.general_config.pack(ipadx=10, fill="both", expand=True)
self.tabs = {} self.tabs = {}
@ -222,11 +262,12 @@ class SyngGui(customtkinter.CTk):
self.tabs[source_name] = SourceTab( self.tabs[source_name] = SourceTab(
tabview.tab(source_name), source_name, source_config tabview.tab(source_name), source_name, source_config
) )
self.tabs[source_name].pack(ipadx=10) self.tabs[source_name].pack(ipadx=10, expand=True, fill="both")
self.updateQr() self.updateQr()
def start(self): @async_handler
async def start(self):
sources = {} sources = {}
for source, tab in self.tabs.items(): for source, tab in self.tabs.items():
sources[source] = tab.get_config() sources[source] = tab.get_config()
@ -235,6 +276,14 @@ class SyngGui(customtkinter.CTk):
config = {"sources": sources, "config": general_config} config = {"sources": sources, "config": general_config}
print(config) print(config)
await start_client(config)
def open_web(self):
config = self.general_config.get_config()
server = config["server"]
server += "" if server.endswith("/") else "/"
room = config["room"]
webbrowser.open(server + room)
def changeQr(self, data: str): def changeQr(self, data: str):
qr = qrcode.QRCode(box_size=20, border=2) qr = qrcode.QRCode(box_size=20, border=2)
@ -254,9 +303,11 @@ class SyngGui(customtkinter.CTk):
self.changeQr(server + room) self.changeQr(server + room)
def main(): # async def main():
SyngGui().mainloop() # gui = SyngGui()
# await gui.run()
if __name__ == "__main__": if __name__ == "__main__":
main() # asyncio.run(main())
SyngGui().async_mainloop()