initial commit
initial commit
1
.gitignore
vendored
@ -109,6 +109,7 @@ venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
venv_vmcompact/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
|
12
__main__.py
Normal file
@ -0,0 +1,12 @@
|
||||
import voicemeeter
|
||||
import vmcompact
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
kind_id = "banana"
|
||||
|
||||
voicemeeter.launch(kind_id, hide=False)
|
||||
|
||||
with voicemeeter.remote(kind_id) as vmr:
|
||||
app = vmcompact.connect(kind_id, vmr)
|
||||
app.mainloop()
|
15
configs/app.toml
Normal file
@ -0,0 +1,15 @@
|
||||
# load with themes enabled? set the default mode
|
||||
[theme]
|
||||
enabled=true
|
||||
mode="light"
|
||||
# load in extended mode? if so which orientation
|
||||
[extends]
|
||||
extended=true
|
||||
extends_horizontal=false
|
||||
# default dimensions for channel label frames
|
||||
[channel]
|
||||
width=80
|
||||
height=130
|
||||
# default submix bus
|
||||
[submixes]
|
||||
default=6
|
12
configs/vban.toml
Normal file
@ -0,0 +1,12 @@
|
||||
# example connections
|
||||
[connection-1]
|
||||
kind = 'banana'
|
||||
ip = '<ip address 1>'
|
||||
streamname = 'Command1'
|
||||
port = 6990
|
||||
|
||||
[connection-2]
|
||||
kind = 'potato'
|
||||
ip = '<ip address 2>'
|
||||
streamname = 'Command1'
|
||||
port = 6990
|
30
profiles/banana/example.toml
Normal file
@ -0,0 +1,30 @@
|
||||
extends = 'blank'
|
||||
[strip-0]
|
||||
label = "PhysStrip0"
|
||||
|
||||
[strip-1]
|
||||
label = "PhysStrip1"
|
||||
|
||||
[strip-2]
|
||||
label = "PhysStrip2"
|
||||
|
||||
[strip-3]
|
||||
label = "VirtStrip0"
|
||||
|
||||
[strip-4]
|
||||
label = "VirtStrip1"
|
||||
|
||||
[bus-0]
|
||||
label = "PhysBus0"
|
||||
|
||||
[bus-1]
|
||||
label = "PhysBus1"
|
||||
|
||||
[bus-2]
|
||||
label = "PhysBus2"
|
||||
|
||||
[bus-3]
|
||||
label = "VirtBus0"
|
||||
|
||||
[bus-4]
|
||||
label = "VirtBus1"
|
15
profiles/basic/example.toml
Normal file
@ -0,0 +1,15 @@
|
||||
extends = 'blank'
|
||||
[strip-0]
|
||||
label = "PhysStrip0"
|
||||
|
||||
[strip-1]
|
||||
label = "PhysStrip1"
|
||||
|
||||
[strip-2]
|
||||
label = "VirtStrip0"
|
||||
|
||||
[bus-0]
|
||||
label = "PhysBus0"
|
||||
|
||||
[bus-1]
|
||||
label = "PhysBus1"
|
48
profiles/potato/example.toml
Normal file
@ -0,0 +1,48 @@
|
||||
extends = 'blank'
|
||||
[strip-0]
|
||||
label = "PhysStrip0"
|
||||
|
||||
[strip-1]
|
||||
label = "PhysStrip1"
|
||||
|
||||
[strip-2]
|
||||
label = "PhysStrip2"
|
||||
|
||||
[strip-3]
|
||||
label = "PhysStrip3"
|
||||
|
||||
[strip-4]
|
||||
label = "PhysStrip4"
|
||||
|
||||
[strip-5]
|
||||
label = "VirtStrip0"
|
||||
|
||||
[strip-6]
|
||||
label = "VirtStrip1"
|
||||
|
||||
[strip-7]
|
||||
label = "VirtStrip2"
|
||||
|
||||
[bus-0]
|
||||
label = "PhysBus0"
|
||||
|
||||
[bus-1]
|
||||
label = "PhysBus1"
|
||||
|
||||
[bus-2]
|
||||
label = "PhysBus2"
|
||||
|
||||
[bus-3]
|
||||
label = "PhysBus3"
|
||||
|
||||
[bus-4]
|
||||
label = "PhysBus4"
|
||||
|
||||
[bus-5]
|
||||
label = "VirtBus0"
|
||||
|
||||
[bus-6]
|
||||
label = "VirtBus1"
|
||||
|
||||
[bus-7]
|
||||
label = "VirtBus2"
|
25
setup.py
Normal file
@ -0,0 +1,25 @@
|
||||
import setuptools
|
||||
|
||||
with open("README.md", "r", encoding="utf-8") as fh:
|
||||
long_description = fh.read()
|
||||
|
||||
setuptools.setup(
|
||||
name='vmcompact',
|
||||
version='0.0.1',
|
||||
author='Onyx and Iris',
|
||||
author_email='code@onyxandiris.online',
|
||||
description='Compact Tkinter Voicemeeter Remote App',
|
||||
long_description=long_description,
|
||||
long_description_content_type="text/markdown",
|
||||
url='https://github.com/onyx-and-iris/voicemeeter-compact',
|
||||
project_urls = {
|
||||
"Bug Tracker": "https://github.com/onyx-and-iris/voicemeeter-compact/issues"
|
||||
},
|
||||
license='MIT',
|
||||
packages=['vmcompact'],
|
||||
install_requires=[
|
||||
'toml',
|
||||
'voicemeeter@git+https://github.com/onyx-and-iris/voicemeeter-api-python#egg=voicemeeter',
|
||||
'vbancmd@git+https://github.com/onyx-and-iris/vban-cmd-python#egg=vbancmd',
|
||||
],
|
||||
)
|
3
vmcompact/__init__.py
Normal file
@ -0,0 +1,3 @@
|
||||
from .app import connect
|
||||
|
||||
__ALL__ = ["connect"]
|
177
vmcompact/app.py
Normal file
@ -0,0 +1,177 @@
|
||||
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": {
|
||||
"mode": "light",
|
||||
},
|
||||
"extends": {
|
||||
"extended": False,
|
||||
"extends_horizontal": True,
|
||||
},
|
||||
}
|
||||
self.configuration = defaults | self.configuration
|
||||
_base_vals.themes_enabled = self.configuration["theme"]["enabled"]
|
||||
_base_vals.extends_horizontal = self.configuration["extends"][
|
||||
"extends_horizontal"
|
||||
]
|
||||
_base_vals.submixes = self.configuration["submixes"]["default"]
|
||||
|
||||
# create menus
|
||||
self.menus = Menus(self, vmr)
|
||||
self.styletable = ttk.Style()
|
||||
self._vmr = vmr
|
||||
|
||||
# start pdirty watcher
|
||||
self.upd_pdirty()
|
||||
|
||||
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)
|
||||
|
||||
self.iconbitmap(Path.cwd() / "vmcompact" / "img" / "cat.ico")
|
||||
|
||||
@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
|
||||
|
||||
@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
|
||||
self.tk.call("source", "./vmcompact/sun-valley-theme/sun-valley.tcl")
|
||||
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
|
||||
self.after(1, self.upd_pdirty_step)
|
||||
|
||||
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}")
|
37
vmcompact/banner.py
Normal file
@ -0,0 +1,37 @@
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
|
||||
from .data import _base_vals
|
||||
|
||||
|
||||
class Banner(ttk.Frame):
|
||||
def __init__(self, parent):
|
||||
super().__init__()
|
||||
self._parent = parent
|
||||
self.web = "onyxandiris.online"
|
||||
self.submix = tk.StringVar()
|
||||
if self._parent.kind.name == "Potato":
|
||||
self.submix.set(self.target.bus[_base_vals.submixes].label)
|
||||
|
||||
if self._parent.kind.name == "Potato":
|
||||
self.label = ttk.Label(
|
||||
self,
|
||||
text=f"SUBMIX: {self.submix.get().upper()}",
|
||||
)
|
||||
self.label.grid(column=0, row=0, sticky=(tk.N, tk.S, tk.W, tk.E))
|
||||
|
||||
self.upd_submix()
|
||||
|
||||
@property
|
||||
def target(self):
|
||||
"""use the correct interface"""
|
||||
return self._parent.target
|
||||
|
||||
def upd_submix(self):
|
||||
self.after(1, self.upd_submix_step)
|
||||
|
||||
def upd_submix_step(self):
|
||||
if not _base_vals.dragging:
|
||||
self.submix.set(self.target.bus[_base_vals.submixes].label)
|
||||
self.label["text"] = f"SUBMIX: {self.submix.get().upper()}"
|
||||
self.after(100, self.upd_submix_step)
|
376
vmcompact/channels.py
Normal file
@ -0,0 +1,376 @@
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from functools import partial
|
||||
|
||||
from .data import _base_vals
|
||||
from .config import StripConfig, BusConfig
|
||||
|
||||
|
||||
class Channel(ttk.LabelFrame):
|
||||
"""Base class for a single channel"""
|
||||
|
||||
def __init__(self, parent, index, id):
|
||||
super().__init__(parent)
|
||||
self._parent = parent
|
||||
self.index = index
|
||||
self.id = id
|
||||
self.s = self._parent._parent.styletable
|
||||
self.config_frame = None
|
||||
|
||||
self.gain = tk.DoubleVar()
|
||||
self.level = tk.DoubleVar()
|
||||
self.mute = tk.BooleanVar()
|
||||
self.conf = tk.BooleanVar()
|
||||
|
||||
self.sync()
|
||||
self._make_widgets()
|
||||
|
||||
self.col_row_configure()
|
||||
self.watch_pdirty()
|
||||
self.watch_levels()
|
||||
|
||||
@property
|
||||
def identifier(self):
|
||||
return self.id
|
||||
|
||||
@property
|
||||
def target(self):
|
||||
"""use the correct interface"""
|
||||
return self._parent.target
|
||||
|
||||
def getter(self, param):
|
||||
if param in dir(self.target):
|
||||
return getattr(self.target, param)
|
||||
|
||||
def setter(self, param, value):
|
||||
if param in dir(self.target):
|
||||
setattr(self.target, param, value)
|
||||
|
||||
def toggle_mute(self, *args):
|
||||
self.target.mute = self.mute.get()
|
||||
if not _base_vals.using_theme:
|
||||
self.s.configure(
|
||||
f"{self.identifier}Mute{self.index}.TButton",
|
||||
background=f'{"red" if self.mute.get() else "white"}',
|
||||
)
|
||||
|
||||
def reset_gain(self, *args):
|
||||
self.setter("gain", 0)
|
||||
self.gain.set(0)
|
||||
self._parent._parent.nav_frame.info_text.set(0)
|
||||
|
||||
def scale_enter(self, *args):
|
||||
self._parent._parent.nav_frame.info_text.set(round(self.gain.get(), 1))
|
||||
|
||||
def scale_leave(self, *args):
|
||||
self._parent._parent.nav_frame.info_text.set("")
|
||||
|
||||
def scale_press(self, *args):
|
||||
_base_vals.in_scale_button_1 = True
|
||||
|
||||
def scale_release(self, *args):
|
||||
_base_vals.in_scale_button_1 = False
|
||||
|
||||
def scale_callback(self, *args):
|
||||
"""callback function for scale widget"""
|
||||
self.setter("gain", self.gain.get())
|
||||
self._parent._parent.nav_frame.info_text.set(round(self.gain.get(), 1))
|
||||
|
||||
def _make_widgets(self):
|
||||
"""Creates a progressbar, scale, mute button and config button for a single channel"""
|
||||
# Progress bar
|
||||
self.pb = ttk.Progressbar(
|
||||
self,
|
||||
maximum=100,
|
||||
orient="vertical",
|
||||
mode="determinate",
|
||||
variable=self.level,
|
||||
)
|
||||
self.pb.grid(column=0, row=0)
|
||||
|
||||
# Scale
|
||||
self.scale = ttk.Scale(
|
||||
self,
|
||||
from_=12.0,
|
||||
to=-60.0,
|
||||
orient="vertical",
|
||||
variable=self.gain,
|
||||
command=self.scale_callback,
|
||||
length=self._parent.height,
|
||||
)
|
||||
self.scale.grid(column=1, row=0)
|
||||
self.scale.bind("<Double-Button-1>", self.reset_gain)
|
||||
self.scale.bind("<Button-1>", self.scale_press)
|
||||
self.scale.bind("<Enter>", self.scale_enter)
|
||||
self.scale.bind("<ButtonRelease-1>", self.scale_release)
|
||||
self.scale.bind("<Leave>", self.scale_leave)
|
||||
|
||||
# Mute button
|
||||
self.button_mute = ttk.Checkbutton(
|
||||
self,
|
||||
text="MUTE",
|
||||
command=partial(self.toggle_mute, "mute"),
|
||||
style=f"{'Toggle.TButton' if _base_vals.using_theme else f'{self.identifier}Mute{self.index}.TButton'}",
|
||||
variable=self.mute,
|
||||
)
|
||||
self.button_mute.grid(column=0, row=1, columnspan=2)
|
||||
|
||||
self.button_conf = ttk.Checkbutton(
|
||||
self,
|
||||
text="CONFIG",
|
||||
command=self.open_config,
|
||||
style=f"{'Toggle.TButton' if _base_vals.using_theme else f'{self.identifier}Conf{self.index}.TButton'}",
|
||||
variable=self.conf,
|
||||
)
|
||||
self.button_conf.grid(column=0, row=2, columnspan=2)
|
||||
|
||||
def watch_pdirty(self):
|
||||
self.after(1, self.watch_pdirty_step)
|
||||
|
||||
def watch_pdirty_step(self):
|
||||
"""keeps params synced but ensures sliders are responsive"""
|
||||
if self._parent._parent.pdirty and not _base_vals.in_scale_button_1:
|
||||
self.sync()
|
||||
self.after(1, self.watch_pdirty_step)
|
||||
|
||||
def sync(self):
|
||||
"""sync params with voicemeeter"""
|
||||
retval = self.getter("label")
|
||||
if len(retval) > 10:
|
||||
retval = f"{retval[:8]}.."
|
||||
self.configure(text=retval)
|
||||
self.gain.set(self.getter("gain"))
|
||||
self.mute.set(self.getter("mute"))
|
||||
if not _base_vals.using_theme:
|
||||
self.s.configure(
|
||||
f"{self.identifier}Mute{self.index}.TButton",
|
||||
background=f'{"red" if self.mute.get() else "white"}',
|
||||
)
|
||||
self.s.configure(
|
||||
f"{self.identifier}Conf{self.index}.TButton", background="white"
|
||||
)
|
||||
|
||||
def col_row_configure(self):
|
||||
self.grid(sticky=(tk.N, tk.S))
|
||||
[
|
||||
child.grid_configure(padx=1, pady=1, sticky=(tk.W, tk.E))
|
||||
for child in self.winfo_children()
|
||||
if isinstance(child, ttk.Checkbutton)
|
||||
]
|
||||
[
|
||||
child.grid_configure(padx=1, pady=1, sticky=(tk.N, tk.S))
|
||||
for child in self.winfo_children()
|
||||
if isinstance(child, ttk.Progressbar) or isinstance(child, ttk.Scale)
|
||||
]
|
||||
|
||||
|
||||
class Strip(Channel):
|
||||
"""Concrete class representing a single"""
|
||||
|
||||
def __init__(self, parent, index, id):
|
||||
super().__init__(parent, index, id)
|
||||
|
||||
@property
|
||||
def target(self):
|
||||
"""use the correct interface"""
|
||||
_target = super(Strip, self).target
|
||||
return getattr(_target, self.identifier)[self.index]
|
||||
|
||||
def open_config(self):
|
||||
if self.conf.get():
|
||||
self.config_frame = StripConfig(
|
||||
self._parent._parent,
|
||||
self.index,
|
||||
self.identifier,
|
||||
)
|
||||
self.config_frame.grid(column=0, row=1, columnspan=4)
|
||||
self._parent._parent.channel_frame.reset_config_buttons(self)
|
||||
if self._parent._parent.bus_frame is not None:
|
||||
self._parent._parent.bus_frame.reset_config_buttons(self)
|
||||
else:
|
||||
self.config_frame.destroy()
|
||||
|
||||
if not _base_vals.using_theme:
|
||||
self.s.configure(
|
||||
f"{self.identifier}Conf{self.index}.TButton",
|
||||
background=f'{"yellow" if self.conf.get() else "white"}',
|
||||
)
|
||||
|
||||
def watch_levels(self):
|
||||
self.after(1, self.watch_levels_step)
|
||||
|
||||
def watch_levels_step(self):
|
||||
if not _base_vals.dragging:
|
||||
vals = self.target.levels.prefader
|
||||
val = vals[0] if vals[0] > vals[1] else vals[0]
|
||||
self.level.set(val)
|
||||
self.level.set(
|
||||
(0 if self.mute.get() else 100 + (val - 18) + self.gain.get())
|
||||
)
|
||||
self.after(
|
||||
25 if not _base_vals.in_scale_button_1 else 100, self.watch_levels_step
|
||||
)
|
||||
|
||||
|
||||
class Bus(Channel):
|
||||
"""Concrete bus class representing a single bus"""
|
||||
|
||||
def __init__(self, parent, index, id):
|
||||
super().__init__(parent, index, id)
|
||||
|
||||
@property
|
||||
def target(self):
|
||||
"""use the correct interface"""
|
||||
_target = super(Bus, self).target
|
||||
return getattr(_target, self.identifier)[self.index]
|
||||
|
||||
def open_config(self):
|
||||
if self.conf.get():
|
||||
self.config_frame = BusConfig(
|
||||
self._parent._parent,
|
||||
self.index,
|
||||
self.identifier,
|
||||
)
|
||||
if _base_vals.extends_horizontal:
|
||||
self.config_frame.grid(column=0, row=1, columnspan=3)
|
||||
else:
|
||||
self.config_frame.grid(column=0, row=3, columnspan=3)
|
||||
self._parent._parent.channel_frame.reset_config_buttons(self)
|
||||
self._parent._parent.bus_frame.reset_config_buttons(self)
|
||||
else:
|
||||
self.config_frame.destroy()
|
||||
|
||||
if not _base_vals.using_theme:
|
||||
self.s.configure(
|
||||
f"{self.identifier}Conf{self.index}.TButton",
|
||||
background=f'{"yellow" if self.conf.get() else "white"}',
|
||||
)
|
||||
|
||||
def watch_levels(self):
|
||||
self.after(1, self.watch_levels_step)
|
||||
|
||||
def watch_levels_step(self):
|
||||
if not _base_vals.dragging:
|
||||
vals = self.target.levels.all
|
||||
val = vals[0] if vals[0] > vals[1] else vals[0]
|
||||
self.level.set(val)
|
||||
self.level.set(
|
||||
(0 if self.mute.get() else 100 + (val - 18) + self.gain.get())
|
||||
)
|
||||
self.after(
|
||||
25 if not _base_vals.in_scale_button_1 else 100, self.watch_levels_step
|
||||
)
|
||||
|
||||
|
||||
class ChannelFrame(ttk.Frame):
|
||||
@classmethod
|
||||
def make_strips(cls, parent):
|
||||
return cls(parent, is_strip=True)
|
||||
|
||||
@classmethod
|
||||
def make_buses(cls, parent):
|
||||
return cls(parent, is_strip=False)
|
||||
|
||||
def __init__(self, parent, is_strip: bool = True):
|
||||
super().__init__(parent)
|
||||
self._parent = parent
|
||||
self._is_strip = is_strip
|
||||
self.phys_in, self.virt_in = parent.kind.ins
|
||||
self.phys_out, self.virt_out = parent.kind.outs
|
||||
|
||||
defaults = {
|
||||
"width": 80,
|
||||
"height": 150,
|
||||
}
|
||||
self.configuration = defaults | self.configuration
|
||||
self.width = self.configuration["width"]
|
||||
self.height = self.configuration["height"]
|
||||
|
||||
self.watch_pdirty()
|
||||
|
||||
# create labelframes
|
||||
if is_strip:
|
||||
self.strips = [
|
||||
Strip(self, i, self.identifier)
|
||||
for i in range(self.phys_in + self.virt_in)
|
||||
]
|
||||
else:
|
||||
self.buses = [
|
||||
Bus(self, i, self.identifier)
|
||||
for i in range(self.phys_out + self.virt_out)
|
||||
]
|
||||
|
||||
# position label frames. destroy any without label text
|
||||
self.labelframes = self.strips if is_strip else self.buses
|
||||
|
||||
self.col_row_configure()
|
||||
|
||||
for i, labelframe in enumerate(self.labelframes):
|
||||
labelframe.grid(row=0, column=i)
|
||||
if not labelframe.cget("text"):
|
||||
self.columnconfigure(i, minsize=0)
|
||||
labelframe.grid_remove()
|
||||
|
||||
@property
|
||||
def target(self):
|
||||
"""returns the current interface"""
|
||||
return self._parent.target
|
||||
|
||||
@property
|
||||
def configuration(self):
|
||||
return self._parent.configuration["channel"]
|
||||
|
||||
@configuration.setter
|
||||
def configuration(self, val):
|
||||
self._parent.configuration["channel"] = val
|
||||
|
||||
@property
|
||||
def identifier(self):
|
||||
return "strip" if self._is_strip else "bus"
|
||||
|
||||
def reset_config_buttons(self, current):
|
||||
if not _base_vals.using_theme:
|
||||
[
|
||||
labelframe.s.configure(
|
||||
f"{labelframe.identifier}Conf{labelframe.index}.TButton",
|
||||
background="white",
|
||||
)
|
||||
for labelframe in self.labelframes
|
||||
if labelframe is not None
|
||||
]
|
||||
[
|
||||
labelframe.conf.set(False)
|
||||
for labelframe in self.labelframes
|
||||
if labelframe is not None and labelframe != current
|
||||
]
|
||||
[
|
||||
labelframe.config_frame.destroy()
|
||||
for labelframe in self.labelframes
|
||||
if labelframe is not None
|
||||
and labelframe.config_frame
|
||||
and labelframe != current
|
||||
]
|
||||
|
||||
def col_row_configure(self):
|
||||
[
|
||||
self.columnconfigure(i, minsize=self.width)
|
||||
for i, _ in enumerate(self.labelframes)
|
||||
]
|
||||
[self.rowconfigure(0, minsize=130) for i, _ in enumerate(self.labelframes)]
|
||||
|
||||
def watch_pdirty(self):
|
||||
self.after(1, self.watch_pdirty_step)
|
||||
|
||||
def watch_pdirty_step(self):
|
||||
if self._parent.pdirty:
|
||||
self.watch_labels()
|
||||
self.after(1, self.watch_pdirty_step)
|
||||
|
||||
def watch_labels(self):
|
||||
for i, labelframe in enumerate(self.labelframes):
|
||||
if not labelframe.getter("label"):
|
||||
self.columnconfigure(i, minsize=0)
|
||||
labelframe.grid_remove()
|
||||
else:
|
||||
self.columnconfigure(i, minsize=self.width)
|
||||
labelframe.grid()
|
458
vmcompact/config.py
Normal file
@ -0,0 +1,458 @@
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from functools import partial
|
||||
|
||||
from .data import _base_vals
|
||||
|
||||
|
||||
class Config(ttk.Frame):
|
||||
def __init__(self, parent, index, _id):
|
||||
super().__init__(parent)
|
||||
self._parent = parent
|
||||
self.index = index
|
||||
self.id = _id
|
||||
self.s = parent.styletable
|
||||
|
||||
self.phys_in, self.virt_in = parent.kind.ins
|
||||
self.phys_out, self.virt_out = parent.kind.outs
|
||||
|
||||
self.watch_pdirty()
|
||||
|
||||
@property
|
||||
def identifier(self):
|
||||
return self.id
|
||||
|
||||
@property
|
||||
def target(self):
|
||||
"""returns the current interface"""
|
||||
return self._parent.target
|
||||
|
||||
def getter(self, param):
|
||||
if param in dir(self.target):
|
||||
return getattr(self.target, param)
|
||||
|
||||
def setter(self, param, value):
|
||||
if param in dir(self.target):
|
||||
setattr(self.target, param, value)
|
||||
|
||||
def scale_enter(self, *args):
|
||||
_base_vals.in_scale_button_1 = True
|
||||
|
||||
def scale_leave(self, *args):
|
||||
_base_vals.in_scale_button_1 = False
|
||||
self._parent.nav_frame.info_text.set("")
|
||||
|
||||
def scale_callback(self, param, *args):
|
||||
"""callback function for scale widget"""
|
||||
val = self.slider_vars[self.slider_params.index(param)].get()
|
||||
self.setter(param, val)
|
||||
self._parent.nav_frame.info_text.set(round(val, 1))
|
||||
|
||||
def reset_scale(self, param, val, *args):
|
||||
self.setter(param, val)
|
||||
self.slider_vars[self.slider_params.index(param)].set(val)
|
||||
|
||||
def col_row_configure(self):
|
||||
[
|
||||
child.grid_configure(padx=1, pady=1, sticky=(tk.W, tk.E))
|
||||
for child in self.winfo_children()
|
||||
if isinstance(child, ttk.Checkbutton)
|
||||
]
|
||||
self.grid(sticky=(tk.W))
|
||||
|
||||
def watch_pdirty(self):
|
||||
self.after(1, self.watch_pdirty_step)
|
||||
|
||||
def watch_pdirty_step(self):
|
||||
"""keeps params synced but ensures sliders are responsive"""
|
||||
if self._parent.pdirty and not _base_vals.in_scale_button_1:
|
||||
self.sync()
|
||||
self.after(1, self.watch_pdirty_step)
|
||||
|
||||
|
||||
class StripConfig(Config):
|
||||
def __init__(self, parent, index, _id):
|
||||
super().__init__(parent, index, _id)
|
||||
|
||||
# create parameter variables
|
||||
if self._parent.kind.name == "Basic":
|
||||
self.slider_params = ("audibility",)
|
||||
self.slider_vars = (tk.DoubleVar(),)
|
||||
else:
|
||||
self.slider_params = ("comp", "gate", "limit")
|
||||
self.slider_vars = [
|
||||
tk.DoubleVar() for i, _ in enumerate(self.slider_params)
|
||||
]
|
||||
|
||||
self.phys_out_params = [f"A{i+1}" for i in range(self.phys_out)]
|
||||
self.phys_out_params_vars = [
|
||||
tk.BooleanVar() for i, _ in enumerate(self.phys_out_params)
|
||||
]
|
||||
|
||||
self.virt_out_params = [f"B{i+1}" for i in range(self.virt_out)]
|
||||
self.virt_out_params_vars = [
|
||||
tk.BooleanVar() for i, _ in enumerate(self.virt_out_params)
|
||||
]
|
||||
|
||||
self.params = ("mono", "solo")
|
||||
self.param_vars = list(tk.BooleanVar() for i, _ in enumerate(self.params))
|
||||
|
||||
self.make_row0()
|
||||
self.make_row1()
|
||||
self.make_row2()
|
||||
|
||||
# sync all parameters
|
||||
self.sync()
|
||||
self.sync_sliders()
|
||||
|
||||
self.col_row_configure()
|
||||
|
||||
@property
|
||||
def target(self):
|
||||
"""use the correct interface"""
|
||||
_target = super(StripConfig, self).target
|
||||
return getattr(_target, self.identifier)[self.index]
|
||||
|
||||
def make_row0(self):
|
||||
# Create sliders
|
||||
if self.index < self.phys_in:
|
||||
if self._parent.kind.name == "Basic":
|
||||
# audibility
|
||||
aud_label = ttk.Label(self, text="Audibility")
|
||||
aud_scale = ttk.Scale(
|
||||
self,
|
||||
from_=0.0,
|
||||
to=10.0,
|
||||
orient="horizontal",
|
||||
length=_base_vals.level_width,
|
||||
variable=self.slider_vars[self.slider_params.index("audibility")],
|
||||
command=partial(self.scale_callback, "audibility"),
|
||||
)
|
||||
aud_scale.bind(
|
||||
"<Double-Button-1>", partial(self.reset_scale, "audibility", 0)
|
||||
)
|
||||
aud_scale.bind("<Button-1>", self.scale_enter)
|
||||
aud_scale.bind("<ButtonRelease-1>", self.scale_leave)
|
||||
|
||||
aud_label.grid(column=0, row=0)
|
||||
aud_scale.grid(column=1, row=0)
|
||||
else:
|
||||
# comp
|
||||
comp_label = ttk.Label(self, text="Comp")
|
||||
comp_scale = ttk.Scale(
|
||||
self,
|
||||
from_=0.0,
|
||||
to=10.0,
|
||||
orient="horizontal",
|
||||
length=_base_vals.level_width,
|
||||
variable=self.slider_vars[self.slider_params.index("comp")],
|
||||
command=partial(self.scale_callback, "comp"),
|
||||
)
|
||||
comp_scale.bind(
|
||||
"<Double-Button-1>", partial(self.reset_scale, "comp", 0)
|
||||
)
|
||||
comp_scale.bind("<Button-1>", self.scale_enter)
|
||||
comp_scale.bind("<ButtonRelease-1>", self.scale_leave)
|
||||
|
||||
# gate
|
||||
gate_label = ttk.Label(self, text="Gate")
|
||||
gate_scale = ttk.Scale(
|
||||
self,
|
||||
from_=0.0,
|
||||
to=10.0,
|
||||
orient="horizontal",
|
||||
length=_base_vals.level_width,
|
||||
variable=self.slider_vars[self.slider_params.index("gate")],
|
||||
command=partial(self.scale_callback, "gate"),
|
||||
)
|
||||
gate_scale.bind(
|
||||
"<Double-Button-1>", partial(self.reset_scale, "gate", 0)
|
||||
)
|
||||
gate_scale.bind("<Button-1>", self.scale_enter)
|
||||
gate_scale.bind("<ButtonRelease-1>", self.scale_leave)
|
||||
|
||||
# limit
|
||||
limit_label = ttk.Label(self, text="Limit")
|
||||
limit_scale = ttk.Scale(
|
||||
self,
|
||||
from_=-40,
|
||||
to=12,
|
||||
orient="horizontal",
|
||||
length=_base_vals.level_width,
|
||||
variable=self.slider_vars[self.slider_params.index("limit")],
|
||||
command=partial(self.scale_callback, "limit"),
|
||||
)
|
||||
limit_scale.bind(
|
||||
"<Double-Button-1>", partial(self.reset_scale, "limit", 12)
|
||||
)
|
||||
limit_scale.bind("<Button-1>", self.scale_enter)
|
||||
limit_scale.bind("<ButtonRelease-1>", self.scale_leave)
|
||||
|
||||
# Position sliders
|
||||
comp_label.grid(column=0, row=0)
|
||||
comp_scale.grid(column=1, row=0)
|
||||
gate_label.grid(column=2, row=0)
|
||||
gate_scale.grid(column=3, row=0)
|
||||
limit_label.grid(column=4, row=0)
|
||||
limit_scale.grid(column=5, row=0)
|
||||
|
||||
def make_row1(self):
|
||||
# create buttons
|
||||
self.a_buttons = [
|
||||
ttk.Checkbutton(
|
||||
self,
|
||||
text=param,
|
||||
command=partial(self.toggle_a, param),
|
||||
style=f"{'Toggle.TButton' if _base_vals.using_theme else f'{param}.TButton'}",
|
||||
variable=self.phys_out_params_vars[self.phys_out_params.index(param)],
|
||||
)
|
||||
for param in self.phys_out_params
|
||||
]
|
||||
self.b_buttons = [
|
||||
ttk.Checkbutton(
|
||||
self,
|
||||
text=param,
|
||||
command=partial(self.toggle_b, param),
|
||||
style=f"{'Toggle.TButton' if _base_vals.using_theme else f'{param}.TButton'}",
|
||||
variable=self.virt_out_params_vars[self.virt_out_params.index(param)],
|
||||
)
|
||||
for param in self.virt_out_params
|
||||
]
|
||||
|
||||
# set button positions
|
||||
[
|
||||
button.grid(
|
||||
column=self.a_buttons.index(button),
|
||||
row=1,
|
||||
)
|
||||
for button in self.a_buttons
|
||||
]
|
||||
[
|
||||
button.grid(
|
||||
column=len(self.a_buttons) + self.b_buttons.index(button),
|
||||
row=1,
|
||||
)
|
||||
for button in self.b_buttons
|
||||
]
|
||||
|
||||
def toggle_a(self, param):
|
||||
val = self.phys_out_params_vars[self.phys_out_params.index(param)].get()
|
||||
self.setter(param, val)
|
||||
if not _base_vals.using_theme:
|
||||
self.s.configure(
|
||||
f"{param}.TButton", background=f'{"green" if val else "white"}'
|
||||
)
|
||||
|
||||
def toggle_b(self, param):
|
||||
val = self.virt_out_params_vars[self.virt_out_params.index(param)].get()
|
||||
self.setter(param, val)
|
||||
if not _base_vals.using_theme:
|
||||
self.s.configure(
|
||||
f"{param}.TButton", background=f'{"green" if val else "white"}'
|
||||
)
|
||||
|
||||
def make_row2(self):
|
||||
if self._parent.kind.name in ("Banana", "Potato"):
|
||||
if self.index == self.phys_in:
|
||||
self.params = list(map(lambda x: x.replace("mono", "mc"), self.params))
|
||||
if self._parent.kind.name == "Banana":
|
||||
pass
|
||||
# karaoke modes not in RT Packet yet. May implement in future
|
||||
"""
|
||||
if self.index == self.phys_in + 1:
|
||||
self.params = list(
|
||||
map(lambda x: x.replace("mono", "k"), self.params)
|
||||
)
|
||||
self.param_vars[self.params.index("k")] = tk.IntVar
|
||||
"""
|
||||
else:
|
||||
if self.index == self.phys_in + self.virt_in - 1:
|
||||
self.params = list(
|
||||
map(lambda x: x.replace("mono", "mc"), self.params)
|
||||
)
|
||||
|
||||
param_buttons = [
|
||||
ttk.Checkbutton(
|
||||
self,
|
||||
text=param,
|
||||
command=partial(self.toggle_p, param),
|
||||
style=f"{'Toggle.TButton' if _base_vals.using_theme else f'{param}.TButton'}",
|
||||
variable=self.param_vars[self.params.index(param)],
|
||||
)
|
||||
for param in self.params
|
||||
]
|
||||
[
|
||||
button.grid(
|
||||
column=param_buttons.index(button),
|
||||
row=2,
|
||||
)
|
||||
for button in param_buttons
|
||||
]
|
||||
|
||||
def toggle_p(self, param):
|
||||
val = self.param_vars[self.params.index(param)].get()
|
||||
self.setter(param, val)
|
||||
if not _base_vals.using_theme:
|
||||
self.s.configure(
|
||||
f"{param}.TButton", background=f'{"green" if val else "white"}'
|
||||
)
|
||||
|
||||
def sync(self):
|
||||
[
|
||||
self.phys_out_params_vars[self.phys_out_params.index(param)].set(
|
||||
self.getter(param)
|
||||
)
|
||||
for param in self.phys_out_params
|
||||
]
|
||||
[
|
||||
self.virt_out_params_vars[self.virt_out_params.index(param)].set(
|
||||
self.getter(param)
|
||||
)
|
||||
for param in self.virt_out_params
|
||||
]
|
||||
[
|
||||
self.param_vars[self.params.index(param)].set(self.getter(param))
|
||||
for param in self.params
|
||||
]
|
||||
if not _base_vals.using_theme:
|
||||
[
|
||||
self.s.configure(
|
||||
f"{param}.TButton",
|
||||
background=f'{"green" if self.phys_out_params_vars[self.phys_out_params.index(param)].get() else "white"}',
|
||||
)
|
||||
for param in self.phys_out_params
|
||||
]
|
||||
[
|
||||
self.s.configure(
|
||||
f"{param}.TButton",
|
||||
background=f'{"green" if self.virt_out_params_vars[self.virt_out_params.index(param)].get() else "white"}',
|
||||
)
|
||||
for param in self.virt_out_params
|
||||
]
|
||||
[
|
||||
self.s.configure(
|
||||
f"{param}.TButton",
|
||||
background=f'{"green" if self.param_vars[self.params.index(param)].get() else "white"}',
|
||||
)
|
||||
for param in self.params
|
||||
]
|
||||
|
||||
def sync_sliders(self):
|
||||
[
|
||||
self.slider_vars[self.slider_params.index(param)].set(self.getter(param))
|
||||
for param in self.slider_params
|
||||
]
|
||||
|
||||
def col_row_configure(self):
|
||||
super(StripConfig, self).col_row_configure()
|
||||
[
|
||||
self.columnconfigure(i, minsize=80)
|
||||
for i in range(self.phys_out + self.virt_out)
|
||||
]
|
||||
|
||||
|
||||
class BusConfig(Config):
|
||||
def __init__(self, parent, index, _id):
|
||||
super().__init__(parent, index, _id)
|
||||
# fmt: off
|
||||
# create parameter variables
|
||||
self.bus_modes = (
|
||||
"normal", "Amix", "Bmix", "Repeat", "Composite", "TVMix", "UpMix21",
|
||||
"UpMix41", "UpMix61", "CenterOnly", "LFEOnly", "RearOnly",
|
||||
)
|
||||
# fmt: on
|
||||
self.params = ("mono", "eq", "eq_ab")
|
||||
self.param_vars = [tk.BooleanVar() for i, _ in enumerate(self.params)]
|
||||
|
||||
self.make_row0()
|
||||
self.make_row1()
|
||||
|
||||
# sync all parameters
|
||||
self.sync()
|
||||
|
||||
self.col_row_configure()
|
||||
|
||||
@property
|
||||
def target(self):
|
||||
"""returns the current interface"""
|
||||
_target = super(BusConfig, self).target
|
||||
return getattr(_target, self.identifier)[self.index]
|
||||
|
||||
def make_row0(self):
|
||||
self._cur_bus_mode = tk.StringVar()
|
||||
self._cur_bus_mode.set(f"Bus Mode: {self.bus_modes[self.get_current_index()]}")
|
||||
self.busmode_button = ttk.Button(self, textvariable=self._cur_bus_mode)
|
||||
self.busmode_button.grid(column=0, row=0, columnspan=2, sticky=(tk.W))
|
||||
self.busmode_button.bind("<Button-1>", self.rotate_bus_modes_right)
|
||||
self.busmode_button.bind("<Button-3>", self.rotate_bus_modes_left)
|
||||
|
||||
def get_current_index(self):
|
||||
for mode in self.bus_modes:
|
||||
if getattr(self.target.mode, mode.lower()):
|
||||
return self.bus_modes.index(mode)
|
||||
|
||||
def rotate_bus_modes_right(self, *args):
|
||||
current_index = self.get_current_index()
|
||||
if current_index + 1 < len(self.bus_modes):
|
||||
next = self.bus_modes[current_index + 1]
|
||||
else:
|
||||
next = self.bus_modes[0]
|
||||
setattr(self.target.mode, next.lower(), True)
|
||||
self._cur_bus_mode.set(f"Bus Mode: {next}")
|
||||
|
||||
def rotate_bus_modes_left(self, *args):
|
||||
current_index = self.get_current_index()
|
||||
if current_index == 0:
|
||||
next = self.bus_modes[-1]
|
||||
else:
|
||||
next = self.bus_modes[current_index - 1]
|
||||
setattr(self.target.mode, next.lower(), True)
|
||||
self._cur_bus_mode.set(f"Bus Mode: {next}")
|
||||
|
||||
def make_row1(self):
|
||||
param_buttons = [
|
||||
ttk.Checkbutton(
|
||||
self,
|
||||
text=param,
|
||||
command=partial(self.toggle_p, param),
|
||||
style=f"{'Toggle.TButton' if _base_vals.using_theme else f'{param}.TButton'}",
|
||||
variable=self.param_vars[self.params.index(param)],
|
||||
)
|
||||
for param in self.params
|
||||
]
|
||||
[
|
||||
button.grid(
|
||||
column=param_buttons.index(button),
|
||||
row=1,
|
||||
)
|
||||
for button in param_buttons
|
||||
]
|
||||
|
||||
def toggle_p(self, param):
|
||||
val = self.param_vars[self.params.index(param)].get()
|
||||
self.setter(param, val)
|
||||
if not _base_vals.using_theme:
|
||||
self.s.configure(
|
||||
f"{param}.TButton", background=f'{"green" if val else "white"}'
|
||||
)
|
||||
|
||||
def col_row_configure(self):
|
||||
super(BusConfig, self).col_row_configure()
|
||||
[
|
||||
self.columnconfigure(i, minsize=80)
|
||||
for i in range(self.phys_out + self.virt_out)
|
||||
]
|
||||
|
||||
def sync(self):
|
||||
[
|
||||
self.param_vars[self.params.index(param)].set(self.getter(param))
|
||||
for param in self.params
|
||||
]
|
||||
if not _base_vals.using_theme:
|
||||
[
|
||||
self.s.configure(
|
||||
f"{param}.TButton",
|
||||
background=f'{"green" if self.param_vars[self.params.index(param)].get() else "white"}',
|
||||
)
|
||||
for param in self.params
|
||||
]
|
20
vmcompact/configurations.py
Normal file
@ -0,0 +1,20 @@
|
||||
import toml
|
||||
from pathlib import Path
|
||||
|
||||
configuration = {}
|
||||
|
||||
config_path = [Path.cwd() / "configs"]
|
||||
for path in config_path:
|
||||
if path.is_dir():
|
||||
filenames = list(path.glob("*.toml"))
|
||||
configs = {}
|
||||
for filename in filenames:
|
||||
name = filename.with_suffix("").stem
|
||||
try:
|
||||
configs[name] = toml.load(filename)
|
||||
except toml.TomlDecodeError:
|
||||
print(f"Invalid TOML profile: configs/{filename.stem}")
|
||||
|
||||
for name, cfg in configs.items():
|
||||
print(f"Loaded profile configs/{name}")
|
||||
configuration[name] = cfg
|
35
vmcompact/data.py
Normal file
@ -0,0 +1,35 @@
|
||||
from dataclasses import dataclass
|
||||
from voicemeeter import kinds
|
||||
|
||||
|
||||
@dataclass
|
||||
class BaseValues:
|
||||
level_height: int = 100
|
||||
level_width: int = 80
|
||||
|
||||
# are we dragging a scale with mouse 1
|
||||
in_scale_button_1: bool = False
|
||||
# are we dragging main window with mouse 1
|
||||
dragging: bool = False
|
||||
# direction the gui extends
|
||||
extends_horizontal: bool = True
|
||||
# a vban connection established
|
||||
vban_connected: bool = False
|
||||
# are themes enabled
|
||||
themes_enabled: bool = True
|
||||
# are we using a theme
|
||||
using_theme: bool = False
|
||||
# bus assigned as current submix
|
||||
submixes: int = 0
|
||||
|
||||
|
||||
_base_vals = BaseValues()
|
||||
|
||||
|
||||
_kinds = {kind.id: kind for kind in kinds.all}
|
||||
|
||||
_kinds_all = _kinds.values()
|
||||
|
||||
|
||||
def kind_get(kind_id):
|
||||
return _kinds[kind_id]
|
4
vmcompact/errors.py
Normal file
@ -0,0 +1,4 @@
|
||||
class VMCompactErrors(Exception):
|
||||
"""Base classs for VMCompact Errors"""
|
||||
|
||||
pass
|
244
vmcompact/gainlayer.py
Normal file
@ -0,0 +1,244 @@
|
||||
import tkinter as tk
|
||||
from tkinter import ttk, messagebox as msg
|
||||
from functools import partial
|
||||
|
||||
from .data import _base_vals
|
||||
|
||||
|
||||
class GainLayer(ttk.LabelFrame):
|
||||
"""Concrete class representing a single gainlayer"""
|
||||
|
||||
def __init__(self, parent, index, j):
|
||||
super().__init__(parent)
|
||||
self._parent = parent
|
||||
self.index = index
|
||||
self.j = j
|
||||
self.gain = tk.DoubleVar()
|
||||
self.level = tk.DoubleVar()
|
||||
self.on = tk.BooleanVar()
|
||||
self.s = self._parent._parent.styletable
|
||||
|
||||
self.sync()
|
||||
self._make_widgets()
|
||||
|
||||
self.col_row_configure()
|
||||
self.watch_pdirty()
|
||||
self.watch_levels()
|
||||
|
||||
@property
|
||||
def target(self):
|
||||
"""returns the current interface"""
|
||||
_target = self._parent.target
|
||||
return _target.strip[self.index].gainlayer[self.j]
|
||||
|
||||
def getter(self, param):
|
||||
if param in dir(self.target):
|
||||
return getattr(self.target, param)
|
||||
|
||||
def setter(self, param, value):
|
||||
if param in dir(self.target):
|
||||
setattr(self.target, param, value)
|
||||
|
||||
def reset_gain(self, *args):
|
||||
self.setter("gain", 0)
|
||||
self.gain.set(0)
|
||||
self._parent._parent.nav_frame.info_text.set(0)
|
||||
|
||||
def scale_enter(self, *args):
|
||||
self._parent._parent.nav_frame.info_text.set(round(self.gain.get(), 1))
|
||||
|
||||
def scale_leave(self, *args):
|
||||
self._parent._parent.nav_frame.info_text.set("")
|
||||
|
||||
def scale_press(self, *args):
|
||||
_base_vals.in_scale_button_1 = True
|
||||
|
||||
def scale_release(self, *args):
|
||||
_base_vals.in_scale_button_1 = False
|
||||
|
||||
def scale_callback(self, *args):
|
||||
"""callback function for scale widget"""
|
||||
self.setter("gain", self.gain.get())
|
||||
self._parent._parent.nav_frame.info_text.set(round(self.gain.get(), 1))
|
||||
|
||||
def set_on(self):
|
||||
"""enables a gainlayer. sets its button colour"""
|
||||
setattr(
|
||||
self._parent.target.strip[self.index],
|
||||
self._parent.buses[self.j],
|
||||
self.on.get(),
|
||||
)
|
||||
if not _base_vals.using_theme:
|
||||
self.s.configure(
|
||||
f"On.TButton",
|
||||
background=f'{"green" if self.on.get() else "white"}',
|
||||
)
|
||||
|
||||
def _make_widgets(self):
|
||||
"""Creates a progressbar, scale, on button and config button for a single channel"""
|
||||
# Progress bar
|
||||
self.pb = ttk.Progressbar(
|
||||
self,
|
||||
maximum=100,
|
||||
orient="vertical",
|
||||
mode="determinate",
|
||||
variable=self.level,
|
||||
)
|
||||
self.pb.grid(column=0, row=0)
|
||||
|
||||
# Scale
|
||||
self.scale = ttk.Scale(
|
||||
self,
|
||||
from_=12.0,
|
||||
to=-60.0,
|
||||
orient="vertical",
|
||||
variable=self.gain,
|
||||
command=self.scale_callback,
|
||||
length=self._parent.height,
|
||||
)
|
||||
self.scale.grid(column=1, row=0)
|
||||
self.scale.bind("<Double-Button-1>", self.reset_gain)
|
||||
self.scale.bind("<Button-1>", self.scale_press)
|
||||
self.scale.bind("<Enter>", self.scale_enter)
|
||||
self.scale.bind("<ButtonRelease-1>", self.scale_release)
|
||||
self.scale.bind("<Leave>", self.scale_leave)
|
||||
|
||||
# On button
|
||||
self.button_on = ttk.Checkbutton(
|
||||
self,
|
||||
text="ON",
|
||||
command=self.set_on,
|
||||
style=f"{'Toggle.TButton' if _base_vals.using_theme else 'On.TButton'}",
|
||||
variable=self.on,
|
||||
)
|
||||
self.button_on.grid(column=0, row=1, columnspan=2)
|
||||
|
||||
def col_row_configure(self):
|
||||
[
|
||||
child.grid_configure(padx=1, pady=1, sticky=(tk.N, tk.S, tk.W, tk.E))
|
||||
for child in self.winfo_children()
|
||||
if isinstance(child, ttk.Checkbutton)
|
||||
]
|
||||
[
|
||||
child.grid_configure(padx=1, pady=1, sticky=(tk.N, tk.S))
|
||||
for child in self.winfo_children()
|
||||
if isinstance(child, ttk.Progressbar) or isinstance(child, ttk.Scale)
|
||||
]
|
||||
self.columnconfigure(0, minsize=36)
|
||||
self.columnconfigure(1, minsize=36)
|
||||
self.rowconfigure(1, minsize=70)
|
||||
|
||||
def watch_pdirty(self):
|
||||
self.after(1, self.watch_pdirty_step)
|
||||
|
||||
def watch_pdirty_step(self):
|
||||
"""keeps params synced but ensures sliders are responsive"""
|
||||
if self._parent._parent.pdirty and not _base_vals.in_scale_button_1:
|
||||
self.sync()
|
||||
self.after(1, self.watch_pdirty_step)
|
||||
|
||||
def sync(self):
|
||||
"""sync params with voicemeeter"""
|
||||
retval = self.getter("label")
|
||||
if len(retval) > 10:
|
||||
retval = f"{retval[:8]}.."
|
||||
self.configure(text=retval)
|
||||
self.gain.set(self.getter("gain"))
|
||||
self.on.set(
|
||||
getattr(
|
||||
self._parent.target.strip[self.index],
|
||||
self._parent.buses[self.j],
|
||||
)
|
||||
)
|
||||
|
||||
def watch_levels(self):
|
||||
self.after(1, self.watch_levels_step)
|
||||
|
||||
def watch_levels_step(self):
|
||||
if not _base_vals.dragging:
|
||||
vals = self._parent.target.strip[self.index].levels.prefader
|
||||
val = vals[0] if vals[0] > vals[1] else vals[0]
|
||||
self.level.set(val)
|
||||
self.level.set(
|
||||
(
|
||||
0
|
||||
if self._parent._parent.channel_frame.strips[self.index].mute.get()
|
||||
else 100 + (val - 18) + self.gain.get()
|
||||
)
|
||||
)
|
||||
self.after(
|
||||
25 if not _base_vals.in_scale_button_1 else 100, self.watch_levels_step
|
||||
)
|
||||
|
||||
|
||||
class SubMixFrame(ttk.Frame):
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent)
|
||||
self._parent = parent
|
||||
self.phys_out, self.virt_out = parent.kind.outs
|
||||
self.buses = tuple(f"A{i+1}" for i in range(self.phys_out)) + tuple(
|
||||
f"B{i+1}" for i in range(self.virt_out)
|
||||
)
|
||||
defaults = {
|
||||
"width": 80,
|
||||
"height": 150,
|
||||
}
|
||||
self.configuration = defaults | self.configuration
|
||||
self.width = self.configuration["width"]
|
||||
self.height = self.configuration["height"]
|
||||
|
||||
self.gainlayers = [
|
||||
GainLayer(self, index, _base_vals.submixes) for index in range(8)
|
||||
]
|
||||
[
|
||||
gainlayer.grid(row=0, column=self.gainlayers.index(gainlayer))
|
||||
for gainlayer in self.gainlayers
|
||||
]
|
||||
|
||||
self.col_row_configure()
|
||||
|
||||
# destroy any without label text
|
||||
for i, gainlayer in enumerate(self.gainlayers):
|
||||
gainlayer.grid(row=0, column=i)
|
||||
if not gainlayer.cget("text"):
|
||||
self.columnconfigure(i, minsize=0)
|
||||
gainlayer.grid_remove()
|
||||
|
||||
self.watch_pdirty()
|
||||
|
||||
@property
|
||||
def target(self):
|
||||
"""returns the current interface"""
|
||||
return self._parent.target
|
||||
|
||||
@property
|
||||
def configuration(self):
|
||||
return self._parent.configuration["channel"]
|
||||
|
||||
@configuration.setter
|
||||
def configuration(self, val):
|
||||
self._parent.configuration["channel"] = val
|
||||
|
||||
def col_row_configure(self):
|
||||
[
|
||||
self.columnconfigure(i, minsize=self.width)
|
||||
for i, _ in enumerate(self.gainlayers)
|
||||
]
|
||||
[self.rowconfigure(0, minsize=130) for i, _ in enumerate(self.gainlayers)]
|
||||
|
||||
def watch_pdirty(self):
|
||||
self.after(1, self.watch_pdirty_step)
|
||||
|
||||
def watch_pdirty_step(self):
|
||||
if self._parent.pdirty:
|
||||
self.watch_labels()
|
||||
self.after(1, self.watch_pdirty_step)
|
||||
|
||||
def watch_labels(self):
|
||||
for i, gainlayer in enumerate(self.gainlayers):
|
||||
if not self.target.strip[gainlayer.index].label:
|
||||
self.columnconfigure(i, minsize=0)
|
||||
gainlayer.grid_remove()
|
||||
else:
|
||||
self.columnconfigure(i, minsize=80)
|
||||
gainlayer.grid()
|
BIN
vmcompact/img/cat.ico
Normal file
After Width: | Height: | Size: 110 KiB |
316
vmcompact/menu.py
Normal file
@ -0,0 +1,316 @@
|
||||
import tkinter as tk
|
||||
from tkinter import ttk, messagebox
|
||||
from functools import partial
|
||||
import webbrowser
|
||||
|
||||
import vbancmd
|
||||
|
||||
from .configurations import configuration
|
||||
from .data import _base_vals, kind_get
|
||||
from .gainlayer import SubMixFrame
|
||||
|
||||
|
||||
class Menus(tk.Menu):
|
||||
def __init__(self, parent, vmr):
|
||||
super().__init__()
|
||||
self._parent = parent
|
||||
self._vmr = vmr
|
||||
self.vban_conns = [None for i, _ in enumerate(self.configuration_vban)]
|
||||
self.menubar = tk.Menu(self, tearoff=0)
|
||||
parent["menu"] = self.menubar
|
||||
self._is_topmost = tk.BooleanVar()
|
||||
self._selected_bus = list(tk.BooleanVar() for _ in range(8))
|
||||
|
||||
# voicemeeter menu
|
||||
self.menu_voicemeeter = tk.Menu(self.menubar, tearoff=0)
|
||||
self.menubar.add_cascade(menu=self.menu_voicemeeter, label="Voicemeeter")
|
||||
self.menu_voicemeeter.add_checkbutton(
|
||||
label="Always On Top",
|
||||
onvalue=1,
|
||||
offvalue=0,
|
||||
variable=self._is_topmost,
|
||||
command=self.always_on_top,
|
||||
)
|
||||
self.menu_voicemeeter.add_separator()
|
||||
self.menu_voicemeeter.add_command(
|
||||
label="Show",
|
||||
underline=0,
|
||||
command=partial(self.action_invoke_voicemeeter, "show"),
|
||||
)
|
||||
self.menu_voicemeeter.add_command(
|
||||
label="Hide",
|
||||
underline=0,
|
||||
command=partial(self.action_invoke_voicemeeter, "hide"),
|
||||
)
|
||||
self.menu_voicemeeter.add_command(
|
||||
label="Restart",
|
||||
underline=0,
|
||||
command=partial(self.action_invoke_voicemeeter, "restart"),
|
||||
)
|
||||
self.menu_voicemeeter.add_command(
|
||||
label="Shutdown",
|
||||
underline=0,
|
||||
command=partial(self.action_invoke_voicemeeter, "shutdown"),
|
||||
)
|
||||
self.menu_voicemeeter.add_separator()
|
||||
self.menu_lock = tk.Menu(self.menu_voicemeeter, tearoff=0)
|
||||
self.menu_voicemeeter.add_cascade(
|
||||
menu=self.menu_lock, label="GUI Lock", underline=0
|
||||
)
|
||||
self.menu_lock.add_command(
|
||||
label="Lock", command=partial(self.action_set_voicemeeter, "lock")
|
||||
)
|
||||
self.menu_lock.add_command(
|
||||
label="Unlock", command=partial(self.action_set_voicemeeter, "lock", False)
|
||||
)
|
||||
|
||||
# profiles menu
|
||||
menu_profiles = tk.Menu(self.menubar, tearoff=0)
|
||||
self.menubar.add_cascade(menu=menu_profiles, label="Profiles")
|
||||
self.menu_profiles_load = tk.Menu(menu_profiles, tearoff=0)
|
||||
menu_profiles.add_cascade(menu=self.menu_profiles_load, label="Load profile")
|
||||
defaults = {"base", "blank"}
|
||||
if len(vmr.profiles) > len(defaults) and all(
|
||||
key in vmr.profiles for key in defaults
|
||||
):
|
||||
[
|
||||
self.menu_profiles_load.add_command(
|
||||
label=profile, command=partial(self.load_profile, profile)
|
||||
)
|
||||
for profile in vmr.profiles.keys()
|
||||
if profile not in defaults
|
||||
]
|
||||
else:
|
||||
menu_profiles.entryconfig(0, state="disabled")
|
||||
menu_profiles.add_separator()
|
||||
menu_profiles.add_command(label="Reset to defaults", command=self.load_defaults)
|
||||
|
||||
# vban connect menu
|
||||
self.menu_vban = tk.Menu(self.menubar, tearoff=0)
|
||||
self.menubar.add_cascade(menu=self.menu_vban, label="VBAN Connect")
|
||||
for i, _ in enumerate(self.configuration_vban):
|
||||
setattr(self, f"menu_vban_{i+1}", tk.Menu(self.menu_vban, tearoff=0))
|
||||
target_menu = getattr(self, f"menu_vban_{i+1}")
|
||||
self.menu_vban.add_cascade(
|
||||
menu=target_menu, label=f"VBAN Connect #{i+1}", underline=0
|
||||
)
|
||||
target_menu.add_command(
|
||||
label="Connect", command=partial(self.vban_connect, i)
|
||||
)
|
||||
target_menu.add_command(
|
||||
label="Disconnect", command=partial(self.vban_disconnect, i)
|
||||
)
|
||||
target_menu.entryconfig(1, state="disabled")
|
||||
|
||||
# extends menu
|
||||
self.menu_extends = tk.Menu(self.menubar, tearoff=0)
|
||||
self.menubar.add_cascade(menu=self.menu_extends, label="Extends")
|
||||
self.menu_extends.add_command(
|
||||
label="horizontal",
|
||||
underline=0,
|
||||
command=partial(self.switch_orientation, extends_horizontal=True),
|
||||
)
|
||||
self.menu_extends.add_command(
|
||||
label="vertical",
|
||||
underline=0,
|
||||
command=partial(self.switch_orientation, extends_horizontal=False),
|
||||
)
|
||||
self.menu_extends.entryconfig(
|
||||
0 if _base_vals.extends_horizontal else 1, state="disabled"
|
||||
)
|
||||
|
||||
# submixes menu
|
||||
# here we build menu regardless of kind but disable if not Potato
|
||||
buses = tuple(f"A{i+1}" for i in range(5)) + tuple(f"B{i+1}" for i in range(3))
|
||||
self.menu_submixes = tk.Menu(self.menubar, tearoff=0)
|
||||
self.menubar.add_cascade(menu=self.menu_submixes, label="Submixes")
|
||||
[
|
||||
self.menu_submixes.add_checkbutton(
|
||||
label=f"Bus {buses[i]}",
|
||||
underline=0,
|
||||
onvalue=1,
|
||||
offvalue=0,
|
||||
variable=self._selected_bus[i],
|
||||
command=partial(self.set_submix, i),
|
||||
)
|
||||
for i in range(8)
|
||||
]
|
||||
self._selected_bus[_base_vals.submixes].set(True)
|
||||
if self._parent.kind.name != "Potato":
|
||||
self.menubar.entryconfig(4, state="disabled")
|
||||
|
||||
# themes menu
|
||||
self.menu_themes = tk.Menu(self.menubar, tearoff=0)
|
||||
self.menubar.add_cascade(menu=self.menu_themes, label="Themes")
|
||||
tcl_themes = [
|
||||
"light",
|
||||
"dark",
|
||||
]
|
||||
[
|
||||
self.menu_themes.add_command(
|
||||
label=theme.capitalize(),
|
||||
command=partial(self.load_theme, theme),
|
||||
)
|
||||
for theme in tcl_themes
|
||||
]
|
||||
self.menu_themes.entryconfig(
|
||||
0 if self.configuration_app["theme"]["mode"] == "light" else 1,
|
||||
state="disabled",
|
||||
)
|
||||
if not _base_vals.themes_enabled:
|
||||
self.menubar.entryconfig(6, state="disabled")
|
||||
|
||||
# Help menu
|
||||
self.menu_help = tk.Menu(self.menubar, tearoff=0)
|
||||
self.menubar.add_cascade(menu=self.menu_help, label="Help")
|
||||
self.menu_help.add_command(
|
||||
label="Voicemeeter Site",
|
||||
command=self.documentation,
|
||||
)
|
||||
self.menu_help.add_command(
|
||||
label="Source Code",
|
||||
command=self.github,
|
||||
)
|
||||
self.menu_help.add_command(
|
||||
label="App Creator",
|
||||
command=self.onyxandiris,
|
||||
)
|
||||
|
||||
@property
|
||||
def target(self):
|
||||
"""use the correct interface"""
|
||||
return self._parent.target
|
||||
|
||||
@property
|
||||
def configuration_app(self):
|
||||
return configuration["app"]
|
||||
|
||||
@configuration_app.setter
|
||||
def configuration_app(self, val):
|
||||
self.configuration_app = val
|
||||
|
||||
@property
|
||||
def configuration_vban(self):
|
||||
return configuration["vban"]
|
||||
|
||||
def action_invoke_voicemeeter(self, cmd):
|
||||
getattr(self.target.command, cmd)()
|
||||
|
||||
def action_set_voicemeeter(self, cmd, val=True):
|
||||
setattr(self.target.command, cmd, val)
|
||||
|
||||
def load_profile(self, profile):
|
||||
self.target.apply_profile(profile)
|
||||
|
||||
def load_defaults(self):
|
||||
resp = messagebox.askyesno(
|
||||
message="Are you sure you want to Reset values to defaults?\nPhysical strips B1, Virtual strips A1\nMono, Solo, Mute, EQ all OFF"
|
||||
)
|
||||
if resp:
|
||||
self.target.apply_profile("base")
|
||||
|
||||
def always_on_top(self):
|
||||
self._parent.attributes("-topmost", self._is_topmost.get())
|
||||
self._parent.update()
|
||||
|
||||
def switch_orientation(self, extends_horizontal: bool = True, *args):
|
||||
_base_vals.extends_horizontal = extends_horizontal
|
||||
if extends_horizontal:
|
||||
self.menu_extends.entryconfig(0, state="disabled")
|
||||
self.menu_extends.entryconfig(1, state="normal")
|
||||
else:
|
||||
self.menu_extends.entryconfig(1, state="disabled")
|
||||
self.menu_extends.entryconfig(0, state="normal")
|
||||
|
||||
def set_submix(self, i):
|
||||
if _base_vals.submixes != i:
|
||||
_base_vals.submixes = i
|
||||
if self._parent.submix_frame is not None:
|
||||
self._parent.submix_frame.destroy()
|
||||
self._parent.nav_frame.show_submix()
|
||||
for j, var in enumerate(self._selected_bus):
|
||||
var.set(True if i == j else False)
|
||||
|
||||
def load_theme(self, theme):
|
||||
self.tk.call("set_theme", theme)
|
||||
self.configuration_app["theme"]["mode"] = theme
|
||||
self.menu_themes.entryconfig(
|
||||
0,
|
||||
state=f"{'disabled' if theme == 'light' else 'normal'}",
|
||||
)
|
||||
self.menu_themes.entryconfig(
|
||||
1,
|
||||
state=f"{'disabled' if theme == 'dark' else 'normal'}",
|
||||
)
|
||||
[
|
||||
menu.config(bg=f"{'black' if theme == 'dark' else 'white'}")
|
||||
for menu in self.menubar.winfo_children()
|
||||
if isinstance(menu, tk.Menu)
|
||||
]
|
||||
self.menu_lock.config(bg=f"{'black' if theme == 'dark' else 'white'}")
|
||||
self.menu_profiles_load.config(bg=f"{'black' if theme == 'dark' else 'white'}")
|
||||
[
|
||||
menu.config(bg=f"{'black' if theme == 'dark' else 'white'}")
|
||||
for menu in self.menu_vban.winfo_children()
|
||||
if isinstance(menu, tk.Menu)
|
||||
]
|
||||
|
||||
def vban_connect(self, i):
|
||||
[
|
||||
self.menu_vban.entryconfig(j, state="disabled")
|
||||
for j, _ in enumerate(self.menu_vban.winfo_children())
|
||||
if j != i
|
||||
]
|
||||
|
||||
opts = {}
|
||||
opts |= self.configuration_vban[f"connection-{i+1}"]
|
||||
kind_id = opts.pop("kind")
|
||||
self.vban_conns[i] = vbancmd.connect(kind_id, **opts)
|
||||
# login to vban interface
|
||||
self.vban_conns[i].login()
|
||||
# destroy the current App frames
|
||||
self._parent._destroy_top_level_frames()
|
||||
_base_vals.vban_connected = True
|
||||
# build new app frames according to a kind
|
||||
kind = kind_get(kind_id)
|
||||
self._parent._make_app(kind, self.vban_conns[i])
|
||||
target_menu = getattr(self, f"menu_vban_{i+1}")
|
||||
target_menu.entryconfig(0, state="disabled")
|
||||
target_menu.entryconfig(1, state="normal")
|
||||
self.menubar.entryconfig(
|
||||
4, state=f"{'normal' if kind.name == 'Potato' else 'disabled'}"
|
||||
)
|
||||
|
||||
def vban_disconnect(self, i):
|
||||
# destroy the current App frames
|
||||
self._parent._destroy_top_level_frames()
|
||||
_base_vals.vban_connected = False
|
||||
# logout of vban interface
|
||||
i_to_close = self.vban_conns[i]
|
||||
self.vban_conns[i] = None
|
||||
i_to_close.logout()
|
||||
# build new app frames according to a kind
|
||||
kind = kind_get(self._vmr.type)
|
||||
# destroy the current App frames
|
||||
self._parent._destroy_top_level_frames()
|
||||
self._parent._make_app(kind, None)
|
||||
target_menu = getattr(self, f"menu_vban_{i+1}")
|
||||
target_menu.entryconfig(0, state="normal")
|
||||
target_menu.entryconfig(1, state="disabled")
|
||||
|
||||
self.after(15000, self.enable_vban_menus)
|
||||
|
||||
def enable_vban_menus(self):
|
||||
[
|
||||
self.menu_vban.entryconfig(j, state="normal")
|
||||
for j, _ in enumerate(self.menu_vban.winfo_children())
|
||||
]
|
||||
|
||||
def documentation(self):
|
||||
webbrowser.open_new(r"https://voicemeeter.com/")
|
||||
|
||||
def github(self):
|
||||
webbrowser.open_new(r"https://github.com/onyx-and-iris")
|
||||
|
||||
def onyxandiris(self):
|
||||
webbrowser.open_new(r"https://onyxandiris.online")
|
156
vmcompact/navigation.py
Normal file
@ -0,0 +1,156 @@
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
|
||||
from .channels import ChannelFrame
|
||||
from .gainlayer import SubMixFrame
|
||||
from .data import _base_vals
|
||||
|
||||
|
||||
class Navigation(ttk.Frame):
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent)
|
||||
self._parent = parent
|
||||
self.s = parent.styletable
|
||||
|
||||
self.submix = tk.BooleanVar()
|
||||
self.channel = tk.BooleanVar()
|
||||
self.extend = tk.BooleanVar()
|
||||
self.info = tk.BooleanVar()
|
||||
|
||||
self.channel_text = tk.StringVar()
|
||||
self.channel_text.set(parent.channel_frame.identifier.upper())
|
||||
self.extend_text = tk.StringVar()
|
||||
self.extend_text.set("EXTEND")
|
||||
self.info_text = tk.StringVar()
|
||||
self._parent.submix_frame = None
|
||||
|
||||
self._make_widgets()
|
||||
|
||||
self.col_row_configure()
|
||||
|
||||
def _make_widgets(self):
|
||||
"""Creates the navigation buttons"""
|
||||
self.submix_button = ttk.Checkbutton(
|
||||
self,
|
||||
text="SUBMIX",
|
||||
command=self.show_submix,
|
||||
style=f"{'Toggle.TButton' if _base_vals.using_theme else f'Submix.TButton'}",
|
||||
variable=self.submix,
|
||||
)
|
||||
self.channel_button = ttk.Checkbutton(
|
||||
self,
|
||||
textvariable=self.channel_text,
|
||||
command=self.switch_channel,
|
||||
style=f"{'Toggle.TButton' if _base_vals.using_theme else f'Channel.TButton'}",
|
||||
variable=self.channel,
|
||||
)
|
||||
self.extend_button = ttk.Checkbutton(
|
||||
self,
|
||||
textvariable=self.extend_text,
|
||||
command=self.extend_frame,
|
||||
style=f"{'Toggle.TButton' if _base_vals.using_theme else f'Extend.TButton'}",
|
||||
variable=self.extend,
|
||||
)
|
||||
self.info_button = ttk.Checkbutton(
|
||||
self,
|
||||
textvariable=self.info_text,
|
||||
style=f"{'Toggle.TButton' if _base_vals.using_theme else f'Rec.TButton'}",
|
||||
variable=self.info,
|
||||
)
|
||||
self.info_button["state"] = "active"
|
||||
|
||||
""" Position navigation buttons """
|
||||
self.submix_button.grid(column=0, row=0)
|
||||
self.channel_button.grid(column=0, row=1, rowspan=1)
|
||||
self.extend_button.grid(column=0, row=2)
|
||||
self.info_button.grid(column=0, row=3)
|
||||
|
||||
if self._parent.kind.name != "Potato":
|
||||
self.submix_button["state"] = "disabled"
|
||||
|
||||
def show_submix(self):
|
||||
if self.submix.get():
|
||||
if _base_vals.extends_horizontal:
|
||||
self._parent.submix_frame = SubMixFrame(self._parent)
|
||||
self._parent.submix_frame.grid(row=0, column=2)
|
||||
if self._parent.bus_frame:
|
||||
self._parent.bus_frame.grid_remove()
|
||||
else:
|
||||
self._parent.submix_frame = SubMixFrame(self._parent)
|
||||
self._parent.submix_frame.grid(row=2, column=0, sticky=(tk.W))
|
||||
if self._parent.bus_frame:
|
||||
self._parent.bus_frame.grid_remove()
|
||||
else:
|
||||
if _base_vals.extends_horizontal:
|
||||
self._parent.submix_frame.destroy()
|
||||
if self._parent.bus_frame:
|
||||
self._parent.bus_frame.grid()
|
||||
else:
|
||||
self._parent.columnconfigure(1, weight=0)
|
||||
else:
|
||||
self._parent.submix_frame.destroy()
|
||||
if self._parent.bus_frame:
|
||||
self._parent.bus_frame.grid()
|
||||
else:
|
||||
self._parent.rowconfigure(2, weight=0, minsize=0)
|
||||
|
||||
if not _base_vals.using_theme:
|
||||
self.s.configure(
|
||||
f"Submix.TButton",
|
||||
background=f'{"purple" if self.submix.get() else "white"}',
|
||||
)
|
||||
|
||||
def switch_channel(self):
|
||||
if self.channel_text.get() == "STRIP":
|
||||
self._parent.bus_frame = ChannelFrame.make_buses(self._parent)
|
||||
self._parent.bus_frame.grid(row=0, column=0)
|
||||
self._parent.channel_frame.destroy()
|
||||
else:
|
||||
self._parent.channel_frame = ChannelFrame.make_strips(self._parent)
|
||||
self._parent.channel_frame.grid(row=0, column=0)
|
||||
self._parent.bus_frame.destroy()
|
||||
|
||||
self.extend_button["state"] = (
|
||||
"disabled" if self.channel_text.get() == "STRIP" else "normal"
|
||||
)
|
||||
[frame.destroy() for frame in self._parent.configframes]
|
||||
self.channel_text.set("BUS" if self.channel_text.get() == "STRIP" else "STRIP")
|
||||
|
||||
def extend_frame(self):
|
||||
if self.extend.get():
|
||||
self.channel_button["state"] = "disabled"
|
||||
self._parent.bus_frame = ChannelFrame.make_buses(self._parent)
|
||||
if _base_vals.extends_horizontal:
|
||||
self._parent.bus_frame.grid(row=0, column=2)
|
||||
else:
|
||||
self._parent.bus_frame.grid(row=2, column=0, sticky=(tk.W))
|
||||
else:
|
||||
[
|
||||
frame.destroy()
|
||||
for frame in self._parent.configframes
|
||||
if "!busconfig" in str(frame)
|
||||
]
|
||||
self._parent.bus_frame.destroy()
|
||||
self._parent.bus_frame = None
|
||||
self.channel_button["state"] = "normal"
|
||||
|
||||
if self._parent.submix_frame:
|
||||
self._parent.submix_frame.destroy()
|
||||
self.submix.set(False)
|
||||
if not _base_vals.using_theme:
|
||||
self.s.configure(
|
||||
f"Submix.TButton",
|
||||
background=f'{"purple" if self.submix.get() else "white"}',
|
||||
)
|
||||
|
||||
self.extend_text.set("REDUCE" if self.extend.get() else "EXTEND")
|
||||
|
||||
def col_row_configure(self):
|
||||
[
|
||||
child.grid_configure(padx=1, pady=1, sticky=(tk.N, tk.S, tk.W, tk.E))
|
||||
for child in self.winfo_children()
|
||||
if isinstance(child, ttk.Checkbutton)
|
||||
]
|
||||
|
||||
self.rowconfigure(1, minsize=self._parent.channel_frame.height - 18)
|
||||
self.grid(sticky=(tk.N))
|
21
vmcompact/sun-valley-theme/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 rdbende
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
88
vmcompact/sun-valley-theme/sun-valley.tcl
Normal file
@ -0,0 +1,88 @@
|
||||
# Copyright © 2021 rdbende <rdbende@gmail.com>
|
||||
|
||||
source [file join [file dirname [info script]] theme light.tcl]
|
||||
source [file join [file dirname [info script]] theme dark.tcl]
|
||||
|
||||
option add *tearOff 0
|
||||
|
||||
proc set_theme {mode} {
|
||||
if {$mode == "dark"} {
|
||||
ttk::style theme use "sun-valley-dark"
|
||||
|
||||
array set colors {
|
||||
-fg "#ffffff"
|
||||
-bg "#1c1c1c"
|
||||
-disabledfg "#595959"
|
||||
-selectfg "#ffffff"
|
||||
-selectbg "#2f60d8"
|
||||
}
|
||||
|
||||
ttk::style configure . \
|
||||
-background $colors(-bg) \
|
||||
-foreground $colors(-fg) \
|
||||
-troughcolor $colors(-bg) \
|
||||
-focuscolor $colors(-selectbg) \
|
||||
-selectbackground $colors(-selectbg) \
|
||||
-selectforeground $colors(-selectfg) \
|
||||
-insertwidth 1 \
|
||||
-insertcolor $colors(-fg) \
|
||||
-fieldbackground $colors(-selectbg) \
|
||||
-font {"Segoe UI" 10} \
|
||||
-borderwidth 0 \
|
||||
-relief flat
|
||||
|
||||
tk_setPalette \
|
||||
background [ttk::style lookup . -background] \
|
||||
foreground [ttk::style lookup . -foreground] \
|
||||
highlightColor [ttk::style lookup . -focuscolor] \
|
||||
selectBackground [ttk::style lookup . -selectbackground] \
|
||||
selectForeground [ttk::style lookup . -selectforeground] \
|
||||
activeBackground [ttk::style lookup . -selectbackground] \
|
||||
activeForeground [ttk::style lookup . -selectforeground]
|
||||
|
||||
ttk::style map . -foreground [list disabled $colors(-disabledfg)]
|
||||
|
||||
option add *font [ttk::style lookup . -font]
|
||||
option add *Menu.selectcolor $colors(-fg)
|
||||
option add *Menu.background #2f2f2f
|
||||
|
||||
} elseif {$mode == "light"} {
|
||||
ttk::style theme use "sun-valley-light"
|
||||
|
||||
array set colors {
|
||||
-fg "#202020"
|
||||
-bg "#fafafa"
|
||||
-disabledfg "#a0a0a0"
|
||||
-selectfg "#ffffff"
|
||||
-selectbg "#2f60d8"
|
||||
}
|
||||
|
||||
ttk::style configure . \
|
||||
-background $colors(-bg) \
|
||||
-foreground $colors(-fg) \
|
||||
-troughcolor $colors(-bg) \
|
||||
-focuscolor $colors(-selectbg) \
|
||||
-selectbackground $colors(-selectbg) \
|
||||
-selectforeground $colors(-selectfg) \
|
||||
-insertwidth 1 \
|
||||
-insertcolor $colors(-fg) \
|
||||
-fieldbackground $colors(-selectbg) \
|
||||
-font {"Segoe UI" 10} \
|
||||
-borderwidth 0 \
|
||||
-relief flat
|
||||
|
||||
tk_setPalette background [ttk::style lookup . -background] \
|
||||
foreground [ttk::style lookup . -foreground] \
|
||||
highlightColor [ttk::style lookup . -focuscolor] \
|
||||
selectBackground [ttk::style lookup . -selectbackground] \
|
||||
selectForeground [ttk::style lookup . -selectforeground] \
|
||||
activeBackground [ttk::style lookup . -selectbackground] \
|
||||
activeForeground [ttk::style lookup . -selectforeground]
|
||||
|
||||
ttk::style map . -foreground [list disabled $colors(-disabledfg)]
|
||||
|
||||
option add *font [ttk::style lookup . -font]
|
||||
option add *Menu.selectcolor $colors(-fg)
|
||||
option add *Menu.background #e7e7e7
|
||||
}
|
||||
}
|
521
vmcompact/sun-valley-theme/theme/dark.tcl
Normal file
@ -0,0 +1,521 @@
|
||||
# Copyright © 2021 rdbende <rdbende@gmail.com>
|
||||
|
||||
# A stunning dark theme for ttk based on Microsoft's Sun Valley visual style
|
||||
|
||||
package require Tk 8.6
|
||||
|
||||
namespace eval ttk::theme::sun-valley-dark {
|
||||
variable version 1.0
|
||||
package provide ttk::theme::sun-valley-dark $version
|
||||
|
||||
ttk::style theme create sun-valley-dark -parent clam -settings {
|
||||
proc load_images {imgdir} {
|
||||
variable images
|
||||
foreach file [glob -directory $imgdir *.png] {
|
||||
set images([file tail [file rootname $file]]) \
|
||||
[image create photo -file $file -format png]
|
||||
}
|
||||
}
|
||||
|
||||
load_images [file join [file dirname [info script]] dark]
|
||||
|
||||
array set colors {
|
||||
-fg "#ffffff"
|
||||
-bg "#1c1c1c"
|
||||
-disabledfg "#595959"
|
||||
-selectfg "#ffffff"
|
||||
-selectbg "#2f60d8"
|
||||
}
|
||||
|
||||
ttk::style layout TButton {
|
||||
Button.button -children {
|
||||
Button.padding -children {
|
||||
Button.label -side left -expand 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ttk::style layout Toolbutton {
|
||||
Toolbutton.button -children {
|
||||
Toolbutton.padding -children {
|
||||
Toolbutton.label -side left -expand 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ttk::style layout TMenubutton {
|
||||
Menubutton.button -children {
|
||||
Menubutton.padding -children {
|
||||
Menubutton.label -side left -expand 1
|
||||
Menubutton.indicator -side right -sticky nsew
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ttk::style layout TOptionMenu {
|
||||
OptionMenu.button -children {
|
||||
OptionMenu.padding -children {
|
||||
OptionMenu.label -side left -expand 1
|
||||
OptionMenu.indicator -side right -sticky nsew
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ttk::style layout Accent.TButton {
|
||||
AccentButton.button -children {
|
||||
AccentButton.padding -children {
|
||||
AccentButton.label -side left -expand 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ttk::style layout Titlebar.TButton {
|
||||
TitlebarButton.button -children {
|
||||
TitlebarButton.padding -children {
|
||||
TitlebarButton.label -side left -expand 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ttk::style layout Close.Titlebar.TButton {
|
||||
CloseButton.button -children {
|
||||
CloseButton.padding -children {
|
||||
CloseButton.label -side left -expand 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ttk::style layout TCheckbutton {
|
||||
Checkbutton.button -children {
|
||||
Checkbutton.padding -children {
|
||||
Checkbutton.indicator -side left
|
||||
Checkbutton.label -side right -expand 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ttk::style layout Switch.TCheckbutton {
|
||||
Switch.button -children {
|
||||
Switch.padding -children {
|
||||
Switch.indicator -side left
|
||||
Switch.label -side right -expand 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ttk::style layout Toggle.TButton {
|
||||
ToggleButton.button -children {
|
||||
ToggleButton.padding -children {
|
||||
ToggleButton.label -side left -expand 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ttk::style layout TRadiobutton {
|
||||
Radiobutton.button -children {
|
||||
Radiobutton.padding -children {
|
||||
Radiobutton.indicator -side left
|
||||
Radiobutton.label -side right -expand 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ttk::style layout Vertical.TScrollbar {
|
||||
Vertical.Scrollbar.trough -sticky ns -children {
|
||||
Vertical.Scrollbar.uparrow -side top
|
||||
Vertical.Scrollbar.downarrow -side bottom
|
||||
Vertical.Scrollbar.thumb -expand 1
|
||||
}
|
||||
}
|
||||
|
||||
ttk::style layout Horizontal.TScrollbar {
|
||||
Horizontal.Scrollbar.trough -sticky ew -children {
|
||||
Horizontal.Scrollbar.leftarrow -side left
|
||||
Horizontal.Scrollbar.rightarrow -side right
|
||||
Horizontal.Scrollbar.thumb -expand 1
|
||||
}
|
||||
}
|
||||
|
||||
ttk::style layout TSeparator {
|
||||
TSeparator.separator -sticky nsew
|
||||
}
|
||||
|
||||
ttk::style layout TCombobox {
|
||||
Combobox.field -sticky nsew -children {
|
||||
Combobox.padding -expand 1 -sticky nsew -children {
|
||||
Combobox.textarea -sticky nsew
|
||||
}
|
||||
}
|
||||
null -side right -sticky ns -children {
|
||||
Combobox.arrow -sticky nsew
|
||||
}
|
||||
}
|
||||
|
||||
ttk::style layout TSpinbox {
|
||||
Spinbox.field -sticky nsew -children {
|
||||
Spinbox.padding -expand 1 -sticky nsew -children {
|
||||
Spinbox.textarea -sticky nsew
|
||||
}
|
||||
|
||||
}
|
||||
null -side right -sticky nsew -children {
|
||||
Spinbox.uparrow -side left -sticky nsew
|
||||
Spinbox.downarrow -side right -sticky nsew
|
||||
}
|
||||
}
|
||||
|
||||
ttk::style layout Card.TFrame {
|
||||
Card.field {
|
||||
Card.padding -expand 1
|
||||
}
|
||||
}
|
||||
|
||||
ttk::style layout TLabelframe {
|
||||
Labelframe.border {
|
||||
Labelframe.padding -expand 1 -children {
|
||||
Labelframe.label -side left
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ttk::style layout TNotebook {
|
||||
Notebook.border -children {
|
||||
TNotebook.Tab -expand 1
|
||||
Notebook.client -sticky nsew
|
||||
}
|
||||
}
|
||||
|
||||
ttk::style layout Treeview.Item {
|
||||
Treeitem.padding -sticky nsew -children {
|
||||
Treeitem.image -side left -sticky {}
|
||||
Treeitem.indicator -side left -sticky {}
|
||||
Treeitem.text -side left -sticky {}
|
||||
}
|
||||
}
|
||||
|
||||
# Button
|
||||
ttk::style configure TButton -padding {8 4} -anchor center -foreground $colors(-fg)
|
||||
|
||||
ttk::style map TButton -foreground \
|
||||
[list disabled #7a7a7a \
|
||||
pressed #d0d0d0]
|
||||
|
||||
ttk::style element create Button.button image \
|
||||
[list $images(button-rest) \
|
||||
{selected disabled} $images(button-disabled) \
|
||||
disabled $images(button-disabled) \
|
||||
selected $images(button-rest) \
|
||||
pressed $images(button-pressed) \
|
||||
active $images(button-hover) \
|
||||
] -border 4 -sticky nsew
|
||||
|
||||
# Toolbutton
|
||||
ttk::style configure Toolbutton -padding {8 4} -anchor center
|
||||
|
||||
ttk::style element create Toolbutton.button image \
|
||||
[list $images(empty) \
|
||||
{selected disabled} $images(button-disabled) \
|
||||
selected $images(button-rest) \
|
||||
pressed $images(button-pressed) \
|
||||
active $images(button-hover) \
|
||||
] -border 4 -sticky nsew
|
||||
|
||||
# Menubutton
|
||||
ttk::style configure TMenubutton -padding {8 4 0 4}
|
||||
|
||||
ttk::style element create Menubutton.button \
|
||||
image [list $images(button-rest) \
|
||||
disabled $images(button-disabled) \
|
||||
pressed $images(button-pressed) \
|
||||
active $images(button-hover) \
|
||||
] -border 4 -sticky nsew
|
||||
|
||||
ttk::style element create Menubutton.indicator image $images(arrow-down) -width 28 -sticky {}
|
||||
|
||||
# OptionMenu
|
||||
ttk::style configure TOptionMenu -padding {8 4 0 4}
|
||||
|
||||
ttk::style element create OptionMenu.button \
|
||||
image [list $images(button-rest) \
|
||||
disabled $images(button-disabled) \
|
||||
pressed $images(button-pressed) \
|
||||
active $images(button-hover) \
|
||||
] -border 4 -sticky nsew
|
||||
|
||||
ttk::style element create OptionMenu.indicator image $images(arrow-down) -width 28 -sticky {}
|
||||
|
||||
# Accent.TButton
|
||||
ttk::style configure Accent.TButton -padding {8 4} -anchor center -foreground #000000
|
||||
|
||||
ttk::style map Accent.TButton -foreground \
|
||||
[list pressed #25536a \
|
||||
disabled #a5a5a5]
|
||||
|
||||
ttk::style element create AccentButton.button image \
|
||||
[list $images(button-accent-rest) \
|
||||
{selected disabled} $images(button-accent-disabled) \
|
||||
disabled $images(button-accent-disabled) \
|
||||
selected $images(button-accent-rest) \
|
||||
pressed $images(button-accent-pressed) \
|
||||
active $images(button-accent-hover) \
|
||||
] -border 4 -sticky nsew
|
||||
|
||||
# Titlebar.TButton
|
||||
ttk::style configure Titlebar.TButton -padding {8 4} -anchor center -foreground #ffffff
|
||||
|
||||
ttk::style map Titlebar.TButton -foreground \
|
||||
[list disabled #6f6f6f \
|
||||
pressed #d1d1d1 \
|
||||
active #ffffff]
|
||||
|
||||
ttk::style element create TitlebarButton.button image \
|
||||
[list $images(empty) \
|
||||
disabled $images(empty) \
|
||||
pressed $images(button-titlebar-pressed) \
|
||||
active $images(button-titlebar-hover) \
|
||||
] -border 4 -sticky nsew
|
||||
|
||||
# Close.Titlebar.TButton
|
||||
ttk::style configure Close.Titlebar.TButton -padding {8 4} -anchor center -foreground #ffffff
|
||||
|
||||
ttk::style map Close.Titlebar.TButton -foreground \
|
||||
[list disabled #6f6f6f \
|
||||
pressed #e8bfbb \
|
||||
active #ffffff]
|
||||
|
||||
ttk::style element create CloseButton.button image \
|
||||
[list $images(empty) \
|
||||
disabled $images(empty) \
|
||||
pressed $images(button-close-pressed) \
|
||||
active $images(button-close-hover) \
|
||||
] -border 4 -sticky nsew
|
||||
|
||||
# Checkbutton
|
||||
ttk::style configure TCheckbutton -padding 4
|
||||
|
||||
ttk::style element create Checkbutton.indicator image \
|
||||
[list $images(check-unsel-rest) \
|
||||
{alternate disabled} $images(check-tri-disabled) \
|
||||
{selected disabled} $images(check-disabled) \
|
||||
disabled $images(check-unsel-disabled) \
|
||||
{pressed alternate} $images(check-tri-hover) \
|
||||
{active alternate} $images(check-tri-hover) \
|
||||
alternate $images(check-tri-rest) \
|
||||
{pressed selected} $images(check-hover) \
|
||||
{active selected} $images(check-hover) \
|
||||
selected $images(check-rest) \
|
||||
{pressed !selected} $images(check-unsel-pressed) \
|
||||
active $images(check-unsel-hover) \
|
||||
] -width 26 -sticky w
|
||||
|
||||
# Switch.TCheckbutton
|
||||
ttk::style element create Switch.indicator image \
|
||||
[list $images(switch-off-rest) \
|
||||
{selected disabled} $images(switch-on-disabled) \
|
||||
disabled $images(switch-off-disabled) \
|
||||
{pressed selected} $images(switch-on-pressed) \
|
||||
{active selected} $images(switch-on-hover) \
|
||||
selected $images(switch-on-rest) \
|
||||
{pressed !selected} $images(switch-off-pressed) \
|
||||
active $images(switch-off-hover) \
|
||||
] -width 46 -sticky w
|
||||
|
||||
# Toggle.TButton
|
||||
ttk::style configure Toggle.TButton -padding {8 4 8 4} -anchor center -foreground $colors(-fg)
|
||||
|
||||
ttk::style map Toggle.TButton -foreground \
|
||||
[list {selected disabled} #a5a5a5 \
|
||||
{selected pressed} #d0d0d0 \
|
||||
selected #000000 \
|
||||
pressed #25536a \
|
||||
disabled #7a7a7a
|
||||
]
|
||||
|
||||
ttk::style element create ToggleButton.button image \
|
||||
[list $images(button-rest) \
|
||||
{selected disabled} $images(button-accent-disabled) \
|
||||
disabled $images(button-disabled) \
|
||||
{pressed selected} $images(button-rest) \
|
||||
{active selected} $images(button-accent-hover) \
|
||||
selected $images(button-accent-rest) \
|
||||
{pressed !selected} $images(button-accent-rest) \
|
||||
active $images(button-hover) \
|
||||
] -border 4 -sticky nsew
|
||||
|
||||
# Radiobutton
|
||||
ttk::style configure TRadiobutton -padding 4
|
||||
|
||||
ttk::style element create Radiobutton.indicator image \
|
||||
[list $images(radio-unsel-rest) \
|
||||
{selected disabled} $images(radio-disabled) \
|
||||
disabled $images(radio-unsel-disabled) \
|
||||
{pressed selected} $images(radio-pressed) \
|
||||
{active selected} $images(radio-hover) \
|
||||
selected $images(radio-rest) \
|
||||
{pressed !selected} $images(radio-unsel-pressed) \
|
||||
active $images(radio-unsel-hover) \
|
||||
] -width 26 -sticky w
|
||||
|
||||
# Scrollbar
|
||||
ttk::style element create Horizontal.Scrollbar.trough image $images(scroll-hor-trough) -sticky ew -border 6
|
||||
ttk::style element create Horizontal.Scrollbar.thumb image $images(scroll-hor-thumb) -sticky ew -border 3
|
||||
|
||||
ttk::style element create Horizontal.Scrollbar.rightarrow image $images(scroll-right) -sticky {} -width 12
|
||||
ttk::style element create Horizontal.Scrollbar.leftarrow image $images(scroll-left) -sticky {} -width 12
|
||||
|
||||
ttk::style element create Vertical.Scrollbar.trough image $images(scroll-vert-trough) -sticky ns -border 6
|
||||
ttk::style element create Vertical.Scrollbar.thumb image $images(scroll-vert-thumb) -sticky ns -border 3
|
||||
|
||||
ttk::style element create Vertical.Scrollbar.uparrow image $images(scroll-up) -sticky {} -height 12
|
||||
ttk::style element create Vertical.Scrollbar.downarrow image $images(scroll-down) -sticky {} -height 12
|
||||
|
||||
# Scale
|
||||
ttk::style element create Horizontal.Scale.trough image $images(scale-trough-hor) \
|
||||
-border 5 -padding 0
|
||||
|
||||
ttk::style element create Vertical.Scale.trough image $images(scale-trough-vert) \
|
||||
-border 5 -padding 0
|
||||
|
||||
ttk::style element create Scale.slider \
|
||||
image [list $images(scale-thumb-rest) \
|
||||
disabled $images(scale-thumb-disabled) \
|
||||
pressed $images(scale-thumb-pressed) \
|
||||
active $images(scale-thumb-hover) \
|
||||
] -sticky {}
|
||||
|
||||
# Progressbar
|
||||
ttk::style element create Horizontal.Progressbar.trough image $images(progress-trough-hor) \
|
||||
-border 1 -sticky ew
|
||||
|
||||
ttk::style element create Horizontal.Progressbar.pbar image $images(progress-pbar-hor) \
|
||||
-border 2 -sticky ew
|
||||
|
||||
ttk::style element create Vertical.Progressbar.trough image $images(progress-trough-vert) \
|
||||
-border 1 -sticky ns
|
||||
|
||||
ttk::style element create Vertical.Progressbar.pbar image $images(progress-pbar-vert) \
|
||||
-border 2 -sticky ns
|
||||
|
||||
# Entry
|
||||
ttk::style configure TEntry -foreground $colors(-fg)
|
||||
|
||||
ttk::style map TEntry -foreground \
|
||||
[list disabled #757575 \
|
||||
pressed #cfcfcf
|
||||
]
|
||||
|
||||
ttk::style element create Entry.field \
|
||||
image [list $images(entry-rest) \
|
||||
{focus hover !invalid} $images(entry-focus) \
|
||||
invalid $images(entry-invalid) \
|
||||
disabled $images(entry-disabled) \
|
||||
{focus !invalid} $images(entry-focus) \
|
||||
hover $images(entry-hover) \
|
||||
] -border 5 -padding 8 -sticky nsew
|
||||
|
||||
# Combobox
|
||||
ttk::style configure TCombobox -foreground $colors(-fg)
|
||||
|
||||
ttk::style map TCombobox -foreground \
|
||||
[list disabled #757575 \
|
||||
pressed #cfcfcf
|
||||
]
|
||||
|
||||
ttk::style configure ComboboxPopdownFrame -borderwidth 1 -relief solid
|
||||
|
||||
ttk::style map TCombobox -selectbackground [list \
|
||||
{readonly hover} $colors(-selectbg) \
|
||||
{readonly focus} $colors(-selectbg) \
|
||||
] -selectforeground [list \
|
||||
{readonly hover} $colors(-selectfg) \
|
||||
{readonly focus} $colors(-selectfg) \
|
||||
]
|
||||
|
||||
ttk::style element create Combobox.field \
|
||||
image [list $images(entry-rest) \
|
||||
{readonly disabled} $images(button-disabled) \
|
||||
{readonly pressed} $images(button-pressed) \
|
||||
{readonly hover} $images(button-hover) \
|
||||
readonly $images(button-rest) \
|
||||
invalid $images(entry-invalid) \
|
||||
disabled $images(entry-disabled) \
|
||||
focus $images(entry-focus) \
|
||||
hover $images(entry-hover) \
|
||||
] -border 5 -padding {8 8 28 8}
|
||||
|
||||
ttk::style element create Combobox.arrow image $images(arrow-down) -width 35 -sticky {}
|
||||
|
||||
# Spinbox
|
||||
ttk::style configure TSpinbox -foreground $colors(-fg)
|
||||
|
||||
ttk::style map TSpinbox -foreground \
|
||||
[list disabled #757575 \
|
||||
pressed #cfcfcf
|
||||
]
|
||||
|
||||
ttk::style element create Spinbox.field \
|
||||
image [list $images(entry-rest) \
|
||||
invalid $images(entry-invalid) \
|
||||
disabled $images(entry-disabled) \
|
||||
focus $images(entry-focus) \
|
||||
hover $images(entry-hover) \
|
||||
] -border 5 -padding {8 8 54 8} -sticky nsew
|
||||
|
||||
ttk::style element create Spinbox.uparrow image $images(arrow-up) -width 35 -sticky {}
|
||||
ttk::style element create Spinbox.downarrow image $images(arrow-down) -width 35 -sticky {}
|
||||
|
||||
# Sizegrip
|
||||
ttk::style element create Sizegrip.sizegrip image $images(sizegrip) \
|
||||
-sticky nsew
|
||||
|
||||
# Separator
|
||||
ttk::style element create TSeparator.separator image $images(separator)
|
||||
|
||||
# Card
|
||||
ttk::style element create Card.field image $images(card) \
|
||||
-border 10 -padding 4 -sticky nsew
|
||||
|
||||
# Labelframe
|
||||
ttk::style element create Labelframe.border image $images(card) \
|
||||
-border 5 -padding 4 -sticky nsew
|
||||
|
||||
# Notebook
|
||||
ttk::style configure TNotebook -padding 1
|
||||
|
||||
ttk::style element create Notebook.border \
|
||||
image $images(notebook-border) -border 5 -padding 5
|
||||
|
||||
ttk::style element create Notebook.client image $images(notebook)
|
||||
|
||||
ttk::style element create Notebook.tab \
|
||||
image [list $images(tab-rest) \
|
||||
selected $images(tab-selected) \
|
||||
active $images(tab-hover) \
|
||||
] -border 13 -padding {16 14 16 6} -height 32
|
||||
|
||||
# Treeview
|
||||
ttk::style element create Treeview.field image $images(card) \
|
||||
-border 5
|
||||
|
||||
ttk::style element create Treeheading.cell \
|
||||
image [list $images(treeheading-rest) \
|
||||
pressed $images(treeheading-pressed) \
|
||||
active $images(treeheading-hover)
|
||||
] -border 5 -padding 15 -sticky nsew
|
||||
|
||||
ttk::style element create Treeitem.indicator \
|
||||
image [list $images(arrow-right) \
|
||||
user2 $images(empty) \
|
||||
user1 $images(arrow-down) \
|
||||
] -width 26 -sticky {}
|
||||
|
||||
ttk::style configure Treeview -background $colors(-bg) -rowheight [expr {[font metrics font -linespace] + 2}]
|
||||
ttk::style map Treeview \
|
||||
-background [list selected #292929] \
|
||||
-foreground [list selected $colors(-selectfg)]
|
||||
|
||||
# Panedwindow
|
||||
# Insane hack to remove clam's ugly sash
|
||||
ttk::style configure Sash -gripcount 0
|
||||
}
|
||||
}
|
BIN
vmcompact/sun-valley-theme/theme/dark/arrow-down.png
Normal file
After Width: | Height: | Size: 270 B |
BIN
vmcompact/sun-valley-theme/theme/dark/arrow-right.png
Normal file
After Width: | Height: | Size: 261 B |
BIN
vmcompact/sun-valley-theme/theme/dark/arrow-up.png
Normal file
After Width: | Height: | Size: 274 B |
BIN
vmcompact/sun-valley-theme/theme/dark/button-accent-disabled.png
Normal file
After Width: | Height: | Size: 262 B |
BIN
vmcompact/sun-valley-theme/theme/dark/button-accent-hover.png
Normal file
After Width: | Height: | Size: 373 B |
BIN
vmcompact/sun-valley-theme/theme/dark/button-accent-pressed.png
Normal file
After Width: | Height: | Size: 363 B |
BIN
vmcompact/sun-valley-theme/theme/dark/button-accent-rest.png
Normal file
After Width: | Height: | Size: 377 B |
BIN
vmcompact/sun-valley-theme/theme/dark/button-close-hover.png
Normal file
After Width: | Height: | Size: 274 B |
BIN
vmcompact/sun-valley-theme/theme/dark/button-close-pressed.png
Normal file
After Width: | Height: | Size: 274 B |
BIN
vmcompact/sun-valley-theme/theme/dark/button-disabled.png
Normal file
After Width: | Height: | Size: 301 B |
BIN
vmcompact/sun-valley-theme/theme/dark/button-hover.png
Normal file
After Width: | Height: | Size: 276 B |
BIN
vmcompact/sun-valley-theme/theme/dark/button-pressed.png
Normal file
After Width: | Height: | Size: 288 B |
BIN
vmcompact/sun-valley-theme/theme/dark/button-rest.png
Normal file
After Width: | Height: | Size: 301 B |
BIN
vmcompact/sun-valley-theme/theme/dark/button-titlebar-hover.png
Normal file
After Width: | Height: | Size: 245 B |
After Width: | Height: | Size: 238 B |
BIN
vmcompact/sun-valley-theme/theme/dark/card.png
Normal file
After Width: | Height: | Size: 386 B |
BIN
vmcompact/sun-valley-theme/theme/dark/check-disabled.png
Normal file
After Width: | Height: | Size: 383 B |
BIN
vmcompact/sun-valley-theme/theme/dark/check-hover.png
Normal file
After Width: | Height: | Size: 474 B |
BIN
vmcompact/sun-valley-theme/theme/dark/check-pressed.png
Normal file
After Width: | Height: | Size: 460 B |
BIN
vmcompact/sun-valley-theme/theme/dark/check-rest.png
Normal file
After Width: | Height: | Size: 475 B |
BIN
vmcompact/sun-valley-theme/theme/dark/check-tri-disabled.png
Normal file
After Width: | Height: | Size: 294 B |
BIN
vmcompact/sun-valley-theme/theme/dark/check-tri-hover.png
Normal file
After Width: | Height: | Size: 362 B |
BIN
vmcompact/sun-valley-theme/theme/dark/check-tri-pressed.png
Normal file
After Width: | Height: | Size: 358 B |
BIN
vmcompact/sun-valley-theme/theme/dark/check-tri-rest.png
Normal file
After Width: | Height: | Size: 363 B |
BIN
vmcompact/sun-valley-theme/theme/dark/check-unsel-disabled.png
Normal file
After Width: | Height: | Size: 312 B |
BIN
vmcompact/sun-valley-theme/theme/dark/check-unsel-hover.png
Normal file
After Width: | Height: | Size: 353 B |
BIN
vmcompact/sun-valley-theme/theme/dark/check-unsel-pressed.png
Normal file
After Width: | Height: | Size: 302 B |
BIN
vmcompact/sun-valley-theme/theme/dark/check-unsel-rest.png
Normal file
After Width: | Height: | Size: 353 B |
BIN
vmcompact/sun-valley-theme/theme/dark/empty.png
Normal file
After Width: | Height: | Size: 129 B |
BIN
vmcompact/sun-valley-theme/theme/dark/entry-disabled.png
Normal file
After Width: | Height: | Size: 273 B |
BIN
vmcompact/sun-valley-theme/theme/dark/entry-focus.png
Normal file
After Width: | Height: | Size: 335 B |
BIN
vmcompact/sun-valley-theme/theme/dark/entry-hover.png
Normal file
After Width: | Height: | Size: 269 B |
BIN
vmcompact/sun-valley-theme/theme/dark/entry-invalid.png
Normal file
After Width: | Height: | Size: 324 B |
BIN
vmcompact/sun-valley-theme/theme/dark/entry-rest.png
Normal file
After Width: | Height: | Size: 297 B |
BIN
vmcompact/sun-valley-theme/theme/dark/notebook-border.png
Normal file
After Width: | Height: | Size: 337 B |
BIN
vmcompact/sun-valley-theme/theme/dark/notebook.png
Normal file
After Width: | Height: | Size: 186 B |
BIN
vmcompact/sun-valley-theme/theme/dark/progress-pbar-hor.png
Normal file
After Width: | Height: | Size: 193 B |
BIN
vmcompact/sun-valley-theme/theme/dark/progress-pbar-vert.png
Normal file
After Width: | Height: | Size: 214 B |
BIN
vmcompact/sun-valley-theme/theme/dark/progress-trough-hor.png
Normal file
After Width: | Height: | Size: 157 B |
BIN
vmcompact/sun-valley-theme/theme/dark/progress-trough-vert.png
Normal file
After Width: | Height: | Size: 160 B |
BIN
vmcompact/sun-valley-theme/theme/dark/radio-disabled.png
Normal file
After Width: | Height: | Size: 553 B |
BIN
vmcompact/sun-valley-theme/theme/dark/radio-hover.png
Normal file
After Width: | Height: | Size: 853 B |
BIN
vmcompact/sun-valley-theme/theme/dark/radio-pressed.png
Normal file
After Width: | Height: | Size: 786 B |
BIN
vmcompact/sun-valley-theme/theme/dark/radio-rest.png
Normal file
After Width: | Height: | Size: 830 B |
BIN
vmcompact/sun-valley-theme/theme/dark/radio-unsel-disabled.png
Normal file
After Width: | Height: | Size: 552 B |
BIN
vmcompact/sun-valley-theme/theme/dark/radio-unsel-hover.png
Normal file
After Width: | Height: | Size: 602 B |
BIN
vmcompact/sun-valley-theme/theme/dark/radio-unsel-pressed.png
Normal file
After Width: | Height: | Size: 616 B |
BIN
vmcompact/sun-valley-theme/theme/dark/radio-unsel-rest.png
Normal file
After Width: | Height: | Size: 621 B |
BIN
vmcompact/sun-valley-theme/theme/dark/scale-thumb-disabled.png
Normal file
After Width: | Height: | Size: 724 B |
BIN
vmcompact/sun-valley-theme/theme/dark/scale-thumb-hover.png
Normal file
After Width: | Height: | Size: 808 B |
BIN
vmcompact/sun-valley-theme/theme/dark/scale-thumb-pressed.png
Normal file
After Width: | Height: | Size: 735 B |
BIN
vmcompact/sun-valley-theme/theme/dark/scale-thumb-rest.png
Normal file
After Width: | Height: | Size: 771 B |
BIN
vmcompact/sun-valley-theme/theme/dark/scale-trough-hor.png
Normal file
After Width: | Height: | Size: 216 B |
BIN
vmcompact/sun-valley-theme/theme/dark/scale-trough-vert.png
Normal file
After Width: | Height: | Size: 215 B |
BIN
vmcompact/sun-valley-theme/theme/dark/scroll-down.png
Normal file
After Width: | Height: | Size: 226 B |
BIN
vmcompact/sun-valley-theme/theme/dark/scroll-hor-thumb.png
Normal file
After Width: | Height: | Size: 254 B |
BIN
vmcompact/sun-valley-theme/theme/dark/scroll-hor-trough.png
Normal file
After Width: | Height: | Size: 338 B |
BIN
vmcompact/sun-valley-theme/theme/dark/scroll-left.png
Normal file
After Width: | Height: | Size: 233 B |
BIN
vmcompact/sun-valley-theme/theme/dark/scroll-right.png
Normal file
After Width: | Height: | Size: 227 B |
BIN
vmcompact/sun-valley-theme/theme/dark/scroll-up.png
Normal file
After Width: | Height: | Size: 236 B |
BIN
vmcompact/sun-valley-theme/theme/dark/scroll-vert-thumb.png
Normal file
After Width: | Height: | Size: 264 B |
BIN
vmcompact/sun-valley-theme/theme/dark/scroll-vert-trough.png
Normal file
After Width: | Height: | Size: 343 B |
BIN
vmcompact/sun-valley-theme/theme/dark/separator.png
Normal file
After Width: | Height: | Size: 128 B |
BIN
vmcompact/sun-valley-theme/theme/dark/sizegrip.png
Normal file
After Width: | Height: | Size: 276 B |
BIN
vmcompact/sun-valley-theme/theme/dark/switch-off-disabled.png
Normal file
After Width: | Height: | Size: 733 B |
BIN
vmcompact/sun-valley-theme/theme/dark/switch-off-hover.png
Normal file
After Width: | Height: | Size: 945 B |
BIN
vmcompact/sun-valley-theme/theme/dark/switch-off-pressed.png
Normal file
After Width: | Height: | Size: 963 B |
BIN
vmcompact/sun-valley-theme/theme/dark/switch-off-rest.png
Normal file
After Width: | Height: | Size: 895 B |
BIN
vmcompact/sun-valley-theme/theme/dark/switch-on-disabled.png
Normal file
After Width: | Height: | Size: 623 B |
BIN
vmcompact/sun-valley-theme/theme/dark/switch-on-hover.png
Normal file
After Width: | Height: | Size: 927 B |
BIN
vmcompact/sun-valley-theme/theme/dark/switch-on-pressed.png
Normal file
After Width: | Height: | Size: 936 B |
BIN
vmcompact/sun-valley-theme/theme/dark/switch-on-rest.png
Normal file
After Width: | Height: | Size: 859 B |
BIN
vmcompact/sun-valley-theme/theme/dark/tab-hover.png
Normal file
After Width: | Height: | Size: 265 B |
BIN
vmcompact/sun-valley-theme/theme/dark/tab-rest.png
Normal file
After Width: | Height: | Size: 164 B |
BIN
vmcompact/sun-valley-theme/theme/dark/tab-selected.png
Normal file
After Width: | Height: | Size: 319 B |
BIN
vmcompact/sun-valley-theme/theme/dark/treeheading-hover.png
Normal file
After Width: | Height: | Size: 295 B |
BIN
vmcompact/sun-valley-theme/theme/dark/treeheading-pressed.png
Normal file
After Width: | Height: | Size: 317 B |