Files
minecraft-launcher/LoaderRow.qml
T

177 lines
7.4 KiB
QML
Raw Normal View History

import QtQuick
import QtQuick.Controls 2.15
// Один модлоадер в карточке сборки: чекбокс и, когда он отмечен, список версий
// именно под выбранную версию Minecraft.
//
// Совместимость проверять не нужно: backend.loaderVersions() отдаёт список уже
// под конкретную версию игры, поэтому несовместимой строки в нём не бывает.
// Пустой список — это «лоадер не поддерживает эту версию игры», и он тоже
// показывается словами, а не пустым выпадающим списком.
Column {
id: loaderRow
required property var backend
required property string loaderKey
required property string title
// Версия Minecraft, выбранная в карточке. Её смена сбрасывает выбор.
property string gameVersion: ""
property alias checked: box.checked
property string selectedVersion: ""
property var entries: []
// Пользователь щёлкнул чекбокс — карточка снимет отметки с остальных
// лоадеров: одновременно в .minecraft может жить только один.
signal userChecked()
// Отметка или версия изменились — карточке пора сохранить сборку.
signal changed()
// Ставится, пока карточка заполняет строку данными сборки: в это время
// обработчики не должны считать изменения пользовательскими.
property bool applying: false
spacing: 4
function versionIndex(version) {
for (var i = 0; i < loaderRow.entries.length; ++i) {
if (loaderRow.entries[i].version === version)
return i
}
return -1
}
2026-08-27 16:44:42 +03:00
// Единственное место, где меняются entries и selectedVersion. Через него
// проходят все три пути — кэш, ответ сети и заполнение из сборки, — потому
// что выбранная версия обязана существовать в списке под текущую версию
// игры. Раньше проверял только путь из сети, и версия лоадера от прошлой
// версии игры доживала до сохранения сборки.
function applyEntries(list) {
loaderRow.entries = list
// Список отсортирован новыми вперёд, поэтому максимально доступная
// версия для этой версии игры — всегда первая строка.
if (loaderRow.versionIndex(loaderRow.selectedVersion) < 0) {
const fallback = list.length > 0 ? list[0].version : ""
const changedValue = loaderRow.selectedVersion !== fallback
loaderRow.selectedVersion = fallback
if (changedValue && !loaderRow.applying)
loaderRow.changed()
}
2026-08-27 16:44:42 +03:00
combo.currentIndex = loaderRow.versionIndex(loaderRow.selectedVersion)
}
function reload() {
if (!box.checked || loaderRow.gameVersion === "") {
2026-08-27 16:44:42 +03:00
loaderRow.applyEntries([])
return
}
// Сначала из кэша — список появляется мгновенно, — потом обновление сети.
2026-08-27 16:44:42 +03:00
loaderRow.applyEntries(loaderRow.backend.loaderVersions(loaderRow.loaderKey,
loaderRow.gameVersion))
loaderRow.backend.refreshLoaderVersions(loaderRow.loaderKey, loaderRow.gameVersion)
}
// Заполнение из сохранённой сборки: без сигналов changed().
function applyBuild(loader, loaderVersion) {
loaderRow.applying = true
box.checked = loader === loaderRow.loaderKey
2026-08-27 16:44:42 +03:00
// Версию из сборки берём как пожелание: если под выбранную версию игры
// её нет, applyEntries() заменит её на максимально доступную.
loaderRow.selectedVersion = box.checked ? loaderVersion : ""
loaderRow.reload()
loaderRow.applying = false
}
onGameVersionChanged: {
2026-08-27 16:44:42 +03:00
// Сборка лоадера привязана к версии игры: под новой версией прежний
// выбор недействителен, и список перезапрашивается заново.
loaderRow.selectedVersion = ""
loaderRow.reload()
}
Connections {
target: loaderRow.backend
function onLoaderVersionsChanged(key, game) {
if (key !== loaderRow.loaderKey || game !== loaderRow.gameVersion || !box.checked)
return
2026-08-27 16:44:42 +03:00
loaderRow.applyEntries(loaderRow.backend.loaderVersions(key, game))
}
}
CheckBox {
id: box
text: loaderRow.title
enabled: loaderRow.gameVersion !== ""
indicator: Rectangle {
implicitWidth: 18
implicitHeight: 18
x: 0
y: (box.height - height) / 2
radius: 4
color: box.checked ? "#91B315" : "#2a2a2a"
border.color: box.checked ? "#91B315" : "#444444"
border.width: 1
Text {
anchors.centerIn: parent
visible: box.checked
text: "✓"
color: "#1e1e1e"
font.pixelSize: 13
font.bold: true
}
}
contentItem: Text {
leftPadding: box.indicator.width + 8
text: box.text
color: box.enabled ? "#ffffff" : "#666666"
font.pixelSize: 12
verticalAlignment: Text.AlignVCenter
}
onToggled: {
if (box.checked)
loaderRow.userChecked()
else
loaderRow.selectedVersion = ""
loaderRow.reload()
loaderRow.changed()
}
}
DarkCombo {
id: combo
visible: box.checked && loaderRow.entries.length > 0
width: loaderRow.width - 26
height: 32
x: 26
model: loaderRow.entries.map(entry => entry.label)
onActivated: (index) => {
if (index < 0 || index >= loaderRow.entries.length)
return
loaderRow.selectedVersion = loaderRow.entries[index].version
loaderRow.changed()
}
}
Text {
visible: box.checked && loaderRow.entries.length === 0
x: 26
width: loaderRow.width - 26
wrapMode: Text.Wrap
font.pixelSize: 11
color: loaderRow.backend.loaderVersionsLoading(loaderRow.loaderKey, loaderRow.gameVersion)
? "#888888" : "#cc6666"
text: loaderRow.backend.loaderVersionsLoading(loaderRow.loaderKey, loaderRow.gameVersion)
? Loc.t.loader.progress.loadingVersions.arg(loaderRow.title)
: Loc.t.loader.status.unsupported.arg(loaderRow.title)
.arg(loaderRow.gameVersion)
}
}