merging latest dev

Signed-off-by: antonmic <56370189+antonmic@users.noreply.github.com>
monroegm-disable-blank-issue-2
antonmic 4 years ago
commit ba76d304dc

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6d6204c6730e5675791765ca194e9b1cbec282208e280507de830afc2805e5fa
size 41127
oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7
size 1232

@ -62,15 +62,16 @@ def Multiplayer_AutoComponent_RPC():
Report.critical_result(TestSuccessFailTuples.find_network_player, player_id.IsValid())
# 4) Check the editor logs for expected and unexpected log output
# Authority->Autonomous RPC
PLAYERID_RPC_WAIT_TIME_SECONDS = 1.0 # The player id is sent from the server as soon as the player script is spawned. 1 second should be more than enough time to send/receive that RPC.
helper.succeed_if_log_line_found('EditorServer', 'Script: AutoComponent_RPC: Sending client PlayerNumber 1', section_tracer.prints, PLAYERID_RPC_WAIT_TIME_SECONDS)
helper.succeed_if_log_line_found('Script', "AutoComponent_RPC: I'm Player #1", section_tracer.prints, PLAYERID_RPC_WAIT_TIME_SECONDS)
# Uncomment once editor game-play mode supports level entities with net-binding
#PLAYFX_RPC_WAIT_TIME_SECONDS = 1.1 # The server will send an RPC to play an fx on the client every second.
#helper.succeed_if_log_line_found('EditorServer', "Script: AutoComponent_RPC_NetLevelEntity Activated on entity: NetLevelEntity", section_tracer.prints, PLAYFX_RPC_WAIT_TIME_SECONDS)
#helper.succeed_if_log_line_found('EditorServer', "Script: AutoComponent_RPC_NetLevelEntity: Authority sending RPC to play some fx.", section_tracer.prints, PLAYFX_RPC_WAIT_TIME_SECONDS)
#helper.succeed_if_log_line_found('Script', "AutoComponent_RPC_NetLevelEntity: I'm a client playing some superficial fx.", section_tracer.prints, PLAYFX_RPC_WAIT_TIME_SECONDS)
# Authority->Client RPC
PLAYFX_RPC_WAIT_TIME_SECONDS = 1.1 # The server will send an RPC to play an fx on the client every second.
helper.succeed_if_log_line_found('EditorServer', "Script: AutoComponent_RPC_NetLevelEntity Activated on entity: NetLevelEntity", section_tracer.prints, PLAYFX_RPC_WAIT_TIME_SECONDS)
helper.succeed_if_log_line_found('EditorServer', "Script: AutoComponent_RPC_NetLevelEntity: Authority sending RPC to play some fx.", section_tracer.prints, PLAYFX_RPC_WAIT_TIME_SECONDS)
helper.succeed_if_log_line_found('Script', "AutoComponent_RPC_NetLevelEntity: I'm a client playing some fx.", section_tracer.prints, PLAYFX_RPC_WAIT_TIME_SECONDS)
# Exit game mode

@ -93,7 +93,7 @@ def Terrain_World_ConfigurationWorks():
# 5) Set the base Terrain World values
world_bounds_max = azmath.Vector3(1100.0, 1100.0, 1100.0)
world_bounds_min = azmath.Vector3(10.0, 10.0, 10.0)
height_query_resolution = azmath.Vector2(1.0, 1.0)
height_query_resolution = 1.0
hydra.set_component_property_value(terrain_world_component, "Configuration|World Bounds (Max)", world_bounds_max)
hydra.set_component_property_value(terrain_world_component, "Configuration|World Bounds (Min)", world_bounds_min)
hydra.set_component_property_value(terrain_world_component, "Configuration|Height Query Resolution (m)", height_query_resolution)
@ -148,7 +148,7 @@ def Terrain_World_ConfigurationWorks():
# 13) Check height value is the expected one when query resolution is changed
testpoint = terrain.TerrainDataRequestBus(bus.Broadcast, 'GetHeightFromFloats', 10.5, 10.5, CLAMP)
height_query_resolution = azmath.Vector2(0.5, 0.5)
height_query_resolution = 0.5
hydra.set_component_property_value(terrain_world_component, "Configuration|Height Query Resolution (m)", height_query_resolution)
general.idle_wait_frames(1)
testpoint2 = terrain.TerrainDataRequestBus(bus.Broadcast, 'GetHeightFromFloats', 10.5, 10.5, CLAMP)
@ -165,4 +165,3 @@ if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(Terrain_World_ConfigurationWorks)

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7
size 1232

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6d6204c6730e5675791765ca194e9b1cbec282208e280507de830afc2805e5fa
size 41127
oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7
size 1232

@ -568,22 +568,10 @@
"$type": "EditorScriptCanvasComponent",
"Id": 14750978061505735417,
"m_name": "GlobalGameData",
"m_assetHolder": {
"m_asset": {
"assetId": {
"guid": "{B16589A0-EA01-56BC-8141-91A3967FB95F}"
},
"assetHint": "levels/multiplayer/autocomponent_rpc/globalgamedata.scriptcanvas"
}
},
"runtimeDataIsValid": true,
"runtimeDataOverrides": {
"source": {
"assetId": {
"guid": "{B16589A0-EA01-56BC-8141-91A3967FB95F}"
},
"assetHint": "levels/multiplayer/autocomponent_rpc/globalgamedata.scriptcanvas"
}
"sourceHandle": {
"id": "{B16589A0-EA01-56BC-8141-91A3967FB95F}",
"path": "levels/multiplayer/autocomponent_rpc/globalgamedata.scriptcanvas"
}
},
"Component_[16436925042043744033]": {
@ -636,6 +624,13 @@
"$type": "SelectionComponent",
"Id": 12302672911455629152
},
"Component_[12517591696100736853]": {
"$type": "GenericComponentWrapper",
"Id": 12517591696100736853,
"m_template": {
"$type": "AutomatedTesting::NetworkTestLevelEntityComponent"
}
},
"Component_[14169903623243423134]": {
"$type": "EditorVisibilityComponent",
"Id": 14169903623243423134
@ -644,13 +639,6 @@
"$type": "EditorInspectorComponent",
"Id": 14607413934411389854
},
"Component_[15396284312416541768]": {
"$type": "GenericComponentWrapper",
"Id": 15396284312416541768,
"m_template": {
"$type": "Multiplayer::LocalPredictionPlayerInputComponent"
}
},
"Component_[15494977028055234270]": {
"$type": "EditorDisabledCompositionComponent",
"Id": 15494977028055234270
@ -682,22 +670,10 @@
"$type": "EditorScriptCanvasComponent",
"Id": 7256163899440301540,
"m_name": "AutoComponent_RPC_NetLevelEntity",
"m_assetHolder": {
"m_asset": {
"assetId": {
"guid": "{1D517006-AC01-5ECA-AE66-0E007871F0CD}"
},
"assetHint": "levels/multiplayer/autocomponent_rpc/autocomponent_rpc_netlevelentity.scriptcanvas"
}
},
"runtimeDataIsValid": true,
"runtimeDataOverrides": {
"source": {
"assetId": {
"guid": "{1D517006-AC01-5ECA-AE66-0E007871F0CD}"
},
"assetHint": "levels/multiplayer/autocomponent_rpc/autocomponent_rpc_netlevelentity.scriptcanvas"
}
"sourceHandle": {
"id": "{1D517006-AC01-5ECA-AE66-0E007871F0CD}",
"path": "levels/multiplayer/autocomponent_rpc/autocomponent_rpc_netlevelentity.scriptcanvas"
}
},
"Component_[731336627222243355]": {
@ -730,13 +706,6 @@
"$type": "NetBindComponent"
}
},
"Component_[9816897251206708579]": {
"$type": "GenericComponentWrapper",
"Id": 9816897251206708579,
"m_template": {
"$type": "AutomatedTesting::NetworkTestPlayerComponent"
}
},
"Component_[9880860858035405475]": {
"$type": "EditorOnlyEntityComponent",
"Id": 9880860858035405475

@ -5,7 +5,7 @@
"ClassData": {
"m_scriptCanvas": {
"Id": {
"id": 7369225496155711251
"id": 7558387155527535988
},
"Name": "AutoComponent_RPC_NetLevelEntity",
"Components": {
@ -93,7 +93,6 @@
],
"Datums": [
{
"isOverloadedStorage": false,
"scriptCanvasType": {
"m_type": 1
},
@ -108,6 +107,9 @@
"methodType": 2,
"methodName": "GetAuthorityToClientNoParams_PlayFxEventByEntityId",
"className": "NetworkTestLevelEntityComponent",
"resultSlotIDs": [
{}
],
"inputSlots": [
{
"m_id": "{AE2A0AA3-99DD-4DE4-AFEA-7560F078943C}"
@ -688,6 +690,199 @@
}
}
},
{
"Id": {
"id": 11750998249450
},
"Name": "SC-Node(IsNetEntityRoleAuthority)",
"Components": {
"Component_[17217487756380135718]": {
"$type": "{E42861BD-1956-45AE-8DD7-CCFC1E3E5ACF} Method",
"Id": 17217487756380135718,
"Slots": [
{
"id": {
"m_id": "{C58EF254-1A51-443B-B35E-B26831323D27}"
},
"contracts": [
{
"$type": "SlotTypeContract"
}
],
"slotName": "EntityId: 0",
"Descriptor": {
"ConnectionType": 1,
"SlotType": 2
},
"DataType": 1
},
{
"id": {
"m_id": "{98927A53-663A-452D-ABAF-01D31F7D5D53}"
},
"contracts": [
{
"$type": "SlotTypeContract"
}
],
"slotName": "In",
"Descriptor": {
"ConnectionType": 1,
"SlotType": 1
}
},
{
"id": {
"m_id": "{89F646CA-FBC6-45D2-916B-0B47474EE693}"
},
"contracts": [
{
"$type": "SlotTypeContract"
}
],
"slotName": "Out",
"Descriptor": {
"ConnectionType": 2,
"SlotType": 1
}
},
{
"id": {
"m_id": "{3FFC81A6-5C31-4CBA-8253-5DF361F10610}"
},
"contracts": [
{
"$type": "SlotTypeContract"
}
],
"slotName": "Is Role Authority",
"DisplayDataType": {
"m_type": 0
},
"Descriptor": {
"ConnectionType": 2,
"SlotType": 2
},
"DataType": 1
}
],
"Datums": [
{
"isOverloadedStorage": false,
"scriptCanvasType": {
"m_type": 1
},
"isNullPointer": false,
"$type": "EntityId",
"value": {
"id": 2901262558
},
"label": "Entity Id"
}
],
"methodType": 2,
"methodName": "IsNetEntityRoleAuthority",
"className": "NetBindComponent",
"inputSlots": [
{
"m_id": "{C58EF254-1A51-443B-B35E-B26831323D27}"
}
],
"prettyClassName": "NetBindComponent"
}
}
},
{
"Id": {
"id": 13206992162794
},
"Name": "SC-Node(Gate)",
"Components": {
"Component_[18126119383071583133]": {
"$type": "Gate",
"Id": 18126119383071583133,
"Slots": [
{
"id": {
"m_id": "{3896AA13-516C-410F-AB9C-2CAA5E71AEF6}"
},
"contracts": [
{
"$type": "SlotTypeContract"
}
],
"slotName": "Condition",
"toolTip": "If true the node will signal the Output and proceed execution",
"Descriptor": {
"ConnectionType": 1,
"SlotType": 2
},
"DataType": 1
},
{
"id": {
"m_id": "{F651B19A-FD1A-44EB-9C16-0CDB44CD85AA}"
},
"contracts": [
{
"$type": "SlotTypeContract"
}
],
"slotName": "In",
"toolTip": "Input signal",
"Descriptor": {
"ConnectionType": 1,
"SlotType": 1
}
},
{
"id": {
"m_id": "{78807DC9-82B7-4893-ADA4-53E51C2AD3D1}"
},
"contracts": [
{
"$type": "SlotTypeContract"
}
],
"slotName": "True",
"toolTip": "Signaled if the condition provided evaluates to true.",
"Descriptor": {
"ConnectionType": 2,
"SlotType": 1
}
},
{
"id": {
"m_id": "{3B353484-F14C-4D93-AD1D-6F3EDAE4B71C}"
},
"contracts": [
{
"$type": "SlotTypeContract"
}
],
"slotName": "False",
"toolTip": "Signaled if the condition provided evaluates to false.",
"Descriptor": {
"ConnectionType": 2,
"SlotType": 1
}
}
],
"Datums": [
{
"isOverloadedStorage": false,
"scriptCanvasType": {
"m_type": 0
},
"isNullPointer": false,
"$type": "bool",
"value": false,
"label": "Condition"
}
]
}
}
},
{
"Id": {
"id": 57025381737912
@ -882,7 +1077,6 @@
],
"Datums": [
{
"isOverloadedStorage": false,
"scriptCanvasType": {
"m_type": 1
},
@ -897,6 +1091,9 @@
"methodType": 2,
"methodName": "AuthorityToClientNoParams_PlayFxByEntityId",
"className": "NetworkTestLevelEntityComponent",
"resultSlotIDs": [
{}
],
"inputSlots": [
{
"m_id": "{029728DF-0939-4D64-A9A1-3DB4B8AF127E}"
@ -1036,7 +1233,6 @@
],
"Datums": [
{
"isOverloadedStorage": false,
"scriptCanvasType": {
"m_type": 4,
"m_azType": "{F429F985-AF00-529B-8449-16E56694E5F9}"
@ -1223,34 +1419,6 @@
}
}
},
{
"Id": {
"id": 57055446508984
},
"Name": "srcEndpoint=(TimeDelay: Done), destEndpoint=(Repeater: Start)",
"Components": {
"Component_[6292481678297438578]": {
"$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection",
"Id": 6292481678297438578,
"sourceEndpoint": {
"nodeId": {
"id": 57012496836024
},
"slotId": {
"m_id": "{158B30BE-BD39-40AE-A8A8-F0E5694F0180}"
}
},
"targetEndpoint": {
"nodeId": {
"id": 56986727032248
},
"slotId": {
"m_id": "{07267CBA-B377-4B57-8A04-E322F8BFC07F}"
}
}
}
}
},
{
"Id": {
"id": 10269167405311
@ -1530,6 +1698,118 @@
}
}
}
},
{
"Id": {
"id": 13030898503658
},
"Name": "srcEndpoint=(TimeDelay: Done), destEndpoint=(IsNetEntityRoleAuthority: In)",
"Components": {
"Component_[14235185264262332827]": {
"$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection",
"Id": 14235185264262332827,
"sourceEndpoint": {
"nodeId": {
"id": 57012496836024
},
"slotId": {
"m_id": "{158B30BE-BD39-40AE-A8A8-F0E5694F0180}"
}
},
"targetEndpoint": {
"nodeId": {
"id": 11750998249450
},
"slotId": {
"m_id": "{98927A53-663A-452D-ABAF-01D31F7D5D53}"
}
}
}
}
},
{
"Id": {
"id": 14005856079850
},
"Name": "srcEndpoint=(IsNetEntityRoleAuthority: Out), destEndpoint=(If: In)",
"Components": {
"Component_[16302238484508620286]": {
"$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection",
"Id": 16302238484508620286,
"sourceEndpoint": {
"nodeId": {
"id": 11750998249450
},
"slotId": {
"m_id": "{89F646CA-FBC6-45D2-916B-0B47474EE693}"
}
},
"targetEndpoint": {
"nodeId": {
"id": 13206992162794
},
"slotId": {
"m_id": "{F651B19A-FD1A-44EB-9C16-0CDB44CD85AA}"
}
}
}
}
},
{
"Id": {
"id": 14302208823274
},
"Name": "srcEndpoint=(IsNetEntityRoleAuthority: Is Role Authority), destEndpoint=(If: Condition)",
"Components": {
"Component_[3887593885874168259]": {
"$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection",
"Id": 3887593885874168259,
"sourceEndpoint": {
"nodeId": {
"id": 11750998249450
},
"slotId": {
"m_id": "{3FFC81A6-5C31-4CBA-8253-5DF361F10610}"
}
},
"targetEndpoint": {
"nodeId": {
"id": 13206992162794
},
"slotId": {
"m_id": "{3896AA13-516C-410F-AB9C-2CAA5E71AEF6}"
}
}
}
}
},
{
"Id": {
"id": 14637216272362
},
"Name": "srcEndpoint=(If: True), destEndpoint=(Repeater: Start)",
"Components": {
"Component_[8721834474263401249]": {
"$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection",
"Id": 8721834474263401249,
"sourceEndpoint": {
"nodeId": {
"id": 13206992162794
},
"slotId": {
"m_id": "{78807DC9-82B7-4893-ADA4-53E51C2AD3D1}"
}
},
"targetEndpoint": {
"nodeId": {
"id": 56986727032248
},
"slotId": {
"m_id": "{07267CBA-B377-4B57-8A04-E322F8BFC07F}"
}
}
}
}
}
]
},
@ -1571,6 +1851,37 @@
}
}
},
{
"Key": {
"id": 11750998249450
},
"Value": {
"ComponentData": {
"{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": {
"$type": "NodeSaveData"
},
"{328FF15C-C302-458F-A43D-E1794DE0904E}": {
"$type": "GeneralNodeTitleComponentSaveData",
"PaletteOverride": "MethodNodeTitlePalette"
},
"{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": {
"$type": "GeometrySaveData",
"Position": [
100.0,
60.0
]
},
"{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": {
"$type": "StylingComponentSaveData",
"SubStyle": ".method"
},
"{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": {
"$type": "PersistentIdComponentSaveData",
"PersistentId": "{73B270BC-9743-41C1-9E48-0CAB5A63AC97}"
}
}
}
},
{
"Key": {
"id": 11993350262154
@ -1587,8 +1898,8 @@
"{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": {
"$type": "GeometrySaveData",
"Position": [
-120.0,
340.0
60.0,
480.0
]
},
"{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": {
@ -1618,8 +1929,8 @@
"{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": {
"$type": "GeometrySaveData",
"Position": [
340.0,
360.0
520.0,
500.0
]
},
"{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": {
@ -1633,6 +1944,36 @@
}
}
},
{
"Key": {
"id": 13206992162794
},
"Value": {
"ComponentData": {
"{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": {
"$type": "NodeSaveData"
},
"{328FF15C-C302-458F-A43D-E1794DE0904E}": {
"$type": "GeneralNodeTitleComponentSaveData",
"PaletteOverride": "LogicNodeTitlePalette"
},
"{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": {
"$type": "GeometrySaveData",
"Position": [
540.0,
60.0
]
},
"{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": {
"$type": "StylingComponentSaveData"
},
"{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": {
"$type": "PersistentIdComponentSaveData",
"PersistentId": "{3F73BD0E-D02D-4A48-9E25-F9FD4A0F1B89}"
}
}
}
},
{
"Key": {
"id": 16962627423626
@ -1649,8 +1990,8 @@
"{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": {
"$type": "GeometrySaveData",
"Position": [
440.0,
-40.0
1240.0,
0.0
]
},
"{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": {
@ -1680,8 +2021,8 @@
"{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": {
"$type": "GeometrySaveData",
"Position": [
80.0,
-60.0
860.0,
0.0
]
},
"{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": {
@ -1740,8 +2081,8 @@
"{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": {
"$type": "GeometrySaveData",
"Position": [
800.0,
260.0
980.0,
400.0
]
},
"{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": {
@ -1770,8 +2111,8 @@
"{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": {
"$type": "GeometrySaveData",
"Position": [
420.0,
100.0
1220.0,
140.0
]
},
"{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": {
@ -1800,8 +2141,8 @@
"{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": {
"$type": "GeometrySaveData",
"Position": [
800.0,
460.0
980.0,
600.0
]
},
"{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": {
@ -1877,16 +2218,16 @@
},
{
"Key": {
"id": 7369225496155711251
"id": 7558387155527535988
},
"Value": {
"ComponentData": {
"{5F84B500-8C45-40D1-8EFC-A5306B241444}": {
"$type": "SceneComponentSaveData",
"ViewParams": {
"Scale": 0.7585823890144868,
"AnchorX": -205.64674377441406,
"AnchorY": -467.9781799316406
"Scale": 0.9585879578077288,
"AnchorX": 488.2181091308594,
"AnchorY": -175.25778198242188
}
}
}
@ -1915,6 +2256,14 @@
"Key": 6462358712820489356,
"Value": 1
},
{
"Key": 8065262779685207188,
"Value": 1
},
{
"Key": 8452971738487658154,
"Value": 1
},
{
"Key": 10684225535275896474,
"Value": 3

@ -42,6 +42,16 @@
"RayTracingAccelerationStructurePass"
]
},
{
"Name": "TerrainDetailTextureComputePass",
"TemplateName": "TerrainDetailTextureComputePassTemplate",
"Enabled": false
},
{
"Name": "TerrainMacroTextureComputePass",
"TemplateName": "TerrainMacroTextureComputePassTemplate",
"Enabled": false
},
{
"Name": "DepthPrePass",
"TemplateName": "DepthMSAAParentTemplate",

@ -78,10 +78,7 @@
<enum>Qt::ClickFocus</enum>
</property>
<property name="toolTip">
<string extracomment="Collapse All"/>
</property>
<property name="toolTipDuration">
<number>3</number>
<string extracomment="Collapse All">Collapse All</string>
</property>
<property name="text">
<string/>

@ -1,369 +0,0 @@
/*
* 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
*
*/
// Description : Tooltip that displays bitmap.
#include "EditorDefs.h"
#include "BitmapToolTip.h"
// Qt
#include <QVBoxLayout>
// Editor
#include "Util/Image.h"
#include "Util/ImageUtil.h"
static const int STATIC_TEXT_C_HEIGHT = 42;
static const int HISTOGRAM_C_HEIGHT = 130;
/////////////////////////////////////////////////////////////////////////////
// CBitmapToolTip
CBitmapToolTip::CBitmapToolTip(QWidget* parent)
: QWidget(parent, Qt::ToolTip)
, m_staticBitmap(new QLabel(this))
, m_staticText(new QLabel(this))
, m_rgbaHistogram(new CImageHistogramCtrl(this))
, m_alphaChannelHistogram(new CImageHistogramCtrl(this))
{
m_nTimer = 0;
m_hToolWnd = nullptr;
m_bShowHistogram = true;
m_bShowFullsize = false;
m_eShowMode = ESHOW_RGB;
connect(&m_timer, &QTimer::timeout, this, &CBitmapToolTip::OnTimer);
auto* layout = new QVBoxLayout(this);
layout->setSizeConstraint(QLayout::SetFixedSize);
layout->addWidget(m_staticBitmap);
layout->addWidget(m_staticText);
auto* histogramLayout = new QHBoxLayout();
histogramLayout->addWidget(m_rgbaHistogram);
histogramLayout->addWidget(m_alphaChannelHistogram);
m_alphaChannelHistogram->setVisible(false);
layout->addLayout(histogramLayout);
setLayout(layout);
}
CBitmapToolTip::~CBitmapToolTip()
{
}
//////////////////////////////////////////////////////////////////////////
void CBitmapToolTip::GetShowMode(EShowMode& eShowMode, bool& bShowInOriginalSize) const
{
bShowInOriginalSize = CheckVirtualKey(Qt::Key_Space);
eShowMode = ESHOW_RGB;
if (m_bHasAlpha)
{
if (CheckVirtualKey(Qt::Key_Control))
{
eShowMode = ESHOW_RGB_ALPHA;
}
else if (CheckVirtualKey(Qt::Key_Alt))
{
eShowMode = ESHOW_ALPHA;
}
else if (CheckVirtualKey(Qt::Key_Shift))
{
eShowMode = ESHOW_RGBA;
}
}
else if (m_bIsLimitedHDR)
{
if (CheckVirtualKey(Qt::Key_Shift))
{
eShowMode = ESHOW_RGBE;
}
}
}
const char* CBitmapToolTip::GetShowModeDescription(EShowMode eShowMode, [[maybe_unused]] bool bShowInOriginalSize) const
{
switch (eShowMode)
{
case ESHOW_RGB:
return "RGB";
case ESHOW_RGB_ALPHA:
return "RGB+A";
case ESHOW_ALPHA:
return "Alpha";
case ESHOW_RGBA:
return "RGBA";
case ESHOW_RGBE:
return "RGBExp";
}
return "";
}
void CBitmapToolTip::RefreshViewmode()
{
LoadImage(m_filename);
if (m_eShowMode == ESHOW_RGB_ALPHA || m_eShowMode == ESHOW_RGBA)
{
m_rgbaHistogram->setVisible(true);
m_alphaChannelHistogram->setVisible(true);
}
else if (m_eShowMode == ESHOW_ALPHA)
{
m_rgbaHistogram->setVisible(false);
m_alphaChannelHistogram->setVisible(true);
}
else
{
m_rgbaHistogram->setVisible(true);
m_alphaChannelHistogram->setVisible(false);
}
}
bool CBitmapToolTip::LoadImage(const QString& imageFilename)
{
EShowMode eShowMode = ESHOW_RGB;
const char* pShowModeDescription = "RGB";
bool bShowInOriginalSize = false;
GetShowMode(eShowMode, bShowInOriginalSize);
pShowModeDescription = GetShowModeDescription(eShowMode, bShowInOriginalSize);
QString convertedFileName = Path::GamePathToFullPath(Path::ReplaceExtension(imageFilename, ".dds"));
// We need to check against both the image filename and the converted filename as it is possible that the
// converted file existed but failed to load previously and we reverted to loading the source asset.
bool alreadyLoadedImage = ((m_filename == convertedFileName) || (m_filename == imageFilename));
if (alreadyLoadedImage && (m_eShowMode == eShowMode) && (m_bShowFullsize == bShowInOriginalSize))
{
return true;
}
CCryFile fileCheck;
if (!fileCheck.Open(convertedFileName.toUtf8().data(), "rb"))
{
// if we didn't find it, then default back to just using what we can find (if any)
convertedFileName = imageFilename;
}
else
{
fileCheck.Close();
}
m_eShowMode = eShowMode;
m_bShowFullsize = bShowInOriginalSize;
CImageEx image;
image.SetHistogramEqualization(CheckVirtualKey(Qt::Key_Shift));
bool loadedRequestedAsset = true;
if (!CImageUtil::LoadImage(convertedFileName, image))
{
//Failed to load the requested asset, let's try loading the source asset if available.
loadedRequestedAsset = false;
if (!CImageUtil::LoadImage(imageFilename, image))
{
m_staticBitmap->clear();
return false;
}
}
QString imginfo;
m_filename = loadedRequestedAsset ? convertedFileName : imageFilename;
m_bHasAlpha = image.HasAlphaChannel();
m_bIsLimitedHDR = image.IsLimitedHDR();
GetShowMode(eShowMode, bShowInOriginalSize);
pShowModeDescription = GetShowModeDescription(eShowMode, bShowInOriginalSize);
if (m_bHasAlpha)
{
imginfo = tr("%1x%2 %3\nShowing %4 (ALT=Alpha, SHIFT=RGBA, CTRL=RGB+A, SPACE=see in original size)");
}
else if (m_bIsLimitedHDR)
{
imginfo = tr("%1x%2 %3\nShowing %4 (SHIFT=see hist.-equalized, SPACE=see in original size)");
}
else
{
imginfo = tr("%1x%2 %3\nShowing %4 (SPACE=see in original size)");
}
imginfo = imginfo.arg(image.GetWidth()).arg(image.GetHeight()).arg(image.GetFormatDescription()).arg(pShowModeDescription);
m_staticText->setText(imginfo);
int w = image.GetWidth();
int h = image.GetHeight();
int multiplier = (m_eShowMode == ESHOW_RGB_ALPHA ? 2 : 1);
int originalW = w * multiplier;
int originalH = h;
if (!bShowInOriginalSize || (w == 0))
{
w = 256;
}
if (!bShowInOriginalSize || (h == 0))
{
h = 256;
}
w *= multiplier;
resize(w + 4, h + 4 + STATIC_TEXT_C_HEIGHT + HISTOGRAM_C_HEIGHT);
setVisible(true);
CImageEx scaledImage;
if (bShowInOriginalSize && (originalW < w))
{
w = originalW;
}
if (bShowInOriginalSize && (originalH < h))
{
h = originalH;
}
scaledImage.Allocate(w, h);
if (m_eShowMode == ESHOW_RGB_ALPHA)
{
CImageUtil::ScaleToDoubleFit(image, scaledImage);
}
else
{
CImageUtil::ScaleToFit(image, scaledImage);
}
if (m_eShowMode == ESHOW_RGB || m_eShowMode == ESHOW_RGBE)
{
scaledImage.SwapRedAndBlue();
scaledImage.FillAlpha();
}
else if (m_eShowMode == ESHOW_ALPHA)
{
for (int hh = 0; hh < scaledImage.GetHeight(); hh++)
{
for (int ww = 0; ww < scaledImage.GetWidth(); ww++)
{
int a = scaledImage.ValueAt(ww, hh) >> 24;
scaledImage.ValueAt(ww, hh) = RGB(a, a, a);
}
}
}
else if (m_eShowMode == ESHOW_RGB_ALPHA)
{
int halfWidth = scaledImage.GetWidth() / 2;
for (int hh = 0; hh < scaledImage.GetHeight(); hh++)
{
for (int ww = 0; ww < halfWidth; ww++)
{
int r = GetRValue(scaledImage.ValueAt(ww, hh));
int g = GetGValue(scaledImage.ValueAt(ww, hh));
int b = GetBValue(scaledImage.ValueAt(ww, hh));
int a = scaledImage.ValueAt(ww, hh) >> 24;
scaledImage.ValueAt(ww, hh) = RGB(b, g, r);
scaledImage.ValueAt(ww + halfWidth, hh) = RGB(a, a, a);
}
}
}
else //if (m_showMode == ESHOW_RGBA)
{
scaledImage.SwapRedAndBlue();
}
QImage qImage(scaledImage.GetWidth(), scaledImage.GetHeight(), QImage::Format_RGB32);
memcpy(qImage.bits(), scaledImage.GetData(), qImage.sizeInBytes());
m_staticBitmap->setPixmap(QPixmap::fromImage(qImage));
if (m_bShowHistogram && scaledImage.GetData())
{
m_rgbaHistogram->ComputeHistogram(image, CImageHistogram::eImageFormat_32BPP_BGRA);
m_rgbaHistogram->setDrawMode(EHistogramDrawMode::OverlappedRGB);
m_alphaChannelHistogram->histogramDisplay()->CopyComputedDataFrom(m_rgbaHistogram->histogramDisplay());
m_alphaChannelHistogram->setDrawMode(EHistogramDrawMode::AlphaChannel);
}
return true;
}
void CBitmapToolTip::OnTimer()
{
/*
if (IsWindowVisible())
{
if (m_bHaveAnythingToRender)
Invalidate();
}
*/
if (m_hToolWnd)
{
QRect toolRc(m_toolRect);
QRect rc = geometry();
QPoint cursorPos = QCursor::pos();
toolRc.moveTopLeft(m_hToolWnd->mapToGlobal(toolRc.topLeft()));
if (!toolRc.contains(cursorPos) && !rc.contains(cursorPos))
{
setVisible(false);
}
else
{
RefreshViewmode();
}
}
}
//////////////////////////////////////////////////////////////////////////
void CBitmapToolTip::showEvent([[maybe_unused]] QShowEvent* event)
{
QPoint cursorPos = QCursor::pos();
move(cursorPos);
m_timer.start(500);
}
//////////////////////////////////////////////////////////////////////////
void CBitmapToolTip::hideEvent([[maybe_unused]] QHideEvent* event)
{
m_timer.stop();
}
//////////////////////////////////////////////////////////////////////////
void CBitmapToolTip::keyPressEvent(QKeyEvent* event)
{
if (event->key() == Qt::Key_Control || event->key() == Qt::Key_Alt || event->key() == Qt::Key_Shift)
{
RefreshViewmode();
}
}
void CBitmapToolTip::keyReleaseEvent(QKeyEvent* event)
{
if (event->key() == Qt::Key_Control || event->key() == Qt::Key_Alt || event->key() == Qt::Key_Shift)
{
RefreshViewmode();
}
}
//////////////////////////////////////////////////////////////////////////
void CBitmapToolTip::SetTool(QWidget* pWnd, const QRect& rect)
{
assert(pWnd);
m_hToolWnd = pWnd;
m_toolRect = rect;
}
#include <Controls/moc_BitmapToolTip.cpp>

@ -1,88 +0,0 @@
/*
* 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
*
*/
// Description : Tooltip that displays bitmap.
#ifndef CRYINCLUDE_EDITOR_CONTROLS_BITMAPTOOLTIP_H
#define CRYINCLUDE_EDITOR_CONTROLS_BITMAPTOOLTIP_H
#pragma once
#if !defined(Q_MOC_RUN)
#include "Controls/ImageHistogramCtrl.h"
#include <QLabel>
#include <QTimer>
#endif
//////////////////////////////////////////////////////////////////////////
class CBitmapToolTip
: public QWidget
{
Q_OBJECT
// Construction
public:
enum EShowMode
{
ESHOW_RGB = 0,
ESHOW_ALPHA,
ESHOW_RGBA,
ESHOW_RGB_ALPHA,
ESHOW_RGBE
};
CBitmapToolTip(QWidget* parent = nullptr);
virtual ~CBitmapToolTip();
bool Create(const RECT& rect);
// Attributes
public:
// Operations
public:
void RefreshViewmode();
bool LoadImage(const QString& imageFilename);
void SetTool(QWidget* pWnd, const QRect& rect);
// Generated message map functions
protected:
void OnTimer();
void keyPressEvent(QKeyEvent* event) override;
void keyReleaseEvent(QKeyEvent* event) override;
void showEvent(QShowEvent* event) override;
void hideEvent(QHideEvent* event) override;
private:
void GetShowMode(EShowMode& showMode, bool& showInOriginalSize) const;
const char* GetShowModeDescription(EShowMode showMode, bool showInOriginalSize) const;
QLabel* m_staticBitmap;
QLabel* m_staticText;
QString m_filename;
bool m_bShowHistogram;
EShowMode m_eShowMode;
bool m_bShowFullsize;
bool m_bHasAlpha;
bool m_bIsLimitedHDR;
CImageHistogramCtrl* m_rgbaHistogram;
CImageHistogramCtrl* m_alphaChannelHistogram;
int m_nTimer;
QWidget* m_hToolWnd;
QRect m_toolRect;
QTimer m_timer;
};
#endif // CRYINCLUDE_EDITOR_CONTROLS_BITMAPTOOLTIP_H

@ -1,172 +0,0 @@
/*
* 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 "EditorDefs.h"
#include "QBitmapPreviewDialog.h"
#include <Controls/ui_QBitmapPreviewDialog.h>
#include <QApplication>
#include <QDesktopWidget>
#include <QPainter>
#include <QScreen>
void QBitmapPreviewDialog::ImageData::setRgba8888(const void* buffer, const int& w, const int& h)
{
const unsigned long bytes = w * h * 4;
m_buffer.resize(bytes);
memcpy(m_buffer.data(), buffer, bytes);
m_image = QImage((uchar*)m_buffer.constData(), w, h, QImage::Format::Format_RGBA8888);
}
static void fillChecker(int w, int h, unsigned int* dst)
{
for (int y = 0; y < h; y++)
{
for (int x = 0; x < w; x++)
{
dst[y * w + x] = 0xFF000000 | (((x >> 2) + (y >> 2)) % 2 == 0 ? 0x007F7F7F : 0x00000000);
}
}
}
QBitmapPreviewDialog::QBitmapPreviewDialog(QWidget* parent)
: QWidget(parent)
, ui(new Ui::QBitmapTooltip)
{
ui->setupUi(this);
setAttribute(Qt::WA_TranslucentBackground);
setAttribute(Qt::WA_ShowWithoutActivating);
// Clear label text
ui->m_placeholderBitmap->setText("");
ui->m_placeholderHistogram->setText("");
ui->m_bitmapSize->setProperty("tableRow", "Odd");
ui->m_Mips->setProperty("tableRow", "Even");
ui->m_Mean->setProperty("tableRow", "Odd");
ui->m_StdDev->setProperty("tableRow", "Even");
ui->m_Median->setProperty("tableRow", "Odd");
ui->m_labelForBitmapSize->setProperty("tooltipLabel", "content");
ui->m_labelForMean->setProperty("tooltipLabel", "content");
ui->m_labelForMedian->setProperty("tooltipLabel", "content");
ui->m_labelForMips->setProperty("tooltipLabel", "content");
ui->m_labelForStdDev->setProperty("tooltipLabel", "content");
ui->m_vBitmapSize->setProperty("tooltipLabel", "content");
ui->m_vMean->setProperty("tooltipLabel", "content");
ui->m_vMedian->setProperty("tooltipLabel", "content");
ui->m_vMips->setProperty("tooltipLabel", "content");
ui->m_vStdDev->setProperty("tooltipLabel", "content");
// Initialize placeholder images
const int w = 64;
const int h = 64;
QByteArray buffer;
buffer.resize(w * h * 4);
unsigned int* dst = (unsigned int*)buffer.data();
fillChecker(w, h, dst);
m_checker.setRgba8888(buffer.constData(), w, h);
m_initialSize = window()->window()->geometry().size();
}
QBitmapPreviewDialog::~QBitmapPreviewDialog()
{
delete ui;
}
void QBitmapPreviewDialog::setImageRgba8888(const void* buffer, const int& w, const int& h, [[maybe_unused]] const QString& info)
{
m_imageMain.setRgba8888(buffer, w, h);
}
QRect QBitmapPreviewDialog::getHistogramArea()
{
return QRect(ui->m_placeholderHistogram->pos(), ui->m_placeholderHistogram->size());
}
void QBitmapPreviewDialog::setFullSize(const bool& fullSize)
{
if (fullSize)
{
QSize desktop = QApplication::screenAt(ui->m_placeholderBitmap->pos())->availableGeometry().size();
QSize image = m_imageMain.m_image.size();
QPoint location = mapToGlobal(ui->m_placeholderBitmap->pos());
QSize finalSize;
finalSize.setWidth((image.width() < (desktop.width() - location.x())) ? image.width() : (desktop.width() - location.x()));
finalSize.setHeight((image.height() < (desktop.height() - location.y())) ? image.height() : (desktop.height() - location.y()));
float scale = (finalSize.width() < finalSize.height()) ? finalSize.width() / float(m_imageMain.m_image.width()) : finalSize.height() / float(m_imageMain.m_image.height());
ui->m_placeholderBitmap->setFixedSize(scale * m_imageMain.m_image.size());
}
else
{
ui->m_placeholderBitmap->setFixedSize(256, 256);
}
adjustSize();
update();
}
void QBitmapPreviewDialog::paintEvent(QPaintEvent* e)
{
QWidget::paintEvent(e);
QRect rect(ui->m_placeholderBitmap->pos(), ui->m_placeholderBitmap->size());
drawImageData(rect, m_imageMain);
}
void QBitmapPreviewDialog::drawImageData(const QRect& rect, const ImageData& imgData)
{
// Draw the
QPainter p(this);
p.drawImage(rect.topLeft(), m_checker.m_image.scaled(rect.size()));
p.drawImage(rect.topLeft(), imgData.m_image.scaled(rect.size()));
// Draw border
QPen pen;
pen.setColor(QColor(0, 0, 0));
p.drawRect(rect.top(), rect.left(), rect.width() - 1, rect.height());
}
void QBitmapPreviewDialog::setSize(QString _value)
{
ui->m_vBitmapSize->setText(_value);
}
void QBitmapPreviewDialog::setMips(QString _value)
{
ui->m_vMips->setText(_value);
}
void QBitmapPreviewDialog::setMean(QString _value)
{
ui->m_vMean->setText(_value);
}
void QBitmapPreviewDialog::setMedian(QString _value)
{
ui->m_vMedian->setText(_value);
}
void QBitmapPreviewDialog::setStdDev(QString _value)
{
ui->m_vStdDev->setText(_value);
}
QSize QBitmapPreviewDialog::GetCurrentBitmapSize()
{
return ui->m_placeholderBitmap->size();
}
QSize QBitmapPreviewDialog::GetOriginalImageSize()
{
return m_imageMain.m_image.size();
}
#include <Controls/moc_QBitmapPreviewDialog.cpp>

@ -1,64 +0,0 @@
/*
* 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
*
*/
#ifndef QBITMAPPREVIEWDIALOG_H
#define QBITMAPPREVIEWDIALOG_H
#if !defined(Q_MOC_RUN)
#include <QWidget>
#include <QPixmap>
#include <QImage>
#endif
class QLabel;
namespace Ui {
class QBitmapTooltip;
}
class QBitmapPreviewDialog
: public QWidget
{
Q_OBJECT
struct ImageData
{
QByteArray m_buffer;
QImage m_image;
void setRgba8888(const void* buffer, const int& w, const int& h);
};
public:
explicit QBitmapPreviewDialog(QWidget* parent = 0);
virtual ~QBitmapPreviewDialog();
QSize GetCurrentBitmapSize();
QSize GetOriginalImageSize();
protected:
void setImageRgba8888(const void* buffer, const int& w, const int& h, const QString& info);
void setSize(QString _value);
void setMips(QString _value);
void setMean(QString _value);
void setMedian(QString _value);
void setStdDev(QString _value);
QRect getHistogramArea();
void setFullSize(const bool& fullSize);
void paintEvent(QPaintEvent* e) override;
private:
void drawImageData(const QRect& rect, const ImageData& imgData);
protected:
Ui::QBitmapTooltip* ui;
QSize m_initialSize;
ImageData m_checker;
ImageData m_imageMain;
};
#endif // QBITMAPPREVIEWDIALOG_H

@ -1,390 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>QBitmapTooltip</class>
<widget class="QWidget" name="QBitmapTooltip">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>256</width>
<height>510</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>256</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="m_placeholderBitmap">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>256</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<property name="text">
<string>Bitmap Area</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="wordWrap">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="m_placeholderHistogram">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>128</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<property name="text">
<string>Histogram Area</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="m_bitmapSize" native="true">
<property name="minimumSize">
<size>
<width>0</width>
<height>24</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>24</height>
</size>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="m_labelForBitmapSize">
<property name="text">
<string>Size:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="m_vBitmapSize">
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
<property name="text">
<string>Size Value</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="m_Mips" native="true">
<property name="minimumSize">
<size>
<width>0</width>
<height>24</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>24</height>
</size>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="m_labelForMips">
<property name="text">
<string>DXT5 Mips:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="m_vMips">
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
<property name="text">
<string>Size Value</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="m_Mean" native="true">
<property name="minimumSize">
<size>
<width>0</width>
<height>24</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>24</height>
</size>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="m_labelForMean">
<property name="text">
<string>Mean:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="m_vMean">
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
<property name="text">
<string>Size Value</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="m_StdDev" native="true">
<property name="minimumSize">
<size>
<width>0</width>
<height>24</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>24</height>
</size>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="m_labelForStdDev">
<property name="text">
<string>StdDev:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="m_vStdDev">
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
<property name="text">
<string>Size Value</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="m_Median" native="true">
<property name="minimumSize">
<size>
<width>0</width>
<height>24</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>24</height>
</size>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="m_labelForMedian">
<property name="text">
<string>Median:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="m_vMedian">
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
<property name="text">
<string>Size Value</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

@ -1,528 +0,0 @@
/*
* 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 "EditorDefs.h"
#include "QBitmapPreviewDialogImp.h"
// Cry
#include <ITexture.h>
// EditorCore
#include <Util/Image.h>
#include <Include/IImageUtil.h>
// QT
AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // 4251: class '...' needs to have dll-interface to be used by clients of class '...'
#include <QEvent>
#include <QKeyEvent>
#include <QPainter>
#include <QPainterPath>
#include <qmath.h>
AZ_POP_DISABLE_WARNING
#include <Controls/ui_QBitmapPreviewDialog.h>
static const int kDefaultWidth = 256;
static const int kDefaultHeight = 256;
QBitmapPreviewDialogImp::QBitmapPreviewDialogImp(QWidget* parent)
: QBitmapPreviewDialog(parent)
, m_image(new CImageEx())
, m_showOriginalSize(false)
, m_showMode(ESHOW_RGB)
, m_histrogramMode(eHistogramMode_OverlappedRGB)
{
setMouseTracking(true);
setImage("");
ui->m_placeholderBitmap->setStyleSheet("background-color: rgba(0, 0, 0, 0);");
ui->m_placeholderHistogram->setStyleSheet("background-color: rgba(0, 0, 0, 0);");
ui->m_labelForBitmapSize->setProperty("tooltipLabel", "Content");
ui->m_labelForMean->setProperty("tooltipLabel", "Content");
ui->m_labelForMedian->setProperty("tooltipLabel", "Content");
ui->m_labelForMips->setProperty("tooltipLabel", "Content");
ui->m_labelForStdDev->setProperty("tooltipLabel", "Content");
ui->m_vBitmapSize->setProperty("tooltipLabel", "Content");
ui->m_vMean->setProperty("tooltipLabel", "Content");
ui->m_vMedian->setProperty("tooltipLabel", "Content");
ui->m_vMips->setProperty("tooltipLabel", "Content");
ui->m_vStdDev->setProperty("tooltipLabel", "Content");
setUIStyleMode(EUISTYLE_IMAGE_ONLY);
}
QBitmapPreviewDialogImp::~QBitmapPreviewDialogImp()
{
SAFE_DELETE(m_image);
}
void QBitmapPreviewDialogImp::setImage(const QString path)
{
if (path.isEmpty()
|| m_path == path
|| !GetIEditor()->GetImageUtil()->LoadImage(path.toUtf8().data(), *m_image))
{
return;
}
m_showOriginalSize = isSizeSmallerThanDefault();
m_path = path;
refreshData();
}
void QBitmapPreviewDialogImp::setShowMode(EShowMode mode)
{
if (mode == ESHOW_NumModes)
{
return;
}
m_showMode = mode;
refreshData();
update();
}
void QBitmapPreviewDialogImp::toggleShowMode()
{
m_showMode = (EShowMode)(((int)m_showMode + 1) % ESHOW_NumModes);
refreshData();
update();
}
void QBitmapPreviewDialogImp::setUIStyleMode(EUIStyle mode)
{
if (mode >= EUISTYLE_NumModes)
{
return;
}
m_uiStyle = mode;
if (m_uiStyle == EUISTYLE_IMAGE_ONLY)
{
ui->m_placeholderHistogram->hide();
ui->m_labelForBitmapSize->hide();
ui->m_labelForMean->hide();
ui->m_labelForMedian->hide();
ui->m_labelForMips->hide();
ui->m_labelForStdDev->hide();
ui->m_vBitmapSize->hide();
ui->m_vMean->hide();
ui->m_vMedian->hide();
ui->m_vMips->hide();
ui->m_vStdDev->hide();
}
else
{
ui->m_placeholderHistogram->show();
ui->m_labelForBitmapSize->show();
ui->m_labelForMean->show();
ui->m_labelForMedian->show();
ui->m_labelForMips->show();
ui->m_labelForStdDev->show();
ui->m_vBitmapSize->show();
ui->m_vMean->show();
ui->m_vMedian->show();
ui->m_vMips->show();
ui->m_vStdDev->show();
}
}
const QBitmapPreviewDialogImp::EShowMode& QBitmapPreviewDialogImp::getShowMode() const
{
return m_showMode;
}
void QBitmapPreviewDialogImp::setHistogramMode(EHistogramMode mode)
{
if (mode == eHistogramMode_NumModes)
{
return;
}
m_histrogramMode = mode;
}
void QBitmapPreviewDialogImp::toggleHistrogramMode()
{
m_histrogramMode = (EHistogramMode)(((int)m_histrogramMode + 1) % eHistogramMode_NumModes);
update();
}
const QBitmapPreviewDialogImp::EHistogramMode& QBitmapPreviewDialogImp::getHistogramMode() const
{
return m_histrogramMode;
}
void QBitmapPreviewDialogImp::toggleOriginalSize()
{
m_showOriginalSize = !m_showOriginalSize;
refreshData();
update();
}
bool QBitmapPreviewDialogImp::isSizeSmallerThanDefault()
{
return m_image->GetWidth() < kDefaultWidth && m_image->GetHeight() < kDefaultHeight;
}
void QBitmapPreviewDialogImp::setOriginalSize(bool value)
{
m_showOriginalSize = value;
refreshData();
update();
}
const char* QBitmapPreviewDialogImp::GetShowModeDescription(EShowMode eShowMode, [[maybe_unused]] bool bShowInOriginalSize) const
{
switch (eShowMode)
{
case ESHOW_RGB:
return "RGB";
case ESHOW_RGB_ALPHA:
return "RGB+A";
case ESHOW_ALPHA:
return "Alpha";
case ESHOW_RGBA:
return "RGBA";
case ESHOW_RGBE:
return "RGBExp";
}
return "";
}
const char* getHistrogramModeStr(QBitmapPreviewDialogImp::EHistogramMode mode, bool shortName)
{
switch (mode)
{
case QBitmapPreviewDialogImp::eHistogramMode_Luminosity:
return shortName ? "Lum" : "Luminosity";
case QBitmapPreviewDialogImp::eHistogramMode_OverlappedRGB:
return shortName ? "Overlap" : "Overlapped RGBA";
case QBitmapPreviewDialogImp::eHistogramMode_SplitRGB:
return shortName ? "R|G|B" : "Split RGB";
case QBitmapPreviewDialogImp::eHistogramMode_RedChannel:
return shortName ? "Red" : "Red Channel";
case QBitmapPreviewDialogImp::eHistogramMode_GreenChannel:
return shortName ? "Green" : "Green Channel";
case QBitmapPreviewDialogImp::eHistogramMode_BlueChannel:
return shortName ? "Blue" : "Blue Channel";
case QBitmapPreviewDialogImp::eHistogramMode_AlphaChannel:
return shortName ? "Alpha" : "Alpha Channel";
default:
break;
}
return "";
}
void QBitmapPreviewDialogImp::refreshData()
{
// Check if we have some usefull data loaded
if (m_image->GetWidth() * m_image->GetHeight() == 0)
{
return;
}
int w = m_image->GetWidth();
int h = m_image->GetHeight();
int multiplier = (m_showMode == ESHOW_RGB_ALPHA ? 2 : 1);
int originalW = w * multiplier;
int originalH = h;
if (!m_showOriginalSize || (w == 0))
{
w = kDefaultWidth;
}
if (!m_showOriginalSize || (h == 0))
{
h = kDefaultHeight;
}
w *= multiplier;
CImageEx scaledImage;
if (m_showOriginalSize && (originalW < w))
{
w = originalW;
}
if (m_showOriginalSize && (originalH < h))
{
h = originalH;
}
scaledImage.Allocate(w, h);
if (m_showMode == ESHOW_RGB_ALPHA)
{
GetIEditor()->GetImageUtil()->ScaleToDoubleFit(*m_image, scaledImage);
}
else
{
GetIEditor()->GetImageUtil()->ScaleToFit(*m_image, scaledImage);
}
if (m_showMode == ESHOW_RGB || m_showMode == ESHOW_RGBE)
{
scaledImage.FillAlpha();
}
else if (m_showMode == ESHOW_ALPHA)
{
for (int h2 = 0; h2 < scaledImage.GetHeight(); h2++)
{
for (int w2 = 0; w2 < scaledImage.GetWidth(); w2++)
{
int a = scaledImage.ValueAt(w2, h2) >> 24;
scaledImage.ValueAt(w2, h2) = RGB(a, a, a) | (a << 24);
}
}
}
else if (m_showMode == ESHOW_RGB_ALPHA)
{
int halfWidth = scaledImage.GetWidth() / 2;
for (int h2 = 0; h2 < scaledImage.GetHeight(); h2++)
{
for (int w2 = 0; w2 < halfWidth; w2++)
{
int r = GetRValue(scaledImage.ValueAt(w2, h2));
int g = GetGValue(scaledImage.ValueAt(w2, h2));
int b = GetBValue(scaledImage.ValueAt(w2, h2));
int a = scaledImage.ValueAt(w2, h2) >> 24;
scaledImage.ValueAt(w2, h2) = RGB(r, g, b) | (a << 24);
scaledImage.ValueAt(w2 + halfWidth, h2) = RGB(a, a, a) | (a << 24);
}
}
}
setImageRgba8888(scaledImage.GetData(), w, h, "");
setSize(QString().asprintf("%d x %d", m_image->GetWidth(), m_image->GetHeight()));
setMips(QString().asprintf("%d", m_image->GetNumberOfMipMaps()));
setFullSize(m_showOriginalSize);
// Compute histogram
m_histogram.ComputeHistogram((BYTE*)scaledImage.GetData(), w, h, CImageHistogram::eImageFormat_32BPP_RGBA);
}
void QBitmapPreviewDialogImp::paintEvent(QPaintEvent* e)
{
QBitmapPreviewDialog::paintEvent(e);
//if showing original size hide other information so it's easier to see
if (m_showOriginalSize)
{
return;
}
if (m_uiStyle == EUISTYLE_IMAGE_ONLY)
{
return;
}
QPainter p(this);
QPen pen;
QPainterPath path[4];
// Fill background color
QRect histogramRect = getHistogramArea();
p.fillRect(histogramRect, QColor(255, 255, 255));
// Draw borders
pen.setColor(QColor(0, 0, 0));
p.setPen(pen);
p.drawRect(histogramRect);
// Draw histogram
QVector<int> drawChannels;
switch (m_histrogramMode)
{
case eHistogramMode_Luminosity:
drawChannels.push_back(3);
break;
case eHistogramMode_SplitRGB:
drawChannels.push_back(0);
drawChannels.push_back(1);
drawChannels.push_back(2);
break;
case eHistogramMode_OverlappedRGB:
drawChannels.push_back(0);
drawChannels.push_back(1);
drawChannels.push_back(2);
break;
case eHistogramMode_RedChannel:
drawChannels.push_back(0);
break;
case eHistogramMode_GreenChannel:
drawChannels.push_back(1);
break;
case eHistogramMode_BlueChannel:
drawChannels.push_back(2);
break;
case eHistogramMode_AlphaChannel:
drawChannels.push_back(3);
break;
}
int graphWidth = qMax(histogramRect.width(), 1);
int graphHeight = qMax(histogramRect.height() - 2, 0);
int graphBottom = histogramRect.bottom() + 1;
int currX[4] = {0, 0, 0, 0};
int prevX[4] = {0, 0, 0, 0};
float scale = 0.0f;
static const int numSubGraphs = 3;
const int subGraph = qCeil(graphWidth / numSubGraphs);
// Fill background for Split RGB histogram
if (m_histrogramMode == eHistogramMode_SplitRGB)
{
const static QColor backgroundColor[numSubGraphs] =
{
QColor(255, 220, 220),
QColor(220, 255, 220),
QColor(220, 220, 255)
};
for (int i = 0; i < numSubGraphs; i++)
{
p.fillRect(histogramRect.left() + subGraph * i,
histogramRect.top(),
subGraph + (i == numSubGraphs - 1 ? 1 : 0),
histogramRect.height(), backgroundColor[i]);
}
}
int lastHeight[CImageHistogram::kNumChannels] = { INT_MAX, INT_MAX, INT_MAX, INT_MAX };
for (int x = 0; x < graphWidth; ++x)
{
for (int j = 0; j < drawChannels.size(); j++)
{
const int c = drawChannels[j];
int& curr_x = currX[c];
int& prev_x = prevX[c];
int& last_height = lastHeight[c];
QPainterPath& curr_path = path[c];
curr_x = histogramRect.left() + x + 1;
int i = static_cast<int>(((float)x / (graphWidth - 1)) * (CImageHistogram::kNumColorLevels - 1));
if (m_histrogramMode == eHistogramMode_SplitRGB)
{
// Filter out to area which we are interested
const int k = x / subGraph;
if (k != c)
{
continue;
}
i = qCeil((i - (subGraph * c)) * numSubGraphs);
i = qMin(i, CImageHistogram::kNumColorLevels - 1);
i = qMax(i, 0);
}
if (m_histrogramMode == eHistogramMode_Luminosity)
{
scale = (float)m_histogram.m_lumCount[i] / m_histogram.m_maxLumCount;
}
else if (m_histogram.m_maxCount[c])
{
scale = (float)m_histogram.m_count[c][i] / m_histogram.m_maxCount[c];
}
int height = static_cast<int>(graphBottom - graphHeight * scale);
if (last_height == INT_MAX)
{
last_height = height;
}
curr_path.moveTo(prev_x, last_height);
curr_path.lineTo(curr_x, height);
last_height = height;
if (prev_x == INT_MAX)
{
prev_x = curr_x;
}
prev_x = curr_x;
}
}
static const QColor kChannelColor[4] =
{
QColor(255, 0, 0),
QColor(0, 255, 0),
QColor(0, 0, 255),
QColor(120, 120, 120)
};
for (int i = 0; i < drawChannels.size(); i++)
{
const int c = drawChannels[i];
pen.setColor(kChannelColor[c]);
p.setPen(pen);
p.drawPath(path[c]);
}
// Update histogram info
{
float mean = 0, stdDev = 0, median = 0;
switch (m_histrogramMode)
{
case eHistogramMode_Luminosity:
case eHistogramMode_SplitRGB:
case eHistogramMode_OverlappedRGB:
mean = m_histogram.m_meanAvg;
stdDev = m_histogram.m_stdDevAvg;
median = m_histogram.m_medianAvg;
break;
case eHistogramMode_RedChannel:
mean = m_histogram.m_mean[0];
stdDev = m_histogram.m_stdDev[0];
median = m_histogram.m_median[0];
break;
case eHistogramMode_GreenChannel:
mean = m_histogram.m_mean[1];
stdDev = m_histogram.m_stdDev[1];
median = m_histogram.m_median[1];
break;
case eHistogramMode_BlueChannel:
mean = m_histogram.m_mean[2];
stdDev = m_histogram.m_stdDev[2];
median = m_histogram.m_median[2];
break;
case eHistogramMode_AlphaChannel:
mean = m_histogram.m_mean[3];
stdDev = m_histogram.m_stdDev[3];
median = m_histogram.m_median[3];
break;
}
QString val;
val.setNum(mean);
setMean(val);
val.setNum(stdDev);
setStdDev(val);
val.setNum(median);
setMedian(val);
}
}
#include <Controls/moc_QBitmapPreviewDialogImp.cpp>

@ -1,89 +0,0 @@
/*
* 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
*
*/
#ifndef QBITMAPPREVIEWDIALOG_IMP_H
#define QBITMAPPREVIEWDIALOG_IMP_H
#if !defined(Q_MOC_RUN)
#include "QBitmapPreviewDialog.h"
#include <Util/ImageHistogram.h>
#endif
class CImageEx;
class QBitmapPreviewDialogImp
: public QBitmapPreviewDialog
{
Q_OBJECT;
public:
enum EUIStyle
{
EUISTYLE_IMAGE_ONLY,
EUISTYLE_IMAGE_HISTOGRAM,
EUISTYLE_NumModes
};
enum EShowMode
{
ESHOW_RGB = 0,
ESHOW_ALPHA,
ESHOW_RGBA,
ESHOW_RGB_ALPHA,
ESHOW_RGBE,
ESHOW_NumModes,
};
enum EHistogramMode
{
eHistogramMode_Luminosity,
eHistogramMode_OverlappedRGB,
eHistogramMode_SplitRGB,
eHistogramMode_RedChannel,
eHistogramMode_GreenChannel,
eHistogramMode_BlueChannel,
eHistogramMode_AlphaChannel,
eHistogramMode_NumModes,
};
explicit QBitmapPreviewDialogImp(QWidget* parent = 0);
virtual ~QBitmapPreviewDialogImp();
void setImage(const QString path);
void setShowMode(EShowMode mode);
void toggleShowMode();
void setUIStyleMode(EUIStyle mode);
const EShowMode& getShowMode() const;
void setHistogramMode(EHistogramMode mode);
void toggleHistrogramMode();
const EHistogramMode& getHistogramMode() const;
void setOriginalSize(bool value);
void toggleOriginalSize();
bool isSizeSmallerThanDefault();
void paintEvent(QPaintEvent* e) override;
protected:
void refreshData();
private:
const char* GetShowModeDescription(EShowMode eShowMode, bool bShowInOriginalSize) const;
private:
CImageEx* m_image;
QString m_path;
CImageHistogram m_histogram;
bool m_showOriginalSize;
EShowMode m_showMode;
EHistogramMode m_histrogramMode;
EUIStyle m_uiStyle;
};
#endif // QBITMAPPREVIEWDIALOG_IMP_H

@ -1,642 +0,0 @@
/*
* 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 "EditorDefs.h"
#include <Controls/QToolTipWidget.h>
#include "QBitmapPreviewDialogImp.h"
#include "qcoreapplication.h"
#include "qguiapplication.h"
#include "qapplication.h"
#include <QDesktopWidget>
#include <QPainter>
#include <QtGlobal>
#include <qgraphicseffect.h>
void QToolTipWidget::RebuildLayout()
{
if (m_title != nullptr)
{
m_title->hide();
}
if (m_content != nullptr)
{
m_content->hide();
}
if (m_specialContent != nullptr)
{
m_specialContent->hide();
}
//empty layout
while (m_layout->count() > 0)
{
m_layout->takeAt(0);
}
qDeleteAll(m_currentShortcuts);
m_currentShortcuts.clear();
if (m_includeTextureShortcuts)
{
m_currentShortcuts.append(new QLabel(tr("Alt - Alpha"), this));
m_currentShortcuts.back()->setProperty("tooltipLabel", "Shortcut");
m_currentShortcuts.append(new QLabel(tr("Shift - RGBA"), this));
m_currentShortcuts.back()->setProperty("tooltipLabel", "Shortcut");
}
if (m_title != nullptr && !m_title->text().isEmpty())
{
m_layout->addWidget(m_title);
m_title->show();
}
for (QLabel* var : m_currentShortcuts)
{
if (var != nullptr)
{
m_layout->addWidget(var);
var->show();
}
}
if (m_specialContent != nullptr)
{
m_layout->addWidget(m_specialContent);
m_specialContent->show();
}
if (m_content != nullptr && !m_content->text().isEmpty())
{
m_layout->addWidget(m_content);
m_content->show();
}
m_background->adjustSize();
adjustSize();
}
void QToolTipWidget::Hide()
{
m_currentShortcuts.clear();
hide();
}
void QToolTipWidget::Show(QPoint pos, ArrowDirection dir)
{
if (!IsValid())
{
return;
}
m_arrow->m_direction = dir;
pos = AdjustTipPosByArrowSize(pos, dir);
m_normalPos = pos;
move(pos);
RebuildLayout();
show();
m_arrow->show();
}
void QToolTipWidget::Display(QRect targetRect, ArrowDirection preferredArrowDir)
{
if (!IsValid())
{
return;
}
KeepTipOnScreen(targetRect, preferredArrowDir);
RebuildLayout();
show();
m_arrow->show();
}
void QToolTipWidget::TryDisplay(QPoint mousePos, const QRect& rect, [[maybe_unused]] ArrowDirection preferredArrowDir)
{
if (rect.contains(mousePos))
{
Display(rect, QToolTipWidget::ArrowDirection::ARROW_RIGHT);
}
else
{
hide();
}
}
void QToolTipWidget::TryDisplay(QPoint mousePos, const QWidget* widget, ArrowDirection preferredArrowDir)
{
const QRect rect(widget->mapToGlobal(QPoint(0,0)), widget->size());
TryDisplay(mousePos, rect, preferredArrowDir);
}
void QToolTipWidget::SetTitle(QString title)
{
if (!title.isEmpty())
{
m_title->setText(title);
}
m_title->setProperty("tooltipLabel", "Title");
setWindowTitle("ToolTip - " + title);
}
void QToolTipWidget::SetContent(QString content)
{
m_content->setWordWrap(true);
m_content->setProperty("tooltipLabel", "Content");
//line-height is not supported via stylesheet so we use the html rich-text subset in QT for it.
m_content->setText(QString("<span style=\"line-height: 14px;\">%1</span>").arg(content));
}
void QToolTipWidget::AppendContent(QString content)
{
m_content->setText(m_content->text() + "\n\n" + content);
update();
RebuildLayout();
m_content->update();
m_content->repaint();
}
QToolTipWidget::QToolTipWidget(QWidget* parent)
: QWidget(parent)
{
m_background = new QWidget(this);
m_background->setProperty("tooltip", "Background");
m_background->stackUnder(this);
m_title = new QLabel(this);
m_currentShortcuts = QVector<QLabel*>();
m_content = new QLabel(this);
m_specialContent = nullptr;
setWindowTitle("ToolTip");
setObjectName("ToolTip");
m_layout = new QVBoxLayout(this);
m_normalPos = QPoint(0, 0);
m_arrow = new QArrow(m_background);
setWindowFlags(Qt::ToolTip | Qt::FramelessWindowHint);
m_arrow->setWindowFlags(Qt::ToolTip | Qt::FramelessWindowHint);
m_arrow->setAttribute(Qt::WA_TranslucentBackground, true);
m_background->setLayout(m_layout);
m_arrow->setObjectName("ToolTipArrow");
m_background->setObjectName("ToolTipBackground");
//we need a drop shadow for the background
QGraphicsDropShadowEffect* dropShadow = new QGraphicsDropShadowEffect(this);
dropShadow->setBlurRadius(m_shadowRadius);
dropShadow->setColor(Qt::black);
dropShadow->setOffset(0);
dropShadow->setEnabled(true);
m_background->setGraphicsEffect(dropShadow);
//we need a second drop shadow effect for the arrow
dropShadow = new QGraphicsDropShadowEffect(m_arrow);
dropShadow->setBlurRadius(m_shadowRadius);
dropShadow->setColor(Qt::black);
dropShadow->setOffset(0);
dropShadow->setEnabled(true);
m_arrow->setGraphicsEffect(dropShadow);
}
QToolTipWidget::~QToolTipWidget()
{
}
void QToolTipWidget::AddSpecialContent(QString type, QString dataStream)
{
if (type.isEmpty())
{
m_includeTextureShortcuts = false;
if (m_specialContent != nullptr)
{
delete m_specialContent;
m_specialContent = nullptr;
}
return;
}
if (type == "TEXTURE")
{
if (m_specialContent == nullptr)
{
QCoreApplication::instance()->installEventFilter(this); //grab the event filter while displaying the advanced texture tooltip
m_specialContent = new QBitmapPreviewDialogImp(this);
}
QString path(dataStream);
qobject_cast<QBitmapPreviewDialogImp*>(m_specialContent)->setImage(path);
// set default showmode to RGB
qobject_cast<QBitmapPreviewDialogImp*>(m_specialContent)->setShowMode(QBitmapPreviewDialogImp::EShowMode::ESHOW_RGB);
QString dir = (path.split("/").count() > path.split("\\").count()) ? path.split("/").back() : path.split("\\").back();
SetTitle(dir);
//always use default size but not image size
qobject_cast<QBitmapPreviewDialogImp*>(m_specialContent)->setOriginalSize(false);
m_includeTextureShortcuts = true;
}
else if (type == "ADD TO CONTENT")
{
AppendContent(dataStream);
m_includeTextureShortcuts = false;
if (m_specialContent != nullptr)
{
delete m_specialContent;
m_specialContent = nullptr;
}
}
else if (type == "REPLACE TITLE")
{
SetTitle(dataStream);
m_includeTextureShortcuts = false;
if (m_specialContent != nullptr)
{
delete m_specialContent;
m_specialContent = nullptr;
}
}
else if (type == "REPLACE CONTENT")
{
SetContent(dataStream);
m_includeTextureShortcuts = false;
if (m_specialContent != nullptr)
{
delete m_specialContent;
m_specialContent = nullptr;
}
}
else
{
m_includeTextureShortcuts = false;
if (m_specialContent != nullptr)
{
delete m_specialContent;
m_specialContent = nullptr;
}
return;
}
m_special = type;
}
bool QToolTipWidget::eventFilter(QObject* obj, QEvent* event)
{
if (event->type() == QEvent::KeyPress)
{
if (m_special == "TEXTURE" && m_specialContent != nullptr)
{
const QKeyEvent* ke = static_cast<QKeyEvent*>(event);
Qt::KeyboardModifiers mods = ke->modifiers();
if (mods & Qt::KeyboardModifier::AltModifier)
{
((QBitmapPreviewDialogImp*)m_specialContent)->setShowMode(QBitmapPreviewDialogImp::ESHOW_ALPHA);
}
else if (mods & Qt::KeyboardModifier::ShiftModifier && !(mods & Qt::KeyboardModifier::ControlModifier))
{
((QBitmapPreviewDialogImp*)m_specialContent)->setShowMode(QBitmapPreviewDialogImp::ESHOW_RGBA);
}
}
}
if (event->type() == QEvent::KeyRelease)
{
if (m_special == "TEXTURE" && m_specialContent != nullptr)
{
const QKeyEvent* ke = static_cast<QKeyEvent*>(event);
Qt::KeyboardModifiers mods = ke->modifiers();
if (!(mods& Qt::KeyboardModifier::AltModifier) && !(mods & Qt::KeyboardModifier::ShiftModifier))
{
((QBitmapPreviewDialogImp*)m_specialContent)->setShowMode(QBitmapPreviewDialogImp::ESHOW_RGB);
}
}
}
return QWidget::eventFilter(obj, event);
}
void QToolTipWidget::hideEvent(QHideEvent* event)
{
QWidget::hideEvent(event);
m_arrow->hide();
}
void QToolTipWidget::UpdateOptionalData(QString optionalData)
{
AddSpecialContent(m_special, optionalData);
}
QPoint QToolTipWidget::AdjustTipPosByArrowSize(QPoint pos, ArrowDirection dir)
{
switch (dir)
{
case QToolTipWidget::ArrowDirection::ARROW_UP:
{
m_arrow->move(pos);
pos.setY(pos.y() + 10);
m_arrow->setFixedSize(20, 10);
pos -= QPoint(m_shadowRadius, m_shadowRadius);
break;
}
case QToolTipWidget::ArrowDirection::ARROW_LEFT:
{
m_arrow->move(pos);
pos.setX(pos.x() + 10);
m_arrow->setFixedSize(10, 20);
pos -= QPoint(m_shadowRadius, m_shadowRadius);
break;
}
case QToolTipWidget::ArrowDirection::ARROW_RIGHT:
{
pos.setX(pos.x() - 10);
m_arrow->move(QPoint(pos.x() + width(), pos.y()));
m_arrow->setFixedSize(10, 20);
pos -= QPoint(-m_shadowRadius, m_shadowRadius);
break;
}
case QToolTipWidget::ArrowDirection::ARROW_DOWN:
{
pos.setY(pos.y() - 10);
m_arrow->move(QPoint(pos.x(), pos.y() + height()));
m_arrow->setFixedSize(20, 10);
pos -= QPoint(m_shadowRadius, -m_shadowRadius);
break;
}
default:
m_arrow->move(-10, -10);
break;
}
return pos;
}
bool QToolTipWidget::IsValid()
{
if (m_title->text().isEmpty() ||
(m_content->text().isEmpty() && m_specialContent == nullptr))
{
return false;
}
return true;
}
void QToolTipWidget::KeepTipOnScreen(QRect targetRect, ArrowDirection preferredArrowDir)
{
QRect desktop = QApplication::desktop()->availableGeometry(this);
if (this->isHidden())
{
setAttribute(Qt::WA_DontShowOnScreen, true);
Show(QPoint(0, 0), preferredArrowDir);
hide();
setAttribute(Qt::WA_DontShowOnScreen, false);
}
//else assume the size is right
//calculate initial rect
QRect tipRect = QRect(0, 0, 0, 0);
switch (preferredArrowDir)
{
case QToolTipWidget::ArrowDirection::ARROW_UP:
{
//tip is below the widget with a left alignment
tipRect.setTopLeft(AdjustTipPosByArrowSize(targetRect.bottomLeft(), preferredArrowDir));
break;
}
case QToolTipWidget::ArrowDirection::ARROW_LEFT:
{
//tip is on the right with the top being even
tipRect.setTopLeft(AdjustTipPosByArrowSize(targetRect.topRight(), preferredArrowDir));
break;
}
case QToolTipWidget::ArrowDirection::ARROW_RIGHT:
{
//tip is on the left with the top being even
tipRect.setY(targetRect.top());
tipRect.setX(targetRect.left() - width());
tipRect.setTopLeft(AdjustTipPosByArrowSize(tipRect.topLeft(), preferredArrowDir));
break;
}
case QToolTipWidget::ArrowDirection::ARROW_DOWN:
{
//tip is above the widget with a left alignment
tipRect.setX(targetRect.left());
tipRect.setY(targetRect.top() - height());
tipRect.setTopLeft(AdjustTipPosByArrowSize(tipRect.topLeft(), preferredArrowDir));
break;
}
default:
{
//tip is on the right with the top being even
preferredArrowDir = QToolTipWidget::ArrowDirection::ARROW_LEFT;
tipRect.setTopLeft(AdjustTipPosByArrowSize(targetRect.topRight(), QToolTipWidget::ArrowDirection::ARROW_LEFT));
break;
}
}
tipRect.setSize(size());
//FixPositioning
if (preferredArrowDir == ArrowDirection::ARROW_LEFT || preferredArrowDir == ArrowDirection::ARROW_RIGHT)
{
if (tipRect.left() <= desktop.left())
{
m_arrow->m_direction = ArrowDirection::ARROW_LEFT;
tipRect.setTopLeft(AdjustTipPosByArrowSize(targetRect.topRight(), m_arrow->m_direction));
}
else if (tipRect.right() >= desktop.right())
{
m_arrow->m_direction = ArrowDirection::ARROW_RIGHT;
tipRect.setLeft(targetRect.left() - width());
tipRect.setTopLeft(AdjustTipPosByArrowSize(tipRect.topLeft(), m_arrow->m_direction));
}
}
else if (preferredArrowDir == ArrowDirection::ARROW_UP || preferredArrowDir == ArrowDirection::ARROW_DOWN)
{
if (tipRect.top() <= desktop.top())
{
m_arrow->m_direction = ArrowDirection::ARROW_UP;
tipRect.setTopLeft(AdjustTipPosByArrowSize(targetRect.bottomLeft(), m_arrow->m_direction));
}
else if (tipRect.bottom() >= desktop.bottom())
{
m_arrow->m_direction = ArrowDirection::ARROW_DOWN;
tipRect.setY(targetRect.top() - height());
tipRect.setTopLeft(AdjustTipPosByArrowSize(tipRect.topLeft(), m_arrow->m_direction));
}
}
//Nudge tip without arrow
if (preferredArrowDir == ArrowDirection::ARROW_UP || preferredArrowDir == ArrowDirection::ARROW_DOWN)
{
if (tipRect.left() <= desktop.left())
{
tipRect.setLeft(desktop.left());
}
else if (tipRect.right() >= desktop.right())
{
tipRect.setLeft(desktop.right() - width());
}
}
else if (preferredArrowDir == ArrowDirection::ARROW_RIGHT || preferredArrowDir == ArrowDirection::ARROW_LEFT)
{
if (tipRect.top() <= desktop.top())
{
tipRect.setTop(desktop.top());
}
else if (tipRect.bottom() >= desktop.bottom())
{
tipRect.setTop(desktop.bottom() - height());
}
}
m_normalPos = tipRect.topLeft();
move(m_normalPos);
}
QPolygonF QToolTipWidget::QArrow::CreateArrow()
{
QVector<QPointF> vertex;
//3 points in triangle
vertex.reserve(3);
//all magic number below are given in order to draw smooth transitions between tooltip and arrow
if (m_direction == ArrowDirection::ARROW_UP)
{
vertex.push_back(QPointF(10, 1));
vertex.push_back(QPointF(19, 10));
vertex.push_back(QPointF(0, 10));
}
else if (m_direction == ArrowDirection::ARROW_RIGHT)
{
vertex.push_back(QPointF(9, 10));
vertex.push_back(QPointF(0, 19));
vertex.push_back(QPointF(0, 1));
}
else if (m_direction == ArrowDirection::ARROW_LEFT)
{
vertex.push_back(QPointF(1, 10));
vertex.push_back(QPointF(10, 19));
vertex.push_back(QPointF(10, 0));
}
else //ArrowDirection::ARROW_DOWN
{
vertex.push_back(QPointF(10, 10));
vertex.push_back(QPointF(19, 0));
vertex.push_back(QPointF(0, 0));
}
return QPolygonF(vertex);
}
void QToolTipWidget::QArrow::paintEvent([[maybe_unused]] QPaintEvent* event)
{
QColor color(255, 255, 255, 255);
QPainter painter(this);
painter.fillRect(rect(), Qt::transparent); //force transparency
painter.setRenderHint(QPainter::Antialiasing, false);
painter.setBrush(color);
painter.setPen(Qt::NoPen);
painter.drawPolygon(CreateArrow());
//painter.setRenderHint(QPainter::Antialiasing, false);
}
QToolTipWrapper::QToolTipWrapper(QWidget* parent)
: QObject(parent)
{
}
void QToolTipWrapper::SetTitle(QString title)
{
m_title = title;
}
void QToolTipWrapper::SetContent(QString content)
{
AddSpecialContent("REPLACE CONTENT", content);
}
void QToolTipWrapper::AppendContent(QString content)
{
AddSpecialContent("ADD TO CONTENT", content);
}
void QToolTipWrapper::AddSpecialContent(QString type, QString dataStream)
{
if (type == "REPLACE CONTENT")
{
m_contentOperations.clear();
}
m_contentOperations.push_back({type, dataStream});
}
void QToolTipWrapper::UpdateOptionalData(QString optionalData)
{
m_contentOperations.push_back({"UPDATE OPTIONAL", optionalData});
}
void QToolTipWrapper::Display(QRect targetRect, QToolTipWidget::ArrowDirection preferredArrowDir)
{
GetOrCreateToolTip()->Display(targetRect, preferredArrowDir);
}
void QToolTipWrapper::TryDisplay(QPoint mousePos, const QWidget * widget, QToolTipWidget::ArrowDirection preferredArrowDir)
{
GetOrCreateToolTip()->TryDisplay(mousePos, widget, preferredArrowDir);
}
void QToolTipWrapper::TryDisplay(QPoint mousePos, const QRect & widget, QToolTipWidget::ArrowDirection preferredArrowDir)
{
GetOrCreateToolTip()->TryDisplay(mousePos, widget, preferredArrowDir);
}
void QToolTipWrapper::hide()
{
DestroyToolTip();
}
void QToolTipWrapper::show()
{
GetOrCreateToolTip()->show();
}
bool QToolTipWrapper::isVisible() const
{
return m_actualTooltip && m_actualTooltip->isVisible();
}
void QToolTipWrapper::update()
{
if (m_actualTooltip)
{
m_actualTooltip->update();
}
}
void QToolTipWrapper::ReplayContentOperations(QToolTipWidget* tooltipWidget)
{
tooltipWidget->SetTitle(m_title);
for (const auto& operation : m_contentOperations)
{
if (operation.first == "UPDATE OPTIONAL")
{
tooltipWidget->UpdateOptionalData(operation.second);
}
else
{
tooltipWidget->AddSpecialContent(operation.first, operation.second);
}
}
}
QToolTipWidget * QToolTipWrapper::GetOrCreateToolTip()
{
if (!m_actualTooltip)
{
QToolTipWidget* tooltipWidget = new QToolTipWidget(static_cast<QWidget*>(parent()));
tooltipWidget->setAttribute(Qt::WA_DeleteOnClose);
ReplayContentOperations(tooltipWidget);
m_actualTooltip = tooltipWidget;
}
return m_actualTooltip.data();
}
void QToolTipWrapper::DestroyToolTip()
{
if (m_actualTooltip)
{
m_actualTooltip->deleteLater();
}
}

@ -1,148 +0,0 @@
/*
* 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
*
*/
#ifndef QToolTipWidget_h__
#define QToolTipWidget_h__
#include "EditorCoreAPI.h"
#include <QPointer>
#include <QWidget>
#include <QLabel>
#include <QString>
#include <QMap>
#include <QMapIterator>
#include <QVector>
#include <QVBoxLayout>
#include <memory>
class IQToolTip
{
public:
virtual void SetTitle(QString title) = 0;
virtual void SetContent(QString content) = 0;
virtual void AppendContent(QString content) = 0;
virtual void AddSpecialContent(QString type, QString dataStream) = 0;
virtual void UpdateOptionalData(QString optionalData) = 0;
};
class EDITOR_CORE_API QToolTipWidget
: public QWidget
, public IQToolTip
{
public:
enum class ArrowDirection
{
ARROW_UP,
ARROW_LEFT,
ARROW_RIGHT,
ARROW_DOWN
};
class QArrow
: public QWidget
{
public:
ArrowDirection m_direction;
QPoint m_pos;
QArrow(QWidget* parent)
: QWidget(parent){ setWindowFlags(Qt::ToolTip); }
virtual ~QArrow(){}
QPolygonF CreateArrow();
virtual void paintEvent(QPaintEvent*) override;
};
QToolTipWidget(QWidget* parent);
~QToolTipWidget();
void SetTitle(QString title) override;
void SetContent(QString content) override;
void AppendContent(QString content) override;
void AddSpecialContent(QString type, QString dataStream) override;
void UpdateOptionalData(QString optionalData) override;
void Display(QRect targetRect, ArrowDirection preferredArrowDir);
//! Displays the tooltip on the given widget, only if the mouse is over it.
void TryDisplay(QPoint mousePos, const QWidget* widget, ArrowDirection preferredArrowDir);
//! Displays the tooltip on the given rect, only if the mouse is over it.
void TryDisplay(QPoint mousePos, const QRect& widget, ArrowDirection preferredArrowDir);
void Hide();
protected:
void Show(QPoint pos, ArrowDirection dir);
bool IsValid();
void KeepTipOnScreen(QRect targetRect, ArrowDirection preferredArrowDir);
QPoint AdjustTipPosByArrowSize(QPoint pos, ArrowDirection dir);
virtual bool eventFilter(QObject* obj, QEvent* event) override;
void RebuildLayout();
virtual void hideEvent(QHideEvent*) override;
QLabel* m_title;
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
QVector<QLabel*> m_currentShortcuts;
AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
//can be anything from QLabel to QBitMapPreviewDialog
//must allow movement, and show/hide calls
QLabel* m_content;
QWidget* m_specialContent;
QWidget* m_background;
QVBoxLayout* m_layout;
QString m_special;
QPoint m_normalPos;
QArrow* m_arrow;
const int m_shadowRadius = 5;
bool m_includeTextureShortcuts; //added since Qt does not support modifier only shortcuts
};
// HACK: The EditorUI_QT classes all were keeping persistent references to QToolTipWidgets around
// This led to many, many top-level widget creations, which led to many platform-side window allocations
// which led to crashes in Qt5.15. As this is legacy code, this is a drop-in replacement that only
// allocates the actual QToolTipWidget (and thus platform window) while the tooltip is visible
class EDITOR_CORE_API QToolTipWrapper
: public QObject
, public IQToolTip
{
public:
QToolTipWrapper(QWidget* parent);
void SetTitle(QString title) override;
void SetContent(QString content) override;
void AppendContent(QString content) override;
void AddSpecialContent(QString type, QString dataStream) override;
void UpdateOptionalData(QString optionalData) override;
void Display(QRect targetRect, QToolTipWidget::ArrowDirection preferredArrowDir);
void TryDisplay(QPoint mousePos, const QWidget* widget, QToolTipWidget::ArrowDirection preferredArrowDir);
void TryDisplay(QPoint mousePos, const QRect& widget, QToolTipWidget::ArrowDirection preferredArrowDir);
void hide();
void show();
bool isVisible() const;
void update();
void repaint(){update();} //Things really shouldn't be calling repaint on these...
void Hide(){hide();}
void close(){hide();}
private:
void ReplayContentOperations(QToolTipWidget* tooltipWidget);
QToolTipWidget* GetOrCreateToolTip();
void DestroyToolTip();
AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // conditional expression is constant, needs to have dll-interface to be used by clients of class 'AzQtComponents::FilteredSearchWidget'
QPointer<QToolTipWidget> m_actualTooltip;
AZ_POP_DISABLE_WARNING
QString m_title;
AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // conditional expression is constant, needs to have dll-interface to be used by clients of class 'AzQtComponents::FilteredSearchWidget'
QVector<QPair<QString, QString>> m_contentOperations;
AZ_POP_DISABLE_WARNING
};
#endif // QToolTipWidget_h__

@ -10,7 +10,6 @@
// Editor
#include "PropertyCtrl.h"
#include "PropertyResourceCtrl.h"
#include "PropertyGenericCtrl.h"
#include "PropertyMiscCtrl.h"
#include "PropertyMotionCtrl.h"
@ -21,7 +20,6 @@ void RegisterReflectedVarHandlers()
if (!registered)
{
registered = true;
EBUS_EVENT(AzToolsFramework::PropertyTypeRegistrationMessages::Bus, RegisterPropertyType, aznew FileResourceSelectorWidgetHandler());
EBUS_EVENT(AzToolsFramework::PropertyTypeRegistrationMessages::Bus, RegisterPropertyType, aznew SequencePropertyHandler());
EBUS_EVENT(AzToolsFramework::PropertyTypeRegistrationMessages::Bus, RegisterPropertyType, aznew SequenceIdPropertyHandler());
EBUS_EVENT(AzToolsFramework::PropertyTypeRegistrationMessages::Bus, RegisterPropertyType, aznew LocalStringPropertyHandler());

@ -1,383 +0,0 @@
/*
* 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 "EditorDefs.h"
#include "PropertyResourceCtrl.h"
// Qt
#include <QHBoxLayout>
#include <QLineEdit>
// AzToolsFramework
#include <AzToolsFramework/AssetBrowser/AssetSelectionModel.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/UI/PropertyEditor/PropertyAudioCtrl.h>
// Editor
#include "Controls/QToolTipWidget.h"
#include "Controls/BitmapToolTip.h"
BrowseButton::BrowseButton(PropertyType type, QWidget* parent /*= nullptr*/)
: QToolButton(parent)
, m_propertyType(type)
{
setAutoRaise(true);
setIcon(QIcon(QStringLiteral(":/stylesheet/img/UI20/browse-edit.svg")));
connect(this, &QAbstractButton::clicked, this, &BrowseButton::OnClicked);
}
void BrowseButton::SetPathAndEmit(const QString& path)
{
//only emit if path changes. Old property control
if (path != m_path)
{
m_path = path;
emit PathChanged(m_path);
}
}
class FileBrowseButton
: public BrowseButton
{
public:
AZ_CLASS_ALLOCATOR(FileBrowseButton, AZ::SystemAllocator, 0);
FileBrowseButton(PropertyType type, QWidget* pParent = nullptr)
: BrowseButton(type, pParent)
{
setToolTip("Browse...");
}
private:
void OnClicked() override
{
QString tempValue("");
if (!m_path.isEmpty() && !Path::GetExt(m_path).isEmpty())
{
tempValue = m_path;
}
AssetSelectionModel selection;
if (m_propertyType == ePropertyTexture)
{
// Filters for texture.
selection = AssetSelectionModel::AssetGroupSelection("Texture");
}
else
{
return;
}
AzToolsFramework::EditorRequests::Bus::Broadcast(&AzToolsFramework::EditorRequests::BrowseForAssets, selection);
if (selection.IsValid())
{
QString newPath = Path::FullPathToGamePath(selection.GetResult()->GetFullPath().c_str()).c_str();
switch (m_propertyType)
{
case ePropertyTexture:
newPath.replace("\\\\", "/");
if (newPath.size() > MAX_PATH)
{
newPath.resize(MAX_PATH);
}
}
SetPathAndEmit(newPath);
}
}
};
class AudioControlSelectorButton
: public BrowseButton
{
public:
AZ_CLASS_ALLOCATOR(AudioControlSelectorButton, AZ::SystemAllocator, 0);
AudioControlSelectorButton(PropertyType type, QWidget* pParent = nullptr)
: BrowseButton(type, pParent)
{
setToolTip(tr("Select Audio Control"));
}
private:
void OnClicked() override
{
AZStd::string resourceResult;
auto ConvertLegacyAudioPropertyType = [](const PropertyType type) -> AzToolsFramework::AudioPropertyType
{
switch (type)
{
case ePropertyAudioTrigger:
return AzToolsFramework::AudioPropertyType::Trigger;
case ePropertyAudioRTPC:
return AzToolsFramework::AudioPropertyType::Rtpc;
case ePropertyAudioSwitch:
return AzToolsFramework::AudioPropertyType::Switch;
case ePropertyAudioSwitchState:
return AzToolsFramework::AudioPropertyType::SwitchState;
case ePropertyAudioEnvironment:
return AzToolsFramework::AudioPropertyType::Environment;
case ePropertyAudioPreloadRequest:
return AzToolsFramework::AudioPropertyType::Preload;
default:
return AzToolsFramework::AudioPropertyType::NumTypes;
}
};
auto propType = ConvertLegacyAudioPropertyType(m_propertyType);
if (propType != AzToolsFramework::AudioPropertyType::NumTypes)
{
AzToolsFramework::AudioControlSelectorRequestBus::EventResult(
resourceResult, propType, &AzToolsFramework::AudioControlSelectorRequestBus::Events::SelectResource,
AZStd::string_view{ m_path.toUtf8().constData() });
SetPathAndEmit(QString{ resourceResult.c_str() });
}
}
};
class TextureEditButton
: public BrowseButton
{
public:
AZ_CLASS_ALLOCATOR(TextureEditButton, AZ::SystemAllocator, 0);
TextureEditButton(QWidget* pParent = nullptr)
: BrowseButton(ePropertyTexture, pParent)
{
setIcon(QIcon(QStringLiteral(":/stylesheet/img/UI20/open-in-internal-app.svg")));
setToolTip(tr("Launch default editor"));
}
private:
void OnClicked() override
{
CFileUtil::EditTextureFile(m_path.toUtf8().data(), true);
}
};
FileResourceSelectorWidget::FileResourceSelectorWidget(QWidget* pParent /*= nullptr*/)
: QWidget(pParent)
, m_propertyType(ePropertyInvalid)
, m_tooltip(nullptr)
{
m_pathEdit = new QLineEdit;
m_mainLayout = new QHBoxLayout(this);
m_mainLayout->addWidget(m_pathEdit, 1);
m_mainLayout->setContentsMargins(0, 0, 0, 0);
// KDAB just ported the MFC texture preview tooltip, but looks like Amazon added their own. Not sure which to use.
// To switch to Amazon QToolTipWidget, remove FileResourceSelectorWidget::event and m_previewTooltip
#ifdef USE_QTOOLTIPWIDGET
m_tooltip = new QToolTipWidget(this);
installEventFilter(this);
#endif
connect(m_pathEdit, &QLineEdit::editingFinished, this, [this]() { OnPathChanged(m_pathEdit->text()); });
}
bool FileResourceSelectorWidget::eventFilter([[maybe_unused]] QObject* obj, QEvent* event)
{
if (m_propertyType == ePropertyTexture)
{
if (event->type() == QEvent::ToolTip)
{
QHelpEvent* e = (QHelpEvent*)event;
m_tooltip->AddSpecialContent("TEXTURE", m_path);
m_tooltip->TryDisplay(e->globalPos(), m_pathEdit, QToolTipWidget::ArrowDirection::ARROW_RIGHT);
return true;
}
if (event->type() == QEvent::Leave)
{
m_tooltip->hide();
}
}
return false;
}
void FileResourceSelectorWidget::SetPropertyType(PropertyType type)
{
if (m_propertyType == type)
{
return;
}
//if the property type changed for some reason, delete all the existing widgets
if (!m_buttons.isEmpty())
{
qDeleteAll(m_buttons.begin(), m_buttons.end());
m_buttons.clear();
}
m_previewToolTip.reset();
m_propertyType = type;
switch (type)
{
case ePropertyTexture:
AddButton(new FileBrowseButton(type));
AddButton(new TextureEditButton);
m_previewToolTip.reset(new CBitmapToolTip);
break;
case ePropertyAudioTrigger:
case ePropertyAudioSwitch:
case ePropertyAudioSwitchState:
case ePropertyAudioRTPC:
case ePropertyAudioEnvironment:
case ePropertyAudioPreloadRequest:
AddButton(new AudioControlSelectorButton(type));
break;
default:
break;
}
m_mainLayout->invalidate();
}
void FileResourceSelectorWidget::AddButton(BrowseButton* button)
{
m_mainLayout->addWidget(button);
m_buttons.push_back(button);
connect(button, &BrowseButton::PathChanged, this, &FileResourceSelectorWidget::OnPathChanged);
}
void FileResourceSelectorWidget::OnPathChanged(const QString& path)
{
bool changed = SetPath(path);
if (changed)
{
emit PathChanged(m_path);
}
}
bool FileResourceSelectorWidget::SetPath(const QString& path)
{
bool changed = false;
const QString newPath = path.toLower();
if (m_path != newPath)
{
m_path = newPath;
UpdateWidgets();
changed = true;
}
return changed;
}
void FileResourceSelectorWidget::UpdateWidgets()
{
m_pathEdit->setText(m_path);
foreach(BrowseButton * button, m_buttons)
{
button->SetPath(m_path);
}
if (m_previewToolTip)
{
m_previewToolTip->SetTool(this, rect());
}
}
QString FileResourceSelectorWidget::GetPath() const
{
return m_path;
}
QWidget* FileResourceSelectorWidget::GetLastInTabOrder()
{
return m_buttons.empty() ? nullptr : m_buttons.last();
}
QWidget* FileResourceSelectorWidget::GetFirstInTabOrder()
{
return m_buttons.empty() ? nullptr : m_buttons.first();
}
void FileResourceSelectorWidget::UpdateTabOrder()
{
if (m_buttons.count() >= 2)
{
for (int i = 0; i < m_buttons.count() - 1; ++i)
{
setTabOrder(m_buttons[i], m_buttons[i + 1]);
}
}
}
bool FileResourceSelectorWidget::event(QEvent* event)
{
if (event->type() == QEvent::ToolTip && m_previewToolTip && !m_previewToolTip->isVisible())
{
if (!m_path.isEmpty())
{
m_previewToolTip->LoadImage(m_path);
m_previewToolTip->setVisible(true);
}
event->accept();
return true;
}
if (event->type() == QEvent::Resize && m_previewToolTip)
{
m_previewToolTip->SetTool(this, rect());
}
return QWidget::event(event);
}
QWidget* FileResourceSelectorWidgetHandler::CreateGUI(QWidget* pParent)
{
FileResourceSelectorWidget* newCtrl = aznew FileResourceSelectorWidget(pParent);
connect(newCtrl, &FileResourceSelectorWidget::PathChanged, newCtrl, [newCtrl]()
{
EBUS_EVENT(AzToolsFramework::PropertyEditorGUIMessages::Bus, RequestWrite, newCtrl);
});
return newCtrl;
}
void FileResourceSelectorWidgetHandler::ConsumeAttribute(FileResourceSelectorWidget* GUI, AZ::u32 attrib, AzToolsFramework::PropertyAttributeReader* attrValue, const char* debugName)
{
Q_UNUSED(GUI);
Q_UNUSED(attrib);
Q_UNUSED(attrValue);
Q_UNUSED(debugName);
}
void FileResourceSelectorWidgetHandler::WriteGUIValuesIntoProperty(size_t index, FileResourceSelectorWidget* GUI, property_t& instance, AzToolsFramework::InstanceDataNode* node)
{
Q_UNUSED(index);
Q_UNUSED(node);
CReflectedVarResource val = instance;
val.m_propertyType = GUI->GetPropertyType();
val.m_path = GUI->GetPath().toUtf8().data();
instance = static_cast<property_t>(val);
}
bool FileResourceSelectorWidgetHandler::ReadValuesIntoGUI(size_t index, FileResourceSelectorWidget* GUI, const property_t& instance, AzToolsFramework::InstanceDataNode* node)
{
Q_UNUSED(index);
Q_UNUSED(node);
CReflectedVarResource val = instance;
GUI->SetPropertyType(val.m_propertyType);
GUI->SetPath(val.m_path.c_str());
return false;
}
#include <Controls/ReflectedPropertyControl/moc_PropertyResourceCtrl.cpp>

@ -1,118 +0,0 @@
/*
* 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
*
*/
#ifndef CRYINCLUDE_EDITOR_UTILS_PROPERTYRESOURCECTRL_H
#define CRYINCLUDE_EDITOR_UTILS_PROPERTYRESOURCECTRL_H
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzCore/base.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzToolsFramework/UI/PropertyEditor/PropertyEditorAPI.h>
#include "ReflectedVar.h"
#include "Util/VariablePropertyType.h"
#include <QWidget>
#include <QtWidgets/QToolButton>
#include <QtCore/QVector>
#endif
class QLineEdit;
class QHBoxLayout;
class CBitmapToolTip;
class QToolTipWidget;
class BrowseButton
: public QToolButton
{
Q_OBJECT
public:
AZ_CLASS_ALLOCATOR(BrowseButton, AZ::SystemAllocator, 0);
BrowseButton(PropertyType type, QWidget* parent = nullptr);
void SetPath(const QString& path) { m_path = path; }
QString GetPath() const { return m_path; }
PropertyType GetPropertyType() const {return m_propertyType; }
signals:
void PathChanged(const QString& path);
protected:
void SetPathAndEmit(const QString& path);
virtual void OnClicked() = 0;
PropertyType m_propertyType;
QString m_path;
};
class FileResourceSelectorWidget
: public QWidget
{
Q_OBJECT
public:
AZ_CLASS_ALLOCATOR(FileResourceSelectorWidget, AZ::SystemAllocator, 0);
FileResourceSelectorWidget(QWidget* pParent = nullptr);
bool SetPath(const QString& path);
QString GetPath() const;
void SetPropertyType(PropertyType type);
PropertyType GetPropertyType() const { return m_propertyType; }
QWidget* GetFirstInTabOrder();
QWidget* GetLastInTabOrder();
void UpdateTabOrder();
bool eventFilter(QObject* obj, QEvent* event) override;
signals:
void PathChanged(const QString& path);
protected:
bool event(QEvent* event) override;
private:
void OnAssignClicked();
void OnMaterialClicked();
void UpdateWidgets();
void AddButton(BrowseButton* button);
void OnPathChanged(const QString& path);
private:
QLineEdit* m_pathEdit;
PropertyType m_propertyType;
QString m_path;
QHBoxLayout* m_mainLayout;
QVector<BrowseButton*> m_buttons;
QScopedPointer<CBitmapToolTip> m_previewToolTip;
QToolTipWidget* m_tooltip;
};
class FileResourceSelectorWidgetHandler
: QObject
, public AzToolsFramework::PropertyHandler < CReflectedVarResource, FileResourceSelectorWidget >
{
Q_OBJECT
public:
AZ_CLASS_ALLOCATOR(FileResourceSelectorWidgetHandler, AZ::SystemAllocator, 0);
virtual AZ::u32 GetHandlerName(void) const override { return AZ_CRC("Resource", 0xbc91f416); }
virtual bool IsDefaultHandler() const override { return true; }
virtual QWidget* GetFirstInTabOrder(FileResourceSelectorWidget* widget) override { return widget->GetFirstInTabOrder(); }
virtual QWidget* GetLastInTabOrder(FileResourceSelectorWidget* widget) override { return widget->GetLastInTabOrder(); }
virtual void UpdateWidgetInternalTabbing(FileResourceSelectorWidget* widget) override { widget->UpdateTabOrder(); }
virtual QWidget* CreateGUI(QWidget* pParent) override;
virtual void ConsumeAttribute(FileResourceSelectorWidget* GUI, AZ::u32 attrib, AzToolsFramework::PropertyAttributeReader* attrValue, const char* debugName) override;
virtual void WriteGUIValuesIntoProperty(size_t index, FileResourceSelectorWidget* GUI, property_t& instance, AzToolsFramework::InstanceDataNode* node) override;
virtual bool ReadValuesIntoGUI(size_t index, FileResourceSelectorWidget* GUI, const property_t& instance, AzToolsFramework::InstanceDataNode* node) override;
};
#endif // CRYINCLUDE_EDITOR_UTILS_PROPERTYRESOURCECTRL_H

@ -70,12 +70,6 @@ void ReflectedVarInit::setupReflection(AZ::SerializeContext* serializeContext)
AZ::EditContext* ec = serializeContext->GetEditContext();
if (ec)
{
ec->Class< CReflectedVarResource >("VarResource", "Resource")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::NameLabelOverride, &CReflectedVarResource::varName)
->Attribute(AZ::Edit::Attributes::DescriptionTextOverride, &CReflectedVarResource::description)
;
ec->Class< CReflectedVarUser >("VarUser", "")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::NameLabelOverride, &CReflectedVarUser::varName)

@ -588,7 +588,7 @@ QMenu* LevelEditorMenuHandler::CreateGameMenu()
bool usePrefabSystemForLevels = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemForLevelsEnabled);
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
if (!usePrefabSystemForLevels)
{
// Export to Engine

@ -13,7 +13,6 @@
#include <QMap>
#include <QTranslator>
#include <QSet>
#include "IEventLoopHook.h"
#include <unordered_map>
#include <AzCore/PlatformDef.h>

@ -706,7 +706,7 @@ void CCryEditApp::OnFileSave()
bool usePrefabSystemForLevels = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemForLevelsEnabled);
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
if (!usePrefabSystemForLevels)
{
@ -1828,34 +1828,6 @@ bool CCryEditApp::InitInstance()
return true;
}
void CCryEditApp::RegisterEventLoopHook(IEventLoopHook* pHook)
{
pHook->pNextHook = m_pEventLoopHook;
m_pEventLoopHook = pHook;
}
void CCryEditApp::UnregisterEventLoopHook(IEventLoopHook* pHookToRemove)
{
IEventLoopHook* pPrevious = nullptr;
for (IEventLoopHook* pHook = m_pEventLoopHook; pHook != nullptr; pHook = pHook->pNextHook)
{
if (pHook == pHookToRemove)
{
if (pPrevious)
{
pPrevious->pNextHook = pHookToRemove->pNextHook;
}
else
{
m_pEventLoopHook = pHookToRemove->pNextHook;
}
pHookToRemove->pNextHook = nullptr;
return;
}
}
}
//////////////////////////////////////////////////////////////////////////
void CCryEditApp::LoadFile(QString fileName)
{
@ -2392,7 +2364,7 @@ void CCryEditApp::ExportLevel(bool bExportToGame, bool bExportTexture, bool bAut
{
bool usePrefabSystemForLevels = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemForLevelsEnabled);
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
if (usePrefabSystemForLevels)
{
AZ_Assert(false, "Prefab system doesn't require level exports.");
@ -2433,7 +2405,7 @@ bool CCryEditApp::UserExportToGame(bool bNoMsgBox)
{
bool usePrefabSystemForLevels = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemForLevelsEnabled);
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
if (usePrefabSystemForLevels)
{
AZ_Assert(false, "Export Level should no longer exist.");
@ -2481,7 +2453,7 @@ void CCryEditApp::ExportToGame(bool bNoMsgBox)
{
bool usePrefabSystemForLevels = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemForLevelsEnabled);
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
if (usePrefabSystemForLevels)
{
AZ_Assert(false, "Prefab system no longer exports levels.");
@ -2989,7 +2961,7 @@ CCryEditApp::ECreateLevelResult CCryEditApp::CreateLevel(const QString& levelNam
{
bool usePrefabSystemForLevels = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemForLevelsEnabled);
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
// If we are creating a new level and we're in simulate mode, then switch it off before we do anything else
if (GetIEditor()->GetGameEngine() && GetIEditor()->GetGameEngine()->GetSimulationMode())
@ -3135,7 +3107,7 @@ bool CCryEditApp::CreateLevel(bool& wasCreateLevelOperationCancelled)
{
bool usePrefabSystemForLevels = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemForLevelsEnabled);
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
if (!usePrefabSystemForLevels)
{
QString str = QObject::tr("Level %1 has been changed. Save Level?").arg(GetIEditor()->GetGameEngine()->GetLevelName());
@ -3349,7 +3321,7 @@ CCryEditDoc* CCryEditApp::OpenDocumentFile(const char* filename, bool addToMostR
bool usePrefabSystemForLevels = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemForLevelsEnabled);
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
// If we are loading and we're in simulate mode, then switch it off before we do anything else
if (GetIEditor()->GetGameEngine() && GetIEditor()->GetGameEngine()->GetSimulationMode())

@ -30,7 +30,6 @@ class CConsoleDialog;
struct mg_connection;
struct mg_request_info;
struct mg_context;
struct IEventLoopHook;
class QAction;
class MainWindow;
class QSharedMemory;
@ -153,8 +152,6 @@ public:
int IdleProcessing(bool bBackground);
bool IsWindowInForeground();
void RunInitPythonScript(CEditCommandLineInfo& cmdInfo);
void RegisterEventLoopHook(IEventLoopHook* pHook);
void UnregisterEventLoopHook(IEventLoopHook* pHook);
void DisableIdleProcessing() override;
void EnableIdleProcessing() override;
@ -344,7 +341,6 @@ private:
QString m_lastOpenLevelPath;
CQuickAccessBar* m_pQuickAccessBar = nullptr;
IEventLoopHook* m_pEventLoopHook = nullptr;
QString m_rootEnginePath;
int m_disableIdleProcessingCounter = 0; //!< Counts requests to disable idle processing. When non-zero, idle processing will be disabled.

@ -371,7 +371,7 @@ void CCryEditDoc::Load(TDocMultiArchive& arrXmlAr, const QString& szFilename)
bool usePrefabSystemForLevels = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemForLevelsEnabled);
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
if (!usePrefabSystemForLevels)
{
@ -636,7 +636,7 @@ bool CCryEditDoc::SaveModified()
bool usePrefabSystemForLevels = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemForLevelsEnabled);
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
if (!usePrefabSystemForLevels)
{
QMessageBox saveModifiedMessageBox(AzToolsFramework::GetActiveWindow());
@ -699,7 +699,7 @@ void CCryEditDoc::OnFileSaveAs()
CCryEditApp::instance()->AddToRecentFileList(levelFileDialog.GetFileName());
bool usePrefabSystemForLevels = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemForLevelsEnabled);
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
if (usePrefabSystemForLevels)
{
AzToolsFramework::Prefab::TemplateId rootPrefabTemplateId =
@ -728,7 +728,7 @@ bool CCryEditDoc::BeforeOpenDocument(const QString& lpszPathName, TOpenDocContex
bool usePrefabSystemForLevels = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemForLevelsEnabled);
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
if (!usePrefabSystemForLevels)
{

@ -101,7 +101,7 @@ void CEditorPreferencesPage_AWS::SaveSettingsRegistryFile()
return;
}
[[maybe_unused]] bool saved{};
[[maybe_unused]] bool saved = false;
constexpr auto configurationMode =
AZ::IO::SystemFile::SF_OPEN_CREATE | AZ::IO::SystemFile::SF_OPEN_CREATE_PATH | AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY;
if (AZ::IO::SystemFile outputFile; outputFile.Open(resolvedPath.data(), configurationMode))

@ -586,6 +586,7 @@ void EditorViewportWidget::OnEditorNotifyEvent(EEditorNotifyEvent event)
break;
case eNotify_OnEndLoad:
case eNotify_OnEndCreate:
UpdateScene();
SetDefaultCamera();
break;

@ -489,7 +489,7 @@ bool CGameEngine::LoadLevel(
bool usePrefabSystemForLevels = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemForLevelsEnabled);
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
if (!usePrefabSystemForLevels)
{

@ -100,7 +100,7 @@ bool CGameExporter::Export(unsigned int flags, [[maybe_unused]] EEndian eExportE
bool usePrefabSystemForLevels = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemForLevelsEnabled);
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
if (usePrefabSystemForLevels)
{
@ -317,8 +317,8 @@ void CGameExporter::ExportLevelInfo(const QString& path)
root->setAttr("Name", levelName.toUtf8().data());
auto terrain = AzFramework::Terrain::TerrainDataRequestBus::FindFirstHandler();
const AZ::Aabb terrainAabb = terrain ? terrain->GetTerrainAabb() : AZ::Aabb::CreateFromPoint(AZ::Vector3::CreateZero());
const AZ::Vector2 terrainGridResolution = terrain ? terrain->GetTerrainHeightQueryResolution() : AZ::Vector2::CreateOne();
const int compiledHeightmapSize = static_cast<int>(terrainAabb.GetXExtent() / terrainGridResolution.GetX());
const float terrainGridResolution = terrain ? terrain->GetTerrainHeightQueryResolution() : 1.0f;
const int compiledHeightmapSize = static_cast<int>(terrainAabb.GetXExtent() / terrainGridResolution);
root->setAttr("HeightmapSize", compiledHeightmapSize);
//////////////////////////////////////////////////////////////////////////

@ -70,7 +70,7 @@ private:
{
bool usePrefabSystemForLevels = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemForLevelsEnabled);
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
if (usePrefabSystemForLevels)
{
AZ_Assert(false, "Level.pak should no longer be used when prefabs are used for levels.");

@ -66,7 +66,6 @@ class IAWSResourceManager;
struct ISystem;
struct IRenderer;
struct AABB;
struct IEventLoopHook;
struct IErrorReport; // Vladimir@conffx
struct IFileUtil; // Vladimir@conffx
struct IEditorLog; // Vladimir@conffx
@ -509,11 +508,6 @@ struct IEditor
virtual void SetActiveView(CViewport* viewport) = 0;
virtual struct IEditorFileMonitor* GetFileMonitor() = 0;
// These are needed for Qt integration:
virtual void RegisterEventLoopHook(IEventLoopHook* pHook) = 0;
virtual void UnregisterEventLoopHook(IEventLoopHook* pHook) = 0;
// ^^^
//! QMimeData is used by the Qt clipboard.
//! IMPORTANT: Any QMimeData allocated for the clipboard will be deleted
//! when the editor exists. If a QMimeData is allocated by a different

@ -789,16 +789,6 @@ IEditorFileMonitor* CEditorImpl::GetFileMonitor()
return m_pEditorFileMonitor.get();
}
void CEditorImpl::RegisterEventLoopHook(IEventLoopHook* pHook)
{
CCryEditApp::instance()->RegisterEventLoopHook(pHook);
}
void CEditorImpl::UnregisterEventLoopHook(IEventLoopHook* pHook)
{
CCryEditApp::instance()->UnregisterEventLoopHook(pHook);
}
float CEditorImpl::GetTerrainElevation(float x, float y)
{
float terrainElevation = AzFramework::Terrain::TerrainDataRequests::GetDefaultTerrainHeight();

@ -155,8 +155,6 @@ public:
CMusicManager* GetMusicManager() override { return m_pMusicManager; };
IEditorFileMonitor* GetFileMonitor() override;
void RegisterEventLoopHook(IEventLoopHook* pHook) override;
void UnregisterEventLoopHook(IEventLoopHook* pHook) override;
IIconManager* GetIconManager() override;
float GetTerrainElevation(float x, float y) override;
Editor::EditorQtApplication* GetEditorQtApplication() override { return m_QtApplication; }

@ -1,24 +0,0 @@
/*
* 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
*
*/
#ifndef CRYINCLUDE_EDITOR_INCLUDE_IEVENTLOOPHOOK_H
#define CRYINCLUDE_EDITOR_INCLUDE_IEVENTLOOPHOOK_H
#pragma once
struct IEventLoopHook
{
IEventLoopHook* pNextHook;
IEventLoopHook()
: pNextHook(0) {}
virtual bool PrePumpMessage() { return false; }
};
#endif // CRYINCLUDE_EDITOR_INCLUDE_IEVENTLOOPHOOK_H

@ -97,8 +97,6 @@ public:
MOCK_METHOD0(GetActiveView, class CViewport* ());
MOCK_METHOD1(SetActiveView, void(CViewport*));
MOCK_METHOD0(GetFileMonitor, struct IEditorFileMonitor* ());
MOCK_METHOD1(RegisterEventLoopHook, void(IEventLoopHook* ));
MOCK_METHOD1(UnregisterEventLoopHook, void(IEventLoopHook* ));
MOCK_CONST_METHOD0(CreateQMimeData, QMimeData* ());
MOCK_CONST_METHOD1(DestroyQMimeData, void(QMimeData*));
MOCK_METHOD0(GetLevelIndependentFileMan, class CLevelIndependentFileMan* ());

@ -659,7 +659,7 @@ void MainWindow::InitActions()
bool usePrefabSystemForLevels = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemForLevelsEnabled);
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
if (!usePrefabSystemForLevels)
{
am->AddAction(ID_FILE_EXPORTTOGAMENOSURFACETEXTURE, tr("&Export to Engine"))

@ -499,14 +499,12 @@ void CEntityObject::AdjustLightProperties(CVarBlockPtr& properties, const char*
pAreaLight->SetHumanName("PlanarLight");
}
bool bCastShadowLegacy = false; // Backward compatibility for existing shadow casting lights
if (IVariable* pCastShadowVarLegacy = FindVariableInSubBlock(properties, pSubBlockVar, "bCastShadow"))
{
pCastShadowVarLegacy->SetFlags(pCastShadowVarLegacy->GetFlags() | IVariable::UI_INVISIBLE);
const QString zeroPrefix("0");
if (!pCastShadowVarLegacy->GetDisplayValue().startsWith(zeroPrefix))
{
bCastShadowLegacy = true;
pCastShadowVarLegacy->SetDisplayValue(zeroPrefix);
}
}

@ -1084,7 +1084,7 @@ void SEditorSettings::SaveSettingsRegistryFile()
return;
}
[[maybe_unused]] bool saved{};
[[maybe_unused]] bool saved = false;
constexpr auto configurationMode = AZ::IO::SystemFile::SF_OPEN_CREATE
| AZ::IO::SystemFile::SF_OPEN_CREATE_PATH
| AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY;

@ -22,13 +22,6 @@ set(FILES
Controls/ReflectedPropertyControl/ReflectedVar.h
Controls/ReflectedPropertyControl/ReflectedVarWrapper.cpp
Controls/ReflectedPropertyControl/ReflectedVarWrapper.h
Controls/QBitmapPreviewDialog.cpp
Controls/QBitmapPreviewDialog.h
Controls/QBitmapPreviewDialog.ui
Controls/QBitmapPreviewDialogImp.cpp
Controls/QBitmapPreviewDialogImp.h
Controls/QToolTipWidget.h
Controls/QToolTipWidget.cpp
UsedResources.cpp
LyViewPaneNames.h
QtViewPaneManager.cpp

@ -270,7 +270,6 @@ set(FILES
Include/ICommandManager.h
Include/IDisplayViewport.h
Include/IEditorClassFactory.h
Include/IEventLoopHook.h
Include/IExportManager.h
Include/IGizmoManager.h
Include/IIconManager.h
@ -314,8 +313,6 @@ set(FILES
AssetEditor/AssetEditorWindow.ui
Commands/CommandManager.cpp
Commands/CommandManager.h
Controls/BitmapToolTip.cpp
Controls/BitmapToolTip.h
Controls/ConsoleSCB.cpp
Controls/ConsoleSCB.h
Controls/ConsoleSCB.ui
@ -337,8 +334,6 @@ set(FILES
Controls/ReflectedPropertyControl/PropertyMiscCtrl.h
Controls/ReflectedPropertyControl/PropertyMotionCtrl.cpp
Controls/ReflectedPropertyControl/PropertyMotionCtrl.h
Controls/ReflectedPropertyControl/PropertyResourceCtrl.cpp
Controls/ReflectedPropertyControl/PropertyResourceCtrl.h
Controls/ReflectedPropertyControl/PropertyCtrl.cpp
Controls/ReflectedPropertyControl/PropertyCtrl.h
MainStatusBar.cpp

@ -36,5 +36,12 @@ namespace AZStd
{
pthread_setname_np(tId, name);
}
uint8_t GetDefaultThreadPriority()
{
// pthread priority is an integer between >=1 and <=99 (although only range 1<=>32 is guaranteed)
// Don't use a scheduling policy value (e.g. SCHED_OTHER or SCHED_FIFO) here.
return 1;
}
}
}

@ -35,7 +35,7 @@ namespace AZStd
void SetThreadPriority(int priority, pthread_attr_t& attr)
{
if (priority == -1)
if (priority <= -1)
{
pthread_attr_setinheritsched(&attr, PTHREAD_INHERIT_SCHED);
}
@ -59,5 +59,18 @@ namespace AZStd
thread_policy_set(mach_thread, THREAD_AFFINITY_POLICY, (thread_policy_t)& policyData, 1);
}
}
//////////////////////////////////////////////////////////////////////////////////
// Apple pthread -> NSThread quality of service level map
// QOS class name | min pthread priority | max pthread priority | comment
// QOS_CLASS_USER_INTERACTIVE | 38 | 47 | Per-frame work
// QOS_CLASS_USER_INITIATED | 32 | 37 | Asynchronous / Cross frame work
// QOS_CLASS_DEFAULT | 21 | 31 | Streaming / Multiple frames deadline
// QOS_CLASS_UTILITY | 5 | 20 | Background asset download
// QOS_CLASS_BACKGROUN | 0 | 4 | Will be prevented from using whole core.
uint8_t GetDefaultThreadPriority()
{
return 10;
}
}
}

@ -21,6 +21,7 @@ namespace AZStd
void PreCreateSetThreadAffinity(int cpuId, pthread_attr_t& attr);
void SetThreadPriority(int priority, pthread_attr_t& attr);
void PostCreateThread(pthread_t tId, const char * name, int cpuId);
uint8_t GetDefaultThreadPriority();
}
namespace Internal
@ -60,12 +61,13 @@ namespace AZStd
}
else
{
priority = SCHED_OTHER;
priority = Platform::GetDefaultThreadPriority();
}
if (desc->m_name)
{
name = desc->m_name;
}
ti->m_name = name;
cpuId = desc->m_cpuId;
pthread_attr_setdetachstate(&attr, desc->m_isJoinable ? PTHREAD_CREATE_JOINABLE : PTHREAD_CREATE_DETACHED);

@ -55,5 +55,12 @@ namespace AZStd
{
pthread_setname_np(tId, name);
}
uint8_t GetDefaultThreadPriority()
{
// pthread priority is an integer between >=1 and <=99 (although only range 1<=>32 is guaranteed)
// Don't use a scheduling policy value (e.g. SCHED_OTHER or SCHED_FIFO) here.
return 1;
}
}
}

@ -447,13 +447,13 @@ namespace UnitTest
{
x -= 1;
});
// a <-- Root
// / \
// b c
// \ /
// d
/*
a <-- Root
/ \
b c
\ /
d
*/
a.Precedes(b, c);
d.Follows(b, c);
@ -522,20 +522,20 @@ namespace UnitTest
{
x -= 1;
});
// NOTE: The ideal way to express this topology is without the wait on the subgraph
// at task g, but this is more an illustrative test. Better is to express the entire
// graph in a single larger graph.
// a <-- Root
// / \
// b c - f
// \ \ \
// \ e - g
// \ /
// \ /
// \ /
// d
/*
NOTE: The ideal way to express this topology is without the wait on the subgraph
at task g, but this is more an illustrative test. Better is to express the entire
graph in a single larger graph.
a <-- Root
/ \
b c - f
\ \ \
\ e - g
\ /
\ /
\ /
d
*/
a.Precedes(b);
a.Precedes(c);
b.Precedes(d);
@ -593,17 +593,17 @@ namespace UnitTest
{
x += 0b1000;
});
// a <-- Root
// / \
// b c - f
// \ \ \
// \ e - g
// \ /
// \ /
// \ /
// d
/*
a <-- Root
/ \
b c - f
\ \ \
\ e - g
\ /
\ /
\ /
d
*/
a.Precedes(b, c);
b.Precedes(d);
c.Precedes(e, f);

@ -112,6 +112,7 @@ namespace AzFramework
virtual void SetPrefabSystemEnabled([[maybe_unused]] bool enable) {}
/// Returns true if Prefab System is enabled for use with levels, false if legacy level system is enabled (level.pak)
/// @deprecated Use 'IsPrefabSystemEnabled' instead
virtual bool IsPrefabSystemForLevelsEnabled() const { return false; }
/// Returns true if code should assert when the Legacy Slice System is used

@ -765,6 +765,7 @@ namespace AzFramework
bool Application::IsPrefabSystemForLevelsEnabled() const
{
AZ_Warning("Application", false, "'IsPrefabSystemForLevelsEnabled' is deprecated, please use 'IsPrefabSystemEnabled' instead.");
return IsPrefabSystemEnabled();
}

@ -1208,7 +1208,7 @@ namespace AZ::IO
bool usePrefabSystemForLevels = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemForLevelsEnabled);
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
if (usePrefabSystemForLevels)
{
@ -1274,7 +1274,7 @@ namespace AZ::IO
bool usePrefabSystemForLevels = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemForLevelsEnabled);
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
AZStd::unique_lock lock(m_csZips);
for (auto it = m_arrZips.begin(); it != m_arrZips.end();)

@ -378,7 +378,7 @@ namespace Physics
m_cachedNativeHeightfield = cachedNativeHeightfield;
}
AZ::Vector2 HeightfieldShapeConfiguration::GetGridResolution() const
const AZ::Vector2& HeightfieldShapeConfiguration::GetGridResolution() const
{
return m_gridResolution;
}

@ -221,7 +221,7 @@ namespace Physics
const void* GetCachedNativeHeightfield() const;
void* GetCachedNativeHeightfield();
void SetCachedNativeHeightfield(void* cachedNativeHeightfield);
AZ::Vector2 GetGridResolution() const;
const AZ::Vector2& GetGridResolution() const;
void SetGridResolution(const AZ::Vector2& gridSpacing);
int32_t GetNumColumns() const;
void SetNumColumns(int32_t numColumns);
@ -235,7 +235,7 @@ namespace Physics
void SetMaxHeightBounds(float maxBounds);
private:
//! The number of meters between each heightfield sample.
//! The number of meters between each heightfield sample in x and y.
AZ::Vector2 m_gridResolution{ 1.0f };
//! The number of columns in the heightfield sample grid.
int32_t m_numColumns{ 0 };

@ -50,8 +50,8 @@ namespace AzFramework
static AZ::Vector3 GetDefaultTerrainNormal() { return AZ::Vector3::CreateAxisZ(); }
// System-level queries to understand world size and resolution
virtual AZ::Vector2 GetTerrainHeightQueryResolution() const = 0;
virtual void SetTerrainHeightQueryResolution(AZ::Vector2 queryResolution) = 0;
virtual float GetTerrainHeightQueryResolution() const = 0;
virtual void SetTerrainHeightQueryResolution(float queryResolution) = 0;
virtual AZ::Aabb GetTerrainAabb() const = 0;
virtual void SetTerrainAabb(const AZ::Aabb& worldBounds) = 0;

@ -49,8 +49,8 @@ namespace UnitTest
AzFramework::Terrain::TerrainDataRequestBus::Handler::BusDisconnect();
}
MOCK_CONST_METHOD0(GetTerrainHeightQueryResolution, AZ::Vector2());
MOCK_METHOD1(SetTerrainHeightQueryResolution, void(AZ::Vector2));
MOCK_CONST_METHOD0(GetTerrainHeightQueryResolution, float());
MOCK_METHOD1(SetTerrainHeightQueryResolution, void(float));
MOCK_CONST_METHOD0(GetTerrainAabb, AZ::Aabb());
MOCK_METHOD1(SetTerrainAabb, void(const AZ::Aabb&));
MOCK_CONST_METHOD3(GetHeight, float(const AZ::Vector3&, Sampler, bool*));

@ -47,8 +47,6 @@ namespace AzManipulatorTestFramework
virtual void UpdateVisibility() = 0;
//! Sets if sticky select is enabled or not.
virtual void SetStickySelect(bool enabled) = 0;
//! Gets default Editor Camera Position.
virtual AZ::Vector3 DefaultEditorCameraPosition() const = 0;
//! Sets if icons are visible in the viewport.
virtual void SetIconsVisible(bool visible) = 0;
//! Sets if helpers are visible in the viewport.

@ -10,6 +10,7 @@
#include <AzFramework/Visibility/EntityVisibilityQuery.h>
#include <AzManipulatorTestFramework/AzManipulatorTestFramework.h>
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
namespace AzFramework
{
@ -22,7 +23,7 @@ namespace AzManipulatorTestFramework
class ViewportInteraction
: public ViewportInteractionInterface
, public AzToolsFramework::ViewportInteraction::ViewportInteractionRequestBus::Handler
, public AzToolsFramework::ViewportInteraction::ViewportSettingsRequestBus::Handler
, public UnitTest::ViewportSettingsTestImpl
, private AzToolsFramework::ViewportInteraction::EditorEntityViewportInteractionRequestBus::Handler
{
public:
@ -50,19 +51,6 @@ namespace AzManipulatorTestFramework
const AzFramework::ScreenPoint& screenPosition) override;
float DeviceScalingFactor() override;
// ViewportSettingsRequestBus overrides ...
bool GridSnappingEnabled() const override;
float GridSize() const override;
bool ShowGrid() const override;
bool AngleSnappingEnabled() const override;
float AngleStep() const override;
float ManipulatorLineBoundWidth() const override;
float ManipulatorCircleBoundWidth() const override;
bool StickySelectEnabled() const override;
AZ::Vector3 DefaultEditorCameraPosition() const override;
bool IconsVisible() const override;
bool HelpersVisible() const override;
// EditorEntityViewportInteractionRequestBus overrides ...
void FindVisibleEntities(AZStd::vector<AZ::EntityId>& visibleEntities) override;
@ -72,12 +60,5 @@ namespace AzManipulatorTestFramework
AzFramework::EntityVisibilityQuery m_entityVisibilityQuery;
AZStd::shared_ptr<AzFramework::DebugDisplayRequests> m_debugDisplayRequests;
AzFramework::CameraState m_cameraState;
float m_gridSize = 1.0f;
float m_angularStep = 0.0f;
bool m_gridSnapping = false;
bool m_angularSnapping = false;
bool m_stickySelect = true;
bool m_iconsVisible = true;
bool m_helpersVisible = true;
};
} // namespace AzManipulatorTestFramework

@ -37,46 +37,6 @@ namespace AzManipulatorTestFramework
return m_cameraState;
}
bool ViewportInteraction::GridSnappingEnabled() const
{
return m_gridSnapping;
}
float ViewportInteraction::GridSize() const
{
return m_gridSize;
}
bool ViewportInteraction::ShowGrid() const
{
return false;
}
bool ViewportInteraction::AngleSnappingEnabled() const
{
return m_angularSnapping;
}
float ViewportInteraction::AngleStep() const
{
return m_angularStep;
}
float ViewportInteraction::ManipulatorLineBoundWidth() const
{
return 0.1f;
}
float ViewportInteraction::ManipulatorCircleBoundWidth() const
{
return 0.1f;
}
bool ViewportInteraction::StickySelectEnabled() const
{
return m_stickySelect;
}
void ViewportInteraction::FindVisibleEntities(AZStd::vector<AZ::EntityId>& visibleEntitiesOut)
{
visibleEntitiesOut.assign(m_entityVisibilityQuery.Begin(), m_entityVisibilityQuery.End());
@ -127,11 +87,6 @@ namespace AzManipulatorTestFramework
m_helpersVisible = visible;
}
AZ::Vector3 ViewportInteraction::DefaultEditorCameraPosition() const
{
return {};
}
void ViewportInteraction::SetGridSize(float size)
{
m_gridSize = size;
@ -162,14 +117,4 @@ namespace AzManipulatorTestFramework
{
return 1.0f;
}
bool ViewportInteraction::IconsVisible() const
{
return m_iconsVisible;
}
bool ViewportInteraction::HelpersVisible() const
{
return m_helpersVisible;
}
} // namespace AzManipulatorTestFramework

@ -295,7 +295,7 @@ namespace AzToolsFramework
bool usePrefabSystemForLevels = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemForLevelsEnabled);
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
for (const AzToolsFramework::AssetFileInfo& assetFileInfo : assetFileInfoList.m_fileInfoList)
{

@ -80,7 +80,6 @@ namespace AzToolsFramework
EntityOutlinerListModel::EntityOutlinerListModel(QObject* parent)
: QAbstractItemModel(parent)
, m_entitySelectQueue()
, m_entityExpandQueue()
, m_entityChangeQueue()
, m_entityChangeQueued(false)
, m_entityLayoutQueued(false)
@ -1275,7 +1274,6 @@ namespace AzToolsFramework
void EntityOutlinerListModel::QueueEntityToExpand(AZ::EntityId entityId, bool expand)
{
m_entityExpansionState[entityId] = expand;
m_entityExpandQueue.insert(entityId);
QueueEntityUpdate(entityId);
}
@ -1301,15 +1299,6 @@ namespace AzToolsFramework
return;
}
{
AZ_PROFILE_SCOPE(Editor, "EntityOutlinerListModel::ProcessEntityUpdates:ExpandQueue");
for (auto entityId : m_entityExpandQueue)
{
emit ExpandEntity(entityId, IsExpanded(entityId));
};
m_entityExpandQueue.clear();
}
{
AZ_PROFILE_SCOPE(Editor, "EntityOutlinerListModel::ProcessEntityUpdates:SelectQueue");
for (auto entityId : m_entitySelectQueue)

@ -156,7 +156,6 @@ namespace AzToolsFramework
void ProcessEntityUpdates();
Q_SIGNALS:
void ExpandEntity(const AZ::EntityId& entityId, bool expand);
void SelectEntity(const AZ::EntityId& entityId, bool select);
void EnableSelectionUpdates(bool enable);
void ResetFilter();
@ -190,7 +189,6 @@ namespace AzToolsFramework
void QueueEntityToExpand(AZ::EntityId entityId, bool expand);
void ProcessEntityInfoResetEnd();
AZStd::unordered_set<AZ::EntityId> m_entitySelectQueue;
AZStd::unordered_set<AZ::EntityId> m_entityExpandQueue;
AZStd::unordered_set<AZ::EntityId> m_entityChangeQueue;
bool m_entityChangeQueued;
bool m_entityLayoutQueued;

@ -81,6 +81,61 @@ namespace AzToolsFramework
update();
}
void EntityOutlinerTreeView::dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector<int>& roles)
{
AzQtComponents::StyledTreeView::dataChanged(topLeft, bottomRight, roles);
if (topLeft.isValid() && topLeft.parent() == bottomRight.parent() && topLeft.row() <= bottomRight.row() &&
topLeft.column() <= bottomRight.column())
{
for (int i = topLeft.row(); i <= bottomRight.row(); i++)
{
auto modelRow = topLeft.sibling(i, EntityOutlinerListModel::ColumnName);
if (modelRow.isValid())
{
checkExpandedState(modelRow);
}
}
}
}
void EntityOutlinerTreeView::rowsInserted(const QModelIndex& parent, int start, int end)
{
if (parent.isValid())
{
for (int i = start; i <= end; i++)
{
auto modelRow = model()->index(i, EntityOutlinerListModel::ColumnName, parent);
if (modelRow.isValid())
{
checkExpandedState(modelRow);
recursiveCheckExpandedStates(modelRow);
}
}
}
AzQtComponents::StyledTreeView::rowsInserted(parent, start, end);
}
void EntityOutlinerTreeView::recursiveCheckExpandedStates(const QModelIndex& current)
{
const int rowCount = model()->rowCount(current);
for (int i = 0; i < rowCount; i++)
{
auto modelRow = model()->index(i, EntityOutlinerListModel::ColumnName, current);
if (modelRow.isValid())
{
checkExpandedState(modelRow);
recursiveCheckExpandedStates(modelRow);
}
}
}
void EntityOutlinerTreeView::checkExpandedState(const QModelIndex& current)
{
const bool expandState = current.data(EntityOutlinerListModel::ExpandedRole).template value<bool>();
setExpanded(current, expandState);
}
void EntityOutlinerTreeView::mousePressEvent(QMouseEvent* event)
{
//postponing normal mouse pressed logic until mouse is released or dragged

@ -51,6 +51,10 @@ namespace AzToolsFramework
Q_SIGNALS:
void ItemDropped();
protected Q_SLOTS:
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles = QVector<int>()) override;
void rowsInserted(const QModelIndex &parent, int start, int end) override;
protected:
// Qt overrides
void mousePressEvent(QMouseEvent* event) override;
@ -75,6 +79,8 @@ namespace AzToolsFramework
void ClearQueuedMouseEvent();
void processQueuedMousePressedEvent(QMouseEvent* event);
void recursiveCheckExpandedStates(const QModelIndex& parent);
void checkExpandedState(const QModelIndex& current);
void StartCustomDrag(const QModelIndexList& indexList, Qt::DropActions supportedActions) override;

@ -224,7 +224,6 @@ namespace AzToolsFramework
connect(m_gui->m_objectTree, &QTreeView::expanded, this, &EntityOutlinerWidget::OnTreeItemExpanded);
connect(m_gui->m_objectTree, &QTreeView::collapsed, this, &EntityOutlinerWidget::OnTreeItemCollapsed);
connect(m_gui->m_objectTree, &EntityOutlinerTreeView::ItemDropped, this, &EntityOutlinerWidget::OnDropEvent);
connect(m_listModel, &EntityOutlinerListModel::ExpandEntity, this, &EntityOutlinerWidget::OnExpandEntity);
connect(m_listModel, &EntityOutlinerListModel::SelectEntity, this, &EntityOutlinerWidget::OnSelectEntity);
connect(m_listModel, &EntityOutlinerListModel::EnableSelectionUpdates, this, &EntityOutlinerWidget::OnEnableSelectionUpdates);
connect(m_listModel, &EntityOutlinerListModel::ResetFilter, this, &EntityOutlinerWidget::ClearFilter);
@ -972,10 +971,6 @@ namespace AzToolsFramework
m_listModel->OnEntityCollapsed(entityId);
}
void EntityOutlinerWidget::OnExpandEntity(const AZ::EntityId& entityId, bool expand)
{
m_gui->m_objectTree->setExpanded(GetIndexFromEntityId(entityId), expand);
}
void EntityOutlinerWidget::OnSelectEntity(const AZ::EntityId& entityId, bool selected)
{

@ -155,7 +155,6 @@ namespace AzToolsFramework
void OnTreeItemDoubleClicked(const QModelIndex& index);
void OnTreeItemExpanded(const QModelIndex& index);
void OnTreeItemCollapsed(const QModelIndex& index);
void OnExpandEntity(const AZ::EntityId& entityId, bool expand);
void OnSelectEntity(const AZ::EntityId& entityId, bool selected);
void OnEnableSelectionUpdates(bool enable);
void OnDropEvent();

@ -306,11 +306,8 @@ namespace AzToolsFramework
{
// Only show the close icon if the prefab is expanded.
// This allows the prefab container to be opened if it was collapsed during propagation.
if (!isExpanded)
if (isExpanded)
{
return;
}
// Use the same color as the background.
QColor backgroundColor = m_backgroundColor;
if (isSelected)
@ -331,17 +328,16 @@ namespace AzToolsFramework
QIcon closeIcon = QIcon(m_prefabEditCloseIconPath);
painter->drawPixmap(option.rect.topLeft() + offset, closeIcon.pixmap(iconSize));
}
}
else
{
// Only show the edit icon on hover.
if (!isHovered)
if (isHovered)
{
return;
}
QIcon openIcon = QIcon(m_prefabEditOpenIconPath);
painter->drawPixmap(option.rect.topLeft() + offset, openIcon.pixmap(iconSize));
}
}
painter->restore();
}

@ -122,7 +122,7 @@ namespace AzToolsFramework
EditorInteractionSystemViewportSelectionRequestBus::Event(
GetEntityContextId(), &EditorInteractionSystemViewportSelection::SetHandler,
[](const EditorVisibleEntityDataCache* entityDataCache, ViewportEditorModeTrackerInterface* viewportEditorModeTracker)
[](const EditorVisibleEntityDataCacheInterface* entityDataCache, ViewportEditorModeTrackerInterface* viewportEditorModeTracker)
{
return AZStd::make_unique<EditorPickEntitySelection>(entityDataCache, viewportEditorModeTracker);
});

@ -90,6 +90,61 @@ namespace UnitTest
return AZStd::string(keyText.toUtf8().data());
}
bool ViewportSettingsTestImpl::GridSnappingEnabled() const
{
return m_gridSnapping;
}
float ViewportSettingsTestImpl::GridSize() const
{
return m_gridSize;
}
bool ViewportSettingsTestImpl::ShowGrid() const
{
return false;
}
bool ViewportSettingsTestImpl::AngleSnappingEnabled() const
{
return m_angularSnapping;
}
float ViewportSettingsTestImpl::AngleStep() const
{
return m_angularStep;
}
float ViewportSettingsTestImpl::ManipulatorLineBoundWidth() const
{
return 0.1f;
}
float ViewportSettingsTestImpl::ManipulatorCircleBoundWidth() const
{
return 0.1f;
}
bool ViewportSettingsTestImpl::StickySelectEnabled() const
{
return m_stickySelect;
}
bool ViewportSettingsTestImpl::IconsVisible() const
{
return m_iconsVisible;
}
bool ViewportSettingsTestImpl::HelpersVisible() const
{
return m_helpersVisible;
}
AZ::Vector3 ViewportSettingsTestImpl::DefaultEditorCameraPosition() const
{
return {};
}
bool TestWidget::eventFilter(QObject* watched, QEvent* event)
{
AZ_UNUSED(watched);

@ -91,6 +91,43 @@ namespace UnitTest
/// @param modifiers Optional keyboard modifiers to include during the wheel events, defaults to Qt::NoModifier
AZStd::string QtKeyToAzString(Qt::Key key, Qt::KeyboardModifiers modifiers = Qt::NoModifier);
//! Test implementation of the ViewportSettingsRequestBus.
//! @note Can be used to customize viewport settings during test execution.
class ViewportSettingsTestImpl : public AzToolsFramework::ViewportInteraction::ViewportSettingsRequestBus::Handler
{
public:
void Connect(const AzFramework::ViewportId viewportId)
{
AzToolsFramework::ViewportInteraction::ViewportSettingsRequestBus::Handler::BusConnect(viewportId);
}
void Disconnect()
{
AzToolsFramework::ViewportInteraction::ViewportSettingsRequestBus::Handler::BusDisconnect();
}
// ViewportSettingsRequestBus overrides ...
bool GridSnappingEnabled() const override;
float GridSize() const override;
bool ShowGrid() const override;
bool AngleSnappingEnabled() const override;
float AngleStep() const override;
float ManipulatorLineBoundWidth() const override;
float ManipulatorCircleBoundWidth() const override;
bool StickySelectEnabled() const override;
AZ::Vector3 DefaultEditorCameraPosition() const override;
bool IconsVisible() const override;
bool HelpersVisible() const override;
float m_gridSize = 1.0f;
float m_angularStep = 0.0f;
bool m_gridSnapping = false;
bool m_angularSnapping = false;
bool m_stickySelect = true;
bool m_iconsVisible = true;
bool m_helpersVisible = true;
};
/// Test widget to store QActions generated by EditorTransformComponentSelection.
class TestWidget : public QWidget
{
@ -207,7 +244,7 @@ namespace UnitTest
m_editorActions.Connect();
const auto viewportHandlerBuilder =
[this](const AzToolsFramework::EditorVisibleEntityDataCache* entityDataCache,
[this](const AzToolsFramework::EditorVisibleEntityDataCacheInterface* entityDataCache,
[[maybe_unused]] AzToolsFramework::ViewportEditorModeTrackerInterface* viewportEditorModeTracker)
{
// create the default viewport (handles ComponentMode)

@ -0,0 +1,27 @@
/*
* 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
#include <AzToolsFramework/API/EditorViewportIconDisplayInterface.h>
#include <gmock/gmock.h>
namespace UnitTest
{
class MockEditorViewportIconDisplayInterface : public AZ::Interface<AzToolsFramework::EditorViewportIconDisplayInterface>::Registrar
{
public:
virtual ~MockEditorViewportIconDisplayInterface() = default;
//! AzToolsFramework::EditorViewportIconDisplayInterface overrides ...
MOCK_METHOD1(DrawIcon, void(const DrawParameters&));
MOCK_METHOD1(GetOrLoadIconForPath, IconId(AZStd::string_view path));
MOCK_METHOD1(GetIconLoadStatus, IconLoadStatus(IconId icon));
};
} // namespace UnitTest

@ -0,0 +1,37 @@
/*
* 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
#include <AzToolsFramework/ViewportSelection/EditorVisibleEntityDataCache.h>
#include <gmock/gmock.h>
namespace UnitTest
{
class MockEditorVisibleEntityDataCacheInterface : public AzToolsFramework::EditorVisibleEntityDataCacheInterface
{
using ComponentEntityAccentType = AzToolsFramework::Components::EditorSelectionAccentSystemComponent::ComponentEntityAccentType;
public:
virtual ~MockEditorVisibleEntityDataCacheInterface() = default;
// AzToolsFramework::EditorVisibleEntityDataCacheInterface overrides ...
MOCK_CONST_METHOD0(VisibleEntityDataCount, size_t());
MOCK_CONST_METHOD1(GetVisibleEntityPosition, AZ::Vector3(size_t));
MOCK_CONST_METHOD1(GetVisibleEntityTransform, const AZ::Transform&(size_t));
MOCK_CONST_METHOD1(GetVisibleEntityId, AZ::EntityId(size_t));
MOCK_CONST_METHOD1(GetVisibleEntityAccent, ComponentEntityAccentType(size_t));
MOCK_CONST_METHOD1(IsVisibleEntityLocked, bool(size_t));
MOCK_CONST_METHOD1(IsVisibleEntityVisible, bool(size_t));
MOCK_CONST_METHOD1(IsVisibleEntitySelected, bool(size_t));
MOCK_CONST_METHOD1(IsVisibleEntityIconHidden, bool(size_t));
MOCK_CONST_METHOD1(IsVisibleEntityIndividuallySelectableInViewport, bool(size_t));
MOCK_CONST_METHOD1(GetVisibleEntityIndexFromId, AZStd::optional<size_t>(AZ::EntityId entityId));
};
} // namespace UnitTest

@ -0,0 +1,29 @@
/*
* 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
#include <AzToolsFramework/FocusMode/FocusModeInterface.h>
#include <gmock/gmock.h>
namespace UnitTest
{
class MockFocusModeInterface : public AZ::Interface<AzToolsFramework::FocusModeInterface>::Registrar
{
public:
virtual ~MockFocusModeInterface() = default;
// AzToolsFramework::FocusModeInterface overrides ...
MOCK_METHOD1(SetFocusRoot, void(AZ::EntityId entityId));
MOCK_METHOD1(ClearFocusRoot, void(AzFramework::EntityContextId entityContextId));
MOCK_METHOD1(GetFocusRoot, AZ::EntityId(AzFramework::EntityContextId entityContextId));
MOCK_METHOD1(GetFocusedEntities, AzToolsFramework::EntityIdList(AzFramework::EntityContextId entityContextId));
MOCK_CONST_METHOD1(IsInFocusSubTree, bool(AZ::EntityId entityId));
};
} // namespace UnitTest

@ -21,9 +21,8 @@ namespace AzToolsFramework
AZ_CLASS_ALLOCATOR_IMPL(EditorDefaultSelection, AZ::SystemAllocator, 0)
EditorDefaultSelection::EditorDefaultSelection(
const EditorVisibleEntityDataCache* entityDataCache, ViewportEditorModeTrackerInterface* viewportEditorModeTracker)
const EditorVisibleEntityDataCacheInterface* entityDataCache, ViewportEditorModeTrackerInterface* viewportEditorModeTracker)
: m_phantomWidget(nullptr)
, m_entityDataCache(entityDataCache)
, m_viewportEditorModeTracker(viewportEditorModeTracker)
, m_componentModeCollection(viewportEditorModeTracker)
{

@ -27,7 +27,8 @@ namespace AzToolsFramework
AZ_CLASS_ALLOCATOR_DECL
//! @cond
EditorDefaultSelection(const EditorVisibleEntityDataCache* entityDataCache, ViewportEditorModeTrackerInterface* viewportEditorModeTracker);
EditorDefaultSelection(
const EditorVisibleEntityDataCacheInterface* entityDataCache, ViewportEditorModeTrackerInterface* viewportEditorModeTracker);
EditorDefaultSelection(const EditorDefaultSelection&) = delete;
EditorDefaultSelection& operator=(const EditorDefaultSelection&) = delete;
virtual ~EditorDefaultSelection();
@ -85,10 +86,8 @@ namespace AzToolsFramework
QWidget m_phantomWidget; //!< The phantom widget responsible for holding QActions while in ComponentMode.
QWidget* m_phantomOverrideWidget = nullptr; //!< It's possible to override the phantom widget in special circumstances (eg testing).
ComponentModeFramework::ComponentModeCollection m_componentModeCollection; //!< Handles all active ComponentMode types.
AZStd::unique_ptr<EditorTransformComponentSelection> m_transformComponentSelection =
nullptr; //!< Viewport selection (responsible for
//!< manipulators and transform modifications).
const EditorVisibleEntityDataCache* m_entityDataCache = nullptr; //!< Reference to cached visible EntityData.
//! Viewport selection (responsible for manipulators and transform modifications).
AZStd::unique_ptr<EditorTransformComponentSelection> m_transformComponentSelection = nullptr;
//! Mapping between passed ActionOverride (AddActionOverride) and allocated QAction.
struct ActionOverrideMapping
@ -112,7 +111,7 @@ namespace AzToolsFramework
AZStd::shared_ptr<AzToolsFramework::ManipulatorManager> m_manipulatorManager; //!< The default manipulator manager.
ViewportInteraction::MouseInteraction m_currentInteraction; //!< Current mouse interaction to be used for drawing manipulators.
ViewportEditorModeTrackerInterface* m_viewportEditorModeTracker = nullptr; //!< Tracker for activating/deactivating viewport editor modes.
//! Tracker for activating/deactivating viewport editor modes.
ViewportEditorModeTrackerInterface* m_viewportEditorModeTracker = nullptr;
};
} // namespace AzToolsFramework

@ -159,7 +159,7 @@ namespace AzToolsFramework
return false;
}
EditorHelpers::EditorHelpers(const EditorVisibleEntityDataCache* entityDataCache)
EditorHelpers::EditorHelpers(const EditorVisibleEntityDataCacheInterface* entityDataCache)
: m_entityDataCache(entityDataCache)
{
m_focusModeInterface = AZ::Interface<FocusModeInterface>::Get();
@ -344,9 +344,19 @@ namespace AzToolsFramework
continue;
}
int iconTextureId = 0;
EditorEntityIconComponentRequestBus::EventResult(
iconTextureId, entityId, &EditorEntityIconComponentRequests::GetEntityIconTextureId);
const AZ::Vector3& entityPosition = m_entityDataCache->GetVisibleEntityPosition(entityCacheIndex);
const AZ::Vector3 entityCameraVector = entityPosition - cameraState.m_position;
if (const float directionFromCamera = entityCameraVector.Dot(cameraState.m_forward); directionFromCamera < 0.0f)
{
continue;
}
const float distanceFromCamera = entityCameraVector.GetLength();
if (distanceFromCamera < cameraState.m_nearClip)
{
continue;
}
using ComponentEntityAccentType = Components::EditorSelectionAccentSystemComponent::ComponentEntityAccentType;
const AZ::Color iconHighlight = [this, entityCacheIndex]()
@ -364,13 +374,13 @@ namespace AzToolsFramework
return AZ::Color(1.0f, 1.0f, 1.0f, 1.0f);
}();
const AZ::Vector3& entityPosition = m_entityDataCache->GetVisibleEntityPosition(entityCacheIndex);
const float distanceFromCamera = cameraState.m_position.GetDistance(entityPosition);
const float iconSize = GetIconSize(distanceFromCamera);
int iconTextureId = 0;
EditorEntityIconComponentRequestBus::EventResult(
iconTextureId, entityId, &EditorEntityIconComponentRequestBus::Events::GetEntityIconTextureId);
editorViewportIconDisplay->DrawIcon({ viewportInfo.m_viewportId, iconTextureId, iconHighlight, entityPosition,
EditorViewportIconDisplayInterface::CoordinateSpace::WorldSpace,
AZ::Vector2{ iconSize, iconSize } });
editorViewportIconDisplay->DrawIcon(EditorViewportIconDisplayInterface::DrawParameters{
viewportInfo.m_viewportId, iconTextureId, iconHighlight, entityPosition,
EditorViewportIconDisplayInterface::CoordinateSpace::WorldSpace, AZ::Vector2(GetIconSize(distanceFromCamera)) });
}
}
}

@ -24,7 +24,7 @@ namespace AzFramework
namespace AzToolsFramework
{
class EditorVisibleEntityDataCache;
class EditorVisibleEntityDataCacheInterface;
class FocusModeInterface;
namespace ViewportInteraction
@ -64,7 +64,7 @@ namespace AzToolsFramework
//! An EditorVisibleEntityDataCache must be passed to EditorHelpers to allow it to
//! efficiently read entity data without resorting to EBus calls.
explicit EditorHelpers(const EditorVisibleEntityDataCache* entityDataCache);
explicit EditorHelpers(const EditorVisibleEntityDataCacheInterface* entityDataCache);
EditorHelpers(const EditorHelpers&) = delete;
EditorHelpers& operator=(const EditorHelpers&) = delete;
~EditorHelpers() = default;
@ -103,7 +103,7 @@ namespace AzToolsFramework
AZStd::unique_ptr<InvalidClicks> m_invalidClicks; //!< Display for invalid click behavior.
const EditorVisibleEntityDataCache* m_entityDataCache = nullptr; //!< Entity Data queried by the EditorHelpers.
const EditorVisibleEntityDataCacheInterface* m_entityDataCache = nullptr; //!< Entity Data queried by the EditorHelpers.
const FocusModeInterface* m_focusModeInterface = nullptr; //!< API to interact with focus mode functionality.
};

@ -84,7 +84,7 @@ namespace AzToolsFramework
void EditorInteractionSystemComponent::SetDefaultHandler()
{
SetHandler(
[](const EditorVisibleEntityDataCache* entityDataCache, ViewportEditorModeTrackerInterface* viewportEditorModeTracker)
[](const EditorVisibleEntityDataCacheInterface* entityDataCache, ViewportEditorModeTrackerInterface* viewportEditorModeTracker)
{
return AZStd::make_unique<EditorDefaultSelection>(entityDataCache, viewportEditorModeTracker);
});

@ -16,7 +16,7 @@
namespace AzToolsFramework
{
class EditorVisibleEntityDataCache;
class EditorVisibleEntityDataCacheInterface;
class ViewportEditorModeTrackerInterface;
//! Bus to handle all mouse events originating from the viewport.
@ -34,7 +34,7 @@ namespace AzToolsFramework
//! Alias for factory function to create a new type implementing the ViewportSelectionRequests interface.
using ViewportSelectionRequestsBuilderFn = AZStd::function<AZStd::unique_ptr<ViewportInteraction::InternalViewportSelectionRequests>(
const EditorVisibleEntityDataCache*, ViewportEditorModeTrackerInterface*)>;
const EditorVisibleEntityDataCacheInterface*, ViewportEditorModeTrackerInterface*)>;
//! Interface for system component implementing the ViewportSelectionRequests interface.
//! This interface also includes a setter to set a custom handler also implementing

@ -17,7 +17,7 @@ namespace AzToolsFramework
AZ_CLASS_ALLOCATOR_IMPL(EditorPickEntitySelection, AZ::SystemAllocator, 0)
EditorPickEntitySelection::EditorPickEntitySelection(
const EditorVisibleEntityDataCache* entityDataCache, ViewportEditorModeTrackerInterface* viewportEditorModeTracker)
const EditorVisibleEntityDataCacheInterface* entityDataCache, ViewportEditorModeTrackerInterface* viewportEditorModeTracker)
: m_editorHelpers(AZStd::make_unique<EditorHelpers>(entityDataCache))
, m_viewportEditorModeTracker(viewportEditorModeTracker)
{

@ -13,6 +13,7 @@
namespace AzToolsFramework
{
class EditorVisibleEntityDataCacheInterface;
class ViewportEditorModeTrackerInterface;
//! Viewport interaction that will handle assigning an entity in the viewport to
@ -23,7 +24,7 @@ namespace AzToolsFramework
AZ_CLASS_ALLOCATOR_DECL
EditorPickEntitySelection(
const EditorVisibleEntityDataCache* entityDataCache, ViewportEditorModeTrackerInterface* viewportEditorModeTracker);
const EditorVisibleEntityDataCacheInterface* entityDataCache, ViewportEditorModeTrackerInterface* viewportEditorModeTracker);
~EditorPickEntitySelection();
private:
@ -35,6 +36,7 @@ namespace AzToolsFramework
AZStd::unique_ptr<EditorHelpers> m_editorHelpers; //!< Editor visualization of entities (icons, shapes, debug visuals etc).
AZ::EntityId m_hoveredEntityId; //!< What EntityId is the mouse currently hovering over (if any).
AZ::EntityId m_cachedEntityIdUnderCursor; //!< Store the EntityId on each mouse move for use in Display.
ViewportEditorModeTrackerInterface* m_viewportEditorModeTracker = nullptr; //!< Tracker for activating/deactivating viewport editor modes.
//! Tracker for activating/deactivating viewport editor modes.
ViewportEditorModeTrackerInterface* m_viewportEditorModeTracker = nullptr;
};
} // namespace AzToolsFramework

@ -381,7 +381,7 @@ namespace AzToolsFramework
EntityIdContainer& selectedEntityIdsBeforeBoxSelect,
EntityIdContainer& potentialSelectedEntityIds,
EntityIdContainer& potentialDeselectedEntityIds,
const EditorVisibleEntityDataCache& entityDataCache,
const EditorVisibleEntityDataCacheInterface& entityDataCache,
const int viewportId,
const ViewportInteraction::KeyboardModifiers currentKeyboardModifiers,
const ViewportInteraction::KeyboardModifiers& previousKeyboardModifiers)
@ -958,7 +958,7 @@ namespace AzToolsFramework
// (useful in the context of drawing when we only care about entities we can see)
// note: return the index if it is selectable, nullopt otherwise
static AZStd::optional<size_t> SelectableInVisibleViewportCache(
const EditorVisibleEntityDataCache& entityDataCache, const AZ::EntityId entityId)
const EditorVisibleEntityDataCacheInterface& entityDataCache, const AZ::EntityId entityId)
{
if (auto entityIndex = entityDataCache.GetVisibleEntityIndexFromId(entityId))
{
@ -1002,7 +1002,7 @@ namespace AzToolsFramework
}
}
EditorTransformComponentSelection::EditorTransformComponentSelection(const EditorVisibleEntityDataCache* entityDataCache)
EditorTransformComponentSelection::EditorTransformComponentSelection(const EditorVisibleEntityDataCacheInterface* entityDataCache)
: m_entityDataCache(entityDataCache)
{
const AzFramework::EntityContextId entityContextId = GetEntityContextId();

@ -34,7 +34,7 @@
namespace AzToolsFramework
{
class EditorVisibleEntityDataCache;
class EditorVisibleEntityDataCacheInterface;
using EntityIdSet = AZStd::unordered_set<AZ::EntityId>; //!< Alias for unordered_set of EntityIds.
@ -167,7 +167,7 @@ namespace AzToolsFramework
AZ_CLASS_ALLOCATOR_DECL
EditorTransformComponentSelection() = default;
explicit EditorTransformComponentSelection(const EditorVisibleEntityDataCache* entityDataCache);
explicit EditorTransformComponentSelection(const EditorVisibleEntityDataCacheInterface* entityDataCache);
EditorTransformComponentSelection(const EditorTransformComponentSelection&) = delete;
EditorTransformComponentSelection& operator=(const EditorTransformComponentSelection&) = delete;
virtual ~EditorTransformComponentSelection();
@ -325,10 +325,8 @@ namespace AzToolsFramework
AZ::EntityId m_currentEntityIdUnderCursor; //!< Store the EntityId on each mouse move for use in Display.
AZ::EntityId m_editorCameraComponentEntityId; //!< The EditorCameraComponent EntityId if it is set.
EntityIdSet m_selectedEntityIds; //!< Represents the current entities in the selection.
const EditorVisibleEntityDataCache* m_entityDataCache = nullptr; //!< A cache of packed EntityData that can be
//!< iterated over efficiently without the need
//!< to make individual EBus calls.
//! A cache of packed EntityData that can be iterated over efficiently without the need to make individual EBus calls.
const EditorVisibleEntityDataCacheInterface* m_entityDataCache = nullptr;
AZStd::unique_ptr<EditorHelpers> m_editorHelpers; //!< Editor visualization of entities (icons, shapes, debug visuals etc).
EntityIdManipulators m_entityIdManipulators; //!< Mapping from a Manipulator to potentially many EntityIds.

@ -20,10 +20,36 @@
namespace AzToolsFramework
{
//! Read-only interface for EditorVisibleEntityDataCache to be used by systems that want to efficiently
//! query the state of visible entities in the viewport.
class EditorVisibleEntityDataCacheInterface
{
using ComponentEntityAccentType = Components::EditorSelectionAccentSystemComponent::ComponentEntityAccentType;
public:
virtual ~EditorVisibleEntityDataCacheInterface() = default;
virtual size_t VisibleEntityDataCount() const = 0;
virtual AZ::Vector3 GetVisibleEntityPosition(size_t index) const = 0;
virtual const AZ::Transform& GetVisibleEntityTransform(size_t index) const = 0;
virtual AZ::EntityId GetVisibleEntityId(size_t index) const = 0;
virtual ComponentEntityAccentType GetVisibleEntityAccent(size_t index) const = 0;
virtual bool IsVisibleEntityLocked(size_t index) const = 0;
virtual bool IsVisibleEntityVisible(size_t index) const = 0;
virtual bool IsVisibleEntitySelected(size_t index) const = 0;
virtual bool IsVisibleEntityIconHidden(size_t index) const = 0;
//! Returns true if the entity is individually selectable (none of its ancestors are a closed container entity).
//! @note It may still be desirable to be able to 'click' an entity that is a descendant of a closed container
//! to select the container itself, not the individual entity.
virtual bool IsVisibleEntityIndividuallySelectableInViewport(size_t index) const = 0;
virtual AZStd::optional<size_t> GetVisibleEntityIndexFromId(AZ::EntityId entityId) const = 0;
};
//! A cache of packed EntityData that can be iterated over efficiently without
//! the need to make individual EBus calls
class EditorVisibleEntityDataCache
: private EditorEntityVisibilityNotificationBus::Router
: public EditorVisibleEntityDataCacheInterface
, private EditorEntityVisibilityNotificationBus::Router
, private EditorEntityLockComponentNotificationBus::Router
, private AZ::TransformNotificationBus::Router
, private EditorComponentSelectionNotificationsBus::Router
@ -45,22 +71,18 @@ namespace AzToolsFramework
void CalculateVisibleEntityDatas(const AzFramework::ViewportInfo& viewportInfo);
//! EditorVisibleEntityDataCache interface
size_t VisibleEntityDataCount() const;
AZ::Vector3 GetVisibleEntityPosition(size_t index) const;
const AZ::Transform& GetVisibleEntityTransform(size_t index) const;
AZ::EntityId GetVisibleEntityId(size_t index) const;
ComponentEntityAccentType GetVisibleEntityAccent(size_t index) const;
bool IsVisibleEntityLocked(size_t index) const;
bool IsVisibleEntityVisible(size_t index) const;
bool IsVisibleEntitySelected(size_t index) const;
bool IsVisibleEntityIconHidden(size_t index) const;
//! Returns true if the entity is individually selectable (none of its ancestors are a closed container entity).
//! @note It may still be desirable to be able to 'click' an entity that is a descendant of a closed container
//! to select the container itself, not the individual entity.
bool IsVisibleEntityIndividuallySelectableInViewport(size_t index) const;
AZStd::optional<size_t> GetVisibleEntityIndexFromId(AZ::EntityId entityId) const;
//! EditorVisibleEntityDataCacheInterface overrides ...
size_t VisibleEntityDataCount() const override;
AZ::Vector3 GetVisibleEntityPosition(size_t index) const override;
const AZ::Transform& GetVisibleEntityTransform(size_t index) const override;
AZ::EntityId GetVisibleEntityId(size_t index) const override;
ComponentEntityAccentType GetVisibleEntityAccent(size_t index) const override;
bool IsVisibleEntityLocked(size_t index) const override;
bool IsVisibleEntityVisible(size_t index) const override;
bool IsVisibleEntitySelected(size_t index) const override;
bool IsVisibleEntityIconHidden(size_t index) const override;
bool IsVisibleEntityIndividuallySelectableInViewport(size_t index) const override;
AZStd::optional<size_t> GetVisibleEntityIndexFromId(AZ::EntityId entityId) const override;
void AddEntityIds(const EntityIdList& entityIds);

@ -9,6 +9,9 @@
set(FILES
UnitTest/AzToolsFrameworkTestHelpers.cpp
UnitTest/AzToolsFrameworkTestHelpers.h
UnitTest/Mocks/MockFocusModeInterface.h
UnitTest/Mocks/MockEditorVisibleEntityDataCacheInterface.h
UnitTest/Mocks/MockEditorViewportIconDisplayInterface.h
UnitTest/ToolsTestApplication.cpp
UnitTest/ToolsTestApplication.h
)

@ -265,7 +265,7 @@ namespace UnitTest
using AzToolsFramework::EditorInteractionSystemViewportSelectionRequestBus;
EditorInteractionSystemViewportSelectionRequestBus::Event(
AzToolsFramework::GetEntityContextId(), &EditorInteractionSystemViewportSelectionRequestBus::Events::SetHandler,
[](const AzToolsFramework::EditorVisibleEntityDataCache* entityDataCache,
[](const AzToolsFramework::EditorVisibleEntityDataCacheInterface* entityDataCache,
[[maybe_unused]] AzToolsFramework::ViewportEditorModeTrackerInterface* viewportEditorModeTracker)
{
return AZStd::make_unique<AzToolsFramework::EditorPickEntitySelection>(entityDataCache, viewportEditorModeTracker);

@ -0,0 +1,116 @@
/*
* 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 <AzCore/UnitTest/TestTypes.h>
#include <AzFramework/UnitTest/TestDebugDisplayRequests.h>
#include <AzFramework/Viewport/CameraState.h>
#include <AzTest/AzTest.h>
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
#include <AzToolsFramework/UnitTest/Mocks/MockEditorViewportIconDisplayInterface.h>
#include <AzToolsFramework/UnitTest/Mocks/MockEditorVisibleEntityDataCacheInterface.h>
#include <AzToolsFramework/UnitTest/Mocks/MockFocusModeInterface.h>
#include <AzToolsFramework/ViewportSelection/EditorHelpers.h>
#include <AzToolsFramework/ViewportSelection/EditorVisibleEntityDataCache.h>
namespace UnitTest
{
class EditorViewportIconFixture : public AllocatorsTestFixture
{
public:
inline static constexpr AzFramework::ViewportId TestViewportId = 2468;
void SetUp() override
{
AllocatorsTestFixture::SetUp();
m_focusModeMock = AZStd::make_unique<::testing::NiceMock<MockFocusModeInterface>>();
m_editorViewportIconDisplayMock = AZStd::make_unique<::testing::NiceMock<MockEditorViewportIconDisplayInterface>>();
m_entityVisibleEntityDataCacheMock = AZStd::make_unique<::testing::NiceMock<MockEditorVisibleEntityDataCacheInterface>>();
m_editorHelpers = AZStd::make_unique<AzToolsFramework::EditorHelpers>(m_entityVisibleEntityDataCacheMock.get());
m_viewportSettings = AZStd::make_unique<ViewportSettingsTestImpl>();
m_viewportSettings->Connect(TestViewportId);
m_viewportSettings->m_helpersVisible = false;
m_viewportSettings->m_iconsVisible = true;
m_cameraState = AzFramework::CreateDefaultCamera(AZ::Transform::CreateIdentity(), AZ::Vector2(1024.0f, 768.0f));
using ::testing::_;
using ::testing::Return;
ON_CALL(*m_entityVisibleEntityDataCacheMock, VisibleEntityDataCount()).WillByDefault(Return(1));
ON_CALL(*m_entityVisibleEntityDataCacheMock, GetVisibleEntityId(_)).WillByDefault(Return(AZ::EntityId()));
ON_CALL(*m_entityVisibleEntityDataCacheMock, IsVisibleEntityIconHidden(_)).WillByDefault(Return(false));
ON_CALL(*m_entityVisibleEntityDataCacheMock, IsVisibleEntityVisible(_)).WillByDefault(Return(true));
ON_CALL(*m_focusModeMock, IsInFocusSubTree(_)).WillByDefault(Return(true));
}
void TearDown() override
{
m_viewportSettings->Disconnect();
m_viewportSettings.reset();
m_editorHelpers.reset();
m_entityVisibleEntityDataCacheMock.reset();
m_editorViewportIconDisplayMock.reset();
m_focusModeMock.reset();
AllocatorsTestFixture::TearDown();
}
AZStd::unique_ptr<ViewportSettingsTestImpl> m_viewportSettings;
AZStd::unique_ptr<AzToolsFramework::EditorHelpers> m_editorHelpers;
AZStd::unique_ptr<::testing::NiceMock<MockFocusModeInterface>> m_focusModeMock;
AZStd::unique_ptr<::testing::NiceMock<MockEditorVisibleEntityDataCacheInterface>> m_entityVisibleEntityDataCacheMock;
AZStd::unique_ptr<::testing::NiceMock<MockEditorViewportIconDisplayInterface>> m_editorViewportIconDisplayMock;
AzFramework::CameraState m_cameraState;
};
TEST_F(EditorViewportIconFixture, ViewportIconsAreNotDisplayedWhenInBetweenCameraAndNearClipPlane)
{
NullDebugDisplayRequests nullDebugDisplayRequests;
const auto insideNearClip = m_cameraState.m_nearClip * 0.5f;
using ::testing::_;
using ::testing::Return;
// given
// entity position (where icon will be drawn) is in between near clip plane and camera position
ON_CALL(*m_entityVisibleEntityDataCacheMock, GetVisibleEntityPosition(_))
.WillByDefault(Return(AZ::Vector3(0.0f, insideNearClip, 0.0f)));
EXPECT_CALL(*m_editorViewportIconDisplayMock, DrawIcon(_)).Times(0);
// when
m_editorHelpers->DisplayHelpers(
AzFramework::ViewportInfo{ TestViewportId }, m_cameraState, nullDebugDisplayRequests,
[](AZ::EntityId)
{
return true;
});
}
TEST_F(EditorViewportIconFixture, ViewportIconsAreNotDisplayedWhenBehindCamera)
{
NullDebugDisplayRequests nullDebugDisplayRequests;
using ::testing::_;
using ::testing::Return;
// given
// entity position (where icon will be drawn) behind the camera position
ON_CALL(*m_entityVisibleEntityDataCacheMock, GetVisibleEntityPosition(_)).WillByDefault(Return(AZ::Vector3(0.0f, -1.0f, 0.0f)));
EXPECT_CALL(*m_editorViewportIconDisplayMock, DrawIcon(_)).Times(0);
// when
m_editorHelpers->DisplayHelpers(
AzFramework::ViewportInfo{ TestViewportId }, m_cameraState, nullDebugDisplayRequests,
[](AZ::EntityId)
{
return true;
});
}
} // namespace UnitTest

@ -573,7 +573,7 @@ namespace UnitTest
using AzToolsFramework::EditorInteractionSystemViewportSelectionRequestBus;
EditorInteractionSystemViewportSelectionRequestBus::Event(
AzToolsFramework::GetEntityContextId(), &EditorInteractionSystemViewportSelectionRequestBus::Events::SetHandler,
[](const AzToolsFramework::EditorVisibleEntityDataCache* entityDataCache,
[](const AzToolsFramework::EditorVisibleEntityDataCacheInterface* entityDataCache,
[[maybe_unused]] AzToolsFramework::ViewportEditorModeTrackerInterface* viewportEditorModeTracker)
{
return AZStd::make_unique<AzToolsFramework::EditorPickEntitySelection>(entityDataCache, viewportEditorModeTracker);
@ -591,7 +591,7 @@ namespace UnitTest
using AzToolsFramework::EditorInteractionSystemViewportSelectionRequestBus;
EditorInteractionSystemViewportSelectionRequestBus::Event(
AzToolsFramework::GetEntityContextId(), &EditorInteractionSystemViewportSelectionRequestBus::Events::SetHandler,
[](const AzToolsFramework::EditorVisibleEntityDataCache* entityDataCache,
[](const AzToolsFramework::EditorVisibleEntityDataCacheInterface* entityDataCache,
[[maybe_unused]] AzToolsFramework::ViewportEditorModeTrackerInterface* viewportEditorModeTracker)
{
return AZStd::make_unique<AzToolsFramework::EditorPickEntitySelection>(entityDataCache, viewportEditorModeTracker);
@ -599,7 +599,7 @@ namespace UnitTest
EditorInteractionSystemViewportSelectionRequestBus::Event(
AzToolsFramework::GetEntityContextId(), &EditorInteractionSystemViewportSelectionRequestBus::Events::SetHandler,
[](const AzToolsFramework::EditorVisibleEntityDataCache* entityDataCache,
[](const AzToolsFramework::EditorVisibleEntityDataCacheInterface* entityDataCache,
[[maybe_unused]] AzToolsFramework::ViewportEditorModeTrackerInterface* viewportEditorModeTracker)
{
return AZStd::make_unique<AzToolsFramework::EditorDefaultSelection>(entityDataCache, viewportEditorModeTracker);

@ -24,6 +24,7 @@ set(FILES
ComponentModeTests.cpp
EditorTransformComponentSelectionTests.cpp
EditorVertexSelectionTests.cpp
EditorViewportIconTests.cpp
Entity/EditorEntityContextComponentTests.cpp
Entity/EditorEntityHelpersTests.cpp
Entity/EditorEntitySearchComponentTests.cpp

@ -19,13 +19,15 @@
#define AZ_DebugSecureSocket(...)
#define AZ_DebugSecureSocketConnection(window, fmt, ...)
//#define AZ_DebugUseSocketDebugLog
//#define AZ_DebugSecureSocket AZ_TracePrintf
//#define AZ_DebugSecureSocketConnection(window, fmt, ...) \
//{\
// AZStd::string line = AZStd::string::format(fmt, __VA_ARGS__);\
// this->m_dbgLog += line;\
//}
/*
#define AZ_DebugUseSocketDebugLog
#define AZ_DebugSecureSocket AZ_TracePrintf
#define AZ_DebugSecureSocketConnection(window, fmt, ...) \
{\
AZStd::string line = AZStd::string::format(fmt, __VA_ARGS__);\
this->m_dbgLog += line;\
}
*/
#if AZ_TRAIT_GRIDMATE_SECURE_SOCKET_DRIVER_HOOK_ENABLED
struct ssl_st;

@ -41,7 +41,7 @@ bool CLevelInfo::OpenLevelPak()
{
bool usePrefabSystemForLevels = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemForLevelsEnabled);
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
// The prefab system doesn't use level.pak
if (usePrefabSystemForLevels)
@ -62,7 +62,7 @@ void CLevelInfo::CloseLevelPak()
{
bool usePrefabSystemForLevels = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemForLevelsEnabled);
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
// The prefab system doesn't use level.pak
if (usePrefabSystemForLevels)
@ -82,7 +82,7 @@ bool CLevelInfo::ReadInfo()
{
bool usePrefabSystemForLevels = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemForLevelsEnabled);
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
// Set up a default game type for legacy code.
m_defaultGameTypeName = "mission0";

@ -1046,7 +1046,7 @@ AZ_POP_DISABLE_WARNING
// LEVEL SYSTEM
bool usePrefabSystemForLevels = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemForLevelsEnabled);
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
if (usePrefabSystemForLevels)
{

@ -30,6 +30,8 @@ set(FILES
native/tests/assetBuilderSDK/SerializationDependenciesTests.cpp
native/tests/assetmanager/AssetProcessorManagerTest.cpp
native/tests/assetmanager/AssetProcessorManagerTest.h
native/tests/assetmanager/ModtimeScanningTests.cpp
native/tests/assetmanager/ModtimeScanningTests.h
native/tests/utilities/assetUtilsTest.cpp
native/tests/platformconfiguration/platformconfigurationtests.cpp
native/tests/platformconfiguration/platformconfigurationtests.h

@ -49,7 +49,7 @@ namespace UnitTests
}
struct PathDependencyBase
: UnitTest::TraceBusRedirector
: ::UnitTest::TraceBusRedirector
{
void Init();
void Destroy();
@ -65,7 +65,7 @@ namespace UnitTests
};
struct PathDependencyDeletionTest
: UnitTest::ScopedAllocatorSetupFixture
: ::UnitTest::ScopedAllocatorSetupFixture
, PathDependencyBase
{
void SetUp() override
@ -357,7 +357,7 @@ namespace UnitTests
}
struct PathDependencyBenchmarks
: UnitTest::ScopedAllocatorFixture
: ::UnitTest::ScopedAllocatorFixture
, PathDependencyBase
{
static inline constexpr int NumTestDependencies = 4; // Must be a multiple of 4
@ -530,7 +530,7 @@ namespace UnitTests
BENCHMARK_F(PathDependencyBenchmarksWrapperClass, BM_DeferredWildcardDependencyResolution)(benchmark::State& state)
{
for (auto _ : state)
for ([[maybe_unused]] auto unused : state)
{
m_benchmarks->m_stateData->SetProductDependencies(m_benchmarks->m_dependencies);

@ -191,7 +191,7 @@ namespace UnitTests
m_data->m_perforceComponent = AZStd::make_unique<MockPerforceComponent>();
m_data->m_perforceComponent->Activate();
m_data->m_perforceComponent->SetConnection(new UnitTest::MockPerforceConnection(m_command));
m_data->m_perforceComponent->SetConnection(new ::UnitTest::MockPerforceConnection(m_command));
}
void TearDown() override

@ -22,108 +22,6 @@
using namespace AssetProcessor;
class AssetProcessorManager_Test
: public AssetProcessorManager
{
public:
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, AssetProcessedImpl_DifferentProductDependenciesPerProduct_SavesCorrectlyToDatabase);
friend class GTEST_TEST_CLASS_NAME_(MultiplatformPathDependencyTest, AssetProcessed_Impl_MultiplatformDependencies);
friend class GTEST_TEST_CLASS_NAME_(MultiplatformPathDependencyTest, AssetProcessed_Impl_MultiplatformDependencies_DeferredResolution);
friend class GTEST_TEST_CLASS_NAME_(MultiplatformPathDependencyTest, SameFilenameForAllPlatforms);
friend class GTEST_TEST_CLASS_NAME_(MultiplatformPathDependencyTest, AssetProcessed_Impl_MultiplatformDependencies_SourcePath);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, DeleteFolder_SignalsDeleteOfContainedFiles);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, QueryAbsolutePathDependenciesRecursive_BasicTest);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, QueryAbsolutePathDependenciesRecursive_WithDifferentTypes_BasicTest);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, QueryAbsolutePathDependenciesRecursive_Reverse_BasicTest);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, QueryAbsolutePathDependenciesRecursive_MissingFiles_ReturnsNoPathWithPlaceholders);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_BeforeComputingDirtiness_AllDirty);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_EmptyDatabase_AllDirty);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_SameAsLastTime_NoneDirty);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_MoreThanLastTime_NewOneIsDirty);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_FewerThanLastTime_Dirty);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_ChangedPattern_CountsAsNew);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_ChangedPatternType_CountsAsNew);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_NewPattern_CountsAsNewBuilder);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_NewVersionNumber_IsNotANewBuilder);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_NewAnalysisFingerprint_IsNotANewBuilder);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_BasicTest);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_UpdateTest);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_MissingFiles_ByUuid);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_MissingFiles_ByName);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_MissingFiles_ByUuid_UpdatesWhenTheyAppear);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_MissingFiles_ByName_UpdatesWhenTheyAppear);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_WildcardMissingFiles_ByName_UpdatesWhenTheyAppear);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, JobDependencyOrderOnce_MultipleJobs_EmitOK);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, SourceFileProcessFailure_ClearsFingerprint);
friend class GTEST_TEST_CLASS_NAME_(AbsolutePathProductDependencyTest, UnresolvedProductPathDependency_AssetProcessedTwice_DoesNotDuplicateDependency);
friend class GTEST_TEST_CLASS_NAME_(AbsolutePathProductDependencyTest, AbsolutePathProductDependency_RetryDeferredDependenciesWithMatchingSource_DependencyResolves);
friend class GTEST_TEST_CLASS_NAME_(AbsolutePathProductDependencyTest, UnresolvedProductPathDependency_AssetProcessedTwice_ValidatePathDependenciesMap);
friend class GTEST_TEST_CLASS_NAME_(AbsolutePathProductDependencyTest, UnresolvedSourceFileTypeProductPathDependency_DependencyHasNoProductOutput_ValidatePathDependenciesMap);
friend class GTEST_TEST_CLASS_NAME_(ModtimeScanningTest, ModtimeSkipping_FileUnchanged_WithoutModtimeSkipping);
friend class GTEST_TEST_CLASS_NAME_(ModtimeScanningTest, ModtimeSkipping_FileUnchanged);
friend class GTEST_TEST_CLASS_NAME_(ModtimeScanningTest, ModtimeSkipping_EnablePlatform_ShouldProcessFilesForPlatform);
friend class GTEST_TEST_CLASS_NAME_(ModtimeScanningTest, ModtimeSkipping_ModifyFile);
friend class GTEST_TEST_CLASS_NAME_(ModtimeScanningTest, ModtimeSkipping_ModifyFile_AndThenRevert_ProcessesAgain);
friend class GTEST_TEST_CLASS_NAME_(ModtimeScanningTest, ModtimeSkipping_ModifyFilesSameHash_BothProcess);
friend class GTEST_TEST_CLASS_NAME_(ModtimeScanningTest, ModtimeSkipping_ModifyTimestamp);
friend class GTEST_TEST_CLASS_NAME_(ModtimeScanningTest, ModtimeSkipping_ModifyTimestampNoHashing_ProcessesFile);
friend class GTEST_TEST_CLASS_NAME_(ModtimeScanningTest, ModtimeSkipping_ModifyMetadataFile);
friend class GTEST_TEST_CLASS_NAME_(ModtimeScanningTest, ModtimeSkipping_DeleteFile);
friend class GTEST_TEST_CLASS_NAME_(DeleteTest, DeleteFolderSharedAcrossTwoScanFolders_CorrectFileAndFolderAreDeletedFromCache);
friend class GTEST_TEST_CLASS_NAME_(MetadataFileTest, MetadataFile_SourceFileExtensionDifferentCase);
friend class AssetProcessorManagerTest;
friend struct ModtimeScanningTest;
friend struct JobDependencyTest;
friend struct ChainJobDependencyTest;
friend struct DeleteTest;
friend struct PathDependencyTest;
friend struct DuplicateProductsTest;
friend struct DuplicateProcessTest;
friend struct AbsolutePathProductDependencyTest;
friend struct WildcardSourceDependencyTest;
explicit AssetProcessorManager_Test(PlatformConfiguration* config, QObject* parent = nullptr);
~AssetProcessorManager_Test() override;
bool CheckJobKeyToJobRunKeyMap(AZStd::string jobKey);
int CountDirtyBuilders() const
{
int numDirty = 0;
for (const auto& element : m_builderDataCache)
{
if (element.second.m_isDirty)
{
++numDirty;
}
}
return numDirty;
}
bool IsBuilderDirty(const AZ::Uuid& builderBusId) const
{
auto finder = m_builderDataCache.find(builderBusId);
if (finder == m_builderDataCache.end())
{
return true;
}
return finder->second.m_isDirty;
}
};
AssetProcessorManager_Test::AssetProcessorManager_Test(AssetProcessor::PlatformConfiguration* config, QObject* parent /*= 0*/)
:AssetProcessorManager(config, parent)
{
@ -3839,632 +3737,6 @@ TEST_F(AssetProcessorManagerTest, SourceFileProcessFailure_ClearsFingerprint)
ASSERT_EQ(source.m_analysisFingerprint, "");
}
void ModtimeScanningTest::SetUp()
{
AssetProcessorManagerTest::SetUp();
m_data = AZStd::make_unique<StaticData>();
// We don't want the mock application manager to provide builder descriptors, mockBuilderInfoHandler will provide our own
m_mockApplicationManager->BusDisconnect();
m_data->m_mockBuilderInfoHandler.m_builderDesc = m_data->m_mockBuilderInfoHandler.CreateBuilderDesc("test builder", "{DF09DDC0-FD22-43B6-9E22-22C8574A6E1E}", { AssetBuilderSDK::AssetBuilderPattern("*.txt", AssetBuilderSDK::AssetBuilderPattern::Wildcard) });
m_data->m_mockBuilderInfoHandler.BusConnect();
ASSERT_TRUE(m_mockApplicationManager->GetBuilderByID("txt files", m_data->m_builderTxtBuilder));
// Run this twice so the test builder doesn't get counted as a "new" builder and bypass the modtime skipping
m_assetProcessorManager->ComputeBuilderDirty();
m_assetProcessorManager->ComputeBuilderDirty();
auto assetConnection = QObject::connect(m_assetProcessorManager.get(), &AssetProcessorManager::AssetToProcess, [this](JobDetails details)
{
m_data->m_processResults.push_back(AZStd::move(details));
});
auto deletedConnection = QObject::connect(m_assetProcessorManager.get(), &AssetProcessorManager::SourceDeleted, [this](QString file)
{
m_data->m_deletedSources.push_back(file);
});
// Create the test file
const auto& scanFolder = m_config->GetScanFolderAt(0);
m_data->m_relativePathFromWatchFolder[0] = "modtimeTestFile.txt";
m_data->m_absolutePath.push_back(QDir(scanFolder.ScanPath()).absoluteFilePath(m_data->m_relativePathFromWatchFolder[0]));
m_data->m_relativePathFromWatchFolder[1] = "modtimeTestDependency.txt";
m_data->m_absolutePath.push_back(QDir(scanFolder.ScanPath()).absoluteFilePath(m_data->m_relativePathFromWatchFolder[1]));
m_data->m_relativePathFromWatchFolder[2] = "modtimeTestDependency.txt.assetinfo";
m_data->m_absolutePath.push_back(QDir(scanFolder.ScanPath()).absoluteFilePath(m_data->m_relativePathFromWatchFolder[2]));
for (const auto& path : m_data->m_absolutePath)
{
ASSERT_TRUE(UnitTestUtils::CreateDummyFile(path, ""));
}
m_data->m_mockBuilderInfoHandler.m_dependencyFilePath = m_data->m_absolutePath[1].toUtf8().data();
// Add file to database with no modtime
{
AssetDatabaseConnection connection;
ASSERT_TRUE(connection.OpenDatabase());
AzToolsFramework::AssetDatabase::FileDatabaseEntry fileEntry;
fileEntry.m_fileName = m_data->m_relativePathFromWatchFolder[0].toUtf8().data();
fileEntry.m_modTime = 0;
fileEntry.m_isFolder = false;
fileEntry.m_scanFolderPK = scanFolder.ScanFolderID();
bool entryAlreadyExists;
ASSERT_TRUE(connection.InsertFile(fileEntry, entryAlreadyExists));
ASSERT_FALSE(entryAlreadyExists);
fileEntry.m_fileID = AzToolsFramework::AssetDatabase::InvalidEntryId; // Reset the id so we make a new entry
fileEntry.m_fileName = m_data->m_relativePathFromWatchFolder[1].toUtf8().data();
ASSERT_TRUE(connection.InsertFile(fileEntry, entryAlreadyExists));
ASSERT_FALSE(entryAlreadyExists);
fileEntry.m_fileID = AzToolsFramework::AssetDatabase::InvalidEntryId; // Reset the id so we make a new entry
fileEntry.m_fileName = m_data->m_relativePathFromWatchFolder[2].toUtf8().data();
ASSERT_TRUE(connection.InsertFile(fileEntry, entryAlreadyExists));
ASSERT_FALSE(entryAlreadyExists);
}
QSet<AssetFileInfo> filePaths = BuildFileSet();
SimulateAssetScanner(filePaths);
ASSERT_TRUE(BlockUntilIdle(5000));
ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 2);
ASSERT_EQ(m_data->m_processResults.size(), 2);
ASSERT_EQ(m_data->m_deletedSources.size(), 0);
ProcessAssetJobs();
m_data->m_processResults.clear();
m_data->m_mockBuilderInfoHandler.m_createJobsCount = 0;
m_isIdling = false;
}
void ModtimeScanningTest::TearDown()
{
m_data = nullptr;
AssetProcessorManagerTest::TearDown();
}
void ModtimeScanningTest::ProcessAssetJobs()
{
m_data->m_productPaths.clear();
for (const auto& processResult : m_data->m_processResults)
{
auto file = QDir(processResult.m_destinationPath).absoluteFilePath(processResult.m_jobEntry.m_databaseSourceName.toLower() + ".arc1");
m_data->m_productPaths.emplace(
QDir(processResult.m_jobEntry.m_watchFolderPath)
.absoluteFilePath(processResult.m_jobEntry.m_databaseSourceName)
.toUtf8()
.constData(),
file);
// Create the file on disk
ASSERT_TRUE(UnitTestUtils::CreateDummyFile(file, "products."));
AssetBuilderSDK::ProcessJobResponse response;
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(file.toUtf8().constData(), AZ::Uuid::CreateNull(), 1));
QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, processResult.m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
}
ASSERT_TRUE(BlockUntilIdle(5000));
m_isIdling = false;
}
void ModtimeScanningTest::SimulateAssetScanner(QSet<AssetFileInfo> filePaths)
{
QMetaObject::invokeMethod(m_assetProcessorManager.get(), "OnAssetScannerStatusChange", Qt::QueuedConnection, Q_ARG(AssetProcessor::AssetScanningStatus, AssetProcessor::AssetScanningStatus::Started));
QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessFilesFromScanner", Qt::QueuedConnection, Q_ARG(QSet<AssetFileInfo>, filePaths));
QMetaObject::invokeMethod(m_assetProcessorManager.get(), "OnAssetScannerStatusChange", Qt::QueuedConnection, Q_ARG(AssetProcessor::AssetScanningStatus, AssetProcessor::AssetScanningStatus::Completed));
}
QSet<AssetFileInfo> ModtimeScanningTest::BuildFileSet()
{
QSet<AssetFileInfo> filePaths;
for (const auto& path : m_data->m_absolutePath)
{
QFileInfo fileInfo(path);
auto modtime = fileInfo.lastModified();
AZ::u64 fileSize = fileInfo.size();
filePaths.insert(AssetFileInfo(path, modtime, fileSize, m_config->GetScanFolderForFile(path), false));
}
return filePaths;
}
void ModtimeScanningTest::ExpectWork(int createJobs, int processJobs)
{
ASSERT_TRUE(BlockUntilIdle(5000));
EXPECT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, createJobs);
EXPECT_EQ(m_data->m_processResults.size(), processJobs);
EXPECT_FALSE(m_data->m_processResults[0].m_autoFail);
EXPECT_FALSE(m_data->m_processResults[1].m_autoFail);
EXPECT_EQ(m_data->m_deletedSources.size(), 0);
m_isIdling = false;
}
void ModtimeScanningTest::ExpectNoWork()
{
// Since there's no work to do, the idle event isn't going to trigger, just process events a couple times
for (int i = 0; i < 10; ++i)
{
QCoreApplication::processEvents(QEventLoop::AllEvents, 10);
}
ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 0);
ASSERT_EQ(m_data->m_processResults.size(), 0);
ASSERT_EQ(m_data->m_deletedSources.size(), 0);
m_isIdling = false;
}
void ModtimeScanningTest::SetFileContents(QString filePath, QString contents)
{
QFile file(filePath);
file.open(QIODevice::WriteOnly | QIODevice::Truncate);
file.write(contents.toUtf8().constData());
file.close();
}
TEST_F(ModtimeScanningTest, ModtimeSkipping_FileUnchanged_WithoutModtimeSkipping)
{
using namespace AzToolsFramework::AssetSystem;
// Make sure modtime skipping is disabled
// We're just going to do 1 quick sanity test to make sure the files are still processed when modtime skipping is turned off
m_assetProcessorManager->m_allowModtimeSkippingFeature = false;
QSet<AssetFileInfo> filePaths = BuildFileSet();
SimulateAssetScanner(filePaths);
// 2 create jobs but 0 process jobs because the file has already been processed before in SetUp
ExpectWork(2, 0);
}
TEST_F(ModtimeScanningTest, ModtimeSkipping_FileUnchanged)
{
using namespace AzToolsFramework::AssetSystem;
// Enable the features we're testing
m_assetProcessorManager->m_allowModtimeSkippingFeature = true;
AssetUtilities::SetUseFileHashOverride(true, true);
QSet<AssetFileInfo> filePaths = BuildFileSet();
SimulateAssetScanner(filePaths);
ExpectNoWork();
}
TEST_F(ModtimeScanningTest, ModtimeSkipping_EnablePlatform_ShouldProcessFilesForPlatform)
{
using namespace AzToolsFramework::AssetSystem;
// Enable the features we're testing
m_assetProcessorManager->m_allowModtimeSkippingFeature = true;
AssetUtilities::SetUseFileHashOverride(true, true);
// Enable android platform after the initial SetUp has already processed the files for pc
QDir tempPath(m_tempDir.path());
AssetBuilderSDK::PlatformInfo androidPlatform("android", { "host", "renderer" });
m_config->EnablePlatform(androidPlatform, true);
// There's no way to remove scanfolders and adding a new one after enabling the platform will cause the pc assets to build as well, which we don't want
// Instead we'll just const cast the vector and modify the enabled platforms for the scanfolder
auto& platforms = const_cast<AZStd::vector<AssetBuilderSDK::PlatformInfo>&>(m_config->GetScanFolderAt(0).GetPlatforms());
platforms.push_back(androidPlatform);
// We need the builder fingerprints to be updated to reflect the newly enabled platform
m_assetProcessorManager->ComputeBuilderDirty();
QSet<AssetFileInfo> filePaths = BuildFileSet();
SimulateAssetScanner(filePaths);
ExpectWork(4, 2); // CreateJobs = 4, 2 files * 2 platforms. ProcessJobs = 2, just the android platform jobs (pc is already processed)
ASSERT_TRUE(m_data->m_processResults[0].m_destinationPath.contains("android"));
ASSERT_TRUE(m_data->m_processResults[1].m_destinationPath.contains("android"));
}
TEST_F(ModtimeScanningTest, ModtimeSkipping_ModifyTimestamp)
{
// Update the timestamp on a file without changing its contents
// This should not cause any job to run since the hash of the file is the same before/after
// Additionally, the timestamp stored in the database should be updated
using namespace AzToolsFramework::AssetSystem;
uint64_t timestamp = 1594923423;
QString databaseName, scanfolderName;
m_config->ConvertToRelativePath(m_data->m_absolutePath[1], databaseName, scanfolderName);
auto* scanFolder = m_config->GetScanFolderForFile(m_data->m_absolutePath[1]);
AzToolsFramework::AssetDatabase::FileDatabaseEntry fileEntry;
m_assetProcessorManager.get()->m_stateData->GetFileByFileNameAndScanFolderId(databaseName, scanFolder->ScanFolderID(), fileEntry);
ASSERT_NE(fileEntry.m_modTime, timestamp);
uint64_t existingTimestamp = fileEntry.m_modTime;
// Modify the timestamp on just one file
AzToolsFramework::ToolsFileUtils::SetModificationTime(m_data->m_absolutePath[1].toUtf8().data(), timestamp);
// Enable the features we're testing
m_assetProcessorManager->m_allowModtimeSkippingFeature = true;
AssetUtilities::SetUseFileHashOverride(true, true);
QSet<AssetFileInfo> filePaths = BuildFileSet();
SimulateAssetScanner(filePaths);
ExpectNoWork();
m_assetProcessorManager.get()->m_stateData->GetFileByFileNameAndScanFolderId(databaseName, scanFolder->ScanFolderID(), fileEntry);
// The timestamp should be updated even though nothing processed
ASSERT_NE(fileEntry.m_modTime, existingTimestamp);
}
TEST_F(ModtimeScanningTest, ModtimeSkipping_ModifyTimestampNoHashing_ProcessesFile)
{
// Update the timestamp on a file without changing its contents
// This should not cause any job to run since the hash of the file is the same before/after
// Additionally, the timestamp stored in the database should be updated
using namespace AzToolsFramework::AssetSystem;
uint64_t timestamp = 1594923423;
// Modify the timestamp on just one file
AzToolsFramework::ToolsFileUtils::SetModificationTime(m_data->m_absolutePath[1].toUtf8().data(), timestamp);
// Enable the features we're testing
m_assetProcessorManager->m_allowModtimeSkippingFeature = true;
AssetUtilities::SetUseFileHashOverride(true, false);
QSet<AssetFileInfo> filePaths = BuildFileSet();
SimulateAssetScanner(filePaths);
ExpectWork(2, 2);
}
TEST_F(ModtimeScanningTest, ModtimeSkipping_ModifyFile)
{
using namespace AzToolsFramework::AssetSystem;
SetFileContents(m_data->m_absolutePath[1].toUtf8().constData(), "hello world");
// Enable the features we're testing
m_assetProcessorManager->m_allowModtimeSkippingFeature = true;
AssetUtilities::SetUseFileHashOverride(true, true);
QSet<AssetFileInfo> filePaths = BuildFileSet();
SimulateAssetScanner(filePaths);
// Even though we're only updating one file, we're expecting 2 createJob calls because our test file is a dependency that triggers the other test file to process as well
ExpectWork(2, 2);
}
TEST_F(ModtimeScanningTest, ModtimeSkipping_ModifyFile_AndThenRevert_ProcessesAgain)
{
using namespace AzToolsFramework::AssetSystem;
auto theFile = m_data->m_absolutePath[1].toUtf8();
const char* theFileString = theFile.constData();
SetFileContents(theFileString, "hello world");
// Enable the features we're testing
m_assetProcessorManager->m_allowModtimeSkippingFeature = true;
AssetUtilities::SetUseFileHashOverride(true, true);
QSet<AssetFileInfo> filePaths = BuildFileSet();
SimulateAssetScanner(filePaths);
// Even though we're only updating one file, we're expecting 2 createJob calls because our test file is a dependency that triggers the other test file to process as well
ExpectWork(2, 2);
ProcessAssetJobs();
m_data->m_mockBuilderInfoHandler.m_createJobsCount = 0;
m_data->m_processResults.clear();
m_data->m_deletedSources.clear();
SetFileContents(theFileString, "");
filePaths = BuildFileSet();
SimulateAssetScanner(filePaths);
// Expect processing to happen again
ExpectWork(2, 2);
}
struct LockedFileTest
: ModtimeScanningTest
, AssetProcessor::ConnectionBus::Handler
{
MOCK_METHOD3(SendRaw, size_t (unsigned, unsigned, const QByteArray&));
MOCK_METHOD3(SendPerPlatform, size_t (unsigned, const AzFramework::AssetSystem::BaseAssetProcessorMessage&, const QString&));
MOCK_METHOD4(SendRawPerPlatform, size_t (unsigned, unsigned, const QByteArray&, const QString&));
MOCK_METHOD2(SendRequest, unsigned (const AzFramework::AssetSystem::BaseAssetProcessorMessage&, const ResponseCallback&));
MOCK_METHOD2(SendResponse, size_t (unsigned, const AzFramework::AssetSystem::BaseAssetProcessorMessage&));
MOCK_METHOD1(RemoveResponseHandler, void (unsigned));
size_t Send(unsigned, const AzFramework::AssetSystem::BaseAssetProcessorMessage& message) override
{
using SourceFileNotificationMessage = AzToolsFramework::AssetSystem::SourceFileNotificationMessage;
switch (message.GetMessageType())
{
case SourceFileNotificationMessage::MessageType:
if (const auto sourceFileMessage = azrtti_cast<const SourceFileNotificationMessage*>(&message); sourceFileMessage != nullptr &&
sourceFileMessage->m_type == SourceFileNotificationMessage::NotificationType::FileRemoved)
{
// The File Remove message will occur before an attempt to delete the file
// Wait for more than 1 File Remove message.
// This indicates the AP has attempted to delete the file once, failed to do so and is now retrying
++m_deleteCounter;
if(m_deleteCounter > 1 && m_callback)
{
m_callback();
m_callback = {}; // Unset it to be safe, we only intend to run the callback once
}
}
break;
default:
break;
}
return 0;
}
void SetUp() override
{
ModtimeScanningTest::SetUp();
ConnectionBus::Handler::BusConnect(0);
}
void TearDown() override
{
ConnectionBus::Handler::BusDisconnect();
ModtimeScanningTest::TearDown();
}
AZStd::atomic_int m_deleteCounter{ 0 };
AZStd::function<void()> m_callback;
};
TEST_F(LockedFileTest, DeleteFile_LockedProduct_DeleteFails)
{
auto theFile = m_data->m_absolutePath[1].toUtf8();
const char* theFileString = theFile.constData();
auto [sourcePath, productPath] = *m_data->m_productPaths.find(theFileString);
{
QFile file(theFileString);
file.remove();
}
ASSERT_GT(m_data->m_productPaths.size(), 0);
QFile product(productPath);
ASSERT_TRUE(product.open(QIODevice::ReadOnly));
// Check if we can delete the file now, if we can't, proceed with the test
// If we can, it means the OS running this test doesn't lock open files so there's nothing to test
if (!AZ::IO::SystemFile::Delete(productPath.toUtf8().constData()))
{
QMetaObject::invokeMethod(
m_assetProcessorManager.get(), "AssessDeletedFile", Qt::QueuedConnection, Q_ARG(QString, QString(theFileString)));
EXPECT_TRUE(BlockUntilIdle(5000));
EXPECT_TRUE(QFile::exists(productPath));
EXPECT_EQ(m_data->m_deletedSources.size(), 0);
}
else
{
SUCCEED() << "Skipping test. OS does not lock open files.";
}
}
TEST_F(LockedFileTest, DeleteFile_LockedProduct_DeletesWhenReleased)
{
// This test is intended to verify the AP will successfully retry deleting a source asset
// when one of its product assets is locked temporarily
// We'll lock the file by holding it open
auto theFile = m_data->m_absolutePath[1].toUtf8();
const char* theFileString = theFile.constData();
auto [sourcePath, productPath] = *m_data->m_productPaths.find(theFileString);
{
QFile file(theFileString);
file.remove();
}
ASSERT_GT(m_data->m_productPaths.size(), 0);
QFile product(productPath);
// Open the file and keep it open to lock it
// We'll start a thread later to unlock the file
// This will allow us to test how AP handles trying to delete a locked file
ASSERT_TRUE(product.open(QIODevice::ReadOnly));
// Check if we can delete the file now, if we can't, proceed with the test
// If we can, it means the OS running this test doesn't lock open files so there's nothing to test
if (!AZ::IO::SystemFile::Delete(productPath.toUtf8().constData()))
{
m_deleteCounter = 0;
// Set up a callback which will fire after at least 1 retry
// Unlock the file at that point so AP can successfully delete it
m_callback = [&product]()
{
product.close();
};
QMetaObject::invokeMethod(
m_assetProcessorManager.get(), "AssessDeletedFile", Qt::QueuedConnection, Q_ARG(QString, QString(theFileString)));
EXPECT_TRUE(BlockUntilIdle(5000));
EXPECT_FALSE(QFile::exists(productPath));
EXPECT_EQ(m_data->m_deletedSources.size(), 1);
EXPECT_GT(m_deleteCounter, 1); // Make sure the AP tried more than once to delete the file
m_errorAbsorber->ExpectAsserts(0);
}
else
{
SUCCEED() << "Skipping test. OS does not lock open files.";
}
}
TEST_F(ModtimeScanningTest, ModtimeSkipping_ModifyFilesSameHash_BothProcess)
{
using namespace AzToolsFramework::AssetSystem;
SetFileContents(m_data->m_absolutePath[1].toUtf8().constData(), "hello world");
// Enable the features we're testing
m_assetProcessorManager->m_allowModtimeSkippingFeature = true;
AssetUtilities::SetUseFileHashOverride(true, true);
QSet<AssetFileInfo> filePaths = BuildFileSet();
SimulateAssetScanner(filePaths);
// Even though we're only updating one file, we're expecting 2 createJob calls because our test file is a dependency that triggers the other test file to process as well
ExpectWork(2, 2);
ProcessAssetJobs();
m_data->m_mockBuilderInfoHandler.m_createJobsCount = 0;
m_data->m_processResults.clear();
m_data->m_deletedSources.clear();
// Make file 0 have the same contents as file 1
SetFileContents(m_data->m_absolutePath[0].toUtf8().constData(), "hello world");
filePaths = BuildFileSet();
SimulateAssetScanner(filePaths);
ExpectWork(1, 1);
}
TEST_F(ModtimeScanningTest, ModtimeSkipping_ModifyMetadataFile)
{
using namespace AzToolsFramework::AssetSystem;
SetFileContents(m_data->m_absolutePath[2].toUtf8().constData(), "hello world");
// Enable the features we're testing
m_assetProcessorManager->m_allowModtimeSkippingFeature = true;
AssetUtilities::SetUseFileHashOverride(true, true);
QSet<AssetFileInfo> filePaths = BuildFileSet();
SimulateAssetScanner(filePaths);
// Even though we're only updating one file, we're expecting 2 createJob calls because our test file is a metadata file
// that triggers the source file which is a dependency that triggers the other test file to process as well
ExpectWork(2, 2);
}
TEST_F(ModtimeScanningTest, ModtimeSkipping_DeleteFile)
{
using namespace AzToolsFramework::AssetSystem;
// Enable the features we're testing
m_assetProcessorManager->m_allowModtimeSkippingFeature = true;
AssetUtilities::SetUseFileHashOverride(true, true);
ASSERT_TRUE(QFile::remove(m_data->m_absolutePath[0]));
// Feed in ONLY one file (the one we didn't delete)
QSet<AssetFileInfo> filePaths;
QFileInfo fileInfo(m_data->m_absolutePath[1]);
auto modtime = fileInfo.lastModified();
AZ::u64 fileSize = fileInfo.size();
filePaths.insert(AssetFileInfo(m_data->m_absolutePath[1], modtime, fileSize, &m_config->GetScanFolderAt(0), false));
SimulateAssetScanner(filePaths);
QElapsedTimer timer;
timer.start();
do
{
QCoreApplication::processEvents(QEventLoop::AllEvents, 10);
} while (m_data->m_deletedSources.size() < m_data->m_relativePathFromWatchFolder[0].size() && timer.elapsed() < 5000);
ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 0);
ASSERT_EQ(m_data->m_processResults.size(), 0);
ASSERT_THAT(m_data->m_deletedSources, testing::ElementsAre(m_data->m_relativePathFromWatchFolder[0]));
}
TEST_F(ModtimeScanningTest, ReprocessRequest_FileNotModified_FileProcessed)
{
using namespace AzToolsFramework::AssetSystem;
m_assetProcessorManager->RequestReprocess(m_data->m_absolutePath[0]);
ASSERT_TRUE(BlockUntilIdle(5000));
ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 1);
ASSERT_EQ(m_data->m_processResults.size(), 1);
}
TEST_F(ModtimeScanningTest, ReprocessRequest_SourceWithDependency_BothWillProcess)
{
using namespace AzToolsFramework::AssetSystem;
using SourceFileDependencyEntry = AzToolsFramework::AssetDatabase::SourceFileDependencyEntry;
SourceFileDependencyEntry newEntry1;
newEntry1.m_sourceDependencyID = AzToolsFramework::AssetDatabase::InvalidEntryId;
newEntry1.m_builderGuid = AZ::Uuid::CreateRandom();
newEntry1.m_source = m_data->m_absolutePath[0].toUtf8().constData();
newEntry1.m_dependsOnSource = m_data->m_absolutePath[1].toUtf8().constData();
newEntry1.m_typeOfDependency = SourceFileDependencyEntry::DEP_SourceToSource;
m_assetProcessorManager->RequestReprocess(m_data->m_absolutePath[0]);
ASSERT_TRUE(BlockUntilIdle(5000));
ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 1);
ASSERT_EQ(m_data->m_processResults.size(), 1);
m_assetProcessorManager->RequestReprocess(m_data->m_absolutePath[1]);
ASSERT_TRUE(BlockUntilIdle(5000));
ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 3);
ASSERT_EQ(m_data->m_processResults.size(), 3);
}
TEST_F(ModtimeScanningTest, ReprocessRequest_RequestFolder_SourceAssetsWillProcess)
{
using namespace AzToolsFramework::AssetSystem;
const auto& scanFolder = m_config->GetScanFolderAt(0);
QString scanPath = scanFolder.ScanPath();
m_assetProcessorManager->RequestReprocess(scanPath);
ASSERT_TRUE(BlockUntilIdle(5000));
// two text files are source assets, assetinfo is not
ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 2);
ASSERT_EQ(m_data->m_processResults.size(), 2);
}
//////////////////////////////////////////////////////////////////////////
MockBuilderInfoHandler::~MockBuilderInfoHandler()
@ -5205,130 +4477,7 @@ TEST_F(ChainJobDependencyTest, TestChainDependency_Multi)
}
}
void DeleteTest::SetUp()
{
AssetProcessorManagerTest::SetUp();
m_data = AZStd::make_unique<StaticData>();
m_assetProcessorManager->m_allowModtimeSkippingFeature = true;
// We don't want the mock application manager to provide builder descriptors, mockBuilderInfoHandler will provide our own
m_mockApplicationManager->BusDisconnect();
m_data->m_mockBuilderInfoHandler.m_builderDesc = m_data->m_mockBuilderInfoHandler.CreateBuilderDesc("test builder", "{DF09DDC0-FD22-43B6-9E22-22C8574A6E1E}", { AssetBuilderSDK::AssetBuilderPattern("*.txt", AssetBuilderSDK::AssetBuilderPattern::Wildcard) });
m_data->m_mockBuilderInfoHandler.BusConnect();
ASSERT_TRUE(m_mockApplicationManager->GetBuilderByID("txt files", m_data->m_builderTxtBuilder));
// Run this twice so the test builder doesn't get counted as a "new" builder and bypass the modtime skipping
m_assetProcessorManager->ComputeBuilderDirty();
m_assetProcessorManager->ComputeBuilderDirty();
auto setupConnectionsFunc = [this]()
{
QObject::connect(m_assetProcessorManager.get(), &AssetProcessorManager::AssetToProcess, [this](JobDetails details)
{
m_data->m_processResults.push_back(AZStd::move(details));
});
QObject::connect(m_assetProcessorManager.get(), &AssetProcessorManager::SourceDeleted, [this](QString file)
{
m_data->m_deletedSources.push_back(file);
});
};
auto createFileAndAddToDatabaseFunc = [this](const AssetProcessor::ScanFolderInfo* scanFolder, QString file)
{
using namespace AzToolsFramework::AssetDatabase;
QString watchFolderPath = scanFolder->ScanPath();
QString absPath(QDir(watchFolderPath).absoluteFilePath(file));
UnitTestUtils::CreateDummyFile(absPath);
m_data->m_absolutePath.push_back(absPath);
AzToolsFramework::AssetDatabase::FileDatabaseEntry fileEntry;
fileEntry.m_fileName = file.toUtf8().constData();
fileEntry.m_modTime = 0;
fileEntry.m_isFolder = false;
fileEntry.m_scanFolderPK = scanFolder->ScanFolderID();
bool entryAlreadyExists;
ASSERT_TRUE(m_assetProcessorManager->m_stateData->InsertFile(fileEntry, entryAlreadyExists));
ASSERT_FALSE(entryAlreadyExists);
};
setupConnectionsFunc();
// Create test files
QDir tempPath(m_tempDir.path());
const auto* scanFolder1 = m_config->GetScanFolderByPath(tempPath.absoluteFilePath("subfolder1"));
const auto* scanFolder4 = m_config->GetScanFolderByPath(tempPath.absoluteFilePath("subfolder4"));
createFileAndAddToDatabaseFunc(scanFolder1, QString("textures/a.txt"));
createFileAndAddToDatabaseFunc(scanFolder4, QString("textures/b.txt"));
// Run the test files through AP all the way to processing stage
QSet<AssetFileInfo> filePaths = BuildFileSet();
SimulateAssetScanner(filePaths);
ASSERT_TRUE(BlockUntilIdle(5000));
ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 2);
ASSERT_EQ(m_data->m_processResults.size(), 2);
ASSERT_EQ(m_data->m_deletedSources.size(), 0);
ProcessAssetJobs();
m_data->m_processResults.clear();
m_data->m_mockBuilderInfoHandler.m_createJobsCount = 0;
// Reboot the APM since we added stuff to the database that needs to be loaded on-startup of the APM
m_assetProcessorManager.reset(new AssetProcessorManager_Test(m_config.get()));
m_idleConnection = QObject::connect(m_assetProcessorManager.get(), &AssetProcessor::AssetProcessorManager::AssetProcessorManagerIdleState, [this](bool newState)
{
m_isIdling = newState;
});
setupConnectionsFunc();
m_assetProcessorManager->ComputeBuilderDirty();
}
TEST_F(DeleteTest, DeleteFolderSharedAcrossTwoScanFolders_CorrectFileAndFolderAreDeletedFromCache)
{
// There was a bug where AP wasn't repopulating the "known folders" list when modtime skipping was enabled and no work was needed
// As a result, deleting a folder didn't count as a "folder", so the wrong code path was taken. This test makes sure the correct deletion events fire
using namespace AzToolsFramework::AssetSystem;
// Modtime skipping has to be on for this
m_assetProcessorManager->m_allowModtimeSkippingFeature = true;
// Feed in the files from the asset scanner, no jobs should run since they're already up-to-date
QSet<AssetFileInfo> filePaths = BuildFileSet();
SimulateAssetScanner(filePaths);
ExpectNoWork();
// Delete one of the folders
QDir tempPath(m_tempDir.path());
QString absPath(tempPath.absoluteFilePath("subfolder1/textures"));
QDir(absPath).removeRecursively();
AZStd::vector<AZStd::string> deletedFolders;
QObject::connect(m_assetProcessorManager.get(), &AssetProcessorManager::SourceFolderDeleted, [&deletedFolders](QString file)
{
deletedFolders.push_back(file.toUtf8().constData());
});
m_assetProcessorManager->AssessDeletedFile(absPath);
ASSERT_TRUE(BlockUntilIdle(5000));
ASSERT_THAT(m_data->m_deletedSources, testing::UnorderedElementsAre("textures/a.txt"));
ASSERT_THAT(deletedFolders, testing::UnorderedElementsAre("textures"));
}
void DuplicateProcessTest::SetUp()
{

@ -37,6 +37,114 @@ public:
MOCK_METHOD1(GetAssetDatabaseLocation, bool(AZStd::string&));
};
class AssetProcessorManager_Test : public AssetProcessor::AssetProcessorManager
{
public:
friend class GTEST_TEST_CLASS_NAME_(
AssetProcessorManagerTest, AssetProcessedImpl_DifferentProductDependenciesPerProduct_SavesCorrectlyToDatabase);
friend class GTEST_TEST_CLASS_NAME_(MultiplatformPathDependencyTest, AssetProcessed_Impl_MultiplatformDependencies);
friend class GTEST_TEST_CLASS_NAME_(MultiplatformPathDependencyTest, AssetProcessed_Impl_MultiplatformDependencies_DeferredResolution);
friend class GTEST_TEST_CLASS_NAME_(MultiplatformPathDependencyTest, SameFilenameForAllPlatforms);
friend class GTEST_TEST_CLASS_NAME_(MultiplatformPathDependencyTest, AssetProcessed_Impl_MultiplatformDependencies_SourcePath);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, DeleteFolder_SignalsDeleteOfContainedFiles);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, QueryAbsolutePathDependenciesRecursive_BasicTest);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, QueryAbsolutePathDependenciesRecursive_WithDifferentTypes_BasicTest);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, QueryAbsolutePathDependenciesRecursive_Reverse_BasicTest);
friend class GTEST_TEST_CLASS_NAME_(
AssetProcessorManagerTest, QueryAbsolutePathDependenciesRecursive_MissingFiles_ReturnsNoPathWithPlaceholders);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_BeforeComputingDirtiness_AllDirty);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_EmptyDatabase_AllDirty);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_SameAsLastTime_NoneDirty);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_MoreThanLastTime_NewOneIsDirty);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_FewerThanLastTime_Dirty);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_ChangedPattern_CountsAsNew);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_ChangedPatternType_CountsAsNew);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_NewPattern_CountsAsNewBuilder);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_NewVersionNumber_IsNotANewBuilder);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_NewAnalysisFingerprint_IsNotANewBuilder);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_BasicTest);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_UpdateTest);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_MissingFiles_ByUuid);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_MissingFiles_ByName);
friend class GTEST_TEST_CLASS_NAME_(
AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_MissingFiles_ByUuid_UpdatesWhenTheyAppear);
friend class GTEST_TEST_CLASS_NAME_(
AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_MissingFiles_ByName_UpdatesWhenTheyAppear);
friend class GTEST_TEST_CLASS_NAME_(
AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_WildcardMissingFiles_ByName_UpdatesWhenTheyAppear);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, JobDependencyOrderOnce_MultipleJobs_EmitOK);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, SourceFileProcessFailure_ClearsFingerprint);
friend class GTEST_TEST_CLASS_NAME_(
AbsolutePathProductDependencyTest, UnresolvedProductPathDependency_AssetProcessedTwice_DoesNotDuplicateDependency);
friend class GTEST_TEST_CLASS_NAME_(
AbsolutePathProductDependencyTest, AbsolutePathProductDependency_RetryDeferredDependenciesWithMatchingSource_DependencyResolves);
friend class GTEST_TEST_CLASS_NAME_(
AbsolutePathProductDependencyTest, UnresolvedProductPathDependency_AssetProcessedTwice_ValidatePathDependenciesMap);
friend class GTEST_TEST_CLASS_NAME_(
AbsolutePathProductDependencyTest,
UnresolvedSourceFileTypeProductPathDependency_DependencyHasNoProductOutput_ValidatePathDependenciesMap);
friend class GTEST_TEST_CLASS_NAME_(DeleteTest, DeleteFolderSharedAcrossTwoScanFolders_CorrectFileAndFolderAreDeletedFromCache);
friend class GTEST_TEST_CLASS_NAME_(MetadataFileTest, MetadataFile_SourceFileExtensionDifferentCase);
friend class AssetProcessorManagerTest;
friend struct JobDependencyTest;
friend struct ChainJobDependencyTest;
friend struct DeleteTest;
friend struct PathDependencyTest;
friend struct DuplicateProductsTest;
friend struct DuplicateProcessTest;
friend struct AbsolutePathProductDependencyTest;
friend struct WildcardSourceDependencyTest;
explicit AssetProcessorManager_Test(AssetProcessor::PlatformConfiguration* config, QObject* parent = nullptr);
~AssetProcessorManager_Test() override;
bool CheckJobKeyToJobRunKeyMap(AZStd::string jobKey);
int CountDirtyBuilders() const
{
int numDirty = 0;
for (const auto& element : m_builderDataCache)
{
if (element.second.m_isDirty)
{
++numDirty;
}
}
return numDirty;
}
bool IsBuilderDirty(const AZ::Uuid& builderBusId) const
{
auto finder = m_builderDataCache.find(builderBusId);
if (finder == m_builderDataCache.end())
{
return true;
}
return finder->second.m_isDirty;
}
void RecomputeDirtyBuilders()
{
// Run this twice so the test builder doesn't get counted as a "new" builder and bypass the modtime skipping
ComputeBuilderDirty();
ComputeBuilderDirty();
}
using AssetProcessorManager::m_stateData;
using AssetProcessorManager::ComputeBuilderDirty;
};
class AssetProcessorManagerTest
: public AssetProcessor::AssetProcessorTest
{
@ -165,33 +273,6 @@ struct MockBuilderInfoHandler
int m_createJobsCount = 0;
};
struct ModtimeScanningTest
: public AssetProcessorManagerTest
{
void SetUp() override;
void TearDown() override;
void ProcessAssetJobs();
void SimulateAssetScanner(QSet<AssetProcessor::AssetFileInfo> filePaths);
QSet<AssetProcessor::AssetFileInfo> BuildFileSet();
void ExpectWork(int createJobs, int processJobs);
void ExpectNoWork();
void SetFileContents(QString filePath, QString contents);
struct StaticData
{
QString m_relativePathFromWatchFolder[3];
AZStd::vector<QString> m_absolutePath;
AZStd::vector<AssetProcessor::JobDetails> m_processResults;
AZStd::unordered_multimap<AZStd::string, QString> m_productPaths;
AZStd::vector<QString> m_deletedSources;
AZStd::shared_ptr<AssetProcessor::InternalMockBuilder> m_builderTxtBuilder;
MockBuilderInfoHandler m_mockBuilderInfoHandler;
};
AZStd::unique_ptr<StaticData> m_data;
};
struct MetadataFileTest
: public AssetProcessorManagerTest
@ -274,9 +355,3 @@ struct DuplicateProductsTest
{
void SetupDuplicateProductsTest(QString& sourceFile, QDir& tempPath, QString& productFile, AZStd::vector<AssetProcessor::JobDetails>& jobDetails, AssetBuilderSDK::ProcessJobResponse& response, bool multipleOutputs, QString extension);
};
struct DeleteTest
: public ModtimeScanningTest
{
void SetUp() override;
};

@ -0,0 +1,706 @@
/*
* 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 <native/tests/assetmanager/ModtimeScanningTests.h>
#include <native/tests/assetmanager/AssetProcessorManagerTest.h>
#include <QObject>
#include <ToolsFileUtils/ToolsFileUtils.h>
namespace UnitTests
{
using AssetFileInfo = AssetProcessor::AssetFileInfo;
void ModtimeScanningTest::SetUpAssetProcessorManager()
{
using namespace AssetProcessor;
m_assetProcessorManager->SetEnableModtimeSkippingFeature(true);
m_assetProcessorManager->RecomputeDirtyBuilders();
QObject::connect(
m_assetProcessorManager.get(), &AssetProcessorManager::AssetToProcess,
[this](JobDetails details)
{
m_data->m_processResults.push_back(AZStd::move(details));
});
QObject::connect(
m_assetProcessorManager.get(), &AssetProcessorManager::SourceDeleted,
[this](QString file)
{
m_data->m_deletedSources.push_back(file);
});
m_idleConnection = QObject::connect(
m_assetProcessorManager.get(), &AssetProcessor::AssetProcessorManager::AssetProcessorManagerIdleState,
[this](bool newState)
{
m_isIdling = newState;
});
}
void ModtimeScanningTest::SetUp()
{
using namespace AssetProcessor;
AssetProcessorManagerTest::SetUp();
m_data = AZStd::make_unique<StaticData>();
// We don't want the mock application manager to provide builder descriptors, mockBuilderInfoHandler will provide our own
m_mockApplicationManager->BusDisconnect();
m_data->m_mockBuilderInfoHandler.m_builderDesc = m_data->m_mockBuilderInfoHandler.CreateBuilderDesc(
"test builder", "{DF09DDC0-FD22-43B6-9E22-22C8574A6E1E}",
{ AssetBuilderSDK::AssetBuilderPattern("*.txt", AssetBuilderSDK::AssetBuilderPattern::Wildcard) });
m_data->m_mockBuilderInfoHandler.BusConnect();
ASSERT_TRUE(m_mockApplicationManager->GetBuilderByID("txt files", m_data->m_builderTxtBuilder));
SetUpAssetProcessorManager();
// Create the test file
const auto& scanFolder = m_config->GetScanFolderAt(0);
m_data->m_relativePathFromWatchFolder[0] = "modtimeTestFile.txt";
m_data->m_absolutePath.push_back(QDir(scanFolder.ScanPath()).absoluteFilePath(m_data->m_relativePathFromWatchFolder[0]));
m_data->m_relativePathFromWatchFolder[1] = "modtimeTestDependency.txt";
m_data->m_absolutePath.push_back(QDir(scanFolder.ScanPath()).absoluteFilePath(m_data->m_relativePathFromWatchFolder[1]));
m_data->m_relativePathFromWatchFolder[2] = "modtimeTestDependency.txt.assetinfo";
m_data->m_absolutePath.push_back(QDir(scanFolder.ScanPath()).absoluteFilePath(m_data->m_relativePathFromWatchFolder[2]));
for (const auto& path : m_data->m_absolutePath)
{
ASSERT_TRUE(UnitTestUtils::CreateDummyFile(path, ""));
}
m_data->m_mockBuilderInfoHandler.m_dependencyFilePath = m_data->m_absolutePath[1].toUtf8().data();
// Add file to database with no modtime
{
AssetDatabaseConnection connection;
ASSERT_TRUE(connection.OpenDatabase());
AzToolsFramework::AssetDatabase::FileDatabaseEntry fileEntry;
fileEntry.m_fileName = m_data->m_relativePathFromWatchFolder[0].toUtf8().data();
fileEntry.m_modTime = 0;
fileEntry.m_isFolder = false;
fileEntry.m_scanFolderPK = scanFolder.ScanFolderID();
bool entryAlreadyExists;
ASSERT_TRUE(connection.InsertFile(fileEntry, entryAlreadyExists));
ASSERT_FALSE(entryAlreadyExists);
fileEntry.m_fileID = AzToolsFramework::AssetDatabase::InvalidEntryId; // Reset the id so we make a new entry
fileEntry.m_fileName = m_data->m_relativePathFromWatchFolder[1].toUtf8().data();
ASSERT_TRUE(connection.InsertFile(fileEntry, entryAlreadyExists));
ASSERT_FALSE(entryAlreadyExists);
fileEntry.m_fileID = AzToolsFramework::AssetDatabase::InvalidEntryId; // Reset the id so we make a new entry
fileEntry.m_fileName = m_data->m_relativePathFromWatchFolder[2].toUtf8().data();
ASSERT_TRUE(connection.InsertFile(fileEntry, entryAlreadyExists));
ASSERT_FALSE(entryAlreadyExists);
}
QSet<AssetFileInfo> filePaths = BuildFileSet();
SimulateAssetScanner(filePaths);
ASSERT_TRUE(BlockUntilIdle(5000));
ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 2);
ASSERT_EQ(m_data->m_processResults.size(), 2);
ASSERT_EQ(m_data->m_deletedSources.size(), 0);
ProcessAssetJobs();
m_data->m_processResults.clear();
m_data->m_mockBuilderInfoHandler.m_createJobsCount = 0;
m_isIdling = false;
}
void ModtimeScanningTest::TearDown()
{
m_data = nullptr;
AssetProcessorManagerTest::TearDown();
}
void ModtimeScanningTest::ProcessAssetJobs()
{
m_data->m_productPaths.clear();
for (const auto& processResult : m_data->m_processResults)
{
auto file =
QDir(processResult.m_destinationPath).absoluteFilePath(processResult.m_jobEntry.m_databaseSourceName.toLower() + ".arc1");
m_data->m_productPaths.emplace(
QDir(processResult.m_jobEntry.m_watchFolderPath)
.absoluteFilePath(processResult.m_jobEntry.m_databaseSourceName)
.toUtf8()
.constData(),
file);
// Create the file on disk
ASSERT_TRUE(UnitTestUtils::CreateDummyFile(file, "products."));
AssetBuilderSDK::ProcessJobResponse response;
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(file.toUtf8().constData(), AZ::Uuid::CreateNull(), 1));
using JobEntry = AssetProcessor::JobEntry;
QMetaObject::invokeMethod(
m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, processResult.m_jobEntry),
Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
}
ASSERT_TRUE(BlockUntilIdle(5000));
m_isIdling = false;
}
void ModtimeScanningTest::SimulateAssetScanner(QSet<AssetProcessor::AssetFileInfo> filePaths)
{
QMetaObject::invokeMethod(
m_assetProcessorManager.get(), "OnAssetScannerStatusChange", Qt::QueuedConnection,
Q_ARG(AssetProcessor::AssetScanningStatus, AssetProcessor::AssetScanningStatus::Started));
QMetaObject::invokeMethod(
m_assetProcessorManager.get(), "AssessFilesFromScanner", Qt::QueuedConnection, Q_ARG(QSet<AssetFileInfo>, filePaths));
QMetaObject::invokeMethod(
m_assetProcessorManager.get(), "OnAssetScannerStatusChange", Qt::QueuedConnection,
Q_ARG(AssetProcessor::AssetScanningStatus, AssetProcessor::AssetScanningStatus::Completed));
}
QSet<AssetProcessor::AssetFileInfo> ModtimeScanningTest::BuildFileSet()
{
QSet<AssetFileInfo> filePaths;
for (const auto& path : m_data->m_absolutePath)
{
QFileInfo fileInfo(path);
auto modtime = fileInfo.lastModified();
AZ::u64 fileSize = fileInfo.size();
filePaths.insert(AssetFileInfo(path, modtime, fileSize, m_config->GetScanFolderForFile(path), false));
}
return filePaths;
}
void ModtimeScanningTest::ExpectWork(int createJobs, int processJobs)
{
ASSERT_TRUE(BlockUntilIdle(5000));
EXPECT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, createJobs);
EXPECT_EQ(m_data->m_processResults.size(), processJobs);
for (int i = 0; i < processJobs; ++i)
{
EXPECT_FALSE(m_data->m_processResults[i].m_autoFail);
}
EXPECT_EQ(m_data->m_deletedSources.size(), 0);
m_isIdling = false;
}
void ModtimeScanningTest::ExpectNoWork()
{
// Since there's no work to do, the idle event isn't going to trigger, just process events a couple times
for (int i = 0; i < 10; ++i)
{
QCoreApplication::processEvents(QEventLoop::AllEvents, 10);
}
ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 0);
ASSERT_EQ(m_data->m_processResults.size(), 0);
ASSERT_EQ(m_data->m_deletedSources.size(), 0);
m_isIdling = false;
}
void ModtimeScanningTest::SetFileContents(QString filePath, QString contents)
{
QFile file(filePath);
file.open(QIODevice::WriteOnly | QIODevice::Truncate);
file.write(contents.toUtf8().constData());
file.close();
}
TEST_F(ModtimeScanningTest, ModtimeSkipping_FileUnchanged_WithoutModtimeSkipping)
{
using namespace AzToolsFramework::AssetSystem;
// Make sure modtime skipping is disabled
// We're just going to do 1 quick sanity test to make sure the files are still processed when modtime skipping is turned off
m_assetProcessorManager->SetEnableModtimeSkippingFeature(false);
QSet<AssetProcessor::AssetFileInfo> filePaths = BuildFileSet();
SimulateAssetScanner(filePaths);
// 2 create jobs but 0 process jobs because the file has already been processed before in SetUp
ExpectWork(2, 0);
}
TEST_F(ModtimeScanningTest, ModtimeSkipping_FileUnchanged)
{
using namespace AzToolsFramework::AssetSystem;
AssetUtilities::SetUseFileHashOverride(true, true);
QSet<AssetFileInfo> filePaths = BuildFileSet();
SimulateAssetScanner(filePaths);
ExpectNoWork();
}
TEST_F(ModtimeScanningTest, ModtimeSkipping_EnablePlatform_ShouldProcessFilesForPlatform)
{
using namespace AzToolsFramework::AssetSystem;
AssetUtilities::SetUseFileHashOverride(true, true);
// Enable android platform after the initial SetUp has already processed the files for pc
QDir tempPath(m_tempDir.path());
AssetBuilderSDK::PlatformInfo androidPlatform("android", { "host", "renderer" });
m_config->EnablePlatform(androidPlatform, true);
// There's no way to remove scanfolders and adding a new one after enabling the platform will cause the pc assets to build as well,
// which we don't want Instead we'll just const cast the vector and modify the enabled platforms for the scanfolder
auto& platforms = const_cast<AZStd::vector<AssetBuilderSDK::PlatformInfo>&>(m_config->GetScanFolderAt(0).GetPlatforms());
platforms.push_back(androidPlatform);
// We need the builder fingerprints to be updated to reflect the newly enabled platform
m_assetProcessorManager->ComputeBuilderDirty();
QSet<AssetFileInfo> filePaths = BuildFileSet();
SimulateAssetScanner(filePaths);
ExpectWork(
4, 2); // CreateJobs = 4, 2 files * 2 platforms. ProcessJobs = 2, just the android platform jobs (pc is already processed)
ASSERT_TRUE(m_data->m_processResults[0].m_destinationPath.contains("android"));
ASSERT_TRUE(m_data->m_processResults[1].m_destinationPath.contains("android"));
}
TEST_F(ModtimeScanningTest, ModtimeSkipping_ModifyTimestamp)
{
// Update the timestamp on a file without changing its contents
// This should not cause any job to run since the hash of the file is the same before/after
// Additionally, the timestamp stored in the database should be updated
using namespace AzToolsFramework::AssetSystem;
uint64_t timestamp = 1594923423;
QString databaseName, scanfolderName;
m_config->ConvertToRelativePath(m_data->m_absolutePath[1], databaseName, scanfolderName);
auto* scanFolder = m_config->GetScanFolderForFile(m_data->m_absolutePath[1]);
AzToolsFramework::AssetDatabase::FileDatabaseEntry fileEntry;
m_assetProcessorManager->m_stateData->GetFileByFileNameAndScanFolderId(databaseName, scanFolder->ScanFolderID(), fileEntry);
ASSERT_NE(fileEntry.m_modTime, timestamp);
uint64_t existingTimestamp = fileEntry.m_modTime;
// Modify the timestamp on just one file
AzToolsFramework::ToolsFileUtils::SetModificationTime(m_data->m_absolutePath[1].toUtf8().data(), timestamp);
AssetUtilities::SetUseFileHashOverride(true, true);
QSet<AssetFileInfo> filePaths = BuildFileSet();
SimulateAssetScanner(filePaths);
ExpectNoWork();
m_assetProcessorManager->m_stateData->GetFileByFileNameAndScanFolderId(databaseName, scanFolder->ScanFolderID(), fileEntry);
// The timestamp should be updated even though nothing processed
ASSERT_NE(fileEntry.m_modTime, existingTimestamp);
}
TEST_F(ModtimeScanningTest, ModtimeSkipping_ModifyTimestampNoHashing_ProcessesFile)
{
// Update the timestamp on a file without changing its contents
// This should not cause any job to run since the hash of the file is the same before/after
// Additionally, the timestamp stored in the database should be updated
using namespace AzToolsFramework::AssetSystem;
uint64_t timestamp = 1594923423;
// Modify the timestamp on just one file
AzToolsFramework::ToolsFileUtils::SetModificationTime(m_data->m_absolutePath[1].toUtf8().data(), timestamp);
AssetUtilities::SetUseFileHashOverride(true, false);
QSet<AssetFileInfo> filePaths = BuildFileSet();
SimulateAssetScanner(filePaths);
ExpectWork(2, 2);
}
TEST_F(ModtimeScanningTest, ModtimeSkipping_ModifyFile)
{
using namespace AzToolsFramework::AssetSystem;
SetFileContents(m_data->m_absolutePath[1].toUtf8().constData(), "hello world");
AssetUtilities::SetUseFileHashOverride(true, true);
QSet<AssetFileInfo> filePaths = BuildFileSet();
SimulateAssetScanner(filePaths);
// Even though we're only updating one file, we're expecting 2 createJob calls because our test file is a dependency that triggers
// the other test file to process as well
ExpectWork(2, 2);
}
TEST_F(ModtimeScanningTest, ModtimeSkipping_ModifyFile_AndThenRevert_ProcessesAgain)
{
using namespace AzToolsFramework::AssetSystem;
auto theFile = m_data->m_absolutePath[1].toUtf8();
const char* theFileString = theFile.constData();
SetFileContents(theFileString, "hello world");
AssetUtilities::SetUseFileHashOverride(true, true);
QSet<AssetFileInfo> filePaths = BuildFileSet();
SimulateAssetScanner(filePaths);
// Even though we're only updating one file, we're expecting 2 createJob calls because our test file is a dependency that triggers
// the other test file to process as well
ExpectWork(2, 2);
ProcessAssetJobs();
m_data->m_mockBuilderInfoHandler.m_createJobsCount = 0;
m_data->m_processResults.clear();
m_data->m_deletedSources.clear();
SetFileContents(theFileString, "");
filePaths = BuildFileSet();
SimulateAssetScanner(filePaths);
// Expect processing to happen again
ExpectWork(2, 2);
}
TEST_F(ModtimeScanningTest, ModtimeSkipping_ModifyFilesSameHash_BothProcess)
{
using namespace AzToolsFramework::AssetSystem;
SetFileContents(m_data->m_absolutePath[1].toUtf8().constData(), "hello world");
AssetUtilities::SetUseFileHashOverride(true, true);
QSet<AssetFileInfo> filePaths = BuildFileSet();
SimulateAssetScanner(filePaths);
// Even though we're only updating one file, we're expecting 2 createJob calls because our test file is a dependency that triggers
// the other test file to process as well
ExpectWork(2, 2);
ProcessAssetJobs();
m_data->m_mockBuilderInfoHandler.m_createJobsCount = 0;
m_data->m_processResults.clear();
m_data->m_deletedSources.clear();
// Make file 0 have the same contents as file 1
SetFileContents(m_data->m_absolutePath[0].toUtf8().constData(), "hello world");
filePaths = BuildFileSet();
SimulateAssetScanner(filePaths);
ExpectWork(1, 1);
}
TEST_F(ModtimeScanningTest, ModtimeSkipping_ModifyMetadataFile)
{
using namespace AzToolsFramework::AssetSystem;
SetFileContents(m_data->m_absolutePath[2].toUtf8().constData(), "hello world");
AssetUtilities::SetUseFileHashOverride(true, true);
QSet<AssetFileInfo> filePaths = BuildFileSet();
SimulateAssetScanner(filePaths);
// Even though we're only updating one file, we're expecting 2 createJob calls because our test file is a metadata file
// that triggers the source file which is a dependency that triggers the other test file to process as well
ExpectWork(2, 2);
}
TEST_F(ModtimeScanningTest, ModtimeSkipping_DeleteFile)
{
using namespace AzToolsFramework::AssetSystem;
AssetUtilities::SetUseFileHashOverride(true, true);
ASSERT_TRUE(QFile::remove(m_data->m_absolutePath[0]));
// Feed in ONLY one file (the one we didn't delete)
QSet<AssetFileInfo> filePaths;
QFileInfo fileInfo(m_data->m_absolutePath[1]);
auto modtime = fileInfo.lastModified();
AZ::u64 fileSize = fileInfo.size();
filePaths.insert(AssetFileInfo(m_data->m_absolutePath[1], modtime, fileSize, &m_config->GetScanFolderAt(0), false));
SimulateAssetScanner(filePaths);
QElapsedTimer timer;
timer.start();
do
{
QCoreApplication::processEvents(QEventLoop::AllEvents, 10);
} while (m_data->m_deletedSources.size() < m_data->m_relativePathFromWatchFolder[0].size() && timer.elapsed() < 5000);
ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 0);
ASSERT_EQ(m_data->m_processResults.size(), 0);
ASSERT_THAT(m_data->m_deletedSources, testing::ElementsAre(m_data->m_relativePathFromWatchFolder[0]));
}
TEST_F(ModtimeScanningTest, ReprocessRequest_FileNotModified_FileProcessed)
{
using namespace AzToolsFramework::AssetSystem;
m_assetProcessorManager->RequestReprocess(m_data->m_absolutePath[0]);
ASSERT_TRUE(BlockUntilIdle(5000));
ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 1);
ASSERT_EQ(m_data->m_processResults.size(), 1);
}
TEST_F(ModtimeScanningTest, ReprocessRequest_SourceWithDependency_BothWillProcess)
{
using namespace AzToolsFramework::AssetSystem;
using SourceFileDependencyEntry = AzToolsFramework::AssetDatabase::SourceFileDependencyEntry;
SourceFileDependencyEntry newEntry1;
newEntry1.m_sourceDependencyID = AzToolsFramework::AssetDatabase::InvalidEntryId;
newEntry1.m_builderGuid = AZ::Uuid::CreateRandom();
newEntry1.m_source = m_data->m_absolutePath[0].toUtf8().constData();
newEntry1.m_dependsOnSource = m_data->m_absolutePath[1].toUtf8().constData();
newEntry1.m_typeOfDependency = SourceFileDependencyEntry::DEP_SourceToSource;
m_assetProcessorManager->RequestReprocess(m_data->m_absolutePath[0]);
ASSERT_TRUE(BlockUntilIdle(5000));
ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 1);
ASSERT_EQ(m_data->m_processResults.size(), 1);
m_assetProcessorManager->RequestReprocess(m_data->m_absolutePath[1]);
ASSERT_TRUE(BlockUntilIdle(5000));
ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 3);
ASSERT_EQ(m_data->m_processResults.size(), 3);
}
TEST_F(ModtimeScanningTest, ReprocessRequest_RequestFolder_SourceAssetsWillProcess)
{
using namespace AzToolsFramework::AssetSystem;
const auto& scanFolder = m_config->GetScanFolderAt(0);
QString scanPath = scanFolder.ScanPath();
m_assetProcessorManager->RequestReprocess(scanPath);
ASSERT_TRUE(BlockUntilIdle(5000));
// two text files are source assets, assetinfo is not
ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 2);
ASSERT_EQ(m_data->m_processResults.size(), 2);
}
void DeleteTest::SetUp()
{
AssetProcessorManagerTest::SetUp();
m_data = AZStd::make_unique<StaticData>();
// We don't want the mock application manager to provide builder descriptors, mockBuilderInfoHandler will provide our own
m_mockApplicationManager->BusDisconnect();
m_data->m_mockBuilderInfoHandler.m_builderDesc = m_data->m_mockBuilderInfoHandler.CreateBuilderDesc(
"test builder", "{DF09DDC0-FD22-43B6-9E22-22C8574A6E1E}",
{ AssetBuilderSDK::AssetBuilderPattern("*.txt", AssetBuilderSDK::AssetBuilderPattern::Wildcard) });
m_data->m_mockBuilderInfoHandler.BusConnect();
ASSERT_TRUE(m_mockApplicationManager->GetBuilderByID("txt files", m_data->m_builderTxtBuilder));
SetUpAssetProcessorManager();
auto createFileAndAddToDatabaseFunc = [this](const AssetProcessor::ScanFolderInfo* scanFolder, QString file)
{
using namespace AzToolsFramework::AssetDatabase;
QString watchFolderPath = scanFolder->ScanPath();
QString absPath(QDir(watchFolderPath).absoluteFilePath(file));
UnitTestUtils::CreateDummyFile(absPath);
m_data->m_absolutePath.push_back(absPath);
AzToolsFramework::AssetDatabase::FileDatabaseEntry fileEntry;
fileEntry.m_fileName = file.toUtf8().constData();
fileEntry.m_modTime = 0;
fileEntry.m_isFolder = false;
fileEntry.m_scanFolderPK = scanFolder->ScanFolderID();
bool entryAlreadyExists;
ASSERT_TRUE(m_assetProcessorManager->m_stateData->InsertFile(fileEntry, entryAlreadyExists));
ASSERT_FALSE(entryAlreadyExists);
};
// Create test files
QDir tempPath(m_tempDir.path());
const auto* scanFolder1 = m_config->GetScanFolderByPath(tempPath.absoluteFilePath("subfolder1"));
const auto* scanFolder4 = m_config->GetScanFolderByPath(tempPath.absoluteFilePath("subfolder4"));
createFileAndAddToDatabaseFunc(scanFolder1, QString("textures/a.txt"));
createFileAndAddToDatabaseFunc(scanFolder4, QString("textures/b.txt"));
// Run the test files through AP all the way to processing stage
QSet<AssetFileInfo> filePaths = BuildFileSet();
SimulateAssetScanner(filePaths);
ASSERT_TRUE(BlockUntilIdle(5000));
ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 2);
ASSERT_EQ(m_data->m_processResults.size(), 2);
ASSERT_EQ(m_data->m_deletedSources.size(), 0);
ProcessAssetJobs();
m_data->m_processResults.clear();
m_data->m_mockBuilderInfoHandler.m_createJobsCount = 0;
// Reboot the APM since we added stuff to the database that needs to be loaded on-startup of the APM
m_assetProcessorManager.reset(new AssetProcessorManager_Test(m_config.get()));
SetUpAssetProcessorManager();
}
TEST_F(DeleteTest, DeleteFolderSharedAcrossTwoScanFolders_CorrectFileAndFolderAreDeletedFromCache)
{
// There was a bug where AP wasn't repopulating the "known folders" list when modtime skipping was enabled and no work was needed
// As a result, deleting a folder didn't count as a "folder", so the wrong code path was taken. This test makes sure the correct
// deletion events fire
using namespace AzToolsFramework::AssetSystem;
// Feed in the files from the asset scanner, no jobs should run since they're already up-to-date
QSet<AssetFileInfo> filePaths = BuildFileSet();
SimulateAssetScanner(filePaths);
ExpectNoWork();
// Delete one of the folders
QDir tempPath(m_tempDir.path());
QString absPath(tempPath.absoluteFilePath("subfolder1/textures"));
QDir(absPath).removeRecursively();
AZStd::vector<AZStd::string> deletedFolders;
QObject::connect(
m_assetProcessorManager.get(), &AssetProcessor::AssetProcessorManager::SourceFolderDeleted,
[&deletedFolders](QString file)
{
deletedFolders.push_back(file.toUtf8().constData());
});
m_assetProcessorManager->AssessDeletedFile(absPath);
ASSERT_TRUE(BlockUntilIdle(5000));
ASSERT_THAT(m_data->m_deletedSources, testing::UnorderedElementsAre("textures/a.txt"));
ASSERT_THAT(deletedFolders, testing::UnorderedElementsAre("textures"));
}
TEST_F(LockedFileTest, DeleteFile_LockedProduct_DeleteFails)
{
auto theFile = m_data->m_absolutePath[1].toUtf8();
const char* theFileString = theFile.constData();
auto [sourcePath, productPath] = *m_data->m_productPaths.find(theFileString);
{
QFile file(theFileString);
file.remove();
}
ASSERT_GT(m_data->m_productPaths.size(), 0);
QFile product(productPath);
ASSERT_TRUE(product.open(QIODevice::ReadOnly));
// Check if we can delete the file now, if we can't, proceed with the test
// If we can, it means the OS running this test doesn't lock open files so there's nothing to test
if (!AZ::IO::SystemFile::Delete(productPath.toUtf8().constData()))
{
QMetaObject::invokeMethod(
m_assetProcessorManager.get(), "AssessDeletedFile", Qt::QueuedConnection, Q_ARG(QString, QString(theFileString)));
EXPECT_TRUE(BlockUntilIdle(5000));
EXPECT_TRUE(QFile::exists(productPath));
EXPECT_EQ(m_data->m_deletedSources.size(), 0);
}
else
{
SUCCEED() << "Skipping test. OS does not lock open files.";
}
}
TEST_F(LockedFileTest, DeleteFile_LockedProduct_DeletesWhenReleased)
{
// This test is intended to verify the AP will successfully retry deleting a source asset
// when one of its product assets is locked temporarily
// We'll lock the file by holding it open
auto theFile = m_data->m_absolutePath[1].toUtf8();
const char* theFileString = theFile.constData();
auto [sourcePath, productPath] = *m_data->m_productPaths.find(theFileString);
{
QFile file(theFileString);
file.remove();
}
ASSERT_GT(m_data->m_productPaths.size(), 0);
QFile product(productPath);
// Open the file and keep it open to lock it
// We'll start a thread later to unlock the file
// This will allow us to test how AP handles trying to delete a locked file
ASSERT_TRUE(product.open(QIODevice::ReadOnly));
// Check if we can delete the file now, if we can't, proceed with the test
// If we can, it means the OS running this test doesn't lock open files so there's nothing to test
if (!AZ::IO::SystemFile::Delete(productPath.toUtf8().constData()))
{
m_deleteCounter = 0;
// Set up a callback which will fire after at least 1 retry
// Unlock the file at that point so AP can successfully delete it
m_callback = [&product]()
{
product.close();
};
QMetaObject::invokeMethod(
m_assetProcessorManager.get(), "AssessDeletedFile", Qt::QueuedConnection, Q_ARG(QString, QString(theFileString)));
EXPECT_TRUE(BlockUntilIdle(5000));
EXPECT_FALSE(QFile::exists(productPath));
EXPECT_EQ(m_data->m_deletedSources.size(), 1);
EXPECT_GT(m_deleteCounter, 1); // Make sure the AP tried more than once to delete the file
m_errorAbsorber->ExpectAsserts(0);
}
else
{
SUCCEED() << "Skipping test. OS does not lock open files.";
}
}
}

@ -0,0 +1,105 @@
/*
* 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
#include <tests/assetmanager/AssetProcessorManagerTest.h>
namespace UnitTests
{
struct ModtimeScanningTest : AssetProcessorManagerTest
{
void SetUpAssetProcessorManager();
void SetUp() override;
void TearDown() override;
void ProcessAssetJobs();
void SimulateAssetScanner(QSet<AssetProcessor::AssetFileInfo> filePaths);
QSet<AssetProcessor::AssetFileInfo> BuildFileSet();
void ExpectWork(int createJobs, int processJobs);
void ExpectNoWork();
void SetFileContents(QString filePath, QString contents);
struct StaticData
{
QString m_relativePathFromWatchFolder[3];
AZStd::vector<QString> m_absolutePath;
AZStd::vector<AssetProcessor::JobDetails> m_processResults;
AZStd::unordered_multimap<AZStd::string, QString> m_productPaths;
AZStd::vector<QString> m_deletedSources;
AZStd::shared_ptr<AssetProcessor::InternalMockBuilder> m_builderTxtBuilder;
MockBuilderInfoHandler m_mockBuilderInfoHandler;
};
AZStd::unique_ptr<StaticData> m_data;
};
struct DeleteTest : ModtimeScanningTest
{
void SetUp() override;
};
struct LockedFileTest
: ModtimeScanningTest
, AssetProcessor::ConnectionBus::Handler
{
MOCK_METHOD3(SendRaw, size_t(unsigned, unsigned, const QByteArray&));
MOCK_METHOD3(SendPerPlatform, size_t(unsigned, const AzFramework::AssetSystem::BaseAssetProcessorMessage&, const QString&));
MOCK_METHOD4(SendRawPerPlatform, size_t(unsigned, unsigned, const QByteArray&, const QString&));
MOCK_METHOD2(SendRequest, unsigned(const AzFramework::AssetSystem::BaseAssetProcessorMessage&, const ResponseCallback&));
MOCK_METHOD2(SendResponse, size_t(unsigned, const AzFramework::AssetSystem::BaseAssetProcessorMessage&));
MOCK_METHOD1(RemoveResponseHandler, void(unsigned));
size_t Send(unsigned, const AzFramework::AssetSystem::BaseAssetProcessorMessage& message) override
{
using SourceFileNotificationMessage = AzToolsFramework::AssetSystem::SourceFileNotificationMessage;
switch (message.GetMessageType())
{
case SourceFileNotificationMessage::MessageType:
if (const auto sourceFileMessage = azrtti_cast<const SourceFileNotificationMessage*>(&message);
sourceFileMessage != nullptr &&
sourceFileMessage->m_type == SourceFileNotificationMessage::NotificationType::FileRemoved)
{
// The File Remove message will occur before an attempt to delete the file
// Wait for more than 1 File Remove message.
// This indicates the AP has attempted to delete the file once, failed to do so and is now retrying
++m_deleteCounter;
if (m_deleteCounter > 1 && m_callback)
{
m_callback();
m_callback = {}; // Unset it to be safe, we only intend to run the callback once
}
}
break;
default:
break;
}
return 0;
}
void SetUp() override
{
ModtimeScanningTest::SetUp();
AssetProcessor::ConnectionBus::Handler::BusConnect(0);
}
void TearDown() override
{
AssetProcessor::ConnectionBus::Handler::BusDisconnect();
ModtimeScanningTest::TearDown();
}
AZStd::atomic_int m_deleteCounter{ 0 };
AZStd::function<void()> m_callback;
};
}

@ -98,15 +98,17 @@ namespace O3DE::ProjectManager
constexpr int minHeaderSectionWidth = 100;
AdjustableHeaderWidget* listHeaderWidget = new AdjustableHeaderWidget(
QStringList{ tr("Gem Name"), tr("Gem Summary"), tr("Status") },
QStringList{ tr("Gem Image"), tr("Gem Name"), tr("Gem Summary"), tr("Status") },
QVector<int>{
GemItemDelegate::s_defaultSummaryStartX - 30,
GemPreviewImageWidth + AdjustableHeaderWidget::s_headerTextIndent,
-GemPreviewImageWidth - AdjustableHeaderWidget::s_headerTextIndent + GemItemDelegate::s_defaultSummaryStartX - 30,
0, // Section is set to stretch to fit
GemItemDelegate::s_buttonWidth + GemItemDelegate::s_itemMargins.left() + GemItemDelegate::s_itemMargins.right() + GemItemDelegate::s_contentMargins.right()
GemItemDelegate::s_statusIconSize + GemItemDelegate::s_statusButtonSpacing + GemItemDelegate::s_buttonWidth + GemItemDelegate::s_contentMargins.right()
},
minHeaderSectionWidth,
QVector<QHeaderView::ResizeMode>
{
QHeaderView::ResizeMode::Fixed,
QHeaderView::ResizeMode::Interactive,
QHeaderView::ResizeMode::Stretch,
QHeaderView::ResizeMode::Fixed

@ -116,7 +116,7 @@ namespace O3DE::ProjectManager
// Separating line
QFrame* hLine = new QFrame();
hLine->setFrameShape(QFrame::HLine);
hLine->setStyleSheet("color: #666666;");
hLine->setObjectName("horizontalSeparatingLine");
vLayout->addWidget(hLine);
UpdateCollapseState();

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

Loading…
Cancel
Save