Implemented GUI for syng #2
2 changed files with 90 additions and 38 deletions
|
@ -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"]
|
||||||
|
|
125
syng/gui.py
125
syng/gui.py
|
@ -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()
|
||||||
|
|
Loading…
Add table
Reference in a new issue