diff --git a/Code/Tools/ProjectManager/Resources/ProjectManager.qss b/Code/Tools/ProjectManager/Resources/ProjectManager.qss index b2217435b2..db33d196fe 100644 --- a/Code/Tools/ProjectManager/Resources/ProjectManager.qss +++ b/Code/Tools/ProjectManager/Resources/ProjectManager.qss @@ -482,10 +482,10 @@ QTabBar::tab:focus { #labelButtonOverlay { background-color: rgba(50,50,50,200); - min-width:210px; - max-width:210px;; - min-height:278px; - max-height:278px; + min-width:208px; + max-width:208px;; + min-height:270px; + max-height:270px; } QProgressBar { diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp index 8f8c8fb3cb..cbb0398f5f 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp @@ -81,7 +81,7 @@ namespace O3DE::ProjectManager m_rightPanelStack = new QStackedWidget(this); m_rightPanelStack->setFixedWidth(sidePanelWidth); - m_gemInspector = new GemInspector(m_gemModel, this); + m_gemInspector = new GemInspector(m_gemModel, m_rightPanelStack); connect(m_gemInspector, &GemInspector::TagClicked, [=](const Tag& tag) { SelectGem(tag.id); }); connect(m_gemInspector, &GemInspector::UpdateGem, this, &GemCatalogScreen::UpdateGem); @@ -233,7 +233,12 @@ namespace O3DE::ProjectManager const QVector& gemInfos = allGemInfosResult.GetValue(); for (const GemInfo& gemInfo : gemInfos) { - gemInfoHash.insert(gemInfo.m_name, gemInfo); + // ${Name} is a special name used for templates and should be ignored + // eventually we should handle this in Python instead of here + if (gemInfo.m_name != "${Name}") + { + gemInfoHash.insert(gemInfo.m_name, gemInfo); + } } } @@ -503,7 +508,10 @@ namespace O3DE::ProjectManager const QVector& allGemInfos = allGemInfosResult.GetValue(); for (const GemInfo& gemInfo : allGemInfos) { - m_gemModel->AddGem(gemInfo); + if (gemInfo.m_name != "${Name}") + { + m_gemModel->AddGem(gemInfo); + } } const AZ::Outcome, AZStd::string>& allRepoGemInfosResult = PythonBindingsInterface::Get()->GetGemInfosForAllRepos(); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp index f6689281b8..2ded6442bd 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp @@ -30,6 +30,10 @@ namespace O3DE::ProjectManager setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); m_mainWidget = new QWidget(); + if (parent) + { + m_mainWidget->setFixedWidth(parent->width()); + } setWidget(m_mainWidget); m_mainLayout = new QVBoxLayout(); diff --git a/Code/Tools/ProjectManager/Source/NewProjectSettingsScreen.cpp b/Code/Tools/ProjectManager/Source/NewProjectSettingsScreen.cpp index f1f56993ca..d45cbaf612 100644 --- a/Code/Tools/ProjectManager/Source/NewProjectSettingsScreen.cpp +++ b/Code/Tools/ProjectManager/Source/NewProjectSettingsScreen.cpp @@ -207,6 +207,7 @@ namespace O3DE::ProjectManager { m_templateDisplayName = new QLabel(this); m_templateDisplayName->setObjectName("displayName"); + m_templateDisplayName->setWordWrap(true); templateDetailsLayout->addWidget(m_templateDisplayName); m_templateSummary = new QLabel(this); diff --git a/Code/Tools/ProjectManager/Source/ProjectButtonWidget.cpp b/Code/Tools/ProjectManager/Source/ProjectButtonWidget.cpp index 39980e1d11..7f2b47f8bf 100644 --- a/Code/Tools/ProjectManager/Source/ProjectButtonWidget.cpp +++ b/Code/Tools/ProjectManager/Source/ProjectButtonWidget.cpp @@ -330,6 +330,11 @@ namespace O3DE::ProjectManager void ProjectButton::ShowWarning(bool show, const QString& warning) { + if (show) + { + // hide any overlay text, we cannot show the warning and an overlay at the same time + m_projectImageLabel->GetOverlayLabel()->setVisible(false); + } m_projectImageLabel->GetWarningLabel()->setTextInteractionFlags(Qt::LinksAccessibleByMouse); m_projectImageLabel->GetWarningLabel()->setText(warning); m_projectImageLabel->GetWarningLabel()->setVisible(show); @@ -362,6 +367,9 @@ namespace O3DE::ProjectManager m_projectImageLabel->SetOverlayText(tr("Building...\n\n")); m_projectMenuButton->setVisible(false); + // hide the warning label about this project needing to be built while building + ShowWarning(false, ""); + QProgressBar* progressBar = m_projectImageLabel->GetProgressBar(); progressBar->setVisible(true); progressBar->setValue(0); diff --git a/Code/Tools/ProjectManager/Source/TemplateButtonWidget.cpp b/Code/Tools/ProjectManager/Source/TemplateButtonWidget.cpp index 57501b162f..51dde9bf97 100644 --- a/Code/Tools/ProjectManager/Source/TemplateButtonWidget.cpp +++ b/Code/Tools/ProjectManager/Source/TemplateButtonWidget.cpp @@ -38,6 +38,7 @@ namespace O3DE::ProjectManager QLabel* label = new QLabel(labelText, this); label->setObjectName("templateLabel"); + label->setWordWrap(true); vLayout->addWidget(label); connect(this, &QAbstractButton::toggled, this, &TemplateButton::onToggled); diff --git a/Templates/DefaultProject/template.json b/Templates/DefaultProject/template.json index 203e1ec4ef..13555889e6 100644 --- a/Templates/DefaultProject/template.json +++ b/Templates/DefaultProject/template.json @@ -7,7 +7,7 @@ "origin_url": "https://github.com/o3de/o3de", "license": "Apache-2.0 or MIT", "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", - "display_name": "Default Project Template", + "display_name": "Default", "summary": "This is the project template that will be used if no project template is specified during project creation.", "canonical_tags": [ "Template", diff --git a/Templates/MinimalProject/template.json b/Templates/MinimalProject/template.json index 4cc3031ff5..fff57e609b 100644 --- a/Templates/MinimalProject/template.json +++ b/Templates/MinimalProject/template.json @@ -7,7 +7,7 @@ "origin_url": "https://github.com/o3de/o3de", "license": "Apache-2.0 or MIT", "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", - "display_name": "Minimal Project Template", + "display_name": "Minimal", "summary": "Use this project template to create project that is the absolute minimum needed to get started in O3DE.", "canonical_tags": [ "Template", diff --git a/scripts/o3de/o3de/manifest.py b/scripts/o3de/o3de/manifest.py index 76ba0a9597..69e4c0bd7f 100644 --- a/scripts/o3de/o3de/manifest.py +++ b/scripts/o3de/o3de/manifest.py @@ -37,6 +37,12 @@ def get_o3de_folder() -> pathlib.Path: return o3de_folder +def get_o3de_user_folder() -> pathlib.Path: + o3de_user_folder = get_home_folder() / 'O3DE' + o3de_user_folder.mkdir(parents=True, exist_ok=True) + return o3de_user_folder + + def get_o3de_registry_folder() -> pathlib.Path: registry_folder = get_o3de_folder() / 'Registry' registry_folder.mkdir(parents=True, exist_ok=True) @@ -62,19 +68,19 @@ def get_o3de_engines_folder() -> pathlib.Path: def get_o3de_projects_folder() -> pathlib.Path: - projects_folder = get_o3de_folder() / 'Projects' + projects_folder = get_o3de_user_folder() / 'Projects' projects_folder.mkdir(parents=True, exist_ok=True) return projects_folder def get_o3de_gems_folder() -> pathlib.Path: - gems_folder = get_o3de_folder() / 'Gems' + gems_folder = get_o3de_user_folder() / 'Gems' gems_folder.mkdir(parents=True, exist_ok=True) return gems_folder def get_o3de_templates_folder() -> pathlib.Path: - templates_folder = get_o3de_folder() / 'Templates' + templates_folder = get_o3de_user_folder() / 'Templates' templates_folder.mkdir(parents=True, exist_ok=True) return templates_folder @@ -426,6 +432,50 @@ def get_templates_for_generic_creation(project_path: pathlib.Path = None) -> lis return generic_templates +def get_json_file_path(object_typename: str, + object_path: str or pathlib.Path) -> pathlib.Path: + if not object_typename or not object_path: + logger.error('Must specify an object typename and object path.') + return None + + object_path = pathlib.Path(object_path).resolve() + return object_path / f'{object_typename}.json' + + +def get_json_data_file(object_json: pathlib.Path, + object_typename: str, + object_validator: callable) -> dict or None: + if not object_typename: + logger.error('Missing object typename.') + return None + + if not object_json or not object_json.is_file(): + logger.error(f'Invalid {object_typename} json {object_json} supplied or file missing.') + return None + + if not object_validator or not object_validator(object_json): + logger.error(f'{object_typename} json {object_json} is not valid or could not be validated.') + return None + + with object_json.open('r') as f: + try: + object_json_data = json.load(f) + except json.JSONDecodeError as e: + logger.warning(f'{object_json} failed to load: {e}') + else: + return object_json_data + + return None + + +def get_json_data(object_typename: str, + object_path: str or pathlib.Path, + object_validator: callable) -> dict or None: + object_json = get_json_file_path(object_typename, object_path) + + return get_json_data_file(object_json, object_typename, object_validator) + + def get_engine_json_data(engine_name: str = None, engine_path: str or pathlib.Path = None) -> dict or None: if not engine_name and not engine_path: @@ -435,28 +485,7 @@ def get_engine_json_data(engine_name: str = None, if engine_name and not engine_path: engine_path = get_registered(engine_name=engine_name) - if not engine_path: - logger.error(f'Engine Path {engine_path} has not been registered.') - return None - - engine_path = pathlib.Path(engine_path).resolve() - engine_json = engine_path / 'engine.json' - if not engine_json.is_file(): - logger.error(f'Engine json {engine_json} is not present.') - return None - if not validation.valid_o3de_engine_json(engine_json): - logger.error(f'Engine json {engine_json} is not valid.') - return None - - with engine_json.open('r') as f: - try: - engine_json_data = json.load(f) - except json.JSONDecodeError as e: - logger.warning(f'{engine_json} failed to load: {str(e)}') - else: - return engine_json_data - - return None + return get_json_data('engine', engine_path, validation.valid_o3de_engine_json) def get_project_json_data(project_name: str = None, @@ -468,28 +497,7 @@ def get_project_json_data(project_name: str = None, if project_name and not project_path: project_path = get_registered(project_name=project_name) - if not project_path: - logger.error(f'Project Path {project_path} has not been registered.') - return None - - project_path = pathlib.Path(project_path).resolve() - project_json = project_path / 'project.json' - if not project_json.is_file(): - logger.error(f'Project json {project_json} is not present.') - return None - if not validation.valid_o3de_project_json(project_json): - logger.error(f'Project json {project_json} is not valid.') - return None - - with project_json.open('r') as f: - try: - project_json_data = json.load(f) - except json.JSONDecodeError as e: - logger.warning(f'{project_json} failed to load: {str(e)}') - else: - return project_json_data - - return None + return get_json_data('project', project_path, validation.valid_o3de_project_json) def get_gem_json_data(gem_name: str = None, gem_path: str or pathlib.Path = None, @@ -501,28 +509,10 @@ def get_gem_json_data(gem_name: str = None, gem_path: str or pathlib.Path = None if gem_name and not gem_path: gem_path = get_registered(gem_name=gem_name, project_path=project_path) - if not gem_path: - logger.error(f'Gem Path {gem_path} has not been registered.') - return None - - gem_path = pathlib.Path(gem_path).resolve() - gem_json = gem_path / 'gem.json' - if not gem_json.is_file(): - logger.error(f'Gem json {gem_json} is not present.') - return None - if not validation.valid_o3de_gem_json(gem_json): - logger.error(f'Gem json {gem_json} is not valid.') - return None - - with gem_json.open('r') as f: - try: - gem_json_data = json.load(f) - except json.JSONDecodeError as e: - logger.warning(f'{gem_json} failed to load: {str(e)}') - else: - return gem_json_data - - return None + if pathlib.Path(gem_path).is_file(): + return get_json_data_file(gem_path, 'gem', validation.valid_o3de_gem_json) + else: + return get_json_data('gem', gem_path, validation.valid_o3de_gem_json) def get_template_json_data(template_name: str = None, template_path: str or pathlib.Path = None, @@ -534,28 +524,7 @@ def get_template_json_data(template_name: str = None, template_path: str or path if template_name and not template_path: template_path = get_registered(template_name=template_name, project_path=project_path) - if not template_path: - logger.error(f'Template Path {template_path} has not been registered.') - return None - - template_path = pathlib.Path(template_path).resolve() - template_json = template_path / 'template.json' - if not template_json.is_file(): - logger.error(f'Template json {template_json} is not present.') - return None - if not validation.valid_o3de_template_json(template_json): - logger.error(f'Template json {template_json} is not valid.') - return None - - with template_json.open('r') as f: - try: - template_json_data = json.load(f) - except json.JSONDecodeError as e: - logger.warning(f'{template_json} failed to load: {str(e)}') - else: - return template_json_data - - return None + return get_json_data('template', template_path, validation.valid_o3de_template_json) def get_restricted_json_data(restricted_name: str = None, restricted_path: str or pathlib.Path = None, @@ -567,28 +536,26 @@ def get_restricted_json_data(restricted_name: str = None, restricted_path: str o if restricted_name and not restricted_path: restricted_path = get_registered(restricted_name=restricted_name, project_path=project_path) - if not restricted_path: - logger.error(f'Restricted Path {restricted_path} has not been registered.') + return get_json_data('restricted', restricted_path, validation.valid_o3de_restricted_json) + + +def get_repo_json_data(repo_uri: str) -> dict or None: + if not repo_uri: + logger.error('Must specify a Repo Uri.') return None - restricted_path = pathlib.Path(restricted_path).resolve() - restricted_json = restricted_path / 'restricted.json' - if not restricted_json.is_file(): - logger.error(f'Restricted json {restricted_json} is not present.') - return None - if not validation.valid_o3de_restricted_json(restricted_json): - logger.error(f'Restricted json {restricted_json} is not valid.') - return None + repo_json = get_repo_path(repo_uri=repo_uri) - with restricted_json.open('r') as f: - try: - restricted_json_data = json.load(f) - except json.JSONDecodeError as e: - logger.warning(f'{restricted_json} failed to load: {str(e)}') - else: - return restricted_json_data + return get_json_data_file(repo_json, "Repo", validation.valid_o3de_repo_json) - return None + +def get_repo_path(repo_uri: str, cache_folder: str or pathlib.Path = None) -> pathlib.Path: + if not cache_folder: + cache_folder = get_o3de_cache_folder() + + repo_manifest = f'{repo_uri}/repo.json' + repo_sha256 = hashlib.sha256(repo_manifest.encode()) + return cache_folder / str(repo_sha256.hexdigest() + '.json') def get_registered(engine_name: str = None, @@ -743,9 +710,7 @@ def get_registered(engine_name: str = None, elif isinstance(repo_name, str): cache_folder = get_o3de_cache_folder() for repo_uri in json_data['repos']: - repo_uri = pathlib.Path(repo_uri).resolve() - repo_sha256 = hashlib.sha256(repo_uri.encode()) - cache_file = cache_folder / str(repo_sha256.hexdigest() + '.json') + cache_file = get_repo_path(repo_uri=repo_uri, cache_folder=cache_folder) if cache_file.is_file(): repo = pathlib.Path(cache_file).resolve() with repo.open('r') as f: diff --git a/scripts/o3de/o3de/register.py b/scripts/o3de/o3de/register.py index 926c413521..a8b9ddaa04 100644 --- a/scripts/o3de/o3de/register.py +++ b/scripts/o3de/o3de/register.py @@ -490,7 +490,7 @@ def register_repo(json_data: dict, repo_sha256 = hashlib.sha256(url.encode()) cache_file = manifest.get_o3de_cache_folder() / str(repo_sha256.hexdigest() + '.json') - result = utils.download_file(url, cache_file, True) + result = utils.download_file(parsed_uri, cache_file, True) if result == 0: json_data.setdefault('repos', []).insert(0, repo_uri)