Files
minecraft-launcher/SeasonalBuildsDialog.qml

340 lines
14 KiB
QML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls 2.15
// Готовые сезонные сборки с сервера лаунчера: таблица со всем, что нужно
// решить, ставить её или нет, и одна кнопка, которая делает всё остальное —
// заводит сборку, ставит версию игры, модлоадер, Java и раскладывает файлы.
//
// Таблица собрана из Row фиксированных колонок, а не из TableView: в проекте
// нет ни одной модели QAbstractItemModel, а строки приходят готовыми
// QVariantMap — заводить ради семи колонок отдельную модель незачем.
Dialog {
id: seasons
required property var backend
// Выбранная строка. Хранится по id, а не по индексу: список обновляется
// под руками, и индекс после обновления указывал бы на другую сборку.
property string selectedId: ""
// Ширины колонок в одном месте: их повторяют и шапка, и делегат.
readonly property var columns: [
{ key: "name", title: Loc.t.common.header.name, width: 250, align: Text.AlignLeft },
{ key: "minecraftVersion", title: Loc.t.common.header.version, width: 90, align: Text.AlignLeft },
{ key: "loaderLabel", title: Loc.t.common.header.loader, width: 150, align: Text.AlignLeft },
{ key: "modCount", title: Loc.t.seasonal.header.mods, width: 70, align: Text.AlignRight },
{ key: "seasonStart", title: Loc.t.seasonal.header.start, width: 110, align: Text.AlignLeft },
{ key: "seasonEnd", title: Loc.t.seasonal.header.end, width: 110, align: Text.AlignLeft },
{ key: "status", title: Loc.t.common.header.status, width: 140, align: Text.AlignLeft }
]
modal: true
padding: 0
width: 960
height: 560
// Пока идёт установка, окно закрывается только кнопкой: случайный щелчок
// мимо не должен спрятать единственную видимую отмену.
closePolicy: seasons.backend.seasonalInstalling
? Popup.NoAutoClose
: (Popup.CloseOnEscape | Popup.CloseOnPressOutside)
background: Rectangle {
color: "#1e1e1e"
radius: 10
border.color: "#91B315"
border.width: 1
}
// Строки приходят из C++ уже отсортированными и сведёнными с локальными
// записями — здесь только показ.
readonly property var entries: seasons.backend.seasonalCatalog
readonly property var selectedEntry: {
const list = seasons.entries
for (var i = 0; i < list.length; ++i) {
if (list[i].id === seasons.selectedId)
return list[i]
}
return null
}
function openCatalog() {
seasons.open()
}
function formatMb(bytes) {
if (!bytes || bytes <= 0)
return ""
return (bytes / (1024 * 1024)).toFixed(1)
}
function cellText(entry, key) {
if (key === "loaderLabel") {
return entry.loaderVersion === ""
? entry.loaderTitle
: entry.loaderTitle + " " + entry.loaderVersion
}
return String(entry[key])
}
// Свежий кэш отвечает без сети, поэтому дёргаем при каждом открытии.
onAboutToShow: seasons.backend.refreshSeasonalCatalog(false)
header: Item {
implicitHeight: 52
Text {
anchors.centerIn: parent
text: Loc.t.common.title.seasonalBuilds
color: "#ffffff"
font.pixelSize: 17
font.bold: true
}
Rectangle {
anchors.bottom: parent.bottom
width: parent.width
height: 1
color: "#333333"
}
}
contentItem: Item {
// ── Шапка таблицы ───────────────────────────────────────────────
Row {
id: tableHeader
anchors.top: parent.top
anchors.left: parent.left
anchors.leftMargin: 18
height: 30
Repeater {
model: seasons.columns
delegate: Text {
required property var modelData
width: modelData.width
height: tableHeader.height
text: modelData.title
color: "#aaaaaa"
font.pixelSize: 12
elide: Text.ElideRight
horizontalAlignment: modelData.align
verticalAlignment: Text.AlignVCenter
rightPadding: 10
}
}
}
Rectangle {
id: headerLine
anchors.top: tableHeader.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 12
anchors.rightMargin: 12
height: 1
color: "#333333"
}
// ── Сообщение вместо таблицы ────────────────────────────────────
// Пустой список и ошибка выглядят одинаково пустыми, поэтому причина
// показывается прямо на месте строк.
Text {
anchors.centerIn: parent
width: parent.width - 80
visible: seasons.entries.length === 0
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
color: seasons.backend.seasonalCatalogError === "" ? "#888888" : "#cc6666"
font.pixelSize: 13
text: seasons.backend.seasonalCatalogLoading
? Loc.t.seasonal.progress.loadingCatalog
: (seasons.backend.seasonalCatalogError === ""
? Loc.t.seasonal.status.catalogEmptyYet
: seasons.backend.seasonalCatalogError)
}
// ── Строки ──────────────────────────────────────────────────────
ListView {
id: buildList
anchors.top: headerLine.bottom
anchors.topMargin: 4
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.leftMargin: 12
anchors.rightMargin: 12
clip: true
spacing: 1
model: seasons.entries
ScrollIndicator.vertical: ScrollIndicator {}
delegate: Rectangle {
id: buildRow
required property var modelData
width: buildList.width
height: 36
radius: 4
color: seasons.selectedId === buildRow.modelData.id ? "#91B315"
: rowArea.containsMouse ? "#2d2d2d" : "transparent"
readonly property bool highlighted: seasons.selectedId === buildRow.modelData.id
Row {
anchors.left: parent.left
anchors.leftMargin: 6
anchors.verticalCenter: parent.verticalCenter
Repeater {
model: seasons.columns
delegate: Text {
required property var modelData
width: modelData.width
text: seasons.cellText(buildRow.modelData, modelData.key)
// Подсвеченная строка залита зелёным — тёмный текст
// на ней читается, светлый нет.
color: buildRow.highlighted
? "#1e1e1e"
: (modelData.key === "status"
&& buildRow.modelData.updateAvailable
? "#91B315" : "#ffffff")
font.pixelSize: 13
elide: Text.ElideRight
horizontalAlignment: modelData.align
rightPadding: 10
}
}
}
MouseArea {
id: rowArea
anchors.fill: parent
hoverEnabled: true
onClicked: seasons.selectedId = buildRow.modelData.id
onDoubleClicked: {
seasons.selectedId = buildRow.modelData.id
seasons.installSelected()
}
}
}
}
}
function installSelected() {
const entry = seasons.selectedEntry
if (!entry || seasons.backend.busy)
return
seasons.backend.installSeasonalBuild(entry.id)
}
footer: Item {
implicitHeight: 76
Rectangle {
anchors.top: parent.top
width: parent.width
height: 1
color: "#333333"
}
// Описание и размер выбранной сборки: они длинные и в таблицу не
// помещаются, а решение принимается по ним.
Column {
anchors.left: parent.left
anchors.leftMargin: 18
anchors.right: footerButtons.left
anchors.rightMargin: 12
anchors.verticalCenter: parent.verticalCenter
spacing: 3
Text {
width: parent.width
elide: Text.ElideRight
color: "#ffffff"
font.pixelSize: 12
text: seasons.selectedEntry ? seasons.selectedEntry.description : ""
}
Text {
width: parent.width
elide: Text.ElideRight
color: "#888888"
font.pixelSize: 11
text: {
if (!seasons.selectedEntry)
return Loc.t.seasonal.hint.pickBuild
const entry = seasons.selectedEntry
var line = Loc.t.seasonal.status.revision.arg(entry.revision)
const size = seasons.formatMb(entry.sizeBytes)
if (size !== "")
line += Loc.t.seasonal.status.sizeSuffix.arg(size)
if (entry.serverUrl !== "")
line += " · " + entry.serverUrl
if (entry.updateAvailable)
line += Loc.t.seasonal.status.installedRevisionSuffix.arg(entry.installedRevision)
return line
}
}
}
Row {
id: footerButtons
anchors.right: parent.right
anchors.rightMargin: 18
anchors.verticalCenter: parent.verticalCenter
spacing: 12
Button {
id: refreshButton
width: 150
height: 36
enabled: !seasons.backend.seasonalCatalogLoading
contentItem: Text {
text: seasons.backend.seasonalCatalogLoading ? Loc.t.common.status.updating
: Loc.t.common.button.refreshList
color: refreshButton.enabled ? "#ffffff" : "#888888"
font.pixelSize: 13
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
background: Rectangle {
color: refreshButton.pressed ? "#444444" : "#333333"
radius: 6
}
onClicked: seasons.backend.refreshSeasonalCatalog(true)
}
Button {
id: installButton
width: 150
height: 36
// Установка занимает и панель загрузки, и .minecraft целиком:
// пока лаунчер занят чем угодно, вторую начинать нельзя.
enabled: seasons.selectedEntry !== null && !seasons.backend.busy
contentItem: Text {
text: seasons.selectedEntry && seasons.selectedEntry.updateAvailable
? Loc.t.common.button.update
: Loc.t.common.button.install
color: installButton.enabled ? "#ffffff" : "#888888"
font.pixelSize: 13
font.bold: true
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
background: Rectangle {
color: !installButton.enabled ? "#2a2a2a"
: installButton.pressed ? "#6a8510" : "#91B315"
radius: 6
}
onClicked: seasons.installSelected()
}
}
}
}