You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
458 lines
17 KiB
C++
458 lines
17 KiB
C++
/*
|
|
* 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 <Tests/UI/UIFixture.h>
|
|
#include <Tests/UI/ModalPopupHandler.h>
|
|
#include <Tests/Mocks/AtomRenderPlugin.h>
|
|
#include <Tests/Mocks/PhysicsSystem.h>
|
|
#include <Tests/D6JointLimitConfiguration.h>
|
|
#include <Integration/System/SystemCommon.h>
|
|
|
|
#include <EMotionStudio/EMStudioSDK/Source/EMStudioManager.h>
|
|
#include <EMotionStudio/EMStudioSDK/Source/PluginManager.h>
|
|
#include <EMotionStudio/Plugins/StandardPlugins/Source/MotionSetsWindow/MotionSetsWindowPlugin.h>
|
|
#include <EMotionStudio/Plugins/StandardPlugins/Source/AnimGraph/AnimGraphPlugin.h>
|
|
#include <EMotionStudio/Plugins/StandardPlugins/Source/AnimGraph/BlendGraphWidget.h>
|
|
#include <EMotionStudio/Plugins/StandardPlugins/Source/AnimGraph/ParameterWindow.h>
|
|
#include <EMotionStudio/Plugins/StandardPlugins/Source/AnimGraph/ParameterCreateEditDialog.h>
|
|
|
|
#include <AzCore/IO/Path/Path.h>
|
|
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
|
|
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
|
|
#include <AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx>
|
|
#include <AzToolsFramework/UI/PropertyEditor/ReflectedPropertyEditor.hxx>
|
|
#include <AzQtComponents/Components/Titlebar.h>
|
|
#include <AzQtComponents/Components/DockBarButton.h>
|
|
#include <AzQtComponents/Components/WindowDecorationWrapper.h>
|
|
#include <AzQtComponents/Components/StyleManager.h>
|
|
|
|
#include <Editor/Plugins/SimulatedObject/SimulatedObjectColliderWidget.h>
|
|
#include <Editor/Plugins/SimulatedObject/SimulatedObjectWidget.h>
|
|
#include <Editor/Plugins/SimulatedObject/SimulatedJointWidget.h>
|
|
|
|
#include <Editor/ReselectingTreeView.h>
|
|
|
|
#include <QtTest>
|
|
#include <QApplication>
|
|
#include <QWidget>
|
|
#include <QToolBar>
|
|
#include <QPushButton>
|
|
|
|
namespace EMotionFX
|
|
{
|
|
void MakeQtApplicationBase::SetUp()
|
|
{
|
|
int argc = 0;
|
|
m_uiApp = new QApplication(argc, nullptr);
|
|
|
|
AzToolsFramework::EditorEvents::Bus::Broadcast(&AzToolsFramework::EditorEvents::NotifyRegisterViews);
|
|
|
|
AZ::IO::FixedMaxPath engineRootPath;
|
|
if (auto settingsRegistry = AZ::SettingsRegistry::Get())
|
|
{
|
|
settingsRegistry->Get(engineRootPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
|
|
}
|
|
(new AzQtComponents::StyleManager(m_uiApp))->initialize(m_uiApp, engineRootPath);
|
|
}
|
|
|
|
MakeQtApplicationBase::~MakeQtApplicationBase()
|
|
{
|
|
delete m_uiApp;
|
|
}
|
|
|
|
void UIFixture::SetupQtAndFixtureBase()
|
|
{
|
|
UIFixtureBase::SetUp();
|
|
MakeQtApplicationBase::SetUp();
|
|
// Set ignore visibilty so that the visibility check can be ignored in plugins
|
|
EMStudio::GetManager()->SetIgnoreVisibility(true);
|
|
}
|
|
|
|
void UIFixture::SetupPluginWindows()
|
|
{
|
|
// Plugins have to be created after both the QApplication object and
|
|
// after the SystemComponent
|
|
const size_t numPlugins = EMStudio::GetPluginManager()->GetNumPlugins();
|
|
for (size_t i = 0; i < numPlugins; ++i)
|
|
{
|
|
EMStudio::EMStudioPlugin* plugin = EMStudio::GetPluginManager()->GetPlugin(i);
|
|
EMStudio::GetPluginManager()->CreateWindowOfType(plugin->GetName());
|
|
}
|
|
}
|
|
|
|
void UIFixture::ReflectMockedSystems()
|
|
{
|
|
if (ShouldReflectPhysicSystem())
|
|
{
|
|
AZ::SerializeContext* serializeContext = GetSerializeContext();
|
|
|
|
Physics::MockPhysicsSystem::Reflect(serializeContext); // Required by Ragdoll plugin to fake PhysX Gem is available
|
|
D6JointLimitConfiguration::Reflect(serializeContext);
|
|
}
|
|
}
|
|
|
|
void UIFixture::OnRegisterPlugin()
|
|
{
|
|
EMStudio::PluginManager* pluginManager = EMStudio::EMStudioManager::GetInstance()->GetPluginManager();
|
|
pluginManager->RegisterPlugin(new EMStudio::MockAtomRenderPlugin());
|
|
}
|
|
|
|
void UIFixture::SetUp()
|
|
{
|
|
Integration::SystemNotificationBus::Handler::BusConnect();
|
|
|
|
using namespace testing;
|
|
SetupQtAndFixtureBase();
|
|
ReflectMockedSystems();
|
|
SetupPluginWindows();
|
|
|
|
m_animGraphPlugin = static_cast<EMStudio::AnimGraphPlugin*>(EMStudio::GetPluginManager()->FindActivePlugin(EMStudio::AnimGraphPlugin::CLASS_ID));
|
|
ON_CALL(m_assetSystemRequestMock, GetFullSourcePathFromRelativeProductPath(_, _))
|
|
.WillByDefault(Return(true));
|
|
m_assetSystemRequestMock.BusConnect();
|
|
}
|
|
|
|
void UIFixture::TearDown()
|
|
{
|
|
m_assetSystemRequestMock.BusDisconnect();
|
|
Integration::SystemNotificationBus::Handler::BusDisconnect();
|
|
CloseAllNotificationWindows();
|
|
|
|
DeselectAllAnimGraphNodes();
|
|
|
|
// Restore visibility
|
|
EMStudio::GetManager()->SetIgnoreVisibility(false);
|
|
|
|
UIFixtureBase::TearDown();
|
|
}
|
|
|
|
QWidget* UIFixture::FindTopLevelWidget(const QString& objectName)
|
|
{
|
|
//const QWidgetList topLevelWidgets = QApplication::topLevelWidgets(); // TODO: Check why QDialogs are no windows anymore and thus the topLevelWidgets() does not include them.
|
|
const QWidgetList topLevelWidgets = QApplication::allWidgets();
|
|
auto iterator = AZStd::find_if(topLevelWidgets.begin(), topLevelWidgets.end(),
|
|
[objectName](const QWidget* widget)
|
|
{
|
|
return (widget->objectName() == objectName);
|
|
});
|
|
|
|
if (iterator != topLevelWidgets.end())
|
|
{
|
|
return *iterator;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
QWidget* UIFixture::GetWidgetFromToolbar(const QToolBar* toolbar, const QString &widgetText)
|
|
{
|
|
/*
|
|
Searches a Toolbar for an action whose text exactly matches the widgetText parameter.
|
|
Returns the widget by pointer if found, nullptr otherwise.
|
|
*/
|
|
for (QAction* action : toolbar->actions())
|
|
{
|
|
if (action->text() == widgetText)
|
|
{
|
|
return toolbar->widgetForAction(action);
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
QWidget* UIFixture::GetWidgetFromToolbarWithObjectName(const QToolBar* toolbar, const QString &objectName)
|
|
{
|
|
for (QAction* action : toolbar->actions())
|
|
{
|
|
if (action->objectName() == objectName)
|
|
{
|
|
return toolbar->widgetForAction(action);
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
QWidget* UIFixture::GetWidgetWithNameFromNamedToolbar(const QWidget* widget, const QString &toolBarName, const QString &objectName)
|
|
{
|
|
auto toolBar = widget->findChild<QToolBar*>(toolBarName);
|
|
if (!toolBar)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
return UIFixture::GetWidgetFromToolbarWithObjectName(toolBar, objectName);
|
|
}
|
|
|
|
QAction* UIFixture::GetNamedAction(const QWidget* widget, const QString& actionText)
|
|
{
|
|
const QList<QAction*> actions = widget->findChildren<QAction*>();
|
|
|
|
for (QAction* action : actions)
|
|
{
|
|
if (action->text() == actionText)
|
|
{
|
|
return action;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void UIFixture::ExecuteCommands(std::vector<std::string> commands)
|
|
{
|
|
AZStd::string result;
|
|
for (const auto& commandStr : commands)
|
|
{
|
|
if (commandStr == "UNDO")
|
|
{
|
|
EXPECT_TRUE(CommandSystem::GetCommandManager()->Undo(result)) << "Undo: " << result.c_str();
|
|
}
|
|
else if (commandStr == "REDO")
|
|
{
|
|
EXPECT_TRUE(CommandSystem::GetCommandManager()->Redo(result)) << "Redo: " << result.c_str();
|
|
}
|
|
else
|
|
{
|
|
EXPECT_TRUE(CommandSystem::GetCommandManager()->ExecuteCommand(commandStr.c_str(), result)) << commandStr.c_str() << ": " << result.c_str();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool UIFixture::GetActionFromContextMenu(QAction*& action, const QMenu* contextMenu, const QString& actionName)
|
|
{
|
|
const auto contextMenuActions = contextMenu->actions();
|
|
auto contextAction = AZStd::find_if(contextMenuActions.begin(), contextMenuActions.end(), [actionName](const QAction* action) {
|
|
return action->text() == actionName;
|
|
});
|
|
action = *contextAction;
|
|
return contextAction != contextMenuActions.end();
|
|
}
|
|
|
|
void UIFixture::CloseAllPlugins()
|
|
{
|
|
const EMStudio::PluginManager::PluginVector plugins = EMStudio::GetPluginManager()->GetActivePlugins();
|
|
for (EMStudio::EMStudioPlugin* plugin : plugins)
|
|
{
|
|
EMStudio::GetPluginManager()->RemoveActivePlugin(plugin);
|
|
}
|
|
}
|
|
|
|
void UIFixture::CloseAllNotificationWindows()
|
|
{
|
|
while (EMStudio::GetManager()->GetNotificationWindowManager()->GetNumNotificationWindow() > 0)
|
|
{
|
|
EMStudio::NotificationWindow* window = EMStudio::GetManager()->GetNotificationWindowManager()->GetNotificationWindow(0);
|
|
delete window;
|
|
}
|
|
}
|
|
|
|
void UIFixture::DeselectAllAnimGraphNodes()
|
|
{
|
|
// Unselectany selected anim graph nodes.
|
|
EMStudio::AnimGraphPlugin*animGraphPlugin = static_cast<EMStudio::AnimGraphPlugin*>(EMStudio::GetPluginManager()->FindActivePlugin(EMStudio::AnimGraphPlugin::CLASS_ID));
|
|
if (!animGraphPlugin)
|
|
{
|
|
return;
|
|
}
|
|
|
|
EMStudio::BlendGraphWidget* graphWidget = animGraphPlugin->GetGraphWidget();
|
|
if (!graphWidget)
|
|
{
|
|
return;
|
|
}
|
|
|
|
EMStudio::NodeGraph* nodeGraph = graphWidget->GetActiveGraph();
|
|
if (!nodeGraph)
|
|
{
|
|
return;
|
|
}
|
|
|
|
nodeGraph->UnselectAllNodes();
|
|
ASSERT_EQ(nodeGraph->GetSelectedAnimGraphNodes().size(), 0) << "No node is selected";
|
|
}
|
|
|
|
void UIFixture::BringUpContextMenu(QObject* widget, const QPoint& pos, const QPoint& globalPos)
|
|
{
|
|
QContextMenuEvent cme(QContextMenuEvent::Mouse, pos, globalPos);
|
|
QSpontaneKeyEvent::setSpontaneous(&cme);
|
|
QApplication::instance()->notify(
|
|
widget,
|
|
&cme
|
|
);
|
|
}
|
|
|
|
void UIFixture::BringUpContextMenu(const QTreeView* treeView, const QRect& rect)
|
|
{
|
|
BringUpContextMenu(treeView->viewport(), rect.center(), treeView->viewport()->mapTo(treeView->window(), rect.center()));
|
|
}
|
|
|
|
void UIFixture::BringUpContextMenu(const QTreeWidget* treeWidget, const QRect& rect)
|
|
{
|
|
QContextMenuEvent cme(QContextMenuEvent::Mouse, rect.center(), treeWidget->viewport()->mapTo(treeWidget->window(), rect.center()));
|
|
QSpontaneKeyEvent::setSpontaneous(&cme);
|
|
QApplication::instance()->notify(
|
|
treeWidget->viewport(),
|
|
&cme
|
|
);
|
|
}
|
|
|
|
void UIFixture::SelectIndexes(const QModelIndexList& indexList, QTreeView* treeView, const int start, const int end)
|
|
{
|
|
QItemSelection selection;
|
|
for (int i = start; i <= end; ++i)
|
|
{
|
|
const QModelIndex index = indexList[i];
|
|
EXPECT_TRUE(index.isValid()) << "Unable to find a model index for the joint of the actor";
|
|
|
|
selection.select(index, index);
|
|
}
|
|
treeView->selectionModel()->select(selection, QItemSelectionModel::Select | QItemSelectionModel::Rows);
|
|
treeView->scrollTo(indexList[end]);
|
|
}
|
|
|
|
AzToolsFramework::PropertyRowWidget* UIFixture::GetNamedPropertyRowWidgetFromReflectedPropertyEditor(AzToolsFramework::ReflectedPropertyEditor* rpe, const QString& name)
|
|
{
|
|
// Search through the RPE's widgets to find the matching widget
|
|
const AzToolsFramework::ReflectedPropertyEditor::WidgetList& widgets = rpe->GetWidgets();
|
|
const auto foundWidget = AZStd::find_if(widgets.begin(), widgets.end(), [name](const auto& widgetIter)
|
|
{
|
|
return name == widgetIter.second->label();
|
|
});
|
|
return foundWidget != widgets.end() ? foundWidget->second : nullptr;
|
|
}
|
|
|
|
|
|
void UIFixture::TriggerContextMenuAction(QWidget* widget, const QString& actionname)
|
|
{
|
|
BringUpContextMenu(widget, QPoint(10, 10), widget->mapToGlobal(QPoint(10, 10)));
|
|
|
|
QMenu* menu = widget->findChild<QMenu*>();
|
|
ASSERT_TRUE(menu) << "Unable to find context menu.";
|
|
|
|
QAction* action = menu->findChild<QAction*>(actionname);
|
|
ASSERT_TRUE(action) << "Unable to find context menu action " << actionname.toUtf8().data();
|
|
action->trigger();
|
|
|
|
menu->close();
|
|
}
|
|
|
|
void UIFixture::TriggerModalContextMenuAction(QWidget* widget, const QString& actionname)
|
|
{
|
|
ModalPopupHandler modalPopupHandler;
|
|
|
|
bool actionComplete = false;
|
|
|
|
// Set up an action to be called when the menu is either triggered or a timeout occurs.
|
|
ActionCompletionCallback completionCallback = [&actionComplete, actionname](const QString& menu)
|
|
{
|
|
ASSERT_STREQ(menu.toUtf8().constData(), actionname.toUtf8().constData());
|
|
|
|
actionComplete = true;
|
|
};
|
|
|
|
modalPopupHandler.ShowContextMenuAndTriggerAction(widget, actionname, 3000, completionCallback);
|
|
|
|
// Shouldn't get here without the action being complete, but just to be safe...
|
|
// Cast to void to avoid nodiscard compiler warning.
|
|
static_cast<void>(QTest::qWaitFor([actionComplete]() {
|
|
return actionComplete;
|
|
}, 10000));
|
|
}
|
|
|
|
AzQtComponents::WindowDecorationWrapper* UIFixture::GetDecorationWrapperForMainWindow() const
|
|
{
|
|
return static_cast<AzQtComponents::WindowDecorationWrapper*>(EMStudio::GetMainWindow()->parent());
|
|
}
|
|
|
|
AzQtComponents::TitleBar* UIFixture::GetTitleBarForMainWindow() const
|
|
{
|
|
return GetDecorationWrapperForMainWindow()->findChild<AzQtComponents::TitleBar*>(QString(), Qt::FindDirectChildrenOnly);
|
|
}
|
|
|
|
AzQtComponents::DockBarButton* UIFixture::GetDockBarButtonForMainWindow(AzQtComponents::DockBarButton::WindowDecorationButton buttonType) const
|
|
{
|
|
const QList<AzQtComponents::DockBarButton*>buttons = GetTitleBarForMainWindow()->findChildren<AzQtComponents::DockBarButton*>();
|
|
|
|
for (AzQtComponents::DockBarButton* button : buttons)
|
|
{
|
|
if (button->buttonType() == buttonType)
|
|
{
|
|
return button;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void UIFixture::SelectActor(Actor* actor)
|
|
{
|
|
AZStd::string result;
|
|
AZStd::string cmd;
|
|
cmd = AZStd::string::format("Select actor %d", actor->GetID());
|
|
EXPECT_TRUE(CommandSystem::GetCommandManager()->ExecuteCommand(cmd.c_str(), result)) << result.c_str();
|
|
}
|
|
|
|
void UIFixture::SelectActorInstance(ActorInstance* actorInstance)
|
|
{
|
|
SelectActor(actorInstance->GetActor());
|
|
}
|
|
|
|
EMStudio::MotionSetsWindowPlugin* UIFixture::GetMotionSetsWindowPlugin()
|
|
{
|
|
return static_cast<EMStudio::MotionSetsWindowPlugin*>(EMStudio::GetPluginManager()->FindActivePlugin(EMStudio::MotionSetsWindowPlugin::CLASS_ID));
|
|
}
|
|
|
|
EMStudio::MotionSetManagementWindow* UIFixture::GetMotionSetManagementWindow()
|
|
{
|
|
EMStudio::MotionSetsWindowPlugin* plugin = GetMotionSetsWindowPlugin();
|
|
EXPECT_TRUE(plugin);
|
|
|
|
return plugin->GetManagementWindow();
|
|
}
|
|
|
|
void UIFixture::CreateAnimGraphParameter(const AZStd::string& name)
|
|
{
|
|
EMStudio::ParameterWindow* parameterWindow = m_animGraphPlugin->GetParameterWindow();
|
|
parameterWindow->OnAddParameter();
|
|
|
|
EMStudio::ParameterCreateEditDialog* paramDialog = parameterWindow->findChild< EMStudio::ParameterCreateEditDialog*>();
|
|
ASSERT_TRUE(paramDialog);
|
|
|
|
const AZStd::unique_ptr<EMotionFX::Parameter>& param = paramDialog->GetParameter();
|
|
param->SetName(name);
|
|
const size_t numParams = m_animGraphPlugin->GetActiveAnimGraph()->GetNumParameters();
|
|
|
|
QPushButton* createButton = paramDialog->findChild<QPushButton*>("EMFX.ParameterCreateEditDialog.CreateApplyButton");
|
|
QTest::mouseClick(createButton, Qt::LeftButton);
|
|
|
|
ASSERT_EQ(m_animGraphPlugin->GetActiveAnimGraph()->GetNumParameters(), numParams + 1);
|
|
}
|
|
|
|
SimulatedObjectColliderWidget* UIFixture::GetSimulatedObjectColliderWidget() const
|
|
{
|
|
const EMotionFX::SimulatedObjectWidget* simulatedObjectWidget = static_cast<EMotionFX::SimulatedObjectWidget*>(EMStudio::GetPluginManager()->FindActivePlugin(EMotionFX::SimulatedObjectWidget::CLASS_ID));
|
|
EXPECT_TRUE(simulatedObjectWidget) << "Simulated Object plugin not found!";
|
|
if (!simulatedObjectWidget)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
const SimulatedJointWidget* simulatedJointWidget = simulatedObjectWidget->GetSimulatedJointWidget();
|
|
EXPECT_TRUE(simulatedJointWidget) << "SimulatedJointWidget not found.";
|
|
if (!simulatedJointWidget)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
SimulatedObjectColliderWidget* simulatedObjectColliderWidget = simulatedJointWidget->findChild<SimulatedObjectColliderWidget*>();
|
|
EXPECT_TRUE(simulatedObjectColliderWidget) << "SimulatedJointWidget not found.";
|
|
|
|
return simulatedObjectColliderWidget;
|
|
}
|
|
} // namespace EMotionFX
|