359 lines
11 KiB
Python
359 lines
11 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 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
|
|
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 QMouseEvent
|
|
from PySide6.QtGui import QIcon
|
|
from PySide6.QtGui import QKeyEvent
|
|
from PySide6.QtCore import Qt # type: ignore
|
|
from PySide6.QtCore import Signal
|
|
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 SpecialButton(QPushButton): # type: ignore
|
|
middleClicked = Signal()
|
|
|
|
def mousePressEvent(self, e: QMouseEvent) -> None:
|
|
if e.button() == Qt.MiddleButton:
|
|
self.middleClicked.emit()
|
|
else:
|
|
super().mousePressEvent(e)
|
|
|
|
|
|
class FilterLineEdit(QLineEdit): # type: ignore
|
|
def keyPressEvent(self, e: QKeyEvent) -> None:
|
|
if e.key() == Qt.Key_Escape:
|
|
self.clear()
|
|
else:
|
|
super().keyPressEvent(e)
|
|
|
|
|
|
class Window:
|
|
app: QApplication
|
|
window: QMainWindow
|
|
root: QVBoxLayout
|
|
view: QVBoxLayout
|
|
view_scene: QGraphicsScene
|
|
speed: QComboBox
|
|
scroll_area: QScrollArea
|
|
info: SpecialButton
|
|
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: 1px solid {Config.alt_border_color};
|
|
}}
|
|
|
|
QMenu::item:selected {{
|
|
background-color: {Config.alt_hover_background_color};
|
|
color: {Config.alt_hover_text_color};
|
|
}}
|
|
|
|
QMessageBox {{
|
|
background-color: {Config.alt_background_color};
|
|
color: {Config.alt_text_color};
|
|
border: 1px solid {Config.alt_border_color};
|
|
}}
|
|
|
|
QMessageBox QLabel {{
|
|
background-color: {Config.alt_background_color};
|
|
color: {Config.alt_text_color};
|
|
}}
|
|
|
|
QMessageBox QPushButton {{
|
|
background-color: {Config.alt_background_color};
|
|
color: {Config.alt_text_color};
|
|
}}
|
|
|
|
QMessageBox 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};
|
|
}}
|
|
|
|
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};
|
|
}}
|
|
|
|
""".strip()
|
|
|
|
Window.app.setStyleSheet(style)
|
|
Window.app.setFont(Window.font)
|
|
|
|
@staticmethod
|
|
def add_buttons() -> None:
|
|
from .ants import Ants
|
|
from .game import Game
|
|
from .filter import Filter
|
|
|
|
root = QWidget()
|
|
container = QHBoxLayout()
|
|
|
|
btn_hatch = SpecialButton("Hatch")
|
|
btn_hatch.setToolTip("Hatch a new ant\nMiddle Click to hatch Trio")
|
|
btn_hatch.clicked.connect(lambda e: Ants.hatch())
|
|
btn_hatch.middleClicked.connect(lambda: Ants.hatch_burst())
|
|
|
|
btn_terminate = SpecialButton("Terminate")
|
|
|
|
btn_terminate.setToolTip(
|
|
"Terminate a random ant\nMiddle Click to terminate all"
|
|
)
|
|
|
|
btn_terminate.clicked.connect(lambda e: Ants.terminate())
|
|
btn_terminate.middleClicked.connect(lambda: Ants.terminate_all())
|
|
|
|
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"])
|
|
Window.speed.setCurrentIndex(1)
|
|
Window.speed.currentIndexChanged.connect(Game.update_speed)
|
|
|
|
Window.filter = FilterLineEdit()
|
|
Window.filter.setFixedWidth(120)
|
|
Window.filter.setPlaceholderText("Filter")
|
|
Window.filter.mousePressEvent = lambda e: Window.to_top()
|
|
Window.filter.keyReleaseEvent = lambda e: Filter.filter(e)
|
|
|
|
container.addWidget(btn_hatch)
|
|
container.addWidget(btn_terminate)
|
|
container.addWidget(Window.speed)
|
|
container.addWidget(Window.filter)
|
|
|
|
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 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())
|
|
|
|
@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 = SpecialButton("---")
|
|
|
|
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.setIcon(QMessageBox.Information)
|
|
msg_box.setText(message)
|
|
msg_box.setWindowTitle("Information")
|
|
msg_box.setStandardButtons(QMessageBox.Ok)
|
|
msg_box.exec()
|