mirror of
https://github.com/deepfakes/faceswap
synced 2025-06-07 10:43:27 -04:00
457 lines
17 KiB
Python
457 lines
17 KiB
Python
#!/usr/bin python3
|
|
""" Global configuration optiopns for the Faceswap GUI """
|
|
from __future__ import annotations
|
|
import logging
|
|
import os
|
|
import sys
|
|
import tkinter as tk
|
|
import typing as T
|
|
|
|
from dataclasses import dataclass, field
|
|
|
|
from lib.gui._config import Config as UserConfig
|
|
from lib.gui.project import Project, Tasks
|
|
from lib.gui.theme import Style
|
|
from .file_handler import FileHandler
|
|
|
|
if T.TYPE_CHECKING:
|
|
from lib.gui.options import CliOptions
|
|
from lib.gui.custom_widgets import StatusBar
|
|
from lib.gui.command import CommandNotebook
|
|
from lib.gui.command import ToolsNotebook
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
PATHCACHE = os.path.join(os.path.realpath(os.path.dirname(sys.argv[0])), "lib", "gui", ".cache")
|
|
_CONFIG: Config | None = None
|
|
|
|
|
|
def initialize_config(root: tk.Tk,
|
|
cli_opts: CliOptions | None,
|
|
statusbar: StatusBar | None) -> Config | None:
|
|
""" Initialize the GUI Master :class:`Config` and add to global constant.
|
|
|
|
This should only be called once on first GUI startup. Future access to :class:`Config`
|
|
should only be executed through :func:`get_config`.
|
|
|
|
Parameters
|
|
----------
|
|
root: :class:`tkinter.Tk`
|
|
The root Tkinter object
|
|
cli_opts: :class:`lib.gui.options.CliOptions` or ``None``
|
|
The command line options object. Must be provided for main GUI. Must be ``None`` for tools
|
|
statusbar: :class:`lib.gui.custom_widgets.StatusBar` or ``None``
|
|
The GUI Status bar. Must be provided for main GUI. Must be ``None`` for tools
|
|
|
|
Returns
|
|
-------
|
|
:class:`Config` or ``None``
|
|
``None`` if the config has already been initialized otherwise the global configuration
|
|
options
|
|
"""
|
|
global _CONFIG # pylint: disable=global-statement
|
|
if _CONFIG is not None:
|
|
return None
|
|
logger.debug("Initializing config: (root: %s, cli_opts: %s, "
|
|
"statusbar: %s)", root, cli_opts, statusbar)
|
|
_CONFIG = Config(root, cli_opts, statusbar)
|
|
return _CONFIG
|
|
|
|
|
|
def get_config() -> "Config":
|
|
""" Get the Master GUI configuration.
|
|
|
|
Returns
|
|
-------
|
|
:class:`Config`
|
|
The Master GUI Config
|
|
"""
|
|
assert _CONFIG is not None
|
|
return _CONFIG
|
|
|
|
|
|
class GlobalVariables():
|
|
""" Global tkinter variables accessible from all parts of the GUI. Should only be accessed from
|
|
:attr:`get_config().tk_vars` """
|
|
def __init__(self) -> None:
|
|
logger.debug("Initializing %s", self.__class__.__name__)
|
|
self._display = tk.StringVar()
|
|
self._running_task = tk.BooleanVar()
|
|
self._is_training = tk.BooleanVar()
|
|
self._action_command = tk.StringVar()
|
|
self._generate_command = tk.StringVar()
|
|
self._console_clear = tk.BooleanVar()
|
|
self._refresh_graph = tk.BooleanVar()
|
|
self._analysis_folder = tk.StringVar()
|
|
|
|
self._initialize_variables()
|
|
logger.debug("Initialized %s", self.__class__.__name__)
|
|
|
|
@property
|
|
def display(self) -> tk.StringVar:
|
|
""" :class:`tkinter.StringVar`: The current Faceswap command running """
|
|
return self._display
|
|
|
|
@property
|
|
def running_task(self) -> tk.BooleanVar:
|
|
""" :class:`tkinter.BooleanVar`: ``True`` if a Faceswap task is running otherwise
|
|
``False`` """
|
|
return self._running_task
|
|
|
|
@property
|
|
def is_training(self) -> tk.BooleanVar:
|
|
""" :class:`tkinter.BooleanVar`: ``True`` if Faceswap is currently training otherwise
|
|
``False`` """
|
|
return self._is_training
|
|
|
|
@property
|
|
def action_command(self) -> tk.StringVar:
|
|
""" :class:`tkinter.StringVar`: The command line action to perform """
|
|
return self._action_command
|
|
|
|
@property
|
|
def generate_command(self) -> tk.StringVar:
|
|
""" :class:`tkinter.StringVar`: The command line action to generate """
|
|
return self._generate_command
|
|
|
|
@property
|
|
def console_clear(self) -> tk.BooleanVar:
|
|
""" :class:`tkinter.BooleanVar`: ``True`` if the console should be cleared otherwise
|
|
``False`` """
|
|
return self._console_clear
|
|
|
|
@property
|
|
def refresh_graph(self) -> tk.BooleanVar:
|
|
""" :class:`tkinter.BooleanVar`: ``True`` if the training graph should be refreshed
|
|
otherwise ``False`` """
|
|
return self._refresh_graph
|
|
|
|
@property
|
|
def analysis_folder(self) -> tk.StringVar:
|
|
""" :class:`tkinter.StringVar`: Full path the analysis folder"""
|
|
return self._analysis_folder
|
|
|
|
def _initialize_variables(self) -> None:
|
|
""" Initialize the default variable values"""
|
|
self._display.set("")
|
|
self._running_task.set(False)
|
|
self._is_training.set(False)
|
|
self._action_command.set("")
|
|
self._generate_command.set("")
|
|
self._console_clear.set(False)
|
|
self._refresh_graph.set(False)
|
|
self._analysis_folder.set("")
|
|
|
|
|
|
@dataclass
|
|
class _GuiObjects:
|
|
""" Data class for commonly accessed GUI Objects """
|
|
cli_opts: CliOptions | None
|
|
tk_vars: GlobalVariables
|
|
project: Project
|
|
tasks: Tasks
|
|
status_bar: StatusBar | None
|
|
default_options: dict[str, dict[str, T.Any]] = field(default_factory=dict)
|
|
command_notebook: CommandNotebook | None = None
|
|
|
|
|
|
class Config():
|
|
""" The centralized configuration class for holding items that should be made available to all
|
|
parts of the GUI.
|
|
|
|
This class should be initialized on GUI startup through :func:`initialize_config`. Any further
|
|
access to this class should be through :func:`get_config`.
|
|
|
|
Parameters
|
|
----------
|
|
root: :class:`tkinter.Tk`
|
|
The root Tkinter object
|
|
cli_opts: :class:`lib.gui.options.CliOptions` or ``None``
|
|
The command line options object. Must be provided for main GUI. Must be ``None`` for tools
|
|
statusbar: :class:`lib.gui.custom_widgets.StatusBar` or ``None``
|
|
The GUI Status bar. Must be provided for main GUI. Must be ``None`` for tools
|
|
"""
|
|
def __init__(self,
|
|
root: tk.Tk,
|
|
cli_opts: CliOptions | None,
|
|
statusbar: StatusBar | None) -> None:
|
|
logger.debug("Initializing %s: (root %s, cli_opts: %s, statusbar: %s)",
|
|
self.__class__.__name__, root, cli_opts, statusbar)
|
|
self._default_font = T.cast(dict,
|
|
tk.font.nametofont("TkDefaultFont").configure())["family"]
|
|
self._constants = {"root": root,
|
|
"scaling_factor": self._get_scaling(root),
|
|
"default_font": self._default_font}
|
|
self._gui_objects = _GuiObjects(
|
|
cli_opts=cli_opts,
|
|
tk_vars=GlobalVariables(),
|
|
project=Project(self, FileHandler),
|
|
tasks=Tasks(self, FileHandler),
|
|
status_bar=statusbar)
|
|
|
|
self._user_config = UserConfig(None)
|
|
self._style = Style(self.default_font, root, PATHCACHE)
|
|
self._user_theme = self._style.user_theme
|
|
logger.debug("Initialized %s", self.__class__.__name__)
|
|
|
|
# Constants
|
|
@property
|
|
def root(self) -> tk.Tk:
|
|
""" :class:`tkinter.Tk`: The root tkinter window. """
|
|
return self._constants["root"]
|
|
|
|
@property
|
|
def scaling_factor(self) -> float:
|
|
""" float: The scaling factor for current display. """
|
|
return self._constants["scaling_factor"]
|
|
|
|
@property
|
|
def pathcache(self) -> str:
|
|
""" str: The path to the GUI cache folder """
|
|
return PATHCACHE
|
|
|
|
# GUI Objects
|
|
@property
|
|
def cli_opts(self) -> CliOptions:
|
|
""" :class:`lib.gui.options.CliOptions`: The command line options for this GUI Session. """
|
|
# This should only be None when a separate tool (not main GUI) is used, at which point
|
|
# cli_opts do not exist
|
|
assert self._gui_objects.cli_opts is not None
|
|
return self._gui_objects.cli_opts
|
|
|
|
@property
|
|
def tk_vars(self) -> GlobalVariables:
|
|
""" dict: The global tkinter variables. """
|
|
return self._gui_objects.tk_vars
|
|
|
|
@property
|
|
def project(self) -> Project:
|
|
""" :class:`lib.gui.project.Project`: The project session handler. """
|
|
return self._gui_objects.project
|
|
|
|
@property
|
|
def tasks(self) -> Tasks:
|
|
""" :class:`lib.gui.project.Tasks`: The session tasks handler. """
|
|
return self._gui_objects.tasks
|
|
|
|
@property
|
|
def default_options(self) -> dict[str, dict[str, T.Any]]:
|
|
""" dict: The default options for all tabs """
|
|
return self._gui_objects.default_options
|
|
|
|
@property
|
|
def statusbar(self) -> StatusBar:
|
|
""" :class:`lib.gui.custom_widgets.StatusBar`: The GUI StatusBar
|
|
:class:`tkinter.ttk.Frame`. """
|
|
# This should only be None when a separate tool (not main GUI) is used, at which point
|
|
# this statusbar does not exist
|
|
assert self._gui_objects.status_bar is not None
|
|
return self._gui_objects.status_bar
|
|
|
|
@property
|
|
def command_notebook(self) -> CommandNotebook | None:
|
|
""" :class:`lib.gui.command.CommandNotebook`: The main Faceswap Command Notebook. """
|
|
return self._gui_objects.command_notebook
|
|
|
|
# Convenience GUI Objects
|
|
@property
|
|
def tools_notebook(self) -> ToolsNotebook:
|
|
""" :class:`lib.gui.command.ToolsNotebook`: The Faceswap Tools sub-Notebook. """
|
|
assert self.command_notebook is not None
|
|
return self.command_notebook.tools_notebook
|
|
|
|
@property
|
|
def modified_vars(self) -> dict[str, tk.BooleanVar]:
|
|
""" dict: The command notebook modified tkinter variables. """
|
|
assert self.command_notebook is not None
|
|
return self.command_notebook.modified_vars
|
|
|
|
@property
|
|
def _command_tabs(self) -> dict[str, int]:
|
|
""" dict: Command tab titles with their IDs. """
|
|
assert self.command_notebook is not None
|
|
return self.command_notebook.tab_names
|
|
|
|
@property
|
|
def _tools_tabs(self) -> dict[str, int]:
|
|
""" dict: Tools command tab titles with their IDs. """
|
|
assert self.command_notebook is not None
|
|
return self.command_notebook.tools_tab_names
|
|
|
|
# Config
|
|
@property
|
|
def user_config(self) -> UserConfig:
|
|
""" dict: The GUI config in dict form. """
|
|
return self._user_config
|
|
|
|
@property
|
|
def user_config_dict(self) -> dict[str, T.Any]: # TODO Dataclass
|
|
""" dict: The GUI config in dict form. """
|
|
return self._user_config.config_dict
|
|
|
|
@property
|
|
def user_theme(self) -> dict[str, T.Any]: # TODO Dataclass
|
|
""" dict: The GUI theme selection options. """
|
|
return self._user_theme
|
|
|
|
@property
|
|
def default_font(self) -> tuple[str, int]:
|
|
""" tuple: The selected font as configured in user settings. First item is the font (`str`)
|
|
second item the font size (`int`). """
|
|
font = self.user_config_dict["font"]
|
|
font = self._default_font if font == "default" else font
|
|
return (font, self.user_config_dict["font_size"])
|
|
|
|
@staticmethod
|
|
def _get_scaling(root) -> float:
|
|
""" Get the display DPI.
|
|
|
|
Returns
|
|
-------
|
|
float:
|
|
The scaling factor
|
|
"""
|
|
dpi = root.winfo_fpixels("1i")
|
|
scaling = dpi / 72.0
|
|
logger.debug("dpi: %s, scaling: %s'", dpi, scaling)
|
|
return scaling
|
|
|
|
def set_default_options(self) -> None:
|
|
""" Set the default options for :mod:`lib.gui.projects`
|
|
|
|
The Default GUI options are stored on Faceswap startup.
|
|
|
|
Exposed as the :attr:`_default_opts` for a project cannot be set until after the main
|
|
Command Tabs have been loaded.
|
|
"""
|
|
default = self.cli_opts.get_option_values()
|
|
logger.debug(default)
|
|
self._gui_objects.default_options = default
|
|
self.project.set_default_options()
|
|
|
|
def set_command_notebook(self, notebook: CommandNotebook) -> None:
|
|
""" Set the command notebook to the :attr:`command_notebook` attribute
|
|
and enable the modified callback for :attr:`project`.
|
|
|
|
Parameters
|
|
----------
|
|
notebook: :class:`lib.gui.command.CommandNotebook`
|
|
The main command notebook for the Faceswap GUI
|
|
"""
|
|
logger.debug("Setting commane notebook: %s", notebook)
|
|
self._gui_objects.command_notebook = notebook
|
|
self.project.set_modified_callback()
|
|
|
|
def set_active_tab_by_name(self, name: str) -> None:
|
|
""" Sets the :attr:`command_notebook` or :attr:`tools_notebook` to active based on given
|
|
name.
|
|
|
|
Parameters
|
|
----------
|
|
name: str
|
|
The name of the tab to set active
|
|
"""
|
|
assert self.command_notebook is not None
|
|
name = name.lower()
|
|
if name in self._command_tabs:
|
|
tab_id = self._command_tabs[name]
|
|
logger.debug("Setting active tab to: (name: %s, id: %s)", name, tab_id)
|
|
self.command_notebook.select(tab_id)
|
|
elif name in self._tools_tabs:
|
|
self.command_notebook.select(self._command_tabs["tools"])
|
|
tab_id = self._tools_tabs[name]
|
|
logger.debug("Setting active Tools tab to: (name: %s, id: %s)", name, tab_id)
|
|
self.tools_notebook.select()
|
|
else:
|
|
logger.debug("Name couldn't be found. Setting to id 0: %s", name)
|
|
self.command_notebook.select(0)
|
|
|
|
def set_modified_true(self, command: str) -> None:
|
|
""" Set the modified variable to ``True`` for the given command in :attr:`modified_vars`.
|
|
|
|
Parameters
|
|
----------
|
|
command: str
|
|
The command to set the modified state to ``True``
|
|
|
|
"""
|
|
tkvar = self.modified_vars.get(command, None)
|
|
if tkvar is None:
|
|
logger.debug("No tkvar for command: '%s'", command)
|
|
return
|
|
tkvar.set(True)
|
|
logger.debug("Set modified var to True for: '%s'", command)
|
|
|
|
def refresh_config(self) -> None:
|
|
""" Reload the user config from file. """
|
|
self._user_config = UserConfig(None)
|
|
|
|
def set_cursor_busy(self, widget: tk.Widget | None = None) -> None:
|
|
""" Set the root or widget cursor to busy.
|
|
|
|
Parameters
|
|
----------
|
|
widget: tkinter object, optional
|
|
The widget to set busy cursor for. If the provided value is ``None`` then sets the
|
|
cursor busy for the whole of the GUI. Default: ``None``.
|
|
"""
|
|
logger.debug("Setting cursor to busy. widget: %s", widget)
|
|
component = self.root if widget is None else widget
|
|
component.config(cursor="watch") # type: ignore
|
|
component.update_idletasks()
|
|
|
|
def set_cursor_default(self, widget: tk.Widget | None = None) -> None:
|
|
""" Set the root or widget cursor to default.
|
|
|
|
Parameters
|
|
----------
|
|
widget: tkinter object, optional
|
|
The widget to set default cursor for. If the provided value is ``None`` then sets the
|
|
cursor busy for the whole of the GUI. Default: ``None``
|
|
"""
|
|
logger.debug("Setting cursor to default. widget: %s", widget)
|
|
component = self.root if widget is None else widget
|
|
component.config(cursor="") # type: ignore
|
|
component.update_idletasks()
|
|
|
|
def set_root_title(self, text: str | None = None) -> None:
|
|
""" Set the main title text for Faceswap.
|
|
|
|
The title will always begin with 'Faceswap.py'. Additional text can be appended.
|
|
|
|
Parameters
|
|
----------
|
|
text: str, optional
|
|
Additional text to be appended to the GUI title bar. Default: ``None``
|
|
"""
|
|
title = "Faceswap.py"
|
|
title += f" - {text}" if text is not None and text else ""
|
|
self.root.title(title)
|
|
|
|
def set_geometry(self, width: int, height: int, fullscreen: bool = False) -> None:
|
|
""" Set the geometry for the root tkinter object.
|
|
|
|
Parameters
|
|
----------
|
|
width: int
|
|
The width to set the window to (prior to scaling)
|
|
height: int
|
|
The height to set the window to (prior to scaling)
|
|
fullscreen: bool, optional
|
|
Whether to set the window to full-screen mode. If ``True`` then :attr:`width` and
|
|
:attr:`height` are ignored. Default: ``False``
|
|
"""
|
|
self.root.tk.call("tk", "scaling", self.scaling_factor)
|
|
if fullscreen:
|
|
initial_dimensions = (self.root.winfo_screenwidth(), self.root.winfo_screenheight())
|
|
else:
|
|
initial_dimensions = (round(width * self.scaling_factor),
|
|
round(height * self.scaling_factor))
|
|
|
|
if fullscreen and sys.platform in ("win32", "darwin"):
|
|
self.root.state('zoomed')
|
|
elif fullscreen:
|
|
self.root.attributes('-zoomed', True)
|
|
else:
|
|
self.root.geometry(f"{str(initial_dimensions[0])}x{str(initial_dimensions[1])}+80+80")
|
|
logger.debug("Geometry: %sx%s", *initial_dimensions)
|