2022-04-11 18:35:28 +01:00
|
|
|
import tkinter as tk
|
|
|
|
from tkinter import ttk
|
|
|
|
from typing import NamedTuple
|
|
|
|
from functools import partial
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
from .errors import VMCompactErrors
|
|
|
|
from .data import _base_vals, _kinds_all
|
|
|
|
from .channels import ChannelFrame
|
|
|
|
from .navigation import Navigation
|
|
|
|
from .menu import Menus
|
|
|
|
from .banner import Banner
|
|
|
|
from .configurations import configuration
|
|
|
|
|
|
|
|
|
|
|
|
class App(tk.Tk):
|
|
|
|
"""Topmost Level of App"""
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def make(cls, kind: NamedTuple):
|
|
|
|
"""
|
|
|
|
Factory function for App
|
|
|
|
|
|
|
|
Returns an App class of a kind
|
|
|
|
"""
|
|
|
|
APP_cls = type(
|
|
|
|
f"Voicemeeter{kind.name}.Compact",
|
|
|
|
(cls,),
|
|
|
|
{
|
|
|
|
"kind": kind,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
return APP_cls
|
|
|
|
|
|
|
|
def __init__(self, vmr):
|
|
|
|
super().__init__()
|
|
|
|
defaults = {
|
|
|
|
"theme": {
|
2022-04-12 00:10:00 +01:00
|
|
|
"enabled": True,
|
2022-04-11 18:35:28 +01:00
|
|
|
"mode": "light",
|
|
|
|
},
|
|
|
|
"extends": {
|
2022-04-14 01:15:29 +01:00
|
|
|
"extended": True,
|
2022-04-11 18:35:28 +01:00
|
|
|
"extends_horizontal": True,
|
|
|
|
},
|
2022-04-14 01:15:29 +01:00
|
|
|
"channel": {
|
|
|
|
"width": 80,
|
|
|
|
"height": 130,
|
|
|
|
},
|
2022-04-12 00:10:00 +01:00
|
|
|
"submixes": {
|
|
|
|
"default": 0,
|
|
|
|
},
|
2022-04-11 18:35:28 +01:00
|
|
|
}
|
2022-04-14 01:15:29 +01:00
|
|
|
if configuration:
|
|
|
|
self.configuration = defaults | self.configuration
|
|
|
|
else:
|
|
|
|
configuration["app"] = defaults
|
2022-04-14 01:19:08 +01:00
|
|
|
_base_vals.themes_enabled = self.configuration["theme"]["enabled"]
|
|
|
|
_base_vals.extends_horizontal = self.configuration["extends"][
|
|
|
|
"extends_horizontal"
|
|
|
|
]
|
|
|
|
_base_vals.submixes = self.configuration["submixes"]["default"]
|
2022-04-11 18:35:28 +01:00
|
|
|
# create menus
|
|
|
|
self.menus = Menus(self, vmr)
|
|
|
|
self.styletable = ttk.Style()
|
|
|
|
self._vmr = vmr
|
|
|
|
|
|
|
|
# start pdirty watcher
|
|
|
|
self.upd_pdirty()
|
2022-04-13 06:50:49 +01:00
|
|
|
self.strip_levels = None
|
|
|
|
self.bus_levels = None
|
|
|
|
self._strip_levels = None
|
|
|
|
self._bus_levels = None
|
|
|
|
self.watch_levels()
|
2022-04-11 18:35:28 +01:00
|
|
|
|
|
|
|
self.resizable(False, False)
|
|
|
|
if _base_vals.themes_enabled:
|
|
|
|
self.apply_theme()
|
|
|
|
self._make_app(self.kind)
|
|
|
|
|
|
|
|
self.drag_id = ""
|
|
|
|
self.bind("<Configure>", self.dragging)
|
|
|
|
|
2022-04-12 00:10:00 +01:00
|
|
|
self.iconbitmap(Path(__file__).parent.resolve() / "img" / "cat.ico")
|
2022-04-11 18:35:28 +01:00
|
|
|
|
|
|
|
@property
|
|
|
|
def target(self):
|
|
|
|
"""returns the current interface"""
|
|
|
|
return self._vban if _base_vals.vban_connected else self._vmr
|
|
|
|
|
|
|
|
@property
|
|
|
|
def pdirty(self):
|
|
|
|
return self._pdirty
|
|
|
|
|
|
|
|
@pdirty.setter
|
|
|
|
def pdirty(self, val):
|
|
|
|
self._pdirty = val
|
|
|
|
|
2022-04-13 06:50:49 +01:00
|
|
|
@property
|
|
|
|
def ldirty(self):
|
|
|
|
return self._ldirty
|
|
|
|
|
|
|
|
@ldirty.setter
|
|
|
|
def ldirty(self, val):
|
|
|
|
self._ldirty = val
|
|
|
|
|
2022-04-11 18:35:28 +01:00
|
|
|
@property
|
|
|
|
def configuration(self):
|
|
|
|
return configuration["app"]
|
|
|
|
|
|
|
|
@configuration.setter
|
|
|
|
def configuration(self, val):
|
|
|
|
self.configuration["app"] = val
|
|
|
|
|
|
|
|
@property
|
|
|
|
def configframes(self):
|
|
|
|
"""returns a tuple of current config frame addresses"""
|
|
|
|
return tuple(
|
|
|
|
frame
|
|
|
|
for frame in self.winfo_children()
|
|
|
|
if isinstance(frame, ttk.Frame)
|
|
|
|
and "!stripconfig" in str(frame)
|
|
|
|
or "!busconfig" in str(frame)
|
|
|
|
)
|
|
|
|
|
|
|
|
def apply_theme(self):
|
|
|
|
_base_vals.using_theme = True
|
2022-04-12 00:32:47 +01:00
|
|
|
self.tk.call(
|
|
|
|
"source",
|
|
|
|
Path(__file__).parent.resolve() / "sun-valley-theme/sun-valley.tcl",
|
|
|
|
)
|
2022-04-11 18:35:28 +01:00
|
|
|
self.tk.call("set_theme", self.configuration["theme"]["mode"])
|
|
|
|
|
|
|
|
def _make_app(self, kind, vban=None):
|
|
|
|
self.title(
|
|
|
|
f'Voicemeeter{kind.name}.Compact [{"Local" if not vban else "Network"} Connection]'
|
|
|
|
)
|
|
|
|
self._vban = vban
|
|
|
|
self.kind = kind
|
|
|
|
|
|
|
|
self._make_top_level_frames()
|
|
|
|
|
|
|
|
def _make_top_level_frames(self):
|
|
|
|
# initialize bus frame variable
|
|
|
|
self.bus_frame = None
|
|
|
|
# channel_frame, left aligned
|
|
|
|
self.channel_frame = ChannelFrame.make_strips(self)
|
|
|
|
self.channel_frame.grid(row=0, column=0, sticky=(tk.W))
|
|
|
|
# separator
|
|
|
|
self.sep = ttk.Separator(self, orient="vertical")
|
|
|
|
self.sep.grid(row=0, column=1, sticky=(tk.S, tk.N))
|
|
|
|
self.columnconfigure(1, minsize=15)
|
|
|
|
|
|
|
|
# navigation frame
|
|
|
|
self.nav_frame = Navigation(self)
|
|
|
|
self.nav_frame.grid(row=0, column=3)
|
|
|
|
|
|
|
|
if self.configuration["extends"]["extended"]:
|
|
|
|
self.nav_frame.extend.set(True)
|
|
|
|
self.nav_frame.extend_frame()
|
|
|
|
|
|
|
|
self.banner = Banner(self)
|
|
|
|
self.banner.grid(row=4, column=0, columnspan=3)
|
|
|
|
|
|
|
|
def _destroy_top_level_frames(self):
|
|
|
|
[
|
|
|
|
frame.destroy()
|
|
|
|
for frame in self.winfo_children()
|
|
|
|
if isinstance(frame, ttk.Frame)
|
|
|
|
]
|
|
|
|
|
|
|
|
def upd_pdirty(self):
|
|
|
|
self.after(1, self.upd_pdirty_step)
|
|
|
|
|
|
|
|
def upd_pdirty_step(self):
|
|
|
|
self.pdirty = self.target.pdirty
|
2022-04-13 06:50:49 +01:00
|
|
|
self.after(_base_vals.pdelay, self.upd_pdirty_step)
|
|
|
|
|
|
|
|
def watch_levels(self):
|
|
|
|
self.after(1, self.watch_levels_step)
|
|
|
|
|
|
|
|
def watch_levels_step(self):
|
|
|
|
"""
|
|
|
|
Continuously fetch level arrays, only update
|
|
|
|
if ldirty
|
|
|
|
"""
|
|
|
|
self._strip_levels = self.target.strip_levels
|
|
|
|
self._bus_levels = self.target.bus_levels
|
|
|
|
|
|
|
|
self.ldirty = not (
|
|
|
|
self.strip_levels == self._strip_levels
|
|
|
|
and self.bus_levels == self._bus_levels
|
|
|
|
)
|
|
|
|
if self.ldirty:
|
|
|
|
self.strip_levels = self._strip_levels
|
|
|
|
self.bus_levels = self._bus_levels
|
|
|
|
self.after(_base_vals.ldelay, self.watch_levels_step)
|
2022-04-11 18:35:28 +01:00
|
|
|
|
|
|
|
def dragging(self, event, *args):
|
|
|
|
if event.widget is self:
|
|
|
|
if self.drag_id == "":
|
|
|
|
_base_vals.in_scale_button_1 = True
|
|
|
|
_base_vals.dragging = True
|
|
|
|
else:
|
|
|
|
self.after_cancel(self.drag_id)
|
|
|
|
self.drag_id = self.after(100, self.stop_drag)
|
|
|
|
|
|
|
|
def stop_drag(self):
|
|
|
|
_base_vals.dragging = False
|
|
|
|
_base_vals.in_scale_button_1 = False
|
|
|
|
self.drag_id = ""
|
|
|
|
|
|
|
|
|
|
|
|
_apps = {kind.id: App.make(kind) for kind in _kinds_all}
|
|
|
|
|
|
|
|
|
|
|
|
def connect(kind_id: str, vmr) -> App:
|
|
|
|
"""return App of the kind requested"""
|
|
|
|
try:
|
|
|
|
VMMIN_cls = _apps[kind_id]
|
|
|
|
return VMMIN_cls(vmr)
|
|
|
|
except KeyError:
|
|
|
|
raise VMCompactErrors(f"Invalid kind: {kind_id}")
|