launcher project v 2.0
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,123 @@
|
||||
from PySide6.QtWidgets import (
|
||||
QDialog, QVBoxLayout, QLineEdit,
|
||||
QPushButton, QLabel, QMessageBox
|
||||
)
|
||||
from PySide6.QtCore import Qt, Signal, QObject
|
||||
from src.api.auth_api import AuthAPI, AuthResult
|
||||
import asyncio
|
||||
|
||||
class LoginSignals(QObject):
|
||||
success = Signal(AuthResult)
|
||||
error = Signal(str)
|
||||
|
||||
class LoginDialog(QDialog):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.auth_api = AuthAPI()
|
||||
self.auth_result = None
|
||||
self.signals = LoginSignals()
|
||||
self.setObjectName("login-dialog")
|
||||
self.setup_ui()
|
||||
|
||||
# Подключаем сигналы
|
||||
self.signals.success.connect(self.on_login_success)
|
||||
self.signals.error.connect(self.on_login_error)
|
||||
|
||||
def setup_ui(self):
|
||||
"""Настройка интерфейса"""
|
||||
self.setWindowTitle("Авторизация")
|
||||
self.setFixedSize(350, 320)
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setSpacing(15)
|
||||
layout.setContentsMargins(30, 30, 30, 30)
|
||||
|
||||
# Заголовок
|
||||
title = QLabel("Вход в аккаунт")
|
||||
title.setAlignment(Qt.AlignCenter)
|
||||
title.setProperty("class", "login-title")
|
||||
layout.addSpacing(5)
|
||||
layout.addWidget(title)
|
||||
layout.addSpacing(20)
|
||||
|
||||
# Поля ввода
|
||||
self.username = QLineEdit()
|
||||
self.username.setPlaceholderText("Имя аккаунта")
|
||||
self.username.setProperty("class", "login-input")
|
||||
self.username.setFixedHeight(40)
|
||||
layout.addWidget(self.username)
|
||||
layout.addSpacing(15)
|
||||
|
||||
self.password = QLineEdit()
|
||||
self.password.setPlaceholderText("Пароль")
|
||||
self.password.setEchoMode(QLineEdit.Password)
|
||||
self.password.setProperty("class", "login-input")
|
||||
self.password.setFixedHeight(40)
|
||||
layout.addWidget(self.password)
|
||||
layout.addSpacing(20)
|
||||
|
||||
# Кнопка входа
|
||||
self.login_button = QPushButton("Войти")
|
||||
self.login_button.setProperty("class", "login-button")
|
||||
self.login_button.setFixedHeight(40)
|
||||
self.login_button.clicked.connect(self.handle_login)
|
||||
layout.addWidget(self.login_button)
|
||||
layout.addSpacing(15)
|
||||
|
||||
# Дополнительные кнопки
|
||||
self.register_button = QPushButton("Регистрация")
|
||||
self.register_button.setProperty("class", "link-button")
|
||||
layout.addWidget(self.register_button)
|
||||
|
||||
self.forgot_button = QPushButton("Забыли пароль?")
|
||||
self.forgot_button.setProperty("class", "link-button")
|
||||
layout.addWidget(self.forgot_button)
|
||||
|
||||
def handle_login(self):
|
||||
"""Обработчик нажатия кнопки входа"""
|
||||
username = self.username.text().strip()
|
||||
password = self.password.text().strip()
|
||||
|
||||
if not username or not password:
|
||||
QMessageBox.warning(
|
||||
self,
|
||||
"Ошибка",
|
||||
"Введите имя аккаунта и пароль"
|
||||
)
|
||||
return
|
||||
|
||||
# Отключаем кнопку на время авторизации
|
||||
self.login_button.setEnabled(False)
|
||||
self.login_button.setText("Вход...")
|
||||
|
||||
# Запускаем асинхронную авторизацию
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.create_task(self.try_login(username, password))
|
||||
|
||||
async def try_login(self, username: str, password: str):
|
||||
"""Асинхронная попытка авторизации"""
|
||||
try:
|
||||
result = await self.auth_api.login(username, password)
|
||||
if result.success:
|
||||
self.signals.success.emit(result)
|
||||
else:
|
||||
self.signals.error.emit(result.message)
|
||||
except Exception as e:
|
||||
self.signals.error.emit(str(e))
|
||||
finally:
|
||||
# Возвращаем кнопку в исходное состояние
|
||||
self.login_button.setEnabled(True)
|
||||
self.login_button.setText("Войти")
|
||||
|
||||
def on_login_success(self, result: AuthResult):
|
||||
"""Обработчик успешной авторизации"""
|
||||
self.auth_result = result
|
||||
self.accept()
|
||||
|
||||
def on_login_error(self, message: str):
|
||||
"""Обработчик ошибки авторизации"""
|
||||
QMessageBox.warning(
|
||||
self,
|
||||
"Ошибка авторизации",
|
||||
message
|
||||
)
|
||||
@@ -0,0 +1,797 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
from PySide6.QtWidgets import (
|
||||
QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
||||
QPushButton, QLabel, QProgressBar, QFrame,
|
||||
QGridLayout, QLineEdit, QDialog, QTabWidget,
|
||||
QCheckBox, QFileDialog, QComboBox, QMenu
|
||||
)
|
||||
from PySide6.QtCore import Qt, QSize, QTimer, QPoint
|
||||
from PySide6.QtGui import (
|
||||
QPixmap, QPalette, QBrush, QFont, QIcon,
|
||||
QPainter, QLinearGradient, QColor, QAction
|
||||
)
|
||||
from src.api.server_api import ServerAPI
|
||||
from src.api.auth_api import AuthResult
|
||||
import asyncio
|
||||
import sys
|
||||
from src.ui.login_dialog import LoginDialog
|
||||
|
||||
# Константы
|
||||
CARD_SPACING = 15
|
||||
CARD_MARGINS = (15, 10, 15, 10)
|
||||
DEFAULT_ICON_SIZE = QSize(20, 20)
|
||||
PLAY_BUTTON_SIZE = QSize(200, 50)
|
||||
SETTINGS_BUTTON_SIZE = QSize(40, 40)
|
||||
PROGRESS_BAR_HEIGHT = 4
|
||||
|
||||
# Цвета
|
||||
COLOR_PRIMARY = "#FFB100"
|
||||
COLOR_SUCCESS = "#2ecc71"
|
||||
COLOR_BACKGROUND = "rgba(0, 0, 0, 0.7)"
|
||||
|
||||
# Размеры
|
||||
MAIN_NEWS_IMAGE_HEIGHT = 200
|
||||
SMALL_NEWS_IMAGE_HEIGHT = 100
|
||||
SETTINGS_DIALOG_WIDTH = 600
|
||||
|
||||
class Card(QFrame):
|
||||
"""Базовый класс для карточек"""
|
||||
def __init__(self, title="", parent=None):
|
||||
super().__init__(parent)
|
||||
self.layout = QVBoxLayout(self)
|
||||
if title:
|
||||
title_label = QLabel(title)
|
||||
title_label.setProperty("class", "title")
|
||||
self.layout.addWidget(title_label)
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
# Загружаем стили
|
||||
with open("assets/styles/main.qss", "r") as f:
|
||||
self.setStyleSheet(f.read())
|
||||
|
||||
# Создаем event loop для асинхронных операций
|
||||
self.loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(self.loop)
|
||||
|
||||
# Инициализация API клиента
|
||||
self.server_api = ServerAPI()
|
||||
|
||||
# Таймер для обновления статуса
|
||||
self.status_timer = QTimer()
|
||||
self.status_timer.timeout.connect(self.update_server_status)
|
||||
self.status_timer.start(30000) # Обновляем каждые 30 секунд
|
||||
|
||||
# Настройки
|
||||
self.settings_file = Path("config/settings.json")
|
||||
self.default_settings = {
|
||||
"game": {
|
||||
"path": "",
|
||||
"realmlist": "logon.server.com",
|
||||
"launch_options": ""
|
||||
},
|
||||
"graphics": {
|
||||
"resolution": "1920x1080",
|
||||
"quality": "Высокое",
|
||||
"windowed": False
|
||||
},
|
||||
"auth": {
|
||||
"username": None,
|
||||
"account_id": None,
|
||||
"auto_login": False
|
||||
}
|
||||
}
|
||||
|
||||
self.settings = self.load_settings()
|
||||
|
||||
self.setWindowTitle("WoW 3.3.5 Launcher")
|
||||
self.setMinimumSize(1200, 800)
|
||||
|
||||
# Фоновое изображение
|
||||
self.background = QPixmap("assets/images/background.jpg")
|
||||
self.updateBackground()
|
||||
|
||||
# Главный виджет
|
||||
central_widget = QWidget()
|
||||
self.setCentralWidget(central_widget)
|
||||
|
||||
# Главный layout
|
||||
main_layout = QVBoxLayout(central_widget)
|
||||
main_layout.setSpacing(20)
|
||||
main_layout.setContentsMargins(40, 30, 40, 30)
|
||||
|
||||
# Компоненты
|
||||
main_layout.addWidget(self.create_header())
|
||||
main_layout.addWidget(self.create_status_cards())
|
||||
main_layout.addWidget(self.create_content())
|
||||
main_layout.addWidget(self.create_footer())
|
||||
|
||||
# Первоначальное получение статуса
|
||||
self.update_server_status()
|
||||
|
||||
self.current_user = None # Текущий пользователь
|
||||
|
||||
# Проверяем сохраненную авторизацию
|
||||
auth_settings = self.settings.get("auth", {})
|
||||
if auth_settings.get("auto_login"):
|
||||
self.current_user = AuthResult(
|
||||
success=True,
|
||||
message="Успешная авторизация",
|
||||
account_id=auth_settings["account_id"],
|
||||
username=auth_settings["username"]
|
||||
)
|
||||
self.update_ui_after_login()
|
||||
|
||||
def create_content(self):
|
||||
content = Card("НОВОСТИ")
|
||||
content.setMinimumHeight(400)
|
||||
|
||||
# Создаем grid layout для новостей
|
||||
grid = QGridLayout()
|
||||
grid.setSpacing(15)
|
||||
content.layout.addLayout(grid)
|
||||
|
||||
# Главная новость (большая карточка слева)
|
||||
main_news = self.create_news_card(
|
||||
"Открытие нового сезона",
|
||||
"Встречайте новый сезон арены с обновленной системой рейтинга и наградами!",
|
||||
"main_news.jpg",
|
||||
is_main=True
|
||||
)
|
||||
grid.addWidget(main_news, 0, 0, 2, 2) # Занимает 2x2 ячейки
|
||||
|
||||
# Дополнительные новости (справа)
|
||||
news_items = [
|
||||
{
|
||||
"title": "Обновление 3.3.5a",
|
||||
"text": "Список изменений и улучшений в новой версии...",
|
||||
"image": "update_news.jpg",
|
||||
"tag": "ОБНОВЛЕНИЕ"
|
||||
},
|
||||
{
|
||||
"title": "Турнир на арене",
|
||||
"text": "Регистрация на турнир начинается через...",
|
||||
"image": "arena_news.jpg",
|
||||
"tag": "СОБЫТИЕ"
|
||||
},
|
||||
{
|
||||
"title": "Новые предметы",
|
||||
"text": "В магазине появились новые предметы...",
|
||||
"image": "items_news.jpg",
|
||||
"tag": "МАГАЗИН"
|
||||
}
|
||||
]
|
||||
|
||||
for i, news in enumerate(news_items):
|
||||
card = self.create_news_card(
|
||||
news["title"],
|
||||
news["text"],
|
||||
news["image"],
|
||||
tag=news["tag"]
|
||||
)
|
||||
grid.addWidget(card, i, 2) # Добавляем справа
|
||||
|
||||
return content
|
||||
|
||||
def create_header(self):
|
||||
"""Создает шапку с логотипом и навигацией"""
|
||||
header = QWidget()
|
||||
layout = QHBoxLayout(header)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
# Логотип
|
||||
logo = QLabel()
|
||||
logo_pixmap = QPixmap("assets/images/wow-logo.png")
|
||||
logo.setPixmap(logo_pixmap.scaled(150, 50, Qt.KeepAspectRatio, Qt.SmoothTransformation))
|
||||
layout.addWidget(logo)
|
||||
|
||||
# Навигация
|
||||
nav = QHBoxLayout()
|
||||
nav.setSpacing(20)
|
||||
|
||||
# Добавляем растягивающийся элемент слева
|
||||
nav.addStretch()
|
||||
|
||||
# Кнопки навигации
|
||||
news_btn = self.create_nav_button("Новости", "assets/images/news-icon.svg")
|
||||
ranking_btn = self.create_nav_button("Рейтинг", "assets/images/ranking-icon.svg")
|
||||
shop_btn = self.create_nav_button("Магазин", "assets/images/shop-icon.svg")
|
||||
|
||||
nav.addWidget(news_btn)
|
||||
nav.addWidget(ranking_btn)
|
||||
nav.addWidget(shop_btn)
|
||||
|
||||
# Кнопка логина/аккаунта
|
||||
self.account_btn = QPushButton("Войти")
|
||||
self.account_btn.setProperty("class", "login-button")
|
||||
self.account_btn.clicked.connect(self.show_login_dialog)
|
||||
nav.addWidget(self.account_btn)
|
||||
|
||||
# Кнопка настроек
|
||||
settings_btn = QPushButton()
|
||||
settings_btn.setIcon(QIcon("assets/images/settings.svg"))
|
||||
settings_btn.setIconSize(DEFAULT_ICON_SIZE)
|
||||
settings_btn.setProperty("class", "icon-button")
|
||||
settings_btn.clicked.connect(self.show_settings)
|
||||
nav.addWidget(settings_btn)
|
||||
|
||||
layout.addLayout(nav)
|
||||
return header
|
||||
|
||||
def create_status_cards(self):
|
||||
"""Создает карточки статуса сервера, онлайна и версии."""
|
||||
widget = QWidget()
|
||||
layout = QHBoxLayout(widget)
|
||||
layout.setSpacing(CARD_SPACING)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
# Статус сервера
|
||||
status_card = Card()
|
||||
status_card.setObjectName("status_card")
|
||||
status_card.setProperty("class", "base-card status-card status-card-green")
|
||||
|
||||
status_layout = QVBoxLayout()
|
||||
status_layout.setSpacing(5)
|
||||
status_layout.setContentsMargins(*CARD_MARGINS)
|
||||
|
||||
title = self.create_label("СТАТУС СЕРВЕРА", "title")
|
||||
self.status_label = self.create_label("Онлайн", "status-value-online")
|
||||
self.realm_name = self.create_label("REALM NAME", "subtitle")
|
||||
|
||||
status_layout.addWidget(title)
|
||||
status_layout.addWidget(self.status_label)
|
||||
status_layout.addWidget(self.realm_name)
|
||||
status_card.layout.addLayout(status_layout)
|
||||
|
||||
# Онлайн
|
||||
online_card = Card()
|
||||
online_card.setProperty("class", "status-card status-card-blue")
|
||||
|
||||
online_layout = QVBoxLayout()
|
||||
online_layout.setSpacing(5)
|
||||
online_layout.setContentsMargins(*CARD_MARGINS)
|
||||
|
||||
online_title = self.create_label("ИГРОКОВ ОНЛАЙН", "title")
|
||||
|
||||
self.online_count = self.create_label("1500", "value")
|
||||
|
||||
self.online_trend = self.create_label("↑ +125 за час", "trend-up")
|
||||
|
||||
online_layout.addWidget(online_title)
|
||||
online_layout.addWidget(self.online_count)
|
||||
online_layout.addWidget(self.online_trend)
|
||||
online_card.layout.addLayout(online_layout)
|
||||
|
||||
# Версия
|
||||
version_card = Card()
|
||||
version_card.setProperty("class", "status-card status-card-purple")
|
||||
|
||||
version_layout = QVBoxLayout()
|
||||
version_layout.setSpacing(5)
|
||||
version_layout.setContentsMargins(*CARD_MARGINS)
|
||||
|
||||
version_title = self.create_label("ВЕРСИЯ", "title")
|
||||
|
||||
version_number = self.create_label("3.3.5a", "value")
|
||||
|
||||
build_number = self.create_label("12340", "subtitle")
|
||||
|
||||
version_layout.addWidget(version_title)
|
||||
version_layout.addWidget(version_number)
|
||||
version_layout.addWidget(build_number)
|
||||
version_card.layout.addLayout(version_layout)
|
||||
|
||||
# Добавляем карточки в layout
|
||||
layout.addWidget(status_card)
|
||||
layout.addWidget(online_card)
|
||||
layout.addWidget(version_card)
|
||||
layout.addStretch()
|
||||
|
||||
return widget
|
||||
|
||||
def create_news_section(self):
|
||||
"""Создает секцию новостей"""
|
||||
news_widget = QWidget()
|
||||
layout = QVBoxLayout(news_widget)
|
||||
layout.setSpacing(20)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
# Заголовок секции
|
||||
title = QLabel("Новости")
|
||||
title.setProperty("class", "section-title")
|
||||
layout.addWidget(title)
|
||||
|
||||
# Сетка новостей
|
||||
news_grid = QGridLayout()
|
||||
news_grid.setSpacing(20)
|
||||
|
||||
# Главная новость
|
||||
main_news = self.create_news_card(
|
||||
"Обновление 3.3.5a",
|
||||
"Установлен патч 3.3.5a...",
|
||||
"assets/images/news/main_news.jpg",
|
||||
True
|
||||
)
|
||||
news_grid.addWidget(main_news, 0, 0, 1, 2)
|
||||
|
||||
# Дополнительные новости
|
||||
news1 = self.create_news_card(
|
||||
"Открытие арены",
|
||||
"Новый сезон арены...",
|
||||
"assets/images/news/arena_news.jpg"
|
||||
)
|
||||
news_grid.addWidget(news1, 1, 0)
|
||||
|
||||
news2 = self.create_news_card(
|
||||
"Новые предметы",
|
||||
"Добавлены новые предметы...",
|
||||
"assets/images/news/items_news.jpg"
|
||||
)
|
||||
news_grid.addWidget(news2, 1, 1)
|
||||
|
||||
layout.addLayout(news_grid)
|
||||
return news_widget
|
||||
|
||||
def create_news_card(self, title, text, image_path, tag=None, is_main=False):
|
||||
card = QFrame()
|
||||
card.setProperty("class", "news-card")
|
||||
|
||||
layout = QVBoxLayout(card)
|
||||
layout.setSpacing(10)
|
||||
|
||||
# Изображение новости
|
||||
image_label = QLabel()
|
||||
image_label.setProperty("class", "news-image")
|
||||
pixmap = QPixmap(f"assets/images/news/{image_path}")
|
||||
|
||||
# Проверяем, загрузилось ли изображение
|
||||
if pixmap.isNull():
|
||||
# Создаем заглушку с градиентом
|
||||
if is_main:
|
||||
pixmap = QPixmap(400, 200)
|
||||
else:
|
||||
pixmap = QPixmap(200, 100)
|
||||
pixmap.fill(Qt.transparent)
|
||||
|
||||
# Можно добавить градиент или цвет заглушки
|
||||
painter = QPainter(pixmap)
|
||||
gradient = QLinearGradient(0, 0, pixmap.width(), 0)
|
||||
gradient.setColorAt(0, QColor("#2c3e50"))
|
||||
gradient.setColorAt(1, QColor("#3498db"))
|
||||
painter.fillRect(pixmap.rect(), gradient)
|
||||
painter.end()
|
||||
|
||||
# Устанавливаем размер и масштабируем изображение
|
||||
if is_main:
|
||||
image_label.setFixedHeight(200)
|
||||
else:
|
||||
image_label.setFixedHeight(100)
|
||||
|
||||
scaled_pixmap = pixmap.scaled(
|
||||
image_label.width() if image_label.width() > 0 else pixmap.width(),
|
||||
image_label.height(),
|
||||
Qt.KeepAspectRatioByExpanding,
|
||||
Qt.SmoothTransformation
|
||||
)
|
||||
|
||||
image_label.setPixmap(scaled_pixmap)
|
||||
layout.addWidget(image_label) # Добавляем изображение в layout
|
||||
|
||||
# Тег (если есть)
|
||||
if tag:
|
||||
tag_label = QLabel(tag)
|
||||
tag_label.setProperty("class", "news-tag")
|
||||
# Даем тегу возможность подстроиться под содержимое
|
||||
tag_label.adjustSize()
|
||||
# Добавляем небольшой отступ для текста внутри тега
|
||||
tag_label.setContentsMargins(8, 2, 8, 2)
|
||||
layout.addWidget(tag_label)
|
||||
|
||||
# Заголовок
|
||||
title_label = QLabel(title)
|
||||
title_label.setProperty("class", "news-title")
|
||||
if is_main:
|
||||
title_label.setProperty("main", "true")
|
||||
layout.addWidget(title_label) # Добавляем заголовок в layout
|
||||
|
||||
# Текст
|
||||
text_label = QLabel(text)
|
||||
text_label.setWordWrap(True)
|
||||
text_label.setProperty("class", "news-text")
|
||||
layout.addWidget(text_label) # Добавляем текст в layout
|
||||
|
||||
# Кнопка "Подробнее"
|
||||
if is_main:
|
||||
more_btn = QPushButton("Подробнее →")
|
||||
more_btn.setProperty("class", "more-button")
|
||||
layout.addWidget(more_btn)
|
||||
|
||||
layout.addStretch()
|
||||
return card
|
||||
|
||||
def create_footer(self):
|
||||
footer = Card()
|
||||
|
||||
layout = QVBoxLayout()
|
||||
footer.layout.addLayout(layout)
|
||||
|
||||
# Кнопки
|
||||
buttons = QHBoxLayout()
|
||||
|
||||
self.play_button = QPushButton("ИГРАТЬ")
|
||||
self.play_button.setFixedSize(200, 50)
|
||||
self.play_button.setProperty("class", "play-button")
|
||||
|
||||
buttons.addWidget(self.play_button)
|
||||
buttons.addStretch()
|
||||
|
||||
# Прогресс
|
||||
self.progress = QProgressBar()
|
||||
self.progress.setFixedHeight(4)
|
||||
self.progress.setTextVisible(False)
|
||||
self.progress.setProperty("class", "progress-bar")
|
||||
self.progress.setValue(100)
|
||||
|
||||
layout.addLayout(buttons)
|
||||
layout.addWidget(self.progress)
|
||||
|
||||
return footer
|
||||
|
||||
def resizeEvent(self, event):
|
||||
super().resizeEvent(event)
|
||||
self.updateBackground()
|
||||
|
||||
def updateBackground(self):
|
||||
# Получаем размеры окна
|
||||
window_size = self.size()
|
||||
|
||||
# Масштабируем изображение, чтобы оно покрывало всё окно
|
||||
scaled_bg = self.background.scaled(
|
||||
window_size.width() + 50, # Добавляем небольшой запас по ширине
|
||||
window_size.height() + 50, # и высоте, чтобы избежать пустых краёв
|
||||
Qt.AspectRatioMode.KeepAspectRatioByExpanding,
|
||||
Qt.TransformationMode.SmoothTransformation
|
||||
)
|
||||
|
||||
# Если изображение больше окна, обрезаем его по центру
|
||||
if scaled_bg.width() > window_size.width() or scaled_bg.height() > window_size.height():
|
||||
x = (scaled_bg.width() - window_size.width()) // 2
|
||||
y = (scaled_bg.height() - window_size.height()) // 2
|
||||
scaled_bg = scaled_bg.copy(
|
||||
x, y,
|
||||
window_size.width(),
|
||||
window_size.height()
|
||||
)
|
||||
|
||||
# Устанавливаем фон
|
||||
palette = self.palette()
|
||||
palette.setBrush(QPalette.Window, QBrush(scaled_bg))
|
||||
self.setPalette(palette)
|
||||
|
||||
def pulse_play_button(self):
|
||||
self.play_button.setProperty("class", "play-button-pulse")
|
||||
|
||||
def load_settings(self):
|
||||
"""Загружает настройки из файла"""
|
||||
if self.settings_file.exists():
|
||||
try:
|
||||
with open(self.settings_file, "r") as f:
|
||||
return json.load(f)
|
||||
except:
|
||||
return self.default_settings.copy()
|
||||
return self.default_settings.copy()
|
||||
|
||||
def save_settings(self):
|
||||
"""Сохраняет настройки в файл"""
|
||||
self.settings_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(self.settings_file, "w") as f:
|
||||
json.dump(self.settings, f, indent=4)
|
||||
|
||||
def get_setting(self, category, key):
|
||||
"""Получает значение настройки по категории и ключу"""
|
||||
return self.settings.get(category, {}).get(key, self.default_settings[category][key])
|
||||
|
||||
def set_setting(self, category, key, value):
|
||||
"""Устанавливает значение настройки"""
|
||||
if category not in self.settings:
|
||||
self.settings[category] = {}
|
||||
self.settings[category][key] = value
|
||||
self.save_settings()
|
||||
|
||||
def show_settings(self):
|
||||
dialog = SettingsDialog(self) # Теперь передаем self вместо settings_manager
|
||||
if dialog.exec(): # Если нажата кнопка "Сохранить"
|
||||
# Получаем значения из диалога и сохраняем их
|
||||
self.set_setting("game", "path", dialog.game_path_edit.text())
|
||||
self.set_setting("game", "realmlist", dialog.realm_box.text())
|
||||
self.set_setting("game", "launch_options", dialog.launch_options.text())
|
||||
self.set_setting("graphics", "resolution", dialog.resolution.currentText())
|
||||
self.set_setting("graphics", "quality", dialog.graphics.currentText())
|
||||
self.set_setting("graphics", "windowed", dialog.windowed.isChecked())
|
||||
|
||||
def create_label(self, text, class_name, additional_props=None):
|
||||
"""Создает стилизованный QLabel с заданным классом.
|
||||
|
||||
Args:
|
||||
text (str): Текст метки
|
||||
class_name (str): Имя класса для стилей
|
||||
additional_props (dict, optional): Дополнительные свойства
|
||||
|
||||
Returns:
|
||||
QLabel: Созданная метка
|
||||
"""
|
||||
label = QLabel(text)
|
||||
label.setProperty("class", class_name)
|
||||
if additional_props:
|
||||
for key, value in additional_props.items():
|
||||
label.setProperty(key, value)
|
||||
return label
|
||||
|
||||
def update_server_status(self):
|
||||
"""Обновляет информацию о статусе сервера"""
|
||||
async def get_status():
|
||||
status = await self.server_api.get_server_status()
|
||||
if status:
|
||||
# Обновляем UI в главном потоке
|
||||
online = status.auth_online and status.world_online
|
||||
|
||||
# Определяем текст статуса
|
||||
if online:
|
||||
status_text = "Онлайн"
|
||||
elif not status.auth_online and not status.world_online:
|
||||
status_text = "Оффлайн"
|
||||
elif not status.auth_online:
|
||||
status_text = "Auth Оффлайн"
|
||||
else:
|
||||
status_text = "World Оффлайн"
|
||||
|
||||
# Обновляем текст и стиль статуса
|
||||
self.status_label.setText(status_text)
|
||||
self.status_label.setProperty(
|
||||
"class",
|
||||
"status-value-online" if online else "status-value-offline"
|
||||
)
|
||||
self.status_label.style().unpolish(self.status_label)
|
||||
self.status_label.style().polish(self.status_label)
|
||||
|
||||
# Обновляем стиль карточки статуса
|
||||
status_card = self.findChild(Card, "status_card")
|
||||
if status_card:
|
||||
new_class = f"base-card status-card {'status-card-green' if online else 'status-card-red'}"
|
||||
print(f"Setting card class to: {new_class}") # Отладочный вывод
|
||||
status_card.setProperty("class", new_class)
|
||||
status_card.style().unpolish(status_card)
|
||||
status_card.style().polish(status_card)
|
||||
status_card.update() # Принудительно обновляем виджет
|
||||
|
||||
self.realm_name.setText(status.realm_name)
|
||||
self.online_count.setText(str(status.players_online))
|
||||
|
||||
# Обновляем тренд
|
||||
self.online_trend.setText(f"↑ {status.players_online} из {status.max_players}")
|
||||
else:
|
||||
self.status_label.setText("Недоступен")
|
||||
self.status_label.setProperty("class", "status-value-offline")
|
||||
self.status_label.style().unpolish(self.status_label)
|
||||
self.status_label.style().polish(self.status_label)
|
||||
|
||||
# Обновляем стиль карточки на красный
|
||||
status_card = self.findChild(Card, "status_card")
|
||||
if status_card:
|
||||
status_card.setProperty("class", "base-card status-card status-card-red")
|
||||
status_card.style().unpolish(status_card)
|
||||
status_card.style().polish(status_card)
|
||||
|
||||
# Запускаем асинхронную задачу в нашем event loop
|
||||
future = asyncio.run_coroutine_threadsafe(get_status(), self.loop)
|
||||
future.add_done_callback(lambda f: self.handle_status_update_error(f))
|
||||
|
||||
def handle_status_update_error(self, future):
|
||||
"""Обрабатывает ошибки при обновлении статуса"""
|
||||
try:
|
||||
future.result()
|
||||
except Exception as e:
|
||||
print(f"Error updating server status: {e}")
|
||||
|
||||
def show_login(self):
|
||||
"""Показывает диалог авторизации"""
|
||||
dialog = LoginDialog(self)
|
||||
if dialog.exec_():
|
||||
# Успешная авторизация
|
||||
self.current_user = dialog.auth_result
|
||||
self.update_ui_after_login()
|
||||
|
||||
def update_ui_after_login(self):
|
||||
"""Обновляет UI после успешной авторизации"""
|
||||
if self.current_user:
|
||||
# Сохраняем данные авторизации
|
||||
self.settings["auth"] = {
|
||||
"username": self.current_user.username,
|
||||
"account_id": self.current_user.account_id,
|
||||
"auto_login": True
|
||||
}
|
||||
self.save_settings() # Теперь этот вызов должен работать
|
||||
# Обновляем кнопку
|
||||
self.account_btn.setText(self.current_user.username)
|
||||
self.account_btn.setProperty("class", "account-button")
|
||||
self.account_btn.style().unpolish(self.account_btn)
|
||||
self.account_btn.style().polish(self.account_btn)
|
||||
|
||||
def show_login_dialog(self):
|
||||
"""Показывает диалог авторизации"""
|
||||
if not self.current_user: # Если пользователь не авторизован
|
||||
dialog = LoginDialog(self)
|
||||
if dialog.exec_():
|
||||
# Успешная авторизация
|
||||
self.current_user = dialog.auth_result
|
||||
self.update_ui_after_login()
|
||||
else: # Если пользователь уже авторизован
|
||||
self.show_account_menu()
|
||||
|
||||
def show_account_menu(self):
|
||||
"""Показывает меню аккаунта"""
|
||||
menu = QMenu(self)
|
||||
menu.setProperty("class", "account-menu")
|
||||
|
||||
# Добавляем информацию об аккаунте
|
||||
account_info = QAction(f"Аккаунт: {self.current_user.username}", menu)
|
||||
account_info.setEnabled(False)
|
||||
menu.addAction(account_info)
|
||||
|
||||
menu.addSeparator()
|
||||
|
||||
# Добавляем действия
|
||||
logout = QAction("Выйти", menu)
|
||||
logout.triggered.connect(self.logout)
|
||||
menu.addAction(logout)
|
||||
|
||||
# Показываем меню под кнопкой
|
||||
menu.exec_(self.account_btn.mapToGlobal(
|
||||
QPoint(0, self.account_btn.height())
|
||||
))
|
||||
|
||||
def logout(self):
|
||||
"""Выход из аккаунта"""
|
||||
self.current_user = None
|
||||
# Очищаем данные авторизации
|
||||
self.settings["auth"] = self.default_settings["auth"]
|
||||
self.save_settings()
|
||||
self.account_btn.setText("Войти")
|
||||
self.account_btn.setProperty("class", "login-button")
|
||||
self.account_btn.style().unpolish(self.account_btn)
|
||||
self.account_btn.style().polish(self.account_btn)
|
||||
|
||||
def create_nav_button(self, text: str, icon_path: str) -> QPushButton:
|
||||
"""Создает навигационную кнопку"""
|
||||
btn = QPushButton(text)
|
||||
btn.setIcon(QIcon(icon_path))
|
||||
btn.setIconSize(DEFAULT_ICON_SIZE)
|
||||
btn.setProperty("class", "nav-button")
|
||||
return btn
|
||||
|
||||
class SettingsDialog(QDialog):
|
||||
def __init__(self, main_window, parent=None):
|
||||
super().__init__(parent)
|
||||
self.main_window = main_window
|
||||
self.setWindowTitle("Настройки")
|
||||
self.setMinimumWidth(600)
|
||||
self.setProperty("class", "settings-dialog")
|
||||
|
||||
self.setup_ui()
|
||||
|
||||
def setup_ui(self):
|
||||
layout = QVBoxLayout(self)
|
||||
|
||||
# Создаем вкладки
|
||||
tabs = QTabWidget()
|
||||
tabs.addTab(self.create_game_tab(), "Игра")
|
||||
tabs.addTab(self.create_graphics_tab(), "Графика")
|
||||
tabs.addTab(self.create_addons_tab(), "Аддоны")
|
||||
|
||||
layout.addWidget(tabs)
|
||||
|
||||
# Кнопки
|
||||
buttons = QHBoxLayout()
|
||||
buttons.addStretch()
|
||||
|
||||
save_btn = QPushButton("Сохранить")
|
||||
save_btn.setProperty("class", "save-button")
|
||||
|
||||
cancel_btn = QPushButton("Отмена")
|
||||
cancel_btn.setProperty("class", "cancel-button")
|
||||
|
||||
buttons.addWidget(cancel_btn)
|
||||
buttons.addWidget(save_btn)
|
||||
|
||||
layout.addLayout(buttons)
|
||||
|
||||
# Подключаем сигналы
|
||||
save_btn.clicked.connect(self.accept)
|
||||
cancel_btn.clicked.connect(self.reject)
|
||||
|
||||
def create_game_tab(self):
|
||||
tab = QWidget()
|
||||
layout = QVBoxLayout(tab)
|
||||
|
||||
# Путь к игре
|
||||
self.game_path_edit = QLineEdit()
|
||||
self.game_path_edit.setText(self.main_window.get_setting("game", "path"))
|
||||
game_path = self.create_path_selector(
|
||||
self.main_window.create_label("Путь к игре:", "settings-label"),
|
||||
"Выбрать папку...",
|
||||
self.game_path_edit
|
||||
)
|
||||
layout.addLayout(game_path)
|
||||
|
||||
# Реалм-лист
|
||||
self.realm_box = QLineEdit()
|
||||
self.realm_box.setText(self.main_window.get_setting("game", "realmlist"))
|
||||
self.realm_box.setPlaceholderText("set realmlist logon.server.com")
|
||||
self.realm_box.setProperty("class", "settings-input")
|
||||
layout.addWidget(self.main_window.create_label("Реалм-лист:", "settings-label"))
|
||||
layout.addWidget(self.realm_box)
|
||||
|
||||
# Дополнительные параметры запуска
|
||||
self.launch_options = QLineEdit()
|
||||
self.launch_options.setText(self.main_window.get_setting("game", "launch_options"))
|
||||
self.launch_options.setPlaceholderText("-console -nosound")
|
||||
self.launch_options.setProperty("class", "settings-input")
|
||||
layout.addWidget(self.main_window.create_label("Параметры запуска:", "settings-label"))
|
||||
layout.addWidget(self.launch_options)
|
||||
|
||||
layout.addStretch()
|
||||
return tab
|
||||
|
||||
def create_graphics_tab(self):
|
||||
tab = QWidget()
|
||||
layout = QVBoxLayout(tab)
|
||||
|
||||
# Разрешение экрана
|
||||
self.resolution = QComboBox()
|
||||
self.resolution.addItems(["1920x1080", "1600x900", "1366x768"])
|
||||
self.resolution.setProperty("class", "settings-combobox")
|
||||
layout.addWidget(QLabel("Разрешение:"))
|
||||
layout.addWidget(self.resolution)
|
||||
|
||||
# Качество графики
|
||||
self.graphics = QComboBox()
|
||||
self.graphics.addItems(["Низкое", "Среднее", "Высокое", "Ультра"])
|
||||
self.graphics.setProperty("class", "settings-combobox")
|
||||
layout.addWidget(QLabel("Качество графики:"))
|
||||
layout.addWidget(self.graphics)
|
||||
|
||||
# Оконный режим
|
||||
self.windowed = QCheckBox("Оконный режим")
|
||||
self.windowed.setProperty("class", "settings-checkbox")
|
||||
layout.addWidget(self.windowed)
|
||||
|
||||
layout.addStretch()
|
||||
return tab
|
||||
|
||||
def create_addons_tab(self):
|
||||
tab = QWidget()
|
||||
layout = QVBoxLayout(tab)
|
||||
layout.addWidget(QLabel("Список аддонов появится в следующем обновлении"))
|
||||
layout.addStretch()
|
||||
return tab
|
||||
|
||||
def create_path_selector(self, label_text, button_text, line_edit):
|
||||
layout = QHBoxLayout()
|
||||
|
||||
layout.addWidget(label_text)
|
||||
layout.addWidget(line_edit)
|
||||
|
||||
browse_btn = QPushButton(button_text)
|
||||
browse_btn.setProperty("class", "browse-button")
|
||||
browse_btn.clicked.connect(lambda: self.browse_path(line_edit))
|
||||
layout.addWidget(browse_btn)
|
||||
|
||||
return layout
|
||||
|
||||
def browse_path(self, line_edit):
|
||||
path = QFileDialog.getExistingDirectory(self, "Выберите папку")
|
||||
if path:
|
||||
line_edit.setText(path)
|
||||
Reference in New Issue
Block a user