cromulant/cromulant/window.py

378 lines
12 KiB
Python

from __future__ import annotations
from typing import Any
from collections.abc import Callable
import signal
from PySide6.QtWidgets import QApplication # type: ignore
from PySide6.QtWidgets import QDialog
from PySide6.QtWidgets import QMainWindow
from PySide6.QtWidgets import QWidget
from PySide6.QtWidgets import QGraphicsScene
from PySide6.QtWidgets import QVBoxLayout
from PySide6.QtWidgets import QLabel
from PySide6.QtWidgets import QPushButton
from PySide6.QtWidgets import QHBoxLayout
from PySide6.QtWidgets import QScrollArea
from PySide6.QtWidgets import QComboBox
from PySide6.QtWidgets import QLayout
from PySide6.QtWidgets import QSizePolicy
from PySide6.QtWidgets import QMessageBox
from PySide6.QtWidgets import QLineEdit
from PySide6.QtGui import QFontDatabase # type: ignore
from PySide6.QtGui import QIcon
from PySide6.QtGui import QKeyEvent
from PySide6.QtCore import Qt # type: ignore
from PySide6.QtCore import QUrl
from PySide6.QtMultimedia import QMediaPlayer # type: ignore
from PySide6.QtMultimedia import QAudioOutput
from .config import Config
from .utils import Utils
class FilterLineEdit(QLineEdit): # type: ignore
def keyPressEvent(self, e: QKeyEvent) -> None:
if e.key() == Qt.Key_Escape:
self.clear()
else:
super().keyPressEvent(e)
class ComboBoxDialog(QDialog): # type: ignore
def __init__(self, message: str, options: list[str], defindex: int) -> None:
super().__init__()
self.setWindowTitle("Select Option")
self.setFixedSize(300, 150) # Set a fixed size for the dialog
self.layout = QVBoxLayout()
self.label = QLabel(message)
self.layout.addWidget(self.label)
self.combo_box = QComboBox()
self.combo_box.addItems(options)
self.combo_box.setCurrentIndex(defindex)
self.layout.addWidget(self.combo_box)
self.button_layout = QHBoxLayout()
self.cancel_button = QPushButton("Cancel")
self.cancel_button.clicked.connect(self.reject)
self.button_layout.addWidget(self.cancel_button)
self.ok_button = QPushButton("OK")
self.ok_button.clicked.connect(self.accept)
self.ok_button.setDefault(True)
self.button_layout.addWidget(self.ok_button)
self.layout.addLayout(self.button_layout)
self.setLayout(self.layout)
def get_selection(self) -> str:
return str(self.combo_box.currentText())
class Window:
app: QApplication
window: QMainWindow
root: QVBoxLayout
view: QVBoxLayout
view_scene: QGraphicsScene
speed: QComboBox
scroll_area: QScrollArea
info: QPushButton
font: str
emoji_font: str
player: QMediaPlayer
audio: QAudioOutput
filter: QLineEdit
@staticmethod
def prepare() -> None:
Window.make()
Window.add_buttons()
Window.add_view()
Window.add_footer()
@staticmethod
def make() -> None:
Window.app = QApplication([])
Window.app.setApplicationName(Config.program)
Window.window = QMainWindow()
Window.window.setWindowTitle(Config.title)
Window.window.resize(Config.width, Config.height)
central_widget = QWidget()
Window.root = QVBoxLayout()
central_widget.setLayout(Window.root)
Window.root.setAlignment(Qt.AlignTop)
Window.window.setCentralWidget(central_widget)
Window.window.setWindowIcon(QIcon(str(Config.icon_path)))
Window.root.setContentsMargins(0, 0, 0, 0)
Window.set_style()
@staticmethod
def set_style() -> None:
font_id = QFontDatabase.addApplicationFont(str(Config.font_path))
emoji_font_id = QFontDatabase.addApplicationFont(str(Config.emoji_font_path))
if font_id != -1:
Window.font = QFontDatabase.applicationFontFamilies(font_id)[0]
if emoji_font_id != -1:
Window.emoji_font = QFontDatabase.applicationFontFamilies(emoji_font_id)[0]
style = f"""
QWidget {{
background-color: {Config.background_color};
color: {Config.text_color};
font-size: {Config.font_size}px;
}}
QMenu {{
background-color: {Config.alt_background_color};
color: {Config.alt_text_color};
border: 2px solid {Config.alt_border_color};
}}
QMenu::item:selected {{
background-color: {Config.alt_hover_background_color};
color: {Config.alt_hover_text_color};
}}
QDialog {{
background-color: {Config.alt_background_color};
color: {Config.alt_text_color};
border: 2px solid {Config.alt_border_color};
}}
QDialog QLabel {{
background-color: {Config.alt_background_color};
color: {Config.alt_text_color};
}}
QDialog QPushButton {{
background-color: {Config.alt_background_color};
color: {Config.alt_text_color};
}}
QDialog QPushButton:hover {{
background-color: {Config.message_box_button_hover_background_color};
color: {Config.message_box_button_hover_text_color};
}}
QComboBox {{
selection-background-color: {Config.alt_hover_background_color};
selection-color: {Config.alt_hover_text_color};
}}
QComboBox QAbstractItemView {{
background-color: {Config.alt_background_color};
color: {Config.alt_text_color};
border: 1px solid {Config.alt_border_color};
padding: 6px;
}}
QScrollBar:vertical {{
border: 0px solid transparent;
background: {Config.background_color};
width: 15px;
margin: 0px 0px 0px 0px;
}}
QScrollBar::handle:vertical {{
background: {Config.scrollbar_handle_color};
min-height: 20px;
}}
QScrollBar::add-line:vertical {{
border: none;
background: none;
}}
QScrollBar::sub-line:vertical {{
border: none;
background: none;
}}
QLineEdit {{
background-color: {Config.input_background_color};
color: {Config.input_text_color};
border: 1px solid {Config.input_border_color};
}}
QFrame#horizontal_line {{
background-color: white;
color: white;
}}
QLabel#menu_label:hover {{
background-color: {Config.alt_hover_background_color};
}}
""".strip()
Window.app.setStyleSheet(style)
Window.app.setFont(Window.font)
@staticmethod
def add_buttons() -> None:
from .game import Game
from .filter import Filter
root = QWidget()
container = QHBoxLayout()
btn_menu = QPushButton("Menu")
btn_menu.setToolTip("The main menu")
btn_menu.clicked.connect(Game.menu)
Window.speed = QComboBox()
tooltip = "The speed of the updates\n"
tooltip += f"Fast: {Utils.get_seconds(Config.loop_delay_fast)}\n"
tooltip += f"Normal: {Utils.get_seconds(Config.loop_delay_normal)}\n"
tooltip += f"Slow: {Utils.get_seconds(Config.loop_delay_slow)}"
Window.speed.setToolTip(tooltip)
Window.speed.addItems(["Fast", "Normal", "Slow", "Paused"])
Window.speed.setCurrentIndex(1)
Window.speed.currentIndexChanged.connect(Game.update_speed)
Window.filter = FilterLineEdit()
Window.filter.setPlaceholderText("Filter")
Window.filter.mousePressEvent = lambda e: Window.to_top()
Window.filter.keyReleaseEvent = lambda e: Filter.filter(e)
container.addWidget(btn_menu, 1)
container.addWidget(Window.speed, 1)
container.addWidget(Window.filter, 1)
root.setLayout(container)
Window.root.addWidget(root)
@staticmethod
def add_view() -> None:
Window.scroll_area = QScrollArea()
Window.scroll_area.setWidgetResizable(True)
container = QWidget()
parent = QVBoxLayout(container)
Window.view = QVBoxLayout()
parent.addLayout(Window.view)
Window.view.setAlignment(Qt.AlignTop)
Window.scroll_area.setWidget(container)
Window.root.addWidget(Window.scroll_area)
@staticmethod
def start() -> None:
signal.signal(signal.SIGINT, signal.SIG_DFL)
Window.window.show()
Window.app.exec()
@staticmethod
def close() -> None:
Window.app.quit()
@staticmethod
def delete_layout(layout: QLayout) -> None:
while layout.count():
item = layout.takeAt(0)
if item.widget():
item.widget().deleteLater()
elif item.layout():
Window.delete_layout(item.layout())
layout.deleteLater()
@staticmethod
def expand(widget: QWidget) -> None:
widget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred)
@staticmethod
def expand_2(widget: QWidget) -> None:
widget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
@staticmethod
def clear_view() -> None:
while Window.view.count():
item = Window.view.takeAt(0)
if item.widget():
item.widget().deleteLater()
elif item.layout():
Window.delete_layout(item.layout())
@staticmethod
def to_top() -> None:
Window.scroll_area.verticalScrollBar().setValue(0)
@staticmethod
def to_bottom() -> None:
Window.scroll_area.verticalScrollBar().setValue(
Window.scroll_area.verticalScrollBar().maximum()
)
@staticmethod
def toggle_scroll() -> None:
maxim = Window.scroll_area.verticalScrollBar().maximum()
if Window.scroll_area.verticalScrollBar().value() == maxim:
Window.to_top()
else:
Window.to_bottom()
@staticmethod
def add_footer() -> None:
root = QWidget()
root.setContentsMargins(0, 0, 0, 0)
container = QHBoxLayout()
Window.info = QPushButton("---")
Window.info.setToolTip("Click to scroll to the bottom or top")
Window.info.clicked.connect(Window.toggle_scroll)
Window.info.setMinimumSize(35, 35)
container.addWidget(Window.info)
root.setLayout(container)
Window.root.addWidget(root)
@staticmethod
def play_audio(path: str, on_stop: Callable[..., Any] | None = None) -> None:
Window.player = QMediaPlayer()
Window.audio = QAudioOutput()
Window.player.setAudioOutput(Window.audio)
Window.player.setSource(QUrl.fromLocalFile(path))
Window.audio.setVolume(100)
def handle_state_change(state: QMediaPlayer.State) -> None:
if state == QMediaPlayer.StoppedState:
if on_stop:
on_stop()
Window.player.playbackStateChanged.connect(handle_state_change)
Window.player.play()
@staticmethod
def stop_audio() -> None:
Window.player.stop()
@staticmethod
def alert(message: str) -> None:
msg_box = QMessageBox()
msg_box.setWindowFlags(Qt.Popup)
msg_box.setIcon(QMessageBox.Information)
msg_box.setText(message)
msg_box.setWindowTitle("Information")
msg_box.setStandardButtons(QMessageBox.Ok)
msg_box.exec()
@staticmethod
def prompt_combobox(message: str, options: list[str], defindex: int = 0) -> str:
dialog = ComboBoxDialog(message, options, defindex)
dialog.setWindowFlags(Qt.Popup)
if dialog.exec() == QDialog.Accepted:
return dialog.get_selection()
return ""