From e8e0c5159de6099e49f2c2cb3a16abe75e66a5e9 Mon Sep 17 00:00:00 2001 From: iisaev Date: Sun, 18 Oct 2026 12:41:39 +0300 Subject: [PATCH] init mod --- locale/en.properties | 221 +++ locale/ru.properties | 221 +++ manifest.json | 19 + mod.png | Bin 0 -> 9360 bytes scenes/components/mod_card.tscn | 145 ++ scenes/components/mod_detail_view.tscn | 180 +++ scenes/components/mod_filter_panel.tscn | 43 + scenes/components/mod_grid_group.tscn | 16 + scenes/components/mod_grid_view.tscn | 12 + scenes/mod_collections_screen.tscn | 198 +++ scenes/mod_manager_screen.tscn | 127 ++ scenes/mods_hub_screen.tscn | 39 + src/ModCollectionsScreen.cs | 1894 +++++++++++++++++++++++ src/ModHubScreen.cs | 371 +++++ src/ModManagerScreen.cs | 503 ++++++ src/Model/ModActionKind.cs | 32 + src/Model/ModActions.cs | 44 + src/Model/ModCardData.cs | 155 ++ src/Model/ModChoice.cs | 21 + src/Model/ModFilterState.cs | 110 ++ src/Model/ModItemStatus.cs | 30 + src/Model/ModManagerModel.cs | 264 ++++ src/Model/ModQueries.cs | 58 + src/Model/ModSortMode.cs | 15 + src/UI/CollectionEdge.cs | 31 + src/UI/CollectionEdgeLayer.cs | 121 ++ src/UI/CollectionGroupCard.cs | 325 ++++ src/UI/CollectionModChip.cs | 176 +++ src/UI/CollectionResizeHandle.cs | 48 + src/UI/CollectionSchemeCanvas.cs | 567 +++++++ src/UI/CollectionsGallery.cs | 511 ++++++ src/UI/ModCard.cs | 290 ++++ src/UI/ModDetailView.cs | 341 ++++ src/UI/ModFilterPanel.cs | 153 ++ src/UI/ModGridGroup.cs | 44 + src/UI/ModGridView.cs | 132 ++ src/UI/ModManagerTheme.cs | 81 + src/Util/CollectionDeletion.cs | 59 + src/Util/CollectionStateNames.cs | 40 + src/Util/ModActionNames.cs | 27 + src/Util/ModCatalogSource.cs | 46 + src/Util/ModCategoryNames.cs | 69 + src/Util/ModCollectionLinks.cs | 25 + src/Util/ModPreviewLoader.cs | 65 + src/Util/ModStatusNames.cs | 54 + ui/icons/mod_manager.png | Bin 0 -> 624 bytes 46 files changed, 7923 insertions(+) create mode 100644 locale/en.properties create mode 100644 locale/ru.properties create mode 100644 manifest.json create mode 100644 mod.png create mode 100644 scenes/components/mod_card.tscn create mode 100644 scenes/components/mod_detail_view.tscn create mode 100644 scenes/components/mod_filter_panel.tscn create mode 100644 scenes/components/mod_grid_group.tscn create mode 100644 scenes/components/mod_grid_view.tscn create mode 100644 scenes/mod_collections_screen.tscn create mode 100644 scenes/mod_manager_screen.tscn create mode 100644 scenes/mods_hub_screen.tscn create mode 100644 src/ModCollectionsScreen.cs create mode 100644 src/ModHubScreen.cs create mode 100644 src/ModManagerScreen.cs create mode 100644 src/Model/ModActionKind.cs create mode 100644 src/Model/ModActions.cs create mode 100644 src/Model/ModCardData.cs create mode 100644 src/Model/ModChoice.cs create mode 100644 src/Model/ModFilterState.cs create mode 100644 src/Model/ModItemStatus.cs create mode 100644 src/Model/ModManagerModel.cs create mode 100644 src/Model/ModQueries.cs create mode 100644 src/Model/ModSortMode.cs create mode 100644 src/UI/CollectionEdge.cs create mode 100644 src/UI/CollectionEdgeLayer.cs create mode 100644 src/UI/CollectionGroupCard.cs create mode 100644 src/UI/CollectionModChip.cs create mode 100644 src/UI/CollectionResizeHandle.cs create mode 100644 src/UI/CollectionSchemeCanvas.cs create mode 100644 src/UI/CollectionsGallery.cs create mode 100644 src/UI/ModCard.cs create mode 100644 src/UI/ModDetailView.cs create mode 100644 src/UI/ModFilterPanel.cs create mode 100644 src/UI/ModGridGroup.cs create mode 100644 src/UI/ModGridView.cs create mode 100644 src/UI/ModManagerTheme.cs create mode 100644 src/Util/CollectionDeletion.cs create mode 100644 src/Util/CollectionStateNames.cs create mode 100644 src/Util/ModActionNames.cs create mode 100644 src/Util/ModCatalogSource.cs create mode 100644 src/Util/ModCategoryNames.cs create mode 100644 src/Util/ModCollectionLinks.cs create mode 100644 src/Util/ModPreviewLoader.cs create mode 100644 src/Util/ModStatusNames.cs create mode 100644 ui/icons/mod_manager.png diff --git a/locale/en.properties b/locale/en.properties new file mode 100644 index 0000000..09de939 --- /dev/null +++ b/locale/en.properties @@ -0,0 +1,221 @@ +# mod_manager — mod manager localization + +mod_mod_manager_name = Mod Manager +mod_mod_manager_desc = Nexus-style mod manager: catalog browsing, category filters, install, update and remove. + +mod_manager/menu_title = Mods & Collections +mod_manager/title = Mod Manager +mod_manager/back = Close +mod_manager/back_to_list = Back +mod_manager/search_placeholder = Search mods... +mod_manager/sort_name = By name +mod_manager/sort_author = By author +mod_manager/sort_status = By status +mod_manager/refresh = Refresh catalog +mod_manager/loading = Loading catalog... +mod_manager/results = Mods: {0} + +mod_manager/filters = Filters +mod_manager/reset_filters = Reset +mod_manager/filter_status = Status +mod_manager/filter_categories = Categories +mod_manager/group_by_category = Group by category + +mod_manager/status_installed = Installed +mod_manager/status_not_installed = Not installed +mod_manager/status_update = Update available +mod_manager/status_downgrade = Older version available +mod_manager/status_modified = Modified locally +mod_manager/status_disabled = Disabled +mod_manager/status_conflict = Catalog conflict +mod_manager/conflict_title = Warning +mod_manager/conflict_warning = The same mod with the same version and title is published by different catalogs with different content. One variant may be counterfeit: install only from a trusted source. +mod_manager/source_catalog = Catalog: {0} +mod_manager/source_local = Local mod + +mod_manager/category_story = Story +mod_manager/category_characters = Characters +mod_manager/category_gameplay = Gameplay +mod_manager/category_visual = Visual +mod_manager/category_audio = Audio +mod_manager/category_ui = UI +mod_manager/category_locale = Locale +mod_manager/category_library = Library +mod_manager/category_engine = Engine +mod_manager/category_overhaul = Overhaul +mod_manager/category_tools = Tools +mod_manager/category_editor = Editor +mod_manager/category_debug = Debug +mod_manager/category_other = Other + +mod_manager/action_install = Install +mod_manager/action_update = Update +mod_manager/action_downgrade = Downgrade +mod_manager/action_reinstall = Reinstall +mod_manager/action_delete = Delete +mod_manager/action_enable = Enable +mod_manager/action_disable = Disable + +mod_manager/restart_required = Changes will take effect after restarting the game +mod_manager/progress_installing = Installing "{0}"... +mod_manager/install_failed = Install failed. Check your connection and try again. +mod_manager/delete_title = Delete mod +mod_manager/delete_text = Delete mod "{0}"? This is irreversible and takes effect after restart. +mod_manager/delete_confirm = Delete +mod_manager/delete_failed = Failed to delete the mod. See the log. +mod_manager/cancel = Cancel + +mod_manager/installed_version = Installed: {0} +mod_manager/not_installed_hint = Not installed +mod_manager/latest = latest +mod_manager/installed_marker = installed +mod_manager/no_results = Nothing found +mod_manager/description = Description +mod_manager/depends = Depends on +mod_manager/used_by = Used by +mod_manager/conflicts = Conflicts +mod_manager/changelog = Changelog +mod_manager/version = Version: +mod_manager/self_hint = System mod: cannot be deleted or disabled + +mod_manager/collections_menu_title = Collections +mod_manager/collections_title = Collections +mod_manager/collections_empty = No collections found +mod_manager/collection_result = Mods: {0}/{1} +mod_manager/collection_mechanics = Mechanics +mod_manager/collection_node = Mod +mod_manager/collection_plan = Install plan +mod_manager/collection_apply = Apply collection +mod_manager/collection_plan_ready = Everything is ready +mod_manager/collection_install_count = To install: {0} +mod_manager/collection_enable_count = To enable: {0} +mod_manager/collection_missing = Missing from the catalog: {0} +mod_manager/collection_mismatch = Version mismatch: {0} +mod_manager/collection_optional = optional +mod_manager/collection_externals = Dependencies +mod_manager/collection_external = dependency +mod_manager/collection_node_hint = Select a mod on the graph +mod_manager/collection_requires = Requires: {0} +mod_manager/collection_origin_builtin = built-in +mod_manager/collection_origin_user = local +mod_manager/collection_origin_catalog = catalog +mod_manager/collection_progress = Applying "{0}"... +mod_manager/collection_apply_failed = Failed to apply the collection. See the log. +mod_manager/collection_state_installed = Installed +mod_manager/collection_state_disabled = Disabled +mod_manager/collection_state_catalog = Available in catalog +mod_manager/collection_state_missing = Not found +mod_manager/collection_state_mismatch = Version mismatch + +# Scheme editor (collections pyramid) +mod_manager/collection_add_mechanic = + Mechanic +mod_manager/collection_new_game = + Game +mod_manager/collection_new_game_name = New game +mod_manager/collection_new_game_desc = Assemble a game from mechanics: add blocks and mods. +mod_manager/collection_new_mechanic_name = New mechanic +mod_manager/collection_group_ready = ready +mod_manager/collection_group_empty = empty +mod_manager/collection_group_off = off +mod_manager/collection_links = Links +mod_manager/collection_problems = Problems +mod_manager/collection_tab_inspector = Inspector +mod_manager/collection_tab_problems = Check +mod_manager/collection_tab_plan = Plan +mod_manager/collection_plan_install = to install +mod_manager/collection_plan_enable = to enable +mod_manager/collection_save = Save +mod_manager/collection_reset = Reset +mod_manager/collection_json = JSON +mod_manager/collection_save_ok = Saved to user://collections.json +mod_manager/collection_no_changes = Nothing to save +mod_manager/collection_reset_confirm = Discard unsaved changes? +mod_manager/collection_rename_title = Name and description +mod_manager/collection_field_name = Name +mod_manager/collection_field_note = Description +mod_manager/collection_add_mod = Add mod +mod_manager/collection_empty_mechanic = Empty mechanic +mod_manager/collection_empty_mechanic_desc = Create a contour from scratch: add the name, description and mods manually. +mod_manager/collection_pick_mechanic = Add mechanic +mod_manager/collection_pick_replace = Replace mechanic +mod_manager/collection_choose = Add +mod_manager/collection_replace = Replace +mod_manager/collection_remove = Remove +mod_manager/collection_rename = Rename +mod_manager/collection_create = Create +mod_manager/collection_where = Add to +mod_manager/collection_new_extra = + new "Extra" mechanic +mod_manager/collection_extra_name = Extra +mod_manager/collection_extra_note = Manually added mods. +mod_manager/collection_search_mechanic = Search mechanics... +mod_manager/collection_search_mod = Search mods... +mod_manager/collection_problems_none = No problems - the assembly is consistent. +mod_manager/collection_problem_missing = Block "{0}" not found +mod_manager/collection_problem_cycle = Cycle: "{0}" -> "{1}" +mod_manager/collection_problem_role = Slot "{0}" is claimed twice +mod_manager/collection_problem_require = "{0}": requirement "{1}" is not met +mod_manager/collection_show = Show +mod_manager/collection_hint = Select a contour or a mod. Drag frames by the header, resize by the corner, move mods freely. Drag the empty canvas with the left button to pan. +mod_manager/collection_mechanic = mechanic +mod_manager/collection_group = group +mod_manager/collection_mods = mods +mod_manager/collection_in_group = In group +mod_manager/collection_remove_mod = Remove from mechanic +mod_manager/collection_turn_on = Turn on +mod_manager/collection_turn_off = Turn off +mod_manager/collection_unsaved = unsaved changes + +# ─── Mods & collections hub ─── +mod_manager/hub_tab_mods = Mods +mod_manager/hub_tab_collections = Collections +mod_manager/hub_back_to_menu = ← Menu +mod_manager/hub_back_to_gallery = ← Games +mod_manager/hub_in_one_collection = in {0} collection +mod_manager/hub_in_many_collections = in {0} collections + +# Gallery of ready games +mod_manager/gallery_crumb = Ready-made games +mod_manager/gallery_title = Games +mod_manager/gallery_hint = ready-made mod sets: one button installs everything +mod_manager/gallery_search_placeholder = Search collections... +mod_manager/gallery_new_game = + Build your own game +mod_manager/gallery_new_game_blank = + Build your own game from scratch +mod_manager/gallery_empty_hint = Add a collections/ folder with JSON files to your mod — the collections will appear here. +mod_manager/gallery_edit_scheme = Edit scheme +mod_manager/gallery_ready = Ready to play +mod_manager/gallery_ready_done = Ready ✓ +mod_manager/gallery_missing_n = Missing {0} +mod_manager/gallery_problems_n = Problems: {0} +mod_manager/gallery_enable_badge_n = Enable {0} +mod_manager/gallery_install_btn_n = Install ({0}) +mod_manager/gallery_enable_btn_n = Enable ({0}) +mod_manager/gallery_draft = Draft +mod_manager/gallery_continue = Continue building + +# Bottom build bar (scheme editor) +mod_manager/build_caption = Build +mod_manager/build_stat_install = Install +mod_manager/build_stat_enable = Enable +mod_manager/build_stat_problems = Problems +mod_manager/build_hint_problems = Fix the problems to apply the collection +mod_manager/build_open_manager = Open mod manager +mod_manager/collection_crumb_scheme = Scheme +mod_manager/collection_find_in_catalog = Find in catalog + + +mod_manager/collection_duplicate = Duplicate +mod_manager/collection_revert = Revert to built-in +mod_manager/collection_saves = Saves: {0} +mod_manager/collection_next_launch = Changes apply the next time the game starts. +mod_manager/collection_json_apply = Apply +mod_manager/collection_json_invalid = Invalid JSON: {0} +mod_manager/collection_json_id_changed = The collection id cannot change — use Duplicate for a new game. +mod_manager/collection_copy_name = {0} (copy) +mod_manager/collection_copy_saves = The original game has saves ({0}). Copy them to the new game? +mod_manager/collection_copy_saves_ok = Copy +mod_manager/collection_revert_confirm = Discard your changes and restore the built-in version of the game? Saves stay. +mod_manager/collection_revert_done = Built-in version restored. +mod_manager/collection_delete = Delete collection +mod_manager/collection_delete_confirm = The collection «{0}» will be deleted. Its mods stay installed in the system, and its saves ({1}) stay on disk. +mod_manager/collection_delete_done = Collection deleted. Its mods stay in the system. +mod_manager/collection_nested_readonly = This block is nested deeper — open it on its own to change it. +mod_manager/collection_forked = Block “{0}” copied for this game — other games stay unchanged. diff --git a/locale/ru.properties b/locale/ru.properties new file mode 100644 index 0000000..1a7737f --- /dev/null +++ b/locale/ru.properties @@ -0,0 +1,221 @@ +# mod_manager — локализация менеджера модов + +mod_mod_manager_name = Менеджер модов +mod_mod_manager_desc = Менеджер модов в стиле Nexus: каталог, фильтры по категориям, установка, обновление и удаление. + +mod_manager/menu_title = Моды и коллекции +mod_manager/title = Менеджер модов +mod_manager/back = Закрыть +mod_manager/back_to_list = Назад +mod_manager/search_placeholder = Поиск модов... +mod_manager/sort_name = По имени +mod_manager/sort_author = По автору +mod_manager/sort_status = По статусу +mod_manager/refresh = Обновить каталог +mod_manager/loading = Загрузка каталога... +mod_manager/results = Модов: {0} + +mod_manager/filters = Фильтры +mod_manager/reset_filters = Сбросить +mod_manager/filter_status = Статус +mod_manager/filter_categories = Категории +mod_manager/group_by_category = Группировать по категориям + +mod_manager/status_installed = Установлен +mod_manager/status_not_installed = Не установлен +mod_manager/status_update = Есть обновление +mod_manager/status_downgrade = Доступна старая версия +mod_manager/status_modified = Изменён локально +mod_manager/status_disabled = Выключен +mod_manager/status_conflict = Конфликт каталогов +mod_manager/conflict_title = Предупреждение +mod_manager/conflict_warning = Один и тот же мод с одинаковой версией и названием опубликован в разных каталогах с разным содержимым. Один из вариантов может быть подделкой: устанавливайте только из доверенного источника. +mod_manager/source_catalog = Каталог: {0} +mod_manager/source_local = Локальный мод + +mod_manager/category_story = Сюжет +mod_manager/category_characters = Персонажи +mod_manager/category_gameplay = Геймплей +mod_manager/category_visual = Визуал +mod_manager/category_audio = Аудио +mod_manager/category_ui = Интерфейс +mod_manager/category_locale = Перевод +mod_manager/category_library = Библиотека +mod_manager/category_engine = Движок +mod_manager/category_overhaul = Оверхаул +mod_manager/category_tools = Инструменты +mod_manager/category_editor = Редактор +mod_manager/category_debug = Отладка +mod_manager/category_other = Прочее + +mod_manager/action_install = Установить +mod_manager/action_update = Обновить +mod_manager/action_downgrade = Понизить +mod_manager/action_reinstall = Переустановить +mod_manager/action_delete = Удалить +mod_manager/action_enable = Включить +mod_manager/action_disable = Выключить + +mod_manager/restart_required = Изменения вступят в силу после перезапуска игры +mod_manager/progress_installing = Установка "{0}"... +mod_manager/install_failed = Не удалось установить мод. Проверьте подключение и повторите. +mod_manager/delete_title = Удаление мода +mod_manager/delete_text = Удалить мод "{0}"? Действие необратимо и вступит в силу после перезапуска. +mod_manager/delete_confirm = Удалить +mod_manager/delete_failed = Не удалось удалить мод. Смотрите лог. +mod_manager/cancel = Отмена + +mod_manager/installed_version = Установлено: {0} +mod_manager/not_installed_hint = Не установлен +mod_manager/latest = последняя +mod_manager/installed_marker = установлена +mod_manager/no_results = Ничего не найдено +mod_manager/description = Описание +mod_manager/depends = Зависимости +mod_manager/used_by = Используется +mod_manager/conflicts = Конфликты +mod_manager/changelog = Изменения +mod_manager/version = Версия: +mod_manager/self_hint = Системный мод: удаление и выключение недоступны + +mod_manager/collections_menu_title = Коллекции +mod_manager/collections_title = Коллекции +mod_manager/collections_empty = Коллекции не найдены +mod_manager/collection_result = Модов: {0}/{1} +mod_manager/collection_mechanics = Механики +mod_manager/collection_node = Мод +mod_manager/collection_plan = План установки +mod_manager/collection_apply = Применить коллекцию +mod_manager/collection_plan_ready = Всё готово +mod_manager/collection_install_count = Установить: {0} +mod_manager/collection_enable_count = Включить: {0} +mod_manager/collection_missing = Нет в каталоге: {0} +mod_manager/collection_mismatch = Несовпадение версий: {0} +mod_manager/collection_optional = необязательная +mod_manager/collection_externals = Зависимости +mod_manager/collection_external = зависимость +mod_manager/collection_node_hint = Выберите мод на графе +mod_manager/collection_requires = Требуется: {0} +mod_manager/collection_origin_builtin = встроенная +mod_manager/collection_origin_user = локальная +mod_manager/collection_origin_catalog = из каталога +mod_manager/collection_progress = Применение "{0}"... +mod_manager/collection_apply_failed = Не удалось применить коллекцию. Смотрите лог. +mod_manager/collection_state_installed = Установлен +mod_manager/collection_state_disabled = Выключен +mod_manager/collection_state_catalog = Доступен в каталоге +mod_manager/collection_state_missing = Не найден +mod_manager/collection_state_mismatch = Версия не подходит + +# Схема коллекции (пирамида) +mod_manager/collection_add_mechanic = + Механика +mod_manager/collection_new_game = + Игра +mod_manager/collection_new_game_name = Новая игра +mod_manager/collection_new_game_desc = Соберите игру из механик: добавляйте блоки и моды. +mod_manager/collection_new_mechanic_name = Новая механика +mod_manager/collection_group_ready = готово +mod_manager/collection_group_empty = пусто +mod_manager/collection_group_off = выключена +mod_manager/collection_links = Связи +mod_manager/collection_problems = Проблемы +mod_manager/collection_tab_inspector = Инспектор +mod_manager/collection_tab_problems = Проверка +mod_manager/collection_tab_plan = План +mod_manager/collection_plan_install = установить +mod_manager/collection_plan_enable = включить +mod_manager/collection_save = Сохранить +mod_manager/collection_reset = Сбросить +mod_manager/collection_json = JSON +mod_manager/collection_save_ok = Сохранено в user://collections.json +mod_manager/collection_no_changes = Нет изменений для сохранения +mod_manager/collection_reset_confirm = Сбросить несохранённые правки? +mod_manager/collection_rename_title = Название и описание +mod_manager/collection_field_name = Название +mod_manager/collection_field_note = Описание +mod_manager/collection_add_mod = Добавить мод +mod_manager/collection_empty_mechanic = Пустая механика +mod_manager/collection_empty_mechanic_desc = Создать контур с нуля: название, описание и моды добавите вручную. +mod_manager/collection_pick_mechanic = Добавить механику +mod_manager/collection_pick_replace = Заменить механику +mod_manager/collection_choose = Добавить +mod_manager/collection_replace = Заменить +mod_manager/collection_remove = Убрать +mod_manager/collection_rename = Переименовать +mod_manager/collection_create = Создать +mod_manager/collection_where = Куда добавить +mod_manager/collection_new_extra = + новая механика «Дополнительно» +mod_manager/collection_extra_name = Дополнительно +mod_manager/collection_extra_note = Добавленные вручную моды. +mod_manager/collection_search_mechanic = Поиск механики... +mod_manager/collection_search_mod = Поиск мода... +mod_manager/collection_problems_none = Проблем нет — сборка согласована. +mod_manager/collection_problem_missing = Блок «{0}» не найден +mod_manager/collection_problem_cycle = Цикл: «{0}» → «{1}» +mod_manager/collection_problem_role = Слот «{0}» занят дважды +mod_manager/collection_problem_require = «{0}»: не выполнено требование «{1}» +mod_manager/collection_show = Показать +mod_manager/collection_hint = Выберите контур или мод. Контуры двигаются за заголовок, тянутся за угол, моды внутри двигаются свободно. Пустое поле схемы перемещается перетаскиванием ЛКМ. +mod_manager/collection_mechanic = механика +mod_manager/collection_group = группа +mod_manager/collection_mods = модов +mod_manager/collection_in_group = В механике +mod_manager/collection_remove_mod = Убрать из механики +mod_manager/collection_turn_on = Включить +mod_manager/collection_turn_off = Выключить +mod_manager/collection_unsaved = ● несохранённые правки + +# ─── Хаб «Моды и коллекции» ─── +mod_manager/hub_tab_mods = Моды +mod_manager/hub_tab_collections = Коллекции +mod_manager/hub_back_to_menu = ← Меню +mod_manager/hub_back_to_gallery = ← К играм +mod_manager/hub_in_one_collection = в {0} коллекции +mod_manager/hub_in_many_collections = в {0} коллекциях + +# Галерея готовых игр +mod_manager/gallery_crumb = Готовые игры +mod_manager/gallery_title = Игры +mod_manager/gallery_hint = готовые наборы модов: одна кнопка — и всё установлено +mod_manager/gallery_search_placeholder = Поиск коллекций... +mod_manager/gallery_new_game = + Собрать свою игру +mod_manager/gallery_new_game_blank = + Собрать свою игру с нуля +mod_manager/gallery_empty_hint = Добавьте в свой мод папку collections/ с JSON-файлами — коллекции появятся здесь. +mod_manager/gallery_edit_scheme = Изменить схему +mod_manager/gallery_ready = Готова к игре +mod_manager/gallery_ready_done = Готова ✓ +mod_manager/gallery_missing_n = Не хватает {0} +mod_manager/gallery_problems_n = Проблемы: {0} +mod_manager/gallery_enable_badge_n = Включить {0} +mod_manager/gallery_install_btn_n = Установить ({0}) +mod_manager/gallery_enable_btn_n = Включить ({0}) +mod_manager/gallery_draft = Черновик +mod_manager/gallery_continue = Продолжить сборку + +# Нижняя панель сборки (редактор схемы) +mod_manager/build_caption = Сборка +mod_manager/build_stat_install = Установить +mod_manager/build_stat_enable = Включить +mod_manager/build_stat_problems = Проблемы +mod_manager/build_hint_problems = Устраните проблемы, чтобы применить коллекцию +mod_manager/build_open_manager = Открыть менеджер модов +mod_manager/collection_crumb_scheme = Схема +mod_manager/collection_find_in_catalog = Найти в каталоге + + +mod_manager/collection_duplicate = Дублировать +mod_manager/collection_revert = Вернуть встроенную +mod_manager/collection_saves = Сохранений: {0} +mod_manager/collection_next_launch = Изменения вступят в силу при следующем запуске игры. +mod_manager/collection_json_apply = Применить +mod_manager/collection_json_invalid = JSON не разобран: {0} +mod_manager/collection_json_id_changed = Id коллекции менять нельзя — для новой игры используйте «Дублировать». +mod_manager/collection_copy_name = {0} (копия) +mod_manager/collection_copy_saves = У исходной игры есть сохранения ({0}). Скопировать их в новую игру? +mod_manager/collection_copy_saves_ok = Скопировать +mod_manager/collection_revert_confirm = Удалить ваши изменения и вернуть встроенную версию игры? Сохранения останутся. +mod_manager/collection_revert_done = Возвращена встроенная версия. +mod_manager/collection_delete = Удалить коллекцию +mod_manager/collection_delete_confirm = Коллекция «{0}» будет удалена. Моды из неё останутся установленными в системе, сохранения ({1}) — на диске. +mod_manager/collection_delete_done = Коллекция удалена. Моды остались в системе. +mod_manager/collection_nested_readonly = Этот блок вложен глубже — откройте его отдельно, чтобы изменить. +mod_manager/collection_forked = Блок «{0}» скопирован для этой игры — другие игры не изменятся. diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..19cd23b --- /dev/null +++ b/manifest.json @@ -0,0 +1,19 @@ +{ + "id": "mod_manager", + "name": "Mod Manager", + "version": "1.0.0", + "author": "ct", + "description": "Nexus-style mod manager: browse the catalog, filter by category and status, install, update and remove mods.", + "category": "tools, ui", + "sources": [ + "src/**/*.cs" + ], + "depends": ["interface>=1"], + "main_menu": [ + { + "id": "mods_hub", + "scene_path": "scenes/mods_hub_screen.tscn", + "button": { "icon": "mod_manager", "locale": "mod_manager/menu_title", "index": 100 } + } + ] +} diff --git a/mod.png b/mod.png new file mode 100644 index 0000000000000000000000000000000000000000..7ec376737702f222137b00bd5491eeb66aaac109 GIT binary patch literal 9360 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 literal 0 HcmV?d00001 diff --git a/scenes/components/mod_card.tscn b/scenes/components/mod_card.tscn new file mode 100644 index 0000000..6fd286b --- /dev/null +++ b/scenes/components/mod_card.tscn @@ -0,0 +1,145 @@ +[gd_scene format=3] + +[ext_resource type="Script" path="src/UI/ModCard.cs" id="1_card"] + +[node name="ModCard" type="PanelContainer"] +custom_minimum_size = Vector2(236, 0) +size_flags_horizontal = 0 +mouse_filter = 0 +script = ExtResource("1_card") + +[node name="VBox" type="VBoxContainer" parent="."] +layout_mode = 2 +theme_override_constants/separation = 0 + +[node name="Cover" type="Control" parent="VBox"] +custom_minimum_size = Vector2(0, 132) +layout_mode = 2 +clip_contents = true +mouse_filter = 2 + +[node name="Placeholder" type="ColorRect" parent="VBox/Cover"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_filter = 2 +color = Color(0.054902, 0.058824, 0.082353, 1) + +[node name="Image" type="TextureRect" parent="VBox/Cover"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_filter = 2 +expand_mode = 1 +stretch_mode = 6 + +[node name="Body" type="MarginContainer" parent="VBox"] +layout_mode = 2 +theme_override_constants/margin_left = 10 +theme_override_constants/margin_top = 8 +theme_override_constants/margin_right = 10 +theme_override_constants/margin_bottom = 10 + +[node name="Box" type="VBoxContainer" parent="VBox/Body"] +layout_mode = 2 +theme_override_constants/separation = 5 + +[node name="Title" type="Label" parent="VBox/Body/Box"] +custom_minimum_size = Vector2(0, 40) +layout_mode = 2 +mouse_filter = 2 +clip_text = true +autowrap_mode = 3 +max_lines_visible = 2 +text_overrun_behavior = 3 + +[node name="Author" type="Label" parent="VBox/Body/Box"] +custom_minimum_size = Vector2(0, 16) +layout_mode = 2 +mouse_filter = 2 +clip_text = true +text_overrun_behavior = 3 +theme_override_font_sizes/font_size = 11 + +[node name="Source" type="Label" parent="VBox/Body/Box"] +custom_minimum_size = Vector2(0, 16) +layout_mode = 2 +mouse_filter = 2 +clip_text = true +text_overrun_behavior = 3 +theme_override_font_sizes/font_size = 11 + +[node name="MetaRow" type="HBoxContainer" parent="VBox/Body/Box"] +custom_minimum_size = Vector2(0, 28) +layout_mode = 2 +theme_override_constants/separation = 6 + +[node name="Status" type="Label" parent="VBox/Body/Box/MetaRow"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 4 +mouse_filter = 2 +clip_text = true +text_overrun_behavior = 3 +theme_override_font_sizes/font_size = 11 +vertical_alignment = 1 + +[node name="VersionHost" type="Control" parent="VBox/Body/Box/MetaRow"] +custom_minimum_size = Vector2(102, 28) +layout_mode = 2 +size_flags_horizontal = 0 +size_flags_vertical = 4 +mouse_filter = 2 +clip_contents = true + +[node name="Versions" type="OptionButton" parent="VBox/Body/Box/MetaRow/VersionHost"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_font_sizes/font_size = 11 + +[node name="ActionRow" type="HBoxContainer" parent="VBox/Body/Box"] +custom_minimum_size = Vector2(0, 32) +layout_mode = 2 +theme_override_constants/separation = 6 + +[node name="ActionHost" type="Control" parent="VBox/Body/Box/ActionRow"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 4 +mouse_filter = 2 + +[node name="Action" type="Button" parent="VBox/Body/Box/ActionRow/ActionHost"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +clip_text = true +text_overrun_behavior = 3 + +[node name="EnabledHost" type="Control" parent="VBox/Body/Box/ActionRow"] +custom_minimum_size = Vector2(40, 32) +layout_mode = 2 +size_flags_horizontal = 0 +size_flags_vertical = 4 +mouse_filter = 2 +clip_contents = true + +[node name="Enabled" type="CheckButton" parent="VBox/Body/Box/ActionRow/EnabledHost"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 diff --git a/scenes/components/mod_detail_view.tscn b/scenes/components/mod_detail_view.tscn new file mode 100644 index 0000000..a8e990d --- /dev/null +++ b/scenes/components/mod_detail_view.tscn @@ -0,0 +1,180 @@ +[gd_scene format=3] + +[ext_resource type="Script" path="src/UI/ModDetailView.cs" id="1_detail"] + +[node name="ModDetailView" type="VBoxContainer"] +theme_override_constants/separation = 8 +script = ExtResource("1_detail") + +[node name="Scroll" type="ScrollContainer" parent="."] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +horizontal_scroll_mode = 0 + +[node name="Content" type="VBoxContainer" parent="Scroll"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_constants/separation = 14 + +[node name="Hero" type="PanelContainer" parent="Scroll/Content"] +layout_mode = 2 + +[node name="HeroMargin" type="MarginContainer" parent="Scroll/Content/Hero"] +layout_mode = 2 +theme_override_constants/margin_left = 14 +theme_override_constants/margin_top = 14 +theme_override_constants/margin_right = 14 +theme_override_constants/margin_bottom = 14 + +[node name="HeroRow" type="HBoxContainer" parent="Scroll/Content/Hero/HeroMargin"] +layout_mode = 2 +theme_override_constants/separation = 16 + +[node name="Cover" type="Control" parent="Scroll/Content/Hero/HeroMargin/HeroRow"] +custom_minimum_size = Vector2(270, 180) +layout_mode = 2 +clip_contents = true +mouse_filter = 2 + +[node name="Placeholder" type="ColorRect" parent="Scroll/Content/Hero/HeroMargin/HeroRow/Cover"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_filter = 2 +color = Color(0.054902, 0.058824, 0.082353, 1) + +[node name="Image" type="TextureRect" parent="Scroll/Content/Hero/HeroMargin/HeroRow/Cover"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_filter = 2 +expand_mode = 1 +stretch_mode = 6 + +[node name="Info" type="VBoxContainer" parent="Scroll/Content/Hero/HeroMargin/HeroRow"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_constants/separation = 6 + +[node name="Back" type="Button" parent="Scroll/Content/Hero/HeroMargin/HeroRow/Info"] +layout_mode = 2 +size_flags_horizontal = 0 + +[node name="Title" type="Label" parent="Scroll/Content/Hero/HeroMargin/HeroRow/Info"] +layout_mode = 2 +autowrap_mode = 3 +theme_override_font_sizes/font_size = 22 + +[node name="Author" type="Label" parent="Scroll/Content/Hero/HeroMargin/HeroRow/Info"] +layout_mode = 2 + +[node name="Meta" type="Label" parent="Scroll/Content/Hero/HeroMargin/HeroRow/Info"] +layout_mode = 2 + +[node name="VersionRow" type="HBoxContainer" parent="Scroll/Content/Hero/HeroMargin/HeroRow/Info"] +layout_mode = 2 +theme_override_constants/separation = 8 + +[node name="Caption" type="Label" parent="Scroll/Content/Hero/HeroMargin/HeroRow/Info/VersionRow"] +layout_mode = 2 +size_flags_vertical = 4 + +[node name="Versions" type="OptionButton" parent="Scroll/Content/Hero/HeroMargin/HeroRow/Info/VersionRow"] +layout_mode = 2 +size_flags_horizontal = 0 +size_flags_vertical = 4 + +[node name="Installed" type="Label" parent="Scroll/Content/Hero/HeroMargin/HeroRow/Info/VersionRow"] +layout_mode = 2 +size_flags_vertical = 4 + +[node name="Size" type="Label" parent="Scroll/Content/Hero/HeroMargin/HeroRow/Info/VersionRow"] +layout_mode = 2 +size_flags_vertical = 4 + +[node name="ActionsRow" type="HBoxContainer" parent="Scroll/Content"] +layout_mode = 2 +theme_override_constants/separation = 8 + +[node name="ConflictBox" type="VBoxContainer" parent="Scroll/Content"] +visible = false +layout_mode = 2 +theme_override_constants/separation = 4 + +[node name="Caption" type="Label" parent="Scroll/Content/ConflictBox"] +layout_mode = 2 +theme_override_font_sizes/font_size = 12 + +[node name="Text" type="Label" parent="Scroll/Content/ConflictBox"] +layout_mode = 2 +autowrap_mode = 3 + +[node name="DescriptionBox" type="VBoxContainer" parent="Scroll/Content"] +layout_mode = 2 +theme_override_constants/separation = 4 + +[node name="Caption" type="Label" parent="Scroll/Content/DescriptionBox"] +layout_mode = 2 +theme_override_font_sizes/font_size = 12 + +[node name="Text" type="Label" parent="Scroll/Content/DescriptionBox"] +layout_mode = 2 +autowrap_mode = 3 + +[node name="ChangelogBox" type="VBoxContainer" parent="Scroll/Content"] +layout_mode = 2 +theme_override_constants/separation = 4 + +[node name="Caption" type="Label" parent="Scroll/Content/ChangelogBox"] +layout_mode = 2 +theme_override_font_sizes/font_size = 12 + +[node name="Text" type="Label" parent="Scroll/Content/ChangelogBox"] +layout_mode = 2 +autowrap_mode = 3 + +[node name="DependsBox" type="VBoxContainer" parent="Scroll/Content"] +visible = false +layout_mode = 2 +theme_override_constants/separation = 4 + +[node name="Caption" type="Label" parent="Scroll/Content/DependsBox"] +layout_mode = 2 +theme_override_font_sizes/font_size = 12 + +[node name="Text" type="Label" parent="Scroll/Content/DependsBox"] +layout_mode = 2 +autowrap_mode = 3 + +[node name="UsedByBox" type="VBoxContainer" parent="Scroll/Content"] +visible = false +layout_mode = 2 +theme_override_constants/separation = 4 + +[node name="Caption" type="Label" parent="Scroll/Content/UsedByBox"] +layout_mode = 2 +theme_override_font_sizes/font_size = 12 + +[node name="Text" type="Label" parent="Scroll/Content/UsedByBox"] +layout_mode = 2 +autowrap_mode = 3 + +[node name="ConflictsBox" type="VBoxContainer" parent="Scroll/Content"] +visible = false +layout_mode = 2 +theme_override_constants/separation = 4 + +[node name="Caption" type="Label" parent="Scroll/Content/ConflictsBox"] +layout_mode = 2 +theme_override_font_sizes/font_size = 12 + +[node name="Text" type="Label" parent="Scroll/Content/ConflictsBox"] +layout_mode = 2 +autowrap_mode = 3 diff --git a/scenes/components/mod_filter_panel.tscn b/scenes/components/mod_filter_panel.tscn new file mode 100644 index 0000000..33265ee --- /dev/null +++ b/scenes/components/mod_filter_panel.tscn @@ -0,0 +1,43 @@ +[gd_scene format=3] + +[ext_resource type="Script" path="src/UI/ModFilterPanel.cs" id="1_filter"] + +[node name="ModFilterPanel" type="VBoxContainer"] +theme_override_constants/separation = 8 +script = ExtResource("1_filter") + +[node name="Title" type="Label" parent="."] +layout_mode = 2 +theme_override_font_sizes/font_size = 16 + +[node name="Reset" type="Button" parent="."] +layout_mode = 2 +size_flags_horizontal = 0 + +[node name="Sep1" type="HSeparator" parent="."] +layout_mode = 2 + +[node name="StatusCaption" type="Label" parent="."] +layout_mode = 2 +theme_override_font_sizes/font_size = 12 + +[node name="StatusList" type="VBoxContainer" parent="."] +layout_mode = 2 +theme_override_constants/separation = 2 + +[node name="Sep2" type="HSeparator" parent="."] +layout_mode = 2 + +[node name="CategoryCaption" type="Label" parent="."] +layout_mode = 2 +theme_override_font_sizes/font_size = 12 + +[node name="CategoryList" type="VBoxContainer" parent="."] +layout_mode = 2 +theme_override_constants/separation = 2 + +[node name="Sep3" type="HSeparator" parent="."] +layout_mode = 2 + +[node name="GroupToggle" type="CheckButton" parent="."] +layout_mode = 2 diff --git a/scenes/components/mod_grid_group.tscn b/scenes/components/mod_grid_group.tscn new file mode 100644 index 0000000..0dbef99 --- /dev/null +++ b/scenes/components/mod_grid_group.tscn @@ -0,0 +1,16 @@ +[gd_scene format=3] + +[ext_resource type="Script" path="src/UI/ModGridGroup.cs" id="1_group"] + +[node name="ModGridGroup" type="VBoxContainer"] +theme_override_constants/separation = 8 +script = ExtResource("1_group") + +[node name="Header" type="Label" parent="."] +layout_mode = 2 +theme_override_font_sizes/font_size = 15 + +[node name="Flow" type="HFlowContainer" parent="."] +layout_mode = 2 +theme_override_constants/h_separation = 12 +theme_override_constants/v_separation = 12 diff --git a/scenes/components/mod_grid_view.tscn b/scenes/components/mod_grid_view.tscn new file mode 100644 index 0000000..5150bce --- /dev/null +++ b/scenes/components/mod_grid_view.tscn @@ -0,0 +1,12 @@ +[gd_scene format=3] + +[ext_resource type="Script" path="src/UI/ModGridView.cs" id="1_grid"] + +[node name="ModGridView" type="ScrollContainer"] +horizontal_scroll_mode = 0 +script = ExtResource("1_grid") + +[node name="Host" type="VBoxContainer" parent="."] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_constants/separation = 18 diff --git a/scenes/mod_collections_screen.tscn b/scenes/mod_collections_screen.tscn new file mode 100644 index 0000000..7865a7d --- /dev/null +++ b/scenes/mod_collections_screen.tscn @@ -0,0 +1,198 @@ +[gd_scene format=3] + +[ext_resource type="Script" path="src/ModCollectionsScreen.cs" id="1_screen"] +[ext_resource type="Script" path="src/UI/CollectionSchemeCanvas.cs" id="2_canvas"] + +[node name="ModCollectionsScreen" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_filter = 0 +script = ExtResource("1_screen") + +[node name="Background" type="ColorRect" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_filter = 2 +color = Color(0.054902, 0.058824, 0.082353, 1) + +[node name="Margin" type="MarginContainer" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/margin_left = 16 +theme_override_constants/margin_top = 12 +theme_override_constants/margin_right = 16 +theme_override_constants/margin_bottom = 12 + +[node name="Root" type="VBoxContainer" parent="Margin"] +layout_mode = 2 +theme_override_constants/separation = 10 + +[node name="Header" type="HBoxContainer" parent="Margin/Root"] +layout_mode = 2 +theme_override_constants/separation = 8 + +[node name="Close" type="Button" parent="Margin/Root/Header"] +layout_mode = 2 + +[node name="Title" type="Label" parent="Margin/Root/Header"] +layout_mode = 2 +size_flags_vertical = 4 +theme_override_font_sizes/font_size = 24 +uppercase = true + +[node name="GameSelect" type="OptionButton" parent="Margin/Root/Header"] +custom_minimum_size = Vector2(220, 0) +layout_mode = 2 +size_flags_vertical = 4 + +[node name="AddMechanic" type="Button" parent="Margin/Root/Header"] +layout_mode = 2 +size_flags_vertical = 4 + +[node name="NewGame" type="Button" parent="Margin/Root/Header"] +layout_mode = 2 +size_flags_vertical = 4 + +[node name="Spacer" type="Control" parent="Margin/Root/Header"] +layout_mode = 2 +size_flags_horizontal = 3 +mouse_filter = 2 + +[node name="EdgesToggle" type="CheckBox" parent="Margin/Root/Header"] +layout_mode = 2 +size_flags_vertical = 4 +button_pressed = true + +[node name="Problems" type="Button" parent="Margin/Root/Header"] +layout_mode = 2 +size_flags_vertical = 4 + +[node name="Plan" type="Button" parent="Margin/Root/Header"] +layout_mode = 2 +size_flags_vertical = 4 + +[node name="ModsCount" type="Label" parent="Margin/Root/Header"] +layout_mode = 2 +size_flags_vertical = 4 + +[node name="Dirty" type="Label" parent="Margin/Root/Header"] +visible = false +layout_mode = 2 +size_flags_vertical = 4 + +[node name="Json" type="Button" parent="Margin/Root/Header"] +layout_mode = 2 +size_flags_vertical = 4 + +[node name="Reset" type="Button" parent="Margin/Root/Header"] +layout_mode = 2 +size_flags_vertical = 4 + +[node name="Save" type="Button" parent="Margin/Root/Header"] +layout_mode = 2 +size_flags_vertical = 4 + +[node name="Refresh" type="Button" parent="Margin/Root/Header"] +layout_mode = 2 +size_flags_vertical = 4 + +[node name="Crumbs" type="HBoxContainer" parent="Margin/Root"] +layout_mode = 2 +theme_override_constants/separation = 8 + +[node name="Body" type="HSplitContainer" parent="Margin/Root"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="CanvasScroll" type="ScrollContainer" parent="Margin/Root/Body"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +horizontal_scroll_mode = 1 +vertical_scroll_mode = 1 + +[node name="SchemeCanvas" type="Control" parent="Margin/Root/Body/CanvasScroll"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +script = ExtResource("2_canvas") + +[node name="Details" type="PanelContainer" parent="Margin/Root/Body"] +custom_minimum_size = Vector2(380, 0) +layout_mode = 2 + +[node name="DetailScroll" type="ScrollContainer" parent="Margin/Root/Body/Details"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +horizontal_scroll_mode = 0 + +[node name="Detail" type="VBoxContainer" parent="Margin/Root/Body/Details/DetailScroll"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_constants/separation = 8 + +[node name="Tabs" type="HBoxContainer" parent="Margin/Root/Body/Details/DetailScroll/Detail"] +layout_mode = 2 +theme_override_constants/separation = 4 + +[node name="TabInspector" type="Button" parent="Margin/Root/Body/Details/DetailScroll/Detail/Tabs"] +layout_mode = 2 +toggle_mode = true +button_pressed = true + +[node name="TabProblems" type="Button" parent="Margin/Root/Body/Details/DetailScroll/Detail/Tabs"] +layout_mode = 2 +toggle_mode = true + +[node name="TabPlan" type="Button" parent="Margin/Root/Body/Details/DetailScroll/Detail/Tabs"] +layout_mode = 2 +toggle_mode = true + +[node name="DetailTitle" type="Label" parent="Margin/Root/Body/Details/DetailScroll/Detail"] +layout_mode = 2 +theme_override_font_sizes/font_size = 20 +autowrap_mode = 3 + +[node name="DetailMeta" type="Label" parent="Margin/Root/Body/Details/DetailScroll/Detail"] +layout_mode = 2 +autowrap_mode = 3 + +[node name="DetailDescription" type="Label" parent="Margin/Root/Body/Details/DetailScroll/Detail"] +layout_mode = 2 +autowrap_mode = 3 + +[node name="DetailExtra" type="VBoxContainer" parent="Margin/Root/Body/Details/DetailScroll/Detail"] +layout_mode = 2 +theme_override_constants/separation = 6 + +[node name="Apply" type="Button" parent="Margin/Root/Body/Details/DetailScroll/Detail"] +layout_mode = 2 + +[node name="BuildBar" type="PanelContainer" parent="Margin/Root"] +layout_mode = 2 + +[node name="BarRow" type="HBoxContainer" parent="Margin/Root/BuildBar"] +layout_mode = 2 +theme_override_constants/separation = 12 + +[node name="Banner" type="PanelContainer" parent="Margin/Root"] +visible = false +layout_mode = 2 + +[node name="BannerLabel" type="Label" parent="Margin/Root/Banner"] +layout_mode = 2 +size_flags_horizontal = 3 +horizontal_alignment = 1 diff --git a/scenes/mod_manager_screen.tscn b/scenes/mod_manager_screen.tscn new file mode 100644 index 0000000..0664cb2 --- /dev/null +++ b/scenes/mod_manager_screen.tscn @@ -0,0 +1,127 @@ +[gd_scene format=3] + +[ext_resource type="Script" path="src/ModManagerScreen.cs" id="1_screen"] +[ext_resource type="PackedScene" path="scenes/components/mod_filter_panel.tscn" id="2_filter"] +[ext_resource type="PackedScene" path="scenes/components/mod_grid_view.tscn" id="3_grid"] +[ext_resource type="PackedScene" path="scenes/components/mod_detail_view.tscn" id="4_detail"] + +[node name="ModManagerScreen" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_filter = 0 +script = ExtResource("1_screen") + +[node name="Background" type="ColorRect" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_filter = 2 +color = Color(0.054902, 0.058824, 0.082353, 1) + +[node name="Margin" type="MarginContainer" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/margin_left = 16 +theme_override_constants/margin_top = 12 +theme_override_constants/margin_right = 16 +theme_override_constants/margin_bottom = 12 + +[node name="Root" type="VBoxContainer" parent="Margin"] +layout_mode = 2 +theme_override_constants/separation = 10 + +[node name="Header" type="HBoxContainer" parent="Margin/Root"] +layout_mode = 2 +theme_override_constants/separation = 10 + +[node name="Close" type="Button" parent="Margin/Root/Header"] +layout_mode = 2 + +[node name="Title" type="Label" parent="Margin/Root/Header"] +layout_mode = 2 +size_flags_vertical = 4 +theme_override_font_sizes/font_size = 24 +theme_override_colors/font_color = Color(0.956863, 0.941176, 0.905882, 1) +uppercase = true + +[node name="Spacer" type="Control" parent="Margin/Root/Header"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Search" type="LineEdit" parent="Margin/Root/Header"] +custom_minimum_size = Vector2(220, 0) +layout_mode = 2 +size_flags_vertical = 4 + +[node name="Sort" type="OptionButton" parent="Margin/Root/Header"] +layout_mode = 2 +size_flags_vertical = 4 + +[node name="Refresh" type="Button" parent="Margin/Root/Header"] +layout_mode = 2 +size_flags_vertical = 4 + +[node name="Result" type="Label" parent="Margin/Root/Header"] +layout_mode = 2 +size_flags_vertical = 4 + +[node name="Body" type="HBoxContainer" parent="Margin/Root"] +layout_mode = 2 +size_flags_vertical = 3 +theme_override_constants/separation = 12 + +[node name="Sidebar" type="PanelContainer" parent="Margin/Root/Body"] +custom_minimum_size = Vector2(260, 0) +layout_mode = 2 + +[node name="SidebarScroll" type="ScrollContainer" parent="Margin/Root/Body/Sidebar"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +horizontal_scroll_mode = 0 + +[node name="FilterPanel" parent="Margin/Root/Body/Sidebar/SidebarScroll" instance=ExtResource("2_filter")] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Content" type="Control" parent="Margin/Root/Body"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="Grid" parent="Margin/Root/Body/Content" instance=ExtResource("3_grid")] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="Detail" parent="Margin/Root/Body/Content" instance=ExtResource("4_detail")] +visible = false +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="Banner" type="PanelContainer" parent="Margin/Root"] +visible = false +layout_mode = 2 + +[node name="BannerLabel" type="Label" parent="Margin/Root/Banner"] +layout_mode = 2 +size_flags_horizontal = 3 +horizontal_alignment = 1 diff --git a/scenes/mods_hub_screen.tscn b/scenes/mods_hub_screen.tscn new file mode 100644 index 0000000..aecf7a4 --- /dev/null +++ b/scenes/mods_hub_screen.tscn @@ -0,0 +1,39 @@ +[gd_scene format=3] + +[ext_resource type="Script" path="src/ModHubScreen.cs" id="1_hub"] + +[node name="ModHubScreen" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_filter = 0 +script = ExtResource("1_hub") + +[node name="Background" type="ColorRect" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_filter = 2 +color = Color(0.054902, 0.058824, 0.082353, 1) + +[node name="Margin" type="MarginContainer" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/margin_left = 16 +theme_override_constants/margin_top = 12 +theme_override_constants/margin_right = 16 +theme_override_constants/margin_bottom = 12 + +[node name="Root" type="VBoxContainer" parent="Margin"] +layout_mode = 2 +theme_override_constants/separation = 10 diff --git a/src/ModCollectionsScreen.cs b/src/ModCollectionsScreen.cs new file mode 100644 index 0000000..752dd5f --- /dev/null +++ b/src/ModCollectionsScreen.cs @@ -0,0 +1,1894 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using Cthangover.Core.Mods; +using Cthangover.Core.Settings; +using Cthangover.Core.UI; +using Cthangover.Core.UI.Theming; +using Cthangover.Core.Utils; +using Godot; + +using Cthangover.Mods.Interface.Base.Lists; +using Cthangover.Mods.Interface.Theming; +namespace Mods.ModManager +{ + /// + /// Fullscreen scheme editor opened from the main menu: the whole game on one + /// canvas exactly like the approved mockup. The header picks the game and + /// creates new games/mechanics, the canvas shows movable and resizable + /// mechanic contours with freely placed mod chips and dependency lines, and + /// the inspector edits the selected group or mod. + /// + /// All heavy lifting stays in the core: + /// merges documents, expands includes + /// into groups and problems, decides what to + /// download and enable, and persists the + /// arrangement. Edits mutate drafts in _overrides — never the loaded + /// documents — and only "Сохранить" writes them back through + /// , so the player can explore + /// layouts without committing anything. + /// + public partial class ModCollectionsScreen : ModWidget + { + private const string RoleExtra = "extra"; + + private Button _close; + private Button _refresh; + private Button _addMechanic; + private Button _newGame; + private Button _apply; + private OptionButton _gameSelect; + private CheckBox _edgesToggle; + private Button _pillProblems; + private Button _pillPlan; + private Label _modsCount; + private Label _dirty; + private Button _json; + private Button _reset; + private Button _save; + private Label _bannerLabel; + private Label _detailTitle; + private Label _detailMeta; + private Label _detailDescription; + private VBoxContainer _detailExtra; + private PanelContainer _bannerPanel; + private CollectionSchemeCanvas _canvas; + private Button _tabInspector; + private Button _tabProblems; + private Button _tabPlan; + private HBoxContainer _crumbs; + private Button _crumbCollections; + private Label _buildName; + private Label _buildInstall; + private Label _buildEnable; + private Label _buildProblems; + private Label _buildHint; + private Button _buildManager; + private Button _buildApply; + + private List _collections = new(); + private readonly Dictionary _overrides = new(StringComparer.OrdinalIgnoreCase); + private string _currentId; + private ModCollectionGraph _graph; + private ModCollectionPlan _plan; + private ModCollectionLayout _layout = new(); + private string _selectedGroupKey; + private string _selectedModId; + private string _tab = "inspector"; + + /// Mods installed during this session; the registry only sees them after a restart. + private HashSet _sessionInstalled = new(StringComparer.OrdinalIgnoreCase); + private bool _installing; + private bool _restartRequired; + private string _statusMessage; + private Button _duplicate; + private Button _revert; + private Button _delete; + private Label _savesInfo; + + /// Drafts created by "Duplicate", mapped to the game they were copied from (offer to copy its saves). + private readonly Dictionary _duplicatedFrom = new(StringComparer.OrdinalIgnoreCase); + + /// + /// When true the editor runs as the second level of the collections + /// tab: the close/title/new-game/catalog-refresh header controls are + /// hidden (the hosting owns navigation) and + /// the game selector moves into the breadcrumb row. Editing, saving + /// and applying behaviour are unchanged. + /// + public bool Embedded { get; set; } + + /// Raised when the breadcrumbs ask to go back to the games gallery. + public event Action GalleryRequested; + + /// Raised when a "not found" problem asks to search the mod catalog; the argument is the missing id. + public event Action OpenManagerRequested; + + /// Raised after every rebuild so the hosting hub can refresh its header. + public event Action ViewChanged; + + /// Localized, '@key'-resolved title of the collection being edited; null when nothing is selected. + public string CurrentCollectionTitle + { + get + { + var collection = EffectiveCollection(_currentId); + if (collection == null) + return null; + + var raw = collection.GetLocalizedName(TranslationServer.GetLocale()); + return CollectionText.Resolve(raw); + } + } + + /// Shares the hub-wide set of mods installed this session so gallery and editor stay consistent. + public void SetSessionInstalled(HashSet sessionInstalled) + => _sessionInstalled = sessionInstalled ?? new HashSet(StringComparer.OrdinalIgnoreCase); + + /// + /// Opens the scheme editor on the given collection id, falling back to + /// the first available game when the id is null or unknown. Selection + /// is cleared — the same fresh view a game-switch produces. + /// + public void OpenCollection(string collectionId) + { + _currentId = collectionId; + _selectedGroupKey = null; + _selectedModId = null; + // The gallery re-reads the store on every refresh; re-read it here + // too, so a collection that appeared since this editor was built + // opens instead of falling back to the first game. + ReloadCollections(); + } + + /// Creates a fresh player game draft and opens it — the gallery's "build your own game" action. + public void OpenNewGame() => OnNewGamePressed(); + + protected override void Construct() + { + MouseFilter = MouseFilterEnum.Stop; + CthangoverTheme.Apply(this); + + BindSceneNodes(); + SubscribeEvents(); + + CatalogService.LoadCache(); + ReloadCollections(); + StartCatalogRefreshIfNeeded(); + } + + protected override void Destruct() + { + UnsubscribeEvents(); + } + + private void BindSceneNodes() + { + _close = GetNode