Add account manager and the account page is now fully functional.
Fix "Missing javaVersion" error when launching legacy Minecraft. Add settings page. (But is not fully implemented yet)
This commit is contained in:
+294
-46
@@ -1,31 +1,52 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import webbrowser
|
||||
from pathlib import Path
|
||||
|
||||
from PySide6.QtCore import Qt, QSize
|
||||
from PySide6.QtGui import QPixmap
|
||||
from PySide6.QtWidgets import QWidget, QVBoxLayout, QLabel, QListWidget, QPushButton, QHBoxLayout, QListWidgetItem, \
|
||||
QMenu
|
||||
from PySide6.QtCore import QSize, Qt, Signal, QTimer
|
||||
from PySide6.QtGui import QPixmap, QFontDatabase
|
||||
from PySide6.QtWidgets import (
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QListWidget,
|
||||
QListWidgetItem,
|
||||
QMenu,
|
||||
QMessageBox,
|
||||
QPushButton,
|
||||
QVBoxLayout,
|
||||
QWidget, QDialog, QLineEdit, QApplication,
|
||||
)
|
||||
|
||||
from constant import CLIENT_ID, MICROSOFT_VERIFY_ENDPOINT
|
||||
|
||||
from core_lib.qt.hack import move_window_to_center
|
||||
from manager.account import AccountManager, AccountSummary
|
||||
|
||||
|
||||
class AccountWidget(QWidget):
|
||||
def __init__(self, account_name, account_head: QPixmap, login_status, parent=None,
|
||||
/, delete_account_callback=None, check_account_callback=None, switch_account_callback=None):
|
||||
QWidget.__init__(self, parent)
|
||||
def __init__(self, account_name: str, account_head: QPixmap, login_status: str, parent=None,
|
||||
/, delete_account_callback=None, check_account_callback=None, switch_account_callback=None,):
|
||||
super().__init__(parent)
|
||||
|
||||
# Layout
|
||||
self.layout = QHBoxLayout(self)
|
||||
|
||||
# Widgets
|
||||
self.head_icon_label = QLabel()
|
||||
self.head_icon_label.setObjectName("HeadIcon")
|
||||
self.head_icon_label.setFixedSize(account_head.size())
|
||||
self.head_icon_label.setPixmap(account_head)
|
||||
|
||||
self.account_name = QLabel(account_name)
|
||||
|
||||
self.status_label = QLabel(login_status)
|
||||
|
||||
# Bind widgets
|
||||
self.layout.addWidget(self.head_icon_label)
|
||||
self.layout.addWidget(self.account_name)
|
||||
self.layout.addStretch()
|
||||
self.layout.addWidget(self.status_label)
|
||||
|
||||
# Callback
|
||||
self.delete_account_callback = delete_account_callback
|
||||
self.check_account_callback = check_account_callback
|
||||
self.switch_account_callback = switch_account_callback
|
||||
@@ -34,67 +55,294 @@ class AccountWidget(QWidget):
|
||||
self.customContextMenuRequested.connect(self.show_right_click_menu)
|
||||
|
||||
def show_right_click_menu(self, position):
|
||||
context_menu = QMenu(self)
|
||||
menu = QMenu(self)
|
||||
switch_action = menu.addAction("Switch to this account")
|
||||
check_action = menu.addAction("Check login status")
|
||||
menu.addSeparator()
|
||||
delete_action = menu.addAction("Delete account")
|
||||
selected = menu.exec(self.mapToGlobal(position))
|
||||
|
||||
switch_acc_action = context_menu.addAction("Switch to this account")
|
||||
check_log_stat_action = context_menu.addAction("Check login status")
|
||||
if selected == delete_action and callable(self.delete_account_callback):
|
||||
self.delete_account_callback()
|
||||
elif selected == check_action and callable(self.check_account_callback):
|
||||
self.check_account_callback()
|
||||
elif selected == switch_action and callable(self.switch_account_callback):
|
||||
self.switch_account_callback()
|
||||
|
||||
context_menu.addSeparator()
|
||||
class LoginDialog(QDialog):
|
||||
def __init__(self, app, parent=None, code="", error_message=""):
|
||||
super().__init__(parent)
|
||||
self.app = app
|
||||
self.setWindowTitle("Microsoft Login")
|
||||
self.setMinimumWidth(440)
|
||||
self.setMinimumHeight(380)
|
||||
self.resize(440, 420)
|
||||
move_window_to_center(self)
|
||||
|
||||
delete_account_action = context_menu.addAction("Delete account")
|
||||
# Layout
|
||||
self.layout = QVBoxLayout(self)
|
||||
self.layout.setContentsMargins(24, 22, 24, 22)
|
||||
self.layout.setSpacing(14)
|
||||
|
||||
global_position = self.mapToGlobal(position)
|
||||
# Top widgets
|
||||
self.title_label = QLabel("Login Minecraft Account")
|
||||
self.title_label.setObjectName("dialogTitle")
|
||||
self.help_label = QLabel("Copy the code below, then continue in your browser.")
|
||||
self.help_label.setObjectName("helpText")
|
||||
self.help_label.setWordWrap(True)
|
||||
|
||||
selected_action = context_menu.exec(global_position)
|
||||
# Center
|
||||
self.code_edit = QLineEdit()
|
||||
self.code_edit.setObjectName("codeEdit")
|
||||
self.code_edit.setReadOnly(True)
|
||||
self.code_edit.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
self.code_edit.setFont(QFontDatabase.systemFont(QFontDatabase.SystemFont.FixedFont))
|
||||
self.code_edit.setText(code)
|
||||
self.code_label = self.code_edit
|
||||
self.error_label = QLabel()
|
||||
self.error_label.setObjectName("errorMessage")
|
||||
self.error_label.setWordWrap(True)
|
||||
self.error_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
|
||||
|
||||
if selected_action == delete_account_action:
|
||||
# handle callback here
|
||||
if callable(self.delete_account_callback): self.delete_account_callback()
|
||||
elif selected_action == check_log_stat_action and callable(self.check_account_callback): self.check_account_callback()
|
||||
elif selected_action == switch_acc_action and callable(self.switch_account_callback): self.switch_account_callback()
|
||||
# Button
|
||||
self.copy_code_button = QPushButton("Copy code")
|
||||
self.copy_code_button.setObjectName("copyCodeButton")
|
||||
self.open_browser_button = QPushButton("Open browser")
|
||||
self.open_browser_button.setObjectName("openBrowserButton")
|
||||
|
||||
self.center_layout = QVBoxLayout()
|
||||
self.center_layout.setSpacing(10)
|
||||
self.center_layout.addWidget(self.code_edit)
|
||||
self.center_layout.addWidget(self.error_label)
|
||||
|
||||
self.button_layout = QHBoxLayout()
|
||||
self.button_layout.setSpacing(8)
|
||||
self.button_layout.addWidget(self.copy_code_button)
|
||||
self.button_layout.addWidget(self.open_browser_button)
|
||||
|
||||
self.layout.addWidget(self.title_label)
|
||||
self.layout.addWidget(self.help_label)
|
||||
self.layout.addStretch()
|
||||
self.layout.addLayout(self.center_layout)
|
||||
self.layout.addStretch()
|
||||
self.layout.addLayout(self.button_layout)
|
||||
self.set_error_message(error_message)
|
||||
|
||||
self.copy_code_button.clicked.connect(self.copy_code)
|
||||
|
||||
self.app.apply_qss(Path("login_dialog.qss"), self.setStyleSheet)
|
||||
|
||||
@property
|
||||
def code(self):
|
||||
return self.code_edit.text()
|
||||
|
||||
def set_code(self, code):
|
||||
self.code_edit.setText(str(code or ""))
|
||||
|
||||
def copy_code(self):
|
||||
QApplication.clipboard().setText(self.code)
|
||||
self.copy_code_button.setText("Copied!")
|
||||
self.copy_code_button.setEnabled(False)
|
||||
QTimer.singleShot(1200, self._reset_copy_button)
|
||||
|
||||
def _reset_copy_button(self):
|
||||
self.copy_code_button.setText("Copy code")
|
||||
self.copy_code_button.setEnabled(True)
|
||||
|
||||
def set_error_message(self, message):
|
||||
message = str(message or "").strip()
|
||||
self.error_label.setText(message)
|
||||
self.error_label.setVisible(bool(message))
|
||||
|
||||
class AccountPage(QWidget):
|
||||
account_authenticated = Signal(dict)
|
||||
|
||||
def __init__(self, app, parent=None):
|
||||
QWidget.__init__(self, parent)
|
||||
super().__init__(parent)
|
||||
self.app = app
|
||||
self.widget_manager = app.widget_manager
|
||||
self.account_manager: AccountManager = app.account_manager
|
||||
self.client_id = CLIENT_ID
|
||||
|
||||
self.layout = QHBoxLayout()
|
||||
self.login_dialog = None
|
||||
|
||||
self.main_layout = QVBoxLayout()
|
||||
self.right_layout = QVBoxLayout()
|
||||
# Layout
|
||||
main_layout = QVBoxLayout()
|
||||
right_layout = QVBoxLayout()
|
||||
layout = QHBoxLayout(self)
|
||||
|
||||
# Widgets
|
||||
self.account_list = QListWidget(self)
|
||||
|
||||
self.add_account_button = QPushButton("Add Account")
|
||||
self.refresh_button = QPushButton("Refresh all")
|
||||
self.delete_all_button = QPushButton("Delete All")
|
||||
|
||||
self.main_layout.addWidget(self.account_list)
|
||||
self.right_layout.addWidget(self.add_account_button)
|
||||
self.right_layout.addWidget(self.refresh_button)
|
||||
self.right_layout.addWidget(self.delete_all_button)
|
||||
self.right_layout.addStretch()
|
||||
# Button click handle
|
||||
self.add_account_button.clicked.connect(self.start_login_page)
|
||||
self.refresh_button.clicked.connect(self.refresh_accounts)
|
||||
self.delete_all_button.clicked.connect(self.account_manager.delete_all_accounts)
|
||||
|
||||
self.layout.addLayout(self.main_layout)
|
||||
self.layout.addLayout(self.right_layout)
|
||||
# Bind widget
|
||||
main_layout.addWidget(self.account_list)
|
||||
right_layout.addWidget(self.add_account_button)
|
||||
right_layout.addWidget(self.refresh_button)
|
||||
right_layout.addWidget(self.delete_all_button)
|
||||
right_layout.addStretch()
|
||||
|
||||
self.setLayout(self.layout)
|
||||
layout.addLayout(main_layout, 1)
|
||||
layout.addLayout(right_layout)
|
||||
|
||||
self.test()
|
||||
self.account_manager.accounts_changed.connect(self._render_accounts)
|
||||
self.account_manager.current_account_changed.connect(self._current_account_changed)
|
||||
self.account_manager.login_signal.device_code_ready.connect(self._show_device_code)
|
||||
self.account_manager.login_signal.login_succeeded.connect(self._login_succeeded)
|
||||
self.account_manager.login_signal.login_failed.connect(self._login_failed)
|
||||
self.account_manager.login_signal.finished.connect(self._login_finished)
|
||||
self.account_manager.head_signal.updated.connect(self._account_head_updated)
|
||||
self._render_accounts()
|
||||
|
||||
def test(self):
|
||||
head_icon = self.app.get_icon(Path(self.app.icon_dir, "head.png"))
|
||||
for i in range(1, 11):
|
||||
name = f"Player_{i}"
|
||||
head_pixmap = head_icon.pixmap(QSize(50, 50))
|
||||
list_item = QListWidgetItem()
|
||||
def _render_accounts(self):
|
||||
self.account_list.clear()
|
||||
|
||||
def delete():
|
||||
self.account_list.removeItemWidget(list_item)
|
||||
item = AccountWidget(name, head_pixmap, self, delete_account_callback=delete)
|
||||
list_item.setSizeHint(item.sizeHint())
|
||||
self.account_list.addItem(list_item)
|
||||
self.account_list.setItemWidget(list_item, item)
|
||||
current_id = self.account_manager.get_current_account_id()
|
||||
accounts = self.account_manager.list_accounts()
|
||||
|
||||
# Add suggestion to account list if no account available
|
||||
if not accounts:
|
||||
empty_item = QListWidgetItem("No accounts yet. Use Add Account to sign in.")
|
||||
empty_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
empty_item.setFlags(Qt.ItemFlag.NoItemFlags)
|
||||
self.account_list.addItem(empty_item)
|
||||
|
||||
for account in accounts:
|
||||
self._add_account_item(account, account.id == current_id)
|
||||
|
||||
def _add_account_item(self, account: AccountSummary, is_current: bool):
|
||||
list_item = QListWidgetItem()
|
||||
list_item.setData(Qt.ItemDataRole.UserRole, account.id)
|
||||
|
||||
head_size = QSize(32, 32)
|
||||
widget = AccountWidget(
|
||||
account.display_name,
|
||||
self.account_manager.get_head_pixmap(account.avatar, head_size),
|
||||
"Current" if is_current else "Available",
|
||||
self,
|
||||
delete_account_callback=lambda: self._delete_account(account.id),
|
||||
check_account_callback=lambda: self._show_account_status(account.id),
|
||||
switch_account_callback=lambda: self._switch_account(account.id),
|
||||
)
|
||||
|
||||
list_item.setSizeHint(widget.sizeHint())
|
||||
self.account_list.addItem(list_item)
|
||||
self.account_list.setItemWidget(list_item, widget)
|
||||
|
||||
if not self.account_manager.has_saved_head(account.avatar):
|
||||
self.account_manager.request_account_head(
|
||||
account.id,
|
||||
account.minecraft_name or account.username,
|
||||
max(head_size.width(), head_size.height()),
|
||||
)
|
||||
|
||||
def _account_head_updated(self, account_id: str):
|
||||
account = self.account_manager.get_account(account_id)
|
||||
if account is None:
|
||||
return
|
||||
|
||||
for row in range(self.account_list.count()):
|
||||
item = self.account_list.item(row)
|
||||
if item.data(Qt.ItemDataRole.UserRole) != account_id:
|
||||
continue
|
||||
widget = self.account_list.itemWidget(item)
|
||||
if isinstance(widget, AccountWidget):
|
||||
widget.head_icon_label.setPixmap(
|
||||
self.account_manager.get_head_pixmap(account.avatar, QSize(32, 32))
|
||||
)
|
||||
break
|
||||
|
||||
def _delete_account(self, account_id: str):
|
||||
if self.account_manager.remove_account(account_id):
|
||||
self.account_manager.save()
|
||||
|
||||
def _show_account_status(self, account_id: str):
|
||||
account = self.account_manager.get_account(account_id)
|
||||
|
||||
if account is None:
|
||||
return
|
||||
|
||||
QMessageBox.information(
|
||||
self,
|
||||
"Account status",
|
||||
f"{account.display_name} — Minecraft profile "
|
||||
f"{'available' if account.minecraft_uuid else 'not available'}",
|
||||
)
|
||||
|
||||
def _switch_account(self, account_id: str):
|
||||
self.account_manager.set_current_account(account_id)
|
||||
self.account_manager.save()
|
||||
|
||||
def _current_account_changed(self, _account_id):
|
||||
self._render_accounts()
|
||||
|
||||
def refresh_accounts(self):
|
||||
self.account_manager.reload()
|
||||
|
||||
# Microsoft sign-in
|
||||
def start_login_page(self):
|
||||
# Active existing login dialog
|
||||
if self.login_dialog is not None:
|
||||
self.login_dialog.raise_()
|
||||
self.login_dialog.activateWindow()
|
||||
return
|
||||
|
||||
# Create login dialog
|
||||
self.add_account_button.setEnabled(False)
|
||||
self.login_dialog = LoginDialog(parent=self, app=self.app)
|
||||
self.login_dialog.help_label.setText("Requesting a Microsoft sign-in code…")
|
||||
self.login_dialog.copy_code_button.setEnabled(False)
|
||||
self.login_dialog.open_browser_button.setEnabled(False)
|
||||
self.login_dialog.open_browser_button.clicked.connect(self.open_sign_in_page)
|
||||
self.login_dialog.rejected.connect(self.account_manager.cancel_login)
|
||||
self.login_dialog.show()
|
||||
|
||||
self.account_manager.start_login()
|
||||
|
||||
def _show_device_code(self, device: dict):
|
||||
self._verification_uri = device.get("verification_uri", MICROSOFT_VERIFY_ENDPOINT)
|
||||
|
||||
if self.login_dialog is not None:
|
||||
self.login_dialog.help_label.setText(
|
||||
device.get("message") or "Copy the code below, then continue in your browser."
|
||||
)
|
||||
self.login_dialog.set_code(device.get("user_code", ""))
|
||||
self.login_dialog.copy_code_button.setEnabled(True)
|
||||
self.login_dialog.open_browser_button.setEnabled(True)
|
||||
|
||||
self.open_sign_in_page()
|
||||
|
||||
def open_sign_in_page(self):
|
||||
webbrowser.open(self._verification_uri)
|
||||
|
||||
def _login_succeeded(self, account: dict):
|
||||
created_id = self.account_manager.login_succeeded(account)
|
||||
|
||||
# Cleanup
|
||||
if self.login_dialog is not None:
|
||||
dialog = self.login_dialog
|
||||
self.login_dialog = None
|
||||
dialog.accept()
|
||||
dialog.deleteLater()
|
||||
|
||||
if created_id is not None:
|
||||
self.account_authenticated.emit({"account_id": created_id})
|
||||
|
||||
def _login_failed(self, message: str):
|
||||
if self.login_dialog is not None:
|
||||
self.login_dialog.set_error_message(message)
|
||||
self.login_dialog.help_label.setText("Microsoft sign-in could not be completed.")
|
||||
self.login_dialog.copy_code_button.setEnabled(False)
|
||||
self.login_dialog.open_browser_button.setEnabled(False)
|
||||
else:
|
||||
QMessageBox.critical(self, "Microsoft sign-in failed", message)
|
||||
|
||||
def _login_finished(self):
|
||||
self.add_account_button.setEnabled(True)
|
||||
|
||||
+47
-43
@@ -197,7 +197,7 @@ class LaunchProfilePage(QWidget):
|
||||
self.bottom_layout = QHBoxLayout()
|
||||
|
||||
# Widgets
|
||||
self.status_label = QLabel("Select a version to launch")
|
||||
self.status_label = QLabel("Select a profile to launch")
|
||||
self.status_label.setStyleSheet("font-size: 12px; font-weight: 600;")
|
||||
self.status_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
@@ -363,50 +363,54 @@ class LaunchProfilePage(QWidget):
|
||||
self.profile_button.set_popup_open(False)
|
||||
|
||||
def launch_profile(self) -> None:
|
||||
profile_id = self.profile_button.property("profile_id")
|
||||
self.status_label.setText("Preparing to launch...")
|
||||
launch_profile = self.app.profile_manager.get_launch_profile(profile_id)
|
||||
try:
|
||||
profile_id = self.profile_button.property("profile_id")
|
||||
self.status_label.setText("Preparing to launch...")
|
||||
launch_profile = self.app.profile_manager.get_launch_profile(profile_id)
|
||||
|
||||
if launch_profile is None:
|
||||
self.status_label.setText("Unable to load the selected profile.")
|
||||
return
|
||||
if launch_profile is None:
|
||||
self.status_label.setText("Unable to load the selected profile.")
|
||||
return
|
||||
|
||||
if self.launch_manager.is_profile_running(launch_profile.profile_id):
|
||||
request = AskForRequest(
|
||||
"Launch Request",
|
||||
"Target profile {} is running. Are you sure you want to launch it again?".format(
|
||||
launch_profile.display_name
|
||||
),
|
||||
)
|
||||
self.widget_mgr.signal.askyesno.emit(request)
|
||||
if self.launch_manager.is_profile_running(launch_profile.profile_id):
|
||||
request = AskForRequest(
|
||||
"Launch Request",
|
||||
"Target profile {} is running. Are you sure you want to launch it again?".format(
|
||||
launch_profile.display_name
|
||||
),
|
||||
)
|
||||
self.widget_mgr.signal.askyesno.emit(request)
|
||||
|
||||
if not request.wait(60):
|
||||
if not request.wait(60):
|
||||
self.widget_mgr.signal.show_error_message.emit(
|
||||
"Time out waiting for request for launch."
|
||||
)
|
||||
return
|
||||
|
||||
if not request.result() is True:
|
||||
return
|
||||
|
||||
result = self.launch_manager.launch(launch_profile)
|
||||
|
||||
if result.success:
|
||||
self.status_label.setText("Launched!")
|
||||
log_window = ProcessLogWindow(
|
||||
result.process,
|
||||
launch_profile.profile_id,
|
||||
title=f"Minecraft Log - {launch_profile.display_name} ({launch_profile.version_id})",
|
||||
parent=None,
|
||||
finished_callback=self.launch_manager.profile_finished,
|
||||
)
|
||||
for warning in result.warnings:
|
||||
log_window.append_message(f"[launcher warning] {warning}")
|
||||
self.process_log_windows.append(log_window)
|
||||
log_window.show()
|
||||
else:
|
||||
self.widget_mgr.signal.show_error_message.emit(
|
||||
"Time out waiting for request for launch."
|
||||
"Unable to launch profile {}: {}".format(
|
||||
launch_profile.display_name,
|
||||
result.error_message,
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
if not request.result() is True:
|
||||
return
|
||||
|
||||
result = self.launch_manager.launch(launch_profile)
|
||||
|
||||
if result.success:
|
||||
self.status_label.setText("Launched!")
|
||||
log_window = ProcessLogWindow(
|
||||
result.process,
|
||||
launch_profile.profile_id,
|
||||
title=f"Minecraft Log - {launch_profile.display_name} ({launch_profile.version_id})",
|
||||
parent=None,
|
||||
)
|
||||
for warning in result.warnings:
|
||||
log_window.append_message(f"[launcher warning] {warning}")
|
||||
self.process_log_windows.append(log_window)
|
||||
log_window.show()
|
||||
else:
|
||||
self.widget_mgr.signal.show_error_message.emit(
|
||||
"Unable to launch profile {}: {}".format(
|
||||
launch_profile.display_name,
|
||||
result.error_message,
|
||||
)
|
||||
)
|
||||
finally:
|
||||
self.status_label.setText("Select a profile to launch")
|
||||
|
||||
@@ -0,0 +1,296 @@
|
||||
from PySide6.QtCore import Qt, Signal
|
||||
from PySide6.QtGui import QPalette, QFont
|
||||
from PySide6.QtWidgets import (
|
||||
QCheckBox,
|
||||
QComboBox,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QPushButton,
|
||||
QSlider,
|
||||
QStackedWidget,
|
||||
QVBoxLayout,
|
||||
QWidget, QLineEdit,
|
||||
)
|
||||
|
||||
|
||||
class SettingsItem(QPushButton):
|
||||
"""
|
||||
A modern look settings button.
|
||||
"""
|
||||
clicked_page = Signal()
|
||||
|
||||
def __init__(self, title: str, description: str = ""):
|
||||
super().__init__()
|
||||
|
||||
self.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
# Widget size
|
||||
self.setMinimumHeight(68)
|
||||
self.setMaximumWidth(800)
|
||||
|
||||
# Layout
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setContentsMargins(16, 10, 16, 10)
|
||||
layout.setSpacing(3)
|
||||
|
||||
# Widgets
|
||||
self.title_label = QLabel(title)
|
||||
self.title_label.setAttribute(
|
||||
Qt.WidgetAttribute.WA_TransparentForMouseEvents
|
||||
)
|
||||
|
||||
self.description_label = QLabel(description)
|
||||
self.description_label.setAttribute(
|
||||
Qt.WidgetAttribute.WA_TransparentForMouseEvents
|
||||
)
|
||||
|
||||
layout.addWidget(self.title_label)
|
||||
|
||||
# Show description if existing
|
||||
if description:
|
||||
layout.addWidget(self.description_label)
|
||||
|
||||
self.update_palette()
|
||||
|
||||
def update_palette(self):
|
||||
"""
|
||||
Apply system palette
|
||||
:return:
|
||||
"""
|
||||
palette = self.palette()
|
||||
|
||||
button_text = palette.color(QPalette.ColorRole.ButtonText)
|
||||
placeholder_text = palette.color(QPalette.ColorRole.PlaceholderText)
|
||||
|
||||
title_palette = self.title_label.palette()
|
||||
title_palette.setColor(
|
||||
QPalette.ColorRole.WindowText,
|
||||
button_text,
|
||||
)
|
||||
self.title_label.setPalette(title_palette)
|
||||
|
||||
description_palette = self.description_label.palette()
|
||||
description_palette.setColor(
|
||||
QPalette.ColorRole.WindowText,
|
||||
placeholder_text,
|
||||
)
|
||||
self.description_label.setPalette(description_palette)
|
||||
|
||||
title_font = self.title_label.font()
|
||||
title_font.setPointSize(11)
|
||||
title_font.setWeight(QFont.Weight.Bold)
|
||||
self.title_label.setFont(title_font)
|
||||
|
||||
description_font = self.description_label.font()
|
||||
description_font.setPointSize(9)
|
||||
self.description_label.setFont(description_font)
|
||||
|
||||
|
||||
class SettingsSubPage(QWidget):
|
||||
def __init__(self, title: str, back_callback):
|
||||
super().__init__()
|
||||
|
||||
main_layout = QVBoxLayout(self)
|
||||
main_layout.setContentsMargins(20, 16, 20, 20)
|
||||
main_layout.setSpacing(16)
|
||||
|
||||
header_layout = QHBoxLayout()
|
||||
|
||||
self.back_button = QPushButton("←")
|
||||
self.back_button.setFixedSize(36, 36)
|
||||
self.back_button.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
self.back_button.clicked.connect(back_callback)
|
||||
|
||||
self.title_label = QLabel(title)
|
||||
|
||||
title_font = self.title_label.font()
|
||||
title_font.setPointSize(15)
|
||||
title_font.setBold(True)
|
||||
self.title_label.setFont(title_font)
|
||||
|
||||
header_layout.addWidget(self.back_button)
|
||||
header_layout.addWidget(self.title_label)
|
||||
header_layout.addStretch()
|
||||
|
||||
self.content_layout = QVBoxLayout()
|
||||
self.content_layout.setSpacing(12)
|
||||
|
||||
main_layout.addLayout(header_layout)
|
||||
main_layout.addLayout(self.content_layout)
|
||||
main_layout.addStretch()
|
||||
|
||||
self.update_palette()
|
||||
|
||||
def update_palette(self):
|
||||
# Apply system palette
|
||||
palette = self.palette()
|
||||
|
||||
button_color = palette.color(QPalette.ColorRole.Button)
|
||||
border_color = palette.color(QPalette.ColorRole.Mid)
|
||||
|
||||
self.back_button.setStyleSheet("""
|
||||
QPushButton {
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
background-color: transparent;
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
QPushButton:hover {
|
||||
background-color: palette(button);
|
||||
}
|
||||
|
||||
QPushButton:pressed {
|
||||
background-color: palette(midlight);
|
||||
}
|
||||
""")
|
||||
|
||||
def changeEvent(self, event):
|
||||
# Update when system palette change
|
||||
if event.type() in (
|
||||
event.Type.PaletteChange,
|
||||
event.Type.ApplicationPaletteChange,
|
||||
):
|
||||
self.update_palette()
|
||||
|
||||
super().changeEvent(event)
|
||||
|
||||
|
||||
class SettingsPage(QWidget):
|
||||
def __init__(self, app, parent=None):
|
||||
super().__init__(parent)
|
||||
self.app = app
|
||||
|
||||
self.setWindowTitle("Settings")
|
||||
self.resize(560, 440)
|
||||
|
||||
root_layout = QVBoxLayout(self)
|
||||
root_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
self.stack = QStackedWidget()
|
||||
|
||||
self.main_page = self.create_main_page()
|
||||
self.general_page = self.create_general_page()
|
||||
self.appearance_page = self.create_appearance_page()
|
||||
self.profile_page = self.create_profile_page()
|
||||
|
||||
self.stack.addWidget(self.main_page)
|
||||
self.stack.addWidget(self.general_page)
|
||||
self.stack.addWidget(self.appearance_page)
|
||||
self.stack.addWidget(self.profile_page)
|
||||
|
||||
root_layout.addWidget(self.stack)
|
||||
|
||||
def create_main_page(self) -> QWidget:
|
||||
page = QWidget()
|
||||
|
||||
layout = QVBoxLayout(page)
|
||||
layout.setContentsMargins(24, 20, 24, 24)
|
||||
layout.setSpacing(12)
|
||||
|
||||
title = QLabel("Settings")
|
||||
|
||||
title_font = title.font()
|
||||
title_font.setPointSize(18)
|
||||
title_font.setBold(True)
|
||||
title.setFont(title_font)
|
||||
|
||||
# Setting options
|
||||
info_button = SettingsItem(
|
||||
"Wait!",
|
||||
"Setting page is not full implemented yet.",
|
||||
)
|
||||
|
||||
general_button = SettingsItem(
|
||||
"General",
|
||||
"Some general settings for launcher ",
|
||||
)
|
||||
general_button.clicked.connect(
|
||||
lambda: self.open_page(self.general_page)
|
||||
)
|
||||
|
||||
appearance_button = SettingsItem(
|
||||
"Appearance",
|
||||
"Settings to change the launcher look like",
|
||||
)
|
||||
appearance_button.clicked.connect(
|
||||
lambda: self.open_page(self.appearance_page)
|
||||
)
|
||||
|
||||
profile_button = SettingsItem(
|
||||
"Profile",
|
||||
"Store profile settings",
|
||||
)
|
||||
profile_button.clicked.connect(
|
||||
lambda: self.open_page(self.profile_page)
|
||||
)
|
||||
|
||||
layout.addWidget(title)
|
||||
layout.addSpacing(8)
|
||||
layout.addWidget(info_button)
|
||||
layout.addWidget(general_button)
|
||||
layout.addWidget(appearance_button)
|
||||
layout.addWidget(profile_button)
|
||||
layout.addStretch()
|
||||
|
||||
return page
|
||||
|
||||
def create_general_page(self) -> QWidget:
|
||||
page = SettingsSubPage("General", self.go_back)
|
||||
|
||||
language_label = QLabel("Language")
|
||||
|
||||
language_combo = QComboBox()
|
||||
language_combo.addItems([
|
||||
"English",
|
||||
"Chinese (Traditional)",
|
||||
])
|
||||
|
||||
launcher_dir_label = QLabel("Current launcher working directory")
|
||||
launcher_dir = QLineEdit(self.app.work_dir.as_posix())
|
||||
launcher_dir.setReadOnly(True)
|
||||
|
||||
program_dir_label = QLabel("Current program directory")
|
||||
program_dir = QLineEdit(self.app.program_dir.as_posix())
|
||||
program_dir.setReadOnly(True)
|
||||
|
||||
page.content_layout.addWidget(launcher_dir_label)
|
||||
page.content_layout.addWidget(launcher_dir)
|
||||
page.content_layout.addWidget(program_dir_label)
|
||||
page.content_layout.addWidget(program_dir)
|
||||
page.content_layout.addSpacing(8)
|
||||
page.content_layout.addWidget(language_label)
|
||||
page.content_layout.addWidget(language_combo)
|
||||
|
||||
return page
|
||||
|
||||
def create_appearance_page(self) -> QWidget:
|
||||
page = SettingsSubPage("Appearance", self.go_back)
|
||||
|
||||
theme_label = QLabel("Theme")
|
||||
|
||||
theme_combo = QComboBox()
|
||||
theme_combo.addItems([
|
||||
"Follow system settings",
|
||||
"Light (unfinished)",
|
||||
"Dark",
|
||||
])
|
||||
|
||||
page.content_layout.addWidget(theme_label)
|
||||
page.content_layout.addWidget(theme_combo)
|
||||
|
||||
return page
|
||||
|
||||
def create_profile_page(self) -> QWidget:
|
||||
page = SettingsSubPage("Profile", self.go_back)
|
||||
|
||||
test_label = QLabel("Still in development...")
|
||||
|
||||
page.content_layout.addWidget(test_label)
|
||||
|
||||
return page
|
||||
|
||||
def open_page(self, page: QWidget):
|
||||
self.stack.setCurrentWidget(page)
|
||||
|
||||
def go_back(self):
|
||||
self.stack.setCurrentWidget(self.main_page)
|
||||
+3
-2
@@ -23,7 +23,7 @@ from core_lib.game.version_info import (find_useful_part_in_specific_version_man
|
||||
download_core_file)
|
||||
from core_lib.qt.hack import is_main_thread
|
||||
from core_lib.runtime.java import find_java_installations
|
||||
from manager.game import ManifestManager
|
||||
from manager.game import ManifestManager, JavaManager
|
||||
from manager.widgets import SelectPathRequest, AskForRequest
|
||||
|
||||
MAX_DOWNLOAD_ATTEMPTS = 3
|
||||
@@ -37,6 +37,7 @@ class TestPage(QWidget):
|
||||
self.parent = parent
|
||||
self.widget_mgr = widget_manager
|
||||
self.manifest_mgr: ManifestManager = app.game_manager.manifest
|
||||
self.java_mgr: JavaManager = app.game_manager.java
|
||||
|
||||
self.resize(800, 600)
|
||||
self.setWindowFlags(self.windowFlags() | Qt.WindowType.WindowMaximizeButtonHint)
|
||||
@@ -781,7 +782,7 @@ class TestPage(QWidget):
|
||||
return
|
||||
|
||||
# Use found java runtime (if available)
|
||||
java_path = self.get_support_runtime_jvm_executable(
|
||||
java_path = self.java_mgr.get_support_runtime_jvm_executable(
|
||||
java_major_version,
|
||||
no_error=True
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user