new docs for project

This commit is contained in:
2026-09-03 09:16:56 +03:00
parent f1a840174b
commit 00e7c957e4
34 changed files with 5323 additions and 0 deletions
+179
View File
@@ -0,0 +1,179 @@
# BuildsDialog
## Обзор компонента
`Minecraft_launcher` — десктопный лаунчер Minecraft на Qt Quick. Сборка в нём — это именованный
набор «версия игры + модлоадер + адрес сервера» вместе с собственным содержимым `.minecraft`:
модами, конфигами и мирами. Активная сборка одна, её содержимое лежит в `.minecraft`, остальные
хранятся в архивах и разворачиваются при переключении.
`BuildsDialog` — окно управления этими сборками: слева список со сменой активной, справа карточка
выбранной — имя, сервер, версия Minecraft и модлоадеры. Отсюда же сборка ставится (скачивание
версии игры и модлоадера) и удаляется.
Карточка сохраняет правки по ходу редактирования, отдельной кнопки «Сохранить» нет: иначе
появляется неочевидное несохранённое состояние, пока пользователь переключается между сборками в
левом списке.
## Место в проекте и зависимости
В начале файла объявлена `pragma ComponentBehavior: Bound`. Импортирует `QtQuick`,
`QtQuick.Controls 2.15` и `QtQuick.Layouts 2.15`.
Использует три компонента из того же QML-модуля:
- [LabelledField](LabelledField.md) — поля названия сборки и адреса сервера;
- [LoaderRow](LoaderRow.md) — по одной строке на каждый из четырёх модлоадеров;
- [VersionPickerDialog](VersionPickerDialog.md) — вложенное окно выбора версии Minecraft.
Работает с C++-типом `LauncherBackend` (`launcherbackend.h`, `QML_ELEMENT` в модуле
`Minecraft_launcher`) через свойство `backend`. Читает свойства `customBuildNames`,
`activeBuildIndex`, `switching` и `busy`; вызывает `customBuildAt()`, `addCustomBuild()`,
`updateCustomBuild()`, `customBuildRemovalInfo()`, `removeCustomBuild()`, `checkInstallation()`,
`installCustomBuild()` и `refreshVersionCatalog()`; слушает сигнал `installedVersionsChanged`.
Объявлен в `QML_FILES` модуля (`CMakeLists.txt`); использует ресурс `images/Trash.svg`.
Инстанцируется в [Main](Main.md).
## Иерархия и роль
Корневой тип — `Dialog`: модальный, 880×600 px, нулевой внутренний отступ, тёмный фон со
скруглением и акцентной рамкой. Пока идёт смена активной сборки (`backend.switching`), окно
закрывается только кнопкой: архивация и распаковка `.minecraft` не должны прерываться случайным
щелчком мимо.
Содержимое — `RowLayout` из двух частей:
- **Слева** (260 px) список сборок и строка «+ Новая сборка» под ним. Строка списка показывает имя,
пометку «активна» у активной сборки и корзину, появляющуюся при наведении. Вся колонка
выключается на время смены активной сборки.
- **Справа** карточка выбранной сборки внутри `Flickable` — она может не поместиться по высоте.
Карточка выключается и приглушается, когда сборка не выбрана или идёт переключение.
Карточка сверху вниз: название, адрес сервера, поле версии Minecraft, панель модлоадеров и строка
состояния комплектности. Поле версии — не выпадающий список, а прямоугольник, который только
показывает выбор и открывает отдельное окно: версий около тысячи, и разбираться в них удобнее в
окне с категориями.
Отдельно объявлен вложенный `Dialog` подтверждения удаления шириной 420 px с красной рамкой,
привязанный к тому же родителю, что и само окно.
Подвал несёт три кнопки: «Установить», «Сделать активной» и «Закрыть».
## Свойства
| Свойство | Тип | По умолчанию | Обязательное | Описание |
|----------|-----|--------------|--------------|----------|
| `backend` | `var` | — | **Да** | Экземпляр `LauncherBackend` — хранилище сборок и исполнитель установки, удаления и переключения. |
| `editIndex` | `int` | `-1` | Нет | Индекс сборки, открытой в карточке справа. Значение `-1` означает, что карточка пуста и выключена. С активной сборкой не связан: активную меняет отдельная кнопка, чтобы случайный клик по списку не запускал архивацию `.minecraft`. |
| `loading` | `bool` | `false` | Нет | Признак того, что карточка сейчас заполняется данными сборки. Пока он выставлен, обработчики полей не должны писать обратно — иначе открытие сборки немедленно перезаписывало бы её. |
### Внутренняя панель модлоадеров
Панель `loaderPanel` — это `Column` с четырьмя строками [LoaderRow](LoaderRow.md) и собственным
маленьким API. Лоадеры несовместимы между собой: игра запускается ровно с одним профилем
`versions/<id>`, поэтому отметка одного снимает остальные, а «ничего не отмечено» — это чистая
ваниль.
| Свойство панели | Тип | Описание |
|-----------------|-----|----------|
| `rows` | `var` | Массив из четырёх строк лоадеров в порядке показа: Minecraft Forge (`forge`), Fabric Loader (`fabric`), NeoForge (`neoforge`), Quilt Loader (`quilt`). |
Функции панели: `applyBuild(loader, loaderVersion)` раздаёт данные сборки всем строкам;
`selectedRow()` возвращает отмеченную строку или `null`; `keepOnly(row)` очищает все строки,
кроме переданной; `commitSelection()` сохраняет выбор в сборку, записывая ключ лоадера, его версию
и пустой `resolvedVersionId` — профиль появится только после установки, а до неё сборка
запускается на чистой ванили.
## Сигналы
Собственных сигналов компонент не объявляет: все изменения уходят прямо в бэкенд, и о них
остальной интерфейс узнаёт по его сигналам.
## Методы
#### selectBuild(int index) : void
Открывает сборку с указанным индексом в карточке. Читает данные через `customBuildAt()`,
выставляет `loading` на время заполнения полей, раздаёт лоадеры панели через `applyBuild()` и в
конце обновляет строку состояния.
#### askRemove(int index) : void
Спрашивает подтверждение перед удалением: оно необратимо и уносит с собой архив сборки. Запрашивает
`customBuildRemovalInfo()` и наполняет окно подтверждения именем сборки и тремя признаками — есть
ли у неё архив, активна ли она сейчас и последняя ли она. Индекс запоминается в самом окне
подтверждения, потому что к моменту ответа строка списка под курсором может быть уже другой.
Окно подтверждения объясняет последствия по этим признакам: вместе со сборкой удалится её архив, и
моды, конфиги и миры восстановить будет нельзя; активная сборка владеет содержимым `.minecraft`,
оно будет очищено, а на его место развернётся следующая сборка; для последней сборки содержимое
`.minecraft` остаётся на месте.
#### performRemove(int index) : void
Удаляет сборку через бэкенд и восстанавливает состояние карточки: если сборок не осталось,
сбрасывает `editIndex` в `-1`, иначе открывает соседнюю — ту же позицию или последнюю оставшуюся.
#### commit(var fields) : void
Сохраняет часть полей сборки: передаёт `QVariantMap` в `updateCustomBuild()` и обновляет строку
состояния. Ничего не делает во время заполнения карточки (`loading`) и при пустом `editIndex`
это и есть защита от записи при открытии сборки.
Вызывается по завершении правки каждого поля: имени, адреса сервера, версии Minecraft и выбора
модлоадера.
#### refreshStatus() : void
Пересчитывает строку комплектности под карточкой. Спрашивает у бэкенда `checkInstallation()` и
показывает либо сообщение о готовности к запуску, либо число недостающих файлов вместе с первым из
них. При пустом `editIndex` очищает строку.
#### newBuildName() : string
Придумывает имя для новой сборки: перебирает «Сборка 1», «Сборка 2» и так далее, пока не найдёт
свободное среди существующих имён.
## Взаимодействие с другими компонентами
**Со стороны родителя.** [Main](Main.md) задаёт `backend` и открывает окно. Начальное состояние
окно выбирает само в обработчике `onAboutToShow`: просит обновить каталог версий и открывает
активную сборку, а при пустом списке оставляет карточку выключенной.
**Внутрь — к вложенным компонентам.** Поля [LabelledField](LabelledField.md) сообщают о правке
сигналом `editingFinished`, и карточка сразу вызывает `commit()` с обрезанным по краям значением.
Поле версии открывает [VersionPickerDialog](VersionPickerDialog.md) вызовом `openFor()` и получает
результат сигналом `versionChosen`; запись выбранной версии сама вызывает `commit()` через
обработчик изменения. Строки [LoaderRow](LoaderRow.md) получают `gameVersion` привязкой к
выбранной версии игры, поэтому смена версии Minecraft автоматически перезапрашивает списки версий
лоадеров.
**Наружу — к бэкенду.** Кнопка «Установить» вызывает `installCustomBuild()`, кнопка «Сделать
активной» пишет в свойство `activeBuildIndex`. Обе выключены, пока лаунчер занят (`busy`), а
кнопка активации — ещё и когда выбранная сборка уже активна. Ход установки и переключения
показывает плашка [ProgressPanel](ProgressPanel.md) главного окна, а не это окно.
**Обратная связь от бэкенда.** Окно подписано на `installedVersionsChanged` через `Connections`:
установка версии или модлоадера меняет комплектность сборки, и строка состояния должна это
заметить, не дожидаясь переоткрытия окна.
## Пример использования
```qml
BuildsDialog {
id: buildsDialog
parent: Overlay.overlay
anchors.centerIn: parent
backend: launcherBackend
}
Button {
text: qsTr("Сборки")
onClicked: buildsDialog.open()
}
```
---
При создании этого документа использовался ИИ.
+89
View File
@@ -0,0 +1,89 @@
# DarkCombo
## Обзор компонента
`Minecraft_launcher` — десктопный лаунчер Minecraft на Qt Quick. Всё окно оформлено вручную в
тёмной теме, а стиль Qt Quick Controls принудительно выставлен в `Basic` (`main.cpp`), потому что
нативные стили игнорируют пользовательские `contentItem` и `background`. Из-за этого каждый
стандартный элемент управления, попадающий в интерфейс, приходится переопределять самому.
`DarkCombo` — как раз такое переопределение: выпадающий список в общем тёмном стиле окна.
Компонент не добавляет логики, он целиком про внешний вид — фон, рамку, стрелку, делегат строки и
всплывающую панель. К нему обращаются всюду, где нужен выпадающий список внутри диалогов: выбор
версии модлоадера, выбор Java, поля в настройках.
## Место в проекте и зависимости
Импортирует `QtQuick` и `QtQuick.Controls 2.15`; собственных C++-типов не использует.
Объявлен в `QML_FILES` модуля `Minecraft_launcher` (см. `CMakeLists.txt`), поэтому доступен по
имени `DarkCombo` в любом файле того же модуля без явного импорта.
Используется в [LoaderRow](LoaderRow.md) — список версий модлоадера — и дважды в [Main](Main.md):
в выпадающих списках профиля и сборки на главном окне.
Компонент ссылается на ресурсы `images/Profile_Box/Asset_23.svg` и
`images/Profile_Box/Asset_24.svg` — они перечислены в `RESOURCES` того же QML-модуля, отдельного
подключения не требуют.
## Иерархия и роль
Корневой тип — `ComboBox` из Qt Quick Controls. Всё поведение (модель, `currentIndex`,
`activated`, `displayText`, клавиатурная навигация) наследуется без изменений; `DarkCombo`
переопределяет только четыре визуальных слота базового типа:
| Слот | Что даёт `DarkCombo` |
|------|----------------------|
| `indicator` | стрелка из SVG-ресурса вместо двойного шеврона Basic-стиля; при открытом списке (`down`) картинка меняется |
| `contentItem` | текст текущего значения белым, с обрезкой справа многоточием и отступом под стрелку |
| `background` | тёмный прямоугольник со скруглением 6 px; рамка подсвечивается акцентным цветом при фокусе |
| `delegate` | строка списка: белый текст, подсветка фона у элемента под курсором |
| `popup` | всплывающая панель шириной с сам комбобокс, высотой не более 220 px, с вертикальным индикатором прокрутки |
## Свойства
Собственных свойств компонент не объявляет — доступен весь набор свойств `ComboBox`
(`model`, `currentIndex`, `currentText`, `displayText`, `editable` и прочие).
Внутри `delegate` объявлены два обязательных свойства делегата, они относятся к строке списка,
а не к самому комбобоксу:
| Свойство | Тип | По умолчанию | Обязательное | Описание |
|----------|-----|--------------|--------------|----------|
| `modelData` | `var` | — | **Да** | Значение строки модели; выводится текстом в строке списка. Модель ожидается плоской — списком строк, а не объектов. |
| `index` | `int` | — | **Да** | Позиция строки; сравнивается с `highlightedIndex` комбобокса, чтобы подсветить строку под курсором. |
## Сигналы
Собственных сигналов нет. Наследуются сигналы `ComboBox`, из которых на практике используется
`activated(int index)` — выбор строки пользователем (в отличие от `currentIndexChanged`, он не
срабатывает при программной смене значения).
## Методы
Собственных функций нет.
## Взаимодействие с другими компонентами
Компонент самодостаточен и ничего не знает ни о бэкенде, ни о родителе: модель приходит извне
через `model`, результат выбора родитель получает через унаследованный `activated`. Так,
[LoaderRow](LoaderRow.md) передаёт в `model` список подписей версий модлоадера и в обработчике
`activated` переводит индекс обратно в номер версии.
Поскольку модель ожидается списком строк, вызывающий код обычно сам приводит массив объектов к
массиву подписей перед присваиванием.
## Пример использования
```qml
DarkCombo {
width: 200
height: 32
model: ["1.21.1", "1.20.6", "1.20.4"]
onActivated: (index) => console.log("выбрано:", model[index])
}
```
---
При создании этого документа использовался ИИ.
+149
View File
@@ -0,0 +1,149 @@
# JavaPickerDialog
## Обзор компонента
`Minecraft_launcher` — десктопный лаунчер Minecraft на Qt Quick. Разным версиям игры нужны разные
версии Java, и лаунчер умеет скачивать их сам: официальные сборки Mojang и сборки Temurin в двух
вариантах — JDK и JRE. Держать в голове, какая Java нужна какой версии игры, пользователь не
обязан.
`JavaPickerDialog` — окно выбора сборки Java: слева типы сборок, справа сами версии с поиском.
Устроено так же, как [VersionPickerDialog](VersionPickerDialog.md), с одним принципиальным
отличием: выбранное здесь ещё и качается. У версий игры загрузку начинает сама сборка, а сборка
Java, которой нет на диске, запуску ничем не поможет — поэтому кнопка подтверждения при
необходимости сразу ставит выбранную сборку в очередь на скачивание.
Окно также показывает, какая версия Java нужна выбранной версии игры, помечает сборки, которые
для неё слишком старые, и позволяет удалять скачанное прямо из списка.
## Место в проекте и зависимости
В начале файла объявлена `pragma ComponentBehavior: Bound`. Импортирует `QtQuick`,
`QtQuick.Controls 2.15` и `QtQuick.Layouts 2.15`.
Работает с C++-типом `LauncherBackend` (`launcherbackend.h`, `QML_ELEMENT` в модуле
`Minecraft_launcher`), передаваемым снаружи в свойство `backend`. Читает свойства `javaCatalog` и
`javaCatalogLoading`, вызывает `refreshJavaCatalog()`, `installJavaRuntime()` и
`removeJavaRuntime()`.
Объявлен в `QML_FILES` модуля (`CMakeLists.txt`); использует ресурс `images/Trash.svg`.
Инстанцируется в диалоге настроек внутри [Main](Main.md) — сборка Java выбирается один раз для
всего лаунчера, а не отдельно для каждой сборки Minecraft.
Каталог приходит из C++ уже отсортированным — новые сверху, скачанные первыми, — поэтому окно
только отбирает записи и порядок не трогает. Запись каталога содержит поля `id`, `label`, `kind`
(тип сборки), `major` (мажорная версия Java числом), `installed`, `downloadable`, `lts`, `sizeMb`,
`detail`, `coverage` и `search`.
## Иерархия и роль
Корневой тип — `Dialog`: модальный, 720×480 px, нулевой внутренний отступ, закрывается по Escape и
щелчку мимо, оформление тёмное со скруглением и акцентной рамкой.
Содержимое — `RowLayout` из колонки типов шириной 170 px и области версий, разделённых линией.
Каждый пункт колонки типов показывает название и пояснение мелким шрифтом, а под списком типов —
подсказка о требовании выбранной версии игры, видимая только когда это требование известно.
Область версий повторяет устройство окна выбора версии Minecraft: поле поиска сверху, флажок
«Только скачанные» снизу, `ListView` между ними. Строка списка выше обычной (44 px), потому что
содержит две строки текста: подпись сборки с меткой LTS и строку подробностей, где через точку
собраны описание, покрытие версий игры, размер в мегабайтах и — при необходимости —
предупреждение о том, что сборки не хватит.
Пустое состояние объясняется текстом по центру и различает загрузку каталога, отсутствие
соединения и пустой результат поиска.
## Свойства
| Свойство | Тип | По умолчанию | Обязательное | Описание |
|----------|-----|--------------|--------------|----------|
| `backend` | `var` | — | **Да** | Экземпляр `LauncherBackend` — источник каталога Java, исполнитель установки и удаления. |
| `selectedId` | `string` | `""` | Нет | Идентификатор выбранной сборки. Окно открывается с текущей сборкой, и по «Отмене» выбор возвращается к ней. |
| `category` | `string` | `"java"` | Нет | Ключ активного типа сборок. Допустимые значения: `java` (сборки Mojang), `jdk` (Temurin с инструментами), `jre` (Temurin, только запуск). |
| `filterText` | `string` | `""` | Нет | Текст поиска; сравнивается в нижнем регистре с полем `search` записи внутри активного типа. |
| `installedOnly` | `bool` | `false` | Нет | Показывать только скачанные сборки. |
| `requiredMajor` | `int` | `0` | Нет | Мажорная версия Java, ниже которой выбранной версии игры не запуститься. Значение `0` означает, что версия игры не выбрана и предупреждать не о чем: подсказка в колонке типов скрывается, пометка «слишком старая» не ставится. |
| `categories` | `var` (только чтение) | список из трёх записей | Нет | Описание колонки типов: массив объектов с полями `key`, `title` и `hint`. Задаёт порядок пунктов и набор допустимых значений `category`. |
| `visibleEntries` | `var` (только чтение) | вычисляется | Нет | Отобранные строки каталога: записи активного типа, прошедшие флажок «только скачанные» и текст поиска. Модель списка. |
| `selectedEntry` | `var` (только чтение) | вычисляется | Нет | Полная запись выбранной сборки из каталога или `null`, если ничего не выбрано. Ищется по всему каталогу, а не по видимым строкам, поэтому выбор не теряется при смене фильтра. От неё зависят подпись в подвале и надпись на кнопке подтверждения. |
## Сигналы
#### runtimeChosen(string runtimeId)
Сборка Java подтверждена — кнопкой, двойным щелчком по строке или клавишей Enter в поле поиска. В
параметре приходит идентификатор сборки.
Обработчик записывает выбранную сборку в настройки лаунчера — сборка Java общая для всех сборок
Minecraft, а требование конкретной версии игры влияет только на пометки в списке. Сигнал испускается и для ещё не скачанной сборки — загрузка при
этом начинается сама, и обработчику ждать её завершения не нужно.
## Методы
#### openFor(string runtimeId, int required) : void
Открывает окно на переданной сборке. Запоминает её в `selectedId`, выставляет `requiredMajor` из
второго аргумента (отсутствующее или нулевое значение означает «требование неизвестно»), очищает
поиск, переключается на тип именно этой сборки, просит бэкенд обновить каталог и прокручивает
список к выбранной строке.
#### categoryOf(string runtimeId) : string
Возвращает тип сборки по каталогу; для неизвестного идентификатора — `java`.
#### indexOfSelected() : int
Позиция выбранной сборки в `visibleEntries` или `-1`, если под текущим фильтром её не видно.
#### revealSelected() : void
Выставляет текущий индекс списка на выбранную сборку и прокручивает список так, чтобы строка
оказалась по центру.
#### acceptSelection() : void
Подтверждает выбор. Ничего не делает, если `selectedEntry` пуст. Иначе испускает
`runtimeChosen()`, а затем — если сборка не установлена, но доступна для скачивания, — вызывает
`installJavaRuntime()` у бэкенда и закрывает окно. Именно поэтому кнопка подтверждения называется
«Скачать» для отсутствующей сборки и «Выбрать» для уже скачанной.
## Взаимодействие с другими компонентами
**Со стороны родителя.** Вызывающий код задаёт `backend`, открывает окно вызовом `openFor()` с
текущей сборкой и требуемой мажорной версией Java (её отдаёт `requiredJavaMajor()` бэкенда) и
подписывается на `runtimeChosen()`.
**Со стороны бэкенда.** `javaCatalog` и `javaCatalogLoading` — привязки, пересчитывающие модель и
текст пустого состояния при каждом обновлении каталога. `refreshJavaCatalog()` вызывается при
открытии окна, `installJavaRuntime()` — при подтверждении отсутствующей сборки,
`removeJavaRuntime()` — по щелчку на корзине в строке. Ход самой загрузки окно не показывает: за
это отвечает плашка [ProgressPanel](ProgressPanel.md) в главном окне.
**Удаление.** Корзина в строке доступна только у скачанных сборок; сборки весят по двести
мегабайт, и удалять их нужно прямо здесь, иначе папка лаунчера растёт молча. Если удалена была
выбранная сборка, выбор снимается.
**Слишком старые сборки.** Сборка ниже требования игры остаётся доступной для выбора, но
помечается в строке подробностей: она может пригодиться для другой сборки Minecraft.
## Пример использования
```qml
JavaPickerDialog {
id: javaPicker
x: (window.width - width) / 2
y: (window.height - height) / 2
backend: launcherBackend
onRuntimeChosen: (runtimeId) => settingsDialog.javaRuntimeId = runtimeId
}
MouseArea {
anchors.fill: javaField
onClicked: javaPicker.openFor(settingsDialog.javaRuntimeId,
launcherBackend.requiredJavaMajor(launcherBackend.activeBuildIndex))
}
```
---
При создании этого документа использовался ИИ.
+76
View File
@@ -0,0 +1,76 @@
# LabelledField
## Обзор компонента
`Minecraft_launcher` — десктопный лаунчер Minecraft на Qt Quick с полностью самостоятельно
оформленным тёмным интерфейсом. В диалоге настроек и в редакторе сборок много однотипных полей
ввода: подпись сверху, поле под ней. `LabelledField` собирает эту пару в один компонент, чтобы
отступы, цвета и подсветка фокуса не переписывались в каждом месте заново.
Компонент нужен там, где пользователь вводит короткое значение: имя профиля, объём памяти, путь,
аргументы запуска.
## Место в проекте и зависимости
Импортирует `QtQuick` и `QtQuick.Controls 2.15`; C++-типы не используются.
Объявлен в `QML_FILES` модуля `Minecraft_launcher` (`CMakeLists.txt`) и доступен по имени внутри
модуля без импорта. Применяется в диалоге настроек и в карточке сборки — см. [Main](Main.md) и
[BuildsDialog](BuildsDialog.md).
## Иерархия и роль
Корневой тип — `Column` с расстоянием 3 px между элементами. Колонка содержит два потомка:
`Text` с подписью (серый, 11 px) и `TextField` фиксированной высоты 32 px с тёмным фоном,
скруглением 6 px и рамкой, которая при фокусе поля меняет цвет на акцентный.
Ширина поля привязана к ширине самой колонки, поэтому размер задаётся снаружи одним свойством
`width` корневого элемента. Высоту `Column` вычисляет сам.
## Свойства
| Свойство | Тип | По умолчанию | Обязательное | Описание |
|----------|-----|--------------|--------------|----------|
| `text` | `string` (алиас на `text` внутреннего поля) | `""` | Нет | Содержимое поля ввода. Работает в обе стороны: чтение возвращает введённое значение, запись подставляет новое. |
| `validator` | `var` (алиас на `validator` внутреннего поля) | `null` | Нет | Валидатор ввода — например `IntValidator` для числовых полей. Ограничивает то, что пользователь может набрать. |
| `label` | `string` | `""` | Нет | Текст подписи над полем. |
| `placeholder` | `string` | `""` | Нет | Подсказка, показываемая в пустом поле приглушённым цветом. |
## Сигналы
#### editingFinished()
Проброшен из внутреннего `TextField`: срабатывает, когда правка закончена — поле потеряло фокус
или пользователь нажал Enter. Промежуточные нажатия клавиш сигнала не вызывают.
Обработчик обычно сохраняет введённое значение: читает `text` и передаёт его в бэкенд или в
модель родительского диалога. Именно из-за этой семантики поля настроек сохраняются по завершении
правки, а не на каждый символ.
## Методы
Собственных функций нет.
## Взаимодействие с другими компонентами
Компонент не знает ни о бэкенде, ни о содержащем его диалоге. Родитель задаёт `label`,
`placeholder`, начальный `text` и при необходимости `validator`, а затем подписывается на
`editingFinished`, чтобы записать значение. Двусторонней привязки к бэкенду внутри компонента нет
— решение о том, когда и куда сохранять, целиком за родителем.
## Пример использования
```qml
LabelledField {
width: parent.width
label: "Оперативная память, МБ"
placeholder: "2048"
text: String(settings.memoryMb)
validator: IntValidator { bottom: 512; top: 32768 }
onEditingFinished: settings.memoryMb = parseInt(text)
}
```
---
При создании этого документа использовался ИИ.
+150
View File
@@ -0,0 +1,150 @@
# LoaderRow
## Обзор компонента
`Minecraft_launcher` — десктопный лаунчер Minecraft на Qt Quick. Кроме чистой игры он умеет
ставить модлоадеры: Forge, Fabric, NeoForge и Quilt. В карточке сборки лоадер выбирается
чекбоксом, а под ним — конкретная версия лоадера.
`LoaderRow` — одна такая строка: чекбокс с названием лоадера и, когда он отмечен, выпадающий
список версий именно под выбранную версию Minecraft. Компонент берёт на себя всю возню со
списком версий: подтягивает его из кэша, обновляет по сети, следит, чтобы выбранная версия
всегда существовала под текущую версию игры, и словами объясняет случай «лоадер эту версию игры
не поддерживает» вместо показа пустого списка.
Совместимость компонент не проверяет и проверять не должен: бэкенд отдаёт список, уже собранный
под конкретную версию игры, поэтому несовместимой строки в нём не бывает. Пустой список — это и
есть отсутствие поддержки.
## Место в проекте и зависимости
Импортирует `QtQuick` и `QtQuick.Controls 2.15`.
Использует компонент [DarkCombo](DarkCombo.md) из того же QML-модуля — выпадающий список версий в
тёмном стиле окна.
Работает с C++-типом `LauncherBackend` (`launcherbackend.h`, зарегистрирован через `QML_ELEMENT`
в модуле `Minecraft_launcher`), который передаётся снаружи в свойство `backend`. Из него
компонент вызывает `loaderVersions()`, `refreshLoaderVersions()` и `loaderVersionsLoading()`, а
также слушает сигнал `loaderVersionsChanged`.
Объявлен в `QML_FILES` модуля `Minecraft_launcher` (`CMakeLists.txt`). Инстанцируется в
[BuildsDialog](BuildsDialog.md) — по одной строке на каждый поддерживаемый лоадер.
## Иерархия и роль
Корневой тип — `Column` с расстоянием 4 px. Внутри три потомка, видимость которых
взаимоисключающая по нижней части:
- `CheckBox` с полностью переопределённым индикатором (квадрат со скруглением и галочкой) и
подписью. Выключен, пока не выбрана версия игры.
- [DarkCombo](DarkCombo.md) со списком версий лоадера — виден, только когда чекбокс отмечен и
список непустой.
- Текстовая строка на месте списка — видна, когда чекбокс отмечен, а список пуст. Пока идёт
запрос, она серая и говорит о загрузке; когда запрос закончен, она красноватая и сообщает, что
лоадер не поддерживает выбранную версию Minecraft.
Ширина внутренних элементов считается от ширины колонки, поэтому снаружи достаточно задать
`width`.
## Свойства
| Свойство | Тип | По умолчанию | Обязательное | Описание |
|----------|-----|--------------|--------------|----------|
| `backend` | `var` | — | **Да** | Экземпляр `LauncherBackend`. Через него запрашиваются и обновляются списки версий лоадера. |
| `loaderKey` | `string` | — | **Да** | Ключ лоадера, которым он опознаётся в бэкенде и в сохранённой сборке: `forge`, `fabric`, `neoforge`, `quilt`. |
| `title` | `string` | — | **Да** | Человекочитаемое название лоадера рядом с чекбоксом; оно же подставляется в сообщения о загрузке и об отсутствии поддержки. |
| `gameVersion` | `string` | `""` | Нет | Версия Minecraft, выбранная в карточке. Пустая строка выключает чекбокс. Смена значения сбрасывает выбранную версию лоадера и перезапрашивает список. |
| `checked` | `bool` (алиас на чекбокс) | `false` | Нет | Отмечен ли лоадер. Чтение даёт текущее состояние, запись переключает чекбокс программно — без сигнала `userChecked()`. |
| `selectedVersion` | `string` | `""` | Нет | Выбранная версия лоадера. Устанавливается только через `applyEntries()`, поэтому всегда либо пуста, либо присутствует в текущем списке. |
| `entries` | `var` | `[]` | Нет | Текущий список версий лоадера — массив записей, у каждой есть поля `version` (значение) и `label` (подпись для списка). Заполняется из бэкенда. |
| `applying` | `bool` | `false` | Нет | Признак того, что строку сейчас заполняет карточка данными сохранённой сборки. Пока он выставлен, изменения не считаются пользовательскими и сигнал `changed()` не испускается. |
## Сигналы
#### userChecked()
Пользователь сам отметил чекбокс (не программная установка `checked`). Карточка сборки в ответ
снимает отметки с остальных строк лоадеров: одновременно в `.minecraft` может жить только один
лоадер.
#### changed()
Отметка или версия лоадера изменились и это изменение пользовательское. Обработчик — карточка
сборки — сохраняет сборку с новыми значениями.
Сигнал сознательно не испускается, пока выставлен `applying`, то есть при заполнении строки из
уже сохранённой сборки: иначе загрузка карточки сразу же приводила бы к её перезаписи.
## Методы
#### versionIndex(string version) : int
Возвращает позицию версии в текущем массиве `entries` или `-1`, если такой версии в списке нет.
Вспомогательная функция для синхронизации выбранного значения с выпадающим списком.
#### applyEntries(var list) : void
Единственное место, где меняются `entries` и `selectedVersion`. Через него проходят все три пути
получения списка — кэш, ответ сети и заполнение из сохранённой сборки, — потому что выбранная
версия обязана существовать в списке под текущую версию игры.
Записывает новый список, а затем проверяет выбранную версию: если её в списке нет, подставляет
первую строку (список отсортирован новыми вперёд, поэтому первая — максимально доступная под эту
версию игры) или пустую строку для пустого списка. Если подстановка изменила значение и строка не
находится в режиме `applying`, испускает `changed()`. В конце синхронизирует `currentIndex`
выпадающего списка.
#### reload() : void
Перезапрашивает список версий. Если лоадер не отмечен или версия игры не выбрана, очищает список
через `applyEntries([])`. Иначе сначала берёт список из кэша бэкенда (`loaderVersions()`) — он
появляется мгновенно, — а затем просит обновление по сети (`refreshLoaderVersions()`), результат
которого придёт позже сигналом.
#### applyBuild(string loader, string loaderVersion) : void
Заполняет строку данными сохранённой сборки, не испуская `changed()`: на время работы выставляет
`applying`. Отмечает чекбокс, если ключ лоадера сборки совпадает с `loaderKey`, подставляет версию
из сборки как пожелание и вызывает `reload()`. Если под выбранную версию игры такой версии
лоадера нет, `applyEntries()` заменит её на максимально доступную.
## Взаимодействие с другими компонентами
**Что приходит извне.** Карточка сборки в [BuildsDialog](BuildsDialog.md) задаёт `backend`,
`loaderKey`, `title` и привязывает `gameVersion` к версии Minecraft, выбранной в карточке.
Заполнение сохранённой сборкой идёт вызовом `applyBuild()` снаружи.
**Что уходит наружу.** По `userChecked()` карточка снимает отметки с остальных строк — набор
строк она держит в собственном списке. По `changed()` карточка сохраняет сборку, читая `checked` и
`selectedVersion`.
**Бэкенд.** Компонент сам подписан на сигнал `loaderVersionsChanged(key, game)` через
`Connections`: пришедшее обновление принимается, только если ключ и версия игры совпадают с
текущими и чекбокс отмечен, — иначе ответ относится к другой строке или устарел. Текст в пустом
состоянии опрашивает `loaderVersionsLoading()`, чтобы отличать «ещё грузим» от «не поддерживается».
**Реакция на смену версии игры.** Обработчик `onGameVersionChanged` сбрасывает `selectedVersion` и
вызывает `reload()`: сборка лоадера привязана к версии игры, поэтому под новой версией прежний
выбор недействителен.
## Пример использования
```qml
LoaderRow {
id: forgeRow
width: parent.width
backend: launcherBackend
loaderKey: "forge"
title: "Forge"
gameVersion: buildCard.gameVersion
onUserChecked: buildCard.keepOnly(forgeRow)
onChanged: buildCard.commitSelection()
}
```
---
При создании этого документа использовался ИИ.
+212
View File
@@ -0,0 +1,212 @@
# 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).
---
При создании этого документа использовался ИИ.
+121
View File
@@ -0,0 +1,121 @@
# MicrosoftLoginDialog
## Обзор компонента
`Minecraft_launcher` — десктопный лаунчер Minecraft на Qt Quick. Он поддерживает несколько
способов входа: офлайн-профиль, сервер Ely.by и учётную запись Microsoft. Последний путь требует
показать пользователю настоящую страницу входа Microsoft и дождаться, пока браузер уйдёт на
`redirect_uri` с кодом авторизации в адресе — ровно так же поступает официальный лаунчер.
`MicrosoftLoginDialog` — окно с этой страницей. Внутри него живёт `WebEngineView`; диалог следит
за сменой адреса, отдаёт перехваченный код бэкенду и закрывается. Собственной логики разбора
адреса у него нет — она в C++, чтобы правила совпадения совпадали с теми, по которым сервис сам
строит `redirect_uri`.
## Место в проекте и зависимости
Импортирует `QtQuick`, `QtQuick.Controls 2.15` и `QtWebEngine`. В начале файла объявлена
`pragma ComponentBehavior: Bound`.
Работает с C++-типом `LauncherBackend` (`launcherbackend.h`, `QML_ELEMENT`), который передаётся
снаружи в свойство `backend`. Вызывает у него `inspectMicrosoftRedirect()`,
`finishMicrosoftLogin()` и `cancelMicrosoftLogin()`.
**Особенность сборки.** Это единственный QML-файл проекта, который попадает в модуль условно.
`CMakeLists.txt` ищет `Qt6WebEngineQuick` через `find_package(... QUIET)`; модуль объявлен
необязательным сознательно — он ставится отдельной галочкой в установщике Qt и тянет за собой
WebChannel с Positioning, которых в типовой установке нет. Если модуль найден, файл добавляется в
`QML_FILES` и определяется макрос `LAUNCHER_HAS_WEBENGINE`; если нет — лаунчер собирается и
работает как прежде, только без входа через Microsoft. В QML это различие видно через свойство
`backend.microsoftAvailable`, и интерфейс не должен предлагать этот путь, когда оно ложно.
Инстанцируется динамически из [Main](Main.md) — главное окно создаёт диалог по требованию, потому
что при сборке без WebEngine самого типа в модуле не существует.
## Иерархия и роль
Корневой тип — `Dialog` из Qt Quick Controls: модальный, 560×680 px, по центру родителя, с нулевым
внутренним отступом и `closePolicy: Popup.NoAutoClose` — окно нельзя закрыть щелчком мимо или
клавишей Escape, выход только через кнопку отмены или успешный вход.
Оформление задано вручную: тёмный фон со скруглением и акцентной рамкой, заголовок с
разделительной линией, подвал с кнопкой «Отмена».
Содержимое — `WebEngineView` во всю площадь с отступом 12 px и индикатор занятости по центру,
видимый на время загрузки страницы. Рядом объявлен `WebEngineProfilePrototype` без `storageName`:
профиль без имени хранилища означает профиль без диска, поэтому куки живут только пока работает
лаунчер и в общий браузер не попадают. За выбор аккаунта в пределах сессии отвечает параметр
`prompt=select_account` в адресе входа, который формирует бэкенд.
## Свойства
| Свойство | Тип | По умолчанию | Обязательное | Описание |
|----------|-----|--------------|--------------|----------|
| `backend` | `var` | — | **Да** | Экземпляр `LauncherBackend`. Разбирает перехваченный адрес и завершает или отменяет вход. |
| `codeTaken` | `bool` | `false` | Нет | Код авторизации уже отдан бэкенду. Защита от повторной обработки: `WebEngineView` успевает сообщить об изменении адреса несколько раз, и без этого признака код ушёл бы дважды. Сбрасывается в `openAt()` и принудительно выставляется при отмене. |
## Сигналы
#### failed(string message)
Вход не завершён: адрес совпал с `redirect_uri`, но кода в нём нет — например, пользователь
отказался выдать разрешение, или Microsoft вернула ошибку. В параметре приходит текст ошибки от
бэкенда, а если его нет — сообщение по умолчанию о незавершённом входе.
Окно ничего не знает про тосты главного окна, поэтому о неудаче сообщает сигналом. Обработчик в
[Main](Main.md) показывает это сообщение пользователю. К моменту испускания сигнала диалог уже
закрыт, а вход у бэкенда отменён — обработчику остаётся только уведомить.
Отмена по кнопке сигнала не испускает: пользователь и так знает, что закрыл окно.
## Методы
#### openAt(string url) : void
Открывает диалог на переданном адресе страницы входа. Сбрасывает `codeTaken`, загружает адрес в
`WebEngineView` и показывает окно. Адрес формирует бэкенд — в нём уже присутствуют `redirect_uri`,
идентификатор клиента и `prompt=select_account`.
#### handleUrl(url) : void
Обработчик смены адреса в `WebEngineView`; вызывать снаружи не нужно. Ничего не делает, если код
уже перехвачен. Иначе передаёт адрес в `backend.inspectMicrosoftRedirect()` и смотрит на поле
`matched` ответа: если адрес не является `redirect_uri`, обработка на этом заканчивается — это
обычная навигация по страницам входа.
При совпадении выставляет `codeTaken`, закрывает окно и дальше расходится по двум путям: непустое
поле `code` уходит в `backend.finishMicrosoftLogin()`, иначе вход отменяется через
`backend.cancelMicrosoftLogin()` и испускается сигнал `failed()` с текстом из поля `error`.
## Взаимодействие с другими компонентами
**Со стороны главного окна.** [Main](Main.md) создаёт диалог динамически (функция
`openMicrosoftLogin()`), задаёт `backend`, вызывает `openAt()` с адресом от бэкенда и
подписывается на `failed()`, чтобы показать тост с ошибкой. Показывать ли кнопку входа через
Microsoft вообще, главное окно решает по `backend.microsoftAvailable`.
**Со стороны бэкенда.** Диалог только доставляет код: `inspectMicrosoftRedirect()` — разбор
адреса, `finishMicrosoftLogin()` — продолжение обмена кода на токены, `cancelMicrosoftLogin()`
сброс начатой сессии входа. Результат входа диалогу не возвращается: об успехе главное окно
узнаёт от бэкенда по его собственным сигналам, а диалог к этому моменту уже закрыт.
**Кнопка отмены.** Выставляет `codeTaken`, закрывает окно и отменяет вход у бэкенда. Признак
ставится до закрытия, чтобы последний сигнал об изменении адреса при закрытии не был обработан.
## Пример использования
```qml
MicrosoftLoginDialog {
id: msLogin
parent: Overlay.overlay
backend: launcherBackend
onFailed: (message) => showToast(message, "#cc6666", 4000)
}
// открывать только в сборке с Qt WebEngine
Component.onCompleted: if (launcherBackend.microsoftAvailable) msLogin.openAt(loginUrl)
```
---
При создании этого документа использовался ИИ.
+90
View File
@@ -0,0 +1,90 @@
# ProgressPanel
## Обзор компонента
`Minecraft_launcher` — десктопный лаунчер Minecraft на Qt Quick. Почти каждое действие в нём
долгое: скачивание версии игры, установка Java, распаковка и архивация папки `.minecraft` при
смене сборки, загрузка сезонной сборки. Лаунчер не блокирует окно на это время, поэтому ход
операции нужно показывать неотрывно от остального интерфейса.
`ProgressPanel` — та самая плашка прогресса. Она размещается в левом нижнем углу главного окна,
где не перекрывает кнопку запуска, сообщение по центру и кнопки папки и настроек. Компонент
только отображает переданное состояние; сам он ничего не считает и ни за чем не следит.
## Место в проекте и зависимости
Импортирует `QtQuick` и `QtQuick.Controls 2.15`; C++-типы напрямую не использует.
Объявлен в `QML_FILES` модуля `Minecraft_launcher` (`CMakeLists.txt`), поэтому доступен по имени
внутри модуля без импорта. Инстанцируется в [Main](Main.md) — по одной плашке на вид долгой
операции.
## Иерархия и роль
Корневой тип — `Rectangle` фиксированного размера 320×72 px со скруглением 8 px, тёмной заливкой,
акцентной рамкой и лёгкой полупрозрачностью, чтобы плашка читалась поверх фонового изображения
окна.
Внутри — пять элементов без внешних зависимостей: заголовок слева сверху, проценты справа сверху,
полоса прогресса (дорожка и заполнение с плавной анимацией ширины на 120 мс), строка подробностей
снизу и крестик отмены в правом нижнем углу с увеличенной областью нажатия.
## Свойства
| Свойство | Тип | По умолчанию | Обязательное | Описание |
|----------|-----|--------------|--------------|----------|
| `title` | `string` | `""` | Нет | Заголовок операции в левом верхнем углу, полужирным. Длинный текст обрезается справа многоточием. |
| `status` | `string` | `""` | Нет | Строка состояния внизу плашки. Показывается только когда `detail` пуст. |
| `fraction` | `double` | `-1` | Нет | Доля выполнения от `0` до `1`. Значение `-1` означает «итог ещё неизвестен»: вместо процентов выводится многоточие, а полоса остаётся пустой. |
| `detail` | `string` | `""` | Нет | Необязательная вторая строка подробностей — мегабайты у загрузки, путь у архивации. Если задана, вытесняет `status`. Длинный текст обрезается посередине, чтобы у пути были видны и начало, и конец. |
| `cancellable` | `bool` | `true` | Нет | Показывать ли крестик отмены. Ставится в `false` для операций, которые прерывать нельзя. |
## Сигналы
#### cancelRequested()
Пользователь нажал крестик в правом нижнем углу. Сигнал сообщает только о намерении: плашка не
скрывает себя и не меняет своё состояние.
Обработчик должен сам остановить операцию в бэкенде и убрать плашку с экрана — как правило, вызвав
соответствующий метод отмены у `LauncherBackend`; видимость плашки при этом снимется сама, потому
что она привязана к свойству занятости бэкенда.
Сигнал не испускается при `cancellable: false` — в этом случае крестик скрыт.
## Методы
Собственных функций нет.
## Взаимодействие с другими компонентами
Все пять свойств плашки — точки внешней привязки. В [Main](Main.md) они связаны со свойствами
`LauncherBackend`: у загрузки версии это группа `downloading` / `downloadProgress` /
`downloadVersion` / `downloadStatus` / `downloadBytesDone` / `downloadBytesTotal`, у смены сборки —
`switching` / `switchProgress` / `switchStage` / `switchStatus`. Байты в мегабайты переводит
функция `formatMb()` главного окна, а не сама плашка.
Видимостью плашки управляет родитель, обычно привязывая её к тому же признаку занятости, который
питает `fraction`. Сигнал `cancelRequested` родитель замыкает на метод отмены бэкенда.
## Пример использования
```qml
ProgressPanel {
anchors.left: parent.left
anchors.bottom: parent.bottom
anchors.margins: 16
visible: backend.downloading
title: qsTr("Загрузка Minecraft %1").arg(backend.downloadVersion)
status: backend.downloadStatus
fraction: backend.downloadProgress
detail: formatMb(backend.downloadBytesDone) + " / " + formatMb(backend.downloadBytesTotal)
onCancelRequested: backend.cancelDownload()
}
```
---
При создании этого документа использовался ИИ.
+135
View File
@@ -0,0 +1,135 @@
# SeasonalBuildsDialog
## Обзор компонента
`Minecraft_launcher` — десктопный лаунчер Minecraft на Qt Quick. Кроме сборок, которые
пользователь собирает сам, он умеет ставить готовые сезонные сборки с сервера лаунчера: набор
модов под конкретную версию игры и модлоадер, подготовленный заранее и выдаваемый целиком.
`SeasonalBuildsDialog` — окно этого каталога: таблица со всем, что нужно знать, чтобы решить,
ставить сборку или нет, и одна кнопка, которая делает всё остальное — заводит сборку, ставит
версию игры, модлоадер, Java и раскладывает файлы. Окно также показывает, что установленная
сборка устарела, и предлагает обновить её до свежей ревизии.
Таблица собрана из строк `Row` с фиксированными колонками, а не из `TableView`: в проекте нет ни
одной модели `QAbstractItemModel`, а строки приходят готовыми `QVariantMap` — заводить ради семи
колонок отдельную модель незачем.
## Место в проекте и зависимости
В начале файла объявлена `pragma ComponentBehavior: Bound`. Импортирует `QtQuick` и
`QtQuick.Controls 2.15` — слои `QtQuick.Layouts` здесь не нужны, вся раскладка на якорях и
фиксированных ширинах колонок.
Работает с C++-типом `LauncherBackend` (`launcherbackend.h`, `QML_ELEMENT` в модуле
`Minecraft_launcher`), передаваемым снаружи в свойство `backend`. Читает свойства
`seasonalCatalog`, `seasonalCatalogLoading`, `seasonalCatalogError`, `seasonalInstalling` и `busy`,
вызывает `refreshSeasonalCatalog()` и `installSeasonalBuild()`.
Объявлен в `QML_FILES` модуля (`CMakeLists.txt`). Инстанцируется в [Main](Main.md).
Строки каталога приходят из C++ уже отсортированными и сведёнными с локальными записями — окно
показывает статус, не считая ничего само. Запись содержит поля `id`, `name`, `description`,
`minecraftVersion`, `loaderTitle`, `loaderVersion`, `modCount`, `seasonStart`, `seasonEnd`,
`status`, `revision`, `installedRevision`, `updateAvailable`, `sizeBytes` и `serverUrl`.
## Иерархия и роль
Корневой тип — `Dialog`: модальный, 960×560 px, нулевой внутренний отступ, тёмный фон со
скруглением и акцентной рамкой.
Политика закрытия зависит от состояния: пока идёт установка, окно закрывается только кнопкой
(`Popup.NoAutoClose`), потому что случайный щелчок мимо не должен спрятать единственную видимую
отмену; в остальное время работают Escape и щелчок мимо.
Содержимое — шапка таблицы (`Row` с `Repeater` по `columns`), разделительная линия, список строк и
сообщение по центру для пустого состояния. Строка списка — `Rectangle` высотой 36 px с вложенным
`Row`, который повторяет тот же набор колонок; ширины берутся из общего описания `columns`,
поэтому шапка и строки не могут разъехаться.
Подвал высотой 76 px несёт описание и размер выбранной сборки (они длинные и в таблицу не
помещаются, а решение принимается именно по ним) и две кнопки — обновления списка и установки.
## Свойства
| Свойство | Тип | По умолчанию | Обязательное | Описание |
|----------|-----|--------------|--------------|----------|
| `backend` | `var` | — | **Да** | Экземпляр `LauncherBackend` — источник каталога сезонных сборок и исполнитель установки. |
| `selectedId` | `string` | `""` | Нет | Идентификатор выбранной строки. Хранится по `id`, а не по индексу: список обновляется под руками, и индекс после обновления указывал бы на другую сборку. |
| `columns` | `var` (только чтение) | список из семи колонок | Нет | Описание таблицы: массив объектов с полями `key` (поле записи), `title` (заголовок), `width` (ширина в пикселях) и `align` (выравнивание). Колонки: название, версия, загрузчик, число модов, начало и конец сезона, статус. Ширины собраны в одном месте, потому что их повторяют и шапка, и делегат строки. |
| `entries` | `var` (только чтение) | `backend.seasonalCatalog` | Нет | Строки каталога напрямую из бэкенда. Отбора и сортировки в окне нет. |
| `selectedEntry` | `var` (только чтение) | вычисляется | Нет | Полная запись выбранной сборки или `null`, если ничего не выбрано. От неё зависят подвал и доступность кнопки установки. |
## Сигналы
Собственных сигналов компонент не объявляет. Результат работы окна виден через состояние бэкенда:
установка меняет список сборок и запускает загрузку, за ходом которой следит главное окно.
## Методы
#### openCatalog() : void
Открывает окно. Обновление каталога происходит само в обработчике `onAboutToShow`, который просит
у бэкенда `refreshSeasonalCatalog(false)` — без принудительного обхода кэша: свежий кэш отвечает
без сети, поэтому вызов при каждом открытии ничего не стоит.
#### formatMb(bytes) : string
Переводит размер в байтах в строку с мегабайтами и одним знаком после запятой. Для нулевого или
отсутствующего значения возвращает пустую строку, чтобы размер просто не попал в строку подвала.
#### cellText(entry, string key) : string
Возвращает текст ячейки для записи и ключа колонки. Для всех колонок это значение одноимённого
поля записи, приведённое к строке; исключение — колонка загрузчика, где название и версия
склеиваются в одну подпись, а при пустой версии остаётся только название.
#### installSelected() : void
Ставит выбранную сборку. Ничего не делает, если строка не выбрана или лаунчер занят другой
операцией: установка занимает и панель загрузки, и `.minecraft` целиком, поэтому вторую начинать
нельзя. Иначе вызывает `installSeasonalBuild()` у бэкенда.
Вызывается кнопкой установки и двойным щелчком по строке.
## Взаимодействие с другими компонентами
**Со стороны родителя.** [Main](Main.md) задаёт `backend` и открывает окно вызовом
`openCatalog()`. Обратной связи наружу через сигналы нет.
**Со стороны бэкенда.** Всё содержимое таблицы — привязка к `seasonalCatalog`, поэтому обновление
каталога и изменение статуса установленной сборки перерисовывают окно сами. Пустое состояние
различает три случая по `seasonalCatalogLoading` и `seasonalCatalogError`: идёт загрузка, сборок
пока нет, произошла ошибка — её текст показывается прямо на месте строк, потому что пустой список
и ошибка выглядят одинаково пустыми.
**Занятость.** Кнопка установки выключается по общему признаку `busy`, кнопка обновления списка —
по `seasonalCatalogLoading` (её подпись при этом меняется на «Обновление…»). Политика закрытия
окна завязана на `seasonalInstalling`.
**Обновление ревизии.** Если у записи выставлен `updateAvailable`, статус в таблице подсвечивается
акцентным цветом, в подвале дописывается установленная ревизия, а кнопка установки называется
«Обновить». Отдельного пути обновления нет — это тот же вызов `installSeasonalBuild()`.
**Ход установки.** Окно не показывает прогресс: за это отвечает плашка
[ProgressPanel](ProgressPanel.md) в главном окне, а отмена — метод `cancelSeasonalInstall()`
бэкенда.
## Пример использования
```qml
SeasonalBuildsDialog {
id: seasonalDialog
parent: Overlay.overlay
backend: launcherBackend
}
Button {
text: qsTr("Сезонные сборки")
onClicked: seasonalDialog.openCatalog()
}
```
---
При создании этого документа использовался ИИ.
+158
View File
@@ -0,0 +1,158 @@
# VersionPickerDialog
## Обзор компонента
`Minecraft_launcher` — десктопный лаунчер Minecraft на Qt Quick. Каталог версий Mojang — это около
тысячи записей: релизы, снапшоты, старые беты и альфы. Раньше выбор версии был обычным выпадающим
списком на всю эту тысячу, и найти в нём, скажем, бету 1.7 можно было только поиском по точному
номеру.
`VersionPickerDialog` заменил тот список отдельным окном: слева категории, справа сами версии с
поиском сверху. Категории делят каталог на обозримые части, а поиск работает внутри выбранной.
Помимо выбора окно умеет удалять уже скачанные версии — с предупреждением о последствиях, потому
что версия весит десятки мегабайт, а поверх неё могут стоять профили модлоадеров.
Окно открывается из карточки сборки, когда пользователь выбирает, на какой версии Minecraft
собирается играть.
## Место в проекте и зависимости
В начале файла объявлена `pragma ComponentBehavior: Bound`. Импортирует `QtQuick`,
`QtQuick.Controls 2.15` и `QtQuick.Layouts 2.15`.
Работает с C++-типом `LauncherBackend` (`launcherbackend.h`, `QML_ELEMENT` в модуле
`Minecraft_launcher`), передаваемым снаружи в свойство `backend`. Читает у него свойства
`versionCatalog` и `catalogLoading`, вызывает `refreshVersionCatalog()`, `versionRemovalInfo()` и
`removeVersion()`.
Объявлен в `QML_FILES` модуля (`CMakeLists.txt`); использует ресурс `images/Trash.svg` из
`RESOURCES` того же модуля. Инстанцируется в [BuildsDialog](BuildsDialog.md).
Каталог приходит из C++ уже отсортированным (новые сверху), поэтому окно только отбирает записи и
порядок не трогает. Каждая запись каталога — объект с полями `id` (идентификатор версии), `label`
(подпись строки), `category` (ключ категории), `installed` (скачана ли) и `search`
(предвычисленная строка для поиска в нижнем регистре).
## Иерархия и роль
Корневой тип — `Dialog`: модальный, 720×480 px, нулевой внутренний отступ, закрывается по Escape и
щелчку мимо. Оформление задано вручную — тёмный фон со скруглением и акцентной рамкой.
Содержимое — `RowLayout` из двух частей, разделённых вертикальной линией: колонка категорий
шириной 150 px (`Repeater` по `categories`) и область версий. В области версий сверху поле поиска,
внизу флажок «Только установленные», между ними `ListView` со строками версий. Строка показывает
подпись, галочку для установленной версии и корзину удаления; галочка рядом со строкой —
единственное место, где видно, что именно скачано.
Отдельно объявлен вложенный `Dialog` подтверждения удаления шириной 420 px с красной рамкой. Он
привязан к тому же родителю, что и само окно, и центрируется в нём вручную, чтобы не оказаться
внутри области выбора.
Пустое состояние списка объясняется текстом по центру, который различает три случая: каталог ещё
грузится, каталог пуст (нет соединения) и по фильтру ничего не найдено.
## Свойства
| Свойство | Тип | По умолчанию | Обязательное | Описание |
|----------|-----|--------------|--------------|----------|
| `backend` | `var` | — | **Да** | Экземпляр `LauncherBackend` — источник каталога версий и исполнитель удаления. |
| `selectedId` | `string` | `""` | Нет | Идентификатор выбранной версии. Окно открывается с текущей версией, и по «Отмене» выбор возвращается к ней, потому что результат уходит наружу только через сигнал. Пустая строка — версия не выбрана, кнопка подтверждения выключена. |
| `category` | `string` | `"release"` | Нет | Ключ активной категории. Допустимые значения: `release` (релизы), `snapshot` (снапшоты), `old_beta` (беты), `old_alpha` (альфы), `other` (прочие — сюда попадают в том числе установленные профили модлоадеров). |
| `filterText` | `string` | `""` | Нет | Текст поиска. Сравнивается в нижнем регистре без учёта регистра с полем `search` записи каталога; поиск идёт внутри активной категории. |
| `installedOnly` | `bool` | `false` | Нет | Показывать только скачанные версии. |
| `categories` | `var` (только чтение) | список из пяти записей | Нет | Описание колонки категорий: массив объектов с полями `key` и переведённым `title`. Задаёт и порядок пунктов, и набор допустимых значений `category`. |
| `visibleEntries` | `var` (только чтение) | вычисляется | Нет | Отобранные строки каталога: записи активной категории, прошедшие флажок «только установленные» и текст поиска. Порядок наследуется от каталога. Служит моделью списка. |
## Сигналы
#### versionChosen(string versionId)
Версия подтверждена — кнопкой «Выбрать», двойным щелчком по строке или клавишей Enter в поле
поиска. В параметре приходит идентификатор версии.
Имя не `accepted()` сознательно: такой сигнал у `Dialog` уже есть и переопределить его нельзя.
Обработчик — карточка сборки — записывает выбранную версию в сборку. К моменту вызова обработчика
окно уже закрыто. При отмене сигнал не испускается, поэтому снаружи ничего откатывать не нужно.
## Методы
#### openFor(string versionId) : void
Открывает окно на переданной версии. Запоминает её в `selectedId`, очищает поиск, переключается на
категорию именно этой версии (а не всегда на релизы), просит бэкенд обновить каталог и
прокручивает список к выбранной строке.
#### categoryOf(string versionId) : string
Возвращает ключ категории версии по каталогу; для неизвестной версии — `release`.
#### indexOfSelected() : int
Позиция выбранной версии в `visibleEntries` или `-1`, если под текущим фильтром её не видно.
#### revealSelected() : void
Выставляет текущий индекс списка на выбранную версию и прокручивает список так, чтобы строка
оказалась по центру. Вызывается после каждой смены фильтра, категории или удаления.
#### acceptSelection() : void
Подтверждает выбор: испускает `versionChosen()` и закрывает окно. При пустом `selectedId` не
делает ничего.
#### catalogHas(string versionId) : bool
Есть ли версия в каталоге. Нужен после удаления: профиль модлоадера присутствовал в каталоге
только потому, что был установлен, и после удаления строка исчезает совсем.
#### askRemove(string versionId) : void
Спрашивает подтверждение перед удалением. Запрашивает у бэкенда `versionRemovalInfo()` и, если
версия действительно установлена, наполняет окно подтверждения: занимаемый объём, список
зависящих профилей модлоадеров и список сборок, которые эту версию используют. Идентификатор
запоминается в самом окне подтверждения, потому что к моменту ответа строка под курсором может
быть уже другой.
Окно подтверждения объясняет три вещи: файлы удалятся из `versions/` и освободится столько-то
мегабайт; профили модлоадеров поверх этой версии без неё не запустятся; библиотеки и ресурсы в
`libraries/` и `assets/` общие для всех версий и остаются на месте.
#### performRemove(string versionId) : void
Удаляет версию через бэкенд. Если удалена была именно выбранная версия и её больше нет в каталоге,
снимает выбор. В конце обновляет позицию списка.
## Взаимодействие с другими компонентами
**Со стороны родителя.** [BuildsDialog](BuildsDialog.md) задаёт `backend`, открывает окно вызовом
`openFor()` с текущей версией сборки и подписывается на `versionChosen()`, чтобы записать выбор.
Никаких других точек входа у окна нет — прямая запись `selectedId` снаружи не предполагается.
**Со стороны бэкенда.** `versionCatalog` и `catalogLoading` — привязки, от которых зависят и
модель списка, и текст пустого состояния: пришедшее обновление каталога пересчитывает
`visibleEntries` само. `refreshVersionCatalog()` вызывается при открытии окна, `removeVersion()`
после подтверждения удаления.
**Клавиатура.** Фокус при открытии уходит в поле поиска. Стрелки вверх и вниз двигают выбор по
списку функцией `step()`, Enter подтверждает выбор, Escape закрывает окно.
## Пример использования
```qml
VersionPickerDialog {
id: versionPicker
parent: Overlay.overlay
backend: launcherBackend
onVersionChosen: (versionId) => buildCard.gameVersion = versionId
}
Button {
text: buildCard.gameVersion || qsTr("Выбрать версию")
onClicked: versionPicker.openFor(buildCard.gameVersion)
}
```
---
При создании этого документа использовался ИИ.