2026-10-11 22:32:13 +03:00
# Main.qml
2026-09-03 09:16:56 +03:00
2026-10-11 22:32:13 +03:00
Главное окно, оно же точка входа: `main.cpp` загружает его через
`engine.loadFromModule("BiLauncher", "Main")` . Корень — `Window` 1280× 720.
2026-09-03 09:16:56 +03:00
2026-10-11 22:32:13 +03:00
Что в нём есть:
2026-09-03 09:16:56 +03:00
2026-10-11 22:32:13 +03:00
- единственный экземпляр `LauncherBackend` и все обработчики его сигналов;
- экран запуска: фон, кнопка игры по центру, список профилей, кнопка активной сборки, кнопки папки
модов, настроек и сезонных сборок;
- плашка сообщений и две панели прогресса ([ProgressPanel ](ProgressPanel.md ));
- диалоги, которые не вынесены в отдельные файлы: новый профиль, редактирование профиля, код
двухфакторной аутентификации, настройки запуска.
Отдельными файлами сделаны [BuildsDialog ](BuildsDialog.md ),
[SeasonalBuildsDialog ](SeasonalBuildsDialog.md ), [JavaPickerDialog ](JavaPickerDialog.md ) и
[MicrosoftLoginDialog ](MicrosoftLoginDialog.md ). Окно передаёт им бэкенд через свойство `backend` .
Стиль Qt Quick Controls — `Basic` (выставлен в `main.cpp` ), и почти у каждого контрола здесь своё
оформление.
## Раскладка
Всё стоит на якорях вокруг кнопки запуска: список профилей слева сверху от неё, кнопка активной
сборки справа сверху, кнопки папки и настроек под списком профилей. Кнопка сезонных сборок стоит в
правом нижнем углу, потому что остальные углы заняты.
2026-10-23 09:55:15 +03:00
В левом верхнем углу фона стоит подпись `launcherVersion` — «Версия: 1.0.1-dev». Текст из
`app.label.version` , номер из `Qt.application.version` , то есть из `LAUNCHER_VERSION`
(см. [main.cpp ](../cpp/main.md )).
2026-10-11 22:32:13 +03:00
Обе панели прогресса (загрузки и смены сборки) стоят в левом нижнем углу на одних и тех же якорях.
Одновременно они не появляются: пока лаунчер занят загрузкой, бэкенд не начнёт смену сборки, и
наоборот. Панель смены сборки отменить нельзя.
У заголовков и подвалов всех диалогов корнем служит `Item` с явным `implicitHeight` . У
`Rectangle` `implicitHeight` всегда 0, и `Dialog` не смог бы посчитать свою высоту.
## Функции
** `showToast(text, color, timeout)` ** — показать сообщение в плашке. `timeout` в миллисекундах,
по умолчанию 4000. `0` значит «держать до следующего сообщения». Так показываются шаги запуска,
чтобы сообщение не исчезало посреди долгой операции.
** `openMicrosoftLogin(url)` ** — открыть окно входа Microsoft. Окно создаётся при первом вызове
через `Qt.createComponent()` и хранится в `microsoftLoginDialog` . Объявить его прямо в файле
нельзя: `MicrosoftLoginDialog.qml` есть в модуле только в сборках с WebEngine, и в остальных
сломалось бы всё главное окно. Если компонент не создался, вход отменяется у бэкенда и показывается
сообщение.
** `formatMb(bytes)` ** — байты в мегабайты с одним знаком после запятой, для панели загрузки.
## Сигналы бэкенда
| Сигнал | Что делает окно |
|--------|-----------------|
| `launched` | зелёное сообщение «запущено» |
| `launchProgress` | сообщение без таймаута |
| `launchError` | красное сообщение на 8 секунд |
| `twoFactorRequired` | открывает диалог кода |
| `gameFinished` | сообщение о закрытии игры; если код не 0 или процесс упал, красное и с кодом |
| `microsoftLoginUrlReady` | `openMicrosoftLogin()` |
| `microsoftLoginSucceeded` | сообщение. Выбор в списке профилей не трогает: новый профиль уже выбран тем, кто его создал, а повторный вход мог быть не для последнего профиля |
| `microsoftLoginFailed` | красное сообщение на 8 секунд |
| `microsoftReloginRequired` | сразу `startMicrosoftLogin()` для этого профиля |
| `gameOutput` | `console.log` |
| `seasonalInstallFinished` | сообщение «сборка установлена, можно играть» |
| `javaRuntimeInstalled` | обновляет строку Java в настройках |
2026-10-20 17:42:45 +03:00
Кнопка запуска выключена, пока `busy` или `gameRunning` . Список профилей выключен, пока `busy` ;
кнопка активной сборки — пока `busy` или `gameRunning` . Так во время загрузки и установки (в том
числе сезонной сборки) доступна только отмена на панели загрузки.
2026-10-11 22:32:13 +03:00
## Профили
Выпадающий список профилей переделан целиком. Наверху всплывающей панели закреплена кнопка
«+ Добавить профиль», под ней список. У каждой строки при наведении появляются карандаш (открывает
редактирование через `openFor()` ) и корзина (`removeProfile()` ).
Типы входа во всех списках идут в одном порядке: 0 — `offline` , 1 — `elyby` , 2 — `microsoft` .
Модель берётся из `Loc.t.profile.authTypes` , и порядок в каталоге важен, потому что код сравнивает
`currentIndex` с 1 и 2. Без WebEngine третий пункт отрезается через `.slice(0, 2)` . В окне
редактирования он остаётся, если профиль уже лицензионный: иначе в сборке без WebEngine такой
профиль молча стал бы офлайновым.
**Новый профиль.** `addProfile()` , новый профиль выбирается в списке. Если тип «Microsoft», сразу
начинается вход, потому что без него такой профиль бесполезен. Скрытые поля при сохранении не
читаются: в них мог остаться текст, набранный до смены типа.
**Редактирование.** Дополнительные свойства диалога: `editIndex` , `msProfile` , `msLinked` (есть ли
сессия Microsoft), `msName` (ник из аккаунта). Ник показывается отдельным полем только для чтения,
а не подставляется в поле логина. Иначе привязка оборвалась бы при первом же вводе в это поле у
обычного профиля. Кнопка входа сначала сохраняет профиль с типом `microsoft` через
`updateProfile()` и только потом вызывает `startMicrosoftLogin()` . Тип могли переключить только
что, и без этого токены достались бы профилю другого типа.
## Двухфакторная аутентификация
Диалог показывает имя профиля (`profileName` ) и поле для кода. «Подтвердить» вызывает
`submitTwoFactorCode()` , «Отмена» — `cancelPendingLaunch()` . Если отмену не вызвать, запуск так и
повиснет.
## Настройки
`settingsDialog.load()` читает `settings()` и `detectedJava()` , раскладывает значения по полям и
открывает диалог. «Сохранить» собирает всё в одну карту и отдаёт в `updateSettings()` .
- **Язык** стоит первым, потому что относится ко всему приложению, а не к запуску игры. В модели
переведённые подписи, рядом лежит непереводимый список `codes` (`system` , `ru` , `en` ). Язык
применяется по «Сохранить» и сразу, без перезапуска.
- **Папка игры**, а под полем — путь, который получится на самом деле (`resolvedGameDir` ). Путь
хранится в свойстве, а не записывается в текст напрямую, иначе подпись не обновилась бы при смене
языка.
- **Java.** Поле открывает [JavaPickerDialog ](JavaPickerDialog.md ) с текущим выбором и требованием
активной сборки (`requiredJavaMajor()` ). Крестик сбрасывает выбор, и тогда Java ищется в системе.
Выбор хранится в `javaRuntimeId` и записывается только по «Сохранить». `javaRuntimeInfo`
обновляется при смене `javaRuntimeId` и по `javaRuntimeInstalled` , потому что
`javaRuntimeInfo()` — обычный вызов, а не привязка. Если сборка выбрана, запуск идёт ею, а путь
из соседнего поля остаётся запасным вариантом.
- Память, размер окна, аргументы JVM, полноэкранный режим.
- Ниже для справки список найденных в системе java.
Окно выбора Java объявлено рядом с диалогом настроек, а не внутри него. Оно шире настроек и
2026-09-03 09:16:56 +03:00
центрируется по окну лаунчера.
2026-10-11 22:32:13 +03:00
## Кнопки
2026-09-03 09:16:56 +03:00
2026-10-11 22:32:13 +03:00
- папка — `openMinecraftFolder()` (открывает `mods` );
- настройки — `settingsDialog.load()` ;
- сезонные сборки — `seasonalDialog.openCatalog()` ;
- активная сборка — `buildsDialog.open()` .
2026-09-03 09:16:56 +03:00
2026-10-11 22:32:13 +03:00
Все тексты окна берутся из `Loc.t` , литералов в разметке нет.
2026-09-03 09:16:56 +03:00
---
При создании этого документа использовался ИИ.