adding startup progress and style fix
This commit is contained in:
@@ -14,14 +14,37 @@ Window {
|
||||
// ── Backend ────────────────────────────────────────────────────────────
|
||||
LauncherBackend {
|
||||
id: backend
|
||||
|
||||
onLaunched: (profileName, versionName, serverUrl) => {
|
||||
console.log("Запуск: профиль=" + profileName
|
||||
+ " версия=" + versionName
|
||||
+ " сервер=" + serverUrl)
|
||||
window.showToast(qsTr("Запущено: %1 — %2").arg(profileName).arg(versionName), "#4b7a1f")
|
||||
}
|
||||
onLaunchError: (message) => {
|
||||
errorLabel.text = message
|
||||
errorTimer.restart()
|
||||
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")
|
||||
}
|
||||
onGameOutput: (line) => console.log(line)
|
||||
}
|
||||
|
||||
// Единая всплывающая плашка: сообщения о ходе запуска, ошибки, статус игры.
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,28 +57,32 @@ Window {
|
||||
fillMode: Image.Stretch
|
||||
}
|
||||
|
||||
// ── Error toast ────────────────────────────────────────────────────────
|
||||
// ── Status toast ───────────────────────────────────────────────────────
|
||||
Rectangle {
|
||||
visible: errorLabel.text !== ""
|
||||
id: toastBackground
|
||||
visible: toastLabel.text !== ""
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 40
|
||||
width: errorLabel.implicitWidth + 32
|
||||
height: 40
|
||||
width: Math.min(window.width - 80, toastLabel.implicitWidth + 32)
|
||||
height: toastLabel.implicitHeight + 20
|
||||
radius: 8
|
||||
color: "#cc3333"
|
||||
|
||||
Text {
|
||||
id: errorLabel
|
||||
id: toastLabel
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - 32
|
||||
color: "#ffffff"
|
||||
font.pixelSize: 14
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: errorTimer
|
||||
interval: 3000
|
||||
onTriggered: errorLabel.text = ""
|
||||
id: toastTimer
|
||||
interval: 4000
|
||||
onTriggered: toastLabel.text = ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,10 +93,12 @@ Window {
|
||||
height: 170
|
||||
anchors.centerIn: parent
|
||||
hoverEnabled: false
|
||||
enabled: !backend.busy && !backend.gameRunning
|
||||
|
||||
background: Image {
|
||||
id: buttonImage
|
||||
source: "images/Play_Button/Play_Active.svg"
|
||||
opacity: button.enabled ? 1.0 : 0.45
|
||||
}
|
||||
|
||||
onPressed: buttonImage.source = "images/Play_Button/Play_pressed.svg"
|
||||
@@ -476,6 +505,30 @@ Window {
|
||||
onClicked: backend.openMinecraftFolder()
|
||||
}
|
||||
|
||||
// ── 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
|
||||
source: settingsButton.pressed ? "images/Options/Options_Pressed.svg"
|
||||
: settingsButton.hovered ? "images/Options/Options_active.svg"
|
||||
: "images/Options/Options_Idle.svg"
|
||||
fillMode: Image.PreserveAspectFit
|
||||
}
|
||||
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Настройки запуска: Java, память, папка игры")
|
||||
|
||||
onClicked: settingsDialog.load()
|
||||
}
|
||||
|
||||
// ── Add Profile Dialog ─────────────────────────────────────────────────
|
||||
// Bug fix: header/footer must be Item (not Rectangle) with explicit
|
||||
// implicitHeight so Dialog correctly computes its own total height.
|
||||
@@ -551,7 +604,8 @@ Window {
|
||||
id: pfPassword
|
||||
x: 20
|
||||
width: parent.width - 40
|
||||
placeholderText: "Пароль"
|
||||
enabled: pfAuth.currentIndex === 1
|
||||
placeholderText: pfAuth.currentIndex === 1 ? "Пароль Ely.by" : "Не нужен в офлайне"
|
||||
echoMode: TextInput.Password
|
||||
color: "#ffffff"
|
||||
placeholderTextColor: "#666666"
|
||||
@@ -562,6 +616,13 @@ Window {
|
||||
border.width: 1
|
||||
}
|
||||
}
|
||||
|
||||
DarkCombo {
|
||||
id: pfAuth
|
||||
x: 20
|
||||
width: parent.width - 40
|
||||
model: ["Офлайн (без пароля)", "Ely.by"]
|
||||
}
|
||||
}
|
||||
|
||||
footer: Item {
|
||||
@@ -613,13 +674,14 @@ Window {
|
||||
onAccepted: {
|
||||
const name = pfName.text.trim()
|
||||
if (name !== "") {
|
||||
backend.addProfile(name, pfLogin.text.trim(), pfPassword.text)
|
||||
backend.addProfile(name, pfLogin.text.trim(), pfPassword.text,
|
||||
pfAuth.currentIndex === 1 ? "elyby" : "offline")
|
||||
profileBox.currentIndex = backend.profileNames.length - 1
|
||||
}
|
||||
pfName.text = ""; pfLogin.text = ""; pfPassword.text = ""
|
||||
pfName.text = ""; pfLogin.text = ""; pfPassword.text = ""; pfAuth.currentIndex = 0
|
||||
}
|
||||
onRejected: {
|
||||
pfName.text = ""; pfLogin.text = ""; pfPassword.text = ""
|
||||
pfName.text = ""; pfLogin.text = ""; pfPassword.text = ""; pfAuth.currentIndex = 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -680,7 +742,7 @@ Window {
|
||||
id: verServer
|
||||
x: 20
|
||||
width: parent.width - 40
|
||||
placeholderText: "URL сервера загрузки"
|
||||
placeholderText: "Адрес сервера, host:port (необязательно)"
|
||||
color: "#ffffff"
|
||||
placeholderTextColor: "#666666"
|
||||
background: Rectangle {
|
||||
@@ -690,6 +752,25 @@ Window {
|
||||
border.width: 1
|
||||
}
|
||||
}
|
||||
|
||||
// Папка внутри .minecraft/versions, которую и будем запускать.
|
||||
DarkCombo {
|
||||
id: verInstalled
|
||||
x: 20
|
||||
width: parent.width - 40
|
||||
model: backend.installedVersions
|
||||
}
|
||||
|
||||
Text {
|
||||
x: 20
|
||||
width: parent.width - 40
|
||||
text: backend.installedVersions.length === 0
|
||||
? qsTr("В .minecraft/versions нет установленных версий")
|
||||
: qsTr("Установленная версия из .minecraft")
|
||||
color: backend.installedVersions.length === 0 ? "#cc6666" : "#888888"
|
||||
font.pixelSize: 11
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
}
|
||||
|
||||
footer: Item {
|
||||
@@ -738,10 +819,12 @@ Window {
|
||||
}
|
||||
}
|
||||
|
||||
onAboutToShow: verInstalled.currentIndex = 0
|
||||
|
||||
onAccepted: {
|
||||
const name = verName.text.trim()
|
||||
if (name !== "") {
|
||||
backend.addVersion(name, verServer.text.trim())
|
||||
backend.addVersion(name, verServer.text.trim(), verInstalled.currentText)
|
||||
versionBox.currentIndex = backend.versionNames.length - 1
|
||||
}
|
||||
verName.text = ""; verServer.text = ""
|
||||
@@ -767,9 +850,10 @@ Window {
|
||||
function openFor(index) {
|
||||
const data = backend.profileAt(index)
|
||||
editIndex = index
|
||||
epName.text = data.name || ""
|
||||
epLogin.text = data.login || ""
|
||||
epPassword.text = data.password || ""
|
||||
epName.text = data.name || ""
|
||||
epLogin.text = data.login || ""
|
||||
epPassword.text = data.password || ""
|
||||
epAuth.currentIndex = data.authType === "elyby" ? 1 : 0
|
||||
profileBox.popup.close()
|
||||
open()
|
||||
}
|
||||
@@ -837,7 +921,8 @@ Window {
|
||||
id: epPassword
|
||||
x: 20
|
||||
width: parent.width - 40
|
||||
placeholderText: "Пароль"
|
||||
enabled: epAuth.currentIndex === 1
|
||||
placeholderText: epAuth.currentIndex === 1 ? "Пароль Ely.by" : "Не нужен в офлайне"
|
||||
echoMode: TextInput.Password
|
||||
color: "#ffffff"
|
||||
placeholderTextColor: "#666666"
|
||||
@@ -848,6 +933,13 @@ Window {
|
||||
border.width: 1
|
||||
}
|
||||
}
|
||||
|
||||
DarkCombo {
|
||||
id: epAuth
|
||||
x: 20
|
||||
width: parent.width - 40
|
||||
model: ["Офлайн (без пароля)", "Ely.by"]
|
||||
}
|
||||
}
|
||||
|
||||
footer: Item {
|
||||
@@ -899,7 +991,8 @@ Window {
|
||||
onAccepted: {
|
||||
const name = epName.text.trim()
|
||||
if (editIndex >= 0 && name !== "")
|
||||
backend.updateProfile(editIndex, name, epLogin.text.trim(), epPassword.text)
|
||||
backend.updateProfile(editIndex, name, epLogin.text.trim(), epPassword.text,
|
||||
epAuth.currentIndex === 1 ? "elyby" : "offline")
|
||||
editIndex = -1
|
||||
epName.text = ""; epLogin.text = ""; epPassword.text = ""
|
||||
}
|
||||
@@ -927,6 +1020,13 @@ Window {
|
||||
editIndex = index
|
||||
evName.text = data.name || ""
|
||||
evServer.text = data.serverUrl || ""
|
||||
evInstalled.currentIndex = backend.installedVersions.indexOf(data.versionId || "")
|
||||
|
||||
const problems = backend.checkInstallation(index)
|
||||
evStatus.text = problems.length === 0
|
||||
? qsTr("Всё на месте, версию можно запускать")
|
||||
: qsTr("Не хватает файлов (%1): %2").arg(problems.length).arg(problems[0])
|
||||
|
||||
versionBox.popup.close()
|
||||
open()
|
||||
}
|
||||
@@ -979,7 +1079,7 @@ Window {
|
||||
id: evServer
|
||||
x: 20
|
||||
width: parent.width - 40
|
||||
placeholderText: "URL сервера загрузки"
|
||||
placeholderText: "Адрес сервера, host:port (необязательно)"
|
||||
color: "#ffffff"
|
||||
placeholderTextColor: "#666666"
|
||||
background: Rectangle {
|
||||
@@ -989,6 +1089,23 @@ Window {
|
||||
border.width: 1
|
||||
}
|
||||
}
|
||||
|
||||
DarkCombo {
|
||||
id: evInstalled
|
||||
x: 20
|
||||
width: parent.width - 40
|
||||
model: backend.installedVersions
|
||||
}
|
||||
|
||||
// Показываем, чего не хватает в .minecraft именно для этой версии.
|
||||
Text {
|
||||
id: evStatus
|
||||
x: 20
|
||||
width: parent.width - 40
|
||||
color: text.indexOf("Всё на месте") === 0 ? "#8fbf5a" : "#cc6666"
|
||||
font.pixelSize: 11
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
}
|
||||
|
||||
footer: Item {
|
||||
@@ -1040,7 +1157,7 @@ Window {
|
||||
onAccepted: {
|
||||
const name = evName.text.trim()
|
||||
if (editIndex >= 0 && name !== "")
|
||||
backend.updateVersion(editIndex, name, evServer.text.trim())
|
||||
backend.updateVersion(editIndex, name, evServer.text.trim(), evInstalled.currentText)
|
||||
editIndex = -1
|
||||
evName.text = ""; evServer.text = ""
|
||||
}
|
||||
@@ -1049,4 +1166,444 @@ Window {
|
||||
evName.text = ""; evServer.text = ""
|
||||
}
|
||||
}
|
||||
|
||||
// ── Two-factor Dialog ──────────────────────────────────────────────────
|
||||
// Ely.by отклоняет пароль с пометкой two factor — код добираем здесь и
|
||||
// продолжаем прерванный запуск.
|
||||
Dialog {
|
||||
id: twoFactorDialog
|
||||
modal: true
|
||||
width: 320
|
||||
x: (window.width - width) / 2
|
||||
y: (window.height - height) / 2
|
||||
padding: 0
|
||||
closePolicy: Popup.NoAutoClose
|
||||
|
||||
property string profileName: ""
|
||||
|
||||
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: 15
|
||||
font.bold: true
|
||||
}
|
||||
Rectangle {
|
||||
anchors.bottom: parent.bottom
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: "#333333"
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: Column {
|
||||
spacing: 12
|
||||
topPadding: 20
|
||||
bottomPadding: 20
|
||||
|
||||
Text {
|
||||
x: 20
|
||||
width: parent.width - 40
|
||||
text: qsTr("Введите код из приложения-аутентификатора для «%1»")
|
||||
.arg(twoFactorDialog.profileName)
|
||||
color: "#aaaaaa"
|
||||
font.pixelSize: 12
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: twoFactorCode
|
||||
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
|
||||
|
||||
function load() {
|
||||
const s = backend.settings()
|
||||
stGameDir.text = s.gameDir || ""
|
||||
stJavaPath.text = s.javaPath || ""
|
||||
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: "автоопределение"
|
||||
}
|
||||
|
||||
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(),
|
||||
"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
|
||||
})
|
||||
}
|
||||
|
||||
// ── Переиспользуемые элементы в общем стиле окна ───────────────────────
|
||||
component DarkCombo: ComboBox {
|
||||
id: darkCombo
|
||||
|
||||
// Та же стрелка, что у комбобоксов профиля и версии на главном окне,
|
||||
// вместо двойного шеврона Basic-стиля.
|
||||
indicator: Image {
|
||||
width: 10; height: 10
|
||||
x: darkCombo.width - width - 12
|
||||
y: (darkCombo.height - height) / 2
|
||||
source: darkCombo.down ? "images/Profile_Box/Asset_23.svg"
|
||||
: "images/Profile_Box/Asset_24.svg"
|
||||
rotation: 180
|
||||
sourceSize.width: 10; sourceSize.height: 10
|
||||
fillMode: Image.PreserveAspectFit
|
||||
}
|
||||
|
||||
contentItem: Text {
|
||||
leftPadding: 10
|
||||
rightPadding: darkCombo.indicator.width + 22
|
||||
text: darkCombo.displayText
|
||||
color: "#ffffff"
|
||||
font: darkCombo.font
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: "#2a2a2a"
|
||||
radius: 6
|
||||
border.color: darkCombo.activeFocus ? "#91B315" : "#444444"
|
||||
border.width: 1
|
||||
}
|
||||
|
||||
delegate: ItemDelegate {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: darkCombo.width
|
||||
highlighted: darkCombo.highlightedIndex === index
|
||||
contentItem: Text {
|
||||
text: modelData
|
||||
color: "#ffffff"
|
||||
elide: Text.ElideRight
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
background: Rectangle { color: highlighted ? "#3a3a3a" : "#2a2a2a" }
|
||||
}
|
||||
|
||||
popup: Popup {
|
||||
y: darkCombo.height
|
||||
width: darkCombo.width
|
||||
implicitHeight: Math.min(contentItem.implicitHeight, 220)
|
||||
padding: 1
|
||||
|
||||
contentItem: ListView {
|
||||
clip: true
|
||||
implicitHeight: contentHeight
|
||||
model: darkCombo.delegateModel
|
||||
currentIndex: darkCombo.highlightedIndex
|
||||
ScrollIndicator.vertical: ScrollIndicator {}
|
||||
}
|
||||
background: Rectangle {
|
||||
color: "#2a2a2a"
|
||||
radius: 6
|
||||
border.color: "#444444"
|
||||
border.width: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Подпись + поле ввода одной колонкой — используется в настройках.
|
||||
component LabelledField: Column {
|
||||
id: labelled
|
||||
|
||||
property alias text: field.text
|
||||
property alias validator: field.validator
|
||||
property string label: ""
|
||||
property string placeholder: ""
|
||||
|
||||
spacing: 3
|
||||
|
||||
Text {
|
||||
text: labelled.label
|
||||
color: "#aaaaaa"
|
||||
font.pixelSize: 11
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: field
|
||||
width: labelled.width
|
||||
height: 32
|
||||
placeholderText: labelled.placeholder
|
||||
color: "#ffffff"
|
||||
placeholderTextColor: "#666666"
|
||||
background: Rectangle {
|
||||
color: "#2a2a2a"
|
||||
radius: 6
|
||||
border.color: field.activeFocus ? "#91B315" : "#444444"
|
||||
border.width: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user