cromulant/cromulant/window.py

359 lines
11 KiB
Python
Raw Normal View History

2024-07-18 06:51:11 +00:00
from __future__ import annotations
2024-07-19 06:33:51 +00:00
from typing import Any
from collections.abc import Callable
2024-07-19 07:43:05 +00:00
import signal
2024-07-19 06:33:51 +00:00
2024-07-18 07:20:36 +00:00
from PySide6.QtWidgets import QApplication # type: ignore
from PySide6.QtWidgets import QMainWindow
from PySide6.QtWidgets import QWidget
from PySide6.QtWidgets import QGraphicsScene
from PySide6.QtWidgets import QVBoxLayout
from PySide6.QtWidgets import QPushButton
from PySide6.QtWidgets import QHBoxLayout
2024-07-19 04:55:49 +00:00
from PySide6.QtWidgets import QScrollArea
2024-07-19 06:03:43 +00:00
from PySide6.QtWidgets import QComboBox
2024-07-19 05:30:42 +00:00
from PySide6.QtWidgets import QLayout
2024-07-19 06:03:43 +00:00
from PySide6.QtWidgets import QSizePolicy
2024-07-19 06:33:51 +00:00
from PySide6.QtWidgets import QMessageBox
2024-07-20 08:16:48 +00:00
from PySide6.QtWidgets import QLineEdit
2024-07-19 10:03:56 +00:00
from PySide6.QtGui import QFontDatabase # type: ignore
from PySide6.QtGui import QMouseEvent
2024-07-19 06:33:51 +00:00
from PySide6.QtGui import QIcon
2024-07-20 08:56:05 +00:00
from PySide6.QtGui import QKeyEvent
2024-07-19 03:08:11 +00:00
from PySide6.QtCore import Qt # type: ignore
2024-07-19 06:33:51 +00:00
from PySide6.QtCore import Signal
2024-07-19 13:24:57 +00:00
from PySide6.QtCore import QUrl
from PySide6.QtMultimedia import QMediaPlayer # type: ignore
from PySide6.QtMultimedia import QAudioOutput
2024-07-18 06:51:11 +00:00
2024-07-18 08:14:03 +00:00
from .config import Config
2024-07-20 02:30:37 +00:00
from .utils import Utils
2024-07-18 06:51:11 +00:00
2024-07-19 06:33:51 +00:00
class SpecialButton(QPushButton): # type: ignore
middleClicked = Signal()
def mousePressEvent(self, e: QMouseEvent) -> None:
2024-07-19 11:28:49 +00:00
if e.button() == Qt.MiddleButton:
2024-07-19 06:33:51 +00:00
self.middleClicked.emit()
else:
super().mousePressEvent(e)
2024-07-20 08:56:05 +00:00
class FilterLineEdit(QLineEdit): # type: ignore
def keyPressEvent(self, e: QKeyEvent) -> None:
if e.key() == Qt.Key_Escape:
self.clear()
else:
super().keyPressEvent(e)
2024-07-18 06:51:11 +00:00
class Window:
app: QApplication
window: QMainWindow
2024-07-19 05:30:42 +00:00
root: QVBoxLayout
view: QVBoxLayout
2024-07-19 02:10:08 +00:00
view_scene: QGraphicsScene
2024-07-19 06:03:43 +00:00
speed: QComboBox
2024-07-19 06:54:22 +00:00
scroll_area: QScrollArea
2024-07-19 07:31:19 +00:00
info: SpecialButton
2024-07-19 10:03:56 +00:00
font: str
emoji_font: str
2024-07-19 13:24:57 +00:00
player: QMediaPlayer
audio: QAudioOutput
2024-07-20 08:49:59 +00:00
filter: QLineEdit
2024-07-18 06:51:11 +00:00
2024-07-19 02:10:08 +00:00
@staticmethod
def prepare() -> None:
Window.make()
Window.add_buttons()
Window.add_view()
2024-07-19 06:54:22 +00:00
Window.add_footer()
2024-07-19 02:10:08 +00:00
2024-07-18 06:51:11 +00:00
@staticmethod
def make() -> None:
Window.app = QApplication([])
2024-07-20 07:07:27 +00:00
Window.app.setApplicationName(Config.program)
2024-07-18 06:51:11 +00:00
Window.window = QMainWindow()
2024-07-18 07:20:36 +00:00
Window.window.setWindowTitle(Config.title)
Window.window.resize(Config.width, Config.height)
2024-07-20 07:07:27 +00:00
2024-07-18 08:14:03 +00:00
central_widget = QWidget()
Window.root = QVBoxLayout()
central_widget.setLayout(Window.root)
2024-07-19 11:28:49 +00:00
Window.root.setAlignment(Qt.AlignTop)
2024-07-18 08:14:03 +00:00
Window.window.setCentralWidget(central_widget)
2024-07-19 02:10:08 +00:00
Window.window.setWindowIcon(QIcon(str(Config.icon_path)))
2024-07-20 02:17:07 +00:00
Window.root.setContentsMargins(0, 0, 0, 0)
Window.set_style()
2024-07-19 04:55:49 +00:00
2024-07-20 02:17:07 +00:00
@staticmethod
def set_style() -> None:
2024-07-19 10:03:56 +00:00
font_id = QFontDatabase.addApplicationFont(str(Config.font_path))
emoji_font_id = QFontDatabase.addApplicationFont(str(Config.emoji_font_path))
2024-07-19 04:55:49 +00:00
2024-07-19 10:03:56 +00:00
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"""
2024-07-20 07:41:14 +00:00
2024-07-19 14:06:12 +00:00
QWidget {{
background-color: {Config.background_color};
color: {Config.text_color};
font-size: {Config.font_size}px;
}}
2024-07-20 01:58:27 +00:00
QMenu {{
2024-07-20 07:24:28 +00:00
background-color: {Config.alt_background_color};
color: {Config.alt_text_color};
border: 1px solid {Config.alt_border_color};
2024-07-20 01:58:27 +00:00
}}
QMenu::item:selected {{
2024-07-20 07:24:28 +00:00
background-color: {Config.alt_hover_background_color};
color: {Config.alt_hover_text_color};
2024-07-20 01:58:27 +00:00
}}
2024-07-20 02:17:07 +00:00
QMessageBox {{
2024-07-20 07:24:28 +00:00
background-color: {Config.alt_background_color};
color: {Config.alt_text_color};
border: 1px solid {Config.alt_border_color};
2024-07-20 02:17:07 +00:00
}}
QMessageBox QLabel {{
2024-07-20 07:24:28 +00:00
background-color: {Config.alt_background_color};
color: {Config.alt_text_color};
2024-07-20 02:17:07 +00:00
}}
QMessageBox QPushButton {{
2024-07-20 07:24:28 +00:00
background-color: {Config.alt_background_color};
color: {Config.alt_text_color};
2024-07-20 02:17:07 +00:00
}}
QMessageBox QPushButton:hover {{
background-color: {Config.message_box_button_hover_background_color};
color: {Config.message_box_button_hover_text_color};
}}
2024-07-20 07:24:28 +00:00
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};
}}
2024-07-20 07:41:14 +00:00
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;
}}
2024-07-20 08:16:48 +00:00
QLineEdit {{
background-color: {Config.input_background_color};
color: {Config.input_text_color};
border: 1px solid {Config.input_border_color};
}}
2024-07-19 14:06:12 +00:00
""".strip()
2024-07-19 10:03:56 +00:00
2024-07-20 01:38:40 +00:00
Window.app.setStyleSheet(style)
2024-07-19 10:03:56 +00:00
Window.app.setFont(Window.font)
2024-07-18 06:51:11 +00:00
@staticmethod
def add_buttons() -> None:
2024-07-19 06:33:51 +00:00
from .ants import Ants
2024-07-19 06:03:43 +00:00
from .game import Game
2024-07-20 09:12:20 +00:00
from .filter import Filter
2024-07-19 06:03:43 +00:00
2024-07-19 07:31:19 +00:00
root = QWidget()
container = QHBoxLayout()
2024-07-19 06:33:51 +00:00
btn_hatch = SpecialButton("Hatch")
2024-07-19 10:33:01 +00:00
btn_hatch.setToolTip("Hatch a new ant\nMiddle Click to hatch Trio")
2024-07-19 06:33:51 +00:00
btn_hatch.clicked.connect(lambda e: Ants.hatch())
btn_hatch.middleClicked.connect(lambda: Ants.hatch_burst())
2024-07-18 06:51:11 +00:00
2024-07-19 06:33:51 +00:00
btn_terminate = SpecialButton("Terminate")
2024-07-19 13:24:57 +00:00
2024-07-19 11:28:49 +00:00
btn_terminate.setToolTip(
"Terminate a random ant\nMiddle Click to terminate all"
)
2024-07-19 13:24:57 +00:00
2024-07-19 06:33:51 +00:00
btn_terminate.clicked.connect(lambda e: Ants.terminate())
btn_terminate.middleClicked.connect(lambda: Ants.terminate_all())
2024-07-19 06:03:43 +00:00
Window.speed = QComboBox()
2024-07-20 02:31:39 +00:00
tooltip = "The speed of the updates\n"
2024-07-20 02:30:37 +00:00
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)
2024-07-19 06:03:43 +00:00
Window.speed.addItems(["Fast", "Normal", "Slow"])
Window.speed.setCurrentIndex(1)
Window.speed.currentIndexChanged.connect(Game.update_speed)
2024-07-20 08:56:05 +00:00
Window.filter = FilterLineEdit()
2024-07-20 08:49:59 +00:00
Window.filter.setFixedWidth(120)
Window.filter.setPlaceholderText("Filter")
Window.filter.mousePressEvent = lambda e: Window.to_top()
2024-07-20 09:12:20 +00:00
Window.filter.keyReleaseEvent = lambda e: Filter.filter(e)
2024-07-18 06:51:11 +00:00
2024-07-19 07:31:19 +00:00
container.addWidget(btn_hatch)
container.addWidget(btn_terminate)
container.addWidget(Window.speed)
2024-07-20 08:49:59 +00:00
container.addWidget(Window.filter)
2024-07-18 06:51:11 +00:00
2024-07-19 07:31:19 +00:00
root.setLayout(container)
Window.root.addWidget(root)
2024-07-18 06:51:11 +00:00
@staticmethod
def add_view() -> None:
2024-07-19 06:54:22 +00:00
Window.scroll_area = QScrollArea()
Window.scroll_area.setWidgetResizable(True)
2024-07-19 04:55:49 +00:00
container = QWidget()
parent = QVBoxLayout(container)
2024-07-19 02:10:08 +00:00
Window.view = QVBoxLayout()
2024-07-19 04:55:49 +00:00
parent.addLayout(Window.view)
2024-07-18 08:14:03 +00:00
2024-07-19 11:28:49 +00:00
Window.view.setAlignment(Qt.AlignTop)
2024-07-19 06:54:22 +00:00
Window.scroll_area.setWidget(container)
Window.root.addWidget(Window.scroll_area)
2024-07-18 08:14:03 +00:00
2024-07-18 06:51:11 +00:00
@staticmethod
def start() -> None:
2024-07-19 07:43:05 +00:00
signal.signal(signal.SIGINT, signal.SIG_DFL)
2024-07-18 06:51:11 +00:00
Window.window.show()
Window.app.exec()
2024-07-19 02:10:08 +00:00
@staticmethod
def close() -> None:
Window.app.quit()
2024-07-19 05:19:08 +00:00
@staticmethod
2024-07-19 05:30:42 +00:00
def delete_layout(layout: QLayout) -> None:
2024-07-19 05:19:08 +00:00
while layout.count():
item = layout.takeAt(0)
if item.widget():
item.widget().deleteLater()
elif item.layout():
Window.delete_layout(item.layout())
layout.deleteLater()
2024-07-19 06:03:43 +00:00
@staticmethod
def expand(widget: QWidget) -> None:
widget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred)
2024-07-19 06:33:51 +00:00
@staticmethod
def confirm(message: str, action: Callable[..., Any]) -> None:
msg_box = QMessageBox()
msg_box.setIcon(QMessageBox.Icon.Question)
msg_box.setWindowTitle("Confirm")
msg_box.setText(message)
msg_box.setStandardButtons(
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
)
msg_box.setDefaultButton(QMessageBox.StandardButton.No)
msg_box.button(QMessageBox.StandardButton.Yes).clicked.connect(action)
msg_box.exec()
@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())
2024-07-19 06:54:22 +00:00
@staticmethod
def to_top() -> None:
Window.scroll_area.verticalScrollBar().setValue(0)
2024-07-19 07:31:19 +00:00
@staticmethod
def to_bottom() -> None:
Window.scroll_area.verticalScrollBar().setValue(
Window.scroll_area.verticalScrollBar().maximum()
)
2024-07-20 08:16:48 +00:00
@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()
2024-07-19 06:54:22 +00:00
@staticmethod
def add_footer() -> None:
2024-07-19 07:18:28 +00:00
root = QWidget()
2024-07-19 07:31:19 +00:00
root.setContentsMargins(0, 0, 0, 0)
container = QHBoxLayout()
Window.info = SpecialButton("---")
2024-07-20 08:16:48 +00:00
2024-07-20 08:56:05 +00:00
Window.info.setToolTip("Click to scroll to the bottom or top")
2024-07-20 08:16:48 +00:00
Window.info.clicked.connect(Window.toggle_scroll)
2024-07-19 07:56:17 +00:00
Window.info.setMinimumSize(35, 35)
2024-07-19 07:31:19 +00:00
container.addWidget(Window.info)
root.setLayout(container)
2024-07-19 07:18:28 +00:00
Window.root.addWidget(root)
2024-07-19 13:24:57 +00:00
@staticmethod
2024-07-20 12:21:55 +00:00
def play_audio(path: str, on_stop: Callable[..., Any] | None = None) -> None:
2024-07-19 13:24:57 +00:00
Window.player = QMediaPlayer()
Window.audio = QAudioOutput()
Window.player.setAudioOutput(Window.audio)
Window.player.setSource(QUrl.fromLocalFile(path))
Window.audio.setVolume(100)
2024-07-20 12:21:55 +00:00
def handle_state_change(state: QMediaPlayer.State) -> None:
if state == QMediaPlayer.StoppedState:
if on_stop:
on_stop()
Window.player.playbackStateChanged.connect(handle_state_change)
2024-07-19 13:24:57 +00:00
Window.player.play()
@staticmethod
def stop_audio() -> None:
Window.player.stop()
2024-07-20 02:17:07 +00:00
@staticmethod
def alert(message: str) -> None:
msg_box = QMessageBox()
msg_box.setIcon(QMessageBox.Information)
msg_box.setText(message)
msg_box.setWindowTitle("Information")
msg_box.setStandardButtons(QMessageBox.Ok)
2024-07-20 02:17:18 +00:00
msg_box.exec()