Merge pull request #5675 from aws-lumberyard-dev/amzn-tommy/gitflow_211116_o3de2

Merge stabilization/2110 as of c3dacb1 to development
This commit is contained in:
Tommy Walton
2021-11-17 11:26:41 -08:00
committed by GitHub
176 changed files with 1622 additions and 1784 deletions
@@ -14,6 +14,7 @@ from datetime import datetime
import ly_test_tools.log.log_monitor
from AWS.common import constants
from AWS.common.resource_mappings import AWS_RESOURCE_MAPPINGS_ACCOUNT_ID_KEY
from .aws_metrics_custom_thread import AWSMetricsThread
# fixture imports
@@ -200,6 +201,59 @@ class TestAWSMetricsWindows(object):
for thread in operational_threads:
thread.join()
@pytest.mark.parametrize('level', ['AWS/Metrics'])
def test_realtime_and_batch_analytics_no_global_accountid(self,
level: str,
launcher: pytest.fixture,
asset_processor: pytest.fixture,
workspace: pytest.fixture,
aws_utils: pytest.fixture,
resource_mappings: pytest.fixture,
aws_metrics_utils: pytest.fixture):
"""
Verify that the metrics events are sent to CloudWatch and S3 for analytics.
"""
# Remove top-level account ID from resource mappings
resource_mappings.clear_select_keys([AWS_RESOURCE_MAPPINGS_ACCOUNT_ID_KEY])
# Start Kinesis analytics application on a separate thread to avoid blocking the test.
kinesis_analytics_application_thread = AWSMetricsThread(target=update_kinesis_analytics_application_status,
args=(aws_metrics_utils, resource_mappings, True))
kinesis_analytics_application_thread.start()
log_monitor = setup(launcher, asset_processor)
# Kinesis analytics application needs to be in the running state before we start the game launcher.
kinesis_analytics_application_thread.join()
launcher.args = ['+LoadLevel', level]
launcher.args.extend(['-rhi=null'])
start_time = datetime.utcnow()
with launcher.start(launch_ap=False):
monitor_metrics_submission(log_monitor)
# Verify that real-time analytics metrics are delivered to CloudWatch.
aws_metrics_utils.verify_cloud_watch_delivery(
AWS_METRICS_FEATURE_NAME,
'TotalLogins',
[],
start_time)
logger.info('Real-time metrics are sent to CloudWatch.')
# Run time-consuming operations on separate threads to avoid blocking the test.
operational_threads = list()
operational_threads.append(
AWSMetricsThread(target=query_metrics_from_s3,
args=(aws_metrics_utils, resource_mappings)))
operational_threads.append(
AWSMetricsThread(target=verify_operational_metrics,
args=(aws_metrics_utils, resource_mappings, start_time)))
operational_threads.append(
AWSMetricsThread(target=update_kinesis_analytics_application_status,
args=(aws_metrics_utils, resource_mappings, False)))
for thread in operational_threads:
thread.start()
for thread in operational_threads:
thread.join()
@pytest.mark.parametrize('level', ['AWS/Metrics'])
def test_unauthorized_user_request_rejected(self,
level: str,
@@ -18,6 +18,7 @@ import ly_test_tools.environment.process_utils as process_utils
import ly_test_tools.o3de.asset_processor_utils as asset_processor_utils
from AWS.common import constants
from AWS.common.resource_mappings import AWS_RESOURCE_MAPPINGS_ACCOUNT_ID_KEY
# fixture imports
from assetpipeline.ap_fixtures.asset_processor_fixture import asset_processor
@@ -141,3 +142,51 @@ class TestAWSCoreAWSResourceInteraction(object):
'The expected file wasn\'t successfully downloaded.'
# clean up the file directories.
shutil.rmtree(s3_download_dir)
@pytest.mark.parametrize('expected_lines', [
['(Script) - [S3] Head object request is done',
'(Script) - [S3] Head object success: Object example.txt is found.',
'(Script) - [S3] Get object success: Object example.txt is downloaded.',
'(Script) - [Lambda] Completed Invoke',
'(Script) - [Lambda] Invoke success: {"statusCode": 200, "body": {}}',
'(Script) - [DynamoDB] Results finished']])
@pytest.mark.parametrize('unexpected_lines', [
['(Script) - [S3] Head object error: No response body.',
'(Script) - [S3] Get object error: Request validation failed, output file directory doesn\'t exist.',
'(Script) - Request validation failed, output file miss full path.',
'(Script) - ']])
def test_scripting_behavior_no_global_accountid(self,
level: str,
launcher: pytest.fixture,
workspace: pytest.fixture,
asset_processor: pytest.fixture,
resource_mappings: pytest.fixture,
aws_utils: pytest.fixture,
expected_lines: typing.List[str],
unexpected_lines: typing.List[str]):
"""
Setup: Updates resource mapping file using existing CloudFormation stacks.
Tests: Interact with AWS S3, DynamoDB and Lambda services.
Verification: Script canvas nodes can communicate with AWS services successfully.
"""
resource_mappings.clear_select_keys([AWS_RESOURCE_MAPPINGS_ACCOUNT_ID_KEY])
log_monitor, s3_download_dir = setup(launcher, asset_processor)
write_test_data_to_dynamodb_table(resource_mappings, aws_utils)
launcher.args = ['+LoadLevel', level]
launcher.args.extend(['-rhi=null'])
with launcher.start(launch_ap=False):
result = log_monitor.monitor_log_for_lines(
expected_lines=expected_lines,
unexpected_lines=unexpected_lines,
halt_on_unexpected=True
)
assert result, "Expected lines weren't found."
assert os.path.exists(os.path.join(s3_download_dir, 'output.txt')), \
'The expected file wasn\'t successfully downloaded.'
# clean up the file directories.
shutil.rmtree(s3_download_dir)
@@ -102,3 +102,17 @@ class ResourceMappings:
def get_resource_name_id(self, resource_key: str):
return self._resource_mappings[AWS_RESOURCE_MAPPINGS_KEY][resource_key]['Name/ID']
def clear_select_keys(self, resource_keys=None) -> None:
"""
Clears values from select resource mapping keys.
:param resource_keys: list of keys to clear out
"""
with open(self._resource_mapping_file_path) as file_content:
resource_mappings = json.load(file_content)
for key in resource_keys:
resource_mappings[key] = ''
with open(self._resource_mapping_file_path, 'w') as file_content:
json.dump(resource_mappings, file_content, indent=4)
@@ -158,7 +158,7 @@ def bundler_batch_setup_fixture(request, workspace, asset_processor, timeout) ->
else:
cmd.append(f"--{key}")
if append_defaults:
cmd.append(f"--project-path={os.path.join(workspace.paths.engine_root(), workspace.project)}")
cmd.append(f"--project-path={workspace.paths.project()}")
return cmd
# ******
@@ -88,214 +88,6 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
bundler_batch_helper.call_bundles(help="")
bundler_batch_helper.call_bundleSeed(help="")
@pytest.mark.BAT
@pytest.mark.assetpipeline
@pytest.mark.test_case_id("C16877175")
@pytest.mark.skip("'animations/animationeditorfiles/sample1.animgraph' missing, needs investigation")
def test_WindowsAndMac_CreateAssetList_DependenciesCorrect(self, workspace, bundler_batch_helper):
r"""
Tests that an asset list created maps dependencies correctly.
testdependencieslevel\level.pak and lists of known dependencies are used for validation
Test Steps:
1. Create an asset list from the level.pak
2. Create Lists of expected assets in the level.pak
3. Add lists of expected assets to a single list
4. Compare list of expected assets to actual assets
"""
helper = bundler_batch_helper
# Create the asset list file
helper.call_assetLists(
addSeed=r"levels\testdependencieslevel\level.pak",
assetListFile=helper['asset_info_file_request']
)
assert os.path.isfile(helper["asset_info_file_result"])
# Lists of known relative locations of assets
default_level_assets = [
"engineassets/texturemsg/defaultnouvs.dds",
"engineassets/texturemsg/defaultnouvs.dds.1",
"engineassets/texturemsg/defaultnouvs.dds.2",
"engineassets/texturemsg/defaultnouvs.dds.3",
"engineassets/texturemsg/defaultnouvs.dds.4",
"engineassets/texturemsg/defaultnouvs.dds.5",
"engineassets/texturemsg/defaultnouvs.dds.6",
"engineassets/texturemsg/defaultnouvs.dds.7",
"engineassets/texturemsg/defaultnouvs_ddn.dds",
"engineassets/texturemsg/defaultnouvs_ddn.dds.1",
"engineassets/texturemsg/defaultnouvs_ddn.dds.2",
"engineassets/texturemsg/defaultnouvs_ddn.dds.3",
"engineassets/texturemsg/defaultnouvs_ddn.dds.4",
"engineassets/texturemsg/defaultnouvs_ddn.dds.5",
"engineassets/texturemsg/defaultnouvs_spec.dds",
"engineassets/texturemsg/defaultnouvs_spec.dds.1",
"engineassets/texturemsg/defaultnouvs_spec.dds.2",
"engineassets/texturemsg/defaultnouvs_spec.dds.3",
"engineassets/texturemsg/defaultnouvs_spec.dds.4",
"engineassets/texturemsg/defaultnouvs_spec.dds.5",
"engineassets/textures/defaults/16_grey.dds",
"engineassets/textures/cubemap/default_level_cubemap.dds",
"engineassets/textures/cubemap/default_level_cubemap.dds.1",
"engineassets/textures/cubemap/default_level_cubemap.dds.2",
"engineassets/textures/cubemap/default_level_cubemap.dds.3",
"engineassets/textures/cubemap/default_level_cubemap.dds.4",
"engineassets/textures/cubemap/default_level_cubemap_diff.dds",
"engineassets/materials/water/ocean_default.mtl",
"engineassets/textures/defaults/spot_default.dds",
"engineassets/textures/defaults/spot_default.dds.1",
"engineassets/textures/defaults/spot_default.dds.2",
"engineassets/textures/defaults/spot_default.dds.3",
"engineassets/textures/defaults/spot_default.dds.4",
"engineassets/textures/defaults/spot_default.dds.5",
"materials/material_terrain_default.mtl",
"textures/skys/night/half_moon.dds",
"textures/skys/night/half_moon.dds.1",
"textures/skys/night/half_moon.dds.2",
"textures/skys/night/half_moon.dds.3",
"textures/skys/night/half_moon.dds.4",
"textures/skys/night/half_moon.dds.5",
"textures/skys/night/half_moon.dds.6",
"engineassets/materials/sky/sky.mtl",
"levels/testdependencieslevel/level.pak",
"levels/testdependencieslevel/terrain/cover.ctc",
"levels/testdependencieslevel/terraintexture.pak",
]
sequence_material_cube_assets = [
"textures/test_texture_sequence/test_texture_sequence000.dds",
"textures/test_texture_sequence/test_texture_sequence001.dds",
"textures/test_texture_sequence/test_texture_sequence002.dds",
"textures/test_texture_sequence/test_texture_sequence003.dds",
"textures/test_texture_sequence/test_texture_sequence004.dds",
"textures/test_texture_sequence/test_texture_sequence005.dds",
"objects/_primitives/_box_1x1.cgf",
"materials/test_texture_sequence.mtl",
"objects/_primitives/_box_1x1.mtl",
"textures/_primitives/middle_gray_checker.dds",
"textures/_primitives/middle_gray_checker.dds.1",
"textures/_primitives/middle_gray_checker.dds.2",
"textures/_primitives/middle_gray_checker.dds.3",
"textures/_primitives/middle_gray_checker.dds.4",
"textures/_primitives/middle_gray_checker.dds.5",
"textures/_primitives/middle_gray_checker_ddn.dds",
"textures/_primitives/middle_gray_checker_ddn.dds.1",
"textures/_primitives/middle_gray_checker_ddn.dds.2",
"textures/_primitives/middle_gray_checker_ddn.dds.3",
"textures/_primitives/middle_gray_checker_ddn.dds.4",
"textures/_primitives/middle_gray_checker_ddn.dds.5",
"textures/_primitives/middle_gray_checker_spec.dds",
"textures/_primitives/middle_gray_checker_spec.dds.1",
"textures/_primitives/middle_gray_checker_spec.dds.2",
"textures/_primitives/middle_gray_checker_spec.dds.3",
"textures/_primitives/middle_gray_checker_spec.dds.4",
"textures/_primitives/middle_gray_checker_spec.dds.5",
]
character_with_simplified_material_assets = [
"objects/characters/jack/jack.actor",
"objects/characters/jack/jack.mtl",
"objects/characters/jack/textures/jack_diff.dds",
"objects/characters/jack/textures/jack_diff.dds.1",
"objects/characters/jack/textures/jack_diff.dds.2",
"objects/characters/jack/textures/jack_diff.dds.3",
"objects/characters/jack/textures/jack_diff.dds.4",
"objects/characters/jack/textures/jack_diff.dds.5",
"objects/characters/jack/textures/jack_diff.dds.6",
"objects/characters/jack/textures/jack_diff.dds.7",
"objects/characters/jack/textures/jack_spec.dds",
"objects/characters/jack/textures/jack_spec.dds.1",
"objects/characters/jack/textures/jack_spec.dds.2",
"objects/characters/jack/textures/jack_spec.dds.3",
"objects/characters/jack/textures/jack_spec.dds.4",
"objects/characters/jack/textures/jack_spec.dds.5",
"objects/characters/jack/textures/jack_spec.dds.6",
"objects/characters/jack/textures/jack_spec.dds.7",
"objects/default/editorprimitive.mtl",
"engineassets/textures/grey.dds",
"animations/animationeditorfiles/sample0.animgraph",
"animations/motions/jack_death_fall_back_zup.motion",
"animations/animationeditorfiles/sample1.animgraph",
"animations/animationeditorfiles/sample0.motionset",
"animations/motions/rin_jump.motion",
"animations/animationeditorfiles/sample1.motionset",
"animations/motions/rin_idle.motion",
"animations/motions/jack_idle_aim_zup.motion",
]
spawner_assets = [
"slices/sphere.dynamicslice",
"objects/default/primitive_sphere.cgf",
"test1.luac",
"test2.luac",
]
ui_canvas_assets = [
"fonts/vera.ttf",
"fonts/vera.font",
"scriptcanvas/mainmenu.scriptcanvas_compiled",
"fonts/vera.fontfamily",
"ui/canvas/start.uicanvas",
"fonts/vera-italic.font",
"ui/textureatlas/sample.texatlasidx",
"fonts/vera-bold-italic.ttf",
"fonts/vera-bold.font",
"ui/textures/prefab/button_normal.dds",
"ui/textures/prefab/button_normal.sprite",
"fonts/vera-italic.ttf",
"ui/textureatlas/sample.dds",
"fonts/vera-bold-italic.font",
"fonts/vera-bold.ttf",
"ui/textures/prefab/button_disabled.dds",
"ui/textures/prefab/button_disabled.sprite",
]
wwise_and_atl_assets = [
"libs/gameaudio/wwise/levels/testdependencieslevel/test_dependencies_level.xml",
"sounds/wwise/test_bank3.bnk",
"sounds/wwise/test_bank4.bnk",
"sounds/wwise/test_bank5.bnk",
"sounds/wwise/test_bank1.bnk",
"sounds/wwise/init.bnk",
"sounds/wwise/499820003.wem",
"sounds/wwise/196049145.wem",
]
particle_library_assets = [
"libs/particles/milestone2particles.xml",
"textures/milestone2/particles/fx_launchermuzzlering_01.dds",
"textures/milestone2/particles/fx_launchermuzzlering_01.dds.1",
"textures/milestone2/particles/fx_launchermuzzlering_01.dds.2",
"textures/milestone2/particles/fx_launchermuzzlering_01.dds.3",
"textures/milestone2/particles/fx_launchermuzzlering_01.dds.4",
"textures/milestone2/particles/fx_launchermuzzlering_01.dds.5",
"textures/milestone2/particles/fx_sparkstreak_01.dds",
"textures/milestone2/particles/fx_launchermuzzlefront_01.dds",
"textures/milestone2/particles/fx_launchermuzzlefront_01.dds.1",
"textures/milestone2/particles/fx_launchermuzzlefront_01.dds.2",
"textures/milestone2/particles/fx_launchermuzzlefront_01.dds.3",
"textures/milestone2/particles/fx_launchermuzzlefront_01.dds.4",
"textures/milestone2/particles/fx_launchermuzzlefront_01.dds.5",
]
lens_flares_library_assets = ["libs/flares/flares.xml", "textures/lights/flare01.dds"]
expected_assets_list = default_level_assets
expected_assets_list.extend(sequence_material_cube_assets)
expected_assets_list.extend(character_with_simplified_material_assets)
expected_assets_list.extend(spawner_assets)
expected_assets_list.extend(ui_canvas_assets)
expected_assets_list.extend(wwise_and_atl_assets)
expected_assets_list.extend(particle_library_assets)
expected_assets_list.extend(lens_flares_library_assets) # All expected assets
# Get actual calculated dependencies from the asset list created
actual_assets_list = []
for rel_path in helper.get_asset_relative_paths(helper["asset_info_file_result"]):
actual_assets_list.append(rel_path)
assert sorted(actual_assets_list) == sorted(expected_assets_list)
@pytest.mark.BAT
@pytest.mark.assetpipeline
@@ -310,9 +102,9 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
3. Read and store contents of asset list into memory
4. Attempt to create a new asset list in without using --allowOverwrites
5. Verify that Asset Bundler returns false
6. Verify that file contents of the orignally created asset list did not change from what was stored in memory
6. Verify that file contents of the originally created asset list did not change from what was stored in memory
7. Attempt to create a new asset list without debug while allowing overwrites
8. Verify that file contents of the orignally created asset list changed from what was stored in memory
8. Verify that file contents of the originally created asset list changed from what was stored in memory
"""
helper = bundler_batch_helper
seed_list = os.path.join(workspace.paths.engine_root(), "Assets", "Engine", "SeedAssetList.seed") # Engine seed list
@@ -919,7 +711,7 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
# Extra arguments for pattern comparison
cmd.extend([f"--filePatternType={pattern_type}", f"--filePattern={pattern}"])
if workspace.project:
cmd.append(f'--project-path={project_name}')
cmd.append(f'--project-path={workspace.paths.project()}')
return cmd
# End generate_compare_command()
@@ -960,7 +752,7 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
output_mac_asset_list = helper.platform_file_name(last_output_arg, platform)
# Build execution command
cmd = generate_compare_command(platform_arg, workspace.project)
cmd = generate_compare_command(platform_arg, workspace.paths.project())
# Execute command
subprocess.check_call(cmd)
@@ -995,7 +787,7 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
f"--comparisonRulesFile={rule_file}",
f"--comparisonType={args[1]}",
r"--addComparison",
f"--project-path={workspace.project}",
f"--project-path={workspace.paths.project()}",
]
if args[1] == "4":
# If pattern comparison, append a few extra arguments
@@ -1117,7 +909,7 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
"--addDefaultSeedListFiles",
"--platform=pc",
"--print",
f"--project-path={workspace.project}"
f"--project-path={workspace.paths.project()}"
],
universal_newlines=True,
)
@@ -1189,7 +981,7 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
# Make sure file gets deleted on teardown
request.addfinalizer(lambda: fs.delete([bundle_result_path], True, False))
bundles_folder = os.path.join(workspace.paths.engine_root(), workspace.project, "Bundles")
bundles_folder = os.path.join(workspace.paths.project(), "Bundles")
level_pak = r"levels\testdependencieslevel\level.pak"
bundle_request_path = os.path.join(bundles_folder, "bundle.pak")
bundle_result_path = os.path.join(bundles_folder,
@@ -1243,23 +1035,64 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
2. Verify file was created
3. Verify that only the expected assets are present in the created asset list
"""
expected_assets = [
expected_assets = sorted([
"ui/canvases/lyshineexamples/animation/multiplesequences.uicanvas",
"ui/textures/prefab/button_normal.sprite"
]
"ui/textures/prefab/button_disabled.tif.streamingimage",
"ui/textures/prefab/tooltip_sliced.tif.streamingimage",
"ui/textures/prefab/button_normal.tif.streamingimage"
])
# Printing these lists out can save a step in debugging if this test fails on Jenkins.
logger.info(f"expected_assets: {expected_assets}")
skip_assets = sorted([
"ui/scripts/lyshineexamples/animation/multiplesequences.luac",
"ui/scripts/lyshineexamples/unloadthiscanvasbutton.luac",
"fonts/vera.fontfamily",
"fonts/vera-italic.font",
"fonts/vera.font",
"fonts/vera-bold.font",
"fonts/vera-bold-italic.font",
"fonts/vera-italic.ttf",
"fonts/vera.ttf",
"fonts/vera-bold.ttf",
"fonts/vera-bold-italic.ttf"
])
logger.info(f"skip_assets: {skip_assets}")
expected_and_skip_assets = sorted(expected_assets + skip_assets)
# Printing both together to make it quick to compare the results in the logs for a test failure on Jenkins
logger.info(f"expected_and_skip_assets: {expected_and_skip_assets}")
# First, generate an asset info file without skipping, to get a list that can be used as a baseline to verify
# the files were actually skipped, and not just missing.
bundler_batch_helper.call_assetLists(
assetListFile=bundler_batch_helper['asset_info_file_request'],
addSeed="ui/canvases/lyshineexamples/animation/multiplesequences.uicanvas"
)
assert os.path.isfile(bundler_batch_helper["asset_info_file_result"])
assets_in_no_skip_list = []
for rel_path in bundler_batch_helper.get_asset_relative_paths(bundler_batch_helper["asset_info_file_result"]):
assets_in_no_skip_list.append(rel_path)
assets_in_no_skip_list = sorted(assets_in_no_skip_list)
logger.info(f"assets_in_no_skip_list: {assets_in_no_skip_list}")
assert assets_in_no_skip_list == expected_and_skip_assets
# Now generate an asset info file using the skip command, and verify the skip files are not in the list.
bundler_batch_helper.call_assetLists(
assetListFile=bundler_batch_helper['asset_info_file_request'],
addSeed="ui/canvases/lyshineexamples/animation/multiplesequences.uicanvas",
skip="ui/textures/prefab/button_disabled.sprite,ui/scripts/lyshineexamples/animation/multiplesequences.luac,"
"ui/textures/prefab/tooltip_sliced.sprite,ui/scripts/lyshineexamples/unloadthiscanvasbutton.luac,fonts/vera.fontfamily,fonts/vera-italic.font,"
"fonts/vera.font,fonts/vera-bold.font,fonts/vera-bold-italic.font,fonts/vera-italic.ttf,fonts/vera.ttf,fonts/vera-bold.ttf,fonts/vera-bold-italic.ttf"
allowOverwrites="",
skip=','.join(skip_assets)
)
assert os.path.isfile(bundler_batch_helper["asset_info_file_result"])
assets_in_list = []
for rel_path in bundler_batch_helper.get_asset_relative_paths(bundler_batch_helper["asset_info_file_result"]):
assets_in_list.append(rel_path)
assets_in_list = sorted(assets_in_list)
logger.info(f"assets_in_list: {assets_in_list}")
assert assets_in_list == expected_assets
assert sorted(assets_in_list) == sorted(expected_assets)
@pytest.mark.BAT
@pytest.mark.assetpipeline
@@ -199,41 +199,6 @@ namespace AzAssetBrowserRequestHandlerPrivate
}
}
}
// Helper utility - determines if the thing being dragged is a FBX from the scene import pipeline
// This is important to differentiate.
// when someone drags a MTL file directly into the viewport, even from a FBX, we want to spawn it as a decal
// but when someone drags a FBX that contains MTL files, we want only to spawn the meshes.
// so we have to specifically differentiate here between the mimeData type that contains the source as the root
// (dragging the fbx file itself)
// and one which contains the actual product at its root.
bool IsDragOfFBX(const QMimeData* mimeData)
{
AZStd::vector<AssetBrowserEntry*> entries;
if (!AssetBrowserEntry::FromMimeData(mimeData, entries))
{
// if mimedata does not even contain entries, no point in proceeding.
return false;
}
for (auto entry : entries)
{
if (entry->GetEntryType() != AssetBrowserEntry::AssetEntryType::Source)
{
continue;
}
// this is a source file. Is it the filetype we're looking for?
if (SourceAssetBrowserEntry* source = azrtti_cast<SourceAssetBrowserEntry*>(entry))
{
if (AzFramework::StringFunc::Equal(source->GetExtension().c_str(), ".fbx", false))
{
return true;
}
}
}
return false;
}
}
AzAssetBrowserRequestHandler::AzAssetBrowserRequestHandler()
+10 -4
View File
@@ -370,10 +370,8 @@ void CCryEditApp::RegisterActionHandlers()
ON_COMMAND(ID_EDIT_FETCH, OnEditFetch)
ON_COMMAND(ID_FILE_EXPORTTOGAMENOSURFACETEXTURE, OnFileExportToGameNoSurfaceTexture)
ON_COMMAND(ID_VIEW_SWITCHTOGAME, OnViewSwitchToGame)
MainWindow::instance()->GetActionManager()->RegisterActionHandler(ID_VIEW_SWITCHTOGAME_FULLSCREEN, [this]() {
ed_previewGameInFullscreen_once = true;
OnViewSwitchToGame();
});
ON_COMMAND(ID_VIEW_SWITCHTOGAME_VIEWPORT, OnViewSwitchToGame)
ON_COMMAND(ID_VIEW_SWITCHTOGAME_FULLSCREEN, OnViewSwitchToGameFullScreen)
ON_COMMAND(ID_MOVE_OBJECT, OnMoveObject)
ON_COMMAND(ID_RENAME_OBJ, OnRenameObj)
ON_COMMAND(ID_UNDO, OnUndo)
@@ -2573,6 +2571,12 @@ void CCryEditApp::OnViewSwitchToGame()
GetIEditor()->SetInGameMode(inGame);
}
void CCryEditApp::OnViewSwitchToGameFullScreen()
{
ed_previewGameInFullscreen_once = true;
OnViewSwitchToGame();
}
//////////////////////////////////////////////////////////////////////////
void CCryEditApp::OnExportSelectedObjects()
{
@@ -4184,6 +4188,8 @@ extern "C" int AZ_DLL_EXPORT CryEditMain(int argc, char* argv[])
"\nThis could be because of incorrectly configured components, or missing required gems."
"\nSee other errors for more details.");
AzToolsFramework::EditorEventsBus::Broadcast(&AzToolsFramework::EditorEvents::NotifyEditorInitialized);
if (didCryEditStart)
{
app->EnableOnIdle();
+1
View File
@@ -212,6 +212,7 @@ public:
void OnEditFetch();
void OnFileExportToGameNoSurfaceTexture();
void OnViewSwitchToGame();
void OnViewSwitchToGameFullScreen();
void OnViewDeploy();
void DeleteSelectedEntities(bool includeDescendants);
void OnMoveObject();
@@ -9,10 +9,6 @@
#define CRYINCLUDE_EDITOR_MATERIAL_IEDITORMATERIALMANAGER_H
#pragma once
#define MATERIAL_FILE_EXT ".mtl"
#define DCC_MATERIAL_FILE_EXT ".dccmtl"
#define MATERIALS_PATH "materials/"
#include <Include/IBaseLibraryManager.h>
#include <IMaterial.h>
+11 -9
View File
@@ -939,27 +939,27 @@ void MainWindow::InitActions()
.Connect(&QAction::triggered, this, &MainWindow::OnRefreshAudioSystem);
// Game actions
am->AddAction(ID_VIEW_SWITCHTOGAME, tr("Play &Game"))
am->AddAction(ID_VIEW_SWITCHTOGAME, tr("Play Game"))
.SetIcon(QIcon(":/stylesheet/img/UI20/toolbar/Play.svg"))
.SetToolTip(tr("Play Game"))
.SetStatusTip(tr("Activate the game input mode"))
.SetCheckable(true)
.RegisterUpdateCallback(cryEdit, &CCryEditApp::OnUpdatePlayGame);
am->AddAction(ID_VIEW_SWITCHTOGAME_VIEWPORT, tr("Play Game"))
.SetShortcut(tr("Ctrl+G"))
.SetToolTip(tr("Play Game (Ctrl+G)"))
.SetStatusTip(tr("Activate the game input mode"))
.SetApplyHoverEffect()
.SetCheckable(true)
.RegisterUpdateCallback(cryEdit, &CCryEditApp::OnUpdatePlayGame);
am->AddAction(ID_VIEW_SWITCHTOGAME_FULLSCREEN, tr("Play &Game (Maximized)"))
am->AddAction(ID_VIEW_SWITCHTOGAME_FULLSCREEN, tr("Play Game (Maximized)"))
.SetShortcut(tr("Ctrl+Shift+G"))
.SetStatusTip(tr("Activate the game input mode (maximized)"))
.SetIcon(Style::icon("Play"))
.SetApplyHoverEffect()
.SetCheckable(true);
.RegisterUpdateCallback(cryEdit, &CCryEditApp::OnUpdatePlayGame);
am->AddAction(ID_TOOLBAR_WIDGET_PLAYCONSOLE_LABEL, tr("Play Controls"))
.SetText(tr("Play Controls"));
am->AddAction(ID_SWITCH_PHYSICS, tr("Simulate"))
.SetIcon(QIcon(":/stylesheet/img/UI20/toolbar/Simulate_Physics.svg"))
.SetShortcut(tr("Ctrl+P"))
.SetToolTip(tr("Simulate (Ctrl+P)"))
.SetCheckable(true)
.SetStatusTip(tr("Enable processing of Physics and AI."))
.SetApplyHoverEffect()
.SetCheckable(true)
@@ -1266,7 +1266,9 @@ void MainWindow::OnGameModeChanged(bool inGameMode)
// block signals on the switch to game actions before setting the checked state, as
// setting the checked state triggers the action, which will re-enter this function
// and result in an infinite loop
AZStd::vector<QAction*> actions = { m_actionManager->GetAction(ID_VIEW_SWITCHTOGAME), m_actionManager->GetAction(ID_VIEW_SWITCHTOGAME_FULLSCREEN) };
AZStd::vector<QAction*> actions = { m_actionManager->GetAction(ID_VIEW_SWITCHTOGAME_VIEWPORT),
m_actionManager->GetAction(ID_VIEW_SWITCHTOGAME_FULLSCREEN),
m_actionManager->GetAction(ID_VIEW_SWITCHTOGAME)};
for (auto action : actions)
{
action->blockSignals(true);
@@ -18,7 +18,6 @@
#include <LmbrCentral/Rendering/LensFlareAsset.h>
#include <LmbrCentral/Rendering/MeshAsset.h>
#include <LmbrCentral/Rendering/MaterialAsset.h>
#include <AzCore/Memory/Memory.h>
#include <AzCore/RTTI/TypeInfo.h>
@@ -136,14 +135,6 @@ AssetCatalogModel::AssetCatalogModel(QObject* parent)
}
}
// Special cases for SimpleAssets. If these get full-fledged AssetData types, these cases can be removed.
QString textureExtensions = LmbrCentral::TextureAsset::GetFileFilter();
m_extensionToAssetType.insert(AZStd::make_pair(textureExtensions.replace("*", "").replace(" ", "").toStdString().c_str(), AZStd::vector<AZ::Uuid> { AZ::AzTypeInfo<LmbrCentral::TextureAsset>::Uuid() }));
QString materialExtensions = LmbrCentral::MaterialAsset::GetFileFilter();
m_extensionToAssetType.insert(AZStd::make_pair(materialExtensions.replace("*", "").replace(" ", "").toStdString().c_str(), AZStd::vector<AZ::Uuid> { AZ::AzTypeInfo<LmbrCentral::MaterialAsset>::Uuid() }));
QString dccMaterialExtensions = LmbrCentral::DccMaterialAsset::GetFileFilter();
m_extensionToAssetType.insert(AZStd::make_pair(dccMaterialExtensions.replace("*", "").replace(" ", "").toStdString().c_str(), AZStd::vector<AZ::Uuid> { AZ::AzTypeInfo<LmbrCentral::DccMaterialAsset>::Uuid() }));
AZ::SerializeContext* serializeContext = nullptr;
EBUS_EVENT_RESULT(serializeContext, AZ::ComponentApplicationBus, GetSerializeContext);
AZ_Assert(serializeContext, "Failed to acquire application serialize context.");
+1
View File
@@ -104,6 +104,7 @@
#define ID_FILE_EXPORTTOGAMENOSURFACETEXTURE 33473
#define ID_VIEW_SWITCHTOGAME 33477
#define ID_VIEW_SWITCHTOGAME_FULLSCREEN 33478
#define ID_VIEW_SWITCHTOGAME_VIEWPORT 33479
#define ID_MOVE_OBJECT 33481
#define ID_RENAME_OBJ 33483
#define ID_FETCH 33496
+35 -4
View File
@@ -590,6 +590,16 @@ AmazonToolbar ToolbarManager::GetObjectToolbar() const
return t;
}
QMenu* ToolbarManager::CreatePlayButtonMenu() const
{
QMenu* playButtonMenu = new QMenu("Play Game");
playButtonMenu->addAction(m_actionManager->GetAction(ID_VIEW_SWITCHTOGAME_VIEWPORT));
playButtonMenu->addAction(m_actionManager->GetAction(ID_VIEW_SWITCHTOGAME_FULLSCREEN));
return playButtonMenu;
}
AmazonToolbar ToolbarManager::GetPlayConsoleToolbar() const
{
AmazonToolbar t = AmazonToolbar("PlayConsole", QObject::tr("Play Controls"));
@@ -598,8 +608,17 @@ AmazonToolbar ToolbarManager::GetPlayConsoleToolbar() const
t.AddAction(ID_TOOLBAR_WIDGET_SPACER_RIGHT, ORIGINAL_TOOLBAR_VERSION);
t.AddAction(ID_TOOLBAR_SEPARATOR, ORIGINAL_TOOLBAR_VERSION);
t.AddAction(ID_TOOLBAR_WIDGET_PLAYCONSOLE_LABEL, ORIGINAL_TOOLBAR_VERSION);
t.AddAction(ID_VIEW_SWITCHTOGAME, TOOLBARS_WITH_PLAY_GAME);
t.AddAction(ID_VIEW_SWITCHTOGAME_FULLSCREEN, TOOLBARS_WITH_PLAY_GAME);
QAction* playAction = m_actionManager->GetAction(ID_VIEW_SWITCHTOGAME);
QToolButton* playButton = new QToolButton(t.Toolbar());
QMenu* menu = CreatePlayButtonMenu();
menu->setParent(t.Toolbar());
playAction->setMenu(menu);
playButton->setDefaultAction(playAction);
t.AddWidget(playButton, ID_VIEW_SWITCHTOGAME, ORIGINAL_TOOLBAR_VERSION);
t.AddAction(ID_TOOLBAR_SEPARATOR, ORIGINAL_TOOLBAR_VERSION);
t.AddAction(ID_SWITCH_PHYSICS, TOOLBARS_WITH_PLAY_GAME);
return t;
@@ -728,7 +747,14 @@ void AmazonToolbar::SetActionsOnInternalToolbar(ActionManager* actionManager)
{
if (actionManager->HasAction(actionId))
{
m_toolbar->addAction(actionManager->GetAction(actionId));
if (actionData.widget != nullptr)
{
m_toolbar->addWidget(actionData.widget);
}
else
{
m_toolbar->addAction(actionManager->GetAction(actionId));
}
}
}
}
@@ -1367,7 +1393,12 @@ void AmazonToolbar::InstantiateToolbar(QMainWindow* mainWindow, ToolbarManager*
void AmazonToolbar::AddAction(int actionId, int toolbarVersionAdded)
{
m_actions.push_back({ actionId, toolbarVersionAdded });
AddWidget(nullptr, actionId, toolbarVersionAdded);
}
void AmazonToolbar::AddWidget(QWidget* widget, int actionId, int toolbarVersionAdded)
{
m_actions.push_back({ actionId, toolbarVersionAdded, widget });
}
void AmazonToolbar::Clear()
+6
View File
@@ -87,6 +87,7 @@ public:
const QString& GetTranslatedName() const { return m_translatedName; }
void AddAction(int actionId, int toolbarVersionAdded = 0);
void AddWidget(QWidget* widget, int actionId, int toolbarVersionAdded = 0);
QToolBar* Toolbar() const { return m_toolbar; }
@@ -117,6 +118,7 @@ private:
{
int actionId;
int toolbarVersionAdded;
QWidget* widget;
bool operator ==(const AmazonToolbar::ActionData& other) const
{
@@ -133,7 +135,9 @@ private:
class AmazonToolBarExpanderWatcher;
class ToolbarManager
: public QObject
{
Q_OBJECT
public:
explicit ToolbarManager(ActionManager* actionManager, MainWindow* mainWindow);
~ToolbarManager();
@@ -178,6 +182,8 @@ private:
void UpdateAllowedAreas(QToolBar* toolbar);
bool IsDirty(const AmazonToolbar& toolbar) const;
QMenu* CreatePlayButtonMenu() const;
const AmazonToolbar* FindDefaultToolbar(const QString& toolbarName) const;
AmazonToolbar* FindToolbar(const QString& toolbarName);
@@ -57,11 +57,12 @@ namespace AZ
int numberOfWorkerThreads = m_numberOfWorkerThreads;
if (numberOfWorkerThreads <= 0) // spawn default number of threads
{
#if (AZ_TRAIT_THREAD_NUM_JOB_MANAGER_WORKER_THREADS)
numberOfWorkerThreads = AZ_TRAIT_THREAD_NUM_JOB_MANAGER_WORKER_THREADS;
#else
uint32_t scaledHardwareThreads = Threading::CalcNumWorkerThreads(cl_jobThreadsConcurrencyRatio, cl_jobThreadsMinNumber, cl_jobThreadsNumReserved);
numberOfWorkerThreads = AZ::GetMin(static_cast<unsigned int>(desc.m_workerThreads.capacity()), scaledHardwareThreads);
#if (AZ_TRAIT_MAX_JOB_MANAGER_WORKER_THREADS)
numberOfWorkerThreads = AZ::GetMin(numberOfWorkerThreads, AZ_TRAIT_MAX_JOB_MANAGER_WORKER_THREADS);
#endif // (AZ_TRAIT_MAX_JOB_MANAGER_WORKER_THREADS)
#endif // (AZ_TRAIT_THREAD_NUM_JOB_MANAGER_WORKER_THREADS)
}
threadDesc.m_cpuId = AFFINITY_MASK_USERTHREADS;
@@ -32,7 +32,12 @@ namespace AZ
if (!s_instance)
{
s_instance = AZ::Environment::CreateVariable<NameDictionary>(NameDictionaryInstanceName);
// Because the NameDictionary allocates memory using the AZ::Allocator and it is created
// in the executable memory space, it's ownership cannot be transferred to other module memory spaces
// Otherwise this could cause the the NameDictionary to be destroyed in static de-init
// after the AZ::Allocators have been destroyed
// Therefore we supply the isTransferOwnership value of false using CreateVariableEx
s_instance = AZ::Environment::CreateVariableEx<NameDictionary>(NameDictionaryInstanceName, true, false);
}
}
@@ -104,6 +104,8 @@ namespace AZ
->HandlesType<AZStd::variant>();
jsonContext->Serializer<JsonOptionalSerializer>()
->HandlesType<AZStd::optional>();
jsonContext->Serializer<JsonBitsetSerializer>()
->HandlesType<AZStd::bitset>();
MathReflect(jsonContext);
}
@@ -15,6 +15,7 @@ namespace AZ
AZ_CLASS_ALLOCATOR_IMPL(JsonAnySerializer, SystemAllocator, 0);
AZ_CLASS_ALLOCATOR_IMPL(JsonVariantSerializer, SystemAllocator, 0);
AZ_CLASS_ALLOCATOR_IMPL(JsonOptionalSerializer, SystemAllocator, 0);
AZ_CLASS_ALLOCATOR_IMPL(JsonBitsetSerializer, SystemAllocator, 0);
JsonSerializationResult::Result JsonUnsupportedTypesSerializer::Load(void*, const Uuid&, const rapidjson::Value&,
JsonDeserializerContext& context)
@@ -49,4 +50,10 @@ namespace AZ
return "The Json Serialization doesn't support AZStd::optional by design. No JSON format has yet been found that wasn't deemed too "
"complex or overly verbose.";
}
AZStd::string_view JsonBitsetSerializer::GetMessage() const
{
return "The Json Serialization doesn't support AZStd::bitset by design. No JSON format has yet been found that is content creator "
"friendly i.e., easy to comprehend the intent.";
}
} // namespace AZ
@@ -65,4 +65,14 @@ namespace AZ
protected:
AZStd::string_view GetMessage() const override;
};
class JsonBitsetSerializer : public JsonUnsupportedTypesSerializer
{
public:
AZ_RTTI(JsonBitsetSerializer, "{10CE969D-D69E-4B3F-8593-069736F8F705}", JsonUnsupportedTypesSerializer);
AZ_CLASS_ALLOCATOR_DECL;
protected:
AZStd::string_view GetMessage() const override;
};
} // namespace AZ
@@ -30,8 +30,13 @@ namespace AZ
if (Interface<TaskGraphActiveInterface>::Get() == nullptr)
{
#if (AZ_TRAIT_THREAD_NUM_TASK_GRAPH_WORKER_THREADS)
const uint32_t numberOfWorkerThreads = AZ_TRAIT_THREAD_NUM_TASK_GRAPH_WORKER_THREADS;
#else
const uint32_t numberOfWorkerThreads = Threading::CalcNumWorkerThreads(cl_taskGraphThreadsConcurrencyRatio, cl_taskGraphThreadsMinNumber, cl_taskGraphThreadsNumReserved);
#endif // (AZ_TRAIT_THREAD_NUM_TASK_GRAPH_WORKER_THREADS)
Interface<TaskGraphActiveInterface>::Register(this); // small window that another thread can try to use taskgraph between this line and the set instance.
m_taskExecutor = aznew TaskExecutor(Threading::CalcNumWorkerThreads(cl_taskGraphThreadsConcurrencyRatio, cl_taskGraphThreadsMinNumber, cl_taskGraphThreadsNumReserved));
m_taskExecutor = aznew TaskExecutor(numberOfWorkerThreads);
TaskExecutor::SetInstance(m_taskExecutor);
}
}
@@ -75,7 +75,6 @@
#define AZ_TRAIT_HEAPSCHEMA_COMPILE_MALLINFO 0
#define AZ_TRAIT_IS_ABS_PATH_IF_COLON_FOUND_ANYWHERE 0
#define AZ_TRAIT_JSON_CLANG_IGNORE_UNKNOWN_WARNING 0
#define AZ_TRAIT_MAX_JOB_MANAGER_WORKER_THREADS 0
#define AZ_TRAIT_PERF_MEMORYBENCHMARK_IS_AVAILABLE 0
#define AZ_TRAIT_PUMP_SYSTEM_EVENTS_WHILE_LOADING 0
#define AZ_TRAIT_PUMP_SYSTEM_EVENTS_WHILE_LOADING_INTERVAL_MS 0
@@ -75,7 +75,6 @@
#define AZ_TRAIT_HEAPSCHEMA_COMPILE_MALLINFO 1
#define AZ_TRAIT_IS_ABS_PATH_IF_COLON_FOUND_ANYWHERE 0
#define AZ_TRAIT_JSON_CLANG_IGNORE_UNKNOWN_WARNING 0
#define AZ_TRAIT_MAX_JOB_MANAGER_WORKER_THREADS 0
#define AZ_TRAIT_PERF_MEMORYBENCHMARK_IS_AVAILABLE 0
#define AZ_TRAIT_PUMP_SYSTEM_EVENTS_WHILE_LOADING 0
#define AZ_TRAIT_PUMP_SYSTEM_EVENTS_WHILE_LOADING_INTERVAL_MS 0
@@ -75,7 +75,6 @@
#define AZ_TRAIT_HEAPSCHEMA_COMPILE_MALLINFO 1
#define AZ_TRAIT_IS_ABS_PATH_IF_COLON_FOUND_ANYWHERE 0
#define AZ_TRAIT_JSON_CLANG_IGNORE_UNKNOWN_WARNING 0
#define AZ_TRAIT_MAX_JOB_MANAGER_WORKER_THREADS 0
#define AZ_TRAIT_PERF_MEMORYBENCHMARK_IS_AVAILABLE 0
#define AZ_TRAIT_PUMP_SYSTEM_EVENTS_WHILE_LOADING 0
#define AZ_TRAIT_PUMP_SYSTEM_EVENTS_WHILE_LOADING_INTERVAL_MS 0
@@ -75,7 +75,6 @@
#define AZ_TRAIT_HEAPSCHEMA_COMPILE_MALLINFO 1
#define AZ_TRAIT_IS_ABS_PATH_IF_COLON_FOUND_ANYWHERE 0
#define AZ_TRAIT_JSON_CLANG_IGNORE_UNKNOWN_WARNING 1
#define AZ_TRAIT_MAX_JOB_MANAGER_WORKER_THREADS 0
#define AZ_TRAIT_PERF_MEMORYBENCHMARK_IS_AVAILABLE 1
#define AZ_TRAIT_PUMP_SYSTEM_EVENTS_WHILE_LOADING 0
#define AZ_TRAIT_PUMP_SYSTEM_EVENTS_WHILE_LOADING_INTERVAL_MS 0
@@ -76,7 +76,6 @@
#define AZ_TRAIT_HEAPSCHEMA_COMPILE_MALLINFO 1
#define AZ_TRAIT_IS_ABS_PATH_IF_COLON_FOUND_ANYWHERE 0
#define AZ_TRAIT_JSON_CLANG_IGNORE_UNKNOWN_WARNING 0
#define AZ_TRAIT_MAX_JOB_MANAGER_WORKER_THREADS 0
#define AZ_TRAIT_PERF_MEMORYBENCHMARK_IS_AVAILABLE 0
#define AZ_TRAIT_PUMP_SYSTEM_EVENTS_WHILE_LOADING 0
#define AZ_TRAIT_PUMP_SYSTEM_EVENTS_WHILE_LOADING_INTERVAL_MS 0
@@ -1241,6 +1241,10 @@ namespace AZ::IO
m_arrZips.insert(revItZip.base(), desc);
// This lock is for m_arrZips.
// Unlock it now because the modification is complete, and events responding to this signal
// will attempt to lock the same mutex, causing the application to lock up.
lock.unlock();
m_levelOpenEvent.Signal(levelDirs);
}
@@ -927,6 +927,9 @@ namespace AzToolsFramework
/// Notify that the MainWindow has been fully initialized
virtual void NotifyMainWindowInitialized(QMainWindow* /*mainWindow*/) {}
/// Notify that the Editor has been fully initialized
virtual void NotifyEditorInitialized() {}
/// Signal that an asset should be highlighted / selected
virtual void SelectAsset(const QString& /* assetPath */) {}
};
@@ -215,12 +215,17 @@ namespace AzToolsFramework
, public AZ::BehaviorEBusHandler
{
AZ_EBUS_BEHAVIOR_BINDER(EditorEventsBusHandler, "{352F80BB-469A-40B6-B322-FE57AB51E4DA}", AZ::SystemAllocator,
NotifyRegisterViews);
NotifyRegisterViews, NotifyEditorInitialized);
void NotifyRegisterViews() override
{
Call(FN_NotifyRegisterViews);
}
void NotifyEditorInitialized() override
{
Call(FN_NotifyEditorInitialized);
}
};
} // Internal
@@ -445,6 +450,7 @@ namespace AzToolsFramework
->Attribute(AZ::Script::Attributes::Module, "editor")
->Handler<Internal::EditorEventsBusHandler>()
->Event("NotifyRegisterViews", &EditorEvents::NotifyRegisterViews)
->Event("NotifyEditorInitialized", &EditorEvents::NotifyEditorInitialized)
;
behaviorContext->EBus<ViewPaneCallbackBus>("ViewPaneCallbackBus")
@@ -234,11 +234,6 @@ namespace AzToolsFramework
return SourceFileDetails("Icons/AssetBrowser/Lua_16.svg");
}
if (AzFramework::StringFunc::Equal(extension.c_str(), ".mtl"))
{
return SourceFileDetails("Icons/AssetBrowser/Material_16.svg");
}
if (AzFramework::StringFunc::Equal(extension.c_str(), AzToolsFramework::SliceUtilities::GetSliceFileExtension().c_str()))
{
return SourceFileDetails("Icons/AssetBrowser/Slice_16.svg");
@@ -31,7 +31,10 @@ AZ_POP_DISABLE_WARNING
AZ_CVAR(
bool, ed_hideAssetPickerPathColumn, true, nullptr, AZ::ConsoleFunctorFlags::Null,
"Hide AssetPicker path column for a clearer view.");
AZ_CVAR_EXTERNED(bool, ed_useNewAssetBrowserTableView);
AZ_CVAR(
bool, ed_useNewAssetPickerView, false, nullptr, AZ::ConsoleFunctorFlags::Null,
"Uses the new Asset Picker View.");
namespace AzToolsFramework
{
@@ -106,7 +109,7 @@ namespace AzToolsFramework
m_persistentState = AZ::UserSettings::CreateFind<AzToolsFramework::QWidgetSavedState>(AZ::Crc32(("AssetBrowserTreeView_Dialog_" + name).toUtf8().data()), AZ::UserSettings::CT_GLOBAL);
m_ui->m_assetBrowserTableViewWidget->setVisible(false);
if (ed_useNewAssetBrowserTableView)
if (ed_useNewAssetPickerView)
{
m_ui->m_assetBrowserTreeViewWidget->setVisible(false);
m_ui->m_assetBrowserTableViewWidget->setVisible(true);
@@ -597,11 +597,13 @@ namespace AzToolsFramework
pte.SetVisibleEnforcement(true);
}
ScopedUndoBatch undo("Modify Entity Property");
PropertyOutcome result = pte.SetProperty(propertyPath, value);
if (result.IsSuccess())
{
PropertyEditorEntityChangeNotificationBus::Event(componentInstance.GetEntityId(), &PropertyEditorEntityChangeNotifications::OnEntityComponentPropertyChanged, componentInstance.GetComponentId());
}
undo.MarkEntityDirty(componentInstance.GetEntityId());
return result;
}
@@ -64,6 +64,9 @@ namespace AzToolsFramework::Prefab
);
m_backButton->setToolTip("Up one level (-)");
// Currently hide this button until we can correctly disable/enable it based on context.
m_backButton->hide();
}
void PrefabViewportFocusPathHandler::OnPrefabFocusChanged()
@@ -4705,13 +4705,6 @@ namespace AzToolsFramework
{
if (mimeData->hasFormat(AssetBrowser::AssetBrowserEntry::GetMimeType()))
{
// extra special case: MTLs from FBX drags are ignored. are we dragging a FBX file?
bool isDraggingFBXFile = false;
AssetBrowser::AssetBrowserEntry::ForEachEntryInMimeData<AssetBrowser::SourceAssetBrowserEntry>(mimeData, [&](const AssetBrowser::SourceAssetBrowserEntry* source)
{
isDraggingFBXFile = isDraggingFBXFile || AzFramework::StringFunc::Equal(source->GetExtension().c_str(), ".fbx", false);
});
// the usual case - we only allow asset browser drops of assets that have actually been associated with a kind of component.
AssetBrowser::AssetBrowserEntry::ForEachEntryInMimeData<AssetBrowser::ProductAssetBrowserEntry>(mimeData, [&](const AssetBrowser::ProductAssetBrowserEntry* product)
{
@@ -4723,17 +4716,7 @@ namespace AzToolsFramework
if (canCreateComponent && !componentTypeId.IsNull())
{
// we have a component type that handles this asset.
// but we disallow it if its a MTL file from a FBX and the FBX itself is being dragged. Its still allowed
// to drag the actual MTL.
EBusFindAssetTypeByName materialAssetTypeResult("Material");
AZ::AssetTypeInfoBus::BroadcastResult(materialAssetTypeResult, &AZ::AssetTypeInfo::GetAssetType);
AZ::Data::AssetType materialAssetType = materialAssetTypeResult.GetAssetType();
if ((!isDraggingFBXFile) || (product->GetAssetType() != materialAssetType))
{
callbackFunction(product);
}
callbackFunction(product);
}
});
}
@@ -690,7 +690,6 @@ namespace AssetBuilderSDK
static const char* textureExtensions = ".dds";
static const char* staticMeshExtensions = ".cgf";
static const char* skinnedMeshExtensions = ".skin";
static const char* materialExtensions = ".mtl";
// MIPS
static const int c_MaxMipsCount = 11; // 11 is for 8k textures non-compressed. When not compressed it is using one file per mip.
@@ -805,11 +804,6 @@ namespace AssetBuilderSDK
return textureAssetType;
}
if (AzFramework::StringFunc::Find(materialExtensions, extension.c_str()) != AZStd::string::npos)
{
return materialAssetType;
}
if (AzFramework::StringFunc::Find(staticMeshExtensions, extension.c_str()) != AZStd::string::npos)
{
return meshAssetType;
@@ -23,25 +23,11 @@ namespace O3DE::ProjectManager
QString cmakeGenerator = (whichNinjaResult.IsSuccess()) ? "Ninja Multi-Config" : "Unix Makefiles";
bool compileProfileOnBuild = (whichNinjaResult.IsSuccess());
// On Linux the default compiler is gcc. For O3DE, it is clang, so we need to specify the version of clang that is detected
// in order to get the compiler option.
auto compilerOptionResult = ProjectUtils::FindSupportedCompilerForPlatform();
if (!compilerOptionResult.IsSuccess())
{
return AZ::Failure(compilerOptionResult.GetError());
}
auto clangCompilers = compilerOptionResult.GetValue().split('|');
AZ_Assert(clangCompilers.length()==2, "Invalid clang compiler pair specification");
QString clangCompilerOption = clangCompilers[0];
QString clangPPCompilerOption = clangCompilers[1];
QString targetBuildPath = QDir(m_projectInfo.m_path).filePath(ProjectBuildPathPostfix);
QStringList generateProjectArgs = QStringList{ProjectCMakeCommand,
"-B", ProjectBuildPathPostfix,
"-S", ".",
QString("-G%1").arg(cmakeGenerator),
QString("-DCMAKE_C_COMPILER=").append(clangCompilerOption),
QString("-DCMAKE_CXX_COMPILER=").append(clangPPCompilerOption),
QString("-DLY_3RDPARTY_PATH=").append(thirdPartyPath)};
if (!compileProfileOnBuild)
{
@@ -9,3 +9,4 @@
#pragma once
#define AZ_TRAIT_PROJECT_MANAGER_CUSTOM_TITLEBAR false
#define AZ_TRAIT_PROJECT_MANAGER_CREATE_DESKTOP_SHORTCUT false
@@ -17,13 +17,11 @@ namespace O3DE::ProjectManager
namespace ProjectUtils
{
// The list of clang C/C++ compiler command lines to validate on the host Linux system
const QStringList SupportedClangCommands = {"clang-12|clang++-12"};
const QStringList SupportedClangVersions = {"13", "12", "11", "10", "9", "8", "7", "6.0"};
AZ::Outcome<QProcessEnvironment, QString> GetCommandLineProcessEnvironment()
{
QProcessEnvironment currentEnvironment(QProcessEnvironment::systemEnvironment());
currentEnvironment.insert("CC", "clang-12");
currentEnvironment.insert("CXX", "clang++-12");
return AZ::Success(currentEnvironment);
}
@@ -39,16 +37,13 @@ namespace O3DE::ProjectManager
}
// Look for the first compatible version of clang. The list below will contain the known clang compilers that have been tested for O3DE.
for (const QString& supportClangCommand : SupportedClangCommands)
for (const QString& supportClangVersion : SupportedClangVersions)
{
auto clangCompilers = supportClangCommand.split('|');
AZ_Assert(clangCompilers.length()==2, "Invalid clang compiler pair specification");
auto whichClangResult = ProjectUtils::ExecuteCommandResult("which", QStringList{clangCompilers[0]}, QProcessEnvironment::systemEnvironment());
auto whichClangPPResult = ProjectUtils::ExecuteCommandResult("which", QStringList{clangCompilers[1]}, QProcessEnvironment::systemEnvironment());
auto whichClangResult = ProjectUtils::ExecuteCommandResult("which", QStringList{QString("clang-%1").arg(supportClangVersion)}, QProcessEnvironment::systemEnvironment());
auto whichClangPPResult = ProjectUtils::ExecuteCommandResult("which", QStringList{QString("clang++-%1").arg(supportClangVersion)}, QProcessEnvironment::systemEnvironment());
if (whichClangResult.IsSuccess() && whichClangPPResult.IsSuccess())
{
return AZ::Success(supportClangCommand);
return AZ::Success(QString("clang-%1").arg(supportClangVersion));
}
}
return AZ::Failure(QObject::tr("Clang not found. <br><br>"
@@ -101,5 +96,10 @@ namespace O3DE::ProjectManager
{
return AZ::Utils::GetExecutableDirectory();
}
AZ::Outcome<QString, QString> CreateDesktopShortcut([[maybe_unused]] const QString& filename, [[maybe_unused]] const QString& targetPath, [[maybe_unused]] const QStringList& arguments)
{
return AZ::Failure(QObject::tr("Creating desktop shortcuts functionality not implemented for this platform yet."));
}
} // namespace ProjectUtils
} // namespace O3DE::ProjectManager
@@ -9,3 +9,4 @@
#pragma once
#define AZ_TRAIT_PROJECT_MANAGER_CUSTOM_TITLEBAR false
#define AZ_TRAIT_PROJECT_MANAGER_CREATE_DESKTOP_SHORTCUT false
@@ -137,5 +137,10 @@ namespace O3DE::ProjectManager
return editorPath;
}
AZ::Outcome<QString, QString> CreateDesktopShortcut([[maybe_unused]] const QString& filename, [[maybe_unused]] const QString& targetPath, [[maybe_unused]] const QStringList& arguments)
{
return AZ::Failure(QObject::tr("Creating desktop shortcuts functionality not implemented for this platform yet."));
}
} // namespace ProjectUtils
} // namespace O3DE::ProjectManager
@@ -9,3 +9,4 @@
#pragma once
#define AZ_TRAIT_PROJECT_MANAGER_CUSTOM_TITLEBAR true
#define AZ_TRAIT_PROJECT_MANAGER_CREATE_DESKTOP_SHORTCUT true
@@ -13,6 +13,7 @@
#include <QFileInfo>
#include <QProcess>
#include <QProcessEnvironment>
#include <QStandardPaths>
#include <AzCore/Utils/Utils.h>
@@ -146,5 +147,26 @@ namespace O3DE::ProjectManager
{
return AZ::Utils::GetExecutableDirectory();
}
AZ::Outcome<QString, QString> CreateDesktopShortcut(const QString& filename, const QString& targetPath, const QStringList& arguments)
{
const QString cmd{"powershell.exe"};
const QString desktopPath = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
const QString shortcutPath = QString("%1/%2.lnk").arg(desktopPath).arg(filename);
const QString arg = QString("$s=(New-Object -COM WScript.Shell).CreateShortcut('%1');$s.TargetPath='%2';$s.Arguments='%3';$s.Save();")
.arg(shortcutPath)
.arg(targetPath)
.arg(arguments.join(' '));
auto createShortcutResult = ExecuteCommandResult(cmd, QStringList{"-Command", arg}, QProcessEnvironment::systemEnvironment());
if (!createShortcutResult.IsSuccess())
{
return AZ::Failure(QObject::tr("Failed to create desktop shortcut %1 <br><br>"
"Please verify you have permission to create files at the specified location.<br><br> %2")
.arg(shortcutPath)
.arg(createShortcutResult.GetError()));
}
return AZ::Success(QObject::tr("Desktop shortcut created at<br><a href=\"%1\">%2</a>").arg(desktopPath).arg(shortcutPath));
}
} // namespace ProjectUtils
} // namespace O3DE::ProjectManager
@@ -41,5 +41,6 @@
<file>Download.svg</file>
<file>in_progress.gif</file>
<file>gem.svg</file>
<file>checkmark.svg</file>
</qresource>
</RCC>
@@ -563,6 +563,52 @@ QProgressBar::chunk {
margin-top:5px;
}
#gemCatalogUpdateGemButton,
#gemCatalogUninstallGemButton
{
qproperty-flat: true;
min-height:24px;
max-height:24px;
border-radius: 3px;
text-align:center;
font-size:12px;
font-weight:600;
}
#gemCatalogUpdateGemButton {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #888888, stop: 1.0 #555555);
}
#gemCatalogUpdateGemButton:hover {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #999999, stop: 1.0 #666666);
}
#gemCatalogUpdateGemButton:pressed {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #555555, stop: 1.0 #777777);
}
#footer > #gemCatalogUninstallGemButton,
#gemCatalogUninstallGemButton {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #E32C27, stop: 1.0 #951D21);
}
#footer > #gemCatalogUninstallGemButton:hover,
#gemCatalogUninstallGemButton:hover {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #FD3129, stop: 1.0 #AF2221);
}
#footer > #gemCatalogUninstallGemButton:pressed,
#gemCatalogUninstallGemButton:pressed {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #951D1F, stop: 1.0 #C92724);
}
#gemCatalogDialogSubTitle {
font-size:14px;
font-weight:600;
}
/************** Filter Tag widget **************/
#FilterTagWidgetTextLabel {
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="15px" height="14px" viewBox="0 0 15 14" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Icons / Hub / Download Copy 5</title>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Screen-1-Copy-80" transform="translate(-425.000000, -251.000000)">
<g id="Icons-/-Hub-/-Download-Copy-5" transform="translate(424.573941, 250.098705)">
<rect id="Icon-Background" x="0" y="0" width="16" height="16"></rect>
<path d="M8,1.33333333 C11.6818983,1.33333333 14.6666667,4.31810167 14.6666667,8 C14.6666667,11.6818983 11.6818983,14.6666667 8,14.6666667 C4.31810167,14.6666667 1.33333333,11.6818983 1.33333333,8 C1.33333333,4.31810167 4.31810167,1.33333333 8,1.33333333 Z M12.0947571,4 L5.96649831,10.1282588 L3.60947571,7.77123617 L2.66666667,8.71404521 L5.96649831,12.0138769 L13.0375661,4.94280904 L12.0947571,4 Z" id="Combined-Shape" fill="#58BC61"></path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -18,7 +18,6 @@ namespace O3DE::ProjectManager
{
DownloadController::DownloadController(QWidget* parent)
: QObject()
, m_lastProgress(0)
, m_parent(parent)
{
m_worker = new DownloadWorker();
@@ -69,10 +68,9 @@ namespace O3DE::ProjectManager
}
}
void DownloadController::UpdateUIProgress(int progress)
void DownloadController::UpdateUIProgress(int bytesDownloaded, int totalBytes)
{
m_lastProgress = progress;
emit GemDownloadProgress(m_gemNames.front(), progress);
emit GemDownloadProgress(m_gemNames.front(), bytesDownloaded, totalBytes);
}
void DownloadController::HandleResults(const QString& result)
@@ -88,6 +86,7 @@ namespace O3DE::ProjectManager
QString gemName = m_gemNames.front();
m_gemNames.erase(m_gemNames.begin());
emit Done(gemName, succeeded);
emit GemDownloadRemoved(gemName);
if (!m_gemNames.empty())
{
@@ -53,7 +53,7 @@ namespace O3DE::ProjectManager
}
}
public slots:
void UpdateUIProgress(int progress);
void UpdateUIProgress(int bytesDownloaded, int totalBytes);
void HandleResults(const QString& result);
signals:
@@ -61,14 +61,12 @@ namespace O3DE::ProjectManager
void Done(const QString& gemName, bool success = true);
void GemDownloadAdded(const QString& gemName);
void GemDownloadRemoved(const QString& gemName);
void GemDownloadProgress(const QString& gemName, int percentage);
void GemDownloadProgress(const QString& gemName, int bytesDownloaded, int totalBytes);
private:
DownloadWorker* m_worker;
QThread m_workerThread;
QWidget* m_parent;
AZStd::vector<QString> m_gemNames;
int m_lastProgress;
};
} // namespace O3DE::ProjectManager
@@ -20,12 +20,13 @@ namespace O3DE::ProjectManager
void DownloadWorker::StartDownload()
{
auto gemDownloadProgress = [=](int downloadProgress)
auto gemDownloadProgress = [=](int bytesDownloaded, int totalBytes)
{
m_downloadProgress = downloadProgress;
emit UpdateProgress(downloadProgress);
emit UpdateProgress(bytesDownloaded, totalBytes);
};
AZ::Outcome<void, AZStd::string> gemInfoResult = PythonBindingsInterface::Get()->DownloadGem(m_gemName, gemDownloadProgress);
AZ::Outcome<void, AZStd::string> gemInfoResult =
PythonBindingsInterface::Get()->DownloadGem(m_gemName, gemDownloadProgress, /*force*/true);
if (gemInfoResult.IsSuccess())
{
emit Done("");
@@ -31,12 +31,11 @@ namespace O3DE::ProjectManager
void SetGemToDownload(const QString& gemName, bool downloadNow = true);
signals:
void UpdateProgress(int progress);
void UpdateProgress(int bytesDownloaded, int totalBytes);
void Done(QString result = "");
private:
QString m_gemName;
int m_downloadProgress;
};
} // namespace O3DE::ProjectManager
@@ -15,6 +15,8 @@
#include <QProgressBar>
#include <TagWidget.h>
#include <QMenu>
#include <QLocale>
#include <QMovie>
namespace O3DE::ProjectManager
{
@@ -224,7 +226,6 @@ namespace O3DE::ProjectManager
connect(m_downloadController, &DownloadController::GemDownloadAdded, this, &CartOverlayWidget::GemDownloadAdded);
connect(m_downloadController, &DownloadController::GemDownloadRemoved, this, &CartOverlayWidget::GemDownloadRemoved);
connect(m_downloadController, &DownloadController::GemDownloadProgress, this, &CartOverlayWidget::GemDownloadProgress);
connect(m_downloadController, &DownloadController::Done, this, &CartOverlayWidget::GemDownloadComplete);
}
void CartOverlayWidget::GemDownloadAdded(const QString& gemName)
@@ -288,29 +289,41 @@ namespace O3DE::ProjectManager
}
}
void CartOverlayWidget::GemDownloadProgress(const QString& gemName, int percentage)
void CartOverlayWidget::GemDownloadProgress(const QString& gemName, int bytesDownloaded, int totalBytes)
{
QWidget* gemToUpdate = m_downloadingListWidget->findChild<QWidget*>(gemName);
if (gemToUpdate)
{
QLabel* progressLabel = gemToUpdate->findChild<QLabel*>("DownloadProgressLabel");
if (progressLabel)
{
progressLabel->setText(QString("%1%").arg(percentage));
}
QProgressBar* progressBar = gemToUpdate->findChild<QProgressBar*>("DownloadProgressBar");
if (progressBar)
// totalBytes can be 0 if the server does not return a content-length for the object
if (totalBytes != 0)
{
progressBar->setValue(percentage);
int downloadPercentage = static_cast<int>((bytesDownloaded / static_cast<float>(totalBytes)) * 100);
if (progressLabel)
{
progressLabel->setText(QString("%1%").arg(downloadPercentage));
}
if (progressBar)
{
progressBar->setValue(downloadPercentage);
}
}
else
{
if (progressLabel)
{
progressLabel->setText(QLocale::system().formattedDataSize(bytesDownloaded));
}
if (progressBar)
{
progressBar->setRange(0, 0);
}
}
}
}
void CartOverlayWidget::GemDownloadComplete(const QString& gemName, bool /*success*/)
{
GemDownloadRemoved(gemName); // update the list to remove the gem that has finished
}
QVector<Tag> CartOverlayWidget::GetTagsFromModelIndices(const QVector<QModelIndex>& gems) const
{
QVector<Tag> tags;
@@ -389,7 +402,7 @@ namespace O3DE::ProjectManager
{
const QVector<QModelIndex> toBeAdded = m_gemModel->GatherGemsToBeAdded(/*includeDependencies=*/true);
const QVector<QModelIndex> toBeRemoved = m_gemModel->GatherGemsToBeRemoved(/*includeDependencies=*/true);
if (toBeAdded.isEmpty() && toBeRemoved.isEmpty())
if (toBeAdded.isEmpty() && toBeRemoved.isEmpty() && m_downloadController->IsDownloadQueueEmpty())
{
return;
}
@@ -430,6 +443,7 @@ namespace O3DE::ProjectManager
GemCatalogHeaderWidget::GemCatalogHeaderWidget(GemModel* gemModel, GemSortFilterProxyModel* filterProxyModel, DownloadController* downloadController, QWidget* parent)
: QFrame(parent)
, m_downloadController(downloadController)
{
QHBoxLayout* hLayout = new QHBoxLayout();
hLayout->setAlignment(Qt::AlignLeft);
@@ -456,8 +470,25 @@ namespace O3DE::ProjectManager
hLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding));
hLayout->addSpacerItem(new QSpacerItem(75, 0, QSizePolicy::Fixed));
CartButton* cartButton = new CartButton(gemModel, downloadController);
hLayout->addWidget(cartButton);
// spinner
m_downloadSpinnerMovie = new QMovie(":/in_progress.gif");
m_downloadSpinner = new QLabel(this);
m_downloadSpinner->setScaledContents(true);
m_downloadSpinner->setMaximumSize(16, 16);
m_downloadSpinner->setMovie(m_downloadSpinnerMovie);
hLayout->addWidget(m_downloadSpinner);
hLayout->addSpacing(8);
// downloading label
m_downloadLabel = new QLabel(tr("Downloading"));
hLayout->addWidget(m_downloadLabel);
m_downloadSpinner->hide();
m_downloadLabel->hide();
hLayout->addSpacing(16);
m_cartButton = new CartButton(gemModel, downloadController);
hLayout->addWidget(m_cartButton);
hLayout->addSpacing(16);
// Separating line
@@ -469,6 +500,7 @@ namespace O3DE::ProjectManager
hLayout->addSpacing(16);
QMenu* gemMenu = new QMenu(this);
gemMenu->addAction( tr("Refresh"), [this]() { emit RefreshGems(); });
gemMenu->addAction( tr("Show Gem Repos"), [this]() { emit OpenGemsRepo(); });
gemMenu->addSeparator();
gemMenu->addAction( tr("Add Existing Gem"), [this]() { emit AddGem(); });
@@ -479,6 +511,27 @@ namespace O3DE::ProjectManager
gemMenuButton->setIcon(QIcon(":/menu.svg"));
gemMenuButton->setIconSize(QSize(36, 24));
hLayout->addWidget(gemMenuButton);
connect(m_downloadController, &DownloadController::GemDownloadAdded, this, &GemCatalogHeaderWidget::GemDownloadAdded);
connect(m_downloadController, &DownloadController::GemDownloadRemoved, this, &GemCatalogHeaderWidget::GemDownloadRemoved);
}
void GemCatalogHeaderWidget::GemDownloadAdded(const QString& /*gemName*/)
{
m_downloadSpinner->show();
m_downloadLabel->show();
m_downloadSpinnerMovie->start();
m_cartButton->ShowOverlay();
}
void GemCatalogHeaderWidget::GemDownloadRemoved(const QString& /*gemName*/)
{
if (m_downloadController->IsDownloadQueueEmpty())
{
m_downloadSpinner->hide();
m_downloadLabel->hide();
m_downloadSpinnerMovie->stop();
}
}
void GemCatalogHeaderWidget::ReinitForProject()
@@ -24,6 +24,7 @@ QT_FORWARD_DECLARE_CLASS(QVBoxLayout)
QT_FORWARD_DECLARE_CLASS(QHBoxLayout)
QT_FORWARD_DECLARE_CLASS(QHideEvent)
QT_FORWARD_DECLARE_CLASS(QMoveEvent)
QT_FORWARD_DECLARE_CLASS(QMovie)
namespace O3DE::ProjectManager
{
@@ -39,8 +40,7 @@ namespace O3DE::ProjectManager
public slots:
void GemDownloadAdded(const QString& gemName);
void GemDownloadRemoved(const QString& gemName);
void GemDownloadProgress(const QString& gemName, int percentage);
void GemDownloadComplete(const QString& gemName, bool success);
void GemDownloadProgress(const QString& gemName, int bytesDownloaded, int totalBytes);
private:
QVector<Tag> GetTagsFromModelIndices(const QVector<QModelIndex>& gems) const;
@@ -96,12 +96,22 @@ namespace O3DE::ProjectManager
void ReinitForProject();
public slots:
void GemDownloadAdded(const QString& gemName);
void GemDownloadRemoved(const QString& gemName);
signals:
void AddGem();
void OpenGemsRepo();
void RefreshGems();
private:
AzQtComponents::SearchLineEdit* m_filterLineEdit = nullptr;
inline constexpr static int s_height = 60;
DownloadController* m_downloadController = nullptr;
QLabel* m_downloadSpinner = nullptr;
QLabel* m_downloadLabel = nullptr;
QMovie* m_downloadSpinnerMovie = nullptr;
CartButton* m_cartButton = nullptr;
};
} // namespace O3DE::ProjectManager
@@ -12,7 +12,11 @@
#include <GemCatalog/GemSortFilterProxyModel.h>
#include <GemCatalog/GemRequirementDialog.h>
#include <GemCatalog/GemDependenciesDialog.h>
#include <GemCatalog/GemUpdateDialog.h>
#include <GemCatalog/GemUninstallDialog.h>
#include <DownloadController.h>
#include <ProjectUtils.h>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPushButton>
@@ -47,6 +51,7 @@ namespace O3DE::ProjectManager
vLayout->addWidget(m_headerWidget);
connect(m_gemModel, &GemModel::gemStatusChanged, this, &GemCatalogScreen::OnGemStatusChanged);
connect(m_headerWidget, &GemCatalogHeaderWidget::RefreshGems, this, &GemCatalogScreen::Refresh);
connect(m_headerWidget, &GemCatalogHeaderWidget::OpenGemsRepo, this, &GemCatalogScreen::HandleOpenGemRepo);
connect(m_headerWidget, &GemCatalogHeaderWidget::AddGem, this, &GemCatalogScreen::OnAddGemClicked);
connect(m_downloadController, &DownloadController::Done, this, &GemCatalogScreen::OnGemDownloadResult);
@@ -60,6 +65,8 @@ namespace O3DE::ProjectManager
m_gemInspector->setFixedWidth(240);
connect(m_gemInspector, &GemInspector::TagClicked, [=](const Tag& tag) { SelectGem(tag.id); });
connect(m_gemInspector, &GemInspector::UpdateGem, this, &GemCatalogScreen::UpdateGem);
connect(m_gemInspector, &GemInspector::UninstallGem, this, &GemCatalogScreen::UninstallGem);
QWidget* filterWidget = new QWidget(this);
filterWidget->setFixedWidth(240);
@@ -99,7 +106,7 @@ namespace O3DE::ProjectManager
FillModel(projectPath);
m_proxyModel->ResetFilters();
m_proxyModel->ResetFilters(false);
m_proxyModel->sort(/*column=*/0);
if (m_filterWidget)
@@ -118,9 +125,10 @@ namespace O3DE::ProjectManager
// Select the first entry after everything got correctly sized
QTimer::singleShot(200, [=]{
QModelIndex firstModelIndex = m_gemListView->model()->index(0,0);
m_gemListView->selectionModel()->select(firstModelIndex, QItemSelectionModel::ClearAndSelect);
});
QModelIndex firstModelIndex = m_gemModel->index(0, 0);
QModelIndex proxyIndex = m_proxyModel->mapFromSource(firstModelIndex);
m_proxyModel->GetSelectionModel()->setCurrentIndex(proxyIndex, QItemSelectionModel::ClearAndSelect);
});
}
void GemCatalogScreen::OnAddGemClicked()
@@ -202,7 +210,7 @@ namespace O3DE::ProjectManager
const bool gemFound = gemInfoHash.contains(gemName);
if (!gemFound && !m_gemModel->IsAdded(index) && !m_gemModel->IsAddedDependency(index))
{
m_gemModel->removeRow(i);
m_gemModel->RemoveGem(index);
}
else
{
@@ -228,8 +236,11 @@ namespace O3DE::ProjectManager
m_proxyModel->sort(/*column=*/0);
// temporary, until we can refresh filter counts
m_proxyModel->ResetFilters();
m_proxyModel->ResetFilters(false);
m_filterWidget->ResetAllFilters();
// Reselect the same selection to proc UI updates
m_proxyModel->GetSelectionModel()->setCurrentIndex(m_proxyModel->GetSelectionModel()->currentIndex(), QItemSelectionModel::Select);
}
void GemCatalogScreen::OnGemStatusChanged(const QString& gemName, uint32_t numChangedDependencies)
@@ -253,24 +264,24 @@ namespace O3DE::ProjectManager
notification = GemModel::GetDisplayName(modelIndex);
if (numChangedDependencies > 0)
{
notification += " " + tr("and") + " ";
notification += tr(" and ");
}
if (added && GemModel::GetDownloadStatus(modelIndex) == GemInfo::DownloadStatus::NotDownloaded)
{
m_downloadController->AddGemDownload(GemModel::GetName(modelIndex));
GemModel::SetDownloadStatus(*m_proxyModel, m_proxyModel->mapFromSource(modelIndex), GemInfo::DownloadStatus::Downloading);
GemModel::SetDownloadStatus(*m_gemModel, modelIndex, GemInfo::DownloadStatus::Downloading);
}
}
if (numChangedDependencies == 1 )
if (numChangedDependencies == 1)
{
notification += "1 Gem " + tr("dependency");
notification += tr("1 Gem dependency");
}
else if (numChangedDependencies > 1)
{
notification += QString("%1 Gem ").arg(numChangedDependencies) + tr("dependencies");
notification += tr("%1 Gem %2").arg(QString(numChangedDependencies), tr("dependencies"));
}
notification += " " + (added ? tr("activated") : tr("deactivated"));
notification += (added ? tr(" activated") : tr(" deactivated"));
AzQtComponents::ToastConfiguration toastConfiguration(AzQtComponents::ToastType::Custom, notification, "");
toastConfiguration.m_customIconImage = ":/gem.svg";
@@ -290,10 +301,102 @@ namespace O3DE::ProjectManager
}
QModelIndex proxyIndex = m_proxyModel->mapFromSource(modelIndex);
m_proxyModel->GetSelectionModel()->select(proxyIndex, QItemSelectionModel::ClearAndSelect);
m_proxyModel->GetSelectionModel()->setCurrentIndex(proxyIndex, QItemSelectionModel::ClearAndSelect);
m_gemListView->scrollTo(proxyIndex);
}
void GemCatalogScreen::UpdateGem(const QModelIndex& modelIndex)
{
const QString selectedGemName = m_gemModel->GetName(modelIndex);
const QString selectedGemLastUpdate = m_gemModel->GetLastUpdated(modelIndex);
const QString selectedDisplayGemName = m_gemModel->GetDisplayName(modelIndex);
const QString selectedGemRepoUri = m_gemModel->GetRepoUri(modelIndex);
// Refresh gem repo
if (!selectedGemRepoUri.isEmpty())
{
AZ::Outcome<void, AZStd::string> refreshResult = PythonBindingsInterface::Get()->RefreshGemRepo(selectedGemRepoUri);
if (refreshResult.IsSuccess())
{
Refresh();
}
else
{
QMessageBox::critical(
this, tr("Operation failed"),
tr("Failed to refresh gem repository %1<br>Error:<br>%2").arg(selectedGemRepoUri, refreshResult.GetError().c_str()));
}
}
// If repo uri isn't specified warn user that repo might not be refreshed
else
{
int result = QMessageBox::warning(
this, tr("Gem Repository Unspecified"),
tr("The repo for %1 is unspecfied. Repository cannot be automatically refreshed. "
"Please ensure this gem's repo is refreshed before attempting to update.")
.arg(selectedDisplayGemName),
QMessageBox::Cancel, QMessageBox::Ok);
// Allow user to cancel update to manually refresh repo
if (result != QMessageBox::Ok)
{
return;
}
}
// Check if there is an update avaliable now that repo is refreshed
bool updateAvaliable = PythonBindingsInterface::Get()->IsGemUpdateAvaliable(selectedGemName, selectedGemLastUpdate);
GemUpdateDialog* confirmUpdateDialog = new GemUpdateDialog(selectedGemName, updateAvaliable, this);
if (confirmUpdateDialog->exec() == QDialog::Accepted)
{
m_downloadController->AddGemDownload(selectedGemName);
}
}
void GemCatalogScreen::UninstallGem(const QModelIndex& modelIndex)
{
const QString selectedDisplayGemName = m_gemModel->GetDisplayName(modelIndex);
GemUninstallDialog* confirmUninstallDialog = new GemUninstallDialog(selectedDisplayGemName, this);
if (confirmUninstallDialog->exec() == QDialog::Accepted)
{
const QString selectedGemPath = m_gemModel->GetPath(modelIndex);
// Remove gem from gems to be added
GemModel::SetIsAdded(*m_gemModel, modelIndex, false);
// Unregister the gem
auto unregisterResult = PythonBindingsInterface::Get()->UnregisterGem(selectedGemPath);
if (!unregisterResult)
{
QMessageBox::critical(this, tr("Failed to unregister gem"), unregisterResult.GetError().c_str());
}
else
{
const QString selectedGemName = m_gemModel->GetName(modelIndex);
// Remove gem from model
m_gemModel->RemoveGem(modelIndex);
// Delete uninstalled gem directory
if (!ProjectUtils::DeleteProjectFiles(selectedGemPath, /*force*/true))
{
QMessageBox::critical(
this, tr("Failed to remove gem directory"), tr("Could not delete gem directory at:<br>%1").arg(selectedGemPath));
}
// Show undownloaded remote gem again
Refresh();
// Select remote gem
QModelIndex remoteGemIndex = m_gemModel->FindIndexByNameString(selectedGemName);
QModelIndex proxyIndex = m_proxyModel->mapFromSource(remoteGemIndex);
m_proxyModel->GetSelectionModel()->setCurrentIndex(proxyIndex, QItemSelectionModel::ClearAndSelect);
}
}
}
void GemCatalogScreen::hideEvent(QHideEvent* event)
{
ScreenWidget::hideEvent(event);
@@ -472,7 +575,8 @@ namespace O3DE::ProjectManager
if (succeeded)
{
// refresh the information for downloaded gems
const AZ::Outcome<QVector<GemInfo>, AZStd::string>& allGemInfosResult = PythonBindingsInterface::Get()->GetAllGemInfos(m_projectPath);
const AZ::Outcome<QVector<GemInfo>, AZStd::string>& allGemInfosResult =
PythonBindingsInterface::Get()->GetAllGemInfos(m_projectPath);
if (allGemInfosResult.IsSuccess())
{
// we should find the gem name now in all gem infos
@@ -480,19 +584,45 @@ namespace O3DE::ProjectManager
{
if (gemInfo.m_name == gemName)
{
QModelIndex index = m_gemModel->FindIndexByNameString(gemName);
if (index.isValid())
QModelIndex oldIndex = m_gemModel->FindIndexByNameString(gemName);
if (oldIndex.isValid())
{
m_gemModel->setData(index, GemInfo::Downloaded, GemModel::RoleDownloadStatus);
m_gemModel->setData(index, gemInfo.m_path, GemModel::RolePath);
m_gemModel->setData(index, gemInfo.m_path, GemModel::RoleDirectoryLink);
// Check if old gem is selected
bool oldGemSelected = false;
if (m_gemModel->GetSelectionModel()->currentIndex() == oldIndex)
{
oldGemSelected = true;
}
// Remove old remote gem
m_gemModel->RemoveGem(oldIndex);
// Add new downloaded version of gem
QModelIndex newIndex = m_gemModel->AddGem(gemInfo);
GemModel::SetDownloadStatus(*m_gemModel, newIndex, GemInfo::DownloadSuccessful);
GemModel::SetIsAdded(*m_gemModel, newIndex, true);
// Select new version of gem if it was previously selected
if (oldGemSelected)
{
QModelIndex proxyIndex = m_proxyModel->mapFromSource(newIndex);
m_proxyModel->GetSelectionModel()->setCurrentIndex(proxyIndex, QItemSelectionModel::ClearAndSelect);
}
}
return;
break;
}
}
}
}
else
{
QModelIndex index = m_gemModel->FindIndexByNameString(gemName);
if (index.isValid())
{
GemModel::SetDownloadStatus(*m_gemModel, index, GemInfo::DownloadFailed);
}
}
}
ProjectManagerScreen GemCatalogScreen::GetScreenEnum()
@@ -51,6 +51,8 @@ namespace O3DE::ProjectManager
void SelectGem(const QString& gemName);
void OnGemDownloadResult(const QString& gemName, bool succeeded = true);
void Refresh();
void UpdateGem(const QModelIndex& modelIndex);
void UninstallGem(const QModelIndex& modelIndex);
protected:
void hideEvent(QHideEvent* event) override;
@@ -77,6 +79,6 @@ namespace O3DE::ProjectManager
DownloadController* m_downloadController = nullptr;
bool m_notificationsEnabled = true;
QSet<QString> m_gemsToRegisterWithProject;
QString m_projectPath = nullptr;
QString m_projectPath;
};
} // namespace O3DE::ProjectManager
@@ -57,7 +57,9 @@ namespace O3DE::ProjectManager
UnknownDownloadStatus = -1,
NotDownloaded,
Downloading,
Downloaded,
DownloadSuccessful,
DownloadFailed,
Downloaded
};
static QString GetDownloadStatusString(DownloadStatus status);
@@ -85,6 +87,7 @@ namespace O3DE::ProjectManager
QString m_licenseLink;
QString m_directoryLink;
QString m_documentationLink;
QString m_repoUri;
QString m_version = "Unknown Version";
QString m_lastUpdatedDate = "Unknown Date";
int m_binarySizeInKB = 0;
@@ -14,6 +14,7 @@
#include <QSpacerItem>
#include <QVBoxLayout>
#include <QIcon>
#include <QPushButton>
namespace O3DE::ProjectManager
{
@@ -70,6 +71,8 @@ namespace O3DE::ProjectManager
void GemInspector::Update(const QModelIndex& modelIndex)
{
m_curModelIndex = modelIndex;
if (!modelIndex.isValid())
{
m_mainWidget->hide();
@@ -123,6 +126,20 @@ namespace O3DE::ProjectManager
const int binarySize = m_model->GetBinarySizeInKB(modelIndex);
m_binarySizeLabel->setText(tr("Binary Size: %1").arg(binarySize ? tr("%1 KB").arg(binarySize) : tr("Unknown")));
// Update and Uninstall buttons
if (m_model->GetGemOrigin(modelIndex) == GemInfo::Remote &&
(m_model->GetDownloadStatus(modelIndex) == GemInfo::Downloaded ||
m_model->GetDownloadStatus(modelIndex) == GemInfo::DownloadSuccessful))
{
m_updateGemButton->show();
m_uninstallGemButton->show();
}
else
{
m_updateGemButton->hide();
m_uninstallGemButton->hide();
}
m_mainWidget->adjustSize();
m_mainWidget->show();
}
@@ -223,7 +240,7 @@ namespace O3DE::ProjectManager
// Depending gems
m_dependingGems = new GemsSubWidget();
connect(m_dependingGems, &GemsSubWidget::TagClicked, this, [=](const Tag& tag){ emit TagClicked(tag); });
connect(m_dependingGems, &GemsSubWidget::TagClicked, this, [this](const Tag& tag){ emit TagClicked(tag); });
m_mainLayout->addWidget(m_dependingGems);
m_mainLayout->addSpacing(20);
@@ -234,5 +251,20 @@ namespace O3DE::ProjectManager
m_versionLabel = CreateStyledLabel(m_mainLayout, s_baseFontSize, s_textColor);
m_lastUpdatedLabel = CreateStyledLabel(m_mainLayout, s_baseFontSize, s_textColor);
m_binarySizeLabel = CreateStyledLabel(m_mainLayout, s_baseFontSize, s_textColor);
m_mainLayout->addSpacing(20);
// Update and Uninstall buttons
m_updateGemButton = new QPushButton(tr("Update Gem"));
m_updateGemButton->setObjectName("gemCatalogUpdateGemButton");
m_mainLayout->addWidget(m_updateGemButton);
connect(m_updateGemButton, &QPushButton::clicked, this , [this]{ emit UpdateGem(m_curModelIndex); });
m_mainLayout->addSpacing(10);
m_uninstallGemButton = new QPushButton(tr("Uninstall Gem"));
m_uninstallGemButton->setObjectName("gemCatalogUninstallGemButton");
m_mainLayout->addWidget(m_uninstallGemButton);
connect(m_uninstallGemButton, &QPushButton::clicked, this , [this]{ emit UninstallGem(m_curModelIndex); });
}
} // namespace O3DE::ProjectManager
@@ -16,11 +16,12 @@
#include <QItemSelection>
#include <QScrollArea>
#include <QSpacerItem>
#endif
QT_FORWARD_DECLARE_CLASS(QVBoxLayout)
QT_FORWARD_DECLARE_CLASS(QLabel)
QT_FORWARD_DECLARE_CLASS(QSpacerItem)
QT_FORWARD_DECLARE_CLASS(QPushButton)
namespace O3DE::ProjectManager
{
@@ -45,6 +46,8 @@ namespace O3DE::ProjectManager
signals:
void TagClicked(const Tag& tag);
void UpdateGem(const QModelIndex& modelIndex);
void UninstallGem(const QModelIndex& modelIndex);
private slots:
void OnSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected);
@@ -55,6 +58,7 @@ namespace O3DE::ProjectManager
GemModel* m_model = nullptr;
QWidget* m_mainWidget = nullptr;
QVBoxLayout* m_mainLayout = nullptr;
QModelIndex m_curModelIndex;
// General info (top) section
QLabel* m_nameLabel = nullptr;
@@ -77,5 +81,8 @@ namespace O3DE::ProjectManager
QLabel* m_versionLabel = nullptr;
QLabel* m_lastUpdatedLabel = nullptr;
QLabel* m_binarySizeLabel = nullptr;
QPushButton* m_updateGemButton = nullptr;
QPushButton* m_uninstallGemButton = nullptr;
};
} // namespace O3DE::ProjectManager
@@ -37,6 +37,8 @@ namespace O3DE::ProjectManager
SetStatusIcon(m_notDownloadedPixmap, ":/Download.svg");
SetStatusIcon(m_unknownStatusPixmap, ":/X.svg");
SetStatusIcon(m_downloadSuccessfulPixmap, ":/checkmark.svg");
SetStatusIcon(m_downloadFailedPixmap, ":/Warning.svg");
m_downloadingMovie = new QMovie(":/in_progress.gif");
}
@@ -480,6 +482,14 @@ namespace O3DE::ProjectManager
currentFrame = currentFrame.scaled(s_statusIconSize, s_statusIconSize);
statusPixmap = &currentFrame;
}
else if (downloadStatus == GemInfo::DownloadStatus::DownloadSuccessful)
{
statusPixmap = &m_downloadSuccessfulPixmap;
}
else if (downloadStatus == GemInfo::DownloadStatus::DownloadFailed)
{
statusPixmap = &m_downloadFailedPixmap;
}
else if (downloadStatus == GemInfo::DownloadStatus::NotDownloaded)
{
statusPixmap = &m_notDownloadedPixmap;
@@ -97,6 +97,8 @@ namespace O3DE::ProjectManager
QPixmap m_unknownStatusPixmap;
QPixmap m_notDownloadedPixmap;
QPixmap m_downloadSuccessfulPixmap;
QPixmap m_downloadFailedPixmap;
QMovie* m_downloadingMovie = nullptr;
};
} // namespace O3DE::ProjectManager
@@ -26,14 +26,14 @@ namespace O3DE::ProjectManager
return m_selectionModel;
}
void GemModel::AddGem(const GemInfo& gemInfo)
QModelIndex GemModel::AddGem(const GemInfo& gemInfo)
{
if (FindIndexByNameString(gemInfo.m_name).isValid())
{
// do not add gems with duplicate names
// this can happen by mistake or when a gem repo has a gem with the same name as a local gem
AZ_TracePrintf("GemModel", "Ignoring duplicate gem: %s", gemInfo.m_name.toUtf8().constData());
return;
return QModelIndex();
}
QStandardItem* item = new QStandardItem();
@@ -61,11 +61,28 @@ namespace O3DE::ProjectManager
item->setData(gemInfo.m_downloadStatus, RoleDownloadStatus);
item->setData(gemInfo.m_licenseText, RoleLicenseText);
item->setData(gemInfo.m_licenseLink, RoleLicenseLink);
item->setData(gemInfo.m_repoUri, RoleRepoUri);
appendRow(item);
const QModelIndex modelIndex = index(rowCount()-1, 0);
m_nameToIndexMap[gemInfo.m_name] = modelIndex;
return modelIndex;
}
void GemModel::RemoveGem(const QModelIndex& modelIndex)
{
removeRow(modelIndex.row());
}
void GemModel::RemoveGem(const QString& gemName)
{
auto nameFind = m_nameToIndexMap.find(gemName);
if (nameFind != m_nameToIndexMap.end())
{
removeRow(nameFind->row());
}
}
void GemModel::Clear()
@@ -255,6 +272,11 @@ namespace O3DE::ProjectManager
return modelIndex.data(RoleLicenseLink).toString();
}
QString GemModel::GetRepoUri(const QModelIndex& modelIndex)
{
return modelIndex.data(RoleRepoUri).toString();
}
GemModel* GemModel::GetSourceModel(QAbstractItemModel* model)
{
GemSortFilterProxyModel* proxyModel = qobject_cast<GemSortFilterProxyModel*>(model);
@@ -369,11 +391,30 @@ namespace O3DE::ProjectManager
void GemModel::OnRowsAboutToBeRemoved(const QModelIndex& parent, int first, int last)
{
bool selectedRowRemoved = false;
for (int i = first; i <= last; ++i)
{
QModelIndex modelIndex = index(i, 0, parent);
const QString& gemName = GetName(modelIndex);
m_nameToIndexMap.remove(gemName);
if (GetSelectionModel()->isRowSelected(i))
{
selectedRowRemoved = true;
}
}
// Select a valid row if currently selected row was removed
if (selectedRowRemoved)
{
for (const QModelIndex& index : m_nameToIndexMap)
{
if (index.isValid())
{
GetSelectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect);
break;
}
}
}
}
@@ -51,10 +51,13 @@ namespace O3DE::ProjectManager
RoleRequirement,
RoleDownloadStatus,
RoleLicenseText,
RoleLicenseLink
RoleLicenseLink,
RoleRepoUri
};
void AddGem(const GemInfo& gemInfo);
QModelIndex AddGem(const GemInfo& gemInfo);
void RemoveGem(const QModelIndex& modelIndex);
void RemoveGem(const QString& gemName);
void Clear();
void UpdateGemDependencies();
@@ -80,6 +83,7 @@ namespace O3DE::ProjectManager
static QString GetRequirement(const QModelIndex& modelIndex);
static QString GetLicenseText(const QModelIndex& modelIndex);
static QString GetLicenseLink(const QModelIndex& modelIndex);
static QString GetRepoUri(const QModelIndex& modelIndex);
static GemModel* GetSourceModel(QAbstractItemModel* model);
static const GemModel* GetSourceModel(const QAbstractItemModel* model);
@@ -204,9 +204,12 @@ namespace O3DE::ProjectManager
emit OnInvalidated();
}
void GemSortFilterProxyModel::ResetFilters()
void GemSortFilterProxyModel::ResetFilters(bool clearSearchString)
{
m_searchString.clear();
if (clearSearchString)
{
m_searchString.clear();
}
m_gemSelectedFilter = GemSelected::NoFilter;
m_gemActiveFilter = GemActive::NoFilter;
m_gemOriginFilter = {};
@@ -70,7 +70,7 @@ namespace O3DE::ProjectManager
void SetFeatures(const QSet<QString>& features) { m_featureFilter = features; InvalidateFilter(); }
void InvalidateFilter();
void ResetFilters();
void ResetFilters(bool clearSearchString = true);
signals:
void OnInvalidated();
@@ -0,0 +1,60 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <GemCatalog/GemUninstallDialog.h>
#include <QVBoxLayout>
#include <QLabel>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QVariant>
namespace O3DE::ProjectManager
{
GemUninstallDialog::GemUninstallDialog(const QString& gemName, QWidget* parent)
: QDialog(parent)
{
setWindowTitle(tr("Uninstall Remote Gem"));
setObjectName("GemUninstallDialog");
setAttribute(Qt::WA_DeleteOnClose);
setModal(true);
QVBoxLayout* layout = new QVBoxLayout();
layout->setMargin(30);
layout->setAlignment(Qt::AlignTop);
setLayout(layout);
// Body
QLabel* subTitleLabel = new QLabel(tr("Are you sure you want to uninstall %1?").arg(gemName));
subTitleLabel->setObjectName("gemCatalogDialogSubTitle");
layout->addWidget(subTitleLabel);
layout->addSpacing(10);
QLabel* bodyLabel = new QLabel(tr("The Gem and its related files will be uninstalled. This does not affect the Gem's repository. "
"You can reinstall this Gem from the Catalog, but its contents may be subject to change."));
bodyLabel->setWordWrap(true);
bodyLabel->setFixedSize(QSize(440, 80));
layout->addWidget(bodyLabel);
layout->addSpacing(40);
// Buttons
QDialogButtonBox* dialogButtons = new QDialogButtonBox();
dialogButtons->setObjectName("footer");
layout->addWidget(dialogButtons);
QPushButton* cancelButton = dialogButtons->addButton(tr("Cancel"), QDialogButtonBox::RejectRole);
cancelButton->setProperty("secondary", true);
QPushButton* uninstallButton = dialogButtons->addButton(tr("Uninstall Gem"), QDialogButtonBox::ApplyRole);
uninstallButton->setObjectName("gemCatalogUninstallGemButton");
connect(cancelButton, &QPushButton::clicked, this, &QDialog::reject);
connect(uninstallButton, &QPushButton::clicked, this, &QDialog::accept);
}
} // namespace O3DE::ProjectManager
@@ -0,0 +1,25 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <QDialog>
#endif
namespace O3DE::ProjectManager
{
class GemUninstallDialog
: public QDialog
{
Q_OBJECT // AUTOMOC
public:
explicit GemUninstallDialog(const QString& gemName, QWidget *parent = nullptr);
~GemUninstallDialog() = default;
};
} // namespace O3DE::ProjectManager
@@ -0,0 +1,64 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <GemCatalog/GemUpdateDialog.h>
#include <QDialogButtonBox>
#include <QLabel>
#include <QPushButton>
#include <QVBoxLayout>
#include <QVariant>
namespace O3DE::ProjectManager
{
GemUpdateDialog::GemUpdateDialog(const QString& gemName, bool updateAvaliable, QWidget* parent)
: QDialog(parent)
{
setWindowTitle(tr("Update Remote Gem"));
setObjectName("GemUpdateDialog");
setAttribute(Qt::WA_DeleteOnClose);
setModal(true);
QVBoxLayout* layout = new QVBoxLayout();
layout->setMargin(30);
layout->setAlignment(Qt::AlignTop);
setLayout(layout);
// Body
QLabel* subTitleLabel = new QLabel(tr("%1 to the latest version of %2?").arg(
updateAvaliable ? tr("Update") : tr("Force update"), gemName));
subTitleLabel->setObjectName("gemCatalogDialogSubTitle");
layout->addWidget(subTitleLabel);
layout->addSpacing(10);
QLabel* bodyLabel = new QLabel(tr("%1The latest version of this Gem may not be compatible with your engine. "
"Updating this Gem will remove any local changes made to this Gem, "
"and may remove old features that are in use.").arg(
updateAvaliable ? "" : tr("No update detected for Gem. "
"This will force a re-download of the gem. ")));
bodyLabel->setWordWrap(true);
bodyLabel->setFixedSize(QSize(440, 80));
layout->addWidget(bodyLabel);
layout->addSpacing(40);
// Buttons
QDialogButtonBox* dialogButtons = new QDialogButtonBox();
dialogButtons->setObjectName("footer");
layout->addWidget(dialogButtons);
QPushButton* cancelButton = dialogButtons->addButton(tr("Cancel"), QDialogButtonBox::RejectRole);
cancelButton->setProperty("secondary", true);
QPushButton* updateButton =
dialogButtons->addButton(tr("%1Update Gem").arg(updateAvaliable ? "" : tr("Force ")), QDialogButtonBox::ApplyRole);
connect(cancelButton, &QPushButton::clicked, this, &QDialog::reject);
connect(updateButton, &QPushButton::clicked, this, &QDialog::accept);
}
} // namespace O3DE::ProjectManager
@@ -0,0 +1,25 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <QDialog>
#endif
namespace O3DE::ProjectManager
{
class GemUpdateDialog
: public QDialog
{
Q_OBJECT // AUTOMOC
public :
explicit GemUpdateDialog(const QString& gemName, bool updateAvaliable = true, QWidget* parent = nullptr);
~GemUpdateDialog() = default;
};
} // namespace O3DE::ProjectManager
@@ -75,7 +75,7 @@ namespace O3DE::ProjectManager
// Select the first entry after everything got correctly sized
QTimer::singleShot(200, [=]{
QModelIndex firstModelIndex = m_gemRepoListView->model()->index(0,0);
m_gemRepoListView->selectionModel()->select(firstModelIndex, QItemSelectionModel::ClearAndSelect);
m_gemRepoListView->selectionModel()->setCurrentIndex(firstModelIndex, QItemSelectionModel::ClearAndSelect);
});
}
@@ -8,7 +8,11 @@
#include <ProjectButtonWidget.h>
#include <ProjectManagerDefs.h>
#include <ProjectUtils.h>
#include <ProjectManager_Traits_Platform.h>
#include <AzQtComponents/Utilities/DesktopUtilities.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/IO/Path/Path.h>
#include <QVBoxLayout>
#include <QHBoxLayout>
@@ -23,6 +27,7 @@
#include <QDir>
#include <QFileInfo>
#include <QDesktopServices>
#include <QMessageBox>
namespace O3DE::ProjectManager
{
@@ -205,6 +210,29 @@ namespace O3DE::ProjectManager
{
AzQtComponents::ShowFileOnDesktop(m_projectInfo.m_path);
});
#if AZ_TRAIT_PROJECT_MANAGER_CREATE_DESKTOP_SHORTCUT
menu->addAction(tr("Create Editor desktop shortcut..."), this, [this]()
{
AZ::IO::FixedMaxPath executableDirectory = ProjectUtils::GetEditorDirectory();
AZStd::string executableFilename = "Editor";
AZ::IO::FixedMaxPath editorExecutablePath = executableDirectory / (executableFilename + AZ_TRAIT_OS_EXECUTABLE_EXTENSION);
const QString shortcutName = QString("%1 Editor").arg(m_projectInfo.m_displayName);
const QString arg = QString("--regset=\"/Amazon/AzCore/Bootstrap/project_path=%1\"").arg(m_projectInfo.m_path);
auto result = ProjectUtils::CreateDesktopShortcut(shortcutName, editorExecutablePath.c_str(), { arg });
if(result.IsSuccess())
{
QMessageBox::information(this, tr("Desktop Shortcut Created"), result.GetValue());
}
else
{
QMessageBox::critical(this, tr("Failed to create shortcut"), result.GetError());
}
});
#endif // AZ_TRAIT_PROJECT_MANAGER_CREATE_DESKTOP_SHORTCUT
menu->addSeparator();
menu->addAction(tr("Duplicate"), this, [this]() { emit CopyProject(m_projectInfo); });
menu->addSeparator();
@@ -628,11 +628,11 @@ namespace O3DE::ProjectManager
return AZ::Failure(QObject::tr("Process for command '%1' timed out at %2 seconds").arg(cmd).arg(commandTimeoutSeconds));
}
int resultCode = execProcess.exitCode();
QString resultOutput = execProcess.readAllStandardOutput();
if (resultCode != 0)
{
return AZ::Failure(QObject::tr("Process for command '%1' failed (result code %2").arg(cmd).arg(resultCode));
return AZ::Failure(QObject::tr("Process for command '%1' failed (result code %2) %3").arg(cmd).arg(resultCode).arg(resultOutput));
}
QString resultOutput = execProcess.readAllStandardOutput();
return AZ::Success(resultOutput);
}
@@ -68,6 +68,15 @@ namespace O3DE::ProjectManager
AZ::Outcome<QString, QString> GetProjectBuildPath(const QString& projectPath);
AZ::Outcome<void, QString> OpenCMakeGUI(const QString& projectPath);
AZ::Outcome<QString, QString> RunGetPythonScript(const QString& enginePath);
/**
* Create a desktop shortcut.
* @param filename the name of the desktop shorcut file
* @param target the path to the target to run
* @param arguments the argument list to provide to the target
* @return AZ::Outcome with the command result on success
*/
AZ::Outcome<QString, QString> CreateDesktopShortcut(const QString& filename, const QString& targetPath, const QStringList& arguments);
AZ::IO::FixedMaxPath GetEditorDirectory();
@@ -515,7 +515,11 @@ namespace O3DE::ProjectManager
auto pyProjectPath = QString_To_Py_Path(projectPath);
for (auto path : m_manifest.attr("get_all_gems")(pyProjectPath))
{
gems.push_back(GemInfoFromPath(path, pyProjectPath));
GemInfo gemInfo = GemInfoFromPath(path, pyProjectPath);
// Mark as downloaded because this gem was registered with an existing directory
gemInfo.m_downloadStatus = GemInfo::DownloadStatus::Downloaded;
gems.push_back(AZStd::move(gemInfo));
}
});
if (!result.IsSuccess())
@@ -560,7 +564,7 @@ namespace O3DE::ProjectManager
return AZ::Success(AZStd::move(gemNames));
}
AZ::Outcome<void, AZStd::string> PythonBindings::RegisterGem(const QString& gemPath, const QString& projectPath)
AZ::Outcome<void, AZStd::string> PythonBindings::GemRegistration(const QString& gemPath, const QString& projectPath, bool remove)
{
bool registrationResult = false;
auto result = ExecuteWithLockErrorHandling(
@@ -582,7 +586,8 @@ namespace O3DE::ProjectManager
pybind11::none(), // default_restricted_folder
pybind11::none(), // default_third_party_folder
pybind11::none(), // external_subdir_engine_path
externalProjectPath // external_subdir_project_path
externalProjectPath, // external_subdir_project_path
remove // remove
);
// Returns an exit code so boolify it then invert result
@@ -595,12 +600,23 @@ namespace O3DE::ProjectManager
}
else if (!registrationResult)
{
return AZ::Failure<AZStd::string>(AZStd::string::format("Failed to register gem path %s", gemPath.toUtf8().constData()));
return AZ::Failure<AZStd::string>(AZStd::string::format(
"Failed to %s gem path %s", remove ? "unregister" : "register", gemPath.toUtf8().constData()));
}
return AZ::Success();
}
AZ::Outcome<void, AZStd::string> PythonBindings::RegisterGem(const QString& gemPath, const QString& projectPath)
{
return GemRegistration(gemPath, projectPath);
}
AZ::Outcome<void, AZStd::string> PythonBindings::UnregisterGem(const QString& gemPath, const QString& projectPath)
{
return GemRegistration(gemPath, projectPath, /*remove*/true);
}
bool PythonBindings::AddProject(const QString& path)
{
bool registrationResult = false;
@@ -715,6 +731,7 @@ namespace O3DE::ProjectManager
gemInfo.m_documentationLink = Py_To_String_Optional(data, "documentation_url", "");
gemInfo.m_licenseText = Py_To_String_Optional(data, "license", "Unspecified License");
gemInfo.m_licenseLink = Py_To_String_Optional(data, "license_url", "");
gemInfo.m_repoUri = Py_To_String_Optional(data, "repo_uri", "");
if (gemInfo.m_creator.contains("Open 3D Engine"))
{
@@ -728,6 +745,11 @@ namespace O3DE::ProjectManager
{
gemInfo.m_gemOrigin = GemInfo::GemOrigin::Remote;
}
// If no origin was provided this cannot be remote and would be specified if O3DE so it should be local
else
{
gemInfo.m_gemOrigin = GemInfo::GemOrigin::Local;
}
// As long Base Open3DEngine gems are installed before first startup non-remote gems will be downloaded
if (gemInfo.m_gemOrigin != GemInfo::GemOrigin::Remote)
@@ -1166,49 +1188,6 @@ namespace O3DE::ProjectManager
return AZ::Success(AZStd::move(gemRepos));
}
AZ::Outcome<void, AZStd::string> PythonBindings::DownloadGem(const QString& gemName, std::function<void(int)> gemProgressCallback)
{
// This process is currently limited to download a single gem at a time.
bool downloadSucceeded = false;
m_requestCancelDownload = false;
auto result = ExecuteWithLockErrorHandling(
[&]
{
auto downloadResult = m_download.attr("download_gem")(
QString_To_Py_String(gemName), // gem name
pybind11::none(), // destination path
false, // skip auto register
false, // force
pybind11::cpp_function(
[this, gemProgressCallback](int progress)
{
gemProgressCallback(progress);
return m_requestCancelDownload;
}) // Callback for download progress and cancelling
);
downloadSucceeded = (downloadResult.cast<int>() == 0);
});
if (!result.IsSuccess())
{
return result;
}
else if (!downloadSucceeded)
{
return AZ::Failure<AZStd::string>("Failed to download gem.");
}
return AZ::Success();
}
void PythonBindings::CancelDownload()
{
m_requestCancelDownload = true;
}
AZ::Outcome<QVector<GemInfo>, AZStd::string> PythonBindings::GetAllGemRepoGemsInfos()
{
QVector<GemInfo> gemInfos;
@@ -1235,4 +1214,64 @@ namespace O3DE::ProjectManager
return AZ::Success(AZStd::move(gemInfos));
}
AZ::Outcome<void, AZStd::string> PythonBindings::DownloadGem(
const QString& gemName, std::function<void(int, int)> gemProgressCallback, bool force)
{
// This process is currently limited to download a single gem at a time.
bool downloadSucceeded = false;
m_requestCancelDownload = false;
auto result = ExecuteWithLockErrorHandling(
[&]
{
auto downloadResult = m_download.attr("download_gem")(
QString_To_Py_String(gemName), // gem name
pybind11::none(), // destination path
false, // skip auto register
force, // force overwrite
pybind11::cpp_function(
[this, gemProgressCallback](int bytesDownloaded, int totalBytes)
{
gemProgressCallback(bytesDownloaded, totalBytes);
return m_requestCancelDownload;
}) // Callback for download progress and cancelling
);
downloadSucceeded = (downloadResult.cast<int>() == 0);
});
if (!result.IsSuccess())
{
return result;
}
else if (!downloadSucceeded)
{
return AZ::Failure<AZStd::string>("Failed to download gem.");
}
return AZ::Success();
}
void PythonBindings::CancelDownload()
{
m_requestCancelDownload = true;
}
bool PythonBindings::IsGemUpdateAvaliable(const QString& gemName, const QString& lastUpdated)
{
bool updateAvaliableResult = false;
bool result = ExecuteWithLock(
[&]
{
auto pyGemName = QString_To_Py_String(gemName);
auto pyLastUpdated = QString_To_Py_String(lastUpdated);
auto pythonUpdateAvaliableResult = m_download.attr("is_o3de_gem_update_available")(pyGemName, pyLastUpdated);
updateAvaliableResult = pythonUpdateAvaliableResult.cast<bool>();
});
return result && updateAvaliableResult;
}
}
@@ -43,6 +43,7 @@ namespace O3DE::ProjectManager
AZ::Outcome<QVector<GemInfo>, AZStd::string> GetAllGemInfos(const QString& projectPath) override;
AZ::Outcome<QVector<AZStd::string>, AZStd::string> GetEnabledGemNames(const QString& projectPath) override;
AZ::Outcome<void, AZStd::string> RegisterGem(const QString& gemPath, const QString& projectPath = {}) override;
AZ::Outcome<void, AZStd::string> UnregisterGem(const QString& gemPath, const QString& projectPath = {}) override;
// Project
AZ::Outcome<ProjectInfo> CreateProject(const QString& projectTemplatePath, const ProjectInfo& projectInfo) override;
@@ -64,9 +65,10 @@ namespace O3DE::ProjectManager
bool AddGemRepo(const QString& repoUri) override;
bool RemoveGemRepo(const QString& repoUri) override;
AZ::Outcome<QVector<GemRepoInfo>, AZStd::string> GetAllGemRepoInfos() override;
AZ::Outcome<void, AZStd::string> DownloadGem(const QString& gemName, std::function<void(int)> gemProgressCallback) override;
void CancelDownload() override;
AZ::Outcome<QVector<GemInfo>, AZStd::string> GetAllGemRepoGemsInfos() override;
AZ::Outcome<void, AZStd::string> DownloadGem(const QString& gemName, std::function<void(int, int)> gemProgressCallback, bool force = false) override;
void CancelDownload() override;
bool IsGemUpdateAvaliable(const QString& gemName, const QString& lastUpdated) override;
private:
AZ_DISABLE_COPY_MOVE(PythonBindings);
@@ -77,6 +79,7 @@ namespace O3DE::ProjectManager
GemRepoInfo GetGemRepoInfo(pybind11::handle repoUri);
ProjectInfo ProjectInfoFromPath(pybind11::handle path);
ProjectTemplateInfo ProjectTemplateInfoFromPath(pybind11::handle path, pybind11::handle pyProjectPath);
AZ::Outcome<void, AZStd::string> GemRegistration(const QString& gemPath, const QString& projectPath, bool remove = false);
bool RegisterThisEngine();
bool StopPython();
@@ -94,11 +94,19 @@ namespace O3DE::ProjectManager
/**
* Registers the gem to the specified project, or to the o3de_manifest.json if no project path is given
* @param gemPath the path to the gem
* @param projectPath the path to the project. If empty, will register the external path in o3de_manifest.json
* @param projectPath the path to the project. If empty, will register the external path in o3de_manifest.json
* @return An outcome with the success flag as well as an error message in case of a failure.
*/
virtual AZ::Outcome<void, AZStd::string> RegisterGem(const QString& gemPath, const QString& projectPath = {}) = 0;
/**
* Unregisters the gem from the specified project, or from the o3de_manifest.json if no project path is given
* @param gemPath the path to the gem
* @param projectPath the path to the project. If empty, will unregister the external path in o3de_manifest.json
* @return An outcome with the success flag as well as an error message in case of a failure.
*/
virtual AZ::Outcome<void, AZStd::string> UnregisterGem(const QString& gemPath, const QString& projectPath = {}) = 0;
// Projects
@@ -209,24 +217,34 @@ namespace O3DE::ProjectManager
*/
virtual AZ::Outcome<QVector<GemRepoInfo>, AZStd::string> GetAllGemRepoInfos() = 0;
/**
* Downloads and registers a Gem.
* @param gemName the name of the Gem to download
* @param gemProgressCallback a callback function that is called with an int percentage download value
* @return an outcome with a string error message on failure.
*/
virtual AZ::Outcome<void, AZStd::string> DownloadGem(const QString& gemName, std::function<void(int)> gemProgressCallback) = 0;
/**
* Cancels the current download.
*/
virtual void CancelDownload() = 0;
/**
* Gathers all gem infos for all gems registered from repos.
* @return A list of gem infos.
*/
virtual AZ::Outcome<QVector<GemInfo>, AZStd::string> GetAllGemRepoGemsInfos() = 0;
/**
* Downloads and registers a Gem.
* @param gemName the name of the Gem to download.
* @param gemProgressCallback a callback function that is called with an int percentage download value.
* @param force should we forcibly overwrite the old version of the gem.
* @return an outcome with a string error message on failure.
*/
virtual AZ::Outcome<void, AZStd::string> DownloadGem(
const QString& gemName, std::function<void(int, int)> gemProgressCallback, bool force = false) = 0;
/**
* Cancels the current download.
*/
virtual void CancelDownload() = 0;
/**
* Checks if there is an update avaliable for a gem on a repo.
* @param gemName the name of the gem to check.
* @param lastUpdated last time the gem was update.
* @return true if update is avaliable, false if not.
*/
virtual bool IsGemUpdateAvaliable(const QString& gemName, const QString& lastUpdated) = 0;
};
using PythonBindingsInterface = AZ::Interface<IPythonBindings>;
@@ -96,6 +96,10 @@ set(FILES
Source/GemCatalog/GemListHeaderWidget.cpp
Source/GemCatalog/GemModel.h
Source/GemCatalog/GemModel.cpp
Source/GemCatalog/GemUninstallDialog.h
Source/GemCatalog/GemUninstallDialog.cpp
Source/GemCatalog/GemUpdateDialog.h
Source/GemCatalog/GemUpdateDialog.cpp
Source/GemCatalog/GemDependenciesDialog.h
Source/GemCatalog/GemDependenciesDialog.cpp
Source/GemCatalog/GemRequirementDialog.h
@@ -68,7 +68,7 @@ namespace AWSCore
},
"AccountIdString": {
"type": "string",
"pattern": "^[0-9]{12}$|EMPTY"
"pattern": "^[0-9]{12}$|EMPTY|^$"
},
"NonEmptyString": {
"type": "string",
@@ -59,6 +59,34 @@ R"({
"Version": "1.0.0"
})";
static constexpr const char TEST_VALID_EMPTY_ACCOUNTID_RESOURCE_MAPPING_CONFIG_FILE[] =
R"({
"AWSResourceMappings": {
"TestLambda": {
"Type": "AWS::Lambda::Function",
"Name/ID": "MyTestLambda",
"Region": "us-east-1",
"AccountId": "012345678912"
},
"TestS3Bucket": {
"Type": "AWS::S3::Bucket",
"Name/ID": "MyTestS3Bucket"
},
"TestService.RESTApiId": {
"Type": "AWS::ApiGateway::RestApi",
"Name/ID": "1234567890"
},
"TestService.RESTApiStage": {
"Type": "AWS::ApiGateway::Stage",
"Name/ID": "prod",
"Region": "us-east-1"
}
},
"AccountId": "",
"Region": "us-west-2",
"Version": "1.0.0"
})";
static constexpr const char TEST_INVALID_RESOURCE_MAPPING_CONFIG_FILE[] =
R"({
"AWSResourceMappings": {},
@@ -237,6 +265,21 @@ TEST_F(AWSResourceMappingManagerTest, ActivateManager_ParseValidConfigFile_Confi
EXPECT_TRUE(actualEbusCalls == testThreadNumber);
}
TEST_F(AWSResourceMappingManagerTest, ActivateManager_ParseValidConfigFile_GlobalAccountIdEmpty)
{
CreateTestConfigFile(TEST_VALID_EMPTY_ACCOUNTID_RESOURCE_MAPPING_CONFIG_FILE);
m_resourceMappingManager->ActivateManager();
AZStd::string actualAccountId;
AZStd::string actualRegion;
AWSResourceMappingRequestBus::BroadcastResult(actualAccountId, &AWSResourceMappingRequests::GetDefaultAccountId);
AWSResourceMappingRequestBus::BroadcastResult(actualRegion, &AWSResourceMappingRequests::GetDefaultRegion);
EXPECT_EQ(m_reloadConfigurationCounter, 0);
EXPECT_TRUE(actualAccountId.empty());
EXPECT_FALSE(actualRegion.empty());
EXPECT_TRUE(m_resourceMappingManager->GetStatus() == AWSResourceMappingManager::Status::Ready);
}
TEST_F(AWSResourceMappingManagerTest, DeactivateManager_AfterActivatingWithValidConfigFile_ConfigDataGetCleanedUp)
{
CreateTestConfigFile(TEST_VALID_RESOURCE_MAPPING_CONFIG_FILE);
@@ -69,17 +69,13 @@ class ViewEditController(QObject):
json_dict: Dict[str, any] = \
json_utils.convert_resources_to_json_dict(self._proxy_model.get_resources(), self._config_file_json_source)
configuration: Configuration = self._configuration_manager.configuration
if json_dict.get(json_utils.RESOURCE_MAPPING_ACCOUNTID_JSON_KEY_NAME) == \
json_utils.RESOURCE_MAPPING_ACCOUNTID_TEMPLATE_VALUE:
json_dict[json_utils.RESOURCE_MAPPING_ACCOUNTID_JSON_KEY_NAME] = configuration.account_id
if json_dict == self._config_file_json_source:
# skip because no difference found against existing json file
return True
# try to write in memory json content into json file
try:
configuration: Configuration = self._configuration_manager.configuration
config_file_full_path: str = file_utils.join_path(configuration.config_directory, config_file_name)
json_utils.write_into_json_file(config_file_full_path, json_dict)
self._config_file_json_source = json_dict
@@ -420,8 +420,30 @@ class TestViewEditController(TestCase):
self._mocked_view_edit_page.config_file_combobox.currentText.return_value = \
TestViewEditController._expected_config_file_name
expected_json_dict: Dict[str, any] = {
"dummyKey": "dummyValue",
self._expected_account_id_attribute_name: self._expected_account_id_template_vale}
"dummyKey": "dummyValue"
}
mock_json_utils.validate_resources_according_to_json_schema.return_value = []
mock_json_utils.convert_resources_to_json_dict.return_value = expected_json_dict
mock_file_utils.join_path.return_value = TestViewEditController._expected_config_file_full_path
mocked_call_args: call = self._mocked_view_edit_page.save_changes_button.clicked.connect.call_args[0]
mocked_call_args[0]() # triggering save_changes_button connected function
mock_json_utils.convert_resources_to_json_dict.assert_called_once()
mock_json_utils.write_into_json_file.assert_called_once_with(
TestViewEditController._expected_config_file_full_path, expected_json_dict)
self._mocked_proxy_model.override_all_resources_status.assert_called_once_with(
ResourceMappingAttributesStatus(ResourceMappingAttributesStatus.SUCCESS_STATUS_VALUE,
[ResourceMappingAttributesStatus.SUCCESS_STATUS_VALUE]))
@patch("controller.view_edit_controller.file_utils")
@patch("controller.view_edit_controller.json_utils")
def test_page_save_changes_button_json_file_saved_and_template_account_id_unchanged(
self, mock_json_utils: MagicMock, mock_file_utils: MagicMock) -> None:
self._mocked_view_edit_page.config_file_combobox.currentText.return_value = \
TestViewEditController._expected_config_file_name
expected_json_dict: Dict[str, any] = {
self._expected_account_id_attribute_name: self._expected_account_id_template_vale
}
mock_json_utils.RESOURCE_MAPPING_ACCOUNTID_JSON_KEY_NAME = self._expected_account_id_attribute_name
mock_json_utils.RESOURCE_MAPPING_ACCOUNTID_TEMPLATE_VALUE = self._expected_account_id_template_vale
mock_json_utils.validate_resources_according_to_json_schema.return_value = []
@@ -430,7 +452,31 @@ class TestViewEditController(TestCase):
mocked_call_args: call = self._mocked_view_edit_page.save_changes_button.clicked.connect.call_args[0]
mocked_call_args[0]() # triggering save_changes_button connected function
assert expected_json_dict["AccountId"] == self._mocked_configuration_manager.configuration.account_id
assert expected_json_dict[mock_json_utils.RESOURCE_MAPPING_ACCOUNTID_JSON_KEY_NAME] == self._expected_account_id_template_vale
mock_json_utils.convert_resources_to_json_dict.assert_called_once()
mock_json_utils.write_into_json_file.assert_called_once_with(
TestViewEditController._expected_config_file_full_path, expected_json_dict)
self._mocked_proxy_model.override_all_resources_status.assert_called_once_with(
ResourceMappingAttributesStatus(ResourceMappingAttributesStatus.SUCCESS_STATUS_VALUE,
[ResourceMappingAttributesStatus.SUCCESS_STATUS_VALUE]))
@patch("controller.view_edit_controller.file_utils")
@patch("controller.view_edit_controller.json_utils")
def test_page_save_changes_button_json_file_saved_and_empty_account_id_unchanged(
self, mock_json_utils: MagicMock, mock_file_utils: MagicMock) -> None:
self._mocked_view_edit_page.config_file_combobox.currentText.return_value = \
TestViewEditController._expected_config_file_name
expected_json_dict: Dict[str, any] = {
self._expected_account_id_attribute_name: ''
}
mock_json_utils.RESOURCE_MAPPING_ACCOUNTID_JSON_KEY_NAME = self._expected_account_id_attribute_name
mock_json_utils.validate_resources_according_to_json_schema.return_value = []
mock_json_utils.convert_resources_to_json_dict.return_value = expected_json_dict
mock_file_utils.join_path.return_value = TestViewEditController._expected_config_file_full_path
mocked_call_args: call = self._mocked_view_edit_page.save_changes_button.clicked.connect.call_args[0]
mocked_call_args[0]() # triggering save_changes_button connected function
assert expected_json_dict[mock_json_utils.RESOURCE_MAPPING_ACCOUNTID_JSON_KEY_NAME] == ''
mock_json_utils.convert_resources_to_json_dict.assert_called_once()
mock_json_utils.write_into_json_file.assert_called_once_with(
TestViewEditController._expected_config_file_full_path, expected_json_dict)
@@ -103,6 +103,11 @@ class TestJsonUtils(TestCase):
invalid_json_dict.pop(json_utils.RESOURCE_MAPPING_ACCOUNTID_JSON_KEY_NAME)
self.assertRaises(KeyError, json_utils.validate_json_dict_according_to_json_schema, invalid_json_dict)
def test_validate_json_dict_according_to_json_schema_raise_error_when_json_dict_has_empty_accountid(self) -> None:
valid_json_dict: Dict[str, any] = copy.deepcopy(TestJsonUtils._expected_json_dict)
valid_json_dict[json_utils.RESOURCE_MAPPING_ACCOUNTID_JSON_KEY_NAME] = ''
json_utils.validate_json_dict_according_to_json_schema(valid_json_dict)
def test_validate_json_dict_according_to_json_schema_pass_when_json_dict_has_template_accountid(self) -> None:
valid_json_dict: Dict[str, any] = copy.deepcopy(TestJsonUtils._expected_json_dict)
valid_json_dict[json_utils.RESOURCE_MAPPING_ACCOUNTID_JSON_KEY_NAME] = \
@@ -28,7 +28,7 @@ _RESOURCE_MAPPING_JSON_FORMAT_VERSION: str = "1.0.0"
RESOURCE_MAPPING_ACCOUNTID_JSON_KEY_NAME: str = "AccountId"
RESOURCE_MAPPING_ACCOUNTID_TEMPLATE_VALUE: str = "EMPTY"
_RESOURCE_MAPPING_ACCOUNTID_PATTERN: str = f"^[0-9]{{12}}|{RESOURCE_MAPPING_ACCOUNTID_TEMPLATE_VALUE}$"
_RESOURCE_MAPPING_ACCOUNTID_PATTERN: str = f"^[0-9]{{12}}$|{RESOURCE_MAPPING_ACCOUNTID_TEMPLATE_VALUE}|^$"
_RESOURCE_MAPPING_REGION_PATTERN: str = "^[a-z]{2}-[a-z]{4,9}-[0-9]{1}$"
_RESOURCE_MAPPING_VERSION_PATTERN: str = "^[0-9]{1}.[0-9]{1}.[0-9]{1}$"
@@ -82,7 +82,7 @@ namespace AZ
// Register Shader Asset Builder
AssetBuilderSDK::AssetBuilderDesc shaderAssetBuilderDescriptor;
shaderAssetBuilderDescriptor.m_name = "Shader Asset Builder";
shaderAssetBuilderDescriptor.m_version = 107; // Required .azsl extension in .shader file references
shaderAssetBuilderDescriptor.m_version = 108; // The Build Time Stamp of ShaderAsset And ShaderVariantAsset Should Be Based On GetTimeUTCMilliSecond()
// .shader file changes trigger rebuilds
shaderAssetBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern( AZStd::string::format("*.%s", RPI::ShaderSourceData::Extension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
shaderAssetBuilderDescriptor.m_busId = azrtti_typeid<ShaderAssetBuilder>();
@@ -108,7 +108,7 @@ namespace AZ
shaderVariantAssetBuilderDescriptor.m_name = "Shader Variant Asset Builder";
// Both "Shader Variant Asset Builder" and "Shader Asset Builder" produce ShaderVariantAsset products. If you update
// ShaderVariantAsset you will need to update BOTH version numbers, not just "Shader Variant Asset Builder".
shaderVariantAssetBuilderDescriptor.m_version = 26; // [AZSL] Changing inlineConstant to rootConstant keyword work.
shaderVariantAssetBuilderDescriptor.m_version = 27; // The Build Time Stamp of ShaderAsset And ShaderVariantAsset Should Be Based On GetTimeUTCMilliSecond().
shaderVariantAssetBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(AZStd::string::format("*.%s", RPI::ShaderVariantListSourceData::Extension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
shaderVariantAssetBuilderDescriptor.m_busId = azrtti_typeid<ShaderVariantAssetBuilder>();
shaderVariantAssetBuilderDescriptor.m_createJobFunction = AZStd::bind(&ShaderVariantAssetBuilder::CreateJobs, &m_shaderVariantAssetBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2);
@@ -162,7 +162,7 @@ namespace AZ
// has the same value, because later the ShaderVariantTreeAsset job will fetch this value from the local ShaderAsset
// which could cross platforms (i.e. building an android ShaderVariantTreeAsset on PC would fetch the tiemstamp from
// the PC's ShaderAsset).
AZStd::sys_time_t shaderAssetBuildTimestamp = AZStd::GetTimeNowMicroSecond();
AZ::u64 shaderAssetBuildTimestamp = AZStd::GetTimeUTCMilliSecond();
// Need to get the name of the azsl file from the .shader source asset, to be able to declare a dependency to SRG Layout Job.
// and the macro options to preprocess.
@@ -229,8 +229,8 @@ namespace AZ
} // for all request.m_enabledPlatforms
AZ_TracePrintf(
ShaderAssetBuilderName, "CreateJobs for %s took %llu microseconds", shaderAssetSourceFileFullPath.c_str(),
AZStd::GetTimeNowMicroSecond() - shaderAssetBuildTimestamp);
ShaderAssetBuilderName, "CreateJobs for %s took %llu milliseconds", shaderAssetSourceFileFullPath.c_str(),
AZStd::GetTimeUTCMilliSecond() - shaderAssetBuildTimestamp);
response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;
}
@@ -355,8 +355,8 @@ namespace AZ
return;
}
// Get the time stamp string as sys_time_t, and also convert back to string to make sure it was converted correctly.
AZStd::sys_time_t shaderAssetBuildTimestamp = 0;
// Get the time stamp string as u64, and also convert back to string to make sure it was converted correctly.
AZ::u64 shaderAssetBuildTimestamp = 0;
auto shaderAssetBuildTimestampIterator = request.m_jobDescription.m_jobParameters.find(ShaderAssetBuildTimestampParam);
if (shaderAssetBuildTimestampIterator != request.m_jobDescription.m_jobParameters.end())
{
@@ -765,7 +765,7 @@ namespace AZ
return;
}
const AZStd::sys_time_t shaderVariantAssetBuildTimestamp = AZStd::GetTimeNowMicroSecond();
const AZ::u64 shaderVariantAssetBuildTimestamp = AZStd::GetTimeUTCMilliSecond();
auto supervariantList = ShaderBuilderUtility::GetSupervariantListFromShaderSourceData(shaderSourceDescriptor);
@@ -38,7 +38,7 @@ namespace AZ
const AZStd::string& m_tempDirPath;
//! Used to synchronize versions of the ShaderAsset and ShaderVariantAsset,
//! especially during hot-reload. A (ShaderVariantAsset.timestamp) >= (ShaderAsset.timestamp).
const AZStd::sys_time_t m_assetBuildTimestamp;
const AZ::u64 m_assetBuildTimestamp;
const RPI::ShaderSourceData& m_shaderSourceDataDescriptor;
const RPI::ShaderOptionGroupLayout& m_shaderOptionGroupLayout;
const MapOfStringToStageType& m_shaderEntryPoints;
@@ -166,7 +166,7 @@ float DirectionalLightShadow::GetThickness(uint lightIndex, float3 shadowCoords[
bool2 DirectionalLightShadow::IsShadowed(float3 shadowCoord, uint indexOfCascade)
{
static const float PixelMargin = 1.5; // avoiding artifact between cascade levels.
static const float DepthMargin = 0.01; // avoiding artifact when near depth bounds.
static const float DepthMargin = 1e-8; // avoiding artifact when near depth bounds.
// size is the shadowap's width and height.
const uint size = ViewSrg::m_directionalLightShadows[m_lightIndex].m_shadowmapSize;
@@ -210,8 +210,8 @@ float DirectionalLightShadow::GetVisibilityFromLightNoFilter()
float DirectionalLightShadow::GetVisibilityFromLightPcf()
{
static const float DepthMargin = 0.01; // avoiding artifact when near depth bounds.
static const float PixelMargin = 1.5; // avoiding artifact between cascade levels.
static const float DepthMargin = 1e-8; // avoiding artifact when near depth bounds.
const uint size = ViewSrg::m_directionalLightShadows[m_lightIndex].m_shadowmapSize;
const uint cascadeCount = ViewSrg::m_directionalLightShadows[m_lightIndex].m_cascadeCount;

Some files were not shown because too many files have changed in this diff Show More