2025-12-29 02:18:35 +03:00
|
|
|
|
import QtQuick
|
|
|
|
|
|
import QtQuick.Layouts 2.15
|
|
|
|
|
|
import QtQuick.Controls 2.15
|
2026-06-09 00:43:06 +03:00
|
|
|
|
import Minecraft_launcher
|
2025-12-29 02:18:35 +03:00
|
|
|
|
|
|
|
|
|
|
Window {
|
|
|
|
|
|
id: window
|
|
|
|
|
|
width: 1280
|
|
|
|
|
|
height: 720
|
|
|
|
|
|
visible: true
|
|
|
|
|
|
flags: Qt.Window
|
2026-06-09 00:43:06 +03:00
|
|
|
|
title: qsTr("Minecraft Launcher")
|
|
|
|
|
|
|
|
|
|
|
|
// ── Backend ────────────────────────────────────────────────────────────
|
|
|
|
|
|
LauncherBackend {
|
|
|
|
|
|
id: backend
|
2026-08-22 18:19:13 +03:00
|
|
|
|
|
2026-08-27 09:21:51 +03:00
|
|
|
|
onLaunched: (profileName, buildName, serverUrl) => {
|
|
|
|
|
|
window.showToast(qsTr("Запущено: %1 — %2").arg(profileName).arg(buildName), "#4b7a1f")
|
2026-08-22 18:19:13 +03:00
|
|
|
|
}
|
|
|
|
|
|
onLaunchProgress: (message) => window.showToast(message, "#3a5a8c", 0)
|
|
|
|
|
|
onLaunchError: (message) => window.showToast(message, "#cc3333", 8000)
|
|
|
|
|
|
onTwoFactorRequired: (profileName) => {
|
|
|
|
|
|
twoFactorDialog.profileName = profileName
|
|
|
|
|
|
twoFactorCode.text = ""
|
|
|
|
|
|
twoFactorDialog.open()
|
|
|
|
|
|
}
|
|
|
|
|
|
onGameFinished: (exitCode, crashed) => {
|
|
|
|
|
|
window.showToast(crashed || exitCode !== 0
|
|
|
|
|
|
? qsTr("Игра завершилась с ошибкой (код %1)").arg(exitCode)
|
|
|
|
|
|
: qsTr("Игра закрыта"),
|
|
|
|
|
|
crashed || exitCode !== 0 ? "#cc3333" : "#555555")
|
2026-06-09 00:43:06 +03:00
|
|
|
|
}
|
2026-09-01 22:25:23 +03:00
|
|
|
|
onMicrosoftLoginUrlReady: (url) => window.openMicrosoftLogin(url)
|
|
|
|
|
|
// Выбор в списке здесь не трогаем: новый профиль уже выбран тем, кто
|
|
|
|
|
|
// его создал, а повторный вход мог быть и не в последний профиль.
|
|
|
|
|
|
onMicrosoftLoginSucceeded: (playerName) =>
|
|
|
|
|
|
window.showToast(qsTr("Вход выполнен: %1").arg(playerName), "#4b7a1f")
|
|
|
|
|
|
onMicrosoftLoginFailed: (message) => window.showToast(message, "#cc3333", 8000)
|
|
|
|
|
|
onMicrosoftReloginRequired: (profileIndex) => backend.startMicrosoftLogin(profileIndex)
|
2026-08-22 18:19:13 +03:00
|
|
|
|
onGameOutput: (line) => console.log(line)
|
2026-08-31 00:59:14 +03:00
|
|
|
|
onSeasonalInstallFinished: (seasonalId, buildName) => {
|
|
|
|
|
|
window.showToast(qsTr("Сборка «%1» установлена — можно запускать").arg(buildName),
|
|
|
|
|
|
"#4b7a1f", 8000)
|
|
|
|
|
|
}
|
2026-08-22 18:19:13 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Единая всплывающая плашка: сообщения о ходе запуска, ошибки, статус игры.
|
|
|
|
|
|
// timeout === 0 — держим до следующего сообщения.
|
|
|
|
|
|
function showToast(text, color, timeout) {
|
|
|
|
|
|
toastLabel.text = text
|
|
|
|
|
|
toastBackground.color = color
|
|
|
|
|
|
toastTimer.stop()
|
|
|
|
|
|
if (timeout === undefined)
|
|
|
|
|
|
timeout = 4000
|
|
|
|
|
|
if (timeout > 0) {
|
|
|
|
|
|
toastTimer.interval = timeout
|
|
|
|
|
|
toastTimer.restart()
|
2026-06-09 00:43:06 +03:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-29 02:18:35 +03:00
|
|
|
|
|
2026-09-01 22:25:23 +03:00
|
|
|
|
// Окно входа Microsoft создаётся по требованию, а не вместе с главным
|
|
|
|
|
|
// окном: MicrosoftLoginDialog.qml попадает в модуль только в сборках с Qt
|
|
|
|
|
|
// WebEngine, и обычная декларация сломала бы всё окно в остальных.
|
|
|
|
|
|
property var microsoftLoginDialog: null
|
|
|
|
|
|
|
|
|
|
|
|
function openMicrosoftLogin(url) {
|
|
|
|
|
|
if (microsoftLoginDialog === null) {
|
|
|
|
|
|
const component = Qt.createComponent("MicrosoftLoginDialog.qml")
|
|
|
|
|
|
if (component.status !== Component.Ready) {
|
|
|
|
|
|
backend.cancelMicrosoftLogin()
|
|
|
|
|
|
window.showToast(qsTr("Окно входа Microsoft недоступно: сборка без Qt WebEngine"),
|
|
|
|
|
|
"#cc3333", 8000)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
microsoftLoginDialog = component.createObject(window, { backend: backend })
|
|
|
|
|
|
microsoftLoginDialog.failed.connect(
|
|
|
|
|
|
(message) => window.showToast(message, "#cc3333", 6000))
|
|
|
|
|
|
}
|
|
|
|
|
|
microsoftLoginDialog.openAt(url)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-24 21:09:52 +03:00
|
|
|
|
function formatMb(bytes) {
|
|
|
|
|
|
return (bytes / 1048576).toFixed(1)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-09 00:43:06 +03:00
|
|
|
|
// ── Background ─────────────────────────────────────────────────────────
|
2025-12-29 02:18:35 +03:00
|
|
|
|
Image {
|
|
|
|
|
|
id: image
|
|
|
|
|
|
opacity: 0.296
|
|
|
|
|
|
anchors.fill: parent
|
|
|
|
|
|
source: "images/GovuztTW8AAHqBf.jpeg"
|
|
|
|
|
|
fillMode: Image.Stretch
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-22 18:19:13 +03:00
|
|
|
|
// ── Status toast ───────────────────────────────────────────────────────
|
2026-06-09 00:43:06 +03:00
|
|
|
|
Rectangle {
|
2026-08-22 18:19:13 +03:00
|
|
|
|
id: toastBackground
|
|
|
|
|
|
visible: toastLabel.text !== ""
|
2026-06-09 00:43:06 +03:00
|
|
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
|
|
|
|
anchors.bottom: parent.bottom
|
2026-08-27 09:21:51 +03:00
|
|
|
|
anchors.bottomMargin: (downloadPanel.visible || switchPanel.visible) ? 110 : 40
|
2026-08-22 18:19:13 +03:00
|
|
|
|
width: Math.min(window.width - 80, toastLabel.implicitWidth + 32)
|
|
|
|
|
|
height: toastLabel.implicitHeight + 20
|
2026-06-09 00:43:06 +03:00
|
|
|
|
radius: 8
|
|
|
|
|
|
color: "#cc3333"
|
|
|
|
|
|
|
|
|
|
|
|
Text {
|
2026-08-22 18:19:13 +03:00
|
|
|
|
id: toastLabel
|
2026-06-09 00:43:06 +03:00
|
|
|
|
anchors.centerIn: parent
|
2026-08-22 18:19:13 +03:00
|
|
|
|
width: parent.width - 32
|
2026-06-09 00:43:06 +03:00
|
|
|
|
color: "#ffffff"
|
|
|
|
|
|
font.pixelSize: 14
|
2026-08-22 18:19:13 +03:00
|
|
|
|
horizontalAlignment: Text.AlignHCenter
|
|
|
|
|
|
wrapMode: Text.Wrap
|
2026-06-09 00:43:06 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Timer {
|
2026-08-22 18:19:13 +03:00
|
|
|
|
id: toastTimer
|
|
|
|
|
|
interval: 4000
|
|
|
|
|
|
onTriggered: toastLabel.text = ""
|
2026-06-09 00:43:06 +03:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-27 09:21:51 +03:00
|
|
|
|
// ── Панели хода работ ──────────────────────────────────────────────────
|
|
|
|
|
|
// Загрузка и смена сборки взаимоисключающи по построению: busy() не даёт
|
|
|
|
|
|
// начать переключение во время установки и наоборот. Поэтому у панелей
|
|
|
|
|
|
// одни и те же якоря.
|
|
|
|
|
|
ProgressPanel {
|
2026-08-24 21:09:52 +03:00
|
|
|
|
id: downloadPanel
|
|
|
|
|
|
visible: backend.downloading
|
|
|
|
|
|
anchors.left: parent.left
|
|
|
|
|
|
anchors.bottom: parent.bottom
|
|
|
|
|
|
anchors.leftMargin: 24
|
|
|
|
|
|
anchors.bottomMargin: 24
|
|
|
|
|
|
|
2026-08-27 09:21:51 +03:00
|
|
|
|
title: qsTr("Загрузка %1").arg(backend.downloadVersion)
|
|
|
|
|
|
fraction: backend.downloadProgress
|
|
|
|
|
|
status: backend.downloadStatus
|
|
|
|
|
|
detail: backend.downloadBytesTotal > 0
|
|
|
|
|
|
? qsTr("%1 — %2 / %3 МБ").arg(backend.downloadStatus)
|
|
|
|
|
|
.arg(window.formatMb(backend.downloadBytesDone))
|
|
|
|
|
|
.arg(window.formatMb(backend.downloadBytesTotal))
|
|
|
|
|
|
: ""
|
|
|
|
|
|
onCancelRequested: backend.cancelDownload()
|
|
|
|
|
|
}
|
2026-08-24 21:09:52 +03:00
|
|
|
|
|
2026-08-27 09:21:51 +03:00
|
|
|
|
ProgressPanel {
|
|
|
|
|
|
id: switchPanel
|
|
|
|
|
|
visible: backend.switching
|
|
|
|
|
|
anchors.left: parent.left
|
|
|
|
|
|
anchors.bottom: parent.bottom
|
|
|
|
|
|
anchors.leftMargin: 24
|
|
|
|
|
|
anchors.bottomMargin: 24
|
2026-08-24 21:09:52 +03:00
|
|
|
|
|
2026-08-27 09:21:51 +03:00
|
|
|
|
title: backend.switchStage
|
|
|
|
|
|
fraction: backend.switchProgress
|
|
|
|
|
|
status: backend.switchStatus
|
|
|
|
|
|
// Отменять можно только пока идёт архивация: после очистки .minecraft
|
|
|
|
|
|
// отступать некуда, операцию нужно довести до конца.
|
|
|
|
|
|
cancellable: false
|
2026-08-24 21:09:52 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-09 00:43:06 +03:00
|
|
|
|
// ── Play button ────────────────────────────────────────────────────────
|
2025-12-29 02:18:35 +03:00
|
|
|
|
Button {
|
|
|
|
|
|
id: button
|
|
|
|
|
|
width: 335
|
|
|
|
|
|
height: 170
|
|
|
|
|
|
anchors.centerIn: parent
|
2026-06-09 00:43:06 +03:00
|
|
|
|
hoverEnabled: false
|
2026-08-22 18:19:13 +03:00
|
|
|
|
enabled: !backend.busy && !backend.gameRunning
|
2026-06-09 00:43:06 +03:00
|
|
|
|
|
2025-12-29 02:18:35 +03:00
|
|
|
|
background: Image {
|
|
|
|
|
|
id: buttonImage
|
|
|
|
|
|
source: "images/Play_Button/Play_Active.svg"
|
2026-08-22 18:19:13 +03:00
|
|
|
|
opacity: button.enabled ? 1.0 : 0.45
|
2025-12-29 02:18:35 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-09 00:43:06 +03:00
|
|
|
|
onPressed: buttonImage.source = "images/Play_Button/Play_pressed.svg"
|
|
|
|
|
|
onReleased: buttonImage.source = "images/Play_Button/Play_Active.svg"
|
2026-08-27 09:21:51 +03:00
|
|
|
|
onClicked: backend.launchGame(profileBox.currentIndex, backend.activeBuildIndex)
|
2025-12-29 02:18:35 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-09 00:43:06 +03:00
|
|
|
|
// ── Profile ComboBox ───────────────────────────────────────────────────
|
2025-12-29 02:18:35 +03:00
|
|
|
|
ComboBox {
|
|
|
|
|
|
id: profileBox
|
2026-06-09 00:43:06 +03:00
|
|
|
|
y: 451
|
|
|
|
|
|
width: 146
|
2025-12-29 02:18:35 +03:00
|
|
|
|
height: 42
|
|
|
|
|
|
anchors.left: button.right
|
|
|
|
|
|
anchors.bottom: button.top
|
|
|
|
|
|
anchors.leftMargin: -335
|
|
|
|
|
|
anchors.bottomMargin: -218
|
2026-06-14 20:04:17 +03:00
|
|
|
|
rotation: 0
|
2025-12-29 02:18:35 +03:00
|
|
|
|
editable: false
|
2026-08-27 16:44:42 +03:00
|
|
|
|
hoverEnabled: true
|
2025-12-29 02:18:35 +03:00
|
|
|
|
|
2026-06-09 00:43:06 +03:00
|
|
|
|
model: backend.profileNames
|
2025-12-29 02:18:35 +03:00
|
|
|
|
|
2026-08-27 16:44:42 +03:00
|
|
|
|
// Пока список закрыт: открытый popup накрывает кнопку, и подсказка
|
|
|
|
|
|
// висела бы поверх собственного меню.
|
|
|
|
|
|
ToolTip.visible: profileBox.hovered && !profileBox.down
|
|
|
|
|
|
ToolTip.text: backend.profileNames.length === 0
|
|
|
|
|
|
? qsTr("Профили игрока — добавьте первый профиль")
|
|
|
|
|
|
: qsTr("Профиль игрока: %1").arg(profileBox.displayText)
|
|
|
|
|
|
|
2026-06-14 20:04:17 +03:00
|
|
|
|
contentItem: Text {
|
|
|
|
|
|
id: profileText
|
|
|
|
|
|
text: profileBox.displayText
|
|
|
|
|
|
font: profileBox.font
|
|
|
|
|
|
color: "#ffffff"
|
|
|
|
|
|
verticalAlignment: Text.AlignVCenter
|
|
|
|
|
|
horizontalAlignment: Text.AlignLeft
|
|
|
|
|
|
elide: Text.ElideRight
|
|
|
|
|
|
anchors.fill: parent
|
|
|
|
|
|
anchors.leftMargin: 30
|
|
|
|
|
|
anchors.rightMargin: 5
|
|
|
|
|
|
anchors.topMargin: 5
|
|
|
|
|
|
anchors.bottomMargin: 5
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-29 02:18:35 +03:00
|
|
|
|
indicator: Image {
|
2026-06-14 20:04:17 +03:00
|
|
|
|
id: profileArrow
|
2026-06-09 00:43:06 +03:00
|
|
|
|
width: 10; height: 10
|
2025-12-29 02:18:35 +03:00
|
|
|
|
visible: true
|
|
|
|
|
|
anchors.left: parent.left
|
2026-06-09 00:43:06 +03:00
|
|
|
|
anchors.leftMargin: 10
|
2025-12-29 02:18:35 +03:00
|
|
|
|
anchors.verticalCenter: parent.verticalCenter
|
2026-06-09 00:43:06 +03:00
|
|
|
|
source: profileBox.down ? "images/Profile_Box/Asset_23.svg"
|
|
|
|
|
|
: "images/Profile_Box/Asset_24.svg"
|
|
|
|
|
|
rotation: 180
|
|
|
|
|
|
sourceSize.width: 10; sourceSize.height: 10
|
2025-12-29 02:18:35 +03:00
|
|
|
|
fillMode: Image.PreserveAspectFit
|
|
|
|
|
|
autoTransform: true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
background: Rectangle {
|
2026-06-09 00:43:06 +03:00
|
|
|
|
implicitWidth: 120; implicitHeight: 40
|
2025-12-29 02:18:35 +03:00
|
|
|
|
color: profileBox.down ? "#91B315" : "#232323"
|
|
|
|
|
|
radius: 6
|
2026-06-09 00:43:06 +03:00
|
|
|
|
border.color: profileBox.pressed ? "#ffffff" : "#232323"
|
|
|
|
|
|
border.width: profileBox.visualFocus ? 2 : 1
|
2025-12-29 02:18:35 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
popup: Popup {
|
|
|
|
|
|
y: profileBox.height - 1
|
|
|
|
|
|
width: profileBox.width
|
2026-06-09 00:43:06 +03:00
|
|
|
|
padding: 0
|
|
|
|
|
|
// Высота = кнопка "+" (32) + элементы списка, не более 200
|
|
|
|
|
|
height: 32 + Math.min(profileItemList.contentHeight, 200)
|
|
|
|
|
|
|
|
|
|
|
|
contentItem: Item {
|
|
|
|
|
|
anchors.fill: parent
|
|
|
|
|
|
|
|
|
|
|
|
// ── "+" button — всегда сверху ──────────────────────────
|
|
|
|
|
|
Rectangle {
|
|
|
|
|
|
id: profileAddBtnBg
|
|
|
|
|
|
anchors.top: parent.top
|
|
|
|
|
|
anchors.left: parent.left
|
|
|
|
|
|
anchors.right: parent.right
|
|
|
|
|
|
height: 32
|
|
|
|
|
|
color: profileAddArea.containsPress ? "#2d2d2d" : "transparent"
|
|
|
|
|
|
|
|
|
|
|
|
Text {
|
|
|
|
|
|
anchors.centerIn: parent
|
|
|
|
|
|
text: "+ Добавить профиль"
|
2025-12-29 02:18:35 +03:00
|
|
|
|
color: "#91B315"
|
2026-06-09 00:43:06 +03:00
|
|
|
|
font.pixelSize: 12
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MouseArea {
|
|
|
|
|
|
id: profileAddArea
|
|
|
|
|
|
anchors.fill: parent
|
|
|
|
|
|
onClicked: {
|
|
|
|
|
|
profileBox.popup.close()
|
|
|
|
|
|
addProfileDialog.open()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-29 02:18:35 +03:00
|
|
|
|
|
2026-06-09 00:43:06 +03:00
|
|
|
|
// ── Добавленные профили — всегда ниже кнопки ───────────
|
|
|
|
|
|
ListView {
|
|
|
|
|
|
id: profileItemList
|
|
|
|
|
|
anchors.top: profileAddBtnBg.bottom
|
|
|
|
|
|
anchors.left: parent.left
|
|
|
|
|
|
anchors.right: parent.right
|
|
|
|
|
|
height: Math.min(contentHeight, 200)
|
|
|
|
|
|
clip: true
|
|
|
|
|
|
model: backend.profileNames
|
|
|
|
|
|
|
|
|
|
|
|
delegate: ItemDelegate {
|
|
|
|
|
|
id: profileItem
|
|
|
|
|
|
required property string modelData
|
|
|
|
|
|
required property int index
|
|
|
|
|
|
width: profileItemList.width
|
|
|
|
|
|
height: 40
|
|
|
|
|
|
contentItem: Text {
|
|
|
|
|
|
text: profileItem.modelData
|
|
|
|
|
|
color: "#ffffff"
|
|
|
|
|
|
font: profileBox.font
|
|
|
|
|
|
elide: Text.ElideRight
|
|
|
|
|
|
horizontalAlignment: Text.AlignLeft
|
|
|
|
|
|
verticalAlignment: Text.AlignVCenter
|
|
|
|
|
|
leftPadding: 8
|
2026-06-16 12:16:59 +03:00
|
|
|
|
rightPadding: 62
|
2026-06-09 00:43:06 +03:00
|
|
|
|
}
|
|
|
|
|
|
background: Rectangle {
|
|
|
|
|
|
color: (profileBox.currentIndex === profileItem.index || profileItem.hovered)
|
|
|
|
|
|
? "#91B315" : "#232323"
|
|
|
|
|
|
radius: 5
|
|
|
|
|
|
}
|
|
|
|
|
|
onClicked: {
|
|
|
|
|
|
profileBox.currentIndex = profileItem.index
|
|
|
|
|
|
profileBox.popup.close()
|
|
|
|
|
|
}
|
2026-06-15 00:22:27 +03:00
|
|
|
|
|
2026-06-16 12:16:59 +03:00
|
|
|
|
// ── Карандаш — видна при наведении на строку ────
|
|
|
|
|
|
Image {
|
|
|
|
|
|
id: profilePencil
|
|
|
|
|
|
width: 16; height: 16
|
|
|
|
|
|
anchors.right: profileTrash.left
|
|
|
|
|
|
anchors.rightMargin: 12
|
|
|
|
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
|
|
|
|
visible: profileItem.hovered
|
|
|
|
|
|
opacity: profilePencilArea.containsMouse ? 1.0 : 0.7
|
|
|
|
|
|
source: "images/Pencil.svg"
|
|
|
|
|
|
sourceSize.width: 16; sourceSize.height: 16
|
|
|
|
|
|
fillMode: Image.PreserveAspectFit
|
|
|
|
|
|
|
|
|
|
|
|
MouseArea {
|
|
|
|
|
|
id: profilePencilArea
|
|
|
|
|
|
anchors.fill: parent
|
|
|
|
|
|
anchors.margins: -6
|
|
|
|
|
|
hoverEnabled: true
|
|
|
|
|
|
onClicked: editProfileDialog.openFor(profileItem.index)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-15 00:22:27 +03:00
|
|
|
|
// ── Корзина — видна при наведении на строку ─────
|
|
|
|
|
|
Image {
|
|
|
|
|
|
id: profileTrash
|
|
|
|
|
|
width: 16; height: 16
|
|
|
|
|
|
anchors.right: parent.right
|
|
|
|
|
|
anchors.rightMargin: 10
|
|
|
|
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
|
|
|
|
visible: profileItem.hovered
|
|
|
|
|
|
opacity: profileTrashArea.containsMouse ? 1.0 : 0.7
|
|
|
|
|
|
source: "images/Trash.svg"
|
|
|
|
|
|
sourceSize.width: 16; sourceSize.height: 16
|
|
|
|
|
|
fillMode: Image.PreserveAspectFit
|
|
|
|
|
|
|
|
|
|
|
|
MouseArea {
|
|
|
|
|
|
id: profileTrashArea
|
|
|
|
|
|
anchors.fill: parent
|
|
|
|
|
|
anchors.margins: -6
|
|
|
|
|
|
hoverEnabled: true
|
|
|
|
|
|
onClicked: backend.removeProfile(profileItem.index)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-06-09 00:43:06 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ScrollIndicator.vertical: ScrollIndicator {
|
|
|
|
|
|
parent: profileItemList.parent
|
|
|
|
|
|
anchors.top: profileItemList.top
|
|
|
|
|
|
anchors.left: profileItemList.right
|
|
|
|
|
|
anchors.bottom: profileItemList.bottom
|
|
|
|
|
|
}
|
2025-12-29 02:18:35 +03:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
background: Rectangle {
|
|
|
|
|
|
color: "#232323"
|
|
|
|
|
|
radius: 6
|
2026-06-09 00:43:06 +03:00
|
|
|
|
border.color: "#232323"
|
2025-12-29 02:18:35 +03:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-27 09:21:51 +03:00
|
|
|
|
// ── Активная сборка ────────────────────────────────────────────────────
|
|
|
|
|
|
// Кнопка занимает место бывшего выпадающего списка версий: и сама сборка,
|
|
|
|
|
|
// и её содержимое правятся теперь в модальном окне.
|
|
|
|
|
|
Button {
|
|
|
|
|
|
id: buildButton
|
2026-06-09 00:43:06 +03:00
|
|
|
|
x: 624
|
|
|
|
|
|
y: 451
|
|
|
|
|
|
width: 183
|
2025-12-29 02:18:35 +03:00
|
|
|
|
height: 42
|
|
|
|
|
|
anchors.right: button.right
|
|
|
|
|
|
anchors.bottom: button.top
|
2026-06-14 20:04:17 +03:00
|
|
|
|
anchors.rightMargin: 0
|
2025-12-29 02:18:35 +03:00
|
|
|
|
anchors.bottomMargin: -218
|
2026-08-27 09:21:51 +03:00
|
|
|
|
hoverEnabled: true
|
|
|
|
|
|
enabled: !backend.gameRunning && !backend.switching
|
2026-06-09 00:43:06 +03:00
|
|
|
|
|
|
|
|
|
|
contentItem: Text {
|
2026-08-27 09:21:51 +03:00
|
|
|
|
text: backend.activeBuildName !== "" ? backend.activeBuildName
|
|
|
|
|
|
: qsTr("Сборка не выбрана")
|
2026-06-09 00:43:06 +03:00
|
|
|
|
color: "#ffffff"
|
2026-08-27 09:21:51 +03:00
|
|
|
|
font: buildButton.font
|
2026-06-09 00:43:06 +03:00
|
|
|
|
verticalAlignment: Text.AlignVCenter
|
|
|
|
|
|
horizontalAlignment: Text.AlignLeft
|
|
|
|
|
|
elide: Text.ElideRight
|
2026-08-27 09:21:51 +03:00
|
|
|
|
leftPadding: 30
|
|
|
|
|
|
rightPadding: 5
|
2026-06-09 00:43:06 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
background: Rectangle {
|
2026-08-27 09:21:51 +03:00
|
|
|
|
color: buildButton.pressed || buildsDialog.opened ? "#91B315" : "#232323"
|
2026-06-09 00:43:06 +03:00
|
|
|
|
radius: 6
|
2026-08-27 09:21:51 +03:00
|
|
|
|
border.color: buildButton.hovered ? "#91B315" : "#232323"
|
|
|
|
|
|
border.width: 1
|
2026-06-09 00:43:06 +03:00
|
|
|
|
|
2026-08-27 09:21:51 +03:00
|
|
|
|
Image {
|
|
|
|
|
|
width: 10; height: 10
|
|
|
|
|
|
anchors.left: parent.left
|
|
|
|
|
|
anchors.leftMargin: 10
|
|
|
|
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
|
|
|
|
source: buildsDialog.opened ? "images/Profile_Box/Asset_23.svg"
|
|
|
|
|
|
: "images/Profile_Box/Asset_24.svg"
|
|
|
|
|
|
rotation: 180
|
|
|
|
|
|
sourceSize.width: 10; sourceSize.height: 10
|
|
|
|
|
|
fillMode: Image.PreserveAspectFit
|
2025-12-29 02:18:35 +03:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-08-27 09:21:51 +03:00
|
|
|
|
|
|
|
|
|
|
ToolTip.visible: hovered && !buildsDialog.opened
|
|
|
|
|
|
ToolTip.text: qsTr("Пользовательские сборки")
|
|
|
|
|
|
|
|
|
|
|
|
onClicked: buildsDialog.open()
|
2026-06-09 00:43:06 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-19 21:03:37 +03:00
|
|
|
|
// ── Open mods folder button ────────────────────────────────────────────
|
|
|
|
|
|
Button {
|
|
|
|
|
|
id: folderButton
|
2026-07-05 19:35:30 +03:00
|
|
|
|
width: 70
|
|
|
|
|
|
height: 40
|
2026-08-27 09:21:51 +03:00
|
|
|
|
anchors.left: buildButton.left
|
2026-07-05 19:35:30 +03:00
|
|
|
|
anchors.bottom: profileBox.bottom
|
|
|
|
|
|
anchors.leftMargin: -59
|
|
|
|
|
|
anchors.bottomMargin: -45
|
2026-06-19 21:03:37 +03:00
|
|
|
|
hoverEnabled: true
|
|
|
|
|
|
|
|
|
|
|
|
background: Image {
|
|
|
|
|
|
id: folderButtonImage
|
|
|
|
|
|
anchors.fill: parent
|
|
|
|
|
|
source: folderButton.pressed ? "images/Folder/Folder_Pressed.svg"
|
|
|
|
|
|
: folderButton.hovered ? "images/Folder/Folder_Active.svg"
|
|
|
|
|
|
: "images/Folder/Folder_Idle.svg"
|
|
|
|
|
|
fillMode: Image.PreserveAspectFit
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ToolTip.visible: hovered
|
|
|
|
|
|
ToolTip.text: qsTr("Открыть папку с модами Minecraft")
|
|
|
|
|
|
|
|
|
|
|
|
onClicked: backend.openMinecraftFolder()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-22 18:19:13 +03:00
|
|
|
|
// ── Settings button ────────────────────────────────────────────────────
|
|
|
|
|
|
Button {
|
|
|
|
|
|
id: settingsButton
|
|
|
|
|
|
width: 70
|
|
|
|
|
|
height: 40
|
|
|
|
|
|
anchors.left: folderButton.right
|
|
|
|
|
|
anchors.verticalCenter: folderButton.verticalCenter
|
|
|
|
|
|
anchors.leftMargin: 8
|
|
|
|
|
|
hoverEnabled: true
|
|
|
|
|
|
|
|
|
|
|
|
background: Image {
|
|
|
|
|
|
anchors.fill: parent
|
2026-08-27 16:44:42 +03:00
|
|
|
|
// Ассеты Options названы наоборот относительно Folder: зелёный
|
|
|
|
|
|
// #637a10 лежит в Options_Pressed.svg, а Options_active.svg —
|
|
|
|
|
|
// тёмный, как Idle. Берём их по цвету, а не по имени, чтобы
|
|
|
|
|
|
// наведение выглядело так же, как у кнопки папки модов.
|
|
|
|
|
|
source: settingsButton.pressed ? "images/Options/Options_active.svg"
|
|
|
|
|
|
: settingsButton.hovered ? "images/Options/Options_Pressed.svg"
|
2026-08-22 18:19:13 +03:00
|
|
|
|
: "images/Options/Options_Idle.svg"
|
|
|
|
|
|
fillMode: Image.PreserveAspectFit
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ToolTip.visible: hovered
|
|
|
|
|
|
ToolTip.text: qsTr("Настройки запуска: Java, память, папка игры")
|
|
|
|
|
|
|
|
|
|
|
|
onClicked: settingsDialog.load()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-31 00:59:14 +03:00
|
|
|
|
// ── Seasonal builds button ─────────────────────────────────────────────
|
|
|
|
|
|
// Правый нижний угол — единственная свободная часть окна: панели хода работ
|
|
|
|
|
|
// висят слева, а всё остальное собрано вокруг кнопки запуска.
|
|
|
|
|
|
Button {
|
|
|
|
|
|
id: seasonsButton
|
|
|
|
|
|
width: 183
|
|
|
|
|
|
height: 42
|
|
|
|
|
|
anchors.right: parent.right
|
|
|
|
|
|
anchors.bottom: parent.bottom
|
|
|
|
|
|
anchors.rightMargin: 24
|
|
|
|
|
|
anchors.bottomMargin: 24
|
|
|
|
|
|
hoverEnabled: true
|
|
|
|
|
|
|
|
|
|
|
|
contentItem: Text {
|
|
|
|
|
|
text: qsTr("Сезонные сборки")
|
|
|
|
|
|
color: "#ffffff"
|
|
|
|
|
|
font.pixelSize: 14
|
|
|
|
|
|
elide: Text.ElideRight
|
|
|
|
|
|
horizontalAlignment: Text.AlignHCenter
|
|
|
|
|
|
verticalAlignment: Text.AlignVCenter
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
background: Rectangle {
|
|
|
|
|
|
color: seasonsButton.pressed ? "#2d2d2d" : "#232323"
|
|
|
|
|
|
radius: 6
|
|
|
|
|
|
border.color: "#91B315"
|
|
|
|
|
|
border.width: seasonsButton.hovered ? 2 : 1
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ToolTip.visible: hovered
|
|
|
|
|
|
ToolTip.text: qsTr("Готовые сборки с сервера: моды, конфиги и Java одной кнопкой")
|
|
|
|
|
|
|
|
|
|
|
|
onClicked: seasonalDialog.openCatalog()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
SeasonalBuildsDialog {
|
|
|
|
|
|
id: seasonalDialog
|
|
|
|
|
|
backend: backend
|
|
|
|
|
|
x: (window.width - width) / 2
|
|
|
|
|
|
y: (window.height - height) / 2
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-09 00:43:06 +03:00
|
|
|
|
// ── Add Profile Dialog ─────────────────────────────────────────────────
|
|
|
|
|
|
// Bug fix: header/footer must be Item (not Rectangle) with explicit
|
|
|
|
|
|
// implicitHeight so Dialog correctly computes its own total height.
|
|
|
|
|
|
// Rectangle.implicitHeight defaults to 0 regardless of height:.
|
|
|
|
|
|
Dialog {
|
|
|
|
|
|
id: addProfileDialog
|
|
|
|
|
|
modal: true
|
|
|
|
|
|
width: 320
|
|
|
|
|
|
x: (window.width - width) / 2
|
|
|
|
|
|
y: (window.height - height) / 2
|
|
|
|
|
|
padding: 0
|
|
|
|
|
|
|
|
|
|
|
|
background: Rectangle {
|
|
|
|
|
|
color: "#1e1e1e"
|
|
|
|
|
|
radius: 10
|
|
|
|
|
|
border.color: "#91B315"
|
|
|
|
|
|
border.width: 1
|
2025-12-29 02:18:35 +03:00
|
|
|
|
}
|
2026-06-09 00:43:06 +03:00
|
|
|
|
|
|
|
|
|
|
header: Item {
|
|
|
|
|
|
implicitHeight: 52 // tells Dialog how tall the header is
|
|
|
|
|
|
Text {
|
|
|
|
|
|
anchors.centerIn: parent
|
|
|
|
|
|
text: "Новый профиль"
|
2025-12-29 02:18:35 +03:00
|
|
|
|
color: "#ffffff"
|
2026-06-09 00:43:06 +03:00
|
|
|
|
font.pixelSize: 17
|
|
|
|
|
|
font.bold: true
|
2025-12-29 02:18:35 +03:00
|
|
|
|
}
|
2026-06-09 00:43:06 +03:00
|
|
|
|
Rectangle {
|
|
|
|
|
|
anchors.bottom: parent.bottom
|
|
|
|
|
|
width: parent.width
|
|
|
|
|
|
height: 1
|
|
|
|
|
|
color: "#333333"
|
2025-12-29 02:18:35 +03:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-06-09 00:43:06 +03:00
|
|
|
|
|
|
|
|
|
|
contentItem: Column {
|
|
|
|
|
|
spacing: 12
|
|
|
|
|
|
topPadding: 20
|
|
|
|
|
|
bottomPadding: 20
|
|
|
|
|
|
|
|
|
|
|
|
TextField {
|
|
|
|
|
|
id: pfName
|
|
|
|
|
|
x: 20
|
|
|
|
|
|
width: parent.width - 40
|
|
|
|
|
|
placeholderText: "Имя профиля"
|
|
|
|
|
|
color: "#ffffff"
|
|
|
|
|
|
placeholderTextColor: "#666666"
|
|
|
|
|
|
background: Rectangle {
|
|
|
|
|
|
color: "#2a2a2a"
|
|
|
|
|
|
radius: 6
|
|
|
|
|
|
border.color: pfName.activeFocus ? "#91B315" : "#444444"
|
|
|
|
|
|
border.width: 1
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TextField {
|
|
|
|
|
|
id: pfLogin
|
|
|
|
|
|
x: 20
|
|
|
|
|
|
width: parent.width - 40
|
2026-09-01 22:25:23 +03:00
|
|
|
|
enabled: pfAuth.currentIndex !== 2
|
|
|
|
|
|
placeholderText: pfAuth.currentIndex === 2 ? "Ник придёт из аккаунта" : "Логин"
|
2026-06-09 00:43:06 +03:00
|
|
|
|
color: "#ffffff"
|
|
|
|
|
|
placeholderTextColor: "#666666"
|
|
|
|
|
|
background: Rectangle {
|
|
|
|
|
|
color: "#2a2a2a"
|
|
|
|
|
|
radius: 6
|
|
|
|
|
|
border.color: pfLogin.activeFocus ? "#91B315" : "#444444"
|
|
|
|
|
|
border.width: 1
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TextField {
|
|
|
|
|
|
id: pfPassword
|
|
|
|
|
|
x: 20
|
|
|
|
|
|
width: parent.width - 40
|
2026-08-22 18:19:13 +03:00
|
|
|
|
enabled: pfAuth.currentIndex === 1
|
2026-09-01 22:25:23 +03:00
|
|
|
|
placeholderText: pfAuth.currentIndex === 1 ? "Пароль Ely.by" : "Не нужен для этого типа"
|
2026-06-09 00:43:06 +03:00
|
|
|
|
echoMode: TextInput.Password
|
|
|
|
|
|
color: "#ffffff"
|
|
|
|
|
|
placeholderTextColor: "#666666"
|
|
|
|
|
|
background: Rectangle {
|
|
|
|
|
|
color: "#2a2a2a"
|
|
|
|
|
|
radius: 6
|
|
|
|
|
|
border.color: pfPassword.activeFocus ? "#91B315" : "#444444"
|
|
|
|
|
|
border.width: 1
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-08-22 18:19:13 +03:00
|
|
|
|
|
|
|
|
|
|
DarkCombo {
|
|
|
|
|
|
id: pfAuth
|
|
|
|
|
|
x: 20
|
|
|
|
|
|
width: parent.width - 40
|
2026-09-01 22:25:23 +03:00
|
|
|
|
model: backend.microsoftAvailable
|
|
|
|
|
|
? ["Офлайн (без пароля)", "Ely.by", "Microsoft (лицензия)"]
|
|
|
|
|
|
: ["Офлайн (без пароля)", "Ely.by"]
|
2026-08-22 18:19:13 +03:00
|
|
|
|
}
|
2025-12-29 02:18:35 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-09 00:43:06 +03:00
|
|
|
|
footer: Item {
|
|
|
|
|
|
implicitHeight: 60 // tells Dialog how tall the footer is
|
|
|
|
|
|
Rectangle {
|
|
|
|
|
|
anchors.top: parent.top
|
|
|
|
|
|
width: parent.width
|
|
|
|
|
|
height: 1
|
|
|
|
|
|
color: "#333333"
|
|
|
|
|
|
}
|
|
|
|
|
|
Row {
|
|
|
|
|
|
anchors.centerIn: parent
|
|
|
|
|
|
spacing: 12
|
|
|
|
|
|
|
|
|
|
|
|
Button {
|
|
|
|
|
|
text: "Отмена"
|
|
|
|
|
|
width: 110; height: 36
|
|
|
|
|
|
contentItem: Text {
|
|
|
|
|
|
text: parent.text
|
|
|
|
|
|
color: "#ffffff"
|
|
|
|
|
|
horizontalAlignment: Text.AlignHCenter
|
|
|
|
|
|
verticalAlignment: Text.AlignVCenter
|
|
|
|
|
|
}
|
|
|
|
|
|
background: Rectangle {
|
|
|
|
|
|
color: parent.pressed ? "#444444" : "#333333"
|
|
|
|
|
|
radius: 6
|
|
|
|
|
|
}
|
|
|
|
|
|
onClicked: addProfileDialog.reject()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Button {
|
|
|
|
|
|
text: "Добавить"
|
|
|
|
|
|
width: 110; height: 36
|
|
|
|
|
|
contentItem: Text {
|
|
|
|
|
|
text: parent.text
|
|
|
|
|
|
color: "#ffffff"
|
|
|
|
|
|
horizontalAlignment: Text.AlignHCenter
|
|
|
|
|
|
verticalAlignment: Text.AlignVCenter
|
|
|
|
|
|
}
|
|
|
|
|
|
background: Rectangle {
|
|
|
|
|
|
color: parent.pressed ? "#6a8510" : "#91B315"
|
|
|
|
|
|
radius: 6
|
|
|
|
|
|
}
|
|
|
|
|
|
onClicked: addProfileDialog.accept()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-29 02:18:35 +03:00
|
|
|
|
}
|
2026-06-09 00:43:06 +03:00
|
|
|
|
|
|
|
|
|
|
onAccepted: {
|
|
|
|
|
|
const name = pfName.text.trim()
|
2026-09-01 22:25:23 +03:00
|
|
|
|
const type = pfAuth.currentIndex === 2 ? "microsoft"
|
|
|
|
|
|
: pfAuth.currentIndex === 1 ? "elyby" : "offline"
|
2026-06-09 00:43:06 +03:00
|
|
|
|
if (name !== "") {
|
2026-09-01 22:25:23 +03:00
|
|
|
|
backend.addProfile(name, pfLogin.text.trim(), pfPassword.text, type)
|
2026-06-09 00:43:06 +03:00
|
|
|
|
profileBox.currentIndex = backend.profileNames.length - 1
|
2026-09-01 22:25:23 +03:00
|
|
|
|
// Профиль Microsoft без входа бесполезен: сразу показываем окно.
|
|
|
|
|
|
if (type === "microsoft")
|
|
|
|
|
|
backend.startMicrosoftLogin(backend.profileNames.length - 1)
|
2026-06-09 00:43:06 +03:00
|
|
|
|
}
|
2026-08-22 18:19:13 +03:00
|
|
|
|
pfName.text = ""; pfLogin.text = ""; pfPassword.text = ""; pfAuth.currentIndex = 0
|
2025-12-29 02:18:35 +03:00
|
|
|
|
}
|
2026-06-09 00:43:06 +03:00
|
|
|
|
onRejected: {
|
2026-08-22 18:19:13 +03:00
|
|
|
|
pfName.text = ""; pfLogin.text = ""; pfPassword.text = ""; pfAuth.currentIndex = 0
|
2025-12-29 02:18:35 +03:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-27 09:21:51 +03:00
|
|
|
|
// ── Edit Profile Dialog ────────────────────────────────────────────────
|
|
|
|
|
|
// Идентичен диалогу создания, но с предзаполненными данными выбранного
|
|
|
|
|
|
// профиля. По нажатию ОК данные перезаписываются под тем же индексом.
|
2026-06-09 00:43:06 +03:00
|
|
|
|
Dialog {
|
2026-08-27 09:21:51 +03:00
|
|
|
|
id: editProfileDialog
|
2026-06-09 00:43:06 +03:00
|
|
|
|
modal: true
|
|
|
|
|
|
width: 320
|
|
|
|
|
|
x: (window.width - width) / 2
|
|
|
|
|
|
y: (window.height - height) / 2
|
|
|
|
|
|
padding: 0
|
|
|
|
|
|
|
2026-08-27 09:21:51 +03:00
|
|
|
|
property int editIndex: -1
|
2026-09-01 22:25:23 +03:00
|
|
|
|
// Состояние входа в Microsoft у открытого профиля — для строки статуса.
|
|
|
|
|
|
property bool msProfile: false
|
|
|
|
|
|
property bool msLinked: false
|
|
|
|
|
|
property string msName: ""
|
2026-08-27 09:21:51 +03:00
|
|
|
|
|
|
|
|
|
|
function openFor(index) {
|
|
|
|
|
|
const data = backend.profileAt(index)
|
|
|
|
|
|
editIndex = index
|
|
|
|
|
|
epName.text = data.name || ""
|
|
|
|
|
|
epLogin.text = data.login || ""
|
|
|
|
|
|
epPassword.text = data.password || ""
|
2026-09-01 22:25:23 +03:00
|
|
|
|
msProfile = data.authType === "microsoft"
|
|
|
|
|
|
epAuth.currentIndex = msProfile ? 2 : data.authType === "elyby" ? 1 : 0
|
|
|
|
|
|
msLinked = data.hasMicrosoftSession === true
|
|
|
|
|
|
msName = data.resolvedName || ""
|
2026-08-27 09:21:51 +03:00
|
|
|
|
profileBox.popup.close()
|
|
|
|
|
|
open()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-09 00:43:06 +03:00
|
|
|
|
background: Rectangle {
|
|
|
|
|
|
color: "#1e1e1e"
|
|
|
|
|
|
radius: 10
|
|
|
|
|
|
border.color: "#91B315"
|
|
|
|
|
|
border.width: 1
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
header: Item {
|
|
|
|
|
|
implicitHeight: 52
|
|
|
|
|
|
Text {
|
|
|
|
|
|
anchors.centerIn: parent
|
2026-08-27 09:21:51 +03:00
|
|
|
|
text: "Редактирование профиля"
|
2026-06-09 00:43:06 +03:00
|
|
|
|
color: "#ffffff"
|
|
|
|
|
|
font.pixelSize: 17
|
|
|
|
|
|
font.bold: true
|
|
|
|
|
|
}
|
|
|
|
|
|
Rectangle {
|
|
|
|
|
|
anchors.bottom: parent.bottom
|
|
|
|
|
|
width: parent.width
|
|
|
|
|
|
height: 1
|
|
|
|
|
|
color: "#333333"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
contentItem: Column {
|
|
|
|
|
|
spacing: 12
|
|
|
|
|
|
topPadding: 20
|
|
|
|
|
|
bottomPadding: 20
|
|
|
|
|
|
|
|
|
|
|
|
TextField {
|
2026-08-27 09:21:51 +03:00
|
|
|
|
id: epName
|
2026-06-09 00:43:06 +03:00
|
|
|
|
x: 20
|
|
|
|
|
|
width: parent.width - 40
|
2026-08-27 09:21:51 +03:00
|
|
|
|
placeholderText: "Имя профиля"
|
2026-06-09 00:43:06 +03:00
|
|
|
|
color: "#ffffff"
|
|
|
|
|
|
placeholderTextColor: "#666666"
|
|
|
|
|
|
background: Rectangle {
|
|
|
|
|
|
color: "#2a2a2a"
|
|
|
|
|
|
radius: 6
|
2026-08-27 09:21:51 +03:00
|
|
|
|
border.color: epName.activeFocus ? "#91B315" : "#444444"
|
2026-06-09 00:43:06 +03:00
|
|
|
|
border.width: 1
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TextField {
|
2026-08-27 09:21:51 +03:00
|
|
|
|
id: epLogin
|
2026-06-09 00:43:06 +03:00
|
|
|
|
x: 20
|
|
|
|
|
|
width: parent.width - 40
|
2026-09-01 22:25:23 +03:00
|
|
|
|
enabled: epAuth.currentIndex !== 2
|
|
|
|
|
|
placeholderText: epAuth.currentIndex === 2 ? "Ник придёт из аккаунта" : "Логин"
|
2026-06-09 00:43:06 +03:00
|
|
|
|
color: "#ffffff"
|
|
|
|
|
|
placeholderTextColor: "#666666"
|
|
|
|
|
|
background: Rectangle {
|
|
|
|
|
|
color: "#2a2a2a"
|
|
|
|
|
|
radius: 6
|
2026-08-27 09:21:51 +03:00
|
|
|
|
border.color: epLogin.activeFocus ? "#91B315" : "#444444"
|
2026-06-09 00:43:06 +03:00
|
|
|
|
border.width: 1
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-08-22 18:19:13 +03:00
|
|
|
|
|
2026-08-27 09:21:51 +03:00
|
|
|
|
TextField {
|
|
|
|
|
|
id: epPassword
|
2026-08-22 18:19:13 +03:00
|
|
|
|
x: 20
|
|
|
|
|
|
width: parent.width - 40
|
2026-08-27 09:21:51 +03:00
|
|
|
|
enabled: epAuth.currentIndex === 1
|
2026-09-01 22:25:23 +03:00
|
|
|
|
placeholderText: epAuth.currentIndex === 1 ? "Пароль Ely.by" : "Не нужен для этого типа"
|
2026-08-27 09:21:51 +03:00
|
|
|
|
echoMode: TextInput.Password
|
|
|
|
|
|
color: "#ffffff"
|
|
|
|
|
|
placeholderTextColor: "#666666"
|
|
|
|
|
|
background: Rectangle {
|
|
|
|
|
|
color: "#2a2a2a"
|
|
|
|
|
|
radius: 6
|
|
|
|
|
|
border.color: epPassword.activeFocus ? "#91B315" : "#444444"
|
|
|
|
|
|
border.width: 1
|
|
|
|
|
|
}
|
2026-08-22 18:19:13 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-27 09:21:51 +03:00
|
|
|
|
DarkCombo {
|
|
|
|
|
|
id: epAuth
|
2026-08-22 18:19:13 +03:00
|
|
|
|
x: 20
|
|
|
|
|
|
width: parent.width - 40
|
2026-09-01 22:25:23 +03:00
|
|
|
|
// Тип уже сохранённого профиля показываем всегда: иначе в
|
|
|
|
|
|
// сборке без WebEngine он молча стал бы офлайновым.
|
|
|
|
|
|
model: (backend.microsoftAvailable || editProfileDialog.msProfile)
|
|
|
|
|
|
? ["Офлайн (без пароля)", "Ely.by", "Microsoft (лицензия)"]
|
|
|
|
|
|
: ["Офлайн (без пароля)", "Ely.by"]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Состояние аккаунта Microsoft и кнопка повторного входа: токен
|
|
|
|
|
|
// может протухнуть, а сменить аккаунт иначе нечем.
|
|
|
|
|
|
Column {
|
|
|
|
|
|
x: 20
|
|
|
|
|
|
width: parent.width - 40
|
|
|
|
|
|
spacing: 8
|
|
|
|
|
|
visible: epAuth.currentIndex === 2
|
|
|
|
|
|
|
|
|
|
|
|
Text {
|
|
|
|
|
|
text: editProfileDialog.msLinked
|
|
|
|
|
|
? qsTr("Вход выполнен: %1").arg(editProfileDialog.msName)
|
|
|
|
|
|
: qsTr("Вход не выполнен")
|
|
|
|
|
|
color: editProfileDialog.msLinked ? "#91B315" : "#aaaaaa"
|
|
|
|
|
|
font.pixelSize: 13
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Button {
|
|
|
|
|
|
width: parent.width; height: 34
|
|
|
|
|
|
enabled: backend.microsoftAvailable
|
|
|
|
|
|
text: editProfileDialog.msLinked ? "Войти заново" : "Войти в Microsoft"
|
|
|
|
|
|
contentItem: Text {
|
|
|
|
|
|
text: parent.text
|
|
|
|
|
|
color: "#ffffff"
|
|
|
|
|
|
horizontalAlignment: Text.AlignHCenter
|
|
|
|
|
|
verticalAlignment: Text.AlignVCenter
|
|
|
|
|
|
}
|
|
|
|
|
|
background: Rectangle {
|
|
|
|
|
|
color: parent.pressed ? "#6a8510" : "#91B315"
|
|
|
|
|
|
radius: 6
|
|
|
|
|
|
}
|
|
|
|
|
|
onClicked: {
|
|
|
|
|
|
// Тип профиля мог быть только что переключён на
|
|
|
|
|
|
// Microsoft — сохраняем его до входа, иначе backend
|
|
|
|
|
|
// припишет токены профилю другого типа.
|
|
|
|
|
|
backend.updateProfile(editProfileDialog.editIndex,
|
|
|
|
|
|
epName.text.trim(), epLogin.text.trim(),
|
|
|
|
|
|
epPassword.text, "microsoft")
|
|
|
|
|
|
editProfileDialog.close()
|
|
|
|
|
|
backend.startMicrosoftLogin(editProfileDialog.editIndex)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-08-22 18:19:13 +03:00
|
|
|
|
}
|
2026-06-09 00:43:06 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
footer: Item {
|
|
|
|
|
|
implicitHeight: 60
|
|
|
|
|
|
Rectangle {
|
|
|
|
|
|
anchors.top: parent.top
|
|
|
|
|
|
width: parent.width
|
|
|
|
|
|
height: 1
|
|
|
|
|
|
color: "#333333"
|
|
|
|
|
|
}
|
|
|
|
|
|
Row {
|
|
|
|
|
|
anchors.centerIn: parent
|
|
|
|
|
|
spacing: 12
|
|
|
|
|
|
|
|
|
|
|
|
Button {
|
|
|
|
|
|
text: "Отмена"
|
|
|
|
|
|
width: 110; height: 36
|
|
|
|
|
|
contentItem: Text {
|
|
|
|
|
|
text: parent.text
|
|
|
|
|
|
color: "#ffffff"
|
|
|
|
|
|
horizontalAlignment: Text.AlignHCenter
|
|
|
|
|
|
verticalAlignment: Text.AlignVCenter
|
|
|
|
|
|
}
|
|
|
|
|
|
background: Rectangle {
|
|
|
|
|
|
color: parent.pressed ? "#444444" : "#333333"
|
|
|
|
|
|
radius: 6
|
|
|
|
|
|
}
|
2026-08-27 09:21:51 +03:00
|
|
|
|
onClicked: editProfileDialog.reject()
|
2026-06-09 00:43:06 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Button {
|
2026-08-27 09:21:51 +03:00
|
|
|
|
text: "ОК"
|
2026-06-09 00:43:06 +03:00
|
|
|
|
width: 110; height: 36
|
|
|
|
|
|
contentItem: Text {
|
|
|
|
|
|
text: parent.text
|
|
|
|
|
|
color: "#ffffff"
|
|
|
|
|
|
horizontalAlignment: Text.AlignHCenter
|
|
|
|
|
|
verticalAlignment: Text.AlignVCenter
|
|
|
|
|
|
}
|
|
|
|
|
|
background: Rectangle {
|
|
|
|
|
|
color: parent.pressed ? "#6a8510" : "#91B315"
|
|
|
|
|
|
radius: 6
|
|
|
|
|
|
}
|
2026-08-27 09:21:51 +03:00
|
|
|
|
onClicked: editProfileDialog.accept()
|
2026-06-09 00:43:06 +03:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onAccepted: {
|
2026-08-27 09:21:51 +03:00
|
|
|
|
const name = epName.text.trim()
|
|
|
|
|
|
if (editIndex >= 0 && name !== "")
|
|
|
|
|
|
backend.updateProfile(editIndex, name, epLogin.text.trim(), epPassword.text,
|
2026-09-01 22:25:23 +03:00
|
|
|
|
epAuth.currentIndex === 2 ? "microsoft"
|
|
|
|
|
|
: epAuth.currentIndex === 1 ? "elyby" : "offline")
|
2026-08-27 09:21:51 +03:00
|
|
|
|
editIndex = -1
|
|
|
|
|
|
epName.text = ""; epLogin.text = ""; epPassword.text = ""
|
2026-06-09 00:43:06 +03:00
|
|
|
|
}
|
|
|
|
|
|
onRejected: {
|
2026-08-27 09:21:51 +03:00
|
|
|
|
editIndex = -1
|
|
|
|
|
|
epName.text = ""; epLogin.text = ""; epPassword.text = ""
|
2026-06-09 00:43:06 +03:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-06-16 12:16:59 +03:00
|
|
|
|
|
2026-08-27 09:21:51 +03:00
|
|
|
|
// ── Builds Dialog ──────────────────────────────────────────────────────
|
|
|
|
|
|
BuildsDialog {
|
|
|
|
|
|
id: buildsDialog
|
|
|
|
|
|
backend: backend
|
|
|
|
|
|
x: (window.width - width) / 2
|
|
|
|
|
|
y: (window.height - height) / 2
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── Two-factor Dialog ──────────────────────────────────────────────────
|
|
|
|
|
|
// Ely.by отклоняет пароль с пометкой two factor — код добираем здесь и
|
|
|
|
|
|
// продолжаем прерванный запуск.
|
2026-06-16 12:16:59 +03:00
|
|
|
|
Dialog {
|
2026-08-27 09:21:51 +03:00
|
|
|
|
id: twoFactorDialog
|
2026-06-16 12:16:59 +03:00
|
|
|
|
modal: true
|
|
|
|
|
|
width: 320
|
|
|
|
|
|
x: (window.width - width) / 2
|
|
|
|
|
|
y: (window.height - height) / 2
|
|
|
|
|
|
padding: 0
|
2026-08-27 09:21:51 +03:00
|
|
|
|
closePolicy: Popup.NoAutoClose
|
2026-06-16 12:16:59 +03:00
|
|
|
|
|
2026-08-27 09:21:51 +03:00
|
|
|
|
property string profileName: ""
|
2026-06-16 12:16:59 +03:00
|
|
|
|
|
|
|
|
|
|
background: Rectangle {
|
|
|
|
|
|
color: "#1e1e1e"
|
|
|
|
|
|
radius: 10
|
|
|
|
|
|
border.color: "#91B315"
|
|
|
|
|
|
border.width: 1
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
header: Item {
|
|
|
|
|
|
implicitHeight: 52
|
|
|
|
|
|
Text {
|
|
|
|
|
|
anchors.centerIn: parent
|
2026-08-27 09:21:51 +03:00
|
|
|
|
text: "Двухфакторная аутентификация"
|
2026-06-16 12:16:59 +03:00
|
|
|
|
color: "#ffffff"
|
2026-08-27 09:21:51 +03:00
|
|
|
|
font.pixelSize: 15
|
2026-06-16 12:16:59 +03:00
|
|
|
|
font.bold: true
|
|
|
|
|
|
}
|
|
|
|
|
|
Rectangle {
|
|
|
|
|
|
anchors.bottom: parent.bottom
|
|
|
|
|
|
width: parent.width
|
|
|
|
|
|
height: 1
|
|
|
|
|
|
color: "#333333"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
contentItem: Column {
|
|
|
|
|
|
spacing: 12
|
|
|
|
|
|
topPadding: 20
|
|
|
|
|
|
bottomPadding: 20
|
|
|
|
|
|
|
2026-08-27 09:21:51 +03:00
|
|
|
|
Text {
|
|
|
|
|
|
x: 20
|
|
|
|
|
|
width: parent.width - 40
|
|
|
|
|
|
text: qsTr("Введите код из приложения-аутентификатора для «%1»")
|
|
|
|
|
|
.arg(twoFactorDialog.profileName)
|
|
|
|
|
|
color: "#aaaaaa"
|
|
|
|
|
|
font.pixelSize: 12
|
|
|
|
|
|
wrapMode: Text.Wrap
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-16 12:16:59 +03:00
|
|
|
|
TextField {
|
2026-08-27 09:21:51 +03:00
|
|
|
|
id: twoFactorCode
|
2026-08-22 18:19:13 +03:00
|
|
|
|
x: 20
|
|
|
|
|
|
width: parent.width - 40
|
|
|
|
|
|
placeholderText: "000000"
|
|
|
|
|
|
inputMethodHints: Qt.ImhDigitsOnly
|
|
|
|
|
|
color: "#ffffff"
|
|
|
|
|
|
placeholderTextColor: "#666666"
|
|
|
|
|
|
background: Rectangle {
|
|
|
|
|
|
color: "#2a2a2a"
|
|
|
|
|
|
radius: 6
|
|
|
|
|
|
border.color: twoFactorCode.activeFocus ? "#91B315" : "#444444"
|
|
|
|
|
|
border.width: 1
|
|
|
|
|
|
}
|
|
|
|
|
|
onAccepted: twoFactorDialog.accept()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
footer: Item {
|
|
|
|
|
|
implicitHeight: 60
|
|
|
|
|
|
Rectangle {
|
|
|
|
|
|
anchors.top: parent.top
|
|
|
|
|
|
width: parent.width
|
|
|
|
|
|
height: 1
|
|
|
|
|
|
color: "#333333"
|
|
|
|
|
|
}
|
|
|
|
|
|
Row {
|
|
|
|
|
|
anchors.centerIn: parent
|
|
|
|
|
|
spacing: 12
|
|
|
|
|
|
|
|
|
|
|
|
Button {
|
|
|
|
|
|
text: "Отмена"
|
|
|
|
|
|
width: 110; height: 36
|
|
|
|
|
|
contentItem: Text {
|
|
|
|
|
|
text: parent.text
|
|
|
|
|
|
color: "#ffffff"
|
|
|
|
|
|
horizontalAlignment: Text.AlignHCenter
|
|
|
|
|
|
verticalAlignment: Text.AlignVCenter
|
|
|
|
|
|
}
|
|
|
|
|
|
background: Rectangle {
|
|
|
|
|
|
color: parent.pressed ? "#444444" : "#333333"
|
|
|
|
|
|
radius: 6
|
|
|
|
|
|
}
|
|
|
|
|
|
onClicked: twoFactorDialog.reject()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Button {
|
|
|
|
|
|
text: "Войти"
|
|
|
|
|
|
width: 110; height: 36
|
|
|
|
|
|
contentItem: Text {
|
|
|
|
|
|
text: parent.text
|
|
|
|
|
|
color: "#ffffff"
|
|
|
|
|
|
horizontalAlignment: Text.AlignHCenter
|
|
|
|
|
|
verticalAlignment: Text.AlignVCenter
|
|
|
|
|
|
}
|
|
|
|
|
|
background: Rectangle {
|
|
|
|
|
|
color: parent.pressed ? "#6a8510" : "#91B315"
|
|
|
|
|
|
radius: 6
|
|
|
|
|
|
}
|
|
|
|
|
|
onClicked: twoFactorDialog.accept()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onAccepted: backend.submitTwoFactorCode(twoFactorCode.text)
|
|
|
|
|
|
onRejected: backend.cancelPendingLaunch()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── Settings Dialog ────────────────────────────────────────────────────
|
|
|
|
|
|
Dialog {
|
|
|
|
|
|
id: settingsDialog
|
|
|
|
|
|
modal: true
|
|
|
|
|
|
width: 460
|
|
|
|
|
|
x: (window.width - width) / 2
|
|
|
|
|
|
y: (window.height - height) / 2
|
|
|
|
|
|
padding: 0
|
|
|
|
|
|
|
2026-08-27 19:35:11 +03:00
|
|
|
|
// Выбранная сборка Java живёт в свойстве, а не в поле ввода: её выбирают
|
|
|
|
|
|
// в отдельном окне, а записывается она только по «Сохранить».
|
|
|
|
|
|
property string javaRuntimeId: ""
|
|
|
|
|
|
// Не привязка: javaRuntimeInfo() — обычный вызов, и сам он не
|
|
|
|
|
|
// пересчитается, когда сборка докачается. Обновляем по событиям.
|
|
|
|
|
|
property var javaRuntimeInfo: null
|
|
|
|
|
|
onJavaRuntimeIdChanged: settingsDialog.refreshJavaInfo()
|
|
|
|
|
|
|
|
|
|
|
|
function refreshJavaInfo() {
|
|
|
|
|
|
javaRuntimeInfo = javaRuntimeId === "" ? null
|
|
|
|
|
|
: backend.javaRuntimeInfo(javaRuntimeId)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Connections {
|
|
|
|
|
|
target: backend
|
|
|
|
|
|
function onJavaRuntimeInstalled(runtimeId) { settingsDialog.refreshJavaInfo() }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-22 18:19:13 +03:00
|
|
|
|
function load() {
|
|
|
|
|
|
const s = backend.settings()
|
|
|
|
|
|
stGameDir.text = s.gameDir || ""
|
|
|
|
|
|
stJavaPath.text = s.javaPath || ""
|
2026-08-27 19:35:11 +03:00
|
|
|
|
settingsDialog.javaRuntimeId = s.javaRuntime || ""
|
|
|
|
|
|
settingsDialog.refreshJavaInfo()
|
2026-08-22 18:19:13 +03:00
|
|
|
|
stMinMemory.text = String(s.minMemoryMb)
|
|
|
|
|
|
stMaxMemory.text = String(s.maxMemoryMb)
|
|
|
|
|
|
stJvmArgs.text = s.jvmArgs || ""
|
|
|
|
|
|
stWidth.text = String(s.windowWidth)
|
|
|
|
|
|
stHeight.text = String(s.windowHeight)
|
|
|
|
|
|
stFullscreen.checked = s.fullscreen === true
|
|
|
|
|
|
stResolved.text = qsTr("Папка игры: %1").arg(s.resolvedGameDir)
|
|
|
|
|
|
stJavaList.text = backend.detectedJava().join("\n") || qsTr("Java не найдена")
|
|
|
|
|
|
open()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
background: Rectangle {
|
|
|
|
|
|
color: "#1e1e1e"
|
|
|
|
|
|
radius: 10
|
|
|
|
|
|
border.color: "#91B315"
|
|
|
|
|
|
border.width: 1
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
header: Item {
|
|
|
|
|
|
implicitHeight: 52
|
|
|
|
|
|
Text {
|
|
|
|
|
|
anchors.centerIn: parent
|
|
|
|
|
|
text: "Настройки запуска"
|
|
|
|
|
|
color: "#ffffff"
|
|
|
|
|
|
font.pixelSize: 17
|
|
|
|
|
|
font.bold: true
|
|
|
|
|
|
}
|
|
|
|
|
|
Rectangle {
|
|
|
|
|
|
anchors.bottom: parent.bottom
|
|
|
|
|
|
width: parent.width
|
|
|
|
|
|
height: 1
|
|
|
|
|
|
color: "#333333"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
contentItem: Column {
|
|
|
|
|
|
spacing: 10
|
|
|
|
|
|
topPadding: 18
|
|
|
|
|
|
bottomPadding: 18
|
|
|
|
|
|
|
|
|
|
|
|
LabelledField {
|
|
|
|
|
|
id: stGameDir
|
|
|
|
|
|
x: 20
|
|
|
|
|
|
width: parent.width - 40
|
|
|
|
|
|
label: "Папка .minecraft"
|
|
|
|
|
|
placeholder: "по умолчанию"
|
|
|
|
|
|
}
|
|
|
|
|
|
LabelledField {
|
|
|
|
|
|
id: stJavaPath
|
|
|
|
|
|
x: 20
|
|
|
|
|
|
width: parent.width - 40
|
|
|
|
|
|
label: "Путь к Java"
|
|
|
|
|
|
placeholder: "автоопределение"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-27 19:35:11 +03:00
|
|
|
|
// Сборка Java из папки лаунчера. Выбрана — запуск идёт ей, а не тем,
|
|
|
|
|
|
// что нашлось в системе; путь выше остаётся запасным вариантом.
|
|
|
|
|
|
Column {
|
|
|
|
|
|
x: 20
|
|
|
|
|
|
width: parent.width - 40
|
|
|
|
|
|
spacing: 3
|
|
|
|
|
|
|
|
|
|
|
|
Text {
|
|
|
|
|
|
text: "Версия Java из папки лаунчера"
|
|
|
|
|
|
color: "#aaaaaa"
|
|
|
|
|
|
font.pixelSize: 11
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Rectangle {
|
|
|
|
|
|
width: parent.width
|
|
|
|
|
|
height: 32
|
|
|
|
|
|
radius: 6
|
|
|
|
|
|
color: "#2a2a2a"
|
|
|
|
|
|
border.color: javaFieldArea.containsMouse ? "#91B315" : "#444444"
|
|
|
|
|
|
border.width: 1
|
|
|
|
|
|
|
|
|
|
|
|
Text {
|
|
|
|
|
|
anchors.fill: parent
|
|
|
|
|
|
leftPadding: 10
|
|
|
|
|
|
rightPadding: 150
|
|
|
|
|
|
verticalAlignment: Text.AlignVCenter
|
|
|
|
|
|
elide: Text.ElideRight
|
|
|
|
|
|
color: settingsDialog.javaRuntimeId === "" ? "#666666" : "#ffffff"
|
|
|
|
|
|
text: {
|
|
|
|
|
|
if (settingsDialog.javaRuntimeId === "")
|
|
|
|
|
|
return qsTr("не выбрана — искать в системе")
|
|
|
|
|
|
const info = settingsDialog.javaRuntimeInfo
|
|
|
|
|
|
if (!info)
|
|
|
|
|
|
return settingsDialog.javaRuntimeId
|
|
|
|
|
|
return info.installed ? info.label
|
|
|
|
|
|
: qsTr("%1 — скачивается").arg(info.label)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Text {
|
|
|
|
|
|
anchors.right: clearJava.left
|
|
|
|
|
|
anchors.rightMargin: 12
|
|
|
|
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
|
|
|
|
text: qsTr("Выбрать…")
|
|
|
|
|
|
color: "#91B315"
|
|
|
|
|
|
font.pixelSize: 12
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Text {
|
|
|
|
|
|
id: clearJava
|
|
|
|
|
|
anchors.right: parent.right
|
|
|
|
|
|
anchors.rightMargin: 12
|
|
|
|
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
|
|
|
|
visible: settingsDialog.javaRuntimeId !== ""
|
|
|
|
|
|
text: "✕"
|
|
|
|
|
|
color: clearJavaArea.containsMouse ? "#cc6666" : "#666666"
|
|
|
|
|
|
font.pixelSize: 12
|
|
|
|
|
|
|
|
|
|
|
|
MouseArea {
|
|
|
|
|
|
id: clearJavaArea
|
|
|
|
|
|
anchors.fill: parent
|
|
|
|
|
|
anchors.margins: -6
|
|
|
|
|
|
hoverEnabled: true
|
|
|
|
|
|
onClicked: settingsDialog.javaRuntimeId = ""
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MouseArea {
|
|
|
|
|
|
id: javaFieldArea
|
|
|
|
|
|
anchors.fill: parent
|
|
|
|
|
|
anchors.rightMargin: 26
|
|
|
|
|
|
hoverEnabled: true
|
|
|
|
|
|
onClicked: javaPicker.openFor(settingsDialog.javaRuntimeId,
|
|
|
|
|
|
backend.requiredJavaMajor(backend.activeBuildIndex))
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-22 18:19:13 +03:00
|
|
|
|
Row {
|
|
|
|
|
|
x: 20
|
|
|
|
|
|
spacing: 10
|
|
|
|
|
|
LabelledField {
|
|
|
|
|
|
id: stMinMemory
|
|
|
|
|
|
width: 100
|
|
|
|
|
|
label: "-Xms, МБ"
|
|
|
|
|
|
validator: IntValidator { bottom: 0; top: 1048576 }
|
|
|
|
|
|
}
|
|
|
|
|
|
LabelledField {
|
|
|
|
|
|
id: stMaxMemory
|
|
|
|
|
|
width: 100
|
|
|
|
|
|
label: "-Xmx, МБ"
|
|
|
|
|
|
validator: IntValidator { bottom: 0; top: 1048576 }
|
|
|
|
|
|
}
|
|
|
|
|
|
LabelledField {
|
|
|
|
|
|
id: stWidth
|
|
|
|
|
|
width: 100
|
|
|
|
|
|
label: "Ширина окна"
|
|
|
|
|
|
validator: IntValidator { bottom: 0; top: 16384 }
|
|
|
|
|
|
}
|
|
|
|
|
|
LabelledField {
|
|
|
|
|
|
id: stHeight
|
|
|
|
|
|
width: 100
|
|
|
|
|
|
label: "Высота окна"
|
|
|
|
|
|
validator: IntValidator { bottom: 0; top: 16384 }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
LabelledField {
|
|
|
|
|
|
id: stJvmArgs
|
|
|
|
|
|
x: 20
|
|
|
|
|
|
width: parent.width - 40
|
|
|
|
|
|
label: "Дополнительные аргументы JVM"
|
|
|
|
|
|
placeholder: "-XX:+UseG1GC …"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CheckBox {
|
|
|
|
|
|
id: stFullscreen
|
|
|
|
|
|
x: 20
|
|
|
|
|
|
text: "Запускать в полноэкранном режиме"
|
|
|
|
|
|
|
|
|
|
|
|
// Индикатор Basic-стиля — крупный светлый квадрат, на тёмном
|
|
|
|
|
|
// фоне диалога он не читается. Рисуем свой в палитре окна.
|
|
|
|
|
|
indicator: Rectangle {
|
|
|
|
|
|
implicitWidth: 18
|
|
|
|
|
|
implicitHeight: 18
|
|
|
|
|
|
x: stFullscreen.leftPadding
|
|
|
|
|
|
y: (stFullscreen.height - height) / 2
|
|
|
|
|
|
radius: 4
|
|
|
|
|
|
color: "#2a2a2a"
|
|
|
|
|
|
border.color: stFullscreen.checked ? "#91B315" : "#444444"
|
|
|
|
|
|
border.width: 1
|
|
|
|
|
|
|
|
|
|
|
|
Rectangle {
|
|
|
|
|
|
anchors.centerIn: parent
|
|
|
|
|
|
width: 10; height: 10
|
|
|
|
|
|
radius: 2
|
|
|
|
|
|
color: "#91B315"
|
|
|
|
|
|
visible: stFullscreen.checked
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
contentItem: Text {
|
|
|
|
|
|
text: stFullscreen.text
|
|
|
|
|
|
color: "#ffffff"
|
|
|
|
|
|
leftPadding: (stFullscreen.indicator ? stFullscreen.indicator.width : 0) + 6
|
|
|
|
|
|
verticalAlignment: Text.AlignVCenter
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Text {
|
|
|
|
|
|
id: stResolved
|
|
|
|
|
|
x: 20
|
|
|
|
|
|
width: parent.width - 40
|
|
|
|
|
|
color: "#888888"
|
|
|
|
|
|
font.pixelSize: 11
|
|
|
|
|
|
wrapMode: Text.Wrap
|
|
|
|
|
|
}
|
|
|
|
|
|
Text {
|
|
|
|
|
|
id: stJavaList
|
|
|
|
|
|
x: 20
|
|
|
|
|
|
width: parent.width - 40
|
|
|
|
|
|
color: "#888888"
|
|
|
|
|
|
font.pixelSize: 11
|
|
|
|
|
|
wrapMode: Text.Wrap
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
footer: Item {
|
|
|
|
|
|
implicitHeight: 60
|
|
|
|
|
|
Rectangle {
|
|
|
|
|
|
anchors.top: parent.top
|
|
|
|
|
|
width: parent.width
|
|
|
|
|
|
height: 1
|
|
|
|
|
|
color: "#333333"
|
|
|
|
|
|
}
|
|
|
|
|
|
Row {
|
|
|
|
|
|
anchors.centerIn: parent
|
|
|
|
|
|
spacing: 12
|
|
|
|
|
|
|
|
|
|
|
|
Button {
|
|
|
|
|
|
text: "Отмена"
|
|
|
|
|
|
width: 110; height: 36
|
|
|
|
|
|
contentItem: Text {
|
|
|
|
|
|
text: parent.text
|
|
|
|
|
|
color: "#ffffff"
|
|
|
|
|
|
horizontalAlignment: Text.AlignHCenter
|
|
|
|
|
|
verticalAlignment: Text.AlignVCenter
|
|
|
|
|
|
}
|
|
|
|
|
|
background: Rectangle {
|
|
|
|
|
|
color: parent.pressed ? "#444444" : "#333333"
|
|
|
|
|
|
radius: 6
|
|
|
|
|
|
}
|
|
|
|
|
|
onClicked: settingsDialog.reject()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Button {
|
|
|
|
|
|
text: "Сохранить"
|
|
|
|
|
|
width: 110; height: 36
|
|
|
|
|
|
contentItem: Text {
|
|
|
|
|
|
text: parent.text
|
|
|
|
|
|
color: "#ffffff"
|
|
|
|
|
|
horizontalAlignment: Text.AlignHCenter
|
|
|
|
|
|
verticalAlignment: Text.AlignVCenter
|
|
|
|
|
|
}
|
|
|
|
|
|
background: Rectangle {
|
|
|
|
|
|
color: parent.pressed ? "#6a8510" : "#91B315"
|
|
|
|
|
|
radius: 6
|
|
|
|
|
|
}
|
|
|
|
|
|
onClicked: settingsDialog.accept()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onAccepted: backend.updateSettings({
|
|
|
|
|
|
"gameDir": stGameDir.text.trim(),
|
|
|
|
|
|
"javaPath": stJavaPath.text.trim(),
|
2026-08-27 19:35:11 +03:00
|
|
|
|
"javaRuntime": settingsDialog.javaRuntimeId,
|
2026-08-22 18:19:13 +03:00
|
|
|
|
"minMemoryMb": parseInt(stMinMemory.text) || 0,
|
|
|
|
|
|
"maxMemoryMb": parseInt(stMaxMemory.text) || 0,
|
|
|
|
|
|
"jvmArgs": stJvmArgs.text.trim(),
|
|
|
|
|
|
"windowWidth": parseInt(stWidth.text) || 0,
|
|
|
|
|
|
"windowHeight": parseInt(stHeight.text) || 0,
|
|
|
|
|
|
"fullscreen": stFullscreen.checked
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
2026-08-27 19:35:11 +03:00
|
|
|
|
|
|
|
|
|
|
// Окно выбора Java. Живёт рядом с настройками, а не внутри: оно шире их и
|
|
|
|
|
|
// центрируется по самому окну лаунчера.
|
|
|
|
|
|
JavaPickerDialog {
|
|
|
|
|
|
id: javaPicker
|
|
|
|
|
|
backend: backend
|
|
|
|
|
|
x: (window.width - width) / 2
|
|
|
|
|
|
y: (window.height - height) / 2
|
|
|
|
|
|
onRuntimeChosen: (runtimeId) => settingsDialog.javaRuntimeId = runtimeId
|
|
|
|
|
|
}
|
2025-12-29 02:18:35 +03:00
|
|
|
|
}
|