# 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()
}
```

---

При создании этого документа использовался ИИ.
