631 lines
22 KiB
QML
631 lines
22 KiB
QML
import QtQuick
|
|
import QtQuick.Layouts 2.15
|
|
import QtQuick.Controls 2.15
|
|
import Minecraft_launcher
|
|
|
|
Window {
|
|
id: window
|
|
width: 1280
|
|
height: 720
|
|
visible: true
|
|
flags: Qt.Window
|
|
title: qsTr("Minecraft Launcher")
|
|
|
|
// ── Backend ────────────────────────────────────────────────────────────
|
|
LauncherBackend {
|
|
id: backend
|
|
onLaunched: (profileName, versionName, serverUrl) => {
|
|
console.log("Запуск: профиль=" + profileName
|
|
+ " версия=" + versionName
|
|
+ " сервер=" + serverUrl)
|
|
}
|
|
onLaunchError: (message) => {
|
|
errorLabel.text = message
|
|
errorTimer.restart()
|
|
}
|
|
}
|
|
|
|
// ── Background ─────────────────────────────────────────────────────────
|
|
Image {
|
|
id: image
|
|
opacity: 0.296
|
|
anchors.fill: parent
|
|
source: "images/GovuztTW8AAHqBf.jpeg"
|
|
fillMode: Image.Stretch
|
|
}
|
|
|
|
// ── Error toast ────────────────────────────────────────────────────────
|
|
Rectangle {
|
|
visible: errorLabel.text !== ""
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
anchors.bottom: parent.bottom
|
|
anchors.bottomMargin: 40
|
|
width: errorLabel.implicitWidth + 32
|
|
height: 40
|
|
radius: 8
|
|
color: "#cc3333"
|
|
|
|
Text {
|
|
id: errorLabel
|
|
anchors.centerIn: parent
|
|
color: "#ffffff"
|
|
font.pixelSize: 14
|
|
}
|
|
|
|
Timer {
|
|
id: errorTimer
|
|
interval: 3000
|
|
onTriggered: errorLabel.text = ""
|
|
}
|
|
}
|
|
|
|
// ── Play button ────────────────────────────────────────────────────────
|
|
Button {
|
|
id: button
|
|
width: 335
|
|
height: 170
|
|
anchors.centerIn: parent
|
|
hoverEnabled: false
|
|
|
|
background: Image {
|
|
id: buttonImage
|
|
source: "images/Play_Button/Play_Active.svg"
|
|
}
|
|
|
|
onPressed: buttonImage.source = "images/Play_Button/Play_pressed.svg"
|
|
onReleased: buttonImage.source = "images/Play_Button/Play_Active.svg"
|
|
onClicked: backend.launchGame(profileBox.currentIndex, versionBox.currentIndex)
|
|
}
|
|
|
|
// ── Profile ComboBox ───────────────────────────────────────────────────
|
|
ComboBox {
|
|
id: profileBox
|
|
y: 451
|
|
width: 146
|
|
height: 42
|
|
anchors.left: button.right
|
|
anchors.bottom: button.top
|
|
anchors.leftMargin: -335
|
|
anchors.bottomMargin: -218
|
|
editable: false
|
|
leftPadding: -30
|
|
|
|
model: backend.profileNames
|
|
|
|
indicator: Image {
|
|
width: 10; height: 10
|
|
visible: true
|
|
anchors.left: parent.left
|
|
anchors.leftMargin: 10
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
source: profileBox.down ? "images/Profile_Box/Asset_23.svg"
|
|
: "images/Profile_Box/Asset_24.svg"
|
|
rotation: 180
|
|
sourceSize.width: 10; sourceSize.height: 10
|
|
fillMode: Image.PreserveAspectFit
|
|
autoTransform: true
|
|
}
|
|
|
|
contentItem: Text {
|
|
x: 26
|
|
width: 114
|
|
rightPadding: profileBox.indicator.width + profileBox.spacing
|
|
text: profileBox.displayText
|
|
font: profileBox.font
|
|
color: "#ffffff"
|
|
verticalAlignment: Text.AlignVCenter
|
|
horizontalAlignment: Text.AlignHCenter
|
|
elide: Text.ElideRight
|
|
}
|
|
|
|
background: Rectangle {
|
|
implicitWidth: 120; implicitHeight: 40
|
|
color: profileBox.down ? "#91B315" : "#232323"
|
|
radius: 6
|
|
border.color: profileBox.pressed ? "#ffffff" : "#232323"
|
|
border.width: profileBox.visualFocus ? 2 : 1
|
|
}
|
|
|
|
popup: Popup {
|
|
y: profileBox.height - 1
|
|
width: profileBox.width
|
|
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: "+ Добавить профиль"
|
|
color: "#91B315"
|
|
font.pixelSize: 12
|
|
}
|
|
|
|
MouseArea {
|
|
id: profileAddArea
|
|
anchors.fill: parent
|
|
onClicked: {
|
|
profileBox.popup.close()
|
|
addProfileDialog.open()
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Добавленные профили — всегда ниже кнопки ───────────
|
|
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
|
|
}
|
|
background: Rectangle {
|
|
color: (profileBox.currentIndex === profileItem.index || profileItem.hovered)
|
|
? "#91B315" : "#232323"
|
|
radius: 5
|
|
}
|
|
onClicked: {
|
|
profileBox.currentIndex = profileItem.index
|
|
profileBox.popup.close()
|
|
}
|
|
}
|
|
|
|
ScrollIndicator.vertical: ScrollIndicator {
|
|
parent: profileItemList.parent
|
|
anchors.top: profileItemList.top
|
|
anchors.left: profileItemList.right
|
|
anchors.bottom: profileItemList.bottom
|
|
}
|
|
}
|
|
}
|
|
|
|
background: Rectangle {
|
|
color: "#232323"
|
|
radius: 6
|
|
border.color: "#232323"
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Version ComboBox ───────────────────────────────────────────────────
|
|
ComboBox {
|
|
id: versionBox
|
|
x: 624
|
|
y: 451
|
|
width: 183
|
|
height: 42
|
|
anchors.right: button.right
|
|
anchors.bottom: button.top
|
|
anchors.bottomMargin: -218
|
|
editable: false
|
|
leftPadding: -30
|
|
|
|
model: backend.versionNames
|
|
|
|
indicator: Image {
|
|
width: 10; height: 10
|
|
visible: true
|
|
anchors.left: parent.left
|
|
anchors.leftMargin: 10
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
source: versionBox.down ? "images/Profile_Box/Asset_23.svg"
|
|
: "images/Profile_Box/Asset_24.svg"
|
|
rotation: 180
|
|
sourceSize.width: 10; sourceSize.height: 10
|
|
fillMode: Image.PreserveAspectFit
|
|
autoTransform: true
|
|
}
|
|
|
|
contentItem: Text {
|
|
x: 26
|
|
width: 150
|
|
leftPadding: 56
|
|
rightPadding: versionBox.indicator.width + versionBox.spacing
|
|
text: versionBox.displayText
|
|
font: versionBox.font
|
|
color: "#ffffff"
|
|
verticalAlignment: Text.AlignVCenter
|
|
horizontalAlignment: Text.AlignLeft
|
|
elide: Text.ElideRight
|
|
}
|
|
|
|
background: Rectangle {
|
|
implicitWidth: 120; implicitHeight: 40
|
|
color: versionBox.down ? "#91B315" : "#232323"
|
|
radius: 6
|
|
border.color: versionBox.pressed ? "#ffffff" : "#232323"
|
|
border.width: versionBox.visualFocus ? 2 : 1
|
|
}
|
|
|
|
popup: Popup {
|
|
y: versionBox.height - 1
|
|
width: versionBox.width
|
|
padding: 0
|
|
height: 32 + Math.min(versionItemList.contentHeight, 200)
|
|
|
|
contentItem: Item {
|
|
anchors.fill: parent
|
|
|
|
// ── "+" button — всегда сверху ──────────────────────────
|
|
Rectangle {
|
|
id: versionAddBtnBg
|
|
anchors.top: parent.top
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
height: 32
|
|
color: versionAddArea.containsPress ? "#2d2d2d" : "transparent"
|
|
|
|
Text {
|
|
anchors.centerIn: parent
|
|
text: "+ Добавить версию"
|
|
color: "#91B315"
|
|
font.pixelSize: 12
|
|
}
|
|
|
|
MouseArea {
|
|
id: versionAddArea
|
|
anchors.fill: parent
|
|
onClicked: {
|
|
versionBox.popup.close()
|
|
addVersionDialog.open()
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Добавленные версии — всегда ниже кнопки ────────────
|
|
ListView {
|
|
id: versionItemList
|
|
anchors.top: versionAddBtnBg.bottom
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
height: Math.min(contentHeight, 200)
|
|
clip: true
|
|
model: backend.versionNames
|
|
|
|
delegate: ItemDelegate {
|
|
id: versionItem
|
|
required property string modelData
|
|
required property int index
|
|
width: versionItemList.width
|
|
height: 40
|
|
contentItem: Text {
|
|
text: versionItem.modelData
|
|
color: "#ffffff"
|
|
font: versionBox.font
|
|
elide: Text.ElideRight
|
|
horizontalAlignment: Text.AlignLeft
|
|
verticalAlignment: Text.AlignVCenter
|
|
leftPadding: 8
|
|
}
|
|
background: Rectangle {
|
|
color: (versionBox.currentIndex === versionItem.index || versionItem.hovered)
|
|
? "#91B315" : "#232323"
|
|
radius: 5
|
|
}
|
|
onClicked: {
|
|
versionBox.currentIndex = versionItem.index
|
|
versionBox.popup.close()
|
|
}
|
|
}
|
|
|
|
ScrollIndicator.vertical: ScrollIndicator {
|
|
parent: versionItemList.parent
|
|
anchors.top: versionItemList.top
|
|
anchors.left: versionItemList.right
|
|
anchors.bottom: versionItemList.bottom
|
|
}
|
|
}
|
|
}
|
|
|
|
background: Rectangle {
|
|
color: "#232323"
|
|
radius: 6
|
|
border.color: "#232323"
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── 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
|
|
}
|
|
|
|
header: Item {
|
|
implicitHeight: 52 // tells Dialog how tall the header is
|
|
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: 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
|
|
placeholderText: "Логин"
|
|
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
|
|
placeholderText: "Пароль"
|
|
echoMode: TextInput.Password
|
|
color: "#ffffff"
|
|
placeholderTextColor: "#666666"
|
|
background: Rectangle {
|
|
color: "#2a2a2a"
|
|
radius: 6
|
|
border.color: pfPassword.activeFocus ? "#91B315" : "#444444"
|
|
border.width: 1
|
|
}
|
|
}
|
|
}
|
|
|
|
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()
|
|
}
|
|
}
|
|
}
|
|
|
|
onAccepted: {
|
|
const name = pfName.text.trim()
|
|
if (name !== "") {
|
|
backend.addProfile(name, pfLogin.text.trim(), pfPassword.text)
|
|
profileBox.currentIndex = backend.profileNames.length - 1
|
|
}
|
|
pfName.text = ""; pfLogin.text = ""; pfPassword.text = ""
|
|
}
|
|
onRejected: {
|
|
pfName.text = ""; pfLogin.text = ""; pfPassword.text = ""
|
|
}
|
|
}
|
|
|
|
// ── Add Version Dialog ─────────────────────────────────────────────────
|
|
Dialog {
|
|
id: addVersionDialog
|
|
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
|
|
}
|
|
|
|
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: 12
|
|
topPadding: 20
|
|
bottomPadding: 20
|
|
|
|
TextField {
|
|
id: verName
|
|
x: 20
|
|
width: parent.width - 40
|
|
placeholderText: "Название версии"
|
|
color: "#ffffff"
|
|
placeholderTextColor: "#666666"
|
|
background: Rectangle {
|
|
color: "#2a2a2a"
|
|
radius: 6
|
|
border.color: verName.activeFocus ? "#91B315" : "#444444"
|
|
border.width: 1
|
|
}
|
|
}
|
|
|
|
TextField {
|
|
id: verServer
|
|
x: 20
|
|
width: parent.width - 40
|
|
placeholderText: "URL сервера загрузки"
|
|
color: "#ffffff"
|
|
placeholderTextColor: "#666666"
|
|
background: Rectangle {
|
|
color: "#2a2a2a"
|
|
radius: 6
|
|
border.color: verServer.activeFocus ? "#91B315" : "#444444"
|
|
border.width: 1
|
|
}
|
|
}
|
|
}
|
|
|
|
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: addVersionDialog.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: addVersionDialog.accept()
|
|
}
|
|
}
|
|
}
|
|
|
|
onAccepted: {
|
|
const name = verName.text.trim()
|
|
if (name !== "") {
|
|
backend.addVersion(name, verServer.text.trim())
|
|
versionBox.currentIndex = backend.versionNames.length - 1
|
|
}
|
|
verName.text = ""; verServer.text = ""
|
|
}
|
|
onRejected: {
|
|
verName.text = ""; verServer.text = ""
|
|
}
|
|
}
|
|
}
|