launcher project v 0.1
64
README.md
Normal file
@ -0,0 +1,64 @@
|
||||

|
||||
|
||||
# WoW Launcher
|
||||
|
||||
Современный лаунчер для World of Warcraft 3.3.5a, написанный на Python и QML.
|
||||
|
||||
## Возможности
|
||||
|
||||
- Современный интерфейс в стиле World of Warcraft
|
||||
- Загрузка и проверка файлов клиента
|
||||
- Поддержка Windows и Linux (через Wine/Lutris/Proton/PortProton)
|
||||
- Автоматическое обновление
|
||||
- Слайд-шоу с новостями
|
||||
- Мониторинг состояния серверов
|
||||
- Система уведомлений
|
||||
- Настраиваемые параметры
|
||||
- Загрузка файлов сегментами
|
||||
- Сворачивание в трей
|
||||
- Возможность запускать игру через Wine, Lutris, Proton, PortProton
|
||||
|
||||
## Требования
|
||||
|
||||
- Python 3.8+
|
||||
- PyQt5
|
||||
- Requests
|
||||
|
||||
## Установка
|
||||
|
||||
1. Клонируйте репозиторий:
|
||||
```
|
||||
git clone https://github.com/ваш-username/wow-launcher.git
|
||||
cd wow-launcher
|
||||
```
|
||||
2. Установите зависимости:
|
||||
```
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
3. Запустите лаунчер:
|
||||
```
|
||||
python backup3.py
|
||||
```
|
||||
|
||||
## Настройка
|
||||
|
||||
1. При первом запуске выберите папку для установки игры
|
||||
2. Настройте параметры в меню настроек
|
||||
3. Для Linux пользователей: выберите предпочитаемый эмулятор Windows
|
||||
|
||||
|
||||
## Разработка
|
||||
|
||||
Проект использует:
|
||||
- Python для бэкенда
|
||||
- QML для интерфейса
|
||||
- Qt Quick Controls 2 для компонентов
|
||||
- Material Design для стилизации
|
||||
|
||||
## Лицензия
|
||||
|
||||
MIT License
|
||||
|
||||
## Авторы
|
||||
|
||||
JumpyLion87
|
||||
1
README.txt
Normal file
@ -0,0 +1 @@
|
||||
|
||||
180
clien.json
Normal file
@ -0,0 +1,180 @@
|
||||
{
|
||||
"files": {
|
||||
"Data/ruRU/Interface/Cinematics/Logo_1024.avi": {
|
||||
"size": 6518380,
|
||||
"hash": "8ca8b2ef476be8d69a678ac34f75498f3c5cc02b5f618d467b9c78e6edae815e"
|
||||
},
|
||||
"Data/ruRU/Interface/Cinematics/Logo_800.avi": {
|
||||
"size": 4967302,
|
||||
"hash": "9f3c345a99073f7f06adb1b9bf37c2ffc7e37773d07a4cc72a0cd163db07b765"
|
||||
},
|
||||
"Data/ruRU/Interface/Cinematics/WOW_FotLK_1024.avi": {
|
||||
"size": 66007136,
|
||||
"hash": "1afe738ad4ad1203f357eb15b1a15926e54ffbd3ad9b7b5ca9fac73dad058292"
|
||||
},
|
||||
"Data/ruRU/Interface/Cinematics/WOW_FotLK_800.avi": {
|
||||
"size": 57244820,
|
||||
"hash": "52cb90f10863c909d613f84c99f0d2da5139793c2998f2b2905c49395b489821"
|
||||
},
|
||||
"Data/ruRU/Interface/Cinematics/WOW_Intro_1024.avi": {
|
||||
"size": 48319392,
|
||||
"hash": "697fad32b005def2bb6908fb3a77f43be134d661fd03ffa29d2c6739ec5cd7b7"
|
||||
},
|
||||
"Data/ruRU/Interface/Cinematics/WOW_Intro_800.avi": {
|
||||
"size": 41883066,
|
||||
"hash": "0d62fcd314daf93add75510f644e04a4192effcd323db0c791df01609bf3e77b"
|
||||
},
|
||||
"Data/ruRU/Interface/Cinematics/WOW_Intro_BC_1024.avi": {
|
||||
"size": 47575820,
|
||||
"hash": "0277fba0da2a8d0ea61121996390ab3d718729c7802c31b9d0f9447233b0dda0"
|
||||
},
|
||||
"Data/ruRU/Interface/Cinematics/WOW_Intro_BC_800.avi": {
|
||||
"size": 41261394,
|
||||
"hash": "ed5511a783734bf214cc084fde253b530b868336f774b61ec090c8891627b880"
|
||||
},
|
||||
"Data/ruRU/Interface/Cinematics/WOW_Intro_LK_1024.avi": {
|
||||
"size": 56177056,
|
||||
"hash": "be3dc55b635da30ffddc84898a241b1551f1e32b1a6a0c97768afdc4a078a51d"
|
||||
},
|
||||
"Data/ruRU/Interface/Cinematics/WOW_Intro_LK_800.avi": {
|
||||
"size": 48820376,
|
||||
"hash": "fcd5e4576be7e7d2e97a436f13e37734e72adfa3457d1fbbcc9e6173aea7f7e5"
|
||||
},
|
||||
"Data/ruRU/Interface/Cinematics/WOW_Wrathgate_1024.avi": {
|
||||
"size": 75685834,
|
||||
"hash": "4b40199be93942548f77fb3cfeaf001738bccee0211940c8ed4ff6f0fb068897"
|
||||
},
|
||||
"Data/ruRU/Interface/Cinematics/WOW_Wrathgate_800.avi": {
|
||||
"size": 65683896,
|
||||
"hash": "acececbffff5e1b2e23f16259fe662df3a02823d4890d3ac3a4d96b3a401c729"
|
||||
},
|
||||
"Data/ruRU/backup-ruRU.MPQ": {
|
||||
"size": 158243976,
|
||||
"hash": "b2acbf9da5adb20926b42194fcc026bdfcf0a5074d5e058fa24c6da57593a8f0"
|
||||
},
|
||||
"Data/ruRU/base-ruRU.MPQ": {
|
||||
"size": 22975058,
|
||||
"hash": "f361e6706aab57cce50e8a7ba8a08f312ba3c328a5219cd6f72db1894d4c1148"
|
||||
},
|
||||
"Data/ruRU/eula.html": {
|
||||
"size": 36274,
|
||||
"hash": "e784b78eb85d4231dec97870e6cfd585d0b2563b1af350ba34ab75545b478b3f"
|
||||
},
|
||||
"Data/ruRU/expansion-locale-ruRU.MPQ": {
|
||||
"size": 16919739,
|
||||
"hash": "4a565f808c1c4b413dcc1bbfa9980ca700500f220506704ecbb4734883d6ee8d"
|
||||
},
|
||||
"Data/ruRU/expansion-speech-ruRU.MPQ": {
|
||||
"size": 269445963,
|
||||
"hash": "69a5057a064fc22efa008acc03faf78fa1abc8b7f2320367e53569b7b2dea3c9"
|
||||
},
|
||||
"Data/ruRU/lichking-locale-ruRU.MPQ": {
|
||||
"size": 12354378,
|
||||
"hash": "89b1eba015927e321683a3867be1b8a76e593c438527eb42f4934d03484f1b04"
|
||||
},
|
||||
"Data/ruRU/lichking-speech-ruRU.MPQ": {
|
||||
"size": 6772364,
|
||||
"hash": "99afb35d5f092a19b760815bb0a21b8eb4b74d7e9361498dc13d4a65cc951b59"
|
||||
},
|
||||
"Data/ruRU/locale-ruRU.MPQ": {
|
||||
"size": 190129109,
|
||||
"hash": "dd587f0a673924d3320c30812d90476fc72a240e30056d0656c8c6c5e32dde1f"
|
||||
},
|
||||
"Data/ruRU/patch-ruRU-2.MPQ": {
|
||||
"size": 270846455,
|
||||
"hash": "25365c0319d186e7c0a09b8f0b63c1de765f4387bd3d532709fe163170c439e3"
|
||||
},
|
||||
"Data/ruRU/patch-ruRU-3.MPQ": {
|
||||
"size": 109883630,
|
||||
"hash": "792f87603eb77000a23c56f85c6250796e4f4032d0cad671a70c0fa300a2a0c3"
|
||||
},
|
||||
"Data/ruRU/patch-ruRU.MPQ": {
|
||||
"size": 613796676,
|
||||
"hash": "af99e71afc6eec6f6087a8a43948210a30c15283e71dc0c49ea0a6543bee69ff"
|
||||
},
|
||||
"Data/ruRU/patch-ruRU-8.MPQ": {
|
||||
"size": 115949,
|
||||
"hash": "3dcc6776a3a1995b833e216c1517522ed5787ec9389c4ebbe206b6d4f7df1ce2"
|
||||
},
|
||||
"Data/ruRU/speech-ruRU.MPQ": {
|
||||
"size": 445791052,
|
||||
"hash": "a76376c43b90633648269fe94e2b89454b00bc096f6acc11302492fd2102727e"
|
||||
},
|
||||
"Data/ruRU/termination.html": {
|
||||
"size": 4882,
|
||||
"hash": "2e2dd7c567c96bb0b3d2aa59ca2822cbc4dedfa110eaddbbbe9294076e987c04"
|
||||
},
|
||||
"Data/ruRU/tos.html": {
|
||||
"size": 96614,
|
||||
"hash": "a7bdc9a6faa1098ed3a844a1a79ed85d3dd0e2a34dcd10cd73e2da5305dc7f45"
|
||||
},
|
||||
"Data/common-2.MPQ": {
|
||||
"size": 1756781838,
|
||||
"hash": "dc0f8fe0607754de306eba74e9972ab6d91eb467076f34b26407ace3446f3f57"
|
||||
},
|
||||
"Data/common.MPQ": {
|
||||
"size": 2856083241,
|
||||
"hash": "63bac342322bbc1f88b3344b1a73e67a84ed527fbbe9c728e2528e8660346399"
|
||||
},
|
||||
"Data/expansion.MPQ": {
|
||||
"size": 1899508519,
|
||||
"hash": "edf04e46a28fee2727d29a61c2f866e4b3dce41468ebd41f34c115fae25a5abf"
|
||||
},
|
||||
"Data/lichking.MPQ": {
|
||||
"size": 2553955175,
|
||||
"hash": "7be69de4ae1328026cf1de021fde70666367c255573e283302089658cdd56cfb"
|
||||
},
|
||||
"Data/patch-2.MPQ": {
|
||||
"size": 1401729059,
|
||||
"hash": "c8b78bb75bcf5773e9ae99e11bdfcabc2a37aed3da947367d8af808d4d3c23c6"
|
||||
},
|
||||
"Data/patch-3.MPQ": {
|
||||
"size": 605089137,
|
||||
"hash": "56dbbfc8f9ce7182ca88538d75284e738020138a7ca4217d72d8168865510dd3"
|
||||
},
|
||||
"Data/patch.MPQ": {
|
||||
"size": 4004713057,
|
||||
"hash": "92b4a94a6c7a23c0b9fd88c47823e41792879a7ee1f70c2349a255153ca25d54"
|
||||
},
|
||||
"Battle.net.dll": {
|
||||
"size": 15588224,
|
||||
"hash": "88116951b04723cbb1073a1a0ff72dfdc40367f6649dffd08c602caa3ef1141f"
|
||||
},
|
||||
"DivxDecoder.dll": {
|
||||
"size": 413696,
|
||||
"hash": "ed34d37b575c91a56704218eb9f6abbefda8b7de0e2ed44c96191abd0f9915a5"
|
||||
},
|
||||
"Microsoft.VC80.CRT.manifest": {
|
||||
"size": 1870,
|
||||
"hash": "a50941352cb9d8f7ba6fbf7db5c8af95fb5ab76fc5d60cfd0984e558678908cc"
|
||||
},
|
||||
"Scan.dll": {
|
||||
"size": 51972,
|
||||
"hash": "22bafdab4e842aa675f089672a366a164c735bd63026eea8704778085200bf3f"
|
||||
},
|
||||
"Wow.exe": {
|
||||
"size": 7704216,
|
||||
"hash": "28287cd94e6ab2d6e68865f49314b456d2750dcb5d0b8ecca12eeddf72b7bf80"
|
||||
},
|
||||
"WowError.exe": {
|
||||
"size": 350360,
|
||||
"hash": "964eb9ed647c4614f3a3ecf977955750223ecbc8ff0a3bb4078e4dff779244f8"
|
||||
},
|
||||
"dbghelp.dll": {
|
||||
"size": 1039728,
|
||||
"hash": "3f9ad6986eb1f3a09eb0b5cad888ba656f9148b3339399076f2541b3bc59213c"
|
||||
},
|
||||
"ijl15.dll": {
|
||||
"size": 372736,
|
||||
"hash": "334aa12f7dee453d1c6cb1b661a3bb3494d3e4cc9c2ff3f9002064c78404e43a"
|
||||
},
|
||||
"msvcr80.dll": {
|
||||
"size": 632656,
|
||||
"hash": "9382aaed2db19cd75a70e38964f06c63f19f63c9dfb5a33b0c2d445bb41b6e46"
|
||||
},
|
||||
"unicows.dll": {
|
||||
"size": 245408,
|
||||
"hash": "22f23cc65698741184ec34f46e6f69717644e0b5aabf5d5bd015101f2d72e56e"
|
||||
}
|
||||
}
|
||||
}
|
||||
313
main.qml
Normal file
@ -0,0 +1,313 @@
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Window 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Controls.Material 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import "./qml/Theme" as Theme
|
||||
import "./qml/components" as Components
|
||||
|
||||
ApplicationWindow {
|
||||
id: mainWindow
|
||||
visible: true
|
||||
width: 1010
|
||||
height: 650
|
||||
minimumWidth: 1010
|
||||
minimumHeight: 650
|
||||
maximumWidth: 1010
|
||||
maximumHeight: 650
|
||||
title: "World of Warcraft 3.3.5a Launcher"
|
||||
|
||||
// Добавляем фоновое изображение
|
||||
background: Rectangle {
|
||||
color: Theme.Theme.backgroundColor
|
||||
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
source: "qml/images/background.jpg" // Путь к изображению
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
opacity: 0.3
|
||||
|
||||
// Добавляем затемнение
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "#000000"
|
||||
opacity: 0.6
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Material.theme: Material.Dark
|
||||
Material.accent: Theme.Theme.accentColor
|
||||
Material.background: Theme.Theme.backgroundColor
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
// Основной контент
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.margins: Theme.Theme.margin
|
||||
spacing: Theme.Theme.spacing
|
||||
|
||||
// Левая панель
|
||||
Components.Section {
|
||||
Layout.preferredWidth: 300
|
||||
Layout.minimumWidth: 300
|
||||
Layout.fillHeight: true
|
||||
|
||||
// Заголовок
|
||||
Components.Header {
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: 70
|
||||
}
|
||||
|
||||
// Информационная панель
|
||||
Components.InfoPanel {
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: 150
|
||||
serverInfo: "x2 WotLK"
|
||||
requirements: "• OS: Windows 7/8/10/11\n• CPU: 2.4 GHz\n• RAM: 2 GB\n• HDD: 15 GB"
|
||||
}
|
||||
|
||||
// Растягивающийся элемент
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
|
||||
// Статус текст
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: 50
|
||||
text: launcher ? launcher.statusText : "Пожалуйста, выберите папку с игрой"
|
||||
color: Theme.Theme.primaryText
|
||||
font.pixelSize: Theme.Theme.normalSize
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 1
|
||||
color: Qt.darker(Theme.Theme.borderColor, 1.2)
|
||||
Layout.margins: Theme.Theme.spacing
|
||||
}
|
||||
|
||||
// Кнопки
|
||||
Components.WoWButton {
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: Theme.Theme.buttonHeight
|
||||
text: "Выбрать папку с игрой"
|
||||
visible: launcher ? !launcher.gamePath : true
|
||||
onClicked: launcher && launcher.selectGamePath()
|
||||
tooltip: "Выберите папку, где будет установлена игра"
|
||||
}
|
||||
|
||||
Components.WoWButton {
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: Theme.Theme.buttonHeight
|
||||
text: launcher && launcher.isDownloading ? "Остановить загрузку" : "Скачать клиент"
|
||||
onClicked: launcher && launcher.startDownload()
|
||||
tooltip: launcher && launcher.isDownloading ?
|
||||
"Остановить текущую загрузку" :
|
||||
"Загрузить клиент World of Warcraft"
|
||||
}
|
||||
|
||||
// Добавляем отступ после кнопок
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: Theme.Theme.spacing * 2 // Отступ снизу
|
||||
}
|
||||
}
|
||||
|
||||
// Правая панель
|
||||
Components.Section {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.minimumWidth: 400
|
||||
|
||||
// Слайд-шоу
|
||||
Components.SlideShow {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.minimumHeight: 300
|
||||
imageUrls: [
|
||||
"qml/images/slide1.jpg",
|
||||
"qml/images/slide2.jpg",
|
||||
"qml/images/slide3.jpg",
|
||||
"qml/images/slide4.jpg"
|
||||
]
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 1
|
||||
color: Qt.darker(Theme.Theme.borderColor, 1.2)
|
||||
Layout.margins: Theme.Theme.spacing
|
||||
}
|
||||
|
||||
// Нижняя часть с прогрессом и кнопкой
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: Theme.Theme.spacing
|
||||
spacing: Theme.Theme.spacing
|
||||
|
||||
// Прогресс-бар и имя файла
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Theme.Theme.spacing / 2
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: launcher ? launcher.currentFileName : ""
|
||||
color: Theme.Theme.primaryText
|
||||
font.pixelSize: Theme.Theme.smallSize
|
||||
visible: text !== ""
|
||||
}
|
||||
|
||||
Components.ProgressSection {
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: 40
|
||||
Layout.leftMargin: 1
|
||||
Layout.rightMargin: 1
|
||||
Layout.topMargin: 1
|
||||
Layout.bottomMargin: 1
|
||||
value: launcher ? launcher.downloadProgress : 0
|
||||
text: {
|
||||
if (launcher) {
|
||||
if (launcher.downloadSpeed && launcher.downloadSizeInfo) {
|
||||
return launcher.downloadSpeed + " / " + launcher.downloadSizeInfo
|
||||
}
|
||||
return launcher.downloadSpeed
|
||||
}
|
||||
return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Кнопка запуска
|
||||
Components.WoWButton {
|
||||
Layout.alignment: Qt.AlignBottom
|
||||
Layout.minimumWidth: 120
|
||||
Layout.minimumHeight: Theme.Theme.buttonHeight
|
||||
text: "ИГРАТЬ"
|
||||
enabled: launcher ? launcher.canPlay : false
|
||||
onClicked: launcher && launcher.launchGame()
|
||||
tooltip: enabled ?
|
||||
"Запустить World of Warcraft" :
|
||||
"Сначала установите игру"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Статус бар
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: 30
|
||||
height: 30
|
||||
color: Qt.rgba(Theme.Theme.frameColor.r, Theme.Theme.frameColor.g, Theme.Theme.frameColor.b, 0.35)
|
||||
border.color: Qt.rgba(Theme.Theme.borderColor.r, Theme.Theme.borderColor.g, Theme.Theme.borderColor.b, 0.4)
|
||||
|
||||
RowLayout {
|
||||
anchors {
|
||||
fill: parent
|
||||
leftMargin: Theme.Theme.margin
|
||||
rightMargin: Theme.Theme.margin
|
||||
topMargin: Theme.Theme.margin / 2
|
||||
bottomMargin: Theme.Theme.margin / 2
|
||||
}
|
||||
spacing: Theme.Theme.spacing
|
||||
|
||||
// Статус сервера
|
||||
Label {
|
||||
Layout.minimumWidth: 100
|
||||
text: launcher ? launcher.serverStatus : "⚫ Offline"
|
||||
color: launcher && launcher.isServerOnline ? Theme.Theme.primaryText : Theme.Theme.disabledText
|
||||
font.bold: true
|
||||
|
||||
ToolTip.visible: serverMouseArea.containsMouse
|
||||
ToolTip.text: launcher && launcher.isServerOnline ?
|
||||
"Сервер доступен" :
|
||||
"Сервер недоступен"
|
||||
|
||||
MouseArea {
|
||||
id: serverMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
}
|
||||
}
|
||||
|
||||
// Растягивающийся элемент
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
// Версия
|
||||
Label {
|
||||
Layout.minimumWidth: 80
|
||||
text: "Версия: " + (launcher ? launcher.version : "3.3.5")
|
||||
color: Theme.Theme.secondaryText
|
||||
}
|
||||
|
||||
// Кнопка настроек
|
||||
ToolButton {
|
||||
Layout.preferredWidth: 30
|
||||
Layout.preferredHeight: 30
|
||||
icon.source: "qml/components/qml/images/icons/settings.png"
|
||||
icon.color: Theme.Theme.secondaryText
|
||||
icon.width: 16
|
||||
icon.height: 16
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: "Настройки"
|
||||
onClicked: settingsDialog.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Components.Notification {
|
||||
id: notification
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
bottom: parent.bottom
|
||||
bottomMargin: Theme.Theme.margin * 2
|
||||
}
|
||||
z: 999
|
||||
}
|
||||
|
||||
// Функция для показа уведомлений
|
||||
function showNotification(message, type) {
|
||||
notification.type = type || "info"
|
||||
notification.text = message
|
||||
notification.show()
|
||||
}
|
||||
|
||||
Components.SettingsDialog {
|
||||
id: settingsDialog
|
||||
}
|
||||
|
||||
Components.AboutDialog {
|
||||
id: aboutDialog
|
||||
}
|
||||
|
||||
Components.ContextMenu {
|
||||
id: contextMenu
|
||||
}
|
||||
|
||||
// Добавить MouseArea для всего окна
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.RightButton
|
||||
onClicked: {
|
||||
if (mouse.button === Qt.RightButton)
|
||||
contextMenu.popup()
|
||||
}
|
||||
}
|
||||
|
||||
// Добавляем обработку закрытия окна
|
||||
onClosing: function(close) {
|
||||
if (launcher.settings.closeToTray) {
|
||||
close.accepted = false
|
||||
launcher.minimizeToTray()
|
||||
}
|
||||
}
|
||||
}
|
||||
41
qml/Theme/Theme.qml
Normal file
@ -0,0 +1,41 @@
|
||||
pragma Singleton
|
||||
import QtQuick 2.15
|
||||
|
||||
QtObject {
|
||||
// Цвета
|
||||
readonly property color backgroundColor: "#0A0A0A"
|
||||
readonly property color frameColor: "#1A1A1A"
|
||||
readonly property color borderColor: "#393939"
|
||||
readonly property color primaryText: "#FFB100"
|
||||
readonly property color secondaryText: "#CD8500"
|
||||
readonly property color disabledText: "#4A4A4A"
|
||||
readonly property color accentColor: "#4B0082"
|
||||
|
||||
// Размеры
|
||||
readonly property int margin: 10
|
||||
readonly property int spacing: 15
|
||||
readonly property int radius: 8
|
||||
readonly property int buttonHeight: 45
|
||||
|
||||
// Шрифты
|
||||
readonly property int titleSize: 24
|
||||
readonly property int subtitleSize: 16
|
||||
readonly property int normalSize: 14
|
||||
readonly property int smallSize: 12
|
||||
|
||||
// Градиенты
|
||||
readonly property var buttonGradient: Gradient {
|
||||
GradientStop { position: 0.0; color: "#1B3859" }
|
||||
GradientStop { position: 1.0; color: "#0A1B2A" }
|
||||
}
|
||||
|
||||
readonly property var buttonHoverGradient: Gradient {
|
||||
GradientStop { position: 0.0; color: "#2B4869" }
|
||||
GradientStop { position: 1.0; color: "#1A2B3A" }
|
||||
}
|
||||
|
||||
readonly property var buttonPressedGradient: Gradient {
|
||||
GradientStop { position: 0.0; color: "#0A1B2A" }
|
||||
GradientStop { position: 1.0; color: "#1B3859" }
|
||||
}
|
||||
}
|
||||
2
qml/Theme/qmldir
Normal file
@ -0,0 +1,2 @@
|
||||
module Theme
|
||||
singleton Theme 1.0 Theme.qml
|
||||
61
qml/components/AboutDialog.qml
Normal file
@ -0,0 +1,61 @@
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Controls.Material 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import "../Theme" as Theme
|
||||
|
||||
Dialog {
|
||||
id: root
|
||||
title: "О программе"
|
||||
modal: true
|
||||
|
||||
x: (parent.width - width) / 2
|
||||
y: (parent.height - height) / 2
|
||||
width: 400
|
||||
height: 300
|
||||
|
||||
Material.background: Theme.Theme.frameColor
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: Theme.Theme.spacing
|
||||
|
||||
Image {
|
||||
source: "qml/images/logo.png"
|
||||
Layout.preferredWidth: 200
|
||||
Layout.preferredHeight: 100
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
fillMode: Image.PreserveAspectFit
|
||||
}
|
||||
|
||||
Label {
|
||||
text: "World of Warcraft 3.3.5a Launcher"
|
||||
font.pixelSize: Theme.Theme.titleSize
|
||||
font.bold: true
|
||||
color: Theme.Theme.primaryText
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
Label {
|
||||
text: "Версия: " + (launcher ? launcher.version : "3.3.5")
|
||||
color: Theme.Theme.secondaryText
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
Item { Layout.fillHeight: true }
|
||||
|
||||
Label {
|
||||
text: "© 2024 Your Server Name"
|
||||
color: Theme.Theme.secondaryText
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
}
|
||||
|
||||
footer: DialogButtonBox {
|
||||
Button {
|
||||
text: "Закрыть"
|
||||
DialogButtonBox.buttonRole: DialogButtonBox.RejectRole
|
||||
onClicked: root.reject()
|
||||
}
|
||||
}
|
||||
}
|
||||
37
qml/components/ContextMenu.qml
Normal file
@ -0,0 +1,37 @@
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import "../Theme" as Theme
|
||||
|
||||
Menu {
|
||||
id: contextMenu
|
||||
|
||||
MenuItem {
|
||||
text: "Открыть папку с игрой"
|
||||
enabled: launcher && launcher.gamePath
|
||||
onTriggered: launcher.openGameFolder()
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: "Проверить файлы"
|
||||
enabled: launcher && launcher.gamePath && !launcher.isDownloading
|
||||
onTriggered: launcher.verifyFiles()
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: "Восстановить клиент"
|
||||
enabled: launcher && launcher.gamePath && !launcher.isDownloading
|
||||
onTriggered: launcher.repairClient()
|
||||
}
|
||||
|
||||
MenuSeparator { }
|
||||
|
||||
MenuItem {
|
||||
text: "Настройки"
|
||||
onTriggered: settingsDialog.open()
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: "О программе"
|
||||
onTriggered: aboutDialog.open()
|
||||
}
|
||||
}
|
||||
122
qml/components/Header.qml
Normal file
@ -0,0 +1,122 @@
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Controls.Material 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import "../Theme" as Theme
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: 70
|
||||
color: "transparent"
|
||||
|
||||
// Анимированный фон логотипа
|
||||
Rectangle {
|
||||
id: logoBackground
|
||||
anchors.left: parent.left
|
||||
width: logo.width
|
||||
height: parent.height
|
||||
color: "transparent"
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
gradient: Gradient {
|
||||
GradientStop { position: 0.0; color: "#00152536" }
|
||||
GradientStop { position: 0.5; color: "#20152536" }
|
||||
GradientStop { position: 1.0; color: "#00152536" }
|
||||
}
|
||||
|
||||
SequentialAnimation on opacity {
|
||||
loops: Animation.Infinite
|
||||
NumberAnimation { to: 0.3; duration: 2000; easing.type: Easing.InOutQuad }
|
||||
NumberAnimation { to: 1.0; duration: 2000; easing.type: Easing.InOutQuad }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Image {
|
||||
id: logo
|
||||
source: "qml/images/logo.png"
|
||||
width: parent.height * 1.5
|
||||
height: parent.height
|
||||
fillMode: Image.PreserveAspectFit
|
||||
|
||||
// Эффект свечения для логотипа
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "#3A5570"
|
||||
opacity: 0
|
||||
|
||||
SequentialAnimation on opacity {
|
||||
loops: Animation.Infinite
|
||||
NumberAnimation { to: 0.2; duration: 2000; easing.type: Easing.InOutQuad }
|
||||
NumberAnimation { to: 0; duration: 2000; easing.type: Easing.InOutQuad }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Статистика сервера
|
||||
Row {
|
||||
anchors {
|
||||
right: parent.right
|
||||
verticalCenter: parent.verticalCenter
|
||||
rightMargin: Theme.Theme.spacing
|
||||
}
|
||||
spacing: Theme.Theme.spacing * 2
|
||||
|
||||
// Онлайн
|
||||
StatsBox {
|
||||
label: "ОНЛАЙН"
|
||||
value: "1234"
|
||||
|
||||
// Анимация при изменении значения
|
||||
Behavior on value {
|
||||
NumberAnimation {
|
||||
duration: 500
|
||||
easing.type: Easing.OutBack
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Рейты
|
||||
StatsBox {
|
||||
label: "РЕЙТЫ"
|
||||
value: "x2"
|
||||
}
|
||||
}
|
||||
|
||||
// Компонент для отображения статистики
|
||||
component StatsBox: Column {
|
||||
property string label: ""
|
||||
property string value: ""
|
||||
spacing: 2
|
||||
|
||||
Label {
|
||||
text: label
|
||||
color: Theme.Theme.secondaryText
|
||||
font.pixelSize: Theme.Theme.smallSize
|
||||
font.bold: true
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
Label {
|
||||
text: value
|
||||
color: "#00AAFF"
|
||||
font.pixelSize: Theme.Theme.titleSize
|
||||
font.bold: true
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
// Подсветка при наведении
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onEntered: parent.color = "#40AAFF"
|
||||
onExited: parent.color = "#00AAFF"
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation { duration: 150 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
127
qml/components/InfoPanel.qml
Normal file
@ -0,0 +1,127 @@
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Controls.Material 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import "../Theme" as Theme
|
||||
|
||||
Pane {
|
||||
id: root
|
||||
Layout.fillWidth: true
|
||||
padding: Theme.Theme.margin
|
||||
|
||||
Material.elevation: 6
|
||||
Material.background: Qt.darker(Theme.Theme.frameColor, 1.1)
|
||||
|
||||
property alias serverInfo: serverLabel.text
|
||||
property alias requirements: reqLabel.text
|
||||
|
||||
background: Rectangle {
|
||||
color: Qt.darker(Theme.Theme.frameColor, 1.1)
|
||||
radius: Theme.Theme.radius / 2
|
||||
border.color: Qt.darker(Theme.Theme.borderColor, 1.2)
|
||||
border.width: 1
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: parent.radius
|
||||
gradient: Gradient {
|
||||
GradientStop { position: 0.0; color: "#20FFFFFF" }
|
||||
GradientStop { position: 1.0; color: "#00FFFFFF" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
width: parent.width
|
||||
spacing: Theme.Theme.spacing * 1.5
|
||||
|
||||
// Секция сервера
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Theme.Theme.spacing
|
||||
|
||||
Image {
|
||||
source: "qml/images/icons/server.png"
|
||||
sourceSize: Qt.size(24, 24)
|
||||
Layout.alignment: Qt.AlignTop
|
||||
opacity: 0.8
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Theme.Theme.spacing / 2
|
||||
|
||||
Label {
|
||||
text: "ИНФОРМАЦИЯ О СЕРВЕРЕ"
|
||||
color: Theme.Theme.secondaryText
|
||||
font.pixelSize: Theme.Theme.smallSize
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Label {
|
||||
id: serverLabel
|
||||
Layout.fillWidth: true
|
||||
color: "#77A7D1"
|
||||
font.pixelSize: Theme.Theme.normalSize
|
||||
font.bold: true
|
||||
|
||||
Behavior on text {
|
||||
SequentialAnimation {
|
||||
NumberAnimation { target: serverLabel; property: "opacity"; to: 0; duration: 100 }
|
||||
PropertyAction { target: serverLabel; property: "text" }
|
||||
NumberAnimation { target: serverLabel; property: "opacity"; to: 1; duration: 100 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 1
|
||||
color: Qt.darker(Theme.Theme.borderColor, 1.2)
|
||||
opacity: 0.5
|
||||
}
|
||||
|
||||
// Секция системных требований
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Theme.Theme.spacing
|
||||
|
||||
Image {
|
||||
source: "qml/images/icons/requirements.png"
|
||||
sourceSize: Qt.size(24, 24)
|
||||
Layout.alignment: Qt.AlignTop
|
||||
opacity: 0.8
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Theme.Theme.spacing / 2
|
||||
|
||||
Label {
|
||||
text: "СИСТЕМНЫЕ ТРЕБОВАНИЯ"
|
||||
color: Theme.Theme.secondaryText
|
||||
font.pixelSize: Theme.Theme.smallSize
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Label {
|
||||
id: reqLabel
|
||||
Layout.fillWidth: true
|
||||
color: Theme.Theme.secondaryText
|
||||
font.pixelSize: Theme.Theme.smallSize
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
Behavior on text {
|
||||
SequentialAnimation {
|
||||
NumberAnimation { target: reqLabel; property: "opacity"; to: 0; duration: 100 }
|
||||
PropertyAction { target: reqLabel; property: "text" }
|
||||
NumberAnimation { target: reqLabel; property: "opacity"; to: 1; duration: 100 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
67
qml/components/Notification.qml
Normal file
@ -0,0 +1,67 @@
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import "../Theme" as Theme
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
width: message.width + Theme.Theme.spacing * 4
|
||||
height: message.height + Theme.Theme.spacing * 2
|
||||
radius: Theme.Theme.radius
|
||||
opacity: 0
|
||||
|
||||
property string text: ""
|
||||
property string type: "info" // info, error, success
|
||||
|
||||
color: {
|
||||
switch(type) {
|
||||
case "error": return "#4D2C2C"
|
||||
case "success": return "#2C4D2C"
|
||||
default: return Theme.Theme.frameColor
|
||||
}
|
||||
}
|
||||
|
||||
border.color: {
|
||||
switch(type) {
|
||||
case "error": return "#FF4444"
|
||||
case "success": return "#44FF44"
|
||||
default: return Theme.Theme.borderColor
|
||||
}
|
||||
}
|
||||
border.width: 1
|
||||
|
||||
Label {
|
||||
id: message
|
||||
anchors.centerIn: parent
|
||||
text: root.text
|
||||
color: Theme.Theme.primaryText
|
||||
font.pixelSize: Theme.Theme.smallSize
|
||||
}
|
||||
|
||||
// Анимации
|
||||
NumberAnimation {
|
||||
id: showAnim
|
||||
target: root
|
||||
property: "opacity"
|
||||
to: 1
|
||||
duration: 200
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
id: hideAnim
|
||||
target: root
|
||||
property: "opacity"
|
||||
to: 0
|
||||
duration: 200
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: hideTimer
|
||||
interval: 3000
|
||||
onTriggered: hideAnim.start()
|
||||
}
|
||||
|
||||
function show() {
|
||||
showAnim.start()
|
||||
hideTimer.restart()
|
||||
}
|
||||
}
|
||||
151
qml/components/ProgressSection.qml
Normal file
@ -0,0 +1,151 @@
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Controls.Material 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import "../Theme" as Theme
|
||||
|
||||
Item {
|
||||
id: root
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: 40
|
||||
visible: value > 0 || text !== ""
|
||||
|
||||
property alias value: progressBar.value
|
||||
property alias text: speedLabel.text
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: Theme.Theme.spacing
|
||||
|
||||
ProgressBar {
|
||||
id: progressBar
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 14
|
||||
from: 0
|
||||
to: 1.0
|
||||
|
||||
background: Rectangle {
|
||||
implicitWidth: 200
|
||||
implicitHeight: 14
|
||||
color: "#0A0A0A"
|
||||
radius: 2
|
||||
border.width: 1
|
||||
border.color: "#2A4055"
|
||||
clip: true
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: parent.radius
|
||||
color: "#152536"
|
||||
visible: progressBar.value > 0 && progressBar.value < 1
|
||||
clip: true
|
||||
|
||||
Rectangle {
|
||||
id: animatedBackground
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
color: "#1E3346"
|
||||
radius: parent.radius
|
||||
|
||||
SequentialAnimation on opacity {
|
||||
loops: Animation.Infinite
|
||||
running: progressBar.value > 0 && progressBar.value < 1
|
||||
NumberAnimation { to: 0.3; duration: 1000 }
|
||||
NumberAnimation { to: 0.1; duration: 1000 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: Item {
|
||||
implicitWidth: 200
|
||||
implicitHeight: 14
|
||||
clip: true
|
||||
|
||||
Rectangle {
|
||||
width: progressBar.visualPosition * parent.width
|
||||
height: parent.height
|
||||
radius: 2
|
||||
clip: true
|
||||
|
||||
gradient: Gradient {
|
||||
GradientStop { position: 0.0; color: "#1E3346" }
|
||||
GradientStop { position: 0.5; color: "#2B4869" }
|
||||
GradientStop { position: 1.0; color: "#1E3346" }
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: "#3A5570"
|
||||
opacity: 0.7
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
radius: parent.radius
|
||||
gradient: Gradient {
|
||||
GradientStop { position: 0.0; color: "#20FFFFFF" }
|
||||
GradientStop { position: 0.5; color: "#40FFFFFF" }
|
||||
GradientStop { position: 1.0; color: "#20FFFFFF" }
|
||||
}
|
||||
|
||||
SequentialAnimation on opacity {
|
||||
loops: Animation.Infinite
|
||||
running: progressBar.value > 0 && progressBar.value < 1
|
||||
NumberAnimation { to: 0.1; duration: 1000 }
|
||||
NumberAnimation { to: 0.3; duration: 1000 }
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: 100
|
||||
easing.type: Easing.OutQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Theme.Theme.spacing
|
||||
|
||||
Label {
|
||||
id: speedLabel
|
||||
color: "#77A7D1"
|
||||
font.pixelSize: Theme.Theme.smallSize
|
||||
font.bold: true
|
||||
visible: text !== ""
|
||||
|
||||
Behavior on text {
|
||||
SequentialAnimation {
|
||||
NumberAnimation { target: speedLabel; property: "opacity"; to: 0.7; duration: 100 }
|
||||
PropertyAction { target: speedLabel; property: "text" }
|
||||
NumberAnimation { target: speedLabel; property: "opacity"; to: 1.0; duration: 100 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: Math.round(progressBar.value * 100) + "%"
|
||||
color: "#77A7D1"
|
||||
font.pixelSize: Theme.Theme.smallSize
|
||||
font.bold: true
|
||||
horizontalAlignment: Text.AlignRight
|
||||
visible: progressBar.value > 0
|
||||
|
||||
Behavior on text {
|
||||
SequentialAnimation {
|
||||
NumberAnimation { target: parent; property: "opacity"; to: 0.7; duration: 100 }
|
||||
PropertyAction { target: parent; property: "text" }
|
||||
NumberAnimation { target: parent; property: "opacity"; to: 1.0; duration: 100 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
73
qml/components/Section.qml
Normal file
@ -0,0 +1,73 @@
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Controls.Material 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import "../Theme" as Theme
|
||||
|
||||
Pane {
|
||||
id: root
|
||||
padding: Theme.Theme.margin
|
||||
|
||||
Material.elevation: 6
|
||||
Material.background: Theme.Theme.frameColor
|
||||
|
||||
// Анимация появления
|
||||
opacity: 0
|
||||
Component.onCompleted: appearAnimation.start()
|
||||
|
||||
NumberAnimation {
|
||||
id: appearAnimation
|
||||
target: root
|
||||
property: "opacity"
|
||||
from: 0
|
||||
to: 1
|
||||
duration: 500
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: Qt.rgba(Theme.Theme.frameColor.r, Theme.Theme.frameColor.g, Theme.Theme.frameColor.b, 0.35)
|
||||
radius: Theme.Theme.radius
|
||||
|
||||
// Граница с тенью
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: parent.radius
|
||||
color: "transparent"
|
||||
border.color: Qt.rgba(Theme.Theme.borderColor.r, Theme.Theme.borderColor.g, Theme.Theme.borderColor.b, 0.4)
|
||||
border.width: 1
|
||||
|
||||
// Нижняя тень
|
||||
Rectangle {
|
||||
anchors.bottom: parent.bottom
|
||||
width: parent.width
|
||||
height: 2
|
||||
color: "#000000"
|
||||
opacity: 0.1
|
||||
}
|
||||
|
||||
// Верхний блик
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: "#FFFFFF"
|
||||
opacity: 0.1
|
||||
}
|
||||
}
|
||||
|
||||
// Внутренний градиент
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: parent.radius
|
||||
gradient: Gradient {
|
||||
GradientStop { position: 0.0; color: Qt.rgba(1, 1, 1, 0.1) }
|
||||
GradientStop { position: 1.0; color: Qt.rgba(1, 1, 1, 0) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Контент
|
||||
contentItem: ColumnLayout {
|
||||
spacing: Theme.Theme.spacing
|
||||
}
|
||||
}
|
||||
206
qml/components/SettingsDialog.qml
Normal file
@ -0,0 +1,206 @@
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Controls.Material 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import "../Theme" as Theme
|
||||
|
||||
Dialog {
|
||||
id: root
|
||||
title: "Настройки"
|
||||
modal: true
|
||||
|
||||
x: (parent.width - width) / 2
|
||||
y: (parent.height - height) / 2
|
||||
width: 400
|
||||
height: 500
|
||||
|
||||
Material.background: Theme.Theme.frameColor
|
||||
|
||||
ScrollView {
|
||||
id: scrollView
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
|
||||
ColumnLayout {
|
||||
width: scrollView.availableWidth
|
||||
spacing: Theme.Theme.spacing
|
||||
|
||||
// Игровые настройки
|
||||
GroupBox {
|
||||
Layout.fillWidth: true
|
||||
title: "Игровые настройки"
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
|
||||
// Путь к игре
|
||||
Label {
|
||||
text: "Путь к игре:"
|
||||
color: Theme.Theme.secondaryText
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Theme.Theme.spacing
|
||||
|
||||
TextField {
|
||||
id: gamePathField
|
||||
Layout.fillWidth: true
|
||||
text: launcher ? launcher.gamePath : ""
|
||||
readOnly: true
|
||||
color: Theme.Theme.primaryText
|
||||
|
||||
background: Rectangle {
|
||||
color: Qt.darker(Theme.Theme.frameColor, 1.2)
|
||||
border.color: Theme.Theme.borderColor
|
||||
radius: 3
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Обзор"
|
||||
onClicked: launcher && launcher.selectGamePath()
|
||||
}
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
text: "Автозапуск при старте Windows"
|
||||
checked: launcher && launcher.settings ? launcher.settings.autostart : false
|
||||
onCheckedChanged: if (launcher && launcher.settings) launcher.settings.autostart = checked
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
text: "Закрывать лаунчер при запуске игры"
|
||||
checked: launcher && launcher.settings ? launcher.settings.closeOnLaunch : false
|
||||
onCheckedChanged: if (launcher && launcher.settings) launcher.settings.closeOnLaunch = checked
|
||||
}
|
||||
|
||||
// Выбор эмулятора для Linux
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
visible: Qt.platform.os !== "windows" // Показываем только на Linux/Mac
|
||||
|
||||
Label {
|
||||
text: "Эмулятор Windows:"
|
||||
color: Theme.Theme.secondaryText
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
Layout.fillWidth: true
|
||||
model: ["Wine", "Lutris", "Proton", "PortProton", "CrossOver"]
|
||||
currentIndex: {
|
||||
if (launcher && launcher.settings) {
|
||||
switch(launcher.settings.linuxEmulator) {
|
||||
case "wine": return 0;
|
||||
case "lutris": return 1;
|
||||
case "proton": return 2;
|
||||
case "portproton": return 3;
|
||||
case "crossover": return 4;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
onCurrentIndexChanged: {
|
||||
if (launcher && launcher.settings) {
|
||||
switch(currentIndex) {
|
||||
case 0: launcher.settings.linuxEmulator = "wine"; break;
|
||||
case 1: launcher.settings.linuxEmulator = "lutris"; break;
|
||||
case 2: launcher.settings.linuxEmulator = "proton"; break;
|
||||
case 3: launcher.settings.linuxEmulator = "portproton"; break;
|
||||
case 4: launcher.settings.linuxEmulator = "crossover"; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Кнопка проверки эмулятора
|
||||
Button {
|
||||
text: "Проверить"
|
||||
onClicked: if (launcher) launcher.checkEmulator()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Настройки загрузки
|
||||
GroupBox {
|
||||
Layout.fillWidth: true
|
||||
title: "Настройки загрузки"
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
|
||||
Label {
|
||||
text: "Ограничение скорости загрузки:"
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
Layout.fillWidth: true
|
||||
model: ["Без ограничений", "1 Мбит/с", "2 Мбит/с", "5 Мбит/с", "10 Мбит/с"]
|
||||
currentIndex: launcher && launcher.settings ? launcher.settings.speedLimit : 0
|
||||
onCurrentIndexChanged: if (launcher && launcher.settings) launcher.settings.speedLimit = currentIndex
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
text: "Автоматически загружать обновления"
|
||||
checked: launcher && launcher.settings ? launcher.settings.autoUpdate : true
|
||||
onCheckedChanged: if (launcher && launcher.settings) launcher.settings.autoUpdate = checked
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Настройки интерфейса
|
||||
GroupBox {
|
||||
Layout.fillWidth: true
|
||||
title: "Настройки интерфейса"
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
|
||||
Label {
|
||||
text: "Интервал слайд-шоу (сек):"
|
||||
}
|
||||
|
||||
SpinBox {
|
||||
Layout.fillWidth: true
|
||||
from: 3
|
||||
to: 15
|
||||
value: launcher && launcher.settings ? launcher.settings.slideInterval : 5
|
||||
onValueChanged: if (launcher && launcher.settings) launcher.settings.slideInterval = value
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
text: "Показывать уведомления"
|
||||
checked: launcher && launcher.settings ? launcher.settings.showNotifications : true
|
||||
onCheckedChanged: if (launcher && launcher.settings) launcher.settings.showNotifications = checked
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: Theme.Theme.spacing * 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer: DialogButtonBox {
|
||||
Button {
|
||||
text: "Применить"
|
||||
DialogButtonBox.buttonRole: DialogButtonBox.ApplyRole
|
||||
onClicked: {
|
||||
if (launcher) launcher.saveSettings()
|
||||
root.accept()
|
||||
}
|
||||
}
|
||||
Button {
|
||||
text: "Отмена"
|
||||
DialogButtonBox.buttonRole: DialogButtonBox.RejectRole
|
||||
onClicked: root.reject()
|
||||
}
|
||||
}
|
||||
}
|
||||
153
qml/components/SlideShow.qml
Normal file
@ -0,0 +1,153 @@
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import "../Theme" as Theme
|
||||
|
||||
Item {
|
||||
id: root
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.minimumHeight: 300
|
||||
|
||||
property var imageUrls: []
|
||||
property int interval: 5000
|
||||
|
||||
// Добавляем второе изображение для плавного перехода
|
||||
Image {
|
||||
id: fadeOutImage
|
||||
anchors.fill: parent
|
||||
fillMode: Image.PreserveAspectFit
|
||||
opacity: 0
|
||||
}
|
||||
|
||||
Image {
|
||||
id: mainImage
|
||||
anchors.fill: parent
|
||||
source: imageUrls.length > 0 ? imageUrls[currentIndex] : ""
|
||||
fillMode: Image.PreserveAspectFit
|
||||
|
||||
property int currentIndex: 0
|
||||
|
||||
Timer {
|
||||
interval: root.interval
|
||||
running: imageUrls.length > 1
|
||||
repeat: true
|
||||
onTriggered: nextSlide()
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation { duration: 800; easing.type: Easing.InOutQuad }
|
||||
}
|
||||
}
|
||||
|
||||
// Кнопки навигации
|
||||
Rectangle {
|
||||
id: prevButton
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 40; height: 40
|
||||
color: "transparent"
|
||||
opacity: prevMouse.containsMouse ? 1 : 0.3
|
||||
visible: imageUrls.length > 1
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "❮"
|
||||
color: "#FFFFFF"
|
||||
font.pixelSize: 24
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: prevMouse
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: prevSlide()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: nextButton
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 40; height: 40
|
||||
color: "transparent"
|
||||
opacity: nextMouse.containsMouse ? 1 : 0.3
|
||||
visible: imageUrls.length > 1
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "❯"
|
||||
color: "#FFFFFF"
|
||||
font.pixelSize: 24
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: nextMouse
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: nextSlide()
|
||||
}
|
||||
}
|
||||
|
||||
// Индикаторы в стиле WoW
|
||||
Row {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottomMargin: 10
|
||||
spacing: 8
|
||||
|
||||
Repeater {
|
||||
model: imageUrls.length
|
||||
|
||||
Rectangle {
|
||||
width: 30
|
||||
height: 4
|
||||
radius: 2
|
||||
color: mainImage.currentIndex === index ? "#00AAFF" : "#80FFFFFF"
|
||||
opacity: mainImage.currentIndex === index ? 1.0 : 0.5
|
||||
|
||||
Rectangle {
|
||||
visible: mainImage.currentIndex === index
|
||||
anchors.fill: parent
|
||||
color: "#40FFFFFF"
|
||||
radius: parent.radius
|
||||
|
||||
SequentialAnimation on opacity {
|
||||
running: mainImage.currentIndex === index
|
||||
loops: Animation.Infinite
|
||||
NumberAnimation { to: 0.2; duration: 1000 }
|
||||
NumberAnimation { to: 1.0; duration: 1000 }
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: showSlide(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function nextSlide() {
|
||||
fadeOutImage.source = mainImage.source
|
||||
fadeOutImage.opacity = 1
|
||||
mainImage.currentIndex = (mainImage.currentIndex + 1) % imageUrls.length
|
||||
fadeOutImage.opacity = 0
|
||||
}
|
||||
|
||||
function prevSlide() {
|
||||
fadeOutImage.source = mainImage.source
|
||||
fadeOutImage.opacity = 1
|
||||
mainImage.currentIndex = mainImage.currentIndex === 0 ? imageUrls.length - 1 : mainImage.currentIndex - 1
|
||||
fadeOutImage.opacity = 0
|
||||
}
|
||||
|
||||
function showSlide(index) {
|
||||
if (index !== mainImage.currentIndex) {
|
||||
fadeOutImage.source = mainImage.source
|
||||
fadeOutImage.opacity = 1
|
||||
mainImage.currentIndex = index
|
||||
fadeOutImage.opacity = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
131
qml/components/WoWButton.qml
Normal file
@ -0,0 +1,131 @@
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Controls.Material 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import "../Theme" as Theme
|
||||
|
||||
Button {
|
||||
id: control
|
||||
height: Theme.Theme.buttonHeight
|
||||
|
||||
background: Rectangle {
|
||||
color: {
|
||||
if (!control.enabled) return "#0A0A0A"
|
||||
if (control.pressed) return "#1A2A3A"
|
||||
if (control.hovered) return "#1E3346"
|
||||
return "#152536"
|
||||
}
|
||||
radius: 3
|
||||
|
||||
// Верхняя грань (блик)
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: {
|
||||
if (!control.enabled) return "#252525"
|
||||
if (control.pressed) return "#2A4055"
|
||||
if (control.hovered) return "#3A5570"
|
||||
return "#2A4055"
|
||||
}
|
||||
opacity: control.pressed ? 0.5 : 1
|
||||
}
|
||||
|
||||
// Нижняя грань (тень)
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
anchors.bottom: parent.bottom
|
||||
color: "#000000"
|
||||
opacity: 0.5
|
||||
}
|
||||
|
||||
// Боковые грани
|
||||
Rectangle {
|
||||
width: 1
|
||||
height: parent.height
|
||||
color: {
|
||||
if (!control.enabled) return "#252525"
|
||||
if (control.pressed) return "#2A4055"
|
||||
if (control.hovered) return "#3A5570"
|
||||
return "#2A4055"
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 1
|
||||
height: parent.height
|
||||
anchors.right: parent.right
|
||||
color: {
|
||||
if (!control.enabled) return "#252525"
|
||||
if (control.pressed) return "#2A4055"
|
||||
if (control.hovered) return "#3A5570"
|
||||
return "#2A4055"
|
||||
}
|
||||
}
|
||||
|
||||
// Эффект свечения при наведении
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "#3A5570"
|
||||
opacity: control.hovered ? 0.1 : 0
|
||||
radius: parent.radius
|
||||
|
||||
SequentialAnimation on opacity {
|
||||
running: control.hovered
|
||||
loops: Animation.Infinite
|
||||
NumberAnimation { to: 0.2; duration: 1000 }
|
||||
NumberAnimation { to: 0.1; duration: 1000 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: Text {
|
||||
text: control.text
|
||||
font {
|
||||
family: "Arial"
|
||||
pixelSize: Theme.Theme.normalSize
|
||||
bold: true
|
||||
capitalization: Font.AllUppercase
|
||||
}
|
||||
color: {
|
||||
if (!control.enabled) return "#4A4A4A"
|
||||
if (control.pressed) return "#88BBDD"
|
||||
if (control.hovered) return "#BDE0FF"
|
||||
return "#77A7D1"
|
||||
}
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation { duration: 150 }
|
||||
}
|
||||
}
|
||||
|
||||
// Улучшенная анимация нажатия
|
||||
states: [
|
||||
State {
|
||||
name: "pressed"
|
||||
when: control.pressed
|
||||
PropertyChanges {
|
||||
target: control
|
||||
scale: 0.97
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
transitions: Transition {
|
||||
NumberAnimation {
|
||||
properties: "scale"
|
||||
duration: 50
|
||||
easing.type: Easing.OutQuad
|
||||
}
|
||||
}
|
||||
|
||||
ToolTip {
|
||||
text: parent.tooltip || ""
|
||||
visible: parent.hovered && text
|
||||
delay: 500
|
||||
}
|
||||
|
||||
property string tooltip: ""
|
||||
}
|
||||
19
qml/components/WoWPanel.qml
Normal file
@ -0,0 +1,19 @@
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import "../Theme" as Theme
|
||||
|
||||
Rectangle {
|
||||
color: Theme.Theme.frameColor
|
||||
radius: Theme.Theme.radius
|
||||
border.color: Theme.Theme.borderColor
|
||||
border.width: 2
|
||||
|
||||
default property alias content: container.children
|
||||
|
||||
ColumnLayout {
|
||||
id: container
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.Theme.margin * 2
|
||||
spacing: Theme.Theme.spacing
|
||||
}
|
||||
}
|
||||
BIN
qml/components/qml/images/icons/requirements.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
qml/components/qml/images/icons/server.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
qml/components/qml/images/icons/settings.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
qml/components/qml/images/logo.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
qml/components/qml/images/requirements.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
qml/components/qml/images/slide1.jpg
Normal file
|
After Width: | Height: | Size: 320 KiB |
BIN
qml/components/qml/images/slide2.jpg
Normal file
|
After Width: | Height: | Size: 415 KiB |
BIN
qml/components/qml/images/slide3.jpg
Normal file
|
After Width: | Height: | Size: 269 KiB |
BIN
qml/components/qml/images/slide4.jpg
Normal file
|
After Width: | Height: | Size: 299 KiB |
7
qml/components/qmldir
Normal file
@ -0,0 +1,7 @@
|
||||
module components
|
||||
WoWPanel 1.0 WoWPanel.qml
|
||||
InfoPanel 1.0 InfoPanel.qml
|
||||
WoWButton 1.0 WoWButton.qml
|
||||
Section 1.0 Section.qml
|
||||
Header 1.0 Header.qml
|
||||
ProgressSection 1.0 ProgressSection.qml
|
||||
BIN
qml/images/background.jpg
Normal file
|
After Width: | Height: | Size: 298 KiB |
BIN
qml/images/icons/tray.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
2
requirements.txt
Normal file
@ -0,0 +1,2 @@
|
||||
PyQt5>=5.15.0
|
||||
requests>=2.25.1
|
||||
BIN
screenshots/image.png
Normal file
|
After Width: | Height: | Size: 662 KiB |