Code some ui and fix parse library will throw exception sometimes.

This commit is contained in:
wei
2026-07-23 00:47:44 +08:00
parent 77c22f5efe
commit 8d850408cd
11 changed files with 745 additions and 28 deletions
+19 -3
View File
@@ -11,7 +11,10 @@ from PySide6.QtWidgets import QApplication, QMainWindow, QLabel, QVBoxLayout, QW
from core_lib.qt.hack import move_window_to_center, is_light_theme, \ from core_lib.qt.hack import move_window_to_center, is_light_theme, \
recolor_icon recolor_icon
from core_lib.qt import messagebox from core_lib.qt import messagebox
from core_lib.launcher.object import Launcher as LauncherObject
from pages.account import AccountPage from pages.account import AccountPage
from pages.create_profile import CreateProfile
from pages.test import TestPage from pages.test import TestPage
LAUNCHER_VERSION = "alpha_0.0.2" LAUNCHER_VERSION = "alpha_0.0.2"
@@ -63,7 +66,7 @@ class ToolBar(QtWidgets.QToolBar):
button.setToolButtonStyle( button.setToolButtonStyle(
Qt.ToolButtonStyle.ToolButtonTextBesideIcon Qt.ToolButtonStyle.ToolButtonTextBesideIcon
) )
button.setFixedWidth(150) button.setMinimumWidth(60)
def add_button(self, text=None, icon: QIcon=None) -> QToolButton: def add_button(self, text=None, icon: QIcon=None) -> QToolButton:
button = QtWidgets.QToolButton(self) button = QtWidgets.QToolButton(self)
@@ -71,6 +74,8 @@ class ToolBar(QtWidgets.QToolBar):
if text: if text:
text = " "+text text = " "+text
button.setText(text) button.setText(text)
# else:
# button.setText("\u00a0")
if icon: if icon:
button.setIcon(icon) button.setIcon(icon)
@@ -86,6 +91,7 @@ class ToolBar(QtWidgets.QToolBar):
) )
button.setAutoRaise(True) button.setAutoRaise(True)
button.setFixedHeight(60)
return button return button
@@ -93,7 +99,7 @@ class ToolBar(QtWidgets.QToolBar):
self._update_widget_size(self.orientation()) self._update_widget_size(self.orientation())
self.update() self.update()
class Launcher(QApplication): class Launcher(QApplication, LauncherObject):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
# Window config # Window config
@@ -157,10 +163,14 @@ class Launcher(QApplication):
# Account page # Account page
self.account_page = AccountPage(self, self.main_window) self.account_page = AccountPage(self, self.main_window)
# Profile page
self.create_profile_page = CreateProfile(self, self.main_window)
# Add page # Add page
self.central.addWidget(self.home_page) self.central.addWidget(self.home_page)
self.central.addWidget(self.test_page) self.central.addWidget(self.test_page)
self.central.addWidget(self.account_page) self.central.addWidget(self.account_page)
self.central.addWidget(self.create_profile_page)
# Bind toolbar and center widget # Bind toolbar and center widget
self.main_window.addToolBar(Qt.ToolBarArea.LeftToolBarArea, self.toolbar) self.main_window.addToolBar(Qt.ToolBarArea.LeftToolBarArea, self.toolbar)
@@ -182,11 +192,13 @@ class Launcher(QApplication):
# Account btn (will be moved to the bottom (or last one item) of the toolbar # Account btn (will be moved to the bottom (or last one item) of the toolbar
self.open_account_page = self.toolbar.add_button(icon=self.get_icon(Path(self.icon_dir, "head.png"))) self.open_account_page = self.toolbar.add_button(icon=self.get_icon(Path(self.icon_dir, "head.png")))
# Profile page # Profile btn
self.open_create_profile_page = self.toolbar.add_button(icon=self.get_icon(Path(self.icon_dir, "add_temp.png")))
# Bind tool buttons # Bind tool buttons
self.toolbar.addWidget(self.home_button) self.toolbar.addWidget(self.home_button)
self.toolbar.addWidget(self.open_test_page) self.toolbar.addWidget(self.open_test_page)
self.toolbar.addWidget(self.open_create_profile_page)
self.toolbar.setMovable(True) self.toolbar.setMovable(True)
self.toolbar.setFloatable(False) self.toolbar.setFloatable(False)
self.toolbar.setAllowedAreas(Qt.ToolBarArea.AllToolBarAreas) self.toolbar.setAllowedAreas(Qt.ToolBarArea.AllToolBarAreas)
@@ -208,6 +220,10 @@ class Launcher(QApplication):
lambda: self.set_current_page(self.account_page, self.open_account_page) lambda: self.set_current_page(self.account_page, self.open_account_page)
) )
self.open_create_profile_page.clicked.connect(
lambda: self.set_current_page(self.create_profile_page, self.open_create_profile_page)
)
self.set_current_page(self.home_page, self.home_button) self.set_current_page(self.home_page, self.home_button)
def exec(self): def exec(self):
+16 -1
View File
@@ -19,16 +19,18 @@ THREADED_DOWNLOAD_MAX_WORKERS = 10
logger = logging.getLogger("Launcher.CoreLib") logger = logging.getLogger("Launcher.CoreLib")
class FileObject: class FileObject:
def __init__(self, path: Path, url=None, sha1=None, sha256=None, md5=None, size=None): def __init__(self, path: Path, url=None, sha1=None, sha256=None, sha512=None, md5=None, size=None):
self.__path = path self.__path = path
self.expected_sha1 = sha1 self.expected_sha1 = sha1
self.expected_sha256 = sha256 self.expected_sha256 = sha256
self.expected_sha512 = sha512
self.expected_md5 = md5 self.expected_md5 = md5
self.expected_size = size self.expected_size = size
self.__sha1 = None self.__sha1 = None
self.__sha256 = None self.__sha256 = None
self.__sha512 = None
self.__md5 = None self.__md5 = None
self.url = url self.url = url
@@ -73,6 +75,13 @@ class FileObject:
return self.__sha256 return self.__sha256
@property
def sha512(self):
if self.__sha512 is None:
self.__sha512 = self._calculate_hash("sha512")
return self.__sha512
@property @property
def md5(self): def md5(self):
if self.__md5 is None: if self.__md5 is None:
@@ -86,6 +95,9 @@ class FileObject:
def verify_sha256(self, expected_hash=None): def verify_sha256(self, expected_hash=None):
return self.sha256 == expected_hash if expected_hash else self.expected_sha256 == self.sha256 return self.sha256 == expected_hash if expected_hash else self.expected_sha256 == self.sha256
def verify_sha512(self, expected_hash=None):
return self.sha512 == expected_hash if expected_hash else self.expected_sha512 == self.sha512
def verify_md5(self, expected_hash=None): def verify_md5(self, expected_hash=None):
return self.sha1 == expected_hash if expected_hash else self.expected_sha1 == self.sha1 return self.sha1 == expected_hash if expected_hash else self.expected_sha1 == self.sha1
@@ -96,6 +108,9 @@ class FileObject:
if algorithm == "sha256": if algorithm == "sha256":
return self.verify_sha256(expected_hash if expected_hash else self.expected_sha256) return self.verify_sha256(expected_hash if expected_hash else self.expected_sha256)
if algorithm == "sha512":
return self.verify_sha512(expected_hash if expected_hash else self.expected_sha512)
if algorithm == "md5": if algorithm == "md5":
return self.verify_md5(expected_hash if expected_hash else self.expected_md5) return self.verify_md5(expected_hash if expected_hash else self.expected_md5)
+52 -1
View File
@@ -1,4 +1,5 @@
import logging import logging
import re
import shlex import shlex
from core_lib.game.library import get_natives_platform_info from core_lib.game.library import get_natives_platform_info
@@ -229,4 +230,54 @@ def generate_launch_command(arg_maps: ArgumentMappings, main_class: str, java_pa
cmd.append(main_class) cmd.append(main_class)
cmd.extend(parsed_game_args) cmd.extend(parsed_game_args)
return cmd return cmd
def is_a_argument(arg: str, startswith="--"):
pattern = re.compile(rf"{startswith}(.+)")
return pattern.match(arg) is not None
def generate_argument_details(arguments: list[str | dict], startswith="--") -> list[dict]:
items = []
for index, arg in enumerate(arguments):
if isinstance(arg, str) and is_a_argument(arg, startswith):
items.append({
"value": arg,
"type": "argument",
"dataType": "string",
})
if index > 0:
previous = items[index - 1]
if previous["type"] == "argument":
previous["isFlag"] = True
elif isinstance(arg, str):
items.append({
"value": arg,
"type": "value",
})
elif isinstance(arg, dict):
value = arg.get("value", None)
if not value:
raise ValueError("Dictionary argument must contain a value")
items.append({
"value": value,
"type": "argument",
"dataType": "dictionary",
"dictValue": arg,
})
return items
def is_same_argument(item: dict, another: dict) -> bool:
pass
+82
View File
@@ -4,6 +4,7 @@ import re
import zipfile import zipfile
from pathlib import Path from pathlib import Path
from typing import Callable from typing import Callable
from urllib.parse import urlsplit, unquote
from ..common.common import get_platform_info, FileObject, download_multiple_files, unzip from ..common.common import get_platform_info, FileObject, download_multiple_files, unzip
from .version_info import ( from .version_info import (
@@ -19,6 +20,87 @@ from ..common.exception import UnsupportedPlatformException, NativeResolveExcept
logger = logging.getLogger("Launcher.CoreLib") logger = logging.getLogger("Launcher.CoreLib")
def parse_maven_coordinate(value: str, default_extension="jar"):
value = value.strip()
coordinate, separator, extension = value.partition("@")
parts = coordinate.split(":")
if len(parts) not in (3, 4):
raise ValueError(f"Invalid maven coordinate {value}")
group, artifact, version = parts[:3]
classifier = parts[3] if len(parts) == 4 else None
if version.count("-") > 0:
version, extra = version.split("-", 1)
if not group or not artifact or not version:
raise ValueError(f"Coordinate key can not be empty: {value!r}")
if not extension:
extension = default_extension
return group, artifact, version, classifier, extension
def convert_maven_version(version: str, failback=None) -> tuple[int, int, int]:
try:
print(version)
items = version.split(".")
if len(items) >= 3:
major, minor, patch = items[0], items[1], items[2]
if len(items) > 3:
logger.debug(f"Found version tag: {".".join(items)}")
elif len(items) == 2:
major, minor, patch = 0, items[0], items[1]
else:
raise ValueError(f"Unable to parse version: {version!r}")
return int(major), int(minor), int(patch)
except ValueError:
return failback
def generate_filename_from_maven_coordinate(artifact: str, version, classifier="", extension="jar") -> str:
classifier = f"-{classifier}" if classifier else ""
return (
f"{artifact}-"
f"{version}"
f"{classifier}."
f"{extension}"
)
def generate_repo_path(group: str, artifact: str, version: str, filename) -> str:
group_path = group.replace(".", "/")
return (
f"{group_path}/"
f"{artifact}/"
f"{version}/"
f"{filename}"
)
def is_complete_maven_url(url: str, group: str, artifact: str, version: str, classifier: str="", extension: str=".jar") -> bool:
filename = generate_filename_from_maven_coordinate(artifact, version, classifier=classifier, extension=extension)
repository_path = generate_repo_path(group, artifact, version, filename)
parsed = urlsplit(url)
if parsed.scheme not in ("http", "https"):
return False
path = unquote(parsed.path).replace("\\", "/")
expected_path = "/" + repository_path
return path.endswith(expected_path)
def generate_repo_url(maven_url: str, group: str, artifact: str, version: str, classifier="", extension=".jar") -> str:
filename = generate_filename_from_maven_coordinate(artifact, version, classifier=classifier, extension=extension)
path = generate_repo_path(group, artifact, version, filename)
if not maven_url.endswith("/"):
maven_url += "/"
return f"{maven_url}{path}"
def is_library_allowed(rules, platform_rule_name, platform_arch_type, platform_version): def is_library_allowed(rules, platform_rule_name, platform_arch_type, platform_version):
""" """
+155
View File
@@ -0,0 +1,155 @@
import json
import logging
import requests
from core_lib.common.exception import VersionManifestFetchException, VersionDataKeyNotFoundException, \
VersionManifestException
logger = logging.getLogger("Launcher.CoreLib")
FABRIC_META_VERSION_ENDPOINT_V2 = "https://meta.fabricmc.net/v2/versions"
FABRIC_META_LOADER_VERSION_ENDPOINT_V2 = "https://meta.fabricmc.net/v1/versions/loader/{}/"
FABRIC_META_LOADER_MANIFEST_ENDPOINT_V2 = "https://meta.fabricmc.net//v2/versions/loader/{}/{}/profile/json"
FABRIC_MAVEN_URL = "https://maven.fabricmc.net/"
def fetch_fabric_version_list(version_manifest_url: str=FABRIC_META_VERSION_ENDPOINT_V2, timeout: int=5) -> list[dict]:
try:
r = requests.get(version_manifest_url, timeout=timeout)
except requests.exceptions.Timeout:
raise VersionManifestFetchException(
"Timeout while fetch from {}".format(version_manifest_url)
)
except requests.exceptions.ConnectionError as e:
raise VersionManifestFetchException(
"Connection error while fetch from {}: {}".format(version_manifest_url, e)
)
except requests.exceptions.HTTPError as e:
raise VersionManifestFetchException(
"HTTP error while fetch from {}: HTTP Status: {}".format(version_manifest_url, e.response.status_code)
)
except Exception as e:
raise VersionManifestFetchException(
"Unknown error while fetch from {}: {}".format(version_manifest_url, e)
)
try:
data = r.json()
except json.decoder.JSONDecodeError as e:
raise VersionManifestFetchException(
"JSON decode error while fetch from {}: {}".format(version_manifest_url, e)
)
versions = data.get("versions")
if not versions:
raise VersionManifestFetchException(
"Version this is empty or not set yet in mod version manifest"
)
return versions
def is_version_supported_and_stable(fabric_version_list: list[dict], target_version: str) -> tuple[bool, bool | None]:
if not fabric_version_list:
raise VersionManifestException(
"Provided fabric version list is empty."
)
for version in fabric_version_list:
if version["version"] == target_version:
is_stable = version.get("stable", None)
return True, is_stable
return False, None
def get_version_support_loader_list(target_version: str, loader_version_url: str=FABRIC_META_LOADER_VERSION_ENDPOINT_V2, timeout: int=5)\
-> list[dict]:
try:
r = requests.get(loader_version_url.format(target_version), timeout=timeout)
except requests.exceptions.Timeout:
raise VersionManifestFetchException(
"Timeout while fetch from {}".format(loader_version_url)
)
except requests.exceptions.ConnectionError as e:
raise VersionManifestFetchException(
"Connection error while fetch from {}: {}".format(loader_version_url, e)
)
except requests.exceptions.HTTPError as e:
raise VersionManifestFetchException(
"HTTP error while fetch from {}: HTTP Status: {}".format(loader_version_url, e.response.status_code)
)
except Exception as e:
raise VersionManifestFetchException(
"Unknown error while fetch from {}: {}".format(loader_version_url, e)
)
try:
versions = r.json()
except json.decoder.JSONDecodeError as e:
raise VersionManifestFetchException(
"JSON decode error while fetch from {}: {}".format(loader_version_url, e)
)
loaders = []
for version in versions:
loader = version.get("loader", None)
if not loader:
logger.warning("Found corrupted or unsupported loader manifest")
continue
loaders.append(loader)
if not loaders:
raise VersionManifestException(
"No available loader found in {}".format(loader_version_url)
)
return loaders
def get_loader_manifest_from_loader_data(target_version: str, loader_data: dict,
loader_manifest_url: str=FABRIC_META_LOADER_MANIFEST_ENDPOINT_V2) -> dict:
loader_ver = loader_data.get("version", None)
if not loader_ver:
raise VersionDataKeyNotFoundException(
"Version key not found in loader manifest {}".format(loader_manifest_url)
)
# Create url
url = loader_manifest_url.format(target_version, loader_ver)
logger.debug("Full manifest url: {}".format(url))
try:
r = requests.get(url, timeout=5)
except requests.exceptions.Timeout:
raise VersionManifestFetchException(
"Timeout while fetch from {}".format(url)
)
except requests.exceptions.ConnectionError as e:
raise VersionManifestFetchException(
"Connection error while fetch from {}: {}".format(url, e)
)
except requests.exceptions.HTTPError as e:
raise VersionManifestFetchException(
"HTTP error while fetch from {}: {}".format(url, e.response.status_code)
)
except Exception as e:
raise VersionManifestFetchException(
"Unknown error while fetch from {}: {}".format(url, e)
)
# We don't need verify data, fabric didn't provide manifest hash.
try:
return r.json()
except json.decoder.JSONDecodeError as e:
raise VersionManifestFetchException(
"JSON decode error while fetch from {}: {}".format(url, e)
)
except Exception as e:
raise VersionManifestFetchException(
"Unknown error while fetch from {}: {}".format(url, e)
)
+243
View File
@@ -0,0 +1,243 @@
import logging
from copy import deepcopy
import shlex
from core_lib.common.exception import VersionDataKeyNotFoundException, DependencyException
from core_lib.game.library import parse_maven_coordinate, convert_maven_version, generate_repo_url, \
is_complete_maven_url
from core_lib.game.version_info import MOJANG_LIBRARIES_ENDPOINT
logger = logging.getLogger("Launcher.CoreLib")
def find_useful_part_in_mod_version_manifest(mod_version_manifest: dict):
ver_id = mod_version_manifest.get("id", None)
inherits_from = mod_version_manifest.get("inheritsFrom", None)
ver_type = mod_version_manifest.get("type", None)
main_class = mod_version_manifest.get("mainClass", None)
arguments = mod_version_manifest.get("arguments", {})
old_arguments = mod_version_manifest.get("minecraftArguments", None)
libraries = mod_version_manifest.get("libraries", [])
if not ver_id:
raise VersionDataKeyNotFoundException(
"Provided mod loader version manifest does not contain key \"id\"."
)
if not ver_type:
raise VersionDataKeyNotFoundException(
"Provided mod loader version manifest does not contain key \"type\"."
)
if not inherits_from:
raise VersionDataKeyNotFoundException(
"Provided mod loader version manifest does not contain key \"inheritsFrom\"."
)
if not main_class:
raise VersionDataKeyNotFoundException(
"Provided mod loader version manifest does not contain key \"mainClass\"."
)
if not arguments and not old_arguments:
raise VersionDataKeyNotFoundException(
"Provided mod loader version manifest does not contain key \"arguments\" or \"minecraftArguments\"."
)
if not libraries:
raise VersionDataKeyNotFoundException(
"Provided mod loader version manifest does not contain key \"libraries\"."
)
return {
"id": ver_id,
"type": ver_type,
"inheritsFrom": inherits_from,
"minecraftArguments": old_arguments,
"libraries": libraries,
"arguments": arguments,
"mainClass": main_class,
}
def merge_necessary_dependencies(inherits_libraries: list, mod_libraries: list, mod_maven_url=None,
inherits_maven_url=MOJANG_LIBRARIES_ENDPOINT, default_extension="jar") -> list[dict]:
inherits = {}
result = []
# ensure below step won't modify original data
inherits_libraries = deepcopy(inherits_libraries)
mod_libraries = deepcopy(mod_libraries)
def check_lib(lib: dict):
url: str = lib.get("url", "")
source_from = lib["sourceFrom"]
lib_group=lib["parsed"]["group"]
lib_artifact=lib["parsed"]["artifact"]
lib_version=lib["parsed"]["version"]
lib_classifier=lib["parsed"]["classifier"]
lib_extension=lib["parsed"]["extension"]
recreated = False
if not url:
if source_from == "official":
new_url = generate_repo_url(inherits_maven_url, lib_group, lib_artifact, lib_version, lib_classifier,
lib_extension)
else:
new_url = generate_repo_url(mod_maven_url, lib_group, lib_artifact, lib_version, lib_classifier,
lib_extension)
lib["url"] = new_url
recreated = True
if not recreated:
url_result = is_complete_maven_url(url, lib_group, lib_artifact, lib_version, lib_classifier, lib_extension)
if not url_result:
# deal with some weird maven url that don't contain repo path
# Some mod loader (such as fabric), did not contain a full path inside the library. We will need to
# generate it from maven coordinate (aka lib_name)
target_maven = mod_maven_url or url
repo_url = generate_repo_url(target_maven, lib_group, lib_artifact, lib_version, lib_classifier,
lib_extension)
lib["url"] = repo_url
for library in inherits_libraries:
name = library.get("name", None)
if not name:
raise DependencyException(
"Provided inherited library does not contain key \"name\"."
)
group, artifact, version, classifier, extension = parse_maven_coordinate(name,
default_extension=default_extension)
# Save into dict for next step to check if library is existing
if not f"{group}:{artifact}" in inherits:
inherits[f"{group}:{artifact}"] = library
inherits[f"{group}:{artifact}"]["parsed"] = {
"group": group,
"artifact": artifact,
"version": version,
"classifier": classifier,
"extension": extension,
}
inherits[f"{group}:{artifact}"]["sourceFrom"] = "official"
else:
logger.warning("Duplicate library: {}".format(name))
for mod_library in mod_libraries:
name = mod_library.get("name", None)
group, artifact, version, classifier, extension = parse_maven_coordinate(name, default_extension=default_extension)
ver_converted = convert_maven_version(version)
if not name:
raise DependencyException(
"Provided mod library does not contain key \"name\"."
)
if ver_converted is None:
raise DependencyException(
"Unable to convert inherits library version {} to tuple".format(version),
)
group, artifact, version, classifier, extension = parse_maven_coordinate(name, default_extension=default_extension)
mod_library["sourceFrom"] = "mod"
mod_library["parsed"] = {
"group": group,
"artifact": artifact,
"version": version,
"classifier": classifier,
"extension": extension,
}
if f"{group}:{artifact}" in inherits:
found_lib = inherits.pop(f"{group}:{artifact}")
found_lib_ver = convert_maven_version(found_lib["parsed"]["version"], None)
if found_lib_ver is None:
raise DependencyException(
"Unable to convert mod library version {} to tuple".format(found_lib["version"])
)
if found_lib_ver > ver_converted or found_lib_ver == ver_converted:
result.append(found_lib)
elif found_lib_ver < ver_converted:
result.append(mod_library)
else:
result.append(mod_library)
# Add the remaining dependencies back to result
for key in inherits:
value = inherits[key]
result.append(value)
for library in result:
check_lib(library)
return result
def merge_arguments(inherits_arguments: str | list, mod_arguments: str | list) -> list[str | dict]:
def str_to_list(args):
p = shlex.split(args)
return p
if isinstance(inherits_arguments, str):
inherits_arguments = str_to_list(inherits_arguments)
if isinstance(mod_arguments, str):
mod_arguments = str_to_list(mod_arguments)
result = []
result.extend(inherits_arguments)
result.extend(mod_arguments)
return result
def merge_game_and_jvm_args(inherits_args: dict[str, list[str]] | str, mod_args: dict[str, list[str]] | str) -> dict:
inherits_game = inherits_args.get("game", []) if isinstance(inherits_args, dict) else inherits_args
inherits_jvm = inherits_args.get("jvm", []) if isinstance(inherits_args, dict) else []
mod_game = mod_args.get("game", []) if isinstance(mod_args, dict) else mod_args
mod_jvm = mod_args.get("jvm", []) if isinstance(mod_args, dict) else []
merged = {
"game": merge_arguments(inherits_game, mod_game),
"jvm": merge_arguments(inherits_jvm, mod_jvm),
}
return merged
def merge_manifest(inherits_version_manifest: dict, mod_version_manifest: dict, mod_maven_url=None) -> dict:
merged_manifest = deepcopy(inherits_version_manifest)
# main class
main_class = mod_version_manifest.get("mainClass", None)
# Apply main class change
if main_class:
merged_manifest["mainClass"] = main_class
# Libraries
inherits_libraries = inherits_version_manifest.get("libraries", [])
mod_libraries = mod_version_manifest.get("libraries", [])
if not inherits_libraries or not mod_libraries:
raise VersionDataKeyNotFoundException(
"Inherited (or mod) version manifest does not contain key \"libraries\"."
)
merged_manifest["libraries"] = merge_necessary_dependencies(inherits_libraries, mod_libraries,
mod_maven_url=mod_maven_url)
# arguments
inherits_arguments = inherits_version_manifest.get("arguments", []) or inherits_version_manifest.get("minecraftArguments", "")
mod_arguments = mod_version_manifest.get("arguments", {}) or mod_version_manifest.get("minecraftArguments", {})
merged_manifest["arguments"] = merge_game_and_jvm_args(inherits_arguments, mod_arguments)
return merged_manifest
-1
View File
@@ -1,6 +1,5 @@
import json import json
import logging import logging
import re
import warnings import warnings
from collections.abc import Callable from collections.abc import Callable
from pathlib import Path from pathlib import Path
+5
View File
@@ -0,0 +1,5 @@
class Launcher:
def __init__(self):
pass
+108 -22
View File
@@ -1,38 +1,124 @@
from pathlib import Path from pathlib import Path
from PySide6.QtCore import Qt from PySide6.QtCore import Qt, QSize
from PySide6.QtGui import QPixmap from PySide6.QtGui import QPixmap
from PySide6.QtWidgets import QWidget, QVBoxLayout, QLabel from PySide6.QtWidgets import QWidget, QVBoxLayout, QLabel, QListWidget, QPushButton, QHBoxLayout, QListWidgetItem, \
QMenu
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)
self.layout = QHBoxLayout(self)
self.head_icon_label = QLabel()
self.head_icon_label.setObjectName("HeadIcon")
self.head_icon_label.setPixmap(account_head)
self.account_name = QLabel(account_name)
self.status_label = QLabel(login_status)
self.layout.addWidget(self.head_icon_label)
self.layout.addWidget(self.account_name)
self.layout.addStretch()
self.layout.addWidget(self.status_label)
self.delete_account_callback = delete_account_callback
self.check_account_callback = check_account_callback
self.switch_account_callback = switch_account_callback
self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
self.customContextMenuRequested.connect(self.show_right_click_menu)
def show_right_click_menu(self, position):
context_menu = QMenu(self)
switch_acc_action = context_menu.addAction("Switch to this account")
check_log_stat_action = context_menu.addAction("Check login status")
context_menu.addSeparator()
delete_account_action = context_menu.addAction("Delete account")
global_position = self.mapToGlobal(position)
selected_action = context_menu.exec(global_position)
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()
class AccountPage(QWidget): class AccountPage(QWidget):
def __init__(self, app, parent=None): def __init__(self, app, parent=None):
QWidget.__init__(self, parent) QWidget.__init__(self, parent)
self.app = app self.app = app
self.layout = QVBoxLayout() self.layout = QHBoxLayout()
# center message (will be deleted if launcher finished development) # center message (will be deleted if launcher finished development)
content = """Still digging!""" # content = """Still digging!"""
message = "If you found that something might be a issue. You can report it to the main repo! Any suggestion are welcome." # message = "If you found that something might be a issue. You can report it to the main repo! Any suggestion are welcome."
self.icon_label = QLabel() # self.icon_label = QLabel()
self.icon_label.setObjectName("icon_label") # self.icon_label.setObjectName("icon_label")
self.dev_info_box = QLabel(content) # self.dev_info_box = QLabel(content)
self.dev_info_box.setObjectName("dev_info_box") # self.dev_info_box.setObjectName("dev_info_box")
self.dev_info_2 = QLabel(message) # self.dev_info_2 = QLabel(message)
self.dev_info_2.setObjectName("dev_info_message") # self.dev_info_2.setObjectName("dev_info_message")
#
# self.layout.addStretch()
# self.layout.addWidget(self.icon_label, alignment=Qt.AlignmentFlag.AlignCenter)
# self.layout.addWidget(self.dev_info_box, alignment=Qt.AlignmentFlag.AlignHCenter)
# self.layout.addWidget(self.dev_info_2, alignment=Qt.AlignmentFlag.AlignHCenter)
# self.layout.addStretch()
#
# if Path(self.app.resources_dir, "pictures", "in_progress.png").exists():
# image = QPixmap(Path(self.app.resources_dir, "pictures", "in_progress.png"))
# self.icon_label.setPixmap(image.scaled(300, 300))
# else:
# self.icon_label.setText("Ouch. The icon is missing.")
#
# self.setLayout(self.layout)
# self.app.apply_qss(Path("main.qss"), self.setStyleSheet)
self.layout.addStretch() self.main_layout = QVBoxLayout()
self.layout.addWidget(self.icon_label, alignment=Qt.AlignmentFlag.AlignCenter) self.right_layout = QVBoxLayout()
self.layout.addWidget(self.dev_info_box, alignment=Qt.AlignmentFlag.AlignHCenter)
self.layout.addWidget(self.dev_info_2, alignment=Qt.AlignmentFlag.AlignHCenter)
self.layout.addStretch()
if Path(self.app.resources_dir, "pictures", "in_progress.png").exists(): self.account_list = QListWidget(self)
image = QPixmap(Path(self.app.resources_dir, "pictures", "in_progress.png"))
self.icon_label.setPixmap(image.scaled(300, 300)) self.add_account_button = QPushButton("Add Account")
else: self.refresh_button = QPushButton("Refresh all")
self.icon_label.setText("Ouch. The icon is missing.") 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()
self.layout.addLayout(self.main_layout)
self.layout.addLayout(self.right_layout)
self.setLayout(self.layout) self.setLayout(self.layout)
self.app.apply_qss(Path("main.qss"), self.setStyleSheet)
self.test()
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 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)
+64
View File
@@ -0,0 +1,64 @@
from pathlib import Path
from PySide6 import QtGui
from PySide6.QtCore import QSize
from PySide6.QtWidgets import QWidget, QVBoxLayout, QComboBox, QListWidget, QPushButton, QLabel, QHBoxLayout
from core_lib.launcher.object import Launcher as LauncherObject
class CreateProfile(QWidget):
def __init__(self, launcher: "LauncherObject",parent=None):
super(CreateProfile, self).__init__(parent)
self.app = launcher
self.layout = QHBoxLayout()
self.main_layout = QVBoxLayout()
self.right_layout = QVBoxLayout()
self.operate_layout = QHBoxLayout()
# Some items
self.version_type_label = QLabel("Version Type")
self.version_type_dropdown = QComboBox()
self.version_type_dropdown.setObjectName("Dropdown")
self.mod_loader_icon_label = QLabel()
icon = self.app.get_icon(Path(self.app.icon_dir, "mod_loader_temp.png"))
pixmap = icon.pixmap(QSize(100, 100))
self.mod_loader_icon_label.setPixmap(pixmap)
self.mod_loader_label = QLabel("Mod Loader")
self.mod_loader_dropdown = QComboBox()
self.mod_loader_dropdown.setObjectName("Dropdown")
self.version_list_type_label = QLabel("Version: Vanilla")
self.version_list_label = QLabel("Select a version to continue:")
self.version_list = QListWidget()
self.version_list.setObjectName("ListWidget")
self.create_profile = QPushButton("Create Profile")
self.create_profile.setObjectName("Button")
self.refresh_button = QPushButton("Refresh")
self.refresh_button.setObjectName("Button")
self.main_layout.addWidget(self.version_list_label)
self.main_layout.addWidget(self.version_list)
self.operate_layout.addWidget(self.version_list_type_label)
self.operate_layout.addStretch()
self.operate_layout.addWidget(self.refresh_button)
self.operate_layout.addWidget(self.create_profile)
self.main_layout.addLayout(self.operate_layout)
self.right_layout.addWidget(self.mod_loader_icon_label)
self.right_layout.addWidget(self.mod_loader_label)
self.right_layout.addWidget(self.mod_loader_dropdown)
self.right_layout.addWidget(self.version_type_label)
self.right_layout.addWidget(self.version_type_dropdown)
self.right_layout.addStretch()
self.right_layout.setContentsMargins(0, 20, 0, 0)
self.setLayout(self.layout)
self.layout.addLayout(self.main_layout)
self.layout.addLayout(self.right_layout)
+1
View File
@@ -47,6 +47,7 @@ QToolBar:left QToolButton,
QToolBar:right QToolButton { QToolBar:right QToolButton {
padding: 14px 6px; padding: 14px 6px;
min-width: 0; min-width: 0;
border-bottom: 2px solid palette(midlight);
} }
QToolBar QPushButton:hover, QToolBar QPushButton:hover,