1 parent
1943996608
commit
f10b654953
34 files changed
+2099
-4576
No files matched your search
+97
-177
@@ -1,211 +1,131 @@
|
||||
# Main
|
||||
# Main.qml
|
||||
|
||||
## Обзор компонента
|
||||
Главное окно, оно же точка входа: `main.cpp` загружает его через
|
||||
`engine.loadFromModule("BiLauncher", "Main")`. Корень — `Window` 1280×720.
|
||||
|
||||
`Minecraft_launcher` — десктопный лаунчер Minecraft на Qt Quick. `Main.qml` — его главное и
|
||||
единственное настоящее окно: точка входа приложения, которую загружает `main.cpp` вызовом
|
||||
`engine.loadFromModule("Minecraft_launcher", "Main")`.
|
||||
Что в нём есть:
|
||||
|
||||
Окно совмещает четыре роли. Оно держит единственный экземпляр `LauncherBackend` — весь остальной
|
||||
интерфейс получает его от главного окна. Оно рисует сам экран запуска: фоновая картинка, большая
|
||||
кнопка игры по центру, выпадающий список профилей, кнопка активной сборки, кнопки папки модов,
|
||||
настроек и сезонных сборок. Оно показывает обратную связь — всплывающую плашку сообщений и две
|
||||
панели хода долгих операций. И наконец, оно объявляет диалоги, которые не вынесены в отдельные
|
||||
файлы: создание и редактирование профиля, ввод кода двухфакторной аутентификации и настройки
|
||||
запуска.
|
||||
- единственный экземпляр `LauncherBackend` и все обработчики его сигналов;
|
||||
- экран запуска: фон, кнопка игры по центру, список профилей, кнопка активной сборки, кнопки папки
|
||||
модов, настроек и сезонных сборок;
|
||||
- плашка сообщений и две панели прогресса ([ProgressPanel](ProgressPanel.md));
|
||||
- диалоги, которые не вынесены в отдельные файлы: новый профиль, редактирование профиля, код
|
||||
двухфакторной аутентификации, настройки запуска.
|
||||
|
||||
## Место в проекте и зависимости
|
||||
Отдельными файлами сделаны [BuildsDialog](BuildsDialog.md),
|
||||
[SeasonalBuildsDialog](SeasonalBuildsDialog.md), [JavaPickerDialog](JavaPickerDialog.md) и
|
||||
[MicrosoftLoginDialog](MicrosoftLoginDialog.md). Окно передаёт им бэкенд через свойство `backend`.
|
||||
|
||||
Импортирует `QtQuick`, `QtQuick.Layouts 2.15`, `QtQuick.Controls 2.15` и сам QML-модуль проекта
|
||||
`Minecraft_launcher`, из которого приходит тип `LauncherBackend`.
|
||||
Стиль Qt Quick Controls — `Basic` (выставлен в `main.cpp`), и почти у каждого контрола здесь своё
|
||||
оформление.
|
||||
|
||||
Инстанцирует четыре компонента модуля: [ProgressPanel](ProgressPanel.md) (дважды),
|
||||
[SeasonalBuildsDialog](SeasonalBuildsDialog.md), [BuildsDialog](BuildsDialog.md),
|
||||
[JavaPickerDialog](JavaPickerDialog.md), а также [DarkCombo](DarkCombo.md) и
|
||||
[LabelledField](LabelledField.md) внутри своих диалогов.
|
||||
[MicrosoftLoginDialog](MicrosoftLoginDialog.md) создаётся динамически — см. ниже.
|
||||
## Раскладка
|
||||
|
||||
Стиль Qt Quick Controls принудительно выставлен в `Basic` в `main.cpp`, потому что нативные стили
|
||||
игнорируют пользовательские `contentItem` и `background`; поэтому в этом файле почти каждый
|
||||
элемент управления переопределяет своё оформление вручную.
|
||||
Всё стоит на якорях вокруг кнопки запуска: список профилей слева сверху от неё, кнопка активной
|
||||
сборки справа сверху, кнопки папки и настроек под списком профилей. Кнопка сезонных сборок стоит в
|
||||
правом нижнем углу, потому что остальные углы заняты.
|
||||
|
||||
Использует ресурсы из `RESOURCES` QML-модуля: фоновую картинку, три состояния кнопки запуска, по
|
||||
три состояния кнопок папки и настроек, стрелки выпадающих списков, `images/Trash.svg` и
|
||||
`images/Pencil.svg`.
|
||||
Обе панели прогресса (загрузки и смены сборки) стоят в левом нижнем углу на одних и тех же якорях.
|
||||
Одновременно они не появляются: пока лаунчер занят загрузкой, бэкенд не начнёт смену сборки, и
|
||||
наоборот. Панель смены сборки отменить нельзя.
|
||||
|
||||
## Иерархия и роль
|
||||
У заголовков и подвалов всех диалогов корнем служит `Item` с явным `implicitHeight`. У
|
||||
`Rectangle` `implicitHeight` всегда 0, и `Dialog` не смог бы посчитать свою высоту.
|
||||
|
||||
Корневой тип — `Window` размером 1280×720 px, видимое при старте. Это не переиспользуемый
|
||||
компонент, а точка входа приложения, поэтому раздел с примером использования здесь неприменим.
|
||||
## Функции
|
||||
|
||||
Раскладка держится на якорях относительно центральной кнопки запуска: список профилей — слева
|
||||
сверху от неё, кнопка активной сборки — справа сверху, кнопки папки и настроек — под списком
|
||||
профилей. Кнопка сезонных сборок стоит в правом нижнем углу: это единственная свободная часть
|
||||
окна, потому что панели хода работ висят слева, а всё остальное собрано вокруг кнопки запуска.
|
||||
**`showToast(text, color, timeout)`** — показать сообщение в плашке. `timeout` в миллисекундах,
|
||||
по умолчанию 4000. `0` значит «держать до следующего сообщения». Так показываются шаги запуска,
|
||||
чтобы сообщение не исчезало посреди долгой операции.
|
||||
|
||||
Панели загрузки и смены сборки имеют одни и те же якоря — они взаимоисключающи по построению:
|
||||
признак занятости бэкенда не даёт начать переключение во время установки и наоборот. Панель смены
|
||||
сборки объявлена неотменяемой: отступать после очистки `.minecraft` некуда, операцию нужно довести
|
||||
до конца.
|
||||
**`openMicrosoftLogin(url)`** — открыть окно входа Microsoft. Окно создаётся при первом вызове
|
||||
через `Qt.createComponent()` и хранится в `microsoftLoginDialog`. Объявить его прямо в файле
|
||||
нельзя: `MicrosoftLoginDialog.qml` есть в модуле только в сборках с WebEngine, и в остальных
|
||||
сломалось бы всё главное окно. Если компонент не создался, вход отменяется у бэкенда и показывается
|
||||
сообщение.
|
||||
|
||||
Заголовки и подвалы всех диалогов сделаны на `Item` с явным `implicitHeight`, а не на
|
||||
`Rectangle`: у прямоугольника `implicitHeight` равен нулю независимо от заданной высоты, и
|
||||
`Dialog` не смог бы вычислить свою полную высоту.
|
||||
**`formatMb(bytes)`** — байты в мегабайты с одним знаком после запятой, для панели загрузки.
|
||||
|
||||
## Свойства
|
||||
## Сигналы бэкенда
|
||||
|
||||
| Свойство | Тип | По умолчанию | Обязательное | Описание |
|
||||
|----------|-----|--------------|--------------|----------|
|
||||
| `microsoftLoginDialog` | `var` | `null` | Нет | Созданный по требованию экземпляр окна входа Microsoft либо `null`, пока вход ни разу не запускался. Хранится в свойстве, чтобы окно создавалось один раз за сеанс. |
|
||||
| Сигнал | Что делает окно |
|
||||
|--------|-----------------|
|
||||
| `launched` | зелёное сообщение «запущено» |
|
||||
| `launchProgress` | сообщение без таймаута |
|
||||
| `launchError` | красное сообщение на 8 секунд |
|
||||
| `twoFactorRequired` | открывает диалог кода |
|
||||
| `gameFinished` | сообщение о закрытии игры; если код не 0 или процесс упал, красное и с кодом |
|
||||
| `microsoftLoginUrlReady` | `openMicrosoftLogin()` |
|
||||
| `microsoftLoginSucceeded` | сообщение. Выбор в списке профилей не трогает: новый профиль уже выбран тем, кто его создал, а повторный вход мог быть не для последнего профиля |
|
||||
| `microsoftLoginFailed` | красное сообщение на 8 секунд |
|
||||
| `microsoftReloginRequired` | сразу `startMicrosoftLogin()` для этого профиля |
|
||||
| `gameOutput` | `console.log` |
|
||||
| `seasonalInstallFinished` | сообщение «сборка установлена, можно играть» |
|
||||
| `javaRuntimeInstalled` | обновляет строку Java в настройках |
|
||||
|
||||
### Внутренние диалоги и их состояние
|
||||
Кнопка запуска выключена, пока `busy` или `gameRunning`. Кнопка активной сборки выключена во время
|
||||
игры и смены сборки.
|
||||
|
||||
`Main.qml` объявляет четыре диалога прямо в файле. Их свойства — часть состояния главного окна.
|
||||
## Профили
|
||||
|
||||
**Диалог редактирования профиля** (`editProfileDialog`):
|
||||
Выпадающий список профилей переделан целиком. Наверху всплывающей панели закреплена кнопка
|
||||
«+ Добавить профиль», под ней список. У каждой строки при наведении появляются карандаш (открывает
|
||||
редактирование через `openFor()`) и корзина (`removeProfile()`).
|
||||
|
||||
| Свойство | Тип | По умолчанию | Описание |
|
||||
|----------|-----|--------------|----------|
|
||||
| `editIndex` | `int` | `-1` | Индекс редактируемого профиля; `-1` — диалог не открыт ни для кого. |
|
||||
| `msProfile` | `bool` | `false` | Открытый профиль имеет тип «Microsoft». |
|
||||
| `msLinked` | `bool` | `false` | У профиля есть действующая сессия Microsoft — от этого зависит строка статуса и подпись кнопки входа. |
|
||||
| `msName` | `string` | `""` | Ник, полученный при официальной авторизации. Показывается отдельным полем только для чтения, а не подменой поля логина: привязка сломалась бы первым же вводом в поле логина обычного профиля. |
|
||||
Типы входа во всех списках идут в одном порядке: 0 — `offline`, 1 — `elyby`, 2 — `microsoft`.
|
||||
Модель берётся из `Loc.t.profile.authTypes`, и порядок в каталоге важен, потому что код сравнивает
|
||||
`currentIndex` с 1 и 2. Без WebEngine третий пункт отрезается через `.slice(0, 2)`. В окне
|
||||
редактирования он остаётся, если профиль уже лицензионный: иначе в сборке без WebEngine такой
|
||||
профиль молча стал бы офлайновым.
|
||||
|
||||
**Диалог двухфакторной аутентификации** (`twoFactorDialog`):
|
||||
**Новый профиль.** `addProfile()`, новый профиль выбирается в списке. Если тип «Microsoft», сразу
|
||||
начинается вход, потому что без него такой профиль бесполезен. Скрытые поля при сохранении не
|
||||
читаются: в них мог остаться текст, набранный до смены типа.
|
||||
|
||||
| Свойство | Тип | По умолчанию | Описание |
|
||||
|----------|-----|--------------|----------|
|
||||
| `profileName` | `string` | `""` | Имя профиля, для которого запрошен код; подставляется в текст просьбы. |
|
||||
**Редактирование.** Дополнительные свойства диалога: `editIndex`, `msProfile`, `msLinked` (есть ли
|
||||
сессия Microsoft), `msName` (ник из аккаунта). Ник показывается отдельным полем только для чтения,
|
||||
а не подставляется в поле логина. Иначе привязка оборвалась бы при первом же вводе в это поле у
|
||||
обычного профиля. Кнопка входа сначала сохраняет профиль с типом `microsoft` через
|
||||
`updateProfile()` и только потом вызывает `startMicrosoftLogin()`. Тип могли переключить только
|
||||
что, и без этого токены достались бы профилю другого типа.
|
||||
|
||||
**Диалог настроек** (`settingsDialog`):
|
||||
## Двухфакторная аутентификация
|
||||
|
||||
| Свойство | Тип | По умолчанию | Описание |
|
||||
|----------|-----|--------------|----------|
|
||||
| `javaRuntimeId` | `string` | `""` | Выбранная сборка Java из папки лаунчера. Живёт в свойстве, а не в поле ввода: её выбирают в отдельном окне, а записывается она только по «Сохранить». Пустая строка означает «искать Java в системе». |
|
||||
| `javaRuntimeInfo` | `var` | `null` | Подробности выбранной сборки для строки поля. Не привязка: `javaRuntimeInfo()` — обычный вызов, и сам он не пересчитается, когда сборка докачается, поэтому значение обновляется по событиям. |
|
||||
Диалог показывает имя профиля (`profileName`) и поле для кода. «Подтвердить» вызывает
|
||||
`submitTwoFactorCode()`, «Отмена» — `cancelPendingLaunch()`. Если отмену не вызвать, запуск так и
|
||||
повиснет.
|
||||
|
||||
Типы профилей во всех выпадающих списках кодируются одинаково: позиция 0 — `offline`
|
||||
(офлайн, без пароля), 1 — `elyby` (Ely.by, с логином и паролем), 2 — `microsoft` (лицензия).
|
||||
Третья позиция показывается, только когда лаунчер собран с Qt WebEngine; в диалоге редактирования
|
||||
она показывается ещё и тогда, когда профиль уже сохранён как лицензионный — иначе в сборке без
|
||||
WebEngine он молча стал бы офлайновым.
|
||||
## Настройки
|
||||
|
||||
## Сигналы
|
||||
`settingsDialog.load()` читает `settings()` и `detectedJava()`, раскладывает значения по полям и
|
||||
открывает диалог. «Сохранить» собирает всё в одну карту и отдаёт в `updateSettings()`.
|
||||
|
||||
Собственных сигналов главное окно не объявляет.
|
||||
- **Язык** стоит первым, потому что относится ко всему приложению, а не к запуску игры. В модели
|
||||
переведённые подписи, рядом лежит непереводимый список `codes` (`system`, `ru`, `en`). Язык
|
||||
применяется по «Сохранить» и сразу, без перезапуска.
|
||||
- **Папка игры**, а под полем — путь, который получится на самом деле (`resolvedGameDir`). Путь
|
||||
хранится в свойстве, а не записывается в текст напрямую, иначе подпись не обновилась бы при смене
|
||||
языка.
|
||||
- **Java.** Поле открывает [JavaPickerDialog](JavaPickerDialog.md) с текущим выбором и требованием
|
||||
активной сборки (`requiredJavaMajor()`). Крестик сбрасывает выбор, и тогда Java ищется в системе.
|
||||
Выбор хранится в `javaRuntimeId` и записывается только по «Сохранить». `javaRuntimeInfo`
|
||||
обновляется при смене `javaRuntimeId` и по `javaRuntimeInstalled`, потому что
|
||||
`javaRuntimeInfo()` — обычный вызов, а не привязка. Если сборка выбрана, запуск идёт ею, а путь
|
||||
из соседнего поля остаётся запасным вариантом.
|
||||
- Память, размер окна, аргументы JVM, полноэкранный режим.
|
||||
- Ниже для справки список найденных в системе java.
|
||||
|
||||
## Методы
|
||||
|
||||
#### showToast(string text, color color, int timeout) : void
|
||||
|
||||
Показывает единую всплывающую плашку сообщений: через неё проходят сообщения о ходе запуска,
|
||||
ошибки и статус игры. Задаёт текст и цвет фона и перезапускает таймер скрытия.
|
||||
|
||||
Параметр `timeout` — время показа в миллисекундах. Пропущенное значение означает четыре секунды;
|
||||
`0` означает «держать до следующего сообщения» — так показываются промежуточные шаги запуска, чтобы
|
||||
сообщение не исчезало посреди долгой операции.
|
||||
|
||||
#### openMicrosoftLogin(url) : void
|
||||
|
||||
Открывает окно входа в аккаунт Microsoft на переданном адресе, создавая его при первом вызове.
|
||||
|
||||
Окно создаётся по требованию, а не вместе с главным: `MicrosoftLoginDialog.qml` попадает в модуль
|
||||
только в сборках с Qt WebEngine, и обычная декларация сломала бы всё главное окно в остальных.
|
||||
Поэтому компонент загружается через `Qt.createComponent()`, и если он не готов — сборка собрана без
|
||||
WebEngine, — вход отменяется у бэкенда, а пользователю показывается сообщение о том, что окно
|
||||
недоступно. При успешном создании окну сразу передаётся бэкенд, а его сигнал `failed`
|
||||
подключается к плашке сообщений.
|
||||
|
||||
#### formatMb(bytes) : string
|
||||
|
||||
Переводит байты в мегабайты с одним знаком после запятой. Используется в строке подробностей
|
||||
панели загрузки.
|
||||
|
||||
## Взаимодействие с другими компонентами
|
||||
|
||||
### Бэкенд
|
||||
|
||||
Единственный экземпляр `LauncherBackend` объявлен прямо в окне и передаётся всем вложенным
|
||||
диалогам через их свойство `backend`. Главное окно — единственное место, где обрабатываются его
|
||||
сигналы:
|
||||
|
||||
| Сигнал бэкенда | Что делает главное окно |
|
||||
|----------------|-------------------------|
|
||||
| `launched(profileName, buildName, serverUrl)` | показывает зелёное сообщение о запуске |
|
||||
| `launchProgress(message)` | показывает сообщение без таймаута — до следующего шага |
|
||||
| `launchError(message)` | показывает ошибку на восемь секунд |
|
||||
| `twoFactorRequired(profileName)` | открывает диалог ввода кода: Ely.by отклонил пароль с пометкой two factor, и код добирается здесь, чтобы продолжить прерванный запуск |
|
||||
| `gameFinished(exitCode, crashed)` | сообщает о закрытии игры; аварийное завершение показывается красным вместе с кодом выхода |
|
||||
| `microsoftLoginUrlReady(url)` | вызывает `openMicrosoftLogin()` |
|
||||
| `microsoftLoginSucceeded(playerName)` | сообщает об успешном входе. Выбор в списке профилей при этом не трогается: новый профиль уже выбран тем, кто его создал, а повторный вход мог быть и не в последний профиль |
|
||||
| `microsoftLoginFailed(message)` | показывает ошибку на восемь секунд |
|
||||
| `microsoftReloginRequired(profileIndex)` | сразу начинает вход заново для этого профиля |
|
||||
| `gameOutput(line)` | пишет строку в консоль |
|
||||
| `seasonalInstallFinished(seasonalId, buildName)` | сообщает, что сезонная сборка установлена и её можно запускать |
|
||||
| `javaRuntimeInstalled(runtimeId)` | обновляет подробности выбранной сборки Java в настройках |
|
||||
|
||||
Привязки к свойствам бэкенда управляют доступностью интерфейса: кнопка запуска выключена, пока
|
||||
лаунчер занят или игра уже идёт; кнопка активной сборки — пока идёт игра или переключение сборок;
|
||||
подпись на ней берётся из `activeBuildName`, а список профилей — из `profileNames`.
|
||||
|
||||
### Профили
|
||||
|
||||
Выпадающий список профилей переопределён целиком: кнопка «+ Добавить профиль» закреплена сверху
|
||||
всплывающей панели, под ней список, где у строки при наведении появляются карандаш и корзина.
|
||||
Карандаш открывает диалог редактирования (`openFor()` заполняет его через `profileAt()`), корзина
|
||||
вызывает `removeProfile()`.
|
||||
|
||||
Создание профиля вызывает `addProfile()`, выбирает новый профиль в списке и, если тип —
|
||||
«Microsoft», сразу начинает вход: такой профиль без входа бесполезен. Скрытые поля при сохранении
|
||||
не читаются — в них мог остаться текст, набранный до переключения типа профиля.
|
||||
|
||||
В диалоге редактирования кнопка входа перед вызовом `startMicrosoftLogin()` сначала сохраняет
|
||||
профиль вызовом `updateProfile()` с типом `microsoft`: тип мог быть только что переключён, и без
|
||||
этого бэкенд приписал бы токены профилю другого типа.
|
||||
|
||||
### Запуск игры
|
||||
|
||||
Кнопка запуска вызывает `launchGame()` с индексом выбранного профиля и индексом активной сборки.
|
||||
Дальше всё идёт через сигналы бэкенда: промежуточные шаги — в плашку сообщений, запрос кода
|
||||
двухфакторной аутентификации — в отдельный диалог, где подтверждение вызывает
|
||||
`submitTwoFactorCode()`, а отмена — `cancelPendingLaunch()`.
|
||||
|
||||
### Настройки
|
||||
|
||||
Диалог настроек открывается методом `load()`, который читает `settings()` бэкенда и раскладывает
|
||||
значения по полям, а также подставляет разрешённый путь папки игры и список найденных в системе
|
||||
сборок Java (`detectedJava()`). Сохранение собирает все поля в один `QVariantMap` и передаёт его
|
||||
в `updateSettings()`.
|
||||
|
||||
Первым пунктом диалога идёт выбор языка интерфейса — настройка уровня приложения, поэтому она
|
||||
стоит над параметрами запуска. Подписи в модели переводятся, а коды (`system`, `ru`, `en`) лежат
|
||||
рядом отдельным списком `codes` и не переводятся. Применяется язык по кнопке «Сохранить», как и
|
||||
всё остальное в этом диалоге, и сразу же, без перезапуска: см. [Localization](../cpp/Localization.md).
|
||||
|
||||
Разрешённый путь папки игры хранится свойством `resolvedGameDir` диалога, а не присваивается
|
||||
тексту напрямую — иначе подпись не пережила бы смену языка.
|
||||
|
||||
Поле выбора сборки Java открывает [JavaPickerDialog](JavaPickerDialog.md), передавая текущий выбор
|
||||
и требование активной сборки (`requiredJavaMajor()`); крестик справа сбрасывает выбор обратно на
|
||||
поиск Java в системе. Само окно выбора объявлено рядом с настройками, а не внутри них: оно шире и
|
||||
Окно выбора Java объявлено рядом с диалогом настроек, а не внутри него. Оно шире настроек и
|
||||
центрируется по окну лаунчера.
|
||||
|
||||
Выбранная сборка Java — общая настройка лаунчера: когда она задана, запуск идёт ею, а путь к Java
|
||||
из соседнего поля остаётся запасным вариантом.
|
||||
## Кнопки
|
||||
|
||||
### Тексты
|
||||
- папка — `openMinecraftFolder()` (открывает `mods`);
|
||||
- настройки — `settingsDialog.load()`;
|
||||
- сезонные сборки — `seasonalDialog.openCatalog()`;
|
||||
- активная сборка — `buildsDialog.open()`.
|
||||
|
||||
Все подписи, сообщения и подсказки окна берутся из синглтона `Loc`: `Loc.t.домен.вид.имя`.
|
||||
Ни одного текстового литерала в разметке не осталось, `qsTr` не используется. Модель типов входа
|
||||
в диалогах профиля — тоже ключ каталога (`Loc.t.profile.authTypes`), причём порядок значений
|
||||
в нём значим: код сравнивает `currentIndex` с 1 и 2, а вариант без Microsoft получается из той же
|
||||
модели через `.slice(0, 2)`. Подробности — в [Localization](../cpp/Localization.md).
|
||||
|
||||
### Прочие кнопки
|
||||
|
||||
Кнопка папки вызывает `openMinecraftFolder()`, кнопка настроек открывает диалог настроек, кнопка
|
||||
сезонных сборок — [SeasonalBuildsDialog](SeasonalBuildsDialog.md) методом `openCatalog()`, кнопка
|
||||
активной сборки — [BuildsDialog](BuildsDialog.md).
|
||||
Все тексты окна берутся из `Loc.t`, литералов в разметке нет.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in new issue
Block a user