mirror of
https://invent.kde.org/marcoa/a-la-karte.git
synced 2026-02-10 05:13:08 +00:00
QML: runner inventory, prefix actions, and runner error notifications
This commit is contained in:
parent
4573a3106e
commit
c7956eed8b
4 changed files with 476 additions and 58 deletions
|
|
@ -97,6 +97,18 @@ Kirigami.OverlaySheet {
|
||||||
wrapMode: Text.WrapAnywhere
|
wrapMode: Text.WrapAnywhere
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QQC2.Label {
|
||||||
|
Kirigami.FormData.label: i18n("Runner ID:")
|
||||||
|
text: launchInfo && launchInfo.runnerId ? launchInfo.runnerId : i18n("None")
|
||||||
|
wrapMode: Text.WrapAnywhere
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.Label {
|
||||||
|
Kirigami.FormData.label: i18n("Runner path:")
|
||||||
|
text: launchInfo && launchInfo.runnerPath ? launchInfo.runnerPath : i18n("Not specified")
|
||||||
|
wrapMode: Text.WrapAnywhere
|
||||||
|
}
|
||||||
|
|
||||||
QQC2.Label {
|
QQC2.Label {
|
||||||
Kirigami.FormData.label: i18n("Program:")
|
Kirigami.FormData.label: i18n("Program:")
|
||||||
text: launchInfo && launchInfo.finalProgram ? launchInfo.finalProgram : ""
|
text: launchInfo && launchInfo.finalProgram ? launchInfo.finalProgram : ""
|
||||||
|
|
@ -120,6 +132,12 @@ Kirigami.OverlaySheet {
|
||||||
text: launchInfo && launchInfo.resolvedPrefixPath ? launchInfo.resolvedPrefixPath : (launchInfo && launchInfo.prefixPath ? launchInfo.prefixPath : "")
|
text: launchInfo && launchInfo.resolvedPrefixPath ? launchInfo.resolvedPrefixPath : (launchInfo && launchInfo.prefixPath ? launchInfo.prefixPath : "")
|
||||||
wrapMode: Text.WrapAnywhere
|
wrapMode: Text.WrapAnywhere
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QQC2.Label {
|
||||||
|
Kirigami.FormData.label: i18n("Steam install path:")
|
||||||
|
text: launchInfo && launchInfo.resolvedSteamInstallPath ? launchInfo.resolvedSteamInstallPath : i18n("Not applicable")
|
||||||
|
wrapMode: Text.WrapAnywhere
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,10 @@ Kirigami.Dialog {
|
||||||
id: envModel
|
id: envModel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ListModel {
|
||||||
|
id: runnerChoicesModel
|
||||||
|
}
|
||||||
|
|
||||||
signal gameCreated(var game)
|
signal gameCreated(var game)
|
||||||
signal gameUpdated(var game)
|
signal gameUpdated(var game)
|
||||||
|
|
||||||
|
|
@ -35,14 +39,35 @@ Kirigami.Dialog {
|
||||||
Kirigami.Action {
|
Kirigami.Action {
|
||||||
text: isEditing ? i18n("Apply") : i18n("Add")
|
text: isEditing ? i18n("Apply") : i18n("Add")
|
||||||
icon.name: isEditing ? "dialog-ok-apply" : "list-add"
|
icon.name: isEditing ? "dialog-ok-apply" : "list-add"
|
||||||
enabled: nameField.text.trim() !== "" && executableField.text.trim() !== "" && (runnerCombo.currentIndex !== 3 || runnerPathField.text.trim() !== "")
|
enabled: nameField.text.trim() !== "" && executableField.text.trim() !== "" && (dialog.currentRunnerChoiceKind() !== "custom" || runnerPathField.text.trim() !== "")
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
let runnerValue = dialog.runnerFromIndex(runnerCombo.currentIndex)
|
let choice = dialog.currentRunnerChoice()
|
||||||
|
let runnerKind = choice && choice.kind ? String(choice.kind) : "native"
|
||||||
|
let runnerType = choice && choice.runnerType ? String(choice.runnerType) : (choice && choice.runner ? String(choice.runner) : "")
|
||||||
|
|
||||||
|
let runnerValue = choice && choice.runner ? String(choice.runner) : ""
|
||||||
|
let runnerIdValue = choice && choice.runnerId ? String(choice.runnerId) : ""
|
||||||
|
|
||||||
let runnerPathValue = runnerPathField.text.trim()
|
let runnerPathValue = runnerPathField.text.trim()
|
||||||
let prefixPathValue = prefixPathField.text.trim()
|
let prefixPathValue = prefixPathField.text.trim()
|
||||||
if (runnerValue === "") {
|
|
||||||
|
if (runnerKind === "native") {
|
||||||
|
runnerValue = ""
|
||||||
|
runnerIdValue = ""
|
||||||
runnerPathValue = ""
|
runnerPathValue = ""
|
||||||
prefixPathValue = ""
|
prefixPathValue = ""
|
||||||
|
} else if (runnerKind === "inventory") {
|
||||||
|
runnerValue = ""
|
||||||
|
runnerPathValue = ""
|
||||||
|
} else if (runnerKind === "wine" || runnerKind === "proton") {
|
||||||
|
runnerIdValue = ""
|
||||||
|
} else if (runnerKind === "custom") {
|
||||||
|
runnerIdValue = ""
|
||||||
|
prefixPathValue = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if (runnerType !== "wine" && runnerType !== "proton") {
|
||||||
|
prefixPathValue = ""
|
||||||
}
|
}
|
||||||
if (isEditing) {
|
if (isEditing) {
|
||||||
game.name = nameField.text.trim()
|
game.name = nameField.text.trim()
|
||||||
|
|
@ -51,6 +76,7 @@ Kirigami.Dialog {
|
||||||
game.workingDirectory = workingDirField.text.trim()
|
game.workingDirectory = workingDirField.text.trim()
|
||||||
game.launchEnv = dialog.envModelToMap()
|
game.launchEnv = dialog.envModelToMap()
|
||||||
game.launchRunner = runnerValue
|
game.launchRunner = runnerValue
|
||||||
|
game.launchRunnerId = runnerIdValue
|
||||||
game.launchRunnerPath = runnerPathValue
|
game.launchRunnerPath = runnerPathValue
|
||||||
game.launchPrefixPath = prefixPathValue
|
game.launchPrefixPath = prefixPathValue
|
||||||
if (selectedCoverPath !== "") {
|
if (selectedCoverPath !== "") {
|
||||||
|
|
@ -65,6 +91,7 @@ Kirigami.Dialog {
|
||||||
newGame.workingDirectory = workingDirField.text.trim()
|
newGame.workingDirectory = workingDirField.text.trim()
|
||||||
newGame.launchEnv = dialog.envModelToMap()
|
newGame.launchEnv = dialog.envModelToMap()
|
||||||
newGame.launchRunner = runnerValue
|
newGame.launchRunner = runnerValue
|
||||||
|
newGame.launchRunnerId = runnerIdValue
|
||||||
newGame.launchRunnerPath = runnerPathValue
|
newGame.launchRunnerPath = runnerPathValue
|
||||||
newGame.launchPrefixPath = prefixPathValue
|
newGame.launchPrefixPath = prefixPathValue
|
||||||
if (selectedCoverPath !== "") {
|
if (selectedCoverPath !== "") {
|
||||||
|
|
@ -86,7 +113,24 @@ Kirigami.Dialog {
|
||||||
|
|
||||||
property string selectedCoverPath: ""
|
property string selectedCoverPath: ""
|
||||||
|
|
||||||
readonly property bool anyMenuOpen: runnerCombo && runnerCombo.popup && runnerCombo.popup.visible
|
readonly property bool anyConfirmOpen: !!(deletePrefixConfirmDialog && deletePrefixConfirmDialog.opened)
|
||||||
|
|
||||||
|
readonly property bool anyMenuOpen: !!(runnerCombo && runnerCombo.popup && runnerCombo.popup.visible)
|
||||||
|
|
||||||
|
function currentConfirmDialog() {
|
||||||
|
if (deletePrefixConfirmDialog && deletePrefixConfirmDialog.opened) return deletePrefixConfirmDialog
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeCurrentConfirmDialog() {
|
||||||
|
let d = currentConfirmDialog()
|
||||||
|
if (!d) return
|
||||||
|
if (typeof d.reject === "function") {
|
||||||
|
d.reject()
|
||||||
|
} else {
|
||||||
|
d.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function closeCurrentMenu() {
|
function closeCurrentMenu() {
|
||||||
if (runnerCombo && runnerCombo.popup && runnerCombo.popup.visible) {
|
if (runnerCombo && runnerCombo.popup && runnerCombo.popup.visible) {
|
||||||
|
|
@ -181,16 +225,84 @@ Kirigami.Dialog {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function currentRunnerChoice() {
|
||||||
|
if (!runnerChoicesModel || runnerChoicesModel.count <= 0) return null
|
||||||
|
let idx = runnerCombo ? runnerCombo.currentIndex : 0
|
||||||
|
if (idx < 0 || idx >= runnerChoicesModel.count) return runnerChoicesModel.get(0)
|
||||||
|
return runnerChoicesModel.get(idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
function currentRunnerChoiceKind() {
|
||||||
|
let c = dialog.currentRunnerChoice()
|
||||||
|
if (!c || !c.kind) return "native"
|
||||||
|
return String(c.kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
function currentRunnerChoiceRunnerType() {
|
||||||
|
let c = dialog.currentRunnerChoice()
|
||||||
|
if (!c) return ""
|
||||||
|
if (c.runnerType) return String(c.runnerType)
|
||||||
|
if (c.runner) return String(c.runner)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
function rebuildRunnerChoices() {
|
||||||
|
runnerChoicesModel.clear()
|
||||||
|
|
||||||
|
runnerChoicesModel.append({ kind: "native", label: i18n("Native"), runner: "", runnerId: "", runnerType: "" })
|
||||||
|
runnerChoicesModel.append({ kind: "wine", label: i18n("Wine (system)"), runner: "wine", runnerId: "", runnerType: "wine" })
|
||||||
|
runnerChoicesModel.append({ kind: "proton", label: i18n("Proton (auto)"), runner: "proton", runnerId: "", runnerType: "proton" })
|
||||||
|
|
||||||
|
let runners = (App.runnerManager && App.runnerManager.runners) ? App.runnerManager.runners : []
|
||||||
|
for (let i = 0; i < runners.length; i++) {
|
||||||
|
let r = runners[i]
|
||||||
|
if (!r || !r.id) continue
|
||||||
|
let name = r.name ? String(r.name) : String(r.id)
|
||||||
|
let source = r.source ? String(r.source) : ""
|
||||||
|
let type = r.type ? String(r.type) : ""
|
||||||
|
let label = name
|
||||||
|
if (type !== "" || source !== "") {
|
||||||
|
label = name + " (" + type + (source !== "" ? (" · " + source) : "") + ")"
|
||||||
|
}
|
||||||
|
runnerChoicesModel.append({ kind: "inventory", label: label, runner: "", runnerId: String(r.id), runnerType: type })
|
||||||
|
}
|
||||||
|
|
||||||
|
runnerChoicesModel.append({ kind: "custom", label: i18n("Custom runner path"), runner: "custom", runnerId: "", runnerType: "custom" })
|
||||||
|
}
|
||||||
|
|
||||||
|
function indexForRunnerId(runnerId) {
|
||||||
|
let id = (runnerId || "").trim()
|
||||||
|
if (id === "") return 0
|
||||||
|
for (let i = 0; i < runnerChoicesModel.count; i++) {
|
||||||
|
let row = runnerChoicesModel.get(i)
|
||||||
|
if (row.kind === "inventory" && String(row.runnerId || "") === id) return i
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function indexForRunner(runner) {
|
||||||
|
let r = (runner || "").trim().toLowerCase()
|
||||||
|
if (r === "wine") return 1
|
||||||
|
if (r === "proton") return 2
|
||||||
|
if (r === "custom") return Math.max(0, runnerChoicesModel.count - 1)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
function loadFields() {
|
function loadFields() {
|
||||||
selectedCoverPath = ""
|
selectedCoverPath = ""
|
||||||
envModel.clear()
|
envModel.clear()
|
||||||
|
dialog.rebuildRunnerChoices()
|
||||||
if (isEditing && game) {
|
if (isEditing && game) {
|
||||||
nameField.text = game.name || ""
|
nameField.text = game.name || ""
|
||||||
developerField.text = game.developer || ""
|
developerField.text = game.developer || ""
|
||||||
executableField.text = game.launchCommand || ""
|
executableField.text = game.launchCommand || ""
|
||||||
workingDirField.text = game.workingDirectory || ""
|
workingDirField.text = game.workingDirectory || ""
|
||||||
|
|
||||||
runnerCombo.currentIndex = dialog.runnerToIndex(game.launchRunner)
|
let idx = dialog.indexForRunnerId(game.launchRunnerId)
|
||||||
|
if (idx === 0) {
|
||||||
|
idx = dialog.indexForRunner(game.launchRunner)
|
||||||
|
}
|
||||||
|
runnerCombo.currentIndex = idx
|
||||||
runnerPathField.text = game.launchRunnerPath || ""
|
runnerPathField.text = game.launchRunnerPath || ""
|
||||||
prefixPathField.text = game.launchPrefixPath || ""
|
prefixPathField.text = game.launchPrefixPath || ""
|
||||||
|
|
||||||
|
|
@ -213,6 +325,66 @@ Kirigami.Dialog {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: App.runnerManager
|
||||||
|
function onRunnersChanged() {
|
||||||
|
if (!dialog.opened) return
|
||||||
|
|
||||||
|
let desiredRunnerId = ""
|
||||||
|
if (isEditing && game) {
|
||||||
|
desiredRunnerId = String(game.launchRunnerId || "").trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
let current = dialog.currentRunnerChoice()
|
||||||
|
let currentRunnerId = current && current.runnerId ? String(current.runnerId) : ""
|
||||||
|
let currentRunner = current && current.runner ? String(current.runner) : ""
|
||||||
|
|
||||||
|
dialog.rebuildRunnerChoices()
|
||||||
|
|
||||||
|
if (desiredRunnerId !== "") {
|
||||||
|
runnerCombo.currentIndex = dialog.indexForRunnerId(desiredRunnerId)
|
||||||
|
} else if (currentRunnerId !== "") {
|
||||||
|
runnerCombo.currentIndex = dialog.indexForRunnerId(currentRunnerId)
|
||||||
|
} else {
|
||||||
|
runnerCombo.currentIndex = dialog.indexForRunner(currentRunner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPrefixEnsured(gameId, prefixPath) {
|
||||||
|
if (!dialog.opened) return
|
||||||
|
if (!isEditing || !game) return
|
||||||
|
if (String(gameId) !== String(game.id)) return
|
||||||
|
prefixPathField.text = String(prefixPath || "")
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPrefixDeleted(gameId, prefixPath) {
|
||||||
|
if (!dialog.opened) return
|
||||||
|
if (!isEditing || !game) return
|
||||||
|
if (String(gameId) !== String(game.id)) return
|
||||||
|
prefixPathField.text = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function effectivePrefixPath() {
|
||||||
|
let p = prefixPathField.text.trim()
|
||||||
|
if (p !== "") return p
|
||||||
|
if (!isEditing || !game) return ""
|
||||||
|
return StandardPaths.writableLocation(StandardPaths.AppDataLocation) + "/prefixes/" + game.id
|
||||||
|
}
|
||||||
|
|
||||||
|
function toFileUrl(path) {
|
||||||
|
let p = String(path || "").trim()
|
||||||
|
if (p === "") return ""
|
||||||
|
if (p.startsWith("~/")) {
|
||||||
|
p = StandardPaths.writableLocation(StandardPaths.HomeLocation) + "/" + p.substring(2)
|
||||||
|
}
|
||||||
|
if (p.startsWith("file:")) return p
|
||||||
|
if (p.startsWith("/")) {
|
||||||
|
return "file:///" + encodeURI(p.substring(1))
|
||||||
|
}
|
||||||
|
return "file:///" + encodeURI(p)
|
||||||
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: GamepadManager
|
target: GamepadManager
|
||||||
function onNavigateUp() {
|
function onNavigateUp() {
|
||||||
|
|
@ -325,26 +497,11 @@ Kirigami.Dialog {
|
||||||
title: i18n("Compatibility")
|
title: i18n("Compatibility")
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
FormCard.FormComboBoxDelegate {
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.margins: Kirigami.Units.largeSpacing
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.right: parent.right
|
|
||||||
spacing: Kirigami.Units.largeSpacing
|
|
||||||
|
|
||||||
QQC2.Label {
|
|
||||||
text: i18n("Runner")
|
|
||||||
Layout.alignment: Qt.AlignVCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
QQC2.ComboBox {
|
|
||||||
id: runnerCombo
|
id: runnerCombo
|
||||||
Layout.fillWidth: true
|
text: i18n("Runner")
|
||||||
model: [i18n("Native"), i18n("Wine"), i18n("Proton"), i18n("Custom")]
|
model: runnerChoicesModel
|
||||||
}
|
textRole: "label"
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FormCard.FormDelegateSeparator {}
|
FormCard.FormDelegateSeparator {}
|
||||||
|
|
@ -353,8 +510,8 @@ Kirigami.Dialog {
|
||||||
id: runnerPathField
|
id: runnerPathField
|
||||||
label: i18n("Runner Path")
|
label: i18n("Runner Path")
|
||||||
text: ""
|
text: ""
|
||||||
placeholderText: runnerCombo.currentIndex === 2 ? i18n("Auto-detect Proton") : (runnerCombo.currentIndex === 1 ? i18n("Use system Wine") : i18n("Required for Custom"))
|
placeholderText: dialog.currentRunnerChoiceKind() === "proton" ? i18n("Auto-detect Proton") : (dialog.currentRunnerChoiceKind() === "wine" ? i18n("Use system Wine") : i18n("Required for Custom"))
|
||||||
enabled: runnerCombo.currentIndex !== 0
|
enabled: dialog.currentRunnerChoiceKind() === "custom" || dialog.currentRunnerChoiceKind() === "wine" || dialog.currentRunnerChoiceKind() === "proton"
|
||||||
}
|
}
|
||||||
|
|
||||||
FormCard.FormDelegateSeparator {}
|
FormCard.FormDelegateSeparator {}
|
||||||
|
|
@ -362,7 +519,7 @@ Kirigami.Dialog {
|
||||||
FormCard.FormButtonDelegate {
|
FormCard.FormButtonDelegate {
|
||||||
text: i18n("Browse Runner...")
|
text: i18n("Browse Runner...")
|
||||||
icon.name: "document-open"
|
icon.name: "document-open"
|
||||||
enabled: runnerCombo.currentIndex !== 0
|
enabled: dialog.currentRunnerChoiceKind() === "custom" || dialog.currentRunnerChoiceKind() === "wine" || dialog.currentRunnerChoiceKind() === "proton"
|
||||||
onClicked: runnerFileDialog.open()
|
onClicked: runnerFileDialog.open()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -373,7 +530,7 @@ Kirigami.Dialog {
|
||||||
label: i18n("Prefix Path")
|
label: i18n("Prefix Path")
|
||||||
text: ""
|
text: ""
|
||||||
placeholderText: i18n("Default (AppDataLocation/prefixes/<gameId>)")
|
placeholderText: i18n("Default (AppDataLocation/prefixes/<gameId>)")
|
||||||
enabled: runnerCombo.currentIndex === 1 || runnerCombo.currentIndex === 2
|
enabled: dialog.currentRunnerChoiceRunnerType() === "wine" || dialog.currentRunnerChoiceRunnerType() === "proton"
|
||||||
}
|
}
|
||||||
|
|
||||||
FormCard.FormDelegateSeparator {}
|
FormCard.FormDelegateSeparator {}
|
||||||
|
|
@ -381,9 +538,52 @@ Kirigami.Dialog {
|
||||||
FormCard.FormButtonDelegate {
|
FormCard.FormButtonDelegate {
|
||||||
text: i18n("Browse Prefix...")
|
text: i18n("Browse Prefix...")
|
||||||
icon.name: "document-open-folder"
|
icon.name: "document-open-folder"
|
||||||
enabled: runnerCombo.currentIndex === 1 || runnerCombo.currentIndex === 2
|
enabled: dialog.currentRunnerChoiceRunnerType() === "wine" || dialog.currentRunnerChoiceRunnerType() === "proton"
|
||||||
onClicked: prefixFolderDialog.open()
|
onClicked: prefixFolderDialog.open()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FormCard.FormDelegateSeparator {}
|
||||||
|
|
||||||
|
FormCard.FormButtonDelegate {
|
||||||
|
text: i18n("Open Prefix Folder")
|
||||||
|
icon.name: "folder-open"
|
||||||
|
enabled: isEditing && (dialog.currentRunnerChoiceRunnerType() === "wine" || dialog.currentRunnerChoiceRunnerType() === "proton")
|
||||||
|
onClicked: {
|
||||||
|
let p = dialog.effectivePrefixPath()
|
||||||
|
if (p === "") return
|
||||||
|
Qt.openUrlExternally(dialog.toFileUrl(p))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FormCard.FormDelegateSeparator {}
|
||||||
|
|
||||||
|
FormCard.FormButtonDelegate {
|
||||||
|
text: i18n("Delete Prefix")
|
||||||
|
icon.name: "edit-delete"
|
||||||
|
enabled: isEditing
|
||||||
|
&& (dialog.currentRunnerChoiceRunnerType() === "wine" || dialog.currentRunnerChoiceRunnerType() === "proton")
|
||||||
|
&& (prefixPathField.text.trim() === "")
|
||||||
|
onClicked: deletePrefixConfirmDialog.open()
|
||||||
|
}
|
||||||
|
|
||||||
|
FormCard.FormDelegateSeparator {}
|
||||||
|
|
||||||
|
FormCard.FormButtonDelegate {
|
||||||
|
text: i18n("Create Prefix")
|
||||||
|
icon.name: "folder-new"
|
||||||
|
enabled: isEditing
|
||||||
|
&& (dialog.currentRunnerChoiceRunnerType() === "wine" || dialog.currentRunnerChoiceRunnerType() === "proton")
|
||||||
|
onClicked: App.runnerManager.ensurePrefix(game.id, dialog.currentRunnerChoiceRunnerType(), prefixPathField.text.trim())
|
||||||
|
}
|
||||||
|
|
||||||
|
FormCard.FormDelegateSeparator {}
|
||||||
|
|
||||||
|
FormCard.FormButtonDelegate {
|
||||||
|
text: i18n("Reset Prefix to Default")
|
||||||
|
icon.name: "edit-clear"
|
||||||
|
enabled: dialog.currentRunnerChoiceRunnerType() === "wine" || dialog.currentRunnerChoiceRunnerType() === "proton"
|
||||||
|
onClicked: prefixPathField.text = ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FormCard.FormCard {
|
FormCard.FormCard {
|
||||||
|
|
@ -435,13 +635,9 @@ Kirigami.Dialog {
|
||||||
onClicked: envModel.clear()
|
onClicked: envModel.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
ColumnLayout {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.margins: Kirigami.Units.largeSpacing
|
Layout.margins: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.right: parent.right
|
|
||||||
spacing: Kirigami.Units.smallSpacing
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
|
|
@ -476,7 +672,6 @@ Kirigami.Dialog {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
FormCard.FormCard {
|
FormCard.FormCard {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
@ -630,9 +825,35 @@ Kirigami.Dialog {
|
||||||
}
|
}
|
||||||
|
|
||||||
onOpened: {
|
onOpened: {
|
||||||
|
App.runnerManager.refreshRunners()
|
||||||
loadFields()
|
loadFields()
|
||||||
nameField.forceActiveFocus()
|
nameField.forceActiveFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
onGameChanged: loadFields()
|
onGameChanged: loadFields()
|
||||||
|
|
||||||
|
Kirigami.PromptDialog {
|
||||||
|
id: deletePrefixConfirmDialog
|
||||||
|
title: i18n("Delete Prefix")
|
||||||
|
subtitle: (isEditing && game)
|
||||||
|
? i18n("Delete the prefix for '%1'? This will remove the entire prefix directory.", game.name)
|
||||||
|
: i18n("Delete this prefix? This will remove the entire prefix directory.")
|
||||||
|
standardButtons: Kirigami.Dialog.Yes | Kirigami.Dialog.No
|
||||||
|
onAccepted: {
|
||||||
|
if (!isEditing || !game) return
|
||||||
|
App.runnerManager.deletePrefix(game.id, prefixPathField.text.trim())
|
||||||
|
}
|
||||||
|
onOpened: {
|
||||||
|
Qt.callLater(function() {
|
||||||
|
if (typeof deletePrefixConfirmDialog.standardButton === "function") {
|
||||||
|
let noButton = deletePrefixConfirmDialog.standardButton(Kirigami.Dialog.No)
|
||||||
|
if (noButton) {
|
||||||
|
noButton.forceActiveFocus()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deletePrefixConfirmDialog.forceActiveFocus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,7 @@ Kirigami.ApplicationWindow {
|
||||||
|
|
||||||
function currentConfirmDialog() {
|
function currentConfirmDialog() {
|
||||||
if (removeGameConfirmDialog.opened) return removeGameConfirmDialog
|
if (removeGameConfirmDialog.opened) return removeGameConfirmDialog
|
||||||
|
if (gameEditDialog.visible && gameEditDialog.anyConfirmOpen) return gameEditDialog.currentConfirmDialog()
|
||||||
let layerContent = root.settingsLayerContentItem()
|
let layerContent = root.settingsLayerContentItem()
|
||||||
if (settingsLayerOpen && layerContent && layerContent.anyConfirmOpen) return layerContent.currentConfirmDialog()
|
if (settingsLayerOpen && layerContent && layerContent.anyConfirmOpen) return layerContent.currentConfirmDialog()
|
||||||
if (settingsSheet.opened && settingsContent.anyConfirmOpen) return settingsContent.currentConfirmDialog()
|
if (settingsSheet.opened && settingsContent.anyConfirmOpen) return settingsContent.currentConfirmDialog()
|
||||||
|
|
@ -177,6 +178,10 @@ Kirigami.ApplicationWindow {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if (gameEditDialog.visible) {
|
if (gameEditDialog.visible) {
|
||||||
|
if (gameEditDialog.anyConfirmOpen) {
|
||||||
|
gameEditDialog.closeCurrentConfirmDialog()
|
||||||
|
return true
|
||||||
|
}
|
||||||
if (gameEditDialog.anyMenuOpen) {
|
if (gameEditDialog.anyMenuOpen) {
|
||||||
gameEditDialog.closeCurrentMenu()
|
gameEditDialog.closeCurrentMenu()
|
||||||
return true
|
return true
|
||||||
|
|
@ -2041,6 +2046,17 @@ Kirigami.ApplicationWindow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: App.runnerManager
|
||||||
|
|
||||||
|
function onLastErrorChanged() {
|
||||||
|
if (!App.runnerManager) return
|
||||||
|
let msg = String(App.runnerManager.lastError || "")
|
||||||
|
if (msg.trim() === "") return
|
||||||
|
showPassiveNotification(i18n("Runner error: %1", msg), "long")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if (!root.isMobile) {
|
if (!root.isMobile) {
|
||||||
const savedW = App.config.windowWidth
|
const savedW = App.config.windowWidth
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,10 @@ ColumnLayout {
|
||||||
id: settingsPage
|
id: settingsPage
|
||||||
spacing: Kirigami.Units.mediumSpacing
|
spacing: Kirigami.Units.mediumSpacing
|
||||||
|
|
||||||
|
property string runnerInstallUrl: ""
|
||||||
|
property string runnerInstallSha256: ""
|
||||||
|
property string runnerInstallName: ""
|
||||||
|
|
||||||
function focusFirstControl() {
|
function focusFirstControl() {
|
||||||
showPlatformBadgesDelegate.forceActiveFocus()
|
showPlatformBadgesDelegate.forceActiveFocus()
|
||||||
}
|
}
|
||||||
|
|
@ -546,6 +550,165 @@ FormCard.FormHeader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FormCard.FormHeader {
|
||||||
|
Layout.topMargin: Kirigami.Units.mediumSpacing
|
||||||
|
Layout.fillWidth: true
|
||||||
|
title: i18n("Runners")
|
||||||
|
}
|
||||||
|
|
||||||
|
FormCard.FormCard {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
FormCard.FormTextFieldDelegate {
|
||||||
|
label: i18n("Archive URL or local path")
|
||||||
|
text: settingsPage.runnerInstallUrl
|
||||||
|
placeholderText: i18n("https://... or /home/.../file.tar.gz")
|
||||||
|
enabled: !App.runnerManager.busy
|
||||||
|
onTextChanged: settingsPage.runnerInstallUrl = text
|
||||||
|
}
|
||||||
|
|
||||||
|
FormCard.FormDelegateSeparator {}
|
||||||
|
|
||||||
|
FormCard.FormTextFieldDelegate {
|
||||||
|
label: i18n("SHA256/SHA512 (optional)")
|
||||||
|
text: settingsPage.runnerInstallSha256
|
||||||
|
placeholderText: i18n("Leave empty to skip verification")
|
||||||
|
enabled: !App.runnerManager.busy
|
||||||
|
onTextChanged: settingsPage.runnerInstallSha256 = text
|
||||||
|
}
|
||||||
|
|
||||||
|
FormCard.FormDelegateSeparator {}
|
||||||
|
|
||||||
|
FormCard.FormTextFieldDelegate {
|
||||||
|
label: i18n("Name (optional)")
|
||||||
|
text: settingsPage.runnerInstallName
|
||||||
|
placeholderText: i18n("Proton-GE")
|
||||||
|
enabled: !App.runnerManager.busy
|
||||||
|
onTextChanged: settingsPage.runnerInstallName = text
|
||||||
|
}
|
||||||
|
|
||||||
|
FormCard.FormDelegateSeparator {}
|
||||||
|
|
||||||
|
FormCard.FormButtonDelegate {
|
||||||
|
text: i18n("Install from URL")
|
||||||
|
description: App.runnerManager.busy ? App.runnerManager.status : i18n("Download and install runner")
|
||||||
|
icon.name: "download"
|
||||||
|
enabled: settingsPage.runnerInstallUrl.trim() !== "" && !App.runnerManager.busy
|
||||||
|
onClicked: App.runnerManager.installRunnerFromUrl(settingsPage.runnerInstallUrl, settingsPage.runnerInstallSha256, settingsPage.runnerInstallName)
|
||||||
|
}
|
||||||
|
|
||||||
|
FormCard.FormDelegateSeparator {}
|
||||||
|
|
||||||
|
FormCard.FormButtonDelegate {
|
||||||
|
text: i18n("Cancel install")
|
||||||
|
icon.name: "dialog-cancel"
|
||||||
|
enabled: App.runnerManager.busy
|
||||||
|
onClicked: App.runnerManager.cancelCurrentInstall()
|
||||||
|
}
|
||||||
|
|
||||||
|
FormCard.FormDelegateSeparator {}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.margins: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
QQC2.Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: App.runnerManager.status
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
visible: App.runnerManager.status !== ""
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: App.runnerManager.lastError
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
visible: App.runnerManager.lastError !== ""
|
||||||
|
color: Kirigami.Theme.negativeTextColor
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.ProgressBar {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
from: 0
|
||||||
|
to: App.runnerManager.totalBytes > 0 ? App.runnerManager.totalBytes : 1
|
||||||
|
value: App.runnerManager.receivedBytes
|
||||||
|
indeterminate: App.runnerManager.busy && App.runnerManager.totalBytes <= 0
|
||||||
|
visible: App.runnerManager.busy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FormCard.FormHeader {
|
||||||
|
Layout.topMargin: Kirigami.Units.mediumSpacing
|
||||||
|
Layout.fillWidth: true
|
||||||
|
title: i18n("Installed Runners")
|
||||||
|
}
|
||||||
|
|
||||||
|
FormCard.FormCard {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
FormCard.FormButtonDelegate {
|
||||||
|
text: i18n("Refresh")
|
||||||
|
icon.name: "view-refresh"
|
||||||
|
enabled: !App.runnerManager.busy
|
||||||
|
onClicked: App.runnerManager.refreshRunners()
|
||||||
|
}
|
||||||
|
|
||||||
|
FormCard.FormDelegateSeparator {}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: App.runnerManager.runners
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
readonly property var runner: modelData
|
||||||
|
readonly property string runnerId: (runner && runner.id) ? runner.id : ""
|
||||||
|
readonly property string runnerName: (runner && runner.name) ? runner.name : runnerId
|
||||||
|
readonly property string runnerType: (runner && runner.type) ? runner.type : ""
|
||||||
|
readonly property string runnerSource: (runner && runner.source) ? runner.source : ""
|
||||||
|
readonly property string runnerPath: (runner && runner.path) ? runner.path : ""
|
||||||
|
|
||||||
|
FormCard.FormTextDelegate {
|
||||||
|
text: runnerName
|
||||||
|
description: (runnerType !== "" ? (runnerType + " · ") : "") + (runnerSource !== "" ? runnerSource : "")
|
||||||
|
textItem.wrapMode: Text.WordWrap
|
||||||
|
}
|
||||||
|
|
||||||
|
FormCard.FormDelegateSeparator {}
|
||||||
|
|
||||||
|
FormCard.FormTextDelegate {
|
||||||
|
text: runnerPath
|
||||||
|
textItem.wrapMode: Text.WrapAnywhere
|
||||||
|
textItem.font: Kirigami.Theme.smallFont
|
||||||
|
}
|
||||||
|
|
||||||
|
FormCard.FormDelegateSeparator {}
|
||||||
|
|
||||||
|
FormCard.FormButtonDelegate {
|
||||||
|
text: i18n("Uninstall")
|
||||||
|
icon.name: "edit-delete"
|
||||||
|
enabled: runnerSource === "installed" && runnerId !== "" && !App.runnerManager.busy
|
||||||
|
onClicked: App.runnerManager.uninstallRunner(runnerId)
|
||||||
|
}
|
||||||
|
|
||||||
|
FormCard.FormDelegateSeparator {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FormCard.FormTextDelegate {
|
||||||
|
visible: App.runnerManager.runners.length === 0
|
||||||
|
text: i18n("No runners found")
|
||||||
|
textItem.font: Kirigami.Theme.smallFont
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
FormCard.FormHeader {
|
FormCard.FormHeader {
|
||||||
Layout.topMargin: Kirigami.Units.mediumSpacing
|
Layout.topMargin: Kirigami.Units.mediumSpacing
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue