1 parent
1943996608
commit
f10b654953
34 files changed
+2099
-4576
No files matched your search
+64
-148
@@ -1,178 +1,94 @@
|
||||
# BuildsDialog
|
||||
# BuildsDialog.qml
|
||||
|
||||
## Обзор компонента
|
||||
Окно пользовательских сборок. Слева список, справа карточка выбранной сборки: имя, сервер, версия
|
||||
Minecraft, модлоадер. Отсюда сборку ставят, делают активной и удаляют.
|
||||
|
||||
`Minecraft_launcher` — десктопный лаунчер Minecraft на Qt Quick. Сборка в нём — это именованный
|
||||
набор «версия игры + модлоадер + адрес сервера» вместе с собственным содержимым `.minecraft`:
|
||||
модами, конфигами и мирами. Активная сборка одна, её содержимое лежит в `.minecraft`, остальные
|
||||
хранятся в архивах и разворачиваются при переключении.
|
||||
Сборка — это версия игры, модлоадер и адрес сервера плюс собственное содержимое `.minecraft`
|
||||
(моды, конфиги, миры). Активная сборка одна, её файлы разложены в `.minecraft`, остальные лежат в
|
||||
архивах. Подробнее в [BuildSwitcher](../cpp/BuildSwitcher.md).
|
||||
|
||||
`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 с красной рамкой,
|
||||
привязанный к тому же родителю, что и само окно.
|
||||
|
||||
Подвал несёт три кнопки: «Установить», «Сделать активной» и «Закрыть».
|
||||
Открывается из [Main](Main.md) по кнопке активной сборки.
|
||||
|
||||
## Свойства
|
||||
|
||||
| Свойство | Тип | По умолчанию | Обязательное | Описание |
|
||||
|----------|-----|--------------|--------------|----------|
|
||||
| `backend` | `var` | — | **Да** | Экземпляр `LauncherBackend` — хранилище сборок и исполнитель установки, удаления и переключения. |
|
||||
| `editIndex` | `int` | `-1` | Нет | Индекс сборки, открытой в карточке справа. Значение `-1` означает, что карточка пуста и выключена. С активной сборкой не связан: активную меняет отдельная кнопка, чтобы случайный клик по списку не запускал архивацию `.minecraft`. |
|
||||
| `loading` | `bool` | `false` | Нет | Признак того, что карточка сейчас заполняется данными сборки. Пока он выставлен, обработчики полей не должны писать обратно — иначе открытие сборки немедленно перезаписывало бы её. |
|
||||
| Свойство | Что это |
|
||||
|----------|---------|
|
||||
| `backend` (required) | `LauncherBackend` |
|
||||
| `editIndex` | сборка, открытая в карточке; −1 — ничего не открыто |
|
||||
| `loading` | карточка сейчас заполняется данными сборки, обработчики полей ничего не записывают |
|
||||
|
||||
### Внутренняя панель модлоадеров
|
||||
`editIndex` не связан с активной сборкой. Щелчок по строке списка только открывает карточку, а
|
||||
активной сборку делает отдельная кнопка. Иначе случайный щелчок запускал бы архивацию
|
||||
`.minecraft`.
|
||||
|
||||
Панель `loaderPanel` — это `Column` с четырьмя строками [LoaderRow](LoaderRow.md) и собственным
|
||||
маленьким API. Лоадеры несовместимы между собой: игра запускается ровно с одним профилем
|
||||
`versions/<id>`, поэтому отметка одного снимает остальные, а «ничего не отмечено» — это чистая
|
||||
ваниль.
|
||||
## Как устроено окно
|
||||
|
||||
| Свойство панели | Тип | Описание |
|
||||
|-----------------|-----|----------|
|
||||
| `rows` | `var` | Массив из четырёх строк лоадеров в порядке показа: Minecraft Forge (`forge`), Fabric Loader (`fabric`), NeoForge (`neoforge`), Quilt Loader (`quilt`). |
|
||||
Модальный `Dialog` 880×600. Пока идёт смена сборки (`backend.switching`), окно закрывается только
|
||||
кнопкой, а список и карточка выключены.
|
||||
|
||||
Функции панели: `applyBuild(loader, loaderVersion)` раздаёт данные сборки всем строкам;
|
||||
`selectedRow()` возвращает отмеченную строку или `null`; `keepOnly(row)` очищает все строки,
|
||||
кроме переданной; `commitSelection()` сохраняет выбор в сборку, записывая ключ лоадера, его версию
|
||||
и пустой `resolvedVersionId` — профиль появится только после установки, а до неё сборка
|
||||
запускается на чистой ванили.
|
||||
**Слева** (260 px) список сборок. У активной пометка «активна», при наведении появляется корзина.
|
||||
Ниже строка «+ Новая сборка»: создаёт сборку с первым свободным именем вида «Сборка N» и сразу
|
||||
открывает её.
|
||||
|
||||
## Сигналы
|
||||
**Справа** карточка в `Flickable`, потому что может не поместиться по высоте:
|
||||
|
||||
Собственных сигналов компонент не объявляет: все изменения уходят прямо в бэкенд, и о них
|
||||
остальной интерфейс узнаёт по его сигналам.
|
||||
- название и адрес сервера ([LabelledField](LabelledField.md));
|
||||
- версия Minecraft. Это не выпадающий список, а поле, которое открывает
|
||||
[VersionPickerDialog](VersionPickerDialog.md): версий около тысячи, и выбирать их удобнее в окне
|
||||
с категориями и поиском;
|
||||
- четыре строки [LoaderRow](LoaderRow.md): Forge, Fabric, NeoForge, Quilt;
|
||||
- строка комплектности: «можно запускать» или «не хватает N файлов» и первый из них
|
||||
(`checkInstallation()`).
|
||||
|
||||
## Методы
|
||||
**Внизу** кнопки «Установить» (`installCustomBuild()`), «Сделать активной» (запись в
|
||||
`activeBuildIndex`) и «Закрыть». Первые две выключены, пока `busy`. «Сделать активной» выключена
|
||||
ещё и для уже активной сборки. Прогресс установки и смены сборки показывают панели главного окна.
|
||||
|
||||
#### selectBuild(int index) : void
|
||||
## Сохранение по ходу
|
||||
|
||||
Открывает сборку с указанным индексом в карточке. Читает данные через `customBuildAt()`,
|
||||
выставляет `loading` на время заполнения полей, раздаёт лоадеры панели через `applyBuild()` и в
|
||||
конце обновляет строку состояния.
|
||||
Отдельной кнопки «Сохранить» нет. Каждое поле записывается сразу после правки через
|
||||
`commit(fields)` → `updateCustomBuild()`. Иначе пришлось бы как-то показывать несохранённые правки
|
||||
при переключении между сборками в списке.
|
||||
|
||||
#### askRemove(int index) : void
|
||||
Чтобы открытие сборки не записывало её саму в себя, на время `selectBuild()` выставляется
|
||||
`loading`, и `commit()` в это время ничего не делает.
|
||||
|
||||
Спрашивает подтверждение перед удалением: оно необратимо и уносит с собой архив сборки. Запрашивает
|
||||
`customBuildRemovalInfo()` и наполняет окно подтверждения именем сборки и тремя признаками — есть
|
||||
ли у неё архив, активна ли она сейчас и последняя ли она. Индекс запоминается в самом окне
|
||||
подтверждения, потому что к моменту ответа строка списка под курсором может быть уже другой.
|
||||
## Модлоадеры
|
||||
|
||||
Окно подтверждения объясняет последствия по этим признакам: вместе со сборкой удалится её архив, и
|
||||
моды, конфиги и миры восстановить будет нельзя; активная сборка владеет содержимым `.minecraft`,
|
||||
оно будет очищено, а на его место развернётся следующая сборка; для последней сборки содержимое
|
||||
`.minecraft` остаётся на месте.
|
||||
`loaderPanel` держит четыре строки и следит, чтобы был отмечен не больше чем один модлоадер: игра
|
||||
запускается с одним профилем из `versions/`. Ни одной отметки — ванильная сборка.
|
||||
|
||||
#### performRemove(int index) : void
|
||||
- `applyBuild(loader, loaderVersion)` — раздать данные сборки строкам;
|
||||
- `selectedRow()` — отмеченная строка или `null`;
|
||||
- `keepOnly(row)` — снять отметки с остальных (на `userChecked` строки);
|
||||
- `commitSelection()` — записать `loader`, `loaderVersion` и пустой `resolvedVersionId` (на
|
||||
`changed` строки). Профиль модлоадера появится только после установки, а до этого сборка
|
||||
запускается как ванильная.
|
||||
|
||||
Удаляет сборку через бэкенд и восстанавливает состояние карточки: если сборок не осталось,
|
||||
сбрасывает `editIndex` в `-1`, иначе открывает соседнюю — ту же позицию или последнюю оставшуюся.
|
||||
Строки получают `gameVersion` привязкой к выбранной версии, поэтому смена версии игры сама
|
||||
перезапрашивает их списки.
|
||||
|
||||
#### commit(var fields) : void
|
||||
## Удаление
|
||||
|
||||
Сохраняет часть полей сборки: передаёт `QVariantMap` в `updateCustomBuild()` и обновляет строку
|
||||
состояния. Ничего не делает во время заполнения карточки (`loading`) и при пустом `editIndex` —
|
||||
это и есть защита от записи при открытии сборки.
|
||||
`askRemove(index)` берёт `customBuildRemovalInfo()` и открывает подтверждение. Текст зависит от
|
||||
трёх признаков:
|
||||
|
||||
Вызывается по завершении правки каждого поля: имени, адреса сервера, версии Minecraft и выбора
|
||||
модлоадера.
|
||||
- `hasArchive` — вместе со сборкой удалится её архив, моды, конфиги и миры будет не вернуть;
|
||||
- `active` — содержимое `.minecraft` принадлежит этой сборке, оно будет очищено, и на его место
|
||||
развернётся следующая;
|
||||
- `lastOne` — для последней сборки содержимое `.minecraft` остаётся как есть.
|
||||
|
||||
#### refreshStatus() : void
|
||||
Индекс запоминается в самом окне подтверждения, потому что к моменту ответа под курсором может
|
||||
оказаться другая строка.
|
||||
|
||||
Пересчитывает строку комплектности под карточкой. Спрашивает у бэкенда `checkInstallation()` и
|
||||
показывает либо сообщение о готовности к запуску, либо число недостающих файлов вместе с первым из
|
||||
них. При пустом `editIndex` очищает строку.
|
||||
`performRemove(index)` удаляет сборку и открывает соседнюю (ту же позицию или последнюю), а если
|
||||
сборок не осталось, очищает карточку.
|
||||
|
||||
#### 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()
|
||||
}
|
||||
```
|
||||
`onAboutToShow` просит обновить каталог версий и открывает активную сборку (или первую). Через
|
||||
`Connections` окно слушает `installedVersionsChanged` и обновляет строку комплектности: установка
|
||||
могла закончиться, пока окно открыто.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+8
-81
@@ -1,88 +1,15 @@
|
||||
# DarkCombo
|
||||
# DarkCombo.qml
|
||||
|
||||
## Обзор компонента
|
||||
`ComboBox` в тёмном оформлении окна: фон `#2a2a2a`, зелёная рамка при фокусе, выпадающая панель
|
||||
высотой до 220 px с полосой прокрутки.
|
||||
|
||||
`Minecraft_launcher` — десктопный лаунчер Minecraft на Qt Quick. Всё окно оформлено вручную в
|
||||
тёмной теме, а стиль Qt Quick Controls принудительно выставлен в `Basic` (`main.cpp`), потому что
|
||||
нативные стили игнорируют пользовательские `contentItem` и `background`. Из-за этого каждый
|
||||
стандартный элемент управления, попадающий в интерфейс, приходится переопределять самому.
|
||||
Стрелка взята та же, что у списков профиля и версии на главном окне
|
||||
(`images/Profile_Box/Asset_23/24.svg`), вместо двойного шеврона стиля `Basic`.
|
||||
|
||||
`DarkCombo` — как раз такое переопределение: выпадающий список в общем тёмном стиле окна.
|
||||
Компонент не добавляет логики, он целиком про внешний вид — фон, рамку, стрелку, делегат строки и
|
||||
всплывающую панель. К нему обращаются всюду, где нужен выпадающий список внутри диалогов: выбор
|
||||
версии модлоадера, выбор Java, поля в настройках.
|
||||
Новых свойств нет, используется как обычный `ComboBox`. Есть одно ограничение: делегат выводит
|
||||
`modelData` как текст, поэтому модель должна быть списком строк. `textRole` не поддерживается.
|
||||
|
||||
## Место в проекте и зависимости
|
||||
|
||||
Импортирует `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])
|
||||
}
|
||||
```
|
||||
Используется в [LoaderRow](LoaderRow.md) и в диалогах [Main](Main.md).
|
||||
|
||||
---
|
||||
|
||||
|
||||
+33
-133
@@ -1,148 +1,48 @@
|
||||
# JavaPickerDialog
|
||||
# JavaPickerDialog.qml
|
||||
|
||||
## Обзор компонента
|
||||
Окно выбора сборки Java. Устроено как [VersionPickerDialog](VersionPickerDialog.md): слева виды,
|
||||
справа список с поиском. Отличие в том, что выбранная здесь сборка сразу и скачивается. Версии игры
|
||||
качает сборка, а Java, которой нет на диске, игре ничем не поможет.
|
||||
|
||||
`Minecraft_launcher` — десктопный лаунчер Minecraft на Qt Quick. Разным версиям игры нужны разные
|
||||
версии Java, и лаунчер умеет скачивать их сам: официальные сборки Mojang и сборки Temurin в двух
|
||||
вариантах — JDK и JRE. Держать в голове, какая Java нужна какой версии игры, пользователь не
|
||||
обязан.
|
||||
Открывается из диалога настроек в [Main](Main.md).
|
||||
|
||||
`JavaPickerDialog` — окно выбора сборки Java: слева типы сборок, справа сами версии с поиском.
|
||||
Устроено так же, как [VersionPickerDialog](VersionPickerDialog.md), с одним принципиальным
|
||||
отличием: выбранное здесь ещё и качается. У версий игры загрузку начинает сама сборка, а сборка
|
||||
Java, которой нет на диске, запуску ничем не поможет — поэтому кнопка подтверждения при
|
||||
необходимости сразу ставит выбранную сборку в очередь на скачивание.
|
||||
## Интерфейс
|
||||
|
||||
Окно также показывает, какая версия Java нужна выбранной версии игры, помечает сборки, которые
|
||||
для неё слишком старые, и позволяет удалять скачанное прямо из списка.
|
||||
| | |
|
||||
|---|---|
|
||||
| `backend` (required) | `LauncherBackend` |
|
||||
| `selectedId` | выбранная сборка |
|
||||
| `category` | `java` (Mojang), `jdk`, `jre` (Temurin) |
|
||||
| `filterText`, `installedOnly` | поиск и «только скачанные» |
|
||||
| `requiredMajor` | минимальная Java для версии игры активной сборки; 0 — версия не выбрана |
|
||||
| `openFor(runtimeId, requiredMajor)` | открыть |
|
||||
| `runtimeChosen(runtimeId)` | выбор подтверждён |
|
||||
|
||||
## Место в проекте и зависимости
|
||||
## Как работает
|
||||
|
||||
В начале файла объявлена `pragma ComponentBehavior: Bound`. Импортирует `QtQuick`,
|
||||
`QtQuick.Controls 2.15` и `QtQuick.Layouts 2.15`.
|
||||
`openFor()` сбрасывает поиск, открывает категорию переданной сборки и просит бэкенд обновить
|
||||
каталог Java. По «Отмене» ничего не меняется.
|
||||
|
||||
Работает с C++-типом `LauncherBackend` (`launcherbackend.h`, `QML_ELEMENT` в модуле
|
||||
`Minecraft_launcher`), передаваемым снаружи в свойство `backend`. Читает свойства `javaCatalog` и
|
||||
`javaCatalogLoading`, вызывает `refreshJavaCatalog()`, `installJavaRuntime()` и
|
||||
`removeJavaRuntime()`.
|
||||
Под категориями слева написано, какая Java нужна выбранной версии игры, чтобы не держать это в
|
||||
голове.
|
||||
|
||||
Объявлен в `QML_FILES` модуля (`CMakeLists.txt`); использует ресурс `images/Trash.svg`.
|
||||
Инстанцируется в диалоге настроек внутри [Main](Main.md) — сборка Java выбирается один раз для
|
||||
всего лаунчера, а не отдельно для каждой сборки Minecraft.
|
||||
`javaCatalog` приходит из C++ уже отсортированным: скачанное первым, потом новые версии сверху.
|
||||
Окно только отбирает строки.
|
||||
|
||||
Каталог приходит из C++ уже отсортированным — новые сверху, скачанные первыми, — поэтому окно
|
||||
только отбирает записи и порядок не трогает. Запись каталога содержит поля `id`, `label`, `kind`
|
||||
(тип сборки), `major` (мажорная версия Java числом), `installed`, `downloadable`, `lts`, `sizeMb`,
|
||||
`detail`, `coverage` и `search`.
|
||||
В строке: версия, пометка LTS, какие версии игры она потянет (`coverage`), размер и галочка у
|
||||
скачанных. Сборка ниже `requiredMajor` не прячется, а помечается красноватым и подписью «слишком
|
||||
старая для этой версии». Она может понадобиться другой сборке.
|
||||
|
||||
## Иерархия и роль
|
||||
Кнопка внизу называется «Скачать», если выбранной сборки нет на диске, и «Выбрать», если есть.
|
||||
`acceptSelection()` испускает `runtimeChosen` и, если сборки нет на диске, вызывает
|
||||
`installJavaRuntime()`. Ход загрузки показывает панель главного окна.
|
||||
|
||||
Корневой тип — `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))
|
||||
}
|
||||
```
|
||||
У скачанных сборок есть корзина, и удаление происходит **без подтверждения**:
|
||||
`removeJavaRuntime()` вызывается сразу. Сборка весит около двухсот мегабайт, и без корзины здесь
|
||||
папка лаунчера росла бы незаметно для пользователя. Если удалили сборку, выбранную в настройках,
|
||||
бэкенд очистит `javaRuntime`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+10
-69
@@ -1,75 +1,16 @@
|
||||
# LabelledField
|
||||
# LabelledField.qml
|
||||
|
||||
## Обзор компонента
|
||||
Подпись и под ней поле ввода. Используется в настройках и в карточке сборки.
|
||||
|
||||
`Minecraft_launcher` — десктопный лаунчер Minecraft на Qt Quick с полностью самостоятельно
|
||||
оформленным тёмным интерфейсом. В диалоге настроек и в редакторе сборок много однотипных полей
|
||||
ввода: подпись сверху, поле под ней. `LabelledField` собирает эту пару в один компонент, чтобы
|
||||
отступы, цвета и подсветка фокуса не переписывались в каждом месте заново.
|
||||
| | |
|
||||
|---|---|
|
||||
| `label` | подпись |
|
||||
| `placeholder` | текст-подсказка в пустом поле |
|
||||
| `text` | алиас текста поля |
|
||||
| `validator` | алиас валидатора поля |
|
||||
| `editingFinished()` | правка закончена: поле потеряло фокус или нажат Enter |
|
||||
|
||||
Компонент нужен там, где пользователь вводит короткое значение: имя профиля, объём памяти, путь,
|
||||
аргументы запуска.
|
||||
|
||||
## Место в проекте и зависимости
|
||||
|
||||
Импортирует `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)
|
||||
}
|
||||
```
|
||||
Ширина поля равна ширине компонента, высота 32 px.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+37
-134
@@ -1,149 +1,52 @@
|
||||
# LoaderRow
|
||||
# LoaderRow.qml
|
||||
|
||||
## Обзор компонента
|
||||
Одна строка модлоадера в карточке [BuildsDialog](BuildsDialog.md): чекбокс, а если он отмечен,
|
||||
выпадающий список версий под выбранную версию Minecraft.
|
||||
|
||||
`Minecraft_launcher` — десктопный лаунчер Minecraft на Qt Quick. Кроме чистой игры он умеет
|
||||
ставить модлоадеры: Forge, Fabric, NeoForge и Quilt. В карточке сборки лоадер выбирается
|
||||
чекбоксом, а под ним — конкретная версия лоадера.
|
||||
Совместимость здесь не проверяется. `backend.loaderVersions()` отдаёт список уже под конкретную
|
||||
версию игры, и несовместимых строк в нём нет. Если список пустой, значит, модлоадер эту версию не
|
||||
поддерживает, и вместо пустого выпадающего списка показывается текст.
|
||||
|
||||
`LoaderRow` — одна такая строка: чекбокс с названием лоадера и, когда он отмечен, выпадающий
|
||||
список версий именно под выбранную версию Minecraft. Компонент берёт на себя всю возню со
|
||||
списком версий: подтягивает его из кэша, обновляет по сети, следит, чтобы выбранная версия
|
||||
всегда существовала под текущую версию игры, и словами объясняет случай «лоадер эту версию игры
|
||||
не поддерживает» вместо показа пустого списка.
|
||||
## Интерфейс
|
||||
|
||||
Совместимость компонент не проверяет и проверять не должен: бэкенд отдаёт список, уже собранный
|
||||
под конкретную версию игры, поэтому несовместимой строки в нём не бывает. Пустой список — это и
|
||||
есть отсутствие поддержки.
|
||||
| | |
|
||||
|---|---|
|
||||
| `backend` (required) | `LauncherBackend` |
|
||||
| `loaderKey` (required) | `forge`, `fabric`, `neoforge`, `quilt` |
|
||||
| `title` (required) | подпись у чекбокса |
|
||||
| `gameVersion` | версия Minecraft из карточки; её смена сбрасывает выбор |
|
||||
| `checked` | алиас чекбокса |
|
||||
| `selectedVersion` | выбранная версия модлоадера |
|
||||
| `entries` | строки `{version, label, recommended, stable}` |
|
||||
| `applying` | строка заполняется из сборки; изменения в это время не считаются пользовательскими |
|
||||
| `userChecked()` | пользователь отметил чекбокс. Карточка снимает отметки с остальных строк |
|
||||
| `changed()` | отметка или версия изменились, карточке пора сохранить сборку |
|
||||
| `applyBuild(loader, loaderVersion)` | заполнить из сохранённой сборки без `changed()` |
|
||||
|
||||
## Место в проекте и зависимости
|
||||
Пока версия игры не выбрана, чекбокс выключен.
|
||||
|
||||
Импортирует `QtQuick` и `QtQuick.Controls 2.15`.
|
||||
## applyEntries(list)
|
||||
|
||||
Использует компонент [DarkCombo](DarkCombo.md) из того же QML-модуля — выпадающий список версий в
|
||||
тёмном стиле окна.
|
||||
Единственное место, где меняются `entries` и `selectedVersion`. Через него проходят все три пути:
|
||||
ответ из кэша, ответ сети и заполнение из сборки. Если выбранной версии в новом списке нет, она
|
||||
заменяется первой строкой, а это самая новая версия для этой версии игры. Если при этом значение
|
||||
изменилось и строка не в режиме `applying`, испускается `changed()`.
|
||||
|
||||
Работает с C++-типом `LauncherBackend` (`launcherbackend.h`, зарегистрирован через `QML_ELEMENT`
|
||||
в модуле `Minecraft_launcher`), который передаётся снаружи в свойство `backend`. Из него
|
||||
компонент вызывает `loaderVersions()`, `refreshLoaderVersions()` и `loaderVersionsLoading()`, а
|
||||
также слушает сигнал `loaderVersionsChanged`.
|
||||
Раньше проверку делал только путь из сети, и версия модлоадера, выбранная под прошлую версию игры,
|
||||
доживала до сохранения сборки.
|
||||
|
||||
Объявлен в `QML_FILES` модуля `Minecraft_launcher` (`CMakeLists.txt`). Инстанцируется в
|
||||
[BuildsDialog](BuildsDialog.md) — по одной строке на каждый поддерживаемый лоадер.
|
||||
## Откуда берётся список
|
||||
|
||||
## Иерархия и роль
|
||||
`reload()` сначала берёт то, что уже есть в кэше (`loaderVersions()`), чтобы список появился сразу,
|
||||
а потом просит обновление (`refreshLoaderVersions()`). Ответ приходит в
|
||||
`backend.loaderVersionsChanged(key, game)`. Строка сравнивает пару со своей и игнорирует чужие
|
||||
ответы: от другого модлоадера или от прошлой версии игры.
|
||||
|
||||
Корневой тип — `Column` с расстоянием 4 px. Внутри три потомка, видимость которых
|
||||
взаимоисключающая по нижней части:
|
||||
Под пустым списком написано «загрузка версий…», пока идёт запрос
|
||||
(`loaderVersionsLoading()`), и «не поддерживает версию N» после.
|
||||
|
||||
- `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()
|
||||
}
|
||||
```
|
||||
`applyBuild()` берёт версию из сборки как пожелание. Если под текущую версию игры её нет,
|
||||
`applyEntries()` заменит её на самую новую.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+97
-177
@@ -1,211 +1,131 @@
|
||||
# Main
|
||||
# Main.qml
|
||||
|
||||
## Обзор компонента
|
||||
Главное окно, оно же точка входа: `main.cpp` загружает его через
|
||||
`engine.loadFromModule("BiLauncher", "Main")`. Корень — `Window` 1280×720.
|
||||
|
||||
`Minecraft_launcher` — десктопный лаунчер Minecraft на Qt Quick. `Main.qml` — его главное и
|
||||
единственное настоящее окно: точка входа приложения, которую загружает `main.cpp` вызовом
|
||||
`engine.loadFromModule("Minecraft_launcher", "Main")`.
|
||||
Что в нём есть:
|
||||
|
||||
Окно совмещает четыре роли. Оно держит единственный экземпляр `LauncherBackend` — весь остальной
|
||||
интерфейс получает его от главного окна. Оно рисует сам экран запуска: фоновая картинка, большая
|
||||
кнопка игры по центру, выпадающий список профилей, кнопка активной сборки, кнопки папки модов,
|
||||
настроек и сезонных сборок. Оно показывает обратную связь — всплывающую плашку сообщений и две
|
||||
панели хода долгих операций. И наконец, оно объявляет диалоги, которые не вынесены в отдельные
|
||||
файлы: создание и редактирование профиля, ввод кода двухфакторной аутентификации и настройки
|
||||
запуска.
|
||||
- единственный экземпляр `LauncherBackend` и все обработчики его сигналов;
|
||||
- экран запуска: фон, кнопка игры по центру, список профилей, кнопка активной сборки, кнопки папки
|
||||
модов, настроек и сезонных сборок;
|
||||
- плашка сообщений и две панели прогресса ([ProgressPanel](ProgressPanel.md));
|
||||
- диалоги, которые не вынесены в отдельные файлы: новый профиль, редактирование профиля, код
|
||||
двухфакторной аутентификации, настройки запуска.
|
||||
|
||||
## Место в проекте и зависимости
|
||||
Отдельными файлами сделаны [BuildsDialog](BuildsDialog.md),
|
||||
[SeasonalBuildsDialog](SeasonalBuildsDialog.md), [JavaPickerDialog](JavaPickerDialog.md) и
|
||||
[MicrosoftLoginDialog](MicrosoftLoginDialog.md). Окно передаёт им бэкенд через свойство `backend`.
|
||||
|
||||
Импортирует `QtQuick`, `QtQuick.Layouts 2.15`, `QtQuick.Controls 2.15` и сам QML-модуль проекта
|
||||
`Minecraft_launcher`, из которого приходит тип `LauncherBackend`.
|
||||
Стиль Qt Quick Controls — `Basic` (выставлен в `main.cpp`), и почти у каждого контрола здесь своё
|
||||
оформление.
|
||||
|
||||
Инстанцирует четыре компонента модуля: [ProgressPanel](ProgressPanel.md) (дважды),
|
||||
[SeasonalBuildsDialog](SeasonalBuildsDialog.md), [BuildsDialog](BuildsDialog.md),
|
||||
[JavaPickerDialog](JavaPickerDialog.md), а также [DarkCombo](DarkCombo.md) и
|
||||
[LabelledField](LabelledField.md) внутри своих диалогов.
|
||||
[MicrosoftLoginDialog](MicrosoftLoginDialog.md) создаётся динамически — см. ниже.
|
||||
## Раскладка
|
||||
|
||||
Стиль Qt Quick Controls принудительно выставлен в `Basic` в `main.cpp`, потому что нативные стили
|
||||
игнорируют пользовательские `contentItem` и `background`; поэтому в этом файле почти каждый
|
||||
элемент управления переопределяет своё оформление вручную.
|
||||
Всё стоит на якорях вокруг кнопки запуска: список профилей слева сверху от неё, кнопка активной
|
||||
сборки справа сверху, кнопки папки и настроек под списком профилей. Кнопка сезонных сборок стоит в
|
||||
правом нижнем углу, потому что остальные углы заняты.
|
||||
|
||||
Использует ресурсы из `RESOURCES` QML-модуля: фоновую картинку, три состояния кнопки запуска, по
|
||||
три состояния кнопок папки и настроек, стрелки выпадающих списков, `images/Trash.svg` и
|
||||
`images/Pencil.svg`.
|
||||
Обе панели прогресса (загрузки и смены сборки) стоят в левом нижнем углу на одних и тех же якорях.
|
||||
Одновременно они не появляются: пока лаунчер занят загрузкой, бэкенд не начнёт смену сборки, и
|
||||
наоборот. Панель смены сборки отменить нельзя.
|
||||
|
||||
## Иерархия и роль
|
||||
У заголовков и подвалов всех диалогов корнем служит `Item` с явным `implicitHeight`. У
|
||||
`Rectangle` `implicitHeight` всегда 0, и `Dialog` не смог бы посчитать свою высоту.
|
||||
|
||||
Корневой тип — `Window` размером 1280×720 px, видимое при старте. Это не переиспользуемый
|
||||
компонент, а точка входа приложения, поэтому раздел с примером использования здесь неприменим.
|
||||
## Функции
|
||||
|
||||
Раскладка держится на якорях относительно центральной кнопки запуска: список профилей — слева
|
||||
сверху от неё, кнопка активной сборки — справа сверху, кнопки папки и настроек — под списком
|
||||
профилей. Кнопка сезонных сборок стоит в правом нижнем углу: это единственная свободная часть
|
||||
окна, потому что панели хода работ висят слева, а всё остальное собрано вокруг кнопки запуска.
|
||||
**`showToast(text, color, timeout)`** — показать сообщение в плашке. `timeout` в миллисекундах,
|
||||
по умолчанию 4000. `0` значит «держать до следующего сообщения». Так показываются шаги запуска,
|
||||
чтобы сообщение не исчезало посреди долгой операции.
|
||||
|
||||
Панели загрузки и смены сборки имеют одни и те же якоря — они взаимоисключающи по построению:
|
||||
признак занятости бэкенда не даёт начать переключение во время установки и наоборот. Панель смены
|
||||
сборки объявлена неотменяемой: отступать после очистки `.minecraft` некуда, операцию нужно довести
|
||||
до конца.
|
||||
**`openMicrosoftLogin(url)`** — открыть окно входа Microsoft. Окно создаётся при первом вызове
|
||||
через `Qt.createComponent()` и хранится в `microsoftLoginDialog`. Объявить его прямо в файле
|
||||
нельзя: `MicrosoftLoginDialog.qml` есть в модуле только в сборках с WebEngine, и в остальных
|
||||
сломалось бы всё главное окно. Если компонент не создался, вход отменяется у бэкенда и показывается
|
||||
сообщение.
|
||||
|
||||
Заголовки и подвалы всех диалогов сделаны на `Item` с явным `implicitHeight`, а не на
|
||||
`Rectangle`: у прямоугольника `implicitHeight` равен нулю независимо от заданной высоты, и
|
||||
`Dialog` не смог бы вычислить свою полную высоту.
|
||||
**`formatMb(bytes)`** — байты в мегабайты с одним знаком после запятой, для панели загрузки.
|
||||
|
||||
## Свойства
|
||||
## Сигналы бэкенда
|
||||
|
||||
| Свойство | Тип | По умолчанию | Обязательное | Описание |
|
||||
|----------|-----|--------------|--------------|----------|
|
||||
| `microsoftLoginDialog` | `var` | `null` | Нет | Созданный по требованию экземпляр окна входа Microsoft либо `null`, пока вход ни разу не запускался. Хранится в свойстве, чтобы окно создавалось один раз за сеанс. |
|
||||
| Сигнал | Что делает окно |
|
||||
|--------|-----------------|
|
||||
| `launched` | зелёное сообщение «запущено» |
|
||||
| `launchProgress` | сообщение без таймаута |
|
||||
| `launchError` | красное сообщение на 8 секунд |
|
||||
| `twoFactorRequired` | открывает диалог кода |
|
||||
| `gameFinished` | сообщение о закрытии игры; если код не 0 или процесс упал, красное и с кодом |
|
||||
| `microsoftLoginUrlReady` | `openMicrosoftLogin()` |
|
||||
| `microsoftLoginSucceeded` | сообщение. Выбор в списке профилей не трогает: новый профиль уже выбран тем, кто его создал, а повторный вход мог быть не для последнего профиля |
|
||||
| `microsoftLoginFailed` | красное сообщение на 8 секунд |
|
||||
| `microsoftReloginRequired` | сразу `startMicrosoftLogin()` для этого профиля |
|
||||
| `gameOutput` | `console.log` |
|
||||
| `seasonalInstallFinished` | сообщение «сборка установлена, можно играть» |
|
||||
| `javaRuntimeInstalled` | обновляет строку Java в настройках |
|
||||
|
||||
### Внутренние диалоги и их состояние
|
||||
Кнопка запуска выключена, пока `busy` или `gameRunning`. Кнопка активной сборки выключена во время
|
||||
игры и смены сборки.
|
||||
|
||||
`Main.qml` объявляет четыре диалога прямо в файле. Их свойства — часть состояния главного окна.
|
||||
## Профили
|
||||
|
||||
**Диалог редактирования профиля** (`editProfileDialog`):
|
||||
Выпадающий список профилей переделан целиком. Наверху всплывающей панели закреплена кнопка
|
||||
«+ Добавить профиль», под ней список. У каждой строки при наведении появляются карандаш (открывает
|
||||
редактирование через `openFor()`) и корзина (`removeProfile()`).
|
||||
|
||||
| Свойство | Тип | По умолчанию | Описание |
|
||||
|----------|-----|--------------|----------|
|
||||
| `editIndex` | `int` | `-1` | Индекс редактируемого профиля; `-1` — диалог не открыт ни для кого. |
|
||||
| `msProfile` | `bool` | `false` | Открытый профиль имеет тип «Microsoft». |
|
||||
| `msLinked` | `bool` | `false` | У профиля есть действующая сессия Microsoft — от этого зависит строка статуса и подпись кнопки входа. |
|
||||
| `msName` | `string` | `""` | Ник, полученный при официальной авторизации. Показывается отдельным полем только для чтения, а не подменой поля логина: привязка сломалась бы первым же вводом в поле логина обычного профиля. |
|
||||
Типы входа во всех списках идут в одном порядке: 0 — `offline`, 1 — `elyby`, 2 — `microsoft`.
|
||||
Модель берётся из `Loc.t.profile.authTypes`, и порядок в каталоге важен, потому что код сравнивает
|
||||
`currentIndex` с 1 и 2. Без WebEngine третий пункт отрезается через `.slice(0, 2)`. В окне
|
||||
редактирования он остаётся, если профиль уже лицензионный: иначе в сборке без WebEngine такой
|
||||
профиль молча стал бы офлайновым.
|
||||
|
||||
**Диалог двухфакторной аутентификации** (`twoFactorDialog`):
|
||||
**Новый профиль.** `addProfile()`, новый профиль выбирается в списке. Если тип «Microsoft», сразу
|
||||
начинается вход, потому что без него такой профиль бесполезен. Скрытые поля при сохранении не
|
||||
читаются: в них мог остаться текст, набранный до смены типа.
|
||||
|
||||
| Свойство | Тип | По умолчанию | Описание |
|
||||
|----------|-----|--------------|----------|
|
||||
| `profileName` | `string` | `""` | Имя профиля, для которого запрошен код; подставляется в текст просьбы. |
|
||||
**Редактирование.** Дополнительные свойства диалога: `editIndex`, `msProfile`, `msLinked` (есть ли
|
||||
сессия Microsoft), `msName` (ник из аккаунта). Ник показывается отдельным полем только для чтения,
|
||||
а не подставляется в поле логина. Иначе привязка оборвалась бы при первом же вводе в это поле у
|
||||
обычного профиля. Кнопка входа сначала сохраняет профиль с типом `microsoft` через
|
||||
`updateProfile()` и только потом вызывает `startMicrosoftLogin()`. Тип могли переключить только
|
||||
что, и без этого токены достались бы профилю другого типа.
|
||||
|
||||
**Диалог настроек** (`settingsDialog`):
|
||||
## Двухфакторная аутентификация
|
||||
|
||||
| Свойство | Тип | По умолчанию | Описание |
|
||||
|----------|-----|--------------|----------|
|
||||
| `javaRuntimeId` | `string` | `""` | Выбранная сборка Java из папки лаунчера. Живёт в свойстве, а не в поле ввода: её выбирают в отдельном окне, а записывается она только по «Сохранить». Пустая строка означает «искать Java в системе». |
|
||||
| `javaRuntimeInfo` | `var` | `null` | Подробности выбранной сборки для строки поля. Не привязка: `javaRuntimeInfo()` — обычный вызов, и сам он не пересчитается, когда сборка докачается, поэтому значение обновляется по событиям. |
|
||||
Диалог показывает имя профиля (`profileName`) и поле для кода. «Подтвердить» вызывает
|
||||
`submitTwoFactorCode()`, «Отмена» — `cancelPendingLaunch()`. Если отмену не вызвать, запуск так и
|
||||
повиснет.
|
||||
|
||||
Типы профилей во всех выпадающих списках кодируются одинаково: позиция 0 — `offline`
|
||||
(офлайн, без пароля), 1 — `elyby` (Ely.by, с логином и паролем), 2 — `microsoft` (лицензия).
|
||||
Третья позиция показывается, только когда лаунчер собран с Qt WebEngine; в диалоге редактирования
|
||||
она показывается ещё и тогда, когда профиль уже сохранён как лицензионный — иначе в сборке без
|
||||
WebEngine он молча стал бы офлайновым.
|
||||
## Настройки
|
||||
|
||||
## Сигналы
|
||||
`settingsDialog.load()` читает `settings()` и `detectedJava()`, раскладывает значения по полям и
|
||||
открывает диалог. «Сохранить» собирает всё в одну карту и отдаёт в `updateSettings()`.
|
||||
|
||||
Собственных сигналов главное окно не объявляет.
|
||||
- **Язык** стоит первым, потому что относится ко всему приложению, а не к запуску игры. В модели
|
||||
переведённые подписи, рядом лежит непереводимый список `codes` (`system`, `ru`, `en`). Язык
|
||||
применяется по «Сохранить» и сразу, без перезапуска.
|
||||
- **Папка игры**, а под полем — путь, который получится на самом деле (`resolvedGameDir`). Путь
|
||||
хранится в свойстве, а не записывается в текст напрямую, иначе подпись не обновилась бы при смене
|
||||
языка.
|
||||
- **Java.** Поле открывает [JavaPickerDialog](JavaPickerDialog.md) с текущим выбором и требованием
|
||||
активной сборки (`requiredJavaMajor()`). Крестик сбрасывает выбор, и тогда Java ищется в системе.
|
||||
Выбор хранится в `javaRuntimeId` и записывается только по «Сохранить». `javaRuntimeInfo`
|
||||
обновляется при смене `javaRuntimeId` и по `javaRuntimeInstalled`, потому что
|
||||
`javaRuntimeInfo()` — обычный вызов, а не привязка. Если сборка выбрана, запуск идёт ею, а путь
|
||||
из соседнего поля остаётся запасным вариантом.
|
||||
- Память, размер окна, аргументы JVM, полноэкранный режим.
|
||||
- Ниже для справки список найденных в системе java.
|
||||
|
||||
## Методы
|
||||
|
||||
#### showToast(string text, color color, int timeout) : void
|
||||
|
||||
Показывает единую всплывающую плашку сообщений: через неё проходят сообщения о ходе запуска,
|
||||
ошибки и статус игры. Задаёт текст и цвет фона и перезапускает таймер скрытия.
|
||||
|
||||
Параметр `timeout` — время показа в миллисекундах. Пропущенное значение означает четыре секунды;
|
||||
`0` означает «держать до следующего сообщения» — так показываются промежуточные шаги запуска, чтобы
|
||||
сообщение не исчезало посреди долгой операции.
|
||||
|
||||
#### openMicrosoftLogin(url) : void
|
||||
|
||||
Открывает окно входа в аккаунт Microsoft на переданном адресе, создавая его при первом вызове.
|
||||
|
||||
Окно создаётся по требованию, а не вместе с главным: `MicrosoftLoginDialog.qml` попадает в модуль
|
||||
только в сборках с Qt WebEngine, и обычная декларация сломала бы всё главное окно в остальных.
|
||||
Поэтому компонент загружается через `Qt.createComponent()`, и если он не готов — сборка собрана без
|
||||
WebEngine, — вход отменяется у бэкенда, а пользователю показывается сообщение о том, что окно
|
||||
недоступно. При успешном создании окну сразу передаётся бэкенд, а его сигнал `failed`
|
||||
подключается к плашке сообщений.
|
||||
|
||||
#### formatMb(bytes) : string
|
||||
|
||||
Переводит байты в мегабайты с одним знаком после запятой. Используется в строке подробностей
|
||||
панели загрузки.
|
||||
|
||||
## Взаимодействие с другими компонентами
|
||||
|
||||
### Бэкенд
|
||||
|
||||
Единственный экземпляр `LauncherBackend` объявлен прямо в окне и передаётся всем вложенным
|
||||
диалогам через их свойство `backend`. Главное окно — единственное место, где обрабатываются его
|
||||
сигналы:
|
||||
|
||||
| Сигнал бэкенда | Что делает главное окно |
|
||||
|----------------|-------------------------|
|
||||
| `launched(profileName, buildName, serverUrl)` | показывает зелёное сообщение о запуске |
|
||||
| `launchProgress(message)` | показывает сообщение без таймаута — до следующего шага |
|
||||
| `launchError(message)` | показывает ошибку на восемь секунд |
|
||||
| `twoFactorRequired(profileName)` | открывает диалог ввода кода: Ely.by отклонил пароль с пометкой two factor, и код добирается здесь, чтобы продолжить прерванный запуск |
|
||||
| `gameFinished(exitCode, crashed)` | сообщает о закрытии игры; аварийное завершение показывается красным вместе с кодом выхода |
|
||||
| `microsoftLoginUrlReady(url)` | вызывает `openMicrosoftLogin()` |
|
||||
| `microsoftLoginSucceeded(playerName)` | сообщает об успешном входе. Выбор в списке профилей при этом не трогается: новый профиль уже выбран тем, кто его создал, а повторный вход мог быть и не в последний профиль |
|
||||
| `microsoftLoginFailed(message)` | показывает ошибку на восемь секунд |
|
||||
| `microsoftReloginRequired(profileIndex)` | сразу начинает вход заново для этого профиля |
|
||||
| `gameOutput(line)` | пишет строку в консоль |
|
||||
| `seasonalInstallFinished(seasonalId, buildName)` | сообщает, что сезонная сборка установлена и её можно запускать |
|
||||
| `javaRuntimeInstalled(runtimeId)` | обновляет подробности выбранной сборки Java в настройках |
|
||||
|
||||
Привязки к свойствам бэкенда управляют доступностью интерфейса: кнопка запуска выключена, пока
|
||||
лаунчер занят или игра уже идёт; кнопка активной сборки — пока идёт игра или переключение сборок;
|
||||
подпись на ней берётся из `activeBuildName`, а список профилей — из `profileNames`.
|
||||
|
||||
### Профили
|
||||
|
||||
Выпадающий список профилей переопределён целиком: кнопка «+ Добавить профиль» закреплена сверху
|
||||
всплывающей панели, под ней список, где у строки при наведении появляются карандаш и корзина.
|
||||
Карандаш открывает диалог редактирования (`openFor()` заполняет его через `profileAt()`), корзина
|
||||
вызывает `removeProfile()`.
|
||||
|
||||
Создание профиля вызывает `addProfile()`, выбирает новый профиль в списке и, если тип —
|
||||
«Microsoft», сразу начинает вход: такой профиль без входа бесполезен. Скрытые поля при сохранении
|
||||
не читаются — в них мог остаться текст, набранный до переключения типа профиля.
|
||||
|
||||
В диалоге редактирования кнопка входа перед вызовом `startMicrosoftLogin()` сначала сохраняет
|
||||
профиль вызовом `updateProfile()` с типом `microsoft`: тип мог быть только что переключён, и без
|
||||
этого бэкенд приписал бы токены профилю другого типа.
|
||||
|
||||
### Запуск игры
|
||||
|
||||
Кнопка запуска вызывает `launchGame()` с индексом выбранного профиля и индексом активной сборки.
|
||||
Дальше всё идёт через сигналы бэкенда: промежуточные шаги — в плашку сообщений, запрос кода
|
||||
двухфакторной аутентификации — в отдельный диалог, где подтверждение вызывает
|
||||
`submitTwoFactorCode()`, а отмена — `cancelPendingLaunch()`.
|
||||
|
||||
### Настройки
|
||||
|
||||
Диалог настроек открывается методом `load()`, который читает `settings()` бэкенда и раскладывает
|
||||
значения по полям, а также подставляет разрешённый путь папки игры и список найденных в системе
|
||||
сборок Java (`detectedJava()`). Сохранение собирает все поля в один `QVariantMap` и передаёт его
|
||||
в `updateSettings()`.
|
||||
|
||||
Первым пунктом диалога идёт выбор языка интерфейса — настройка уровня приложения, поэтому она
|
||||
стоит над параметрами запуска. Подписи в модели переводятся, а коды (`system`, `ru`, `en`) лежат
|
||||
рядом отдельным списком `codes` и не переводятся. Применяется язык по кнопке «Сохранить», как и
|
||||
всё остальное в этом диалоге, и сразу же, без перезапуска: см. [Localization](../cpp/Localization.md).
|
||||
|
||||
Разрешённый путь папки игры хранится свойством `resolvedGameDir` диалога, а не присваивается
|
||||
тексту напрямую — иначе подпись не пережила бы смену языка.
|
||||
|
||||
Поле выбора сборки Java открывает [JavaPickerDialog](JavaPickerDialog.md), передавая текущий выбор
|
||||
и требование активной сборки (`requiredJavaMajor()`); крестик справа сбрасывает выбор обратно на
|
||||
поиск Java в системе. Само окно выбора объявлено рядом с настройками, а не внутри них: оно шире и
|
||||
Окно выбора Java объявлено рядом с диалогом настроек, а не внутри него. Оно шире настроек и
|
||||
центрируется по окну лаунчера.
|
||||
|
||||
Выбранная сборка Java — общая настройка лаунчера: когда она задана, запуск идёт ею, а путь к Java
|
||||
из соседнего поля остаётся запасным вариантом.
|
||||
## Кнопки
|
||||
|
||||
### Тексты
|
||||
- папка — `openMinecraftFolder()` (открывает `mods`);
|
||||
- настройки — `settingsDialog.load()`;
|
||||
- сезонные сборки — `seasonalDialog.openCatalog()`;
|
||||
- активная сборка — `buildsDialog.open()`.
|
||||
|
||||
Все подписи, сообщения и подсказки окна берутся из синглтона `Loc`: `Loc.t.домен.вид.имя`.
|
||||
Ни одного текстового литерала в разметке не осталось, `qsTr` не используется. Модель типов входа
|
||||
в диалогах профиля — тоже ключ каталога (`Loc.t.profile.authTypes`), причём порядок значений
|
||||
в нём значим: код сравнивает `currentIndex` с 1 и 2, а вариант без Microsoft получается из той же
|
||||
модели через `.slice(0, 2)`. Подробности — в [Localization](../cpp/Localization.md).
|
||||
|
||||
### Прочие кнопки
|
||||
|
||||
Кнопка папки вызывает `openMinecraftFolder()`, кнопка настроек открывает диалог настроек, кнопка
|
||||
сезонных сборок — [SeasonalBuildsDialog](SeasonalBuildsDialog.md) методом `openCatalog()`, кнопка
|
||||
активной сборки — [BuildsDialog](BuildsDialog.md).
|
||||
Все тексты окна берутся из `Loc.t`, литералов в разметке нет.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+30
-105
@@ -1,120 +1,45 @@
|
||||
# MicrosoftLoginDialog
|
||||
# MicrosoftLoginDialog.qml
|
||||
|
||||
## Обзор компонента
|
||||
Окно входа в аккаунт Microsoft. Работает так же, как официальный лаунчер: страница входа
|
||||
открывается внутри приложения в `WebEngineView`, а лаунчер ждёт, когда браузер перейдёт на
|
||||
`redirect_uri` с кодом в адресе.
|
||||
|
||||
`Minecraft_launcher` — десктопный лаунчер Minecraft на Qt Quick. Он поддерживает несколько
|
||||
способов входа: офлайн-профиль, сервер Ely.by и учётную запись Microsoft. Последний путь требует
|
||||
показать пользователю настоящую страницу входа Microsoft и дождаться, пока браузер уйдёт на
|
||||
`redirect_uri` с кодом авторизации в адресе — ровно так же поступает официальный лаунчер.
|
||||
Файл попадает в QML-модуль, только если CMake нашёл `Qt6::WebEngineQuick`. Поэтому
|
||||
[Main](Main.md) не объявляет окно в разметке, а создаёт его через `Qt.createComponent()` при первом
|
||||
входе (`openMicrosoftLogin()`).
|
||||
|
||||
`MicrosoftLoginDialog` — окно с этой страницей. Внутри него живёт `WebEngineView`; диалог следит
|
||||
за сменой адреса, отдаёт перехваченный код бэкенду и закрывается. Собственной логики разбора
|
||||
адреса у него нет — она в C++, чтобы правила совпадения совпадали с теми, по которым сервис сам
|
||||
строит `redirect_uri`.
|
||||
## Интерфейс
|
||||
|
||||
## Место в проекте и зависимости
|
||||
| | |
|
||||
|---|---|
|
||||
| `backend` (required) | `LauncherBackend` |
|
||||
| `openAt(url)` | открыть страницу входа |
|
||||
| `failed(message)` | вход не удался. Окно ничего не знает о плашке сообщений главного окна и сообщает о неудаче сигналом |
|
||||
|
||||
Импортирует `QtQuick`, `QtQuick.Controls 2.15` и `QtWebEngine`. В начале файла объявлена
|
||||
`pragma ComponentBehavior: Bound`.
|
||||
## Как работает
|
||||
|
||||
Работает с C++-типом `LauncherBackend` (`launcherbackend.h`, `QML_ELEMENT`), который передаётся
|
||||
снаружи в свойство `backend`. Вызывает у него `inspectMicrosoftRedirect()`,
|
||||
`finishMicrosoftLogin()` и `cancelMicrosoftLogin()`.
|
||||
На каждое `urlChanged` вызывается `handleUrl()`. Он передаёт адрес в
|
||||
`backend.inspectMicrosoftRedirect()`. Разбор сделан в C++, чтобы правила совпадали с тем, как
|
||||
[MsaAuthService](../cpp/MsaAuthService.md) строит `redirect_uri`.
|
||||
|
||||
**Особенность сборки.** Это единственный QML-файл проекта, который попадает в модуль условно.
|
||||
`CMakeLists.txt` ищет `Qt6WebEngineQuick` через `find_package(... QUIET)`; модуль объявлен
|
||||
необязательным сознательно — он ставится отдельной галочкой в установщике Qt и тянет за собой
|
||||
WebChannel с Positioning, которых в типовой установке нет. Если модуль найден, файл добавляется в
|
||||
`QML_FILES` и определяется макрос `LAUNCHER_HAS_WEBENGINE`; если нет — лаунчер собирается и
|
||||
работает как прежде, только без входа через Microsoft. В QML это различие видно через свойство
|
||||
`backend.microsoftAvailable`, и интерфейс не должен предлагать этот путь, когда оно ложно.
|
||||
Когда адрес совпал:
|
||||
|
||||
Инстанцируется динамически из [Main](Main.md) — главное окно создаёт диалог по требованию, потому
|
||||
что при сборке без WebEngine самого типа в модуле не существует.
|
||||
- окно закрывается, `codeTaken` выставляется в `true`;
|
||||
- если есть код, вызывается `backend.finishMicrosoftLogin(code)`;
|
||||
- если кода нет, вызывается `backend.cancelMicrosoftLogin()` и испускается `failed` с текстом от
|
||||
Microsoft или общим «вход не завершён».
|
||||
|
||||
## Иерархия и роль
|
||||
`codeTaken` нужен потому, что `WebEngineView` может сообщить об одном и том же адресе несколько
|
||||
раз, а отдавать код дважды нельзя.
|
||||
|
||||
Корневой тип — `Dialog` из Qt Quick Controls: модальный, 560×680 px, по центру родителя, с нулевым
|
||||
внутренним отступом и `closePolicy: Popup.NoAutoClose` — окно нельзя закрыть щелчком мимо или
|
||||
клавишей Escape, выход только через кнопку отмены или успешный вход.
|
||||
Кнопка «Отмена» выставляет `codeTaken`, закрывает окно и вызывает `cancelMicrosoftLogin()`. Щелчок
|
||||
мимо окна и Esc его не закрывают.
|
||||
|
||||
Оформление задано вручную: тёмный фон со скруглением и акцентной рамкой, заголовок с
|
||||
разделительной линией, подвал с кнопкой «Отмена».
|
||||
## Куки
|
||||
|
||||
Содержимое — `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)
|
||||
```
|
||||
Профиль браузера создаётся через `WebEngineProfilePrototype` без `storageName`, то есть только в
|
||||
памяти. Куки живут, пока работает лаунчер, и в общий браузер не попадают. Выбрать другой аккаунт
|
||||
в пределах одного запуска позволяет `prompt=select_account` в адресе входа.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+15
-81
@@ -1,89 +1,23 @@
|
||||
# ProgressPanel
|
||||
# ProgressPanel.qml
|
||||
|
||||
## Обзор компонента
|
||||
Плашка прогресса долгой операции, 320×72, в левом нижнем углу главного окна. Там она не закрывает
|
||||
кнопку запуска, сообщения по центру и кнопки папки и настроек.
|
||||
|
||||
`Minecraft_launcher` — десктопный лаунчер Minecraft на Qt Quick. Почти каждое действие в нём
|
||||
долгое: скачивание версии игры, установка Java, распаковка и архивация папки `.minecraft` при
|
||||
смене сборки, загрузка сезонной сборки. Лаунчер не блокирует окно на это время, поэтому ход
|
||||
операции нужно показывать неотрывно от остального интерфейса.
|
||||
В [Main](Main.md) их две: загрузка (версия, модлоадер, Java, пак) и смена сборки.
|
||||
|
||||
`ProgressPanel` — та самая плашка прогресса. Она размещается в левом нижнем углу главного окна,
|
||||
где не перекрывает кнопку запуска, сообщение по центру и кнопки папки и настроек. Компонент
|
||||
только отображает переданное состояние; сам он ничего не считает и ни за чем не следит.
|
||||
## Интерфейс
|
||||
|
||||
## Место в проекте и зависимости
|
||||
| | |
|
||||
|---|---|
|
||||
| `title` | заголовок, жирным |
|
||||
| `status` | вторая строка, если нет `detail` |
|
||||
| `fraction` | от 0 до 1. −1 — общий объём неизвестен, вместо процентов «…» |
|
||||
| `detail` | вторая строка вместо `status`: мегабайты у загрузки, путь у архивации. Длинный текст обрезается посередине |
|
||||
| `cancellable` | показывать ли крестик; по умолчанию `true` |
|
||||
| `cancelRequested()` | нажат крестик |
|
||||
|
||||
Импортирует `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()
|
||||
}
|
||||
```
|
||||
Панель только показывает данные. Видимость, значения и реакцию на отмену задаёт главное окно: у
|
||||
загрузки крестик вызывает `backend.cancelDownload()`, у смены сборки крестика нет.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+35
-114
@@ -1,134 +1,55 @@
|
||||
# SeasonalBuildsDialog
|
||||
# SeasonalBuildsDialog.qml
|
||||
|
||||
## Обзор компонента
|
||||
Каталог сезонных сборок с сервера лаунчера. Таблица показывает всё, что нужно, чтобы решить,
|
||||
ставить сборку или нет. Остальное делает одна кнопка: создаёт запись сборки, ставит версию игры,
|
||||
модлоадер и Java и раскладывает файлы. Как это устроено, описано в
|
||||
[LauncherBackend](../cpp/LauncherBackend.md#сезонные-сборки).
|
||||
|
||||
`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 несёт описание и размер выбранной сборки (они длинные и в таблицу не
|
||||
помещаются, а решение принимается именно по ним) и две кнопки — обновления списка и установки.
|
||||
Открывается из [Main](Main.md) кнопкой в правом нижнем углу (`openCatalog()`).
|
||||
|
||||
## Свойства
|
||||
|
||||
| Свойство | Тип | По умолчанию | Обязательное | Описание |
|
||||
|----------|-----|--------------|--------------|----------|
|
||||
| `backend` | `var` | — | **Да** | Экземпляр `LauncherBackend` — источник каталога сезонных сборок и исполнитель установки. |
|
||||
| `selectedId` | `string` | `""` | Нет | Идентификатор выбранной строки. Хранится по `id`, а не по индексу: список обновляется под руками, и индекс после обновления указывал бы на другую сборку. |
|
||||
| `columns` | `var` (только чтение) | список из семи колонок | Нет | Описание таблицы: массив объектов с полями `key` (поле записи), `title` (заголовок), `width` (ширина в пикселях) и `align` (выравнивание). Колонки: название, версия, загрузчик, число модов, начало и конец сезона, статус. Ширины собраны в одном месте, потому что их повторяют и шапка, и делегат строки. |
|
||||
| `entries` | `var` (только чтение) | `backend.seasonalCatalog` | Нет | Строки каталога напрямую из бэкенда. Отбора и сортировки в окне нет. |
|
||||
| `selectedEntry` | `var` (только чтение) | вычисляется | Нет | Полная запись выбранной сборки или `null`, если ничего не выбрано. От неё зависят подвал и доступность кнопки установки. |
|
||||
| | |
|
||||
|---|---|
|
||||
| `backend` (required) | `LauncherBackend` |
|
||||
| `selectedId` | выбранная строка, по `id` сборки |
|
||||
| `columns` | описание колонок: ключ, заголовок, ширина, выравнивание |
|
||||
|
||||
## Сигналы
|
||||
Выбранная строка хранится по `id`, а не по индексу: список может обновиться, пока окно открыто,
|
||||
и индекс начал бы указывать на другую сборку.
|
||||
|
||||
Собственных сигналов компонент не объявляет. Результат работы окна виден через состояние бэкенда:
|
||||
установка меняет список сборок и запускает загрузку, за ходом которой следит главное окно.
|
||||
## Таблица
|
||||
|
||||
## Методы
|
||||
Колонки: название, версия, модлоадер с версией, число модов, начало и конец сезона, статус
|
||||
(«не установлена», «установлена», «доступно обновление»). Ширины заданы в одном месте, `columns`,
|
||||
и их используют и шапка, и строки.
|
||||
|
||||
#### openCatalog() : void
|
||||
Таблица собрана из `Row` с колонками фиксированной ширины, а не из `TableView`. В проекте нет ни
|
||||
одной модели на `QAbstractItemModel`, строки приходят из C++ готовыми `QVariantMap`, и заводить
|
||||
модель ради семи колонок не стоит.
|
||||
|
||||
Открывает окно. Обновление каталога происходит само в обработчике `onAboutToShow`, который просит
|
||||
у бэкенда `refreshSeasonalCatalog(false)` — без принудительного обхода кэша: свежий кэш отвечает
|
||||
без сети, поэтому вызов при каждом открытии ничего не стоит.
|
||||
Строки приходят уже отсортированными и сведёнными с локальными записями: окно ничего не считает.
|
||||
|
||||
#### formatMb(bytes) : string
|
||||
Пустой список и ошибка сервера выглядят одинаково пусто, поэтому причина пишется прямо на месте
|
||||
строк: «загрузка…», «сборок пока нет» или текст `seasonalCatalogError` красным.
|
||||
|
||||
Переводит размер в байтах в строку с мегабайтами и одним знаком после запятой. Для нулевого или
|
||||
отсутствующего значения возвращает пустую строку, чтобы размер просто не попал в строку подвала.
|
||||
Под таблицей описание выбранной сборки, ревизия, размер архива и установленная ревизия, если она
|
||||
есть. Описания длинные и в таблицу не помещаются, а решение принимают как раз по ним.
|
||||
|
||||
#### cellText(entry, string key) : string
|
||||
## Кнопки
|
||||
|
||||
Возвращает текст ячейки для записи и ключа колонки. Для всех колонок это значение одноимённого
|
||||
поля записи, приведённое к строке; исключение — колонка загрузчика, где название и версия
|
||||
склеиваются в одну подпись, а при пустой версии остаётся только название.
|
||||
- **Обновить список** — `refreshSeasonalCatalog(true)`, мимо кэша. Выключена, пока идёт загрузка.
|
||||
- **Установить / Обновить** — `installSeasonalBuild(id)`. Подпись зависит от `updateAvailable`.
|
||||
Выключена, пока лаунчер чем-то занят (`busy`): установка занимает и панель загрузки, и
|
||||
`.minecraft` целиком.
|
||||
|
||||
#### installSelected() : void
|
||||
Двойной щелчок по строке делает то же, что кнопка установки.
|
||||
|
||||
Ставит выбранную сборку. Ничего не делает, если строка не выбрана или лаунчер занят другой
|
||||
операцией: установка занимает и панель загрузки, и `.minecraft` целиком, поэтому вторую начинать
|
||||
нельзя. Иначе вызывает `installSeasonalBuild()` у бэкенда.
|
||||
Каталог обновляется при каждом открытии окна (`refreshSeasonalCatalog(false)`). Свежий кэш отвечает
|
||||
без сети.
|
||||
|
||||
Вызывается кнопкой установки и двойным щелчком по строке.
|
||||
|
||||
## Взаимодействие с другими компонентами
|
||||
|
||||
**Со стороны родителя.** [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()
|
||||
}
|
||||
```
|
||||
Пока идёт установка, окно закрывается только кнопкой. Отменить установку можно на панели загрузки
|
||||
главного окна, и случайный щелчок мимо окна не должен её закрыть.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+40
-139
@@ -1,157 +1,58 @@
|
||||
# VersionPickerDialog
|
||||
# VersionPickerDialog.qml
|
||||
|
||||
## Обзор компонента
|
||||
Окно выбора версии Minecraft. Слева категории, справа список версий выбранной категории с поиском.
|
||||
|
||||
`Minecraft_launcher` — десктопный лаунчер Minecraft на Qt Quick. Каталог версий Mojang — это около
|
||||
тысячи записей: релизы, снапшоты, старые беты и альфы. Раньше выбор версии был обычным выпадающим
|
||||
списком на всю эту тысячу, и найти в нём, скажем, бету 1.7 можно было только поиском по точному
|
||||
номеру.
|
||||
Раньше на этом месте был выпадающий список на тысячу версий, и найти в нём, например, бету 1.7
|
||||
можно было только набрав точный номер. Категории делят каталог на части разумного размера, а поиск
|
||||
работает внутри выбранной.
|
||||
|
||||
`VersionPickerDialog` заменил тот список отдельным окном: слева категории, справа сами версии с
|
||||
поиском сверху. Категории делят каталог на обозримые части, а поиск работает внутри выбранной.
|
||||
Помимо выбора окно умеет удалять уже скачанные версии — с предупреждением о последствиях, потому
|
||||
что версия весит десятки мегабайт, а поверх неё могут стоять профили модлоадеров.
|
||||
Используется в [BuildsDialog](BuildsDialog.md) для поля версии игры.
|
||||
|
||||
Окно открывается из карточки сборки, когда пользователь выбирает, на какой версии Minecraft
|
||||
собирается играть.
|
||||
## Интерфейс
|
||||
|
||||
## Место в проекте и зависимости
|
||||
| | |
|
||||
|---|---|
|
||||
| `backend` (required) | `LauncherBackend` |
|
||||
| `selectedId` | выбранная версия |
|
||||
| `category` | `release`, `snapshot`, `old_beta`, `old_alpha` или `other` |
|
||||
| `filterText` | строка поиска |
|
||||
| `installedOnly` | показывать только скачанные |
|
||||
| `openFor(versionId)` | открыть с этой версией |
|
||||
| `versionChosen(versionId)` | выбор подтверждён |
|
||||
|
||||
В начале файла объявлена `pragma ComponentBehavior: Bound`. Импортирует `QtQuick`,
|
||||
`QtQuick.Controls 2.15` и `QtQuick.Layouts 2.15`.
|
||||
Сигнал называется не `accepted`, потому что такой сигнал у `Dialog` уже есть, и переопределить его
|
||||
нельзя.
|
||||
|
||||
Работает с 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).
|
||||
`openFor()` сбрасывает поиск, переключается на категорию переданной версии (а не всегда на
|
||||
релизы), просит бэкенд обновить каталог (`refreshVersionCatalog()`) и прокручивает список к
|
||||
выбранной строке. Если версии нет в каталоге, окно открывается на релизах. По «Отмене» ничего не
|
||||
меняется: результат уходит наружу только через `versionChosen`.
|
||||
|
||||
Каталог приходит из C++ уже отсортированным (новые сверху), поэтому окно только отбирает записи и
|
||||
порядок не трогает. Каждая запись каталога — объект с полями `id` (идентификатор версии), `label`
|
||||
(подпись строки), `category` (ключ категории), `installed` (скачана ли) и `search`
|
||||
(предвычисленная строка для поиска в нижнем регистре).
|
||||
`versionCatalog` приходит из C++ уже отсортированным, новые сверху. `visibleEntries` только
|
||||
отбирает строки по категории, флажку «только скачанные» и подстроке в поле `search`, а порядок не
|
||||
меняет. В «Прочие» попадают профили модлоадеров и всё, чего нет в манифесте Mojang.
|
||||
|
||||
## Иерархия и роль
|
||||
С клавиатуры: фокус сразу в поиске, стрелки вверх и вниз двигают выбор, Enter подтверждает, Esc
|
||||
закрывает. Мышью выбор подтверждается двойным щелчком или кнопкой.
|
||||
|
||||
Корневой тип — `Dialog`: модальный, 720×480 px, нулевой внутренний отступ, закрывается по Escape и
|
||||
щелчку мимо. Оформление задано вручную — тёмный фон со скруглением и акцентной рамкой.
|
||||
Если список пуст, на его месте написано почему: каталог ещё грузится, каталог не загрузился или по
|
||||
запросу ничего нет.
|
||||
|
||||
Содержимое — `RowLayout` из двух частей, разделённых вертикальной линией: колонка категорий
|
||||
шириной 150 px (`Repeater` по `categories`) и область версий. В области версий сверху поле поиска,
|
||||
внизу флажок «Только установленные», между ними `ListView` со строками версий. Строка показывает
|
||||
подпись, галочку для установленной версии и корзину удаления; галочка рядом со строкой —
|
||||
единственное место, где видно, что именно скачано.
|
||||
## Удаление версии
|
||||
|
||||
Отдельно объявлен вложенный `Dialog` подтверждения удаления шириной 420 px с красной рамкой. Он
|
||||
привязан к тому же родителю, что и само окно, и центрируется в нём вручную, чтобы не оказаться
|
||||
внутри области выбора.
|
||||
У скачанных версий есть корзина. Версия занимает от десятков мегабайт, а это окно — единственное
|
||||
место, где видно, что именно скачано.
|
||||
|
||||
Пустое состояние списка объясняется текстом по центру, который различает три случая: каталог ещё
|
||||
грузится, каталог пуст (нет соединения) и по фильтру ничего не найдено.
|
||||
`askRemove(versionId)` берёт `versionRemovalInfo()` и открывает подтверждение. В нём указаны
|
||||
размер, профили модлоадеров, которые без этой версии не запустятся (`dependents`), и сборки,
|
||||
которые на неё ссылаются (`builds`). id запоминается в окне подтверждения: к моменту ответа под
|
||||
курсором может оказаться другая строка.
|
||||
|
||||
## Свойства
|
||||
|
||||
| Свойство | Тип | По умолчанию | Обязательное | Описание |
|
||||
|----------|-----|--------------|--------------|----------|
|
||||
| `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)
|
||||
}
|
||||
```
|
||||
`performRemove(versionId)` вызывает `removeVersion()`. Если удалили выбранный профиль модлоадера,
|
||||
он пропадает из каталога совсем (он был в нём только потому, что установлен), и выбор
|
||||
сбрасывается.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in new issue
Block a user