mirror of
https://github.com/jeertmans/manim-slides.git
synced 2025-05-19 03:26:17 +08:00
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:
@ -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}`")
|
||||
|
Reference in New Issue
Block a user