# Main ## Обзор компонента `Minecraft_launcher` — десктопный лаунчер Minecraft на Qt Quick. `Main.qml` — его главное и единственное настоящее окно: точка входа приложения, которую загружает `main.cpp` вызовом `engine.loadFromModule("Minecraft_launcher", "Main")`. Окно совмещает четыре роли. Оно держит единственный экземпляр `LauncherBackend` — весь остальной интерфейс получает его от главного окна. Оно рисует сам экран запуска: фоновая картинка, большая кнопка игры по центру, выпадающий список профилей, кнопка активной сборки, кнопки папки модов, настроек и сезонных сборок. Оно показывает обратную связь — всплывающую плашку сообщений и две панели хода долгих операций. И наконец, оно объявляет диалоги, которые не вынесены в отдельные файлы: создание и редактирование профиля, ввод кода двухфакторной аутентификации и настройки запуска. ## Место в проекте и зависимости Импортирует `QtQuick`, `QtQuick.Layouts 2.15`, `QtQuick.Controls 2.15` и сам QML-модуль проекта `Minecraft_launcher`, из которого приходит тип `LauncherBackend`. Инстанцирует четыре компонента модуля: [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`. ## Иерархия и роль Корневой тип — `Window` размером 1280×720 px, видимое при старте. Это не переиспользуемый компонент, а точка входа приложения, поэтому раздел с примером использования здесь неприменим. Раскладка держится на якорях относительно центральной кнопки запуска: список профилей — слева сверху от неё, кнопка активной сборки — справа сверху, кнопки папки и настроек — под списком профилей. Кнопка сезонных сборок стоит в правом нижнем углу: это единственная свободная часть окна, потому что панели хода работ висят слева, а всё остальное собрано вокруг кнопки запуска. Панели загрузки и смены сборки имеют одни и те же якоря — они взаимоисключающи по построению: признак занятости бэкенда не даёт начать переключение во время установки и наоборот. Панель смены сборки объявлена неотменяемой: отступать после очистки `.minecraft` некуда, операцию нужно довести до конца. Заголовки и подвалы всех диалогов сделаны на `Item` с явным `implicitHeight`, а не на `Rectangle`: у прямоугольника `implicitHeight` равен нулю независимо от заданной высоты, и `Dialog` не смог бы вычислить свою полную высоту. ## Свойства | Свойство | Тип | По умолчанию | Обязательное | Описание | |----------|-----|--------------|--------------|----------| | `microsoftLoginDialog` | `var` | `null` | Нет | Созданный по требованию экземпляр окна входа Microsoft либо `null`, пока вход ни разу не запускался. Хранится в свойстве, чтобы окно создавалось один раз за сеанс. | ### Внутренние диалоги и их состояние `Main.qml` объявляет четыре диалога прямо в файле. Их свойства — часть состояния главного окна. **Диалог редактирования профиля** (`editProfileDialog`): | Свойство | Тип | По умолчанию | Описание | |----------|-----|--------------|----------| | `editIndex` | `int` | `-1` | Индекс редактируемого профиля; `-1` — диалог не открыт ни для кого. | | `msProfile` | `bool` | `false` | Открытый профиль имеет тип «Microsoft». | | `msLinked` | `bool` | `false` | У профиля есть действующая сессия Microsoft — от этого зависит строка статуса и подпись кнопки входа. | | `msName` | `string` | `""` | Ник, полученный при официальной авторизации. Показывается отдельным полем только для чтения, а не подменой поля логина: привязка сломалась бы первым же вводом в поле логина обычного профиля. | **Диалог двухфакторной аутентификации** (`twoFactorDialog`): | Свойство | Тип | По умолчанию | Описание | |----------|-----|--------------|----------| | `profileName` | `string` | `""` | Имя профиля, для которого запрошен код; подставляется в текст просьбы. | **Диалог настроек** (`settingsDialog`): | Свойство | Тип | По умолчанию | Описание | |----------|-----|--------------|----------| | `javaRuntimeId` | `string` | `""` | Выбранная сборка Java из папки лаунчера. Живёт в свойстве, а не в поле ввода: её выбирают в отдельном окне, а записывается она только по «Сохранить». Пустая строка означает «искать Java в системе». | | `javaRuntimeInfo` | `var` | `null` | Подробности выбранной сборки для строки поля. Не привязка: `javaRuntimeInfo()` — обычный вызов, и сам он не пересчитается, когда сборка докачается, поэтому значение обновляется по событиям. | Типы профилей во всех выпадающих списках кодируются одинаково: позиция 0 — `offline` (офлайн, без пароля), 1 — `elyby` (Ely.by, с логином и паролем), 2 — `microsoft` (лицензия). Третья позиция показывается, только когда лаунчер собран с Qt WebEngine; в диалоге редактирования она показывается ещё и тогда, когда профиль уже сохранён как лицензионный — иначе в сборке без WebEngine он молча стал бы офлайновым. ## Сигналы Собственных сигналов главное окно не объявляет. ## Методы #### 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 из соседнего поля остаётся запасным вариантом. ### Тексты Все подписи, сообщения и подсказки окна берутся из синглтона `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). --- При создании этого документа использовался ИИ.