refactor: use PyQT5 for window display (#49)

* wip: use PyQT5 for window display

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* wip: first slide is shown

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* wip: pushing non-working code

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* wip: some logging

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* feat: new configuration wizard working

* fix: prevent key error

* wip: making action work

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* wip: soon done! info + video

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fix: bugs in sleep and exiting

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* try: offscreen

* fix: pop default value if not present

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* feat: add aspect ratio option

* chore: typing wip

* fix: now() function returns seconds, not milliseconds anymore

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
Jérome Eertmans
2022-10-19 11:08:41 +02:00
committed by GitHub
parent bc3d55fce2
commit d717bc651d
10 changed files with 464 additions and 255 deletions

View File

@ -1,39 +1,127 @@
import os
import sys
from functools import partial
from typing import Any
import click
import cv2
import numpy as np
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (
QApplication,
QDialog,
QDialogButtonBox,
QGridLayout,
QLabel,
QMessageBox,
QPushButton,
QVBoxLayout,
QWidget,
)
from .commons import config_options, verbosity_option
from .config import Config
from .defaults import CONFIG_PATH, FONT_ARGS
from .config import Config, Key
from .defaults import CONFIG_PATH
from .manim import logger
WINDOW_NAME = "Manim Slides Configuration Wizard"
WINDOW_SIZE = (120, 620)
WINDOW_NAME: str = "Configuration Wizard"
keymap = {}
for key, value in vars(Qt).items():
if isinstance(value, Qt.Key):
keymap[value] = key.partition("_")[2]
def center_text_horizontally(text, window_size, font_args) -> int:
"""Returns centered position for text to be displayed in current window."""
_, width = window_size
font, scale, _, thickness, _ = font_args
(size_in_pixels, _), _ = cv2.getTextSize(text, font, scale, thickness)
return (width - size_in_pixels) // 2
class KeyInput(QDialog):
def __init__(self) -> None:
super().__init__()
self.key = None
self.layout = QVBoxLayout()
self.setWindowTitle("Keyboard Input")
self.label = QLabel("Press any key to register it")
self.layout.addWidget(self.label)
self.setLayout(self.layout)
def keyPressEvent(self, event: Any) -> None:
self.key = event.key()
self.deleteLater()
event.accept()
def prompt(question: str) -> int:
"""Diplays some question in current window and waits for key press."""
display = np.zeros(WINDOW_SIZE, np.uint8)
class Wizard(QWidget):
def __init__(self, config: Config):
text = "* Manim Slides Wizard *"
text_org = center_text_horizontally(text, WINDOW_SIZE, FONT_ARGS), 33
question_org = center_text_horizontally(question, WINDOW_SIZE, FONT_ARGS), 85
super().__init__()
cv2.putText(display, "* Manim Slides Wizard *", text_org, *FONT_ARGS)
cv2.putText(display, question, question_org, *FONT_ARGS)
self.setWindowTitle(WINDOW_NAME)
self.config = config
cv2.imshow(WINDOW_NAME, display)
return cv2.waitKeyEx(-1)
QBtn = QDialogButtonBox.Save | QDialogButtonBox.Cancel
self.buttonBox = QDialogButtonBox(QBtn)
self.buttonBox.accepted.connect(self.saveConfig)
self.buttonBox.rejected.connect(self.closeWithoutSaving)
self.buttons = []
self.layout = QGridLayout()
for i, (key, value) in enumerate(self.config.dict().items()):
# Create label for key name information
label = QLabel()
key_info = value["name"] or key
label.setText(key_info)
self.layout.addWidget(label, i, 0)
# Create button that will pop-up a dialog and ask to input a new key
value = value["ids"].pop()
button = QPushButton(keymap[value])
button.setToolTip(
f"Click to modify the key associated to action {key_info}"
)
self.buttons.append(button)
button.clicked.connect(
partial(self.openDialog, i, getattr(self.config, key))
)
self.layout.addWidget(button, i, 1)
self.layout.addWidget(self.buttonBox, len(self.buttons), 1)
self.setLayout(self.layout)
def closeWithoutSaving(self) -> None:
logger.debug("Closing configuration wizard without saving")
self.deleteLater()
sys.exit(0)
def closeEvent(self, event: Any) -> None:
self.closeWithoutSaving()
event.accept()
def saveConfig(self) -> None:
try:
Config.parse_obj(self.config.dict())
except ValueError:
msg = QMessageBox()
msg.setIcon(QMessageBox.Critical)
msg.setText("Error")
msg.setInformativeText(
"Two or more actions share a common key: make sure actions have distinct key codes."
)
msg.setWindowTitle("Error: duplicated keys")
msg.exec_()
return
self.deleteLater()
def openDialog(self, button_number: int, key: Key) -> None:
button = self.buttons[button_number]
dialog = KeyInput()
dialog.exec_()
if dialog.key is not None:
key_name = keymap[dialog.key]
key.set_ids(dialog.key)
button.setText(key_name)
@click.command()
@ -69,27 +157,27 @@ def _init(config_path, force, merge, skip_interactive=False):
force = choice == "o"
merge = choice == "m"
if force:
click.secho("Overwriting.")
elif merge:
click.secho("Merging.")
else:
click.secho("Exiting.")
if not force and not merge:
logger.debug("Exiting without doing anything")
sys.exit(0)
config = Config()
if force:
logger.debug(f"Overwriting `{config_path}` if exists")
elif merge:
logger.debug("Merging new config into `{config_path}`")
if not skip_interactive:
if os.path.exists(config_path):
config = Config.parse_file(config_path)
cv2.namedWindow(
WINDOW_NAME,
cv2.WINDOW_GUI_NORMAL | cv2.WINDOW_FREERATIO | cv2.WINDOW_AUTOSIZE,
)
app = QApplication(sys.argv)
window = Wizard(config)
window.show()
app.exec()
prompt("Press any key to continue")
for _, key in config:
key.ids = [prompt(f"Press the {key.name} key")]
config = window.config
if merge:
config = Config.parse_file(config_path).merge_with(config)
@ -97,4 +185,4 @@ def _init(config_path, force, merge, skip_interactive=False):
with open(config_path, "w") as config_file:
config_file.write(config.json(indent=2))
click.echo(f"Configuration file successfully save to `{config_path}`")
click.secho(f"Configuration file successfully saved to `{config_path}`")