Move source code to src/leek

This makes the package work again
This commit is contained in:
Toast 2025-05-22 22:52:08 +02:00
parent 1a626fddff
commit 6061590270
9 changed files with 16 additions and 10 deletions

90
src/leek/Main.qml Normal file
View file

@ -0,0 +1,90 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls as Controls
import org.kde.kirigami as Kirigami
Kirigami.ApplicationWindow {
id: root
title: "Leek"
pageStack.initialPage: Kirigami.ScrollablePage {
title: "Mods"
Kirigami.CardsListView {
id: modsView
model: modListModel
delegate: modCardDelegate
}
}
Component {
id: modCardDelegate
Kirigami.AbstractCard {
// headerOrientation: Qt.Horizontal
contentItem: Item {
implicitWidth: modCardLayout.implicitWidth
implicitHeight: modCardLayout.implicitHeight
GridLayout {
id: modCardLayout
anchors {
left: parent.left
top: parent.top
right: parent.right
}
rowSpacing: Kirigami.Units.largeSpacing
columnSpacing: Kirigami.Units.largeSpacing
columns: root.wideScreen ? 4 : 2
// TODO: Replace this with an image once we can get them
Controls.BusyIndicator {
running: true
}
ColumnLayout {
Kirigami.Heading {
Layout.fillWidth: true
text: name
type: Kirigami.Heading.Type.Primary
}
Kirigami.Separator {
Layout.fillWidth: true
}
Controls.Label {
Layout.fillWidth: true
wrapMode: Text.WordWrap
text: description ? description : "No description available"
}
}
ColumnLayout {
Layout.alignment: Qt.AlignRight
Controls.Switch {
text: "Enabled"
checked: true
}
Controls.Button {
text: "Delete"
}
}
}
}
}
}
ListModel {
id: modListModel
ListElement {
name: "DivaNoSpy"
description: "Disables project diva's telemetry"
}
ListElement {
name: "exPatch"
description: "Unlocks extreme difficulty immediately"
}
ListElement {
name: "Mod with no description"
}
ListElement {
name: "Very long description"
description: "
()"
}
}
}

0
src/leek/__init__.py Normal file
View file

14
src/leek/__main__.py Normal file
View file

@ -0,0 +1,14 @@
# https://packaging.python.org/en/latest/discussions/src-layout-vs-flat-layout/#running-a-command-line-interface-from-source-with-src-layout
import os
import sys
if not __package__:
# Make CLI runnable from source tree with
# python src/package
package_source_path = os.path.dirname(os.path.dirname(__file__))
sys.path.insert(0, package_source_path)
from leek import leek_app
leek_app.main()

36
src/leek/leek_app.py Normal file
View file

@ -0,0 +1,36 @@
#!/usr/bin/env python3
import os
import sys
import signal
from PySide6.QtGui import QGuiApplication
from PySide6.QtCore import QUrl
from PySide6.QtQml import QQmlApplicationEngine
def main():
"""Initializes and manages the application execution"""
app: QGuiApplication = QGuiApplication(sys.argv)
app.setDesktopFileName("xyz.toast003.leek")
app.setApplicationName("Leek")
engine = QQmlApplicationEngine()
"""Needed to close the app with Ctrl+C"""
signal.signal(signal.SIGINT, signal.SIG_DFL)
"""Needed to get proper KDE style outside of Plasma"""
if not os.environ.get("QT_QUICK_CONTROLS_STYLE"):
os.environ["QT_QUICK_CONTROLS_STYLE"] = "org.kde.desktop"
base_path = os.path.abspath(os.path.dirname(__file__))
url = QUrl(f"file://{base_path}/Main.qml")
engine.load(url)
if len(engine.rootObjects()) == 0:
quit()
app.exec()
if __name__ == "__main__":
main()

71
src/leek/mod.py Normal file
View file

@ -0,0 +1,71 @@
import tomlkit
class Mod:
@property
def path(self) -> str:
return self.__path
# Mod metadata
@property
def name(self) -> str | None:
if "name" not in self.__config:
return None
return self.__config["name"].as_string()
@property
def description(self) -> str | None:
if "description" not in self.__config.keys():
return None
else:
return self.__config["description"].as_string()
@property
def author(self) -> str | None:
if "author" not in self.__config:
return None
return self.__config["author"].as_string()
@property
def enabled(self) -> bool:
return True if self.__enabled == "true" else False
@enabled.setter
def enabled(self, value: bool) -> None:
if value == self.__enabled:
# Nothing to do
return
with open(self.__path + "config.toml", "w") as config_file:
self.__config["enabled"] = value
tomlkit.dump(self.__config, config_file)
def __init__(self, path: str) -> None:
self.__path = path
try:
with open(path + "config.toml") as config_file:
self.__config = tomlkit.load(config_file)
if "enabled" not in self.__config:
raise InvalidModError("config.toml does not contain the enabled key")
else:
self.__enabled = self.__config["enabled"]
except FileNotFoundError:
raise InvalidModError("config.toml does not exist")
def __str__(self) -> str:
return f"Mod({self.__path})"
class InvalidModError(Exception):
"""
This exception is raised when the Mod class gets given a path of something that's not a valid mod
"""
def __init__(self, message: str) -> None:
super().__init__(message)
self.message = message
def __str__(self) -> str:
return f"{self.message}"

54
src/leek/mod_list.py Normal file
View file

@ -0,0 +1,54 @@
from PySide6.QtQml import QmlElement
from PySide6.QtCore import QAbstractListModel, QModelIndex
from leek.mod import Mod, InvalidModError
import os
QML_IMPORT_NAME = "Leek"
QML_IMPORT_MAJOR_VERSION = 1
# TODO: Don't harcode the mods path
GAME_PATH = "/home/toast/.local/share/Steam/steamapps/common/Hatsune Miku Project DIVA Mega Mix Plus/"
# Qt follows C++ naming conventions
# ruff: noqa: N802
@QmlElement
class QModListModel(QAbstractListModel):
def __init__(self, parent=None) -> None:
super().__init__(parent=parent)
mods: list[Mod] = []
with os.scandir(GAME_PATH + "mods/") as dirs:
for dir in dirs:
try:
new_mod: Mod = Mod(dir.path + "/" )
mods.append(new_mod)
except InvalidModError as e:
print(f"Found invalid mod at {dir.path}: {e.message}")
continue
self.mods = mods
def roleNames(self) -> dict[int, bytes]:
return {
0: b"name",
1: b"description",
2: b"enabled"
}
def rowCount(self, parent=QModelIndex()) -> int:
return len(self.mods)
def data(self, index: QModelIndex, role: int) -> None | str | bool:
i: int = index.row()
result: None | str | bool
if not index.isValid():
result = None
elif role == 0:
result = self.mods[i].name
elif role == 1:
result = self.mods[i].description
elif role == 2:
result = self.mods[i].enabled
else:
result = None
return result