git mv Code\Sandbox\Editor Code/Editor

Signed-off-by: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com>
This commit is contained in:
Esteban Papp
2021-06-29 12:41:59 -07:00
parent 9f0bbf3b74
commit e34e36cb35
1415 changed files with 0 additions and 0 deletions
File diff suppressed because it is too large Load Diff
+251
View File
@@ -0,0 +1,251 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef CRYINCLUDE_EDITOR_2DVIEWPORT_H
#define CRYINCLUDE_EDITOR_2DVIEWPORT_H
#pragma once
#if !defined(Q_MOC_RUN)
#include "Viewport.h"
#include "Objects/DisplayContext.h"
#endif
class QMenu;
/** 2D Viewport used mostly for indoor editing, Front/Top/Left viewports.
*/
class Q2DViewport
: public QtViewport
{
Q_OBJECT
public:
enum ViewMode
{
NothingMode = 0,
ScrollZoomMode,
};
Q2DViewport(QWidget* parent = nullptr);
virtual ~Q2DViewport();
virtual void SetType(EViewportType type);
virtual EViewportType GetType() const { return m_viewType; }
virtual float GetAspectRatio() const { return 1.0f; };
virtual void ResetContent();
virtual void UpdateContent(int flags);
public slots:
// Called every frame to update viewport.
virtual void Update();
public:
//! Map world space position to viewport position.
virtual QPoint WorldToView(const Vec3& wp) const;
virtual QPoint WorldToViewParticleEditor(const Vec3& wp, int width, int height) const; //Eric@conffx
//! Map viewport position to world space position.
virtual Vec3 ViewToWorld(const QPoint& vp, bool* collideWithTerrain = nullptr, bool onlyTerrain = false, bool bSkipVegetation = false, bool bTestRenderMesh = false, bool* collideWithObject = nullptr) const override;
//! Map viewport position to world space ray from camera.
virtual void ViewToWorldRay(const QPoint& vp, Vec3& raySrc, Vec3& rayDir) const;
void OnTitleMenu(QMenu* menu) override;
virtual bool HitTest(const QPoint& point, HitContext& hitInfo) override;
virtual bool IsBoundsVisible(const AABB& box) const;
// ovverided from CViewport.
float GetScreenScaleFactor(const Vec3& worldPoint) const override;
float GetScreenScaleFactor([[maybe_unused]] const CCamera& camera, [[maybe_unused]] const Vec3& object_position) override { return 1; } //Eric@conffx
// Overrided from CViewport.
void OnDragSelectRectangle(const QRect &rect, bool bNormalizeRect = false) override;
void CenterOnSelection() override;
void CenterOnAABB(const AABB& aabb) override;
/** Get 2D viewports origin.
*/
Vec3 GetOrigin2D() const;
/** Assign 2D viewports origin.
*/
void SetOrigin2D(const Vec3& org);
void SetShowViewMarker(bool bEnable) { m_bShowViewMarker = bEnable; }
void SetShowGrid(bool bShowGrid) { m_bShowGrid = bShowGrid; }
bool GetShowGrid() const { return m_bShowGrid; };
void SetShowObjectsInfo(bool bShowObjectsInfo) { m_bShowObjectsInfo = bShowObjectsInfo; }
bool GetShowObjectsInfo() const { return m_bShowObjectsInfo; }
void SetGridLines(bool bShowMinor, bool bShowMajor) { m_bShowMinorGridLines = bShowMinor; m_bShowMajorGridLines = bShowMajor; }
void SetGridLineNumbers(bool bShowNumbers) { m_bShowNumbers = bShowNumbers; }
void SetAutoAdjust(bool bAuto) { m_bAutoAdjustGrids = bAuto; }
protected:
enum EViewportAxis
{
VPA_XY,
VPA_XZ,
VPA_YZ,
VPA_YX,
};
void SetAxis(EViewportAxis axis);
// Scrolling / zooming related
virtual void SetScrollOffset(float x, float y, bool bLimits = true);
virtual void GetScrollOffset(float& x, float& y); // Only x and y components used.
virtual void SetZoom(float fZoomFactor, const QPoint& center);
// overrides from CViewport.
virtual void MakeConstructionPlane(int axis);
virtual const Matrix34& GetConstructionMatrix(RefCoordSys coordSys);
//! Calculate view transformation matrix.
virtual void CalculateViewTM();
virtual void SetViewMode(ViewMode eViewMode) { m_eViewMode = eViewMode; };
ViewMode GetViewMode() { return m_eViewMode; };
protected slots:
// Render
void Render();
protected:
// Draw everything.
virtual void Draw(DisplayContext& dc);
// Draw elements of viewport.
void DrawGrid(DisplayContext& dc, bool bNoXNumbers = false);
void DrawAxis(DisplayContext& dc);
void DrawSelection(DisplayContext& dc);
void DrawObjects(DisplayContext& dc);
void DrawViewerMarker(DisplayContext& dc);
AABB GetWorldBounds(const QPoint& p1, const QPoint& p2);
void OnLButtonDown(Qt::KeyboardModifiers modifiers, const QPoint& point) override;
void OnMButtonDown(Qt::KeyboardModifiers modifiers, const QPoint& point) override;
void OnMButtonUp(Qt::KeyboardModifiers modifiers, const QPoint& point) override;
void OnLButtonUp(Qt::KeyboardModifiers modifiers, const QPoint& point) override;
void OnMouseMove(Qt::KeyboardModifiers modifiers, Qt::MouseButtons buttons, const QPoint& point) override;
void resizeEvent(QResizeEvent* event) override;
void showEvent(QShowEvent* event) override;
void paintEvent(QPaintEvent* event) override;
int OnCreate();
void OnRButtonDown(Qt::KeyboardModifiers modifiers, const QPoint& point);
void OnRButtonUp(Qt::KeyboardModifiers modifiers, const QPoint& point);
void OnMouseWheel(Qt::KeyboardModifiers modifiers, short zDelta, const QPoint& pt);
void OnDestroy();
protected:
//////////////////////////////////////////////////////////////////////////
// Variables.
//////////////////////////////////////////////////////////////////////////
//! XY/XZ/YZ mode of this 2D viewport.
EViewportType m_viewType;
EViewportAxis m_axis;
//! Axis to cull normals with in this Viewport.
int m_cullAxis;
// Viewport origin point.
Vec3 m_origin2D;
// Scrolling / zooming related
QPoint m_cMousePos;
QPoint m_RMouseDownPos;
float m_prevZoomFactor;
QSize m_prevScrollOffset;
QRect m_rcSelect;
QRect m_rcClient;
AABB m_displayBounds;
bool m_bShowTerrain;
bool m_bShowViewMarker;
bool m_bShowGrid;
bool m_bShowObjectsInfo;
bool m_bShowMinorGridLines;
bool m_bShowMajorGridLines;
bool m_bShowNumbers;
bool m_bAutoAdjustGrids;
Matrix34 m_screenTM_Inverted;
float m_gridAlpha;
QColor m_colorGridText;
QColor m_colorAxisText;
QColor m_colorBackground;
bool m_bContentValid;
DisplayContext m_displayContext;
ViewMode m_eViewMode = NothingMode;
//May be changed by parent classes of Q2DViewport
//CTopRendererWnd for example will change m_maxZoom to 250.0f
float m_minZoom = 0.01f;
float m_maxZoom = 500.0f;
};
//////////////////////////////////////////////////////////////////////////
class C2DViewport_XY
: public Q2DViewport
{
Q_OBJECT
public:
static const GUID& GetClassID()
{
return QtViewport::GetClassID<C2DViewport_XY>();
}
C2DViewport_XY(QWidget* parent = nullptr)
: Q2DViewport(parent) { SetType(ET_ViewportXY); }
};
//////////////////////////////////////////////////////////////////////////
class C2DViewport_XZ
: public Q2DViewport
{
Q_OBJECT
public:
static const GUID& GetClassID()
{
return QtViewport::GetClassID<C2DViewport_XZ>();
}
C2DViewport_XZ(QWidget* parent = nullptr)
: Q2DViewport(parent) { SetType(ET_ViewportXZ); }
};
//////////////////////////////////////////////////////////////////////////
class C2DViewport_YZ
: public Q2DViewport
{
Q_OBJECT
public:
static const GUID& GetClassID()
{
return QtViewport::GetClassID<C2DViewport_YZ>();
}
C2DViewport_YZ(QWidget* parent = nullptr)
: Q2DViewport(parent) { SetType(ET_ViewportYZ); }
};
#endif // CRYINCLUDE_EDITOR_2DVIEWPORT_H
+85
View File
@@ -0,0 +1,85 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "AboutDialog.h"
// Qt
#include <QPainter>
#include <QDesktopServices>
#include <QSvgWidget>
// AzCore
#include <AzCore/Casting/numeric_cast.h> // for aznumeric_cast
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
#include <ui_AboutDialog.h>
AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
CAboutDialog::CAboutDialog(QString versionText, QString richTextCopyrightNotice, QWidget* pParent /*=NULL*/)
: QDialog(pParent)
, m_ui(new Ui::CAboutDialog)
{
m_ui->setupUi(this);
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
connect(m_ui->m_transparentAgreement, &QLabel::linkActivated, this, &CAboutDialog::OnCustomerAgreement);
m_ui->m_transparentTrademarks->setText(versionText);
m_ui->m_transparentAllRightReserved->setObjectName("copyrightNotice");
m_ui->m_transparentAllRightReserved->setTextFormat(Qt::RichText);
m_ui->m_transparentAllRightReserved->setText(richTextCopyrightNotice);
m_ui->m_transparentAgreement->setObjectName("link");
setStyleSheet( "CAboutDialog > QLabel#copyrightNotice { color: #AAAAAA; font-size: 9px; }\
CAboutDialog > QLabel#link { text-decoration: underline; color: #00A1C9; }");
// Prepare background image
QImage backgroundImage(QStringLiteral(":/StartupLogoDialog/splashscreen_background_gradient.jpg"));
m_backgroundImage = QPixmap::fromImage(backgroundImage.scaled(m_enforcedWidth, m_enforcedHeight, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
// Draw the Open 3D Engine logo from svg
m_ui->m_logo->load(QStringLiteral(":/StartupLogoDialog/o3de_logo.svg"));
// Prevent re-sizing
setFixedSize(m_enforcedWidth, m_enforcedHeight);
}
CAboutDialog::~CAboutDialog()
{
}
void CAboutDialog::paintEvent(QPaintEvent*)
{
QPainter painter(this);
QRect drawTarget = rect();
painter.drawPixmap(drawTarget, m_backgroundImage);
}
void CAboutDialog::mouseReleaseEvent(QMouseEvent* event)
{
if (event->button() == Qt::LeftButton)
{
accept();
}
QDialog::mouseReleaseEvent(event);
}
void CAboutDialog::OnCustomerAgreement()
{
QDesktopServices::openUrl(QUrl(QStringLiteral("https://www.o3debinaries.org/license")));
}
#include <moc_AboutDialog.cpp>
+43
View File
@@ -0,0 +1,43 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <QDialog>
#include <QString>
#include <QPixmap>
#endif
namespace Ui {
class CAboutDialog;
}
class CAboutDialog
: public QDialog
{
Q_OBJECT
public:
CAboutDialog(QString versionText, QString richTextCopyrightNotice, QWidget* pParent = nullptr);
~CAboutDialog();
private:
void OnCustomerAgreement();
void mouseReleaseEvent(QMouseEvent* event) override;
void paintEvent(QPaintEvent* event) override;
QScopedPointer<Ui::CAboutDialog> m_ui;
QPixmap m_backgroundImage;
int m_enforcedWidth = 600;
int m_enforcedHeight = 400;
};
+285
View File
@@ -0,0 +1,285 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CAboutDialog</class>
<widget class="QWidget" name="CAboutDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>600</width>
<height>360</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>600</width>
<height>360</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>600</width>
<height>360</height>
</size>
</property>
<property name="windowTitle">
<string>About Open 3D Engine Editor</string>
</property>
<property name="styleSheet">
<string notr="true">background-color: transparent</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="sizeConstraint">
<enum>QLayout::SetFixedSize</enum>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="sizeConstraint">
<enum>QLayout::SetFixedSize</enum>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>4</number>
</property>
<property name="topMargin">
<number>10</number>
</property>
<property name="bottomMargin">
<number>10</number>
</property>
<item>
<layout class="QVBoxLayout" name="verticalLayout_42">
<property name="leftMargin">
<number>11</number>
</property>
<property name="topMargin">
<number>12</number>
</property>
<property name="bottomMargin">
<number>12</number>
</property>
<item>
<widget class="QSvgWidget" name="m_logo" native="true">
<property name="minimumSize">
<size>
<width>175</width>
<height>66</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>175</width>
<height>66</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="sizeConstraint">
<enum>QLayout::SetNoConstraint</enum>
</property>
<property name="leftMargin">
<number>20</number>
</property>
<item>
<widget class="QLabel" name="label">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16</height>
</size>
</property>
<property name="text">
<string>O3DE Editor</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="m_transparentDevelopedBy">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>18</height>
</size>
</property>
<property name="text">
<string>Developer Preview</string>
</property>
<property name="textFormat">
<enum>Qt::AutoText</enum>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
<widget class="QLabel" name="m_transparentCopyright">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>180</width>
<height>0</height>
</rect>
</property>
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
</property>
</widget>
</widget>
</item>
<item>
<widget class="QLabel" name="m_transparentTrademarks">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>18</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="ClickableLabel" name="m_transparentAgreement">
<property name="text">
<string>Terms of Use</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="showDecoration" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>16</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="m_transparentAllRightReserved">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>260</width>
<height>24</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>260</width>
<height>24</height>
</size>
</property>
<property name="text">
<string>Specified in AboutDialog.cpp</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>300</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>QSvgWidget</class>
<extends>QWidget</extends>
<header>qsvgwidget.h</header>
</customwidget>
<customwidget>
<class>ClickableLabel</class>
<extends>QLabel</extends>
<header>QtUI/ClickableLabel.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
+650
View File
@@ -0,0 +1,650 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "ActionManager.h"
// Qt
#include <QSignalMapper>
#include <QDebug>
#include <QMenuBar>
#include <QScopedValueRollback>
// Editor
#include "MainWindow.h"
#include "QtViewPaneManager.h"
#include "ShortcutDispatcher.h"
#include "ToolbarManager.h"
static const char* const s_reserved = "Reserved"; ///< "Reserved" property used for actions that cannot be overridden.
///< (e.g. KeySequences such as Ctrl+S and Ctrl+Z for Save/Undo etc.)
static const char* const s_menuIdProperty = "MenuId"; ///< "MenuId" property used when adding top level menus to the
///< menu bar so they can be uniquely identified with FindMenu()
static const int s_invalidGuardActionId = -1;
ActionManagerExecutionGuard::ActionManagerExecutionGuard(ActionManager* actionManager, QAction* action)
: m_actionManager(actionManager)
, m_actionId(s_invalidGuardActionId)
, m_canExecute(true)
{
// both actionManager and action can be nullptr, and this is totally valid.
// the lambdas using this might be out of scope and their QPointers may have been cleared
if (actionManager && action)
{
m_actionId = action->data().toInt();
m_canExecute = actionManager->InsertActionExecuting(m_actionId);
}
}
ActionManagerExecutionGuard::ActionManagerExecutionGuard(ActionManager* actionManager, int actionId)
: m_actionManager(actionManager)
, m_actionId(actionId)
, m_canExecute(true)
{
// both actionManager and action can be nullptr, and this is totally valid. The lambdas using this might be out of scope and
// their QPointers may have been cleared
if (actionManager)
{
m_canExecute = actionManager->InsertActionExecuting(m_actionId);
}
}
ActionManagerExecutionGuard::~ActionManagerExecutionGuard()
{
// Only bother removing the action if it successfully inserted, indicated by m_canExecute.
// If during the insert, it was found that the action was already present, then there
// is no need to remove it, and doing so might cause problems because any it implies
// that something has already executed the action higher up the callstack and we don't
// want to remove the id so that future action triggers within the same callstack are prevented
// from executing.
if (m_canExecute && m_actionManager && (m_actionId != s_invalidGuardActionId))
{
m_actionManager->RemoveActionExecuting(m_actionId);
}
}
PatchedAction::PatchedAction(const QString& name, QObject* parent)
: QAction(name, parent)
{
}
bool PatchedAction::event(QEvent* ev)
{
// *Really* honor Qt::WindowShortcut. Floating dock widgets are a separate window (Qt::Window flag is set) even though they have a parent.
if (ev->type() == QEvent::Shortcut && shortcutContext() == Qt::WindowShortcut)
{
// This prevents shortcuts from firing while we're in a long running operation
// started by a shortcut
static bool reentranceLock = false;
if (reentranceLock)
{
return true;
}
QScopedValueRollback<bool> reset(reentranceLock, true);
QWidget* focusWidget = ShortcutDispatcher::focusWidget();
if (!focusWidget)
{
return QAction::event(ev);
}
for (QWidget* associatedWidget : associatedWidgets())
{
QWidget* associatedWindow = associatedWidget->window();
QWidget* focusWindow = focusWidget->window();
if (associatedWindow == focusWindow)
{
// Fair enough, we accept it.
return QAction::event(ev);
}
else if (associatedWindow && focusWindow)
{
/**
* But do allow if the focused window is actually a floating dock widget.
* For example, If Entity Outliner is floating, the gizmos (key 1, 2, 3 4) should still work.
*
* FIXME: But then why are those main toolbar actions using Qt::WindowShortcut instead of Qt::ApplicationShortcut ?
* This block goes against what the original PatchedAction fixed.
* Consider either removing it and using regular QActions, or using Qt::ApplicationShortcut
* See also LY-35177
*/
QString focusWindowName = focusWindow->objectName();
if (focusWindowName.isEmpty())
{
continue;
}
QWidget* child = associatedWindow->findChild<QWidget*>(focusWindowName);
if (child)
{
// Also accept if the focus window is a child of the associated window
return QAction::event(ev);
}
}
}
// Bug detected: Qt is propagating a shortcut with context Qt::WindowShortcut outside of window boundaries
// Consume the event instead of processing it.
qDebug() << "Discarding buggy shortcut";
return true;
}
return QAction::event(ev);
}
/////////////////////////////////////////////////////////////////////////////
// ActionWrapper
/////////////////////////////////////////////////////////////////////////////
ActionManager::ActionWrapper& ActionManager::ActionWrapper::SetMenu(DynamicMenu* menu)
{
menu->SetAction(m_action, m_actionManager);
return *this;
}
ActionManager::ActionWrapper& ActionManager::ActionWrapper::SetApplyHoverEffect()
{
// Our standard toolbar icons, when hovered on, get a white color effect.
// But for this to work we need .pngs that look good with this effect, so this only works with the standard toolbars
// and looks very ugly for other toolbars, including toolbars loaded from XML (which just show a white rectangle)
m_action->setProperty("IconHasHoverEffect", true);
return *this;
}
ActionManager::ActionWrapper& ActionManager::ActionWrapper::SetReserved()
{
m_action->setProperty("Reserved", true);
return *this;
}
/////////////////////////////////////////////////////////////////////////////
// DynamicMenu
/////////////////////////////////////////////////////////////////////////////
DynamicMenu::DynamicMenu(QObject* parent)
: QObject(parent)
, m_action(nullptr)
, m_menu(nullptr)
{
m_actionMapper = new QSignalMapper(this);
connect(m_actionMapper, SIGNAL(mapped(int)), this, SLOT(TriggerAction(int)));
}
void DynamicMenu::SetAction(QAction* action, ActionManager* am)
{
Q_ASSERT(action);
Q_ASSERT(am);
m_action = action;
m_menu = new QMenu();
m_action->setMenu(m_menu);
connect(m_menu, &QMenu::aboutToShow, this, &DynamicMenu::ShowMenu);
m_actionManager = am;
setParent(m_action);
}
void DynamicMenu::SetParentMenu(QMenu* menu, ActionManager* am)
{
Q_ASSERT(menu && !m_menu);
Q_ASSERT(!m_action);
m_menu = menu;
connect(m_menu, &QMenu::aboutToShow, this, &DynamicMenu::ShowMenu);
m_actionManager = am;
}
void DynamicMenu::AddAction(int id, QAction* action)
{
Q_ASSERT(!m_actions.contains(id));
action->setData(id);
m_actions[id] = action;
m_menu->addAction(action);
connect(action, SIGNAL(triggered()), m_actionMapper, SLOT(map()));
m_actionMapper->setMapping(action, id);
}
void DynamicMenu::AddSeparator()
{
Q_ASSERT(m_menu);
m_menu->addSeparator();
}
ActionManager::ActionWrapper DynamicMenu::AddAction(int id, const QString& name)
{
QAction* action = new PatchedAction(name, this);
AddAction(id, action);
return ActionManager::ActionWrapper(action, m_actionManager);
}
void DynamicMenu::UpdateAllActions()
{
for (auto action : m_menu->actions())
{
int id = action->data().toInt();
OnMenuUpdate(id, action);
}
}
void DynamicMenu::ShowMenu()
{
if (m_actions.isEmpty())
{
CreateMenu();
}
UpdateAllActions();
}
void DynamicMenu::TriggerAction(int id)
{
OnMenuChange(id, m_actions.value(id));
UpdateAllActions();
}
#include <QTimer>
/////////////////////////////////////////////////////////////////////////////
// ActionManager
/////////////////////////////////////////////////////////////////////////////
ActionManager::ActionManager(
MainWindow* parent, QtViewPaneManager* const qtViewPaneManager, ShortcutDispatcher* shortcutDispatcher)
: QObject(parent)
, m_mainWindow(parent)
, m_qtViewPaneManager(qtViewPaneManager)
, m_shortcutDispatcher(shortcutDispatcher)
{
m_actionMapper = new QSignalMapper(this);
connect(m_actionMapper, SIGNAL(mapped(int)), this, SLOT(ActionTriggered(int)));
connect(m_qtViewPaneManager, &QtViewPaneManager::registeredPanesChanged, this, &ActionManager::RebuildRegisteredViewPaneIds);
// KDAB_TODO: This will be used later, particularly for the toolbars
//connect(QCoreApplication::eventDispatcher(), SIGNAL(aboutToBlock()),
// this, SLOT(UpdateActions()));
// so long use a simple timer to make it work
QTimer* timer = new QTimer(this);
timer->setInterval(250);
connect(timer, &QTimer::timeout, this, &ActionManager::UpdateActions);
timer->start();
// connect to the Action Request Bus and notify other listeners this has happened
AzToolsFramework::EditorActionRequestBus::Handler::BusConnect();
}
ActionManager::~ActionManager()
{
AzToolsFramework::EditorActionRequestBus::Handler::BusDisconnect();
}
void ActionManager::AddMenu(QMenu* menu)
{
m_menus.push_back(menu);
connect(menu, &QMenu::aboutToShow, this, &ActionManager::UpdateMenu);
}
ActionManager::MenuWrapper ActionManager::AddMenu(const QString& title, const QString& menuId)
{
const auto menu = new QMenu(title);
// set a unique identifier for this menu item so it
// can be looked up later using FindMenu()
if (!menuId.isEmpty())
{
menu->setProperty(s_menuIdProperty, menuId);
}
AddMenu(menu);
return MenuWrapper(menu, this);
}
ActionManager::MenuWrapper ActionManager::FindMenu(const QString& menuId)
{
// attempt to find menu by menuId
auto menuIt = AZStd::find_if(m_menus.begin(), m_menus.end(), [&menuId](const QMenu* menu)
{
if (!menu->property(s_menuIdProperty).isNull())
{
return menu->property(s_menuIdProperty).toString() == menuId;
}
return false;
});
// return the menu with the matching name, if not found return nullptr
QMenu* menu = [this, menuIt, &menuId]() -> QMenu*
{
if (menuIt != m_menus.end())
{
return *menuIt;
}
AZ_Warning("ActionManager", false, "Did not find menu with menuId %s", menuId.toUtf8().data());
return nullptr;
}();
return MenuWrapper(menu, this);
}
void ActionManager::AddToolBar(QToolBar* toolBar)
{
m_toolBars.push_back(toolBar);
}
ActionManager::ToolBarWrapper ActionManager::AddToolBar(int id)
{
AmazonToolbar t = m_mainWindow->GetToolbarManager()->GetToolbar(id);
Q_ASSERT(t.IsInstantiated());
AddToolBar(t.Toolbar());
return ToolBarWrapper(t.Toolbar(), this);
}
bool ActionManager::eventFilter([[maybe_unused]] QObject* watched, QEvent* event)
{
// if events are shortcut events, we don't want to filter out
if (event->type() == QEvent::Shortcut)
{
m_isShortcutEvent = true;
}
return false;
}
bool ActionManager::InsertActionExecuting(int id)
{
// If the action handler puts up a modal dialog, the event queue will be pumped
// and double clicks on menu items will go through, in some cases.
// This is to guard against that.
if (m_executingIds.find(id) != m_executingIds.end())
{
return false;
}
m_executingIds.insert(id);
return true;
}
bool ActionManager::RemoveActionExecuting(int id)
{
bool idWasInList = m_executingIds.remove(id);
Q_ASSERT(idWasInList);
return idWasInList;
}
void ActionManager::AddAction(int id, QAction* action)
{
action->setData(id);
AddAction(action);
}
void ActionManager::AddAction(QAction* action)
{
const int id = action->data().toInt();
if (m_actions.contains(id))
{
qWarning() << "ActionManager already contains action with id=" << id;
Q_ASSERT(false);
}
m_actions[id] = action;
connect(action, SIGNAL(triggered()), m_actionMapper, SLOT(map()));
m_actionMapper->setMapping(action, id);
action->installEventFilter(this);
// Add the action if the parent is a widget
auto widget = qobject_cast<QWidget*>(parent());
if (widget)
{
widget->addAction(action);
}
}
void ActionManager::RemoveAction(QAction* action)
{
auto storedAction = m_actions.find(action->data().toInt());
if (storedAction != m_actions.end())
{
m_actions.remove(action->data().toInt());
}
action->removeEventFilter(this);
m_actionMapper->removeMappings(action);
if (auto widget = qobject_cast<QWidget*>(parent()))
{
widget->removeAction(action);
}
}
ActionManager::ActionWrapper ActionManager::AddAction(int id, const QString& name)
{
QAction* action = ActionIsWidget(id) ? new WidgetAction(id, m_mainWindow, name, this)
: static_cast<QAction*>(new PatchedAction(name, this)); // static cast to base so ternary compiles
AddAction(id, action);
return ActionWrapper(action, this);
}
bool ActionManager::HasAction(QAction* action) const
{
return action && HasAction(action->data().toInt());
}
bool ActionManager::HasAction(int id) const
{
return m_actions.contains(id);
}
QAction* ActionManager::GetAction(int id) const
{
auto it = m_actions.find(id);
if (it == m_actions.cend())
{
qWarning() << Q_FUNC_INFO << "Couldn't get action " << id;
Q_ASSERT(false);
return nullptr;
}
else
{
return *it;
}
}
void ActionManager::ActionTriggered(int id)
{
if (m_mainWindow->menuBar()->isEnabled())
{
if (m_actionHandlers.contains(id))
{
ActionManagerExecutionGuard guard(this, id);
if (guard.CanExecute())
{
m_actionHandlers[id]();
}
}
}
}
void ActionManager::RebuildRegisteredViewPaneIds()
{
QtViewPanes views = QtViewPaneManager::instance()->GetRegisteredPanes();
for (auto& view : views)
{
m_registeredViewPaneIds.insert(view.m_id);
}
}
// is this action suspended (allowed to respond or not)
static bool ActionSuspended(const bool defaultActionsSuspended, const QAction* const action)
{
// if default actions are suspended and this is not a reserved action, do not update
return defaultActionsSuspended && !action->property(s_reserved).toBool();
}
// for the menu that is about to be opened, visit each action (menu item) and
// update callbacks for unsuspended actions.
// recurse one level deep if the action is itself a menu, if all actions
// in that menu are suspended, gray out the menu so it cannot be selected.
static void UpdateMenus(
QMenu* menu, const bool defaultActionsSuspended,
QHash<int, std::function<void()>>& updateCallbacks,
QList<QAction*> topLevelMenuActions, int depth)
{
const auto actions = menu->actions();
const int actionCount = actions.size();
int suspendedActionCounter = 0;
for (auto action : actions)
{
// if an action is itself a menu, we want to check its
// own menu actions (children), but only one level down
if (action->menu() && depth == 0)
{
UpdateMenus(
action->menu(), defaultActionsSuspended,
updateCallbacks, topLevelMenuActions, depth + 1);
}
if (ActionSuspended(defaultActionsSuspended, action))
{
suspendedActionCounter++;
continue;
}
// call all update callbacks for the given menu
// only do this at the level of the menu we clicked/hovered
const auto id = action->data().toInt();
if (updateCallbacks.contains(id) && depth == 0)
{
updateCallbacks.value(id)();
}
}
// check if we are a top level menu action
if (AZStd::find(
topLevelMenuActions.begin(), topLevelMenuActions.end(),
menu->menuAction()) == topLevelMenuActions.end())
{
// if we're not a top level menu action, we want to disable
// the sub menu if none of the child actions are active, otherwise
// ensure the menu is returned to an enabled state
menu->menuAction()->setEnabled(
defaultActionsSuspended
? suspendedActionCounter != actionCount
: true);
}
}
void ActionManager::UpdateMenu()
{
auto menu = qobject_cast<QMenu*>(sender());
AZ_Assert(menu, "sender() was not convertible to a QMenu*");
int depth = 0;
UpdateMenus(
menu, m_defaultActionsSuspended, m_updateCallbacks,
m_mainWindow->menuBar()->actions(), depth);
}
void ActionManager::UpdateActions()
{
for (auto it = m_updateCallbacks.constBegin(); it != m_updateCallbacks.constEnd(); ++it)
{
if (ActionSuspended(m_defaultActionsSuspended, GetAction(it.key())))
{
continue;
}
it.value()();
}
}
QList<QAction*> ActionManager::GetActions() const
{
return m_actions.values();
}
bool ActionManager::ActionIsWidget(int id) const
{
return id >= ID_TOOLBAR_WIDGET_FIRST && id <= ID_TOOLBAR_WIDGET_LAST;
}
// either enable or disable all registered actions
void SetDefaultActionsEnabled(
const QList<QAction*>& actions, const bool enabled)
{
AZStd::for_each(
actions.begin(), actions.end(),
[enabled](QAction* action)
{
if (!action->property(s_reserved).toBool())
{
action->setEnabled(enabled);
}
});
}
void ActionManager::AddActionViaBus(int id, QAction* action)
{
AddAction(id, action);
}
void ActionManager::RemoveActionViaBus(QAction* action)
{
RemoveAction(action);
}
void ActionManager::EnableDefaultActions()
{
SetDefaultActionsEnabled(GetActions(), true);
m_defaultActionsSuspended = false;
}
void ActionManager::DisableDefaultActions()
{
SetDefaultActionsEnabled(GetActions(), false);
m_defaultActionsSuspended = true;
}
void ActionManager::AttachOverride(QWidget* object)
{
m_shortcutDispatcher->AttachOverride(object);
}
void ActionManager::DetachOverride()
{
m_shortcutDispatcher->DetachOverride();
}
WidgetAction::WidgetAction(int actionId, MainWindow* mainWindow, const QString& name, QObject* parent)
: QWidgetAction(parent)
, m_actionId(actionId)
, m_mainWindow(mainWindow)
{
setText(name);
}
QWidget* WidgetAction::createWidget(QWidget* parent)
{
QWidget* w = m_mainWindow->CreateToolbarWidget(m_actionId);
if (w)
{
w->setParent(parent);
}
return w;
}
#include <moc_ActionManager.cpp>
+432
View File
@@ -0,0 +1,432 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#ifndef ACTIONMANAGER_H
#define ACTIONMANAGER_H
#if !defined(Q_MOC_RUN)
#include <QObject>
#include <QPointer>
#include <QHash>
#include <QVector>
#include <QAction>
#include <QMenu>
#include <QToolBar>
#include <QIcon>
#include <QWidgetAction>
#include <QSet>
#include <functional>
#include <utility>
#include <AzToolsFramework/Viewport/ActionBus.h>
#endif
class QSignalMapper;
class QPixmap;
class QDockWidget;
class DynamicMenu;
class MainWindow;
class QtViewPaneManager;
class ShortcutDispatcher;
class PatchedAction
: public QAction
{
// PatchedAction is a workaround for the fact that Qt doesn't honour Qt::WindowShortcut context for floating dock widgets.
Q_OBJECT
public:
explicit PatchedAction(const QString& name, QObject* parent = nullptr);
bool event(QEvent*) override;
};
class WidgetAction
: public QWidgetAction
{
Q_OBJECT
public:
explicit WidgetAction(int actionId, MainWindow* mainWindow, const QString& name, QObject* parent);
protected:
QWidget* createWidget(QWidget* parent) override;
private:
const int m_actionId;
MainWindow* const m_mainWindow;
};
class ActionManager;
// If the action handler puts up a modal dialog, the event queue will be pumped
// and double clicks on menu items will go through, in some cases.
// This class can be used to guard against that.
class ActionManagerExecutionGuard
{
public:
ActionManagerExecutionGuard(ActionManager* actionManager, QAction* action);
ActionManagerExecutionGuard(ActionManager* actionManager, int actionId);
~ActionManagerExecutionGuard();
// Only execute the action of this returns true
bool CanExecute() const { return m_canExecute; }
private:
QPointer<ActionManager> m_actionManager;
int m_actionId;
bool m_canExecute;
};
class ActionManager
: public QObject
, private AzToolsFramework::EditorActionRequestBus::Handler
{
Q_OBJECT
public:
class ActionWrapper
{
public:
ActionWrapper& SetText(const QString& text) { m_action->setText(text); return *this; }
ActionWrapper& SetIcon(const QIcon& icon) { m_action->setIcon(icon); return *this; }
ActionWrapper& SetIcon(const QPixmap& icon) { m_action->setIcon(QIcon(icon)); return *this; }
ActionWrapper& SetShortcut(const QString& shortcut) { m_action->setShortcut(shortcut); return *this; }
ActionWrapper& SetShortcut(const QKeySequence& shortcut) { m_action->setShortcut(shortcut); return *this; }
ActionWrapper& SetToolTip(const QString& toolTip) { m_action->setToolTip(toolTip); return *this; }
ActionWrapper& SetStatusTip(const QString& statusTip) { m_action->setStatusTip(statusTip); return *this; }
ActionWrapper& SetCheckable(bool value) { m_action->setCheckable(value); return *this; }
ActionWrapper& SetReserved(); // if reserved, the action should not be allowed to be disabled or overridden
//ActionWrapper &SetMenu(QMenu *menu) { m_action->setMenu(menu); return *this; }
//ActionWrapper &SetMenu(QMenu *menu) { m_action->setMenu(menu); return *this; }
template <typename Func1, typename Func2>
ActionWrapper& Connect(Func1 signal, Func2 slot)
{
// The ActionWrapper class is stack based usually so
// the lambda below can't use the this pointer.
// As a result, wrap the actionManager and the action in
// a QPointer, and capture them in the lambda
QPointer<ActionManager> actionManager = m_actionManager;
QPointer<QAction> action = m_action;
QObject::connect(m_action, signal, m_action, [action, actionManager, slot]()
{
ActionManagerExecutionGuard guard(actionManager.data(), action.data());
if (!GetIEditor()->IsInGameMode() && guard.CanExecute())
{
slot();
}
});
return *this;
}
template <typename Func1, typename Object, typename Func2>
ActionWrapper& Connect(Func1 signal, Object* context, const Func2 slot, Qt::ConnectionType type = Qt::AutoConnection)
{
// The ActionWrapper class is stack based usually so
// the lambda below can't use the this pointer.
// As a result, wrap the actionManager and the action in
// a QPointer, and capture them in the lambda
QPointer<ActionManager> actionManager = m_actionManager;
QPointer<QAction> action = m_action;
QObject::connect(m_action, signal, context, [action, actionManager, context, slot]()
{
ActionManagerExecutionGuard guard(actionManager.data(), action.data());
if (!GetIEditor()->IsInGameMode() && guard.CanExecute())
{
(context->*slot)();
}
}, type);
return *this;
}
ActionWrapper& SetMenu(DynamicMenu* menu);
ActionWrapper& SetApplyHoverEffect();
operator QAction*() const {
return m_action;
}
QAction* operator->() const { return m_action; }
template<typename T>
ActionWrapper& RegisterUpdateCallback(T* object, void (T::* method)(QAction*))
{
m_actionManager->RegisterUpdateCallback(m_action->data().toInt(), object, method);
return *this;
}
template<typename Fn>
ActionWrapper& RegisterUpdateCallback(Fn&& fn)
{
m_actionManager->RegisterUpdateCallback(m_action->data().toInt(), AZStd::forward<Fn>(fn));
return *this;
}
private:
friend ActionManager;
friend DynamicMenu;
ActionWrapper(QAction* action, ActionManager* am)
: m_action(action)
, m_actionManager(am) { Q_ASSERT(m_action); }
QAction* m_action;
ActionManager* m_actionManager;
};
class MenuWrapper
{
public:
MenuWrapper() = default;
MenuWrapper(QMenu* menu, ActionManager* am)
: m_menu(menu)
, m_actionManager(am) {}
MenuWrapper& SetTitle(const QString& text) { m_menu->setTitle(text); return *this; }
MenuWrapper& SetIcon(const QIcon& icon) { m_menu->setIcon(icon); return *this; }
QAction* AddAction(int id)
{
auto action = m_actionManager->GetAction(id);
Q_ASSERT(action);
m_menu->addAction(action);
return action;
}
QAction* AddSeparator()
{
return m_menu->addSeparator();
}
MenuWrapper AddMenu(const QString& name, const QString& menuId = QString())
{
auto menu = m_actionManager->AddMenu(name, menuId);
m_menu->addMenu(menu);
return menu;
}
bool isNull() const
{
return !m_menu;
}
operator QMenu*()
{
return m_menu;
}
QMenu* operator->()
{
return m_menu;
}
QMenu* Get()
{
return m_menu;
}
private:
friend ActionManager;
QPointer<QMenu> m_menu = nullptr;
ActionManager* m_actionManager = nullptr;
};
class ToolBarWrapper
{
public:
QAction* AddAction(int id)
{
auto action = m_actionManager->GetAction(id);
Q_ASSERT(action);
m_toolBar->addAction(action);
return action;
}
QAction* AddSeparator()
{
QAction* action = m_toolBar->addSeparator();
// For the Dnd to work:
action->setData(ID_TOOLBAR_SEPARATOR);
QWidget* w = m_toolBar->widgetForAction(action);
w->addAction(action);
return action;
}
void AddWidget(QWidget* widget)
{
m_toolBar->addWidget(widget);
}
operator QToolBar*() const {
return m_toolBar;
}
QToolBar* operator->() const { return m_toolBar; }
private:
friend ActionManager;
ToolBarWrapper(QToolBar* toolBar, ActionManager* am)
: m_toolBar(toolBar)
, m_actionManager(am) { Q_ASSERT(m_toolBar); }
QToolBar* m_toolBar;
ActionManager* m_actionManager;
};
public:
explicit ActionManager(
MainWindow* parent, QtViewPaneManager* qtViewPaneManager,
ShortcutDispatcher* shortcutDispatcher);
~ActionManager();
void AddMenu(QMenu* menu);
MenuWrapper AddMenu(const QString& title, const QString& menuId);
MenuWrapper FindMenu(const QString& menuId);
bool ActionIsWidget(int actionId) const;
void AddToolBar(QToolBar* toolBar);
ToolBarWrapper AddToolBar(int id);
void AddAction(QAction* action);
void AddAction(int id, QAction* action);
void RemoveAction(QAction* action);
ActionWrapper AddAction(int id, const QString& name);
bool HasAction(QAction*) const;
bool HasAction(int id) const;
QAction* GetAction(int id) const;
QList<QAction*> GetActions() const;
// AzToolsFramework::EditorActionRequests
void AddActionViaBus(int id, QAction* action) override;
void RemoveActionViaBus(QAction* action) override;
void EnableDefaultActions() override;
void DisableDefaultActions() override;
void AttachOverride(QWidget* object) override;
void DetachOverride() override;
template<typename T>
void RegisterUpdateCallback(int id, T* object, void (T::*method)(QAction*))
{
Q_ASSERT(m_actions.contains(id));
m_updateCallbacks[id] = [action = m_actions.value(id), object, method] { AZStd::invoke(method, object, action); };
}
template<typename Fn>
void RegisterUpdateCallback(int id, Fn&& fn)
{
Q_ASSERT(m_actions.contains(id));
m_updateCallbacks[id] = [action = m_actions.value(id), fn] { fn(action); };
}
template<typename T>
void RegisterActionHandler(int id, T method)
{
m_actionHandlers[id] = method;
}
template<typename T>
void RegisterActionHandler(int id, T* object, void (T::* method)())
{
m_actionHandlers[id] = std::bind(method, object);
}
template<typename T>
void RegisterActionHandler(int id, T* object, void (T::* method)(UINT))
{
m_actionHandlers[id] = std::bind(method, object, id);
}
bool eventFilter(QObject* watched, QEvent* event);
// returns false if the action was already inserted, indicating that the action should not be processed again
bool InsertActionExecuting(int id);
// returns true if the action was inserted with InsertActionExecuting previously
bool RemoveActionExecuting(int id);
Q_SIGNALS:
void SendMetricsSignal(const char* viewPaneName, const char* openLocation);
public slots:
void ActionTriggered(int id);
private slots:
void UpdateMenu();
void UpdateActions();
private:
QHash<int, QAction*> m_actions;
QVector<QMenu*> m_menus;
QVector<QToolBar*> m_toolBars;
QSignalMapper* m_actionMapper;
QHash<int, std::function<void()> > m_updateCallbacks;
QHash<int, std::function<void()> > m_actionHandlers;
MainWindow* const m_mainWindow;
QtViewPaneManager* m_qtViewPaneManager;
ShortcutDispatcher* m_shortcutDispatcher = nullptr;
// for sending shortcut metrics events
bool m_isShortcutEvent = false;
// for sending toolbar metrics events
QSet<int> m_editorToolbarIds;
// for sending main menu metrics events
QSet<int> m_registeredViewPaneIds;
// update the registered view pane Ids when the registered view pane list is modified
void RebuildRegisteredViewPaneIds();
// Guard against recursive actions being triggered by double clicks on menu items
// while modal dialogs are in the process of popping up
QSet<int> m_executingIds;
// have all default actions (not including reserved actions) been suspended
bool m_defaultActionsSuspended = false;
};
class DynamicMenu
: public QObject
{
Q_OBJECT
public:
explicit DynamicMenu(QObject* parent = nullptr);
void SetAction(QAction* action, ActionManager* am);
void SetParentMenu(QMenu* menu, ActionManager* am);
protected:
virtual void CreateMenu() = 0;
virtual void OnMenuUpdate(int id, QAction* action) = 0;
virtual void OnMenuChange(int id, QAction* action) = 0;
void AddAction(int id, QAction* action);
ActionManager::ActionWrapper AddAction(int id, const QString& name);
void AddSeparator();
void UpdateAllActions();
ActionManager* m_actionManager;
private slots:
void ShowMenu();
void TriggerAction(int id);
private:
QAction* m_action;
QMenu* m_menu;
QSignalMapper* m_actionMapper;
QHash<int, QAction*> m_actions;
};
#endif // ACTIONMANAGER_H
@@ -0,0 +1,32 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "AnimationBipedBoneNames.h"
namespace EditorAnimationBones
{
namespace Biped
{
const char* Pelvis = "Bip01 Pelvis";
const char* Head = "Bip01 Head";
const char* Weapon = "weapon_bone";
const char* LeftEye = "eye_bone_left";
const char* RightEye = "eye_bone_right";
const char* Spine[5] = { "Bip01 Spine", "Bip01 Spine1", "Bip01 Spine2", "Bip01 Spine3", "Bip01 Spine4" };
const char* Neck[2] = { "Bip01 Neck", "Bip01 Neck1" };
const char* LeftHeel = "Bip01 L Heel";
const char* LeftToe[2] = { "Bip01 L Toe0", "Bip01 L Toe1" };
const char* RightHeel = "Bip01 R Heel";
const char* RightToe[2] = { "Bip01 R Toe0", "Bip01 R Toe1" };
}
}
@@ -0,0 +1,33 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef CRYINCLUDE_EDITOR_ANIMATION_ANIMATIONBIPEDBONENAMES_H
#define CRYINCLUDE_EDITOR_ANIMATION_ANIMATIONBIPEDBONENAMES_H
#pragma once
namespace EditorAnimationBones
{
namespace Biped
{
extern const char* Pelvis;
extern const char* Head;
extern const char* Weapon;
extern const char* Spine[5];
extern const char* Neck[2];
extern const char* LeftEye;
extern const char* RightEye;
extern const char* LeftHeel;
extern const char* RightHeel;
extern const char* LeftToe[2];
extern const char* RightToe[2];
}
}
#endif // CRYINCLUDE_EDITOR_ANIMATION_ANIMATIONBIPEDBONENAMES_H
+148
View File
@@ -0,0 +1,148 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "SkeletonHierarchy.h"
using namespace Skeleton;
/*
CHierarchy
*/
CHierarchy::CHierarchy()
{
}
CHierarchy::~CHierarchy()
{
}
//
uint32 CHierarchy::AddNode(const char* name, const QuatT& pose, int32 parent)
{
int32 index = FindNodeIndexByName(name);
if (index < 0)
{
m_nodes.push_back(SNode());
index = int32(m_nodes.size() - 1);
}
m_nodes[index].name = name;
m_nodes[index].pose = pose;
m_nodes[index].parent = parent;
return uint32(index);
}
int32 CHierarchy::FindNodeIndexByName(const char* name) const
{
uint32 count = uint32(m_nodes.size());
for (uint32 i = 0; i < count; ++i)
{
if (::_stricmp(m_nodes[i].name, name))
{
continue;
}
return i;
}
return -1;
}
const CHierarchy::SNode* CHierarchy::FindNode(const char* name) const
{
int32 index = FindNodeIndexByName(name);
return index < 0 ? NULL : &m_nodes[index];
}
void CHierarchy::CreateFrom(IDefaultSkeleton* pIDefaultSkeleton)
{
const uint32 jointCount = pIDefaultSkeleton->GetJointCount();
m_nodes.clear();
m_nodes.reserve(jointCount);
for (uint32 i = 0; i < jointCount; ++i)
{
m_nodes.push_back(SNode());
m_nodes.back().name = pIDefaultSkeleton->GetJointNameByID(int32(i));
m_nodes.back().pose = pIDefaultSkeleton->GetDefaultAbsJointByID(int32(i));
m_nodes.back().parent = pIDefaultSkeleton->GetJointParentIDByID(int32(i));
}
ValidateReferences();
}
void CHierarchy::ValidateReferences()
{
uint32 nodeCount = m_nodes.size();
if (!nodeCount)
{
return;
}
for (uint32 i = 0; i < nodeCount; ++i)
{
if (m_nodes[i].parent < nodeCount)
{
continue;
}
m_nodes[i].parent = -1;
}
}
void CHierarchy::AbsoluteToRelative(const QuatT* pSource, QuatT* pDestination)
{
uint32 count = uint32(m_nodes.size());
std::vector<QuatT> absolutes(count);
for (uint32 i = 0; i < count; ++i)
{
absolutes[i] = pSource[i];
}
for (uint32 i = 0; i < count; ++i)
{
int32 parent = m_nodes[i].parent;
if (parent < 0)
{
pDestination[i] = absolutes[i];
continue;
}
pDestination[i].t = (absolutes[i].t - absolutes[parent].t) * absolutes[parent].q;
pDestination[i].q = absolutes[parent].q.GetInverted() * absolutes[i].q;
}
}
bool CHierarchy::SerializeTo(XmlNodeRef& node)
{
XmlNodeRef hierarchy = node->newChild("Hierarchy");
uint32 nodeCount = uint32(m_nodes.size());
std::vector<IXmlNode*> nodes(nodeCount);
for (uint32 i = 0; i < nodeCount; ++i)
{
XmlNodeRef parent = hierarchy;
if (m_nodes[i].parent > -1)
{
parent = nodes[m_nodes[i].parent];
}
nodes[i] = parent->newChild("Node");
nodes[i]->setAttr("name", m_nodes[i].name);
}
return true;
}
+56
View File
@@ -0,0 +1,56 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef CRYINCLUDE_EDITOR_ANIMATION_SKELETONHIERARCHY_H
#define CRYINCLUDE_EDITOR_ANIMATION_SKELETONHIERARCHY_H
#pragma once
namespace Skeleton {
class CHierarchy
: public _reference_target_t
{
public:
struct SNode
{
string name;
QuatT pose;
int32 parent;
/* TODO: Implement
uint32 childrenIndex;
uint32 childrenCount;
*/
};
public:
CHierarchy();
~CHierarchy();
public:
uint32 AddNode(const char* name, const QuatT& pose, int32 parent = -1);
uint32 GetNodeCount() const { return uint32(m_nodes.size()); }
SNode* GetNode(uint32 index) { return &m_nodes[index]; }
const SNode* GetNode(uint32 index) const { return &m_nodes[index]; }
int32 FindNodeIndexByName(const char* name) const;
const SNode* FindNode(const char* name) const;
void ClearNodes() { m_nodes.clear(); }
void CreateFrom(IDefaultSkeleton* rIDefaultSkeleton);
void ValidateReferences();
void AbsoluteToRelative(const QuatT* pSource, QuatT* pDestination);
bool SerializeTo(XmlNodeRef& node);
private:
std::vector<SNode> m_nodes;
};
} // namespace Skeleton
#endif // CRYINCLUDE_EDITOR_ANIMATION_SKELETONHIERARCHY_H
+367
View File
@@ -0,0 +1,367 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "SkeletonMapper.h"
using namespace Skeleton;
/*
CMapper
*/
CMapper::CMapper()
{
}
CMapper::~CMapper()
{
}
//
void CMapper::CreateFromHierarchy()
{
m_nodes.clear();
uint32 nodeCount = m_hierarchy.GetNodeCount();
m_nodes.resize(nodeCount);
}
//
uint32 CMapper::CreateLocation(const char* name)
{
int32 index = FindLocation(name);
if (index < 1)
{
CMapperLocation* pLocation = new CMapperLocation();
pLocation->SetName(name);
m_locations.push_back(pLocation);
}
return uint32(m_locations.size() - 1);
}
void CMapper::ClearLocations()
{
uint32 count = uint32(m_nodes.size());
for (uint32 i = 0; i < count; ++i)
{
m_nodes[i].position = NULL;
m_nodes[i].orientation = NULL;
}
m_locations.clear();
}
int32 CMapper::FindLocation(const char* name) const
{
uint32 count = uint32(m_locations.size());
for (uint32 i = 0; i < count; ++i)
{
if (::_stricmp(m_locations[i]->GetName(), name))
{
continue;
}
return int32(i);
}
return -1;
}
void CMapper::SetLocation(CMapperLocation& location)
{
int32 index = FindLocation(location.GetName());
if (index < 0)
{
m_locations.push_back(&location);
return;
}
m_locations[index] = &location;
}
//
bool CMapper::CreateLocationsHierarchy(uint32 index, CHierarchy& hierarchy, int32 hierarchyParent)
{
if (NodeHasLocation(index))
{
const CHierarchy::SNode* pNode = m_hierarchy.GetNode(index);
uint32 nodeIndex = hierarchy.AddNode(pNode->name, pNode->pose, hierarchyParent);
hierarchyParent = uint32(nodeIndex);
}
std::vector<uint32> children;
GetChildrenIndices(index, children);
uint32 childCount = uint32(children.size());
for (uint32 i = 0; i < childCount; ++i)
{
CreateLocationsHierarchy(children[i], hierarchy, hierarchyParent);
}
return hierarchy.GetNodeCount() != 0;
}
bool CMapper::CreateLocationsHierarchy(CHierarchy& hierarchy)
{
hierarchy.ClearNodes();
if (!CreateLocationsHierarchy(0, hierarchy, -1))
{
return false;
}
hierarchy.ValidateReferences();
return true;
}
void CMapper::Map(QuatT* pResult)
{
uint32 outputCount = m_hierarchy.GetNodeCount();
std::vector<Quat> absolutes(outputCount);
for (uint32 i = 0; i < outputCount; ++i)
{
pResult[i].SetIdentity();
absolutes[i].SetIdentity();
CHierarchy::SNode* pNode = m_hierarchy.GetNode(i);
if (!pNode)
{
continue;
}
CHierarchy::SNode* pParent = pNode->parent < 0 ?
NULL : m_hierarchy.GetNode(pNode->parent);
if (pParent)
{
pResult[i].t =
(pNode->pose.t - pParent->pose.t) * pParent->pose.q;
}
if (m_nodes[i].position)
{
pResult[i].t = m_nodes[i].position->Compute().t;
}
if (m_nodes[i].orientation)
{
absolutes[i] = m_nodes[i].orientation->Compute().q;
}
else if (pParent)
{
Quat relative = pParent->pose.q.GetInverted() * pNode->pose.q;
absolutes[i] = absolutes[pNode->parent] * relative;
}
}
for (uint32 i = 0; i < outputCount; ++i)
{
CHierarchy::SNode* pNode = m_hierarchy.GetNode(i);
if (!pNode)
{
continue;
}
CHierarchy::SNode* pParent = pNode->parent < 0 ?
NULL : m_hierarchy.GetNode(pNode->parent);
if (!pParent)
{
pResult[i].q = absolutes[i];
continue;
}
pResult[i].q = absolutes[i];
if (!m_nodes[i].position)
{
pResult[i].t = pResult[pNode->parent].t +
pResult[i].t * absolutes[pNode->parent].GetInverted();
}
}
}
//
bool CMapper::NodeHasLocation(uint32 index)
{
if (CMapperOperator* pOperator = m_nodes[index].position)
{
if (pOperator->IsOfClass("Location"))
{
return true;
}
if (pOperator->HasLinksOfClass("Location"))
{
return true;
}
}
if (CMapperOperator* pOperator = m_nodes[index].orientation)
{
if (pOperator->IsOfClass("Location"))
{
return true;
}
if (pOperator->HasLinksOfClass("Location"))
{
return true;
}
}
return false;
}
void CMapper::GetChildrenIndices(uint32 parent, std::vector<uint32>& children)
{
uint32 nodeCount = m_hierarchy.GetNodeCount();
for (uint32 i = 0; i < nodeCount; ++i)
{
if (m_hierarchy.GetNode(i)->parent != parent)
{
continue;
}
children.push_back(i);
}
}
bool CMapper::ChildrenHaveLocation(uint32 index)
{
std::vector<uint32> children;
GetChildrenIndices(index, children);
uint32 childrenCount = uint32(children.size());
for (uint32 i = 0; i < childrenCount; ++i)
{
if (ChildrenHaveLocation(children[i]))
{
return true;
}
}
return false;
}
bool CMapper::NodeOrChildrenHaveLocation(uint32 index)
{
if (NodeHasLocation(index))
{
return true;
}
std::vector<uint32> children;
GetChildrenIndices(index, children);
uint32 childrenCount = uint32(children.size());
for (uint32 i = 0; i < childrenCount; ++i)
{
if (NodeOrChildrenHaveLocation(children[i]))
{
return true;
}
}
return false;
}
bool CMapper::SerializeTo(XmlNodeRef& node)
{
XmlNodeRef hierarchy = node->newChild("Hierarchy");
uint32 nodeCount = GetNodeCount();
std::vector<IXmlNode*> nodes(nodeCount);
for (uint32 i = 0; i < nodeCount; ++i)
{
if (!NodeOrChildrenHaveLocation(i))
{
continue;
}
CHierarchy::SNode* pNode = m_hierarchy.GetNode(i);
if (!pNode)
{
return false;
}
XmlNodeRef xmlParent = hierarchy;
int32 parent = pNode->parent;
if (parent > -1)
{
xmlParent = nodes[parent];
}
nodes[i] = xmlParent->newChild("Node");
nodes[i]->setAttr("name", pNode->name);
if (CMapperOperator* pOperator = m_nodes[i].position)
{
XmlNodeRef position = nodes[i]->newChild("Position");
XmlNodeRef child = position->newChild("Operator");
if (!pOperator->SerializeWithLinksTo(child))
{
return false;
}
}
if (CMapperOperator* pOperator = m_nodes[i].orientation)
{
XmlNodeRef orientation = nodes[i]->newChild("Orientation");
XmlNodeRef child = orientation->newChild("Operator");
if (!pOperator->SerializeWithLinksTo(child))
{
return false;
}
}
}
return true;
}
bool CMapper::SerializeFrom(XmlNodeRef& node, int32 parent)
{
int childCount = uint32(node->getChildCount());
for (int i = 0; i < childCount; ++i)
{
XmlNodeRef child = node->getChild(i);
if (::_stricmp(child->getTag(), "Node"))
{
continue;
}
uint32 index = m_hierarchy.AddNode(child->getAttr("name"), QuatT(IDENTITY), parent);
if (!SerializeFrom(child, int32(index)))
{
return false;
}
}
return true;
}
bool CMapper::SerializeFrom(XmlNodeRef& node)
{
XmlNodeRef hierarchy = node->findChild("Hierarchy");
if (!hierarchy)
{
return false;
}
m_hierarchy.ClearNodes();
if (!SerializeFrom(hierarchy, -1))
{
return false;
}
m_nodes.resize(m_hierarchy.GetNodeCount());
return true;
}
+75
View File
@@ -0,0 +1,75 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef CRYINCLUDE_EDITOR_ANIMATION_SKELETONMAPPER_H
#define CRYINCLUDE_EDITOR_ANIMATION_SKELETONMAPPER_H
#pragma once
#include "SkeletonHierarchy.h"
#include "SkeletonMapperOperator.h"
namespace Skeleton {
class CMapper
{
public:
struct SNode
{
_smart_ptr<CMapperOperator> position;
_smart_ptr<CMapperOperator> orientation;
};
public:
CMapper();
~CMapper();
public:
CHierarchy& GetHierarchy() { return m_hierarchy; }
void CreateFromHierarchy();
uint32 GetNodeCount() const { return uint32(m_nodes.size()); }
SNode* GetNode(uint32 index) { return &m_nodes[index]; }
const SNode* GetNode(uint32 index) const { return &m_nodes[index]; }
uint32 CreateLocation(const char* name);
void ClearLocations();
int32 FindLocation(const char* name) const;
uint32 GetLocationCount() const { return uint32(m_locations.size()); }
void SetLocation(CMapperLocation& location);
CMapperLocation* GetLocation(uint32 index) { return m_locations[index]; }
const CMapperLocation* GetLocation(uint32 index) const { return m_locations[index]; }
bool CreateLocationsHierarchy(CHierarchy& hierarchy);
void Map(QuatT* pResult);
bool SerializeTo(XmlNodeRef& node);
bool SerializeFrom(XmlNodeRef& node);
private:
bool NodeHasLocation(uint32 index);
bool ChildrenHaveLocation(uint32 index);
bool NodeOrChildrenHaveLocation(uint32 index);
bool SerializeFrom(XmlNodeRef& node, int32 parent);
bool CreateLocationsHierarchy(uint32 index, CHierarchy& hierarchy, int32 hierarchyParent = -1);
// TEMP
void GetChildrenIndices(uint32 parent, std::vector<uint32>& children);
private:
CHierarchy m_hierarchy;
std::vector<_smart_ptr<CMapperLocation> > m_locations;
std::vector<SNode> m_nodes;
};
} // namespace Skeleton
#endif // CRYINCLUDE_EDITOR_ANIMATION_SKELETONMAPPER_H
@@ -0,0 +1,283 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "SkeletonMapperOperator.h"
using namespace Skeleton;
/*
CMapperOperatorDesc
*/
std::vector<CMapperOperatorDesc*> CMapperOperatorDesc::s_descs;
//
CMapperOperatorDesc::CMapperOperatorDesc(const char* name)
{
s_descs.push_back(this);
}
/*
CMapperOperator
*/
CMapperOperator::CMapperOperator(const char* className, uint32 positionCount, uint32 orientationCount)
{
m_className = className;
m_position.resize(positionCount, NULL);
m_orientation.resize(orientationCount, NULL);
}
CMapperOperator::~CMapperOperator()
{
}
//
bool CMapperOperator::IsOfClass(const char* className)
{
if (::_stricmp(m_className, className))
{
return false;
}
return true;
}
uint32 CMapperOperator::HasLinksOfClass(const char* className)
{
uint32 count = 0;
uint32 positionCount = m_position.size();
for (uint32 i = 0; i < positionCount; ++i)
{
CMapperOperator* pOperator = m_position[i];
if (!pOperator)
{
continue;
}
if (pOperator->IsOfClass(className))
{
++count;
}
}
uint32 orientationCount = m_orientation.size();
for (uint32 i = 0; i < orientationCount; ++i)
{
CMapperOperator* pOperator = m_orientation[i];
if (!pOperator)
{
continue;
}
if (pOperator->IsOfClass(className))
{
++count;
}
}
return count;
}
//
bool CMapperOperator::SerializeTo(XmlNodeRef& node)
{
node->setAttr("class", m_className);
uint32 parameterCount = uint32(m_parameters.size());
for (uint32 i = 0; i < parameterCount; ++i)
{
m_parameters[i]->Serialize(node, false);
}
return true;
}
bool CMapperOperator::SerializeFrom(XmlNodeRef& node)
{
uint32 parameterCount = uint32(m_parameters.size());
for (uint32 i = 0; i < parameterCount; ++i)
{
m_parameters[i]->Serialize(node, true);
}
return true;
}
bool CMapperOperator::SerializeWithLinksTo(XmlNodeRef& node)
{
if (!SerializeTo(node))
{
return false;
}
uint32 positionCount = uint32(m_position.size());
for (uint32 i = 0; i < positionCount; ++i)
{
CMapperOperator* pOperator = m_position[i];
if (!pOperator)
{
continue;
}
XmlNodeRef position = node->newChild("Position");
position->setAttr("index", i);
XmlNodeRef child = position->newChild("Operator");
if (!pOperator->SerializeWithLinksTo(child))
{
return false;
}
}
uint32 orientationCount = uint32(m_orientation.size());
for (uint32 i = 0; i < orientationCount; ++i)
{
CMapperOperator* pOperator = m_orientation[i];
if (!pOperator)
{
continue;
}
XmlNodeRef orientation = node->newChild("Orientation");
orientation->setAttr("index", i);
XmlNodeRef child = orientation->newChild("Operator");
if (!pOperator->SerializeWithLinksTo(child))
{
return false;
}
}
return true;
}
bool CMapperOperator::SerializeWithLinksFrom(XmlNodeRef& node)
{
if (!SerializeFrom(node))
{
return false;
}
return true;
}
/*
CMapperOperator_Transform
*/
class CMapperOperator_Transform
: public CMapperOperator
{
public:
CMapperOperator_Transform()
: CMapperOperator("Transform", 1, 1)
{
m_pAngles = new CVariable<Vec3>();
m_pAngles->SetName("rotation");
m_pAngles->Set(Vec3(0.0f, 0.0f, 0.0f));
m_pAngles->SetLimits(-180.0f, 180.0f);
AddParameter(*m_pAngles);
m_pVector = new CVariable<Vec3>();
m_pVector->SetName("vector");
m_pVector->Set(Vec3(0.0f, 0.0f, 0.0f));
AddParameter(*m_pVector);
m_pScale = new CVariable<Vec3>();
m_pScale->SetName("scale");
m_pScale->Set(Vec3(1.0f, 1.0f, 1.0f));
AddParameter(*m_pScale);
}
// CMapperOperator
public:
virtual QuatT CMapperOperator_Transform::Compute()
{
QuatT result(IDENTITY);
m_pVector->Get(result.t);
Vec3 scale;
m_pScale->Get(scale);
Vec3 angles;
m_pAngles->Get(angles);
result.q = Quat::CreateRotationXYZ(
Ang3(DEG2RAD(angles.x), DEG2RAD(angles.y), DEG2RAD(angles.z)));
if (CMapperOperator* pOperator = GetPosition(0))
{
result.t = pOperator->Compute().t.CompMul(scale) + result.t;
}
if (CMapperOperator* pOperator = GetOrientation(0))
{
result.q = pOperator->Compute().q * result.q;
}
return result;
}
private:
CVariable<Vec3>* m_pVector;
CVariable<Vec3>* m_pAngles;
CVariable<Vec3>* m_pScale;
};
SkeletonMapperOperatorRegister(Transform, CMapperOperator_Transform)
class CMapperOperator_PositionsToOrientation
: public CMapperOperator
{
public:
CMapperOperator_PositionsToOrientation()
: CMapperOperator("PositionsToOrientation", 3, 0)
{
}
// CMapperOperator
public:
virtual QuatT Compute()
{
CMapperOperator* pOperator0 = GetPosition(0);
CMapperOperator* pOperator1 = GetPosition(1);
CMapperOperator* pOperator2 = GetPosition(2);
if (!pOperator0 || !pOperator1 || !pOperator2)
{
return QuatT(IDENTITY);
}
Vec3 p0 = pOperator0->Compute().t;
Vec3 p1 = pOperator1->Compute().t;
Vec3 p2 = pOperator2->Compute().t;
Vec3 m = (p1 + p2) * 0.5f;
Vec3 y = (m - p0).GetNormalized();
Vec3 z = (p1 - p2).GetNormalized();
Vec3 x = y % z;
z = x % y;
Matrix33 m33;
m33.SetFromVectors(x, y, z);
QuatT result(IDENTITY);
result.q = Quat(m33);
return result;
}
};
SkeletonMapperOperatorRegister(PositionsToOrientation, CMapperOperator_PositionsToOrientation)
@@ -0,0 +1,163 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef CRYINCLUDE_EDITOR_ANIMATION_SKELETONMAPPEROPERATOR_H
#define CRYINCLUDE_EDITOR_ANIMATION_SKELETONMAPPEROPERATOR_H
#pragma once
#include "../Util/Variable.h"
#undef GetClassName
#define SkeletonMapperOperatorRegister(name, className) \
class CMapperOperatorDesc_##name \
: public CMapperOperatorDesc \
{ \
public: \
CMapperOperatorDesc_##name() \
: CMapperOperatorDesc(#name) { } \
protected: \
virtual const char* GetName() { return #name; } \
virtual CMapperOperator* Create() { return new className(); } \
} mapperOperatorDesc__##name;
namespace Skeleton {
class CMapperOperator;
class CMapperOperatorDesc
{
public:
static uint32 GetCount() { return uint32(s_descs.size()); }
static const char* GetName(uint32 index) { return s_descs[index]->GetName(); }
static CMapperOperator* Create(uint32 index) { return s_descs[index]->Create(); }
private:
static std::vector<CMapperOperatorDesc*> s_descs;
public:
CMapperOperatorDesc(const char* name);
protected:
virtual const char* GetName() = 0;
virtual CMapperOperator* Create() = 0;
};
class CMapperOperator
: public _reference_target_t
{
protected:
CMapperOperator(const char* className, uint32 positionCount, uint32 orientationCount);
~CMapperOperator();
public:
const char* GetClassName() { return m_className; }
uint32 GetPositionCount() const { return uint32(m_position.size()); }
void SetPosition(uint32 index, CMapperOperator* pOperator) { m_position[index] = pOperator; }
CMapperOperator* GetPosition(uint32 index) { return m_position[index]; }
uint32 GetOrientationCount() const { return uint32(m_orientation.size()); }
void SetOrientation(uint32 index, CMapperOperator* pOperator) { m_orientation[index] = pOperator; }
CMapperOperator* GetOrientation(uint32 index) { return m_orientation[index]; }
uint32 GetParameterCount() { return uint32(m_parameters.size()); }
IVariable* GetParameter(uint32 index) { return m_parameters[index]; }
bool IsOfClass(const char* className);
uint32 HasLinksOfClass(const char* className);
bool SerializeTo(XmlNodeRef& node);
bool SerializeFrom(XmlNodeRef& node);
bool SerializeWithLinksTo(XmlNodeRef& node);
bool SerializeWithLinksFrom(XmlNodeRef& node);
protected:
void AddParameter(IVariable& variable) { m_parameters.push_back(&variable); }
public:
virtual QuatT Compute() = 0;
private:
const char* m_className;
std::vector<_smart_ptr<CMapperOperator> > m_position;
std::vector<_smart_ptr<CMapperOperator> > m_orientation;
std::vector<IVariablePtr> m_parameters;
};
class CMapperLocation
: public CMapperOperator
{
public:
CMapperLocation()
: CMapperOperator("Location", 0, 0)
{
m_pName = new CVariable<CString>();
m_pName->SetName("name");
m_pName->SetFlags(m_pName->GetFlags() | IVariable::UI_INVISIBLE);
AddParameter(*m_pName);
m_pAxis = new CVariable<Vec3>();
m_pAxis->SetName("axis");
m_pAxis->SetLimits(-3.0f, +3.0f);
m_pAxis->Set(Vec3(1.0f, 2.0f, 3.0f));
AddParameter(*m_pAxis);
m_location = QuatT(IDENTITY);
}
public:
void SetName(const char* name) { m_pName->Set(name); }
CString GetName() const { CString s; m_pName->Get(s); return s; }
void SetLocation(const QuatT& location) { m_location = location; }
const QuatT& GetLocation() const { return m_location; }
// CMapperOperator
public:
virtual QuatT Compute()
{
Vec3 axis;
m_pAxis->Get(axis);
uint32 x = fabs_tpl(axis.x);
uint32 y = fabs_tpl(axis.y);
uint32 z = fabs_tpl(axis.z);
if (x < 1 || y < 1 || z < 1 ||
x > 3 || y > 3 || y > 3 ||
x == y || x == z || y == z)
{
return QuatT(IDENTITY);
}
Matrix33 matrix;
matrix.SetFromVectors(
m_location.q.GetColumn(x - 1) * f32(::sgn(axis.x)),
m_location.q.GetColumn(y - 1) * f32(::sgn(axis.y)),
m_location.q.GetColumn(z - 1) * f32(::sgn(axis.z)));
if (!matrix.IsOrthonormalRH(0.01f))
{
return QuatT(IDENTITY);
}
QuatT result = m_location;
result.q = Quat(matrix);
return result;
}
private:
CVariable<CString>* m_pName;
CVariable<Vec3>* m_pAxis;
QuatT m_location;
};
} // namespace Skeleton
#endif // CRYINCLUDE_EDITOR_ANIMATION_SKELETONMAPPEROPERATOR_H
+816
View File
@@ -0,0 +1,816 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "AnimationContext.h"
// CryCommon
#include <CryCommon/Maestro/Bus/EditorSequenceBus.h>
// Editor
#include "TrackView/TrackViewDialog.h"
#include "RenderViewport.h"
#include "ViewManager.h"
#include "Objects/SelectionGroup.h"
#include "Include/IObjectManager.h"
#include "Objects/EntityObject.h"
//////////////////////////////////////////////////////////////////////////
// Movie Callback.
//////////////////////////////////////////////////////////////////////////
class CMovieCallback
: public IMovieCallback
{
protected:
virtual void OnMovieCallback(ECallbackReason reason, [[maybe_unused]] IAnimNode* pNode)
{
switch (reason)
{
case CBR_CHANGENODE:
// Invalidate nodes
break;
case CBR_CHANGETRACK:
{
// Invalidate tracks
CTrackViewDialog* pTrackViewDialog = CTrackViewDialog::GetCurrentInstance();
if (pTrackViewDialog)
{
pTrackViewDialog->InvalidateDopeSheet();
}
}
break;
}
}
void OnSetCamera(const SCameraParams& Params)
{
// Only switch camera when in Play mode.
GUID camObjId = GUID_NULL;
if (Params.cameraEntityId.IsValid())
{
// Find owner editor entity.
CEntityObject* pEditorEntity = CEntityObject::FindFromEntityId(Params.cameraEntityId);
if (pEditorEntity)
{
camObjId = pEditorEntity->GetId();
}
CViewport* pViewport = GetIEditor()->GetViewManager()->GetSelectedViewport();
if (CRenderViewport* rvp = viewport_cast<CRenderViewport*>(pViewport))
{
if (!rvp->IsSequenceCamera())
{
return;
}
}
}
// Switch camera in active rendering view.
if (GetIEditor()->GetViewManager())
{
GetIEditor()->GetViewManager()->SetCameraObjectId(camObjId);
}
};
bool IsSequenceCamUsed() const
{
if (gEnv->IsEditorGameMode() == true)
{
return true;
}
if (GetIEditor()->GetViewManager() == NULL)
{
return false;
}
CViewport* pRendView = GetIEditor()->GetViewManager()->GetViewport(ET_ViewportCamera);
if (pRendView)
{
return pRendView->IsSequenceCamera();
}
return false;
}
};
static CMovieCallback s_movieCallback;
//////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------------
//!
class CAnimationContextPostRender
: public IPostRenderer
{
public:
CAnimationContextPostRender(CAnimationContext* pAC)
: m_pAC(pAC){}
void OnPostRender() const { assert(m_pAC); m_pAC->OnPostRender(); }
protected:
CAnimationContext* m_pAC;
};
//////////////////////////////////////////////////////////////////////////
CAnimationContext::CAnimationContext()
{
m_paused = 0;
m_playing = false;
m_recording = false;
m_bSavedRecordingState = false;
m_timeRange.Set(0, 0);
m_timeMarker.Set(0, 0);
m_currTime = 0.0f;
m_lastTimeChangedNotificationTime = .0f;
m_bForceUpdateInNextFrame = false;
m_fTimeScale = 1.0f;
m_pSequence = nullptr;
m_mostRecentSequenceId.SetInvalid();
m_mostRecentSequenceTime = 0.0f;
m_bLooping = false;
m_bAutoRecording = false;
m_fRecordingTimeStep = 0;
m_bSingleFrame = false;
m_bPostRenderRegistered = false;
m_bForcingAnimation = false;
GetIEditor()->GetUndoManager()->AddListener(this);
GetIEditor()->GetSequenceManager()->AddListener(this);
GetIEditor()->RegisterNotifyListener(this);
}
//////////////////////////////////////////////////////////////////////////
CAnimationContext::~CAnimationContext()
{
GetIEditor()->GetSequenceManager()->RemoveListener(this);
GetIEditor()->GetUndoManager()->RemoveListener(this);
GetIEditor()->UnregisterNotifyListener(this);
}
//////////////////////////////////////////////////////////////////////////
void CAnimationContext::Init()
{
gEnv->pMovieSystem->SetCallback(&s_movieCallback);
REGISTER_COMMAND("mov_goToFrameEditor", (ConsoleCommandFunc)GoToFrameCmd, 0, "Make a specified sequence go to a given frame time in the editor.");
}
//////////////////////////////////////////////////////////////////////////
void CAnimationContext::AddListener(IAnimationContextListener* pListener)
{
stl::push_back_unique(m_contextListeners, pListener);
}
//////////////////////////////////////////////////////////////////////////
void CAnimationContext::RemoveListener(IAnimationContextListener* pListener)
{
stl::find_and_erase(m_contextListeners, pListener);
}
void CAnimationContext::NotifyTimeChangedListenersUsingCurrTime() const
{
for (size_t i = 0; i < m_contextListeners.size(); ++i)
{
m_contextListeners[i]->OnTimeChanged(m_currTime);
}
m_lastTimeChangedNotificationTime = m_currTime;
}
//////////////////////////////////////////////////////////////////////////
void CAnimationContext::SetSequence(CTrackViewSequence* sequence, bool force, bool noNotify, bool user)
{
float newSeqStartTime = .0f;
CTrackViewSequence* pCurrentSequence = m_pSequence;
if (!force && sequence == pCurrentSequence)
{
return;
}
// Prevent keys being created from time change
const bool bRecording = m_recording;
m_recording = false;
SetRecordingInternal(false);
if (sequence)
{
newSeqStartTime = sequence->GetTimeRange().start;
}
m_currTime = newSeqStartTime;
m_fRecordingCurrTime = newSeqStartTime;
if (!m_bPostRenderRegistered)
{
if (GetIEditor() && GetIEditor()->GetViewManager())
{
CViewport* pViewport = GetIEditor()->GetViewManager()->GetViewport(ET_ViewportCamera);
if (pViewport)
{
pViewport->AddPostRenderer(new CAnimationContextPostRender(this));
m_bPostRenderRegistered = true;
}
}
}
if (m_pSequence)
{
m_pSequence->Deactivate();
if (m_playing)
{
m_pSequence->EndCutScene();
}
m_pSequence->UnBindFromEditorObjects();
}
m_pSequence = sequence;
// Notify a new sequence was just selected.
Maestro::EditorSequenceNotificationBus::Broadcast(&Maestro::EditorSequenceNotificationBus::Events::OnSequenceSelected, m_pSequence ? m_pSequence->GetSequenceComponentEntityId() : AZ::EntityId());
if (m_pSequence)
{
// Set the last valid sequence that was selected.
m_mostRecentSequenceId = m_pSequence->GetSequenceComponentEntityId();
if (m_playing)
{
m_pSequence->BeginCutScene(true);
}
m_timeRange = m_pSequence->GetTimeRange();
m_timeMarker = m_timeRange;
m_pSequence->Activate();
m_pSequence->PrecacheData(newSeqStartTime);
m_pSequence->BindToEditorObjects();
}
else if (user)
{
// If this was a sequence that was selected by the user in Track View
// and it was "No Sequence" clear the m_mostRecentSequenceId so the sequence
// will not be reselected at unwanted events like a slice reload or an undo operation.
m_mostRecentSequenceId.SetInvalid();
}
ForceAnimation();
if (!noNotify)
{
NotifyTimeChangedListenersUsingCurrTime();
for (size_t i = 0; i < m_contextListeners.size(); ++i)
{
m_contextListeners[i]->OnSequenceChanged(m_pSequence);
}
}
TimeChanged(newSeqStartTime);
m_recording = bRecording;
SetRecordingInternal(bRecording);
}
//////////////////////////////////////////////////////////////////////////
void CAnimationContext::UpdateTimeRange()
{
if (m_pSequence)
{
m_timeRange = m_pSequence->GetTimeRange();
// reset the current time to make sure it is clamped
// to the new range.
SetTime(m_currTime);
}
}
//////////////////////////////////////////////////////////////////////////
void CAnimationContext::SetTime(float t)
{
if (t < m_timeRange.start)
{
t = m_timeRange.start;
}
if (t > m_timeRange.end)
{
t = m_timeRange.end;
}
if (fabs(m_currTime - t) < 0.001f)
{
return;
}
m_currTime = t;
m_fRecordingCurrTime = t;
ForceAnimation();
UpdateAnimatedLights();
NotifyTimeChangedListenersUsingCurrTime();
}
void CAnimationContext::TimeChanged(float newTime)
{
if (m_pSequence)
{
m_mostRecentSequenceTime = newTime;
m_pSequence->TimeChanged(newTime);
}
}
//////////////////////////////////////////////////////////////////////////
void CAnimationContext::OnSequenceActivated(AZ::EntityId entityId)
{
// If nothing is selected and there is a valid most recent selected
// try to find that sequence by id and select it. This is useful
// for restoring the selected sequence during undo and redo.
if (m_pSequence == nullptr && m_mostRecentSequenceId.IsValid())
{
if (entityId == m_mostRecentSequenceId)
{
auto editor = GetIEditor();
if (editor != nullptr)
{
auto manager = editor->GetSequenceManager();
if (manager != nullptr)
{
auto sequence = manager->GetSequenceByEntityId(m_mostRecentSequenceId);
if (sequence != nullptr)
{
// Hang onto this because SetSequence() will reset it.
float lastTime = m_mostRecentSequenceTime;
SetSequence(sequence, false, false);
// Restore the current time.
SetTime(lastTime);
// Notify time may have changed, use m_currTime incase it was clamped by SetTime()
TimeChanged(m_currTime);
}
}
}
}
}
}
//////////////////////////////////////////////////////////////////////////
void CAnimationContext::Pause()
{
assert(m_paused >= 0);
m_paused++;
if (m_recording)
{
SetRecordingInternal(false);
}
GetIEditor()->GetMovieSystem()->Pause();
if (m_pSequence)
{
m_pSequence->Pause();
}
}
//////////////////////////////////////////////////////////////////////////
void CAnimationContext::Resume()
{
assert(m_paused > 0);
m_paused--;
if (m_recording && m_paused == 0)
{
SetRecordingInternal(true);
}
GetIEditor()->GetMovieSystem()->Resume();
if (m_pSequence)
{
m_pSequence->Resume();
}
}
//////////////////////////////////////////////////////////////////////////
void CAnimationContext::SetRecording(bool recording)
{
if (recording == m_recording)
{
return;
}
m_paused = 0;
m_recording = recording;
m_playing = false;
if (!recording && m_fRecordingTimeStep != 0)
{
SetAutoRecording(false, 0);
}
// If started recording, assume we have modified the document.
GetIEditor()->SetModifiedFlag();
SetRecordingInternal(recording);
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void CAnimationContext::SetPlaying(bool playing)
{
if (playing == m_playing)
{
return;
}
m_paused = 0;
m_playing = playing;
m_recording = false;
SetRecordingInternal(false);
if (playing)
{
IMovieSystem* pMovieSystem = GetIEditor()->GetMovieSystem();
pMovieSystem->Resume();
if (m_pSequence)
{
m_pSequence->Resume();
IMovieUser* pMovieUser = pMovieSystem->GetUser();
if (pMovieUser)
{
m_pSequence->BeginCutScene(true);
}
}
pMovieSystem->ResumeCutScenes();
}
else
{
IMovieSystem* pMovieSystem = GetIEditor()->GetMovieSystem();
pMovieSystem->Pause();
if (m_pSequence)
{
m_pSequence->Pause();
}
pMovieSystem->PauseCutScenes();
if (m_pSequence)
{
IMovieUser* pMovieUser = pMovieSystem->GetUser();
if (pMovieUser)
{
m_pSequence->EndCutScene();
}
}
}
}
//////////////////////////////////////////////////////////////////////////
void CAnimationContext::Update()
{
if (m_bForceUpdateInNextFrame)
{
ForceAnimation();
m_bForceUpdateInNextFrame = false;
}
// If looking through camera object and recording animation, do not allow camera shake
if ((GetIEditor()->GetViewManager()->GetCameraObjectId() != GUID_NULL) && GetIEditor()->GetAnimation()->IsRecording())
{
GetIEditor()->GetMovieSystem()->EnableCameraShake(false);
}
else
{
GetIEditor()->GetMovieSystem()->EnableCameraShake(true);
}
if (m_paused > 0 || !(m_playing || m_bAutoRecording))
{
if (m_pSequence)
{
m_pSequence->StillUpdate();
}
if (!m_recording)
{
GetIEditor()->GetMovieSystem()->StillUpdate();
}
return;
}
ITimer* pTimer = GetIEditor()->GetSystem()->GetITimer();
if (!m_bAutoRecording)
{
AnimateActiveSequence();
float dt = pTimer->GetFrameTime();
m_currTime += dt * m_fTimeScale;
if (!m_recording)
{
GetIEditor()->GetMovieSystem()->PreUpdate(dt);
GetIEditor()->GetMovieSystem()->PostUpdate(dt);
}
}
else
{
float dt = pTimer->GetFrameTime();
m_fRecordingCurrTime += dt * m_fTimeScale;
if (fabs(m_fRecordingCurrTime - m_currTime) > m_fRecordingTimeStep)
{
m_currTime += m_fRecordingTimeStep;
}
}
if (m_currTime > m_timeMarker.end)
{
if (m_bAutoRecording)
{
SetAutoRecording(false, 0);
}
else
{
if (m_bLooping)
{
m_currTime = m_timeMarker.start;
if (m_pSequence)
{
m_pSequence->OnLoop();
}
}
else
{
SetPlaying(false);
m_currTime = m_timeMarker.end;
}
}
}
if (m_bAutoRecording)
{
// This is auto recording mode.
// Send sync with physics event to all selected entities.
GetIEditor()->GetSelection()->SendEvent(EVENT_PHYSICS_GETSTATE);
}
if (fabs(m_lastTimeChangedNotificationTime - m_currTime) > 0.001f)
{
NotifyTimeChangedListenersUsingCurrTime();
}
UpdateAnimatedLights();
}
//////////////////////////////////////////////////////////////////////////
void CAnimationContext::ForceAnimation()
{
if (m_bForcingAnimation)
{
// reentrant calls are possible when using subsequences
return;
}
m_bForcingAnimation = true;
// Before animating node, pause recording.
if (m_bAutoRecording)
{
Pause();
}
AnimateActiveSequence();
// Animate a second time to properly update camera DoF
AnimateActiveSequence();
if (m_bAutoRecording)
{
Resume();
}
m_bForcingAnimation = false;
}
//////////////////////////////////////////////////////////////////////////
void CAnimationContext::SetAutoRecording(bool bEnable, float fTimeStep)
{
if (bEnable)
{
m_bAutoRecording = true;
m_fRecordingTimeStep = fTimeStep;
SetRecording(bEnable);
}
else
{
m_bAutoRecording = false;
m_fRecordingTimeStep = 0;
}
}
//////////////////////////////////////////////////////////////////////////
void CAnimationContext::GoToFrameCmd(IConsoleCmdArgs* pArgs)
{
if (pArgs->GetArgCount() < 2)
{
gEnv->pLog->LogError("GoToFrame: You must provide a 'frame time' to go to");
return;
}
assert(GetIEditor()->GetAnimation());
CTrackViewSequence* pSeq = GetIEditor()->GetAnimation()->GetSequence();
if (!pSeq)
{
gEnv->pLog->LogError("GoToFrame: No active animation sequence");
return;
}
float targetFrame = (float)atof(pArgs->GetArg(1));
if (pSeq->GetTimeRange().start > targetFrame || targetFrame > pSeq->GetTimeRange().end)
{
gEnv->pLog->LogError("GoToFrame: requested time %f is outside the range of sequence %s (%f, %f)", targetFrame, pSeq->GetName(), pSeq->GetTimeRange().start, pSeq->GetTimeRange().end);
return;
}
GetIEditor()->GetAnimation()->m_currTime = targetFrame;
GetIEditor()->GetAnimation()->m_bSingleFrame = true;
GetIEditor()->GetAnimation()->ForceAnimation();
}
//////////////////////////////////////////////////////////////////////////
void CAnimationContext::OnPostRender()
{
if (m_pSequence)
{
SAnimContext ac;
ac.dt = 0;
ac.fps = GetIEditor()->GetSystem()->GetITimer()->GetFrameRate();
ac.time = m_currTime;
ac.singleFrame = true;
ac.forcePlay = true;
m_pSequence->Render(ac);
}
}
//////////////////////////////////////////////////////////////////////////
void CAnimationContext::UpdateAnimatedLights()
{
bool bLightAnimationSetActive = m_pSequence && (m_pSequence->GetFlags() & IAnimSequence::eSeqFlags_LightAnimationSet);
if (bLightAnimationSetActive == false)
{
return;
}
std::vector<CBaseObject*> entityObjects;
GetIEditor()->GetObjectManager()->FindObjectsOfType(&CEntityObject::staticMetaObject, entityObjects);
std::for_each(std::begin(entityObjects), std::end(entityObjects),
[this](CBaseObject* pBaseObject)
{
CEntityObject* pEntityObject = static_cast<CEntityObject*>(pBaseObject);
bool bLight = pEntityObject && pEntityObject->GetEntityClass().compare("Light") == 0;
if (bLight)
{
bool bTimeScrubbing = pEntityObject->GetEntityPropertyBool("bTimeScrubbingInTrackView");
if (bTimeScrubbing)
{
pEntityObject->SetEntityPropertyFloat("_fTimeScrubbed", m_currTime);
}
}
});
}
//////////////////////////////////////////////////////////////////////////
void CAnimationContext::BeginUndoTransaction()
{
m_bSavedRecordingState = m_recording;
SetRecordingInternal(false);
}
//////////////////////////////////////////////////////////////////////////
void CAnimationContext::EndUndoTransaction()
{
if (m_pSequence)
{
m_pSequence->BindToEditorObjects();
}
SetRecordingInternal(m_bSavedRecordingState);
}
//////////////////////////////////////////////////////////////////////////
void CAnimationContext::TogglePlay()
{
if (!IsPlaying())
{
SetPlaying(true);
}
else
{
SetPlaying(false);
}
}
//////////////////////////////////////////////////////////////////////////
void CAnimationContext::OnSequenceRemoved(CTrackViewSequence* pSequence)
{
if (m_pSequence == pSequence)
{
SetSequence(nullptr, true, false);
}
}
//////////////////////////////////////////////////////////////////////////
void CAnimationContext::OnEditorNotifyEvent(EEditorNotifyEvent event)
{
switch (event)
{
case eNotify_OnBeginGameMode:
if (m_pSequence)
{
m_pSequence->Resume();
}
case eNotify_OnBeginSceneSave:
case eNotify_OnBeginLayerExport:
if (m_pSequence)
{
m_sequenceToRestore = m_pSequence->GetSequenceComponentEntityId();
}
else
{
m_sequenceToRestore.SetInvalid();
}
m_sequenceRestoreTime = GetTime();
m_bSavedRecordingState = m_recording;
SetRecordingInternal(false);
SetSequence(nullptr, true, true);
break;
case eNotify_OnEndGameMode:
case eNotify_OnEndSceneSave:
case eNotify_OnEndLayerExport:
m_currTime = m_sequenceRestoreTime;
SetSequence(GetIEditor()->GetSequenceManager()->GetSequenceByEntityId(m_sequenceToRestore), true, true);
SetTime(m_sequenceRestoreTime);
SetRecordingInternal(m_bSavedRecordingState);
break;
case eNotify_OnQuit:
case eNotify_OnCloseScene:
SetSequence(nullptr, true, false);
break;
case eNotify_OnBeginNewScene:
SetSequence(nullptr, false, false);
break;
case eNotify_OnBeginLoad:
m_mostRecentSequenceId.SetInvalid();
m_mostRecentSequenceTime = 0.0f;
m_bSavedRecordingState = m_recording;
SetRecordingInternal(false);
GetIEditor()->GetAnimation()->SetSequence(nullptr, false, false);
break;
case eNotify_OnEndLoad:
SetRecordingInternal(m_bSavedRecordingState);
break;
}
}
void CAnimationContext::SetRecordingInternal(bool enableRecording)
{
GetIEditor()->GetMovieSystem()->SetRecording(enableRecording);
if (m_pSequence)
{
m_pSequence->SetRecording(enableRecording);
}
}
void CAnimationContext::AnimateActiveSequence()
{
if (!m_pSequence)
{
return;
}
SAnimContext ac;
ac.dt = 0;
ac.fps = GetIEditor()->GetSystem()->GetITimer()->GetFrameRate();
ac.time = m_currTime;
ac.singleFrame = true;
ac.forcePlay = true;
m_pSequence->Animate(ac);
m_pSequence->SyncToConsole(ac);
}
+11
View File
@@ -0,0 +1,11 @@
--- Editor/AnimationContext.cpp
+++ Editor/AnimationContext.cpp
@@ -612,7 +612,7 @@ void CAnimationContext::UpdateAnimatedLights()
return;
std::vector<CBaseObject*> entityObjects;
- GetIEditor()->GetObjectManager()->FindObjectsOfType<CEntityObject*>(entityObjects);
+ GetIEditor()->GetObjectManager()->FindObjectsOfType(&CEntityObject::staticMetaObject, entityObjects);
std::for_each(std::begin(entityObjects), std::end(entityObjects),
[this](CBaseObject *pBaseObject)
{
+269
View File
@@ -0,0 +1,269 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef CRYINCLUDE_EDITOR_ANIMATIONCONTEXT_H
#define CRYINCLUDE_EDITOR_ANIMATIONCONTEXT_H
#pragma once
#include "Undo/Undo.h"
#include "TrackView/TrackViewSequenceManager.h"
#include <Range.h>
#include <IMovieSystem.h>
class CTrackViewSequence;
/** CAnimationContext listener interface
*/
struct IAnimationContextListener
{
virtual void OnSequenceChanged([[maybe_unused]] CTrackViewSequence* pNewSequence) {}
virtual void OnTimeChanged([[maybe_unused]] float newTime) {}
};
AZ_PUSH_DISABLE_DLL_EXPORT_BASECLASS_WARNING
/** CAnimationContext stores information about current editable animation sequence.
Stores information about whenever animation is being recorded know,
current sequence, current time in sequence etc.
*/
class SANDBOX_API CAnimationContext
: public IEditorNotifyListener
, public IUndoManagerListener
, public ITrackViewSequenceManagerListener
{
AZ_POP_DISABLE_DLL_EXPORT_BASECLASS_WARNING
public:
//////////////////////////////////////////////////////////////////////////
// Constructors.
//////////////////////////////////////////////////////////////////////////
/** Constructor.
*/
CAnimationContext();
~CAnimationContext();
//////////////////////////////////////////////////////////////////////////
// Accessors
//////////////////////////////////////////////////////////////////////////
void Init();
// Listeners
void AddListener(IAnimationContextListener* pListener);
void RemoveListener(IAnimationContextListener* pListener);
/** Return current animation time in active sequence.
@return Current time.
*/
float GetTime() const { return m_currTime; };
float GetTimeScale() const { return m_fTimeScale; }
void SetTimeScale(float fScale) { m_fTimeScale = fScale; }
/** Set active editing sequence.
@param sequence New active sequence.
@param force Set to true to always run all of the new active sequence code including listeners even if the sequences is already selected.
@param noNotify Set to true to skip over notifying listeners when a new sequences is selected.
@param user Set to true if the new sequence is being selected by the user, false if set by internal system code.
*/
void SetSequence(CTrackViewSequence* sequence, bool force, bool noNotify, bool user = false);
/** Get currently edited sequence.
*/
CTrackViewSequence* GetSequence() const { return m_pSequence; };
/** Set time markers to play within.
*/
void SetMarkers(Range Marker) { m_timeMarker = Marker; }
/** Get time markers to play within.
*/
Range GetMarkers() { return m_timeMarker; }
/** Get time range of active animation sequence.
*/
Range GetTimeRange() const { return m_timeRange; }
/** Returns true if editor is recording animations now.
*/
bool IsRecording() const { return m_recording && m_paused == 0; };
/** Returns true if editor is playing animation now.
*/
bool IsPlaying() const { return m_playing && m_paused == 0; };
/** Returns true if currently playing or recording is paused.
*/
bool IsPaused() const { return m_paused > 0; }
/** Return if animation context is now in playing mode.
In difference from IsPlaying function this function not affected by pause state.
*/
bool IsPlayMode() const { return m_playing; };
/** Return if animation context is now in recording mode.
In difference from IsRecording function this function not affected by pause state.
*/
bool IsRecordMode() const { return m_recording; };
/** Returns true if currently looping as activated.
*/
bool IsLoopMode() const { return m_bLooping; }
/** Enable/Disable looping.
*/
void SetLoopMode(bool bLooping) { m_bLooping = bLooping; }
//////////////////////////////////////////////////////////////////////////
// Operators
//////////////////////////////////////////////////////////////////////////
/** Set current animation time in active sequence.
@param seq New active time.
*/
void SetTime(float t);
/** Set time in active sequence for reset animation.
@param seq New active time.
*/
void SetResetTime(float t) {m_resetTime = t; };
/** Start animation recorduing.
Automatically stop playing.
@param recording True to start recording, false to stop.
*/
void SetRecording(bool playing);
/** Enables/Disables automatic recording, sets the time step for each recorded frame.
*/
void SetAutoRecording(bool bEnable, float fTimeStep);
//! Check if auto recording enabled.
bool IsAutoRecording() const { return m_bAutoRecording; };
/** Start/Stop animation playing.
Automatically stop recording.
@param playing True to start playing, false to stop.
*/
void SetPlaying(bool playing);
/** Pause animation playing/recording.
*/
void Pause();
/** Toggle playback
*/
void TogglePlay();
/** Resume animation playing/recording.
*/
void Resume();
/** Called every frame to update all animations if animation should be playing.
*/
void Update();
/** Force animation for current sequence.
*/
void ForceAnimation();
void OnPostRender();
void UpdateTimeRange();
/** Notify after a time change is complete and time control is released to 'playback' controls, for example after
* a timeline drag
*/
void TimeChanged(float newTime);
/** Notify after a sequence has been activated, useful for Undo/Redo
*/
void OnSequenceActivated(AZ::EntityId entityId);
private:
static void GoToFrameCmd(IConsoleCmdArgs* pArgs);
// Updates the animation time of lights animated by the light animation set.
void UpdateAnimatedLights();
void NotifyTimeChangedListenersUsingCurrTime() const;
virtual void BeginUndoTransaction() override;
virtual void EndUndoTransaction() override;
virtual void OnSequenceRemoved(CTrackViewSequence* pSequence) override;
virtual void OnEditorNotifyEvent(EEditorNotifyEvent event);
void AnimateActiveSequence();
void SetRecordingInternal(bool enableRecording);
//! Current time within active animation sequence.
float m_currTime;
//! Used to stash the time we send out OnTimeChanged notifications
mutable float m_lastTimeChangedNotificationTime;
//! Force update in next frame
bool m_bForceUpdateInNextFrame;
//! Time within active animation sequence while reset animation.
float m_resetTime;
float m_fTimeScale;
// Recording time step.
float m_fRecordingTimeStep;
float m_fRecordingCurrTime;
bool m_bAutoRecording;
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
//! Time range of active animation sequence.
Range m_timeRange;
Range m_timeMarker;
//! Currently active animation sequence.
CTrackViewSequence* m_pSequence;
//! Id of latest valid sequence that was selected. Useful for restoring the selected
//! sequence after undo has destroyed and recreated it.
AZ::EntityId m_mostRecentSequenceId;
//! The current time of the most recent selected sequence. It's very useful to restore this after an undo.
float m_mostRecentSequenceTime;
//! Id of active sequence to restore (for switching back from game mode and saving)
AZ::EntityId m_sequenceToRestore;
//! Time of active sequence (for switching back from game mode and saving)
float m_sequenceRestoreTime;
bool m_bLooping;
//! True if editor is recording animations now.
bool m_recording;
bool m_bSavedRecordingState;
//! True if editor is playing animation now.
bool m_playing;
//! Stores how many times animation have been paused prior to calling resume.
int m_paused;
bool m_bSingleFrame;
bool m_bPostRenderRegistered;
bool m_bForcingAnimation;
//! Listeners
std::vector<IAnimationContextListener*> m_contextListeners;
AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
};
#endif // CRYINCLUDE_EDITOR_ANIMATIONCONTEXT_H
@@ -0,0 +1,60 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "AssetDatabaseLocationListener.h"
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
// AzToolsFramework
#include <AzToolsFramework/AssetDatabase/AssetDatabaseConnection.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserBus.h>
#include <AzToolsFramework/API/AssetDatabaseBus.h>
namespace AssetDatabase
{
AssetDatabaseLocationListener::AssetDatabaseLocationListener()
{
BusConnect();
m_assetDatabaseConnection = new AzToolsFramework::AssetDatabase::AssetDatabaseConnection();
m_assetDatabaseConnection->OpenDatabase();
AzToolsFramework::AssetBrowser::AssetDatabaseLocationNotificationBus::Broadcast(&AzToolsFramework::AssetBrowser::AssetDatabaseLocationNotifications::OnDatabaseInitialized);
}
AssetDatabaseLocationListener::~AssetDatabaseLocationListener()
{
BusDisconnect();
delete m_assetDatabaseConnection;
m_assetDatabaseConnection = nullptr;
}
AzToolsFramework::AssetDatabase::AssetDatabaseConnection* AssetDatabaseLocationListener::GetAssetDatabaseConnection() const
{
return m_assetDatabaseConnection;
}
bool AssetDatabaseLocationListener::GetAssetDatabaseLocation(AZStd::string& result)
{
if (auto registry = AZ::SettingsRegistry::Get(); registry != nullptr)
{
AZ::SettingsRegistryInterface::FixedValueString projectCacheRootValue;
if (registry->Get(projectCacheRootValue, AZ::SettingsRegistryMergeUtils::FilePathKey_CacheProjectRootFolder);
!projectCacheRootValue.empty())
{
result = projectCacheRootValue;
result += "/assetdb.sqlite";
return true;
}
}
return false;
}
}//namespace AssetDatabase
@@ -0,0 +1,34 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/std/string/string.h>
#include <AzToolsFramework/API/AssetDatabaseBus.h>
#include <AzToolsFramework/AssetDatabase/AssetDatabaseConnection.h>
namespace AssetDatabase
{
class AssetDatabaseLocationListener
: protected AzToolsFramework::AssetDatabase::AssetDatabaseRequestsBus::Handler
{
public:
AssetDatabaseLocationListener();
~AssetDatabaseLocationListener();
AzToolsFramework::AssetDatabase::AssetDatabaseConnection* GetAssetDatabaseConnection() const;
protected:
//////////////////////////////////////////////////////////////////////////
//AzToolsFramework::AssetDatabase::AssetDatabaseRequests::Bus::Listener
bool GetAssetDatabaseLocation(AZStd::string& result) override;
//////////////////////////////////////////////////////////////////////////
private:
AzToolsFramework::AssetDatabase::AssetDatabaseConnection* m_assetDatabaseConnection = nullptr;
};
}//namespace AssetDatabase
@@ -0,0 +1,68 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "AssetEditorRequestsHandler.h"
// AzCore
#include <AzCore/Asset/AssetManager.h>
// Editor
#include "AssetEditorWindow.h"
#include "QtViewPaneManager.h"
AssetEditorRequestsHandler::AssetEditorRequestsHandler()
{
AzToolsFramework::AssetEditor::AssetEditorRequestsBus::Handler::BusConnect();
AzToolsFramework::EditorEvents::Bus::Handler::BusConnect();
}
AssetEditorRequestsHandler::~AssetEditorRequestsHandler()
{
AzToolsFramework::AssetEditor::AssetEditorRequestsBus::Handler::BusDisconnect();
AzToolsFramework::EditorEvents::Bus::Handler::BusDisconnect();
}
void AssetEditorRequestsHandler::NotifyRegisterViews()
{
using namespace AzToolsFramework::AssetEditor;
if (auto assetsWindowsToRestore = AZ::UserSettings::CreateFind<AssetEditorWindowSettings>(AZ::Crc32(AssetEditorWindowSettings::s_name), AZ::UserSettings::CT_GLOBAL))
{
//copy the current list and clear it since they will be re-added as we request to open them
auto windowsToOpen = assetsWindowsToRestore->m_openAssets;
assetsWindowsToRestore->m_openAssets.clear();
for (auto&& assetRef : windowsToOpen)
{
AssetEditorWindow::RegisterViewClass(assetRef);
}
}
}
void AssetEditorRequestsHandler::CreateNewAsset(const AZ::Data::AssetType& assetType)
{
using namespace AzToolsFramework::AssetEditor;
AzToolsFramework::OpenViewPane(LyViewPane::AssetEditor);
AssetEditorWidgetRequestsBus::Broadcast(&AssetEditorWidgetRequests::CreateAsset, assetType);
}
void AssetEditorRequestsHandler::OpenAssetEditor(const AZ::Data::Asset<AZ::Data::AssetData>& asset)
{
using namespace AzToolsFramework::AssetEditor;
// Open the AssetEditor if it isn't open already.
QtViewPaneManager::instance()->OpenPane(LyViewPane::AssetEditor, QtViewPane::OpenMode::RestoreLayout);
AssetEditorWidgetRequestsBus::Broadcast(&AssetEditorWidgetRequests::OpenAsset, asset);
}
void AssetEditorRequestsHandler::OpenAssetEditorById(const AZ::Data::AssetId assetId)
{
AZ::Data::Asset<AZ::Data::AssetData> asset = AZ::Data::AssetManager::Instance().GetAsset<AZ::Data::AssetData>(assetId, AZ::Data::AssetLoadBehavior::NoLoad);
OpenAssetEditor(asset);
}
@@ -0,0 +1,34 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Serialization/SerializeContext.h>
#include <AzToolsFramework/AssetEditor/AssetEditorBus.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
class AssetEditorRequestsHandler
: public AzToolsFramework::AssetEditor::AssetEditorRequestsBus::Handler
, public AzToolsFramework::EditorEvents::Bus::Handler
{
public:
AZ_CLASS_ALLOCATOR(AssetEditorRequestsHandler, AZ::SystemAllocator, 0);
AssetEditorRequestsHandler();
~AssetEditorRequestsHandler() override;
//////////////////////////////////////////////////////////////////////////
// AssetEditorRequests
//////////////////////////////////////////////////////////////////////////
void CreateNewAsset(const AZ::Data::AssetType& assetType) override;
void OpenAssetEditor(const AZ::Data::Asset<AZ::Data::AssetData>& asset) override;
void OpenAssetEditorById(const AZ::Data::AssetId assetId) override;
//////////////////////////////////////////////////////////////////////////
// AzToolsFramework::EditorEvents::Bus::Handler
//////////////////////////////////////////////////////////////////////////
void NotifyRegisterViews() override;
};
@@ -0,0 +1,163 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "AssetEditorWindow.h"
// Qt
#include <QMessageBox>
// AzCore
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/UserSettings/UserSettingsComponent.h>
#include <AzCore/Utils/Utils.h>
// AzToolsFramework
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/API/ViewPaneOptions.h>
// Editor
#include "LyViewPaneNames.h"
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
#include <AssetEditor/ui_AssetEditorWindow.h>
AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
namespace AssetEditorUtils
{
AssetEditorWindow* CreateAssetEditorWithAsset(const AZ::Data::Asset<AZ::Data::AssetData> assetRef)
{
AssetEditorWindow* assetEditorWindow = new AssetEditorWindow();
if (auto assetsWindowsToRestore = AZ::UserSettings::CreateFind<AzToolsFramework::AssetEditor::AssetEditorWindowSettings>(AZ::Crc32(AzToolsFramework::AssetEditor::AssetEditorWindowSettings::s_name), AZ::UserSettings::CT_GLOBAL))
{
assetsWindowsToRestore->m_openAssets.emplace(assetRef);
}
AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequestBus::Events::Save);
auto&& loadedAsset = AZ::Data::AssetManager::Instance().GetAsset(assetRef.GetId(), assetRef.GetType(), assetRef.GetAutoLoadBehavior());
loadedAsset.BlockUntilLoadComplete();
assetEditorWindow->OpenAsset(loadedAsset);
return assetEditorWindow;
}
}
AssetEditorWindow::AssetEditorWindow(QWidget* parent)
: QWidget(parent)
, m_ui(new Ui::AssetEditorWindowClass())
{
using namespace AzToolsFramework::AssetEditor;
m_ui->setupUi(this);
connect(m_ui->m_assetEditorWidget, &AssetEditorWidget::OnAssetSaveFailedSignal, this, &AssetEditorWindow::OnAssetSaveFailed);
connect(m_ui->m_assetEditorWidget, &AssetEditorWidget::OnAssetOpenedSignal, this, &AssetEditorWindow::OnAssetOpened);
BusConnect();
}
AssetEditorWindow::~AssetEditorWindow()
{
using namespace AzToolsFramework::AssetEditor;
BusDisconnect();
}
void AssetEditorWindow::RegisterViewClass(const AZ::Data::Asset<AZ::Data::AssetData>& asset)
{
AzToolsFramework::ViewPaneOptions options;
options.showInMenu = false;
auto& assetName = asset.GetHint();
const char* paneName = assetName.c_str();
AzToolsFramework::RegisterViewPane<AssetEditorWindow>(paneName, LyViewPane::CategoryTools, options, [asset](QWidget*) {return AssetEditorUtils::CreateAssetEditorWithAsset(asset); });
}
void AssetEditorWindow::CreateAsset(const AZ::Data::AssetType& assetType)
{
m_ui->m_assetEditorWidget->CreateAsset(assetType);
}
void AssetEditorWindow::OpenAsset(const AZ::Data::Asset<AZ::Data::AssetData>& asset)
{
m_ui->m_assetEditorWidget->OpenAsset(asset);
}
void AssetEditorWindow::OpenAssetById(const AZ::Data::AssetId assetId)
{
AZ::Data::Asset<AZ::Data::AssetData> asset = AZ::Data::AssetManager::Instance().GetAsset<AZ::Data::AssetData>(assetId, AZ::Data::AssetLoadBehavior::NoLoad);
OpenAsset(asset);
}
void AssetEditorWindow::SaveAssetAs(const AZStd::string_view assetPath)
{
if (assetPath.empty())
{
AZ_Warning("Asset Editor", false, "Could not save asset to empty path.");
return;
}
auto absoluteAssetPath = AZ::IO::FixedMaxPath(AZ::Utils::GetEnginePath()) / assetPath;
if (!m_ui->m_assetEditorWidget->SaveAssetToPath(absoluteAssetPath.Native()))
{
AZ_Warning("Asset Editor", false, "File was not saved correctly via SaveAssetAs.");
}
}
void AssetEditorWindow::RegisterViewClass()
{
AzToolsFramework::ViewPaneOptions options;
options.preferedDockingArea = Qt::LeftDockWidgetArea;
options.showOnToolsToolbar = true;
options.toolbarIcon = ":/Menu/asset_editor.svg";
AzToolsFramework::RegisterViewPane<AssetEditorWindow>(LyViewPane::AssetEditor, LyViewPane::CategoryTools, options);
}
void AssetEditorWindow::OnAssetOpened(const AZ::Data::Asset<AZ::Data::AssetData>& asset)
{
if (asset)
{
AZStd::string assetPath;
AZStd::string assetName;
AZStd::string extension;
AZ::Data::AssetCatalogRequestBus::BroadcastResult(assetPath, &AZ::Data::AssetCatalogRequests::GetAssetPathById, asset.GetId());
AzFramework::StringFunc::Path::Split(assetPath.c_str(), nullptr, nullptr, &assetName, &extension);
// AZStd::string windowTitle = AZStd::string::format("Edit Asset: %s", (assetName + extension).c_str());
AZStd::string windowTitle = asset.GetHint();
qobject_cast<QWidget*>(parent())->setWindowTitle(tr(windowTitle.c_str()));
}
else
{
qobject_cast<QWidget*>(parent())->setWindowTitle(tr("Asset Editor"));
}
}
void AssetEditorWindow::closeEvent(QCloseEvent* event)
{
if (m_ui->m_assetEditorWidget->WaitingToSave())
{
// Don't need to ask to save, as a save is already queued.
m_ui->m_assetEditorWidget->SetCloseAfterSave();
event->ignore();
return;
}
if (m_ui->m_assetEditorWidget->TrySave([this]() { qobject_cast<QWidget*>(parent())->close(); }))
{
event->ignore();
}
}
void AssetEditorWindow::OnAssetSaveFailed(const AZStd::string& error)
{
QMessageBox::warning(this, tr("Unable to Save Asset"),
tr(error.c_str()), QMessageBox::Ok, QMessageBox::Ok);
}
#include <AssetEditor/moc_AssetEditorWindow.cpp>
@@ -0,0 +1,57 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/UserSettings/UserSettings.h>
#include <AzToolsFramework/AssetEditor/AssetEditorBus.h>
#include <QWidget>
#endif
namespace Ui
{
class AssetEditorWindowClass;
}
/**
* Window pane wrapper for the Asset Editor widget.
*/
class AssetEditorWindow
: public QWidget
, AzToolsFramework::AssetEditor::AssetEditorWidgetRequestsBus::Handler
{
Q_OBJECT
public:
AZ_CLASS_ALLOCATOR(AssetEditorWindow, AZ::SystemAllocator, 0);
explicit AssetEditorWindow(QWidget* parent = nullptr);
~AssetEditorWindow() override;
//////////////////////////////////////////////////////////////////////////
// AssetEditorWindow
//////////////////////////////////////////////////////////////////////////
void CreateAsset(const AZ::Data::AssetType& assetType) override;
void OpenAsset(const AZ::Data::Asset<AZ::Data::AssetData>& asset) override;
void OpenAssetById(const AZ::Data::AssetId assetId) override;
void SaveAssetAs(const AZStd::string_view assetPath) override;
static void RegisterViewClass();
static void RegisterViewClass(const AZ::Data::Asset<AZ::Data::AssetData>& asset);
protected Q_SLOTS:
void OnAssetSaveFailed(const AZStd::string& error);
void OnAssetOpened(const AZ::Data::Asset<AZ::Data::AssetData>& asset);
protected:
void closeEvent(QCloseEvent* event) override;
private:
QScopedPointer<Ui::AssetEditorWindowClass> m_ui;
};
@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AssetEditorWindowClass</class>
<widget class="QWidget" name="AssetEditorWindowClass">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>200</width>
<height>200</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>200</width>
<height>200</height>
</size>
</property>
<property name="windowTitle">
<string>Asset Editor</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="AzToolsFramework::AssetEditor::AssetEditorWidget" name="m_assetEditorWidget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>AzToolsFramework::AssetEditor::AssetEditorWidget</class>
<extends>QWidget</extends>
<header>AzToolsFramework/AssetEditor/AssetEditorWidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
@@ -0,0 +1,214 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "AssetImporterDragAndDropHandler.h"
// Qt
#include <QMimeData>
// AzToolsFramework
#include <AzToolsFramework/AssetBrowser/AssetBrowserEntry.h>
// AzQtComponents
#include <AzQtComponents/DragAndDrop/MainWindowDragAndDrop.h>
// Editor
#include "AssetImporter/AssetImporterManager/AssetImporterManager.h"
bool AssetImporterDragAndDropHandler::m_dragAccepted = false;
AssetImporterDragAndDropHandler::AssetImporterDragAndDropHandler(QObject* parent, AssetImporterManager* const assetImporterManager)
: QObject(parent)
, m_assetImporterManager(assetImporterManager)
{
AzQtComponents::DragAndDropEventsBus::Handler::BusConnect(AzQtComponents::DragAndDropContexts::EditorMainWindow);
// They are used to prevent opening the Asset Importer by dragging and dropping files and folders to the Main Window when it is already running
connect(m_assetImporterManager, &AssetImporterManager::StartAssetImporter, this, &AssetImporterDragAndDropHandler::OnStartAssetImporter);
connect(m_assetImporterManager, &AssetImporterManager::StopAssetImporter, this, &AssetImporterDragAndDropHandler::OnStopAssetImporter);
}
AssetImporterDragAndDropHandler::~AssetImporterDragAndDropHandler()
{
AzQtComponents::DragAndDropEventsBus::Handler::BusDisconnect(AzQtComponents::DragAndDropContexts::EditorMainWindow);
}
void AssetImporterDragAndDropHandler::DragEnter(QDragEnterEvent* event, AzQtComponents::DragAndDropContextBase& /*context*/)
{
if (!m_isAssetImporterRunning)
{
ProcessDragEnter(event);
}
}
void AssetImporterDragAndDropHandler::Drop(QDropEvent* event, AzQtComponents::DragAndDropContextBase& /*context*/)
{
if (!m_dragAccepted)
{
return;
}
QStringList fileList = GetFileList(event);
if (!fileList.isEmpty())
{
Q_EMIT OpenAssetImporterManager(fileList);
}
// reset
m_dragAccepted = false;
}
void AssetImporterDragAndDropHandler::ProcessDragEnter(QDragEnterEvent* event)
{
m_dragAccepted = false;
const QMimeData* mimeData = event->mimeData();
// if the event hasn't been accepted already and the mimeData hasUrls()
if (event->isAccepted() || !mimeData->hasUrls())
{
return;
}
// prevent users from dragging and dropping files from the Asset Browser
if (mimeData->hasFormat(AzToolsFramework::AssetBrowser::AssetBrowserEntry::GetMimeType()))
{
return;
}
QList<QUrl> urlList = mimeData->urls();
int urlListSize = urlList.size();
// runs through the file list first and checks for any "crate" files - if it finds ANY, return (and don't accept the event)
for (int i = 0; i < urlListSize; ++i)
{
QUrl currentUrl = urlList.at(i);
if (currentUrl.isLocalFile())
{
QString path = urlList.at(i).toLocalFile();
if (ContainCrateFiles(path))
{
return;
}
}
}
for (int i = 0; i < urlListSize; ++i)
{
// Get the local file path
QString path = urlList.at(i).toLocalFile();
QDir dir(path);
QString relativePath = dir.relativeFilePath(path);
QString absPath = dir.absolutePath();
// check if the files/folders are under the game root directory
QDir gameRoot(Path::GetEditingGameDataFolder().c_str());
QString gameRootAbsPath = gameRoot.absolutePath();
if (absPath.startsWith(gameRootAbsPath, Qt::CaseInsensitive))
{
return;
}
QDirIterator it(absPath, QDir::NoDotAndDotDot | QDir::Files, QDirIterator::Subdirectories);
QFileInfo info(absPath);
QString extension = info.completeSuffix();
// if it's not an empty folder directory or if it's a file,
// then allow the drag and drop process.
// Otherwise, prevent users from dragging and dropping empty folders
if (it.hasNext() || !extension.isEmpty())
{
// this is used in Drop()
m_dragAccepted = true;
}
}
// at this point, all files should be legal to be imported
// since they are not in the database
if (m_dragAccepted)
{
event->acceptProposedAction();
}
}
QStringList AssetImporterDragAndDropHandler::GetFileList(QDropEvent* event)
{
QStringList fileList;
const QMimeData* mimeData = event->mimeData();
QList<QUrl> urlList = mimeData->urls();
for (int i = 0; i < urlList.size(); ++i)
{
QUrl currentUrl = urlList.at(i);
if (currentUrl.isLocalFile())
{
QString path = urlList.at(i).toLocalFile();
if (!ContainCrateFiles(path))
{
fileList.append(path);
}
}
}
return fileList;
}
void AssetImporterDragAndDropHandler::OnStartAssetImporter()
{
m_isAssetImporterRunning = true;
}
void AssetImporterDragAndDropHandler::OnStopAssetImporter()
{
m_isAssetImporterRunning = false;
}
bool AssetImporterDragAndDropHandler::ContainCrateFiles(QString path)
{
QFileInfo fileInfo(path);
if (fileInfo.isFile())
{
return isCrateFile(fileInfo);
}
else
{
QDirIterator it(path, QDir::NoDotAndDotDot | QDir::Files, QDirIterator::Subdirectories);
while (it.hasNext())
{
QString str = it.next();
QFileInfo info(str);
if (isCrateFile(info))
{
return true;
}
}
}
return false;
}
bool AssetImporterDragAndDropHandler::isCrateFile(QFileInfo fileInfo)
{
return QStringLiteral("crate").compare(fileInfo.suffix(), Qt::CaseInsensitive) == 0;
}
#include <AssetImporter/AssetImporterManager/moc_AssetImporterDragAndDropHandler.cpp>
@@ -0,0 +1,68 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <QObject>
#include <QFileInfo>
#include <QString>
#include <AzCore/EBus/EBus.h>
#include <AzQtComponents/Buses/DragAndDrop.h>
#include <AzToolsFramework/API/AssetDatabaseBus.h>
#include <AzCore/std/smart_ptr/scoped_ptr.h>
#endif
class MainWindow;
class AssetImporterManager;
namespace AzToolsFramework
{
namespace AssetDatabase
{
class AssetDatabaseConnection;
}
}
class AssetImporterDragAndDropHandler
: public QObject
, public AzQtComponents::DragAndDropEventsBus::Handler
{
Q_OBJECT
public:
explicit AssetImporterDragAndDropHandler(QObject* parent, AssetImporterManager* const assetImporterManager);
~AssetImporterDragAndDropHandler();
void DragEnter(QDragEnterEvent* event, AzQtComponents::DragAndDropContextBase& context) override;
void Drop(QDropEvent* event, AzQtComponents::DragAndDropContextBase& context) override;
static void ProcessDragEnter(QDragEnterEvent* event);
static QStringList GetFileList(QDropEvent* event);
Q_SIGNALS:
void OpenAssetImporterManager(const QStringList& fileList);
public Q_SLOTS:
void OnStartAssetImporter();
void OnStopAssetImporter();
private:
bool m_isAssetImporterRunning = false;
AssetImporterManager* m_assetImporterManager;
static bool ContainCrateFiles(QString path);
static bool isCrateFile(QFileInfo fileInfo);
// it is used because MainWindow's dropEvent will ask the Ebus to call the Drop() function in AssetImporterDragAndDropHandler.
// That will cause the problem that even the crate objects are blocked by the DragEnter() in AssetImporterDragAndDropHandler,
// it will still open the Asset Importer
static bool m_dragAccepted;
};
@@ -0,0 +1,811 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "AssetImporterManager.h"
// Qt
#include <QFileDialog>
#include <QMessageBox>
#include <QSettings>
#include <QStandardPaths>
// AzFramework
#include <AzFramework/Asset/AssetSystemBus.h>
// AzToolsFramework
#include <AzToolsFramework/UI/UICore/WidgetHelpers.h>
// Editor
#include "AssetImporter/UI/FilesAlreadyExistDialog.h"
#include "AssetImporter/UI/ProcessingAssetsDialog.h"
namespace AssetImporterManagerPrivate
{
const char* g_selectFilesPath = "AssetImporter/SelectFilesPath";
const char* g_selectDestinationFilesPath = "AssetImporter/SelectDestinationFilesPath";
const char* g_errorMessageBoxTitle = "File failed to process.";
const char* g_crateError = "Crate files cannot be imported.";
static const char* s_crateFileExtension = "crate";
};
AssetImporterManager::AssetImporterManager(QWidget* parent)
: QObject(parent)
{
}
AssetImporterManager::~AssetImporterManager()
{
}
void AssetImporterManager::Exec()
{
// tell the AssetImporterDragAndDropHandler that the Asset Importer now is running
Q_EMIT StartAssetImporter();
bool success = OnBrowseFiles();
// prevent users from selecting crate files from the File Explorer and open the Asset Importer.
if (!success)
{
reject();
}
else
{
OnOpenSelectDestinationDialog();
}
}
void AssetImporterManager::Exec(const QStringList& dragAndDropFileList)
{
// note: dragging and dropping an empty folder can also trigger this condition
if (!dragAndDropFileList.isEmpty())
{
OnDragAndDropFiles(&dragAndDropFileList);
// only open the Asset Importer when the folder contains correct type files
if (!m_pathMap.isEmpty())
{
// tell the AssetImporterDragAndDropHandler that the Asset Importer now is running
Q_EMIT StartAssetImporter();
OnOpenSelectDestinationDialog();
}
else
{
reject();
}
}
}
// used to cancel actions and close the dialog
void AssetImporterManager::reject()
{
m_pathMap.clear();
m_destinationRootDirectory = "";
Q_EMIT StopAssetImporter();
}
void AssetImporterManager::OnDragAndDropFiles(const QStringList* fileList)
{
for (int i = 0; i < fileList->size(); ++i)
{
// if the list contains a crate file,
// the whole process should stop
if (!GetAndCheckAllFilesInFolder(fileList->at(i)))
{
QMessageBox::warning(AzToolsFramework::GetActiveWindow(), AssetImporterManagerPrivate::g_errorMessageBoxTitle, AssetImporterManagerPrivate::g_crateError);
reject();
return;
}
}
}
bool AssetImporterManager::OnBrowseFiles()
{
QFileDialog fileDialog;
fileDialog.setFileMode(QFileDialog::ExistingFiles);
fileDialog.setWindowModality(Qt::WindowModality::ApplicationModal);
fileDialog.setViewMode(QFileDialog::Detail);
fileDialog.setWindowTitle(tr("Select files to import"));
fileDialog.setLabelText(QFileDialog::Accept, "Select");
QSettings settings;
QString currentAbsolutePath = settings.value(AssetImporterManagerPrivate::g_selectFilesPath).toString();
QDir gameRoot(Path::GetEditingGameDataFolder().c_str());
QString gameRootAbsPath = gameRoot.absolutePath();
// Case 1: if currentAbsolutePath is empty at this point, that means this is the first time
// users using the Asset Importer, set the default directory to be users' PC's desktop.
// Case 2: if the current folder directory stored in the registry doesn't exist anymore,
// that means users have removed the directory already (deleted or use the Move feature).
// Case 3: if it's a directory under the game root folder, then in general,
// users have modified the folder directory in the registry. It should not be happening.
if (currentAbsolutePath.isEmpty() || !QFile(currentAbsolutePath).exists() || currentAbsolutePath.startsWith(gameRootAbsPath, Qt::CaseInsensitive))
{
currentAbsolutePath = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
}
fileDialog.setDirectory(currentAbsolutePath);
if (!fileDialog.exec())
{
return false;
}
bool encounteredCrate = false;
QStringList invalidFiles;
for (QString path : fileDialog.selectedFiles())
{
QString fileName = GetFileName(path);
QFileInfo info(path);
QString extension = info.completeSuffix(); // extension without '.'
if (QString(AssetImporterManagerPrivate::s_crateFileExtension).compare(extension, Qt::CaseInsensitive) != 0)
{
// prevent users from importing files under the game root directory
if (path.startsWith(gameRootAbsPath, Qt::CaseInsensitive))
{
invalidFiles << fileName;
}
else
{
// store paths into the map.
m_pathMap[path] = fileName;
}
}
else
{
encounteredCrate = true;
}
}
if (invalidFiles.size() > 0)
{
QString fileWarning = QString("Files cannot be imported into their own project. The following files will not be moved or copied:\n");
fileWarning.append(invalidFiles.join(", "));
fileWarning.append('.');
QMessageBox::warning(AzToolsFramework::GetActiveWindow(), AssetImporterManagerPrivate::g_errorMessageBoxTitle, fileWarning);
}
if (encounteredCrate)
{
QMessageBox::warning(AzToolsFramework::GetActiveWindow(), AssetImporterManagerPrivate::g_errorMessageBoxTitle, AssetImporterManagerPrivate::g_crateError);
}
currentAbsolutePath = fileDialog.directory().absolutePath();
settings.setValue(AssetImporterManagerPrivate::g_selectFilesPath, currentAbsolutePath);
// prevent users from selecting crate files from the File Explorer and open the Asset Importer.
return (m_pathMap.size() > 0);
}
void AssetImporterManager::OnBrowseDestinationFilePath(QLineEdit* destinationLineEdit)
{
QFileDialog fileDialog;
fileDialog.setOption(QFileDialog::ShowDirsOnly, true);
fileDialog.setViewMode(QFileDialog::List);
fileDialog.setWindowModality(Qt::WindowModality::ApplicationModal);
fileDialog.setWindowTitle(tr("Select import destination"));
QSettings settings;
QString currentDestination = settings.value(AssetImporterManagerPrivate::g_selectDestinationFilesPath).toString();
QDir gameRoot(Path::GetEditingGameDataFolder().c_str());
QString gameRootAbsPath = gameRoot.absolutePath();
// Case 1: if currentDestination is empty at this point, that means this is the first time
// users using the Asset Importer, set the default directory to be the current game project's root folder
// Case 2: if the current folder directory stored in the registry doesn't exist anymore,
// that means users have removed the directory already (deleted or use the Move feature).
// Case 3: if it's a directory outside of the game root folder, then in general,
// users have modified the folder directory in the registry. It should not be happening.
if (currentDestination.isEmpty() || !QDir(currentDestination).exists() || !currentDestination.startsWith(gameRootAbsPath, Qt::CaseInsensitive))
{
currentDestination = gameRootAbsPath;
}
fileDialog.setDirectory(currentDestination);
// The default file path is the game project root folder.
// After that, the default file path will be the previous opened folder path.
connect(&fileDialog, &QFileDialog::directoryEntered, this, [&fileDialog, &gameRoot, &gameRootAbsPath](const QString& path)
{
// get current relative path
QString relativePath = gameRoot.relativeFilePath(path);
// Guard against navigating outside of the project folder. Lambda used as the dialog had to be captured.
// checking the directory and prevent users from changing the directory outside of the game root
if (!path.startsWith(gameRootAbsPath, Qt::CaseInsensitive) || (relativePath.length() > 2 && relativePath[0] == '.' && relativePath[1] == '.'))
{
fileDialog.setDirectory(gameRoot);
}
});
if (!fileDialog.exec())
{
return;
}
// users can only select one folder at a time, so the index is always 0.
// This fixes the issue that QFileDialog does not select the highlighted folder
QString destinationDirectory = fileDialog.selectedFiles().at(0);
OnSetDestinationDirectory(destinationDirectory);
destinationLineEdit->setText(destinationDirectory);
}
// Copy + Paste
void AssetImporterManager::OnCopyFiles()
{
m_importMethod = ImportFilesMethod::CopyFiles;
ProcessCopyFiles();
}
// Cut + Paste
void AssetImporterManager::OnMoveFiles()
{
m_importMethod = ImportFilesMethod::MoveFiles;
ProcessMoveFiles();
}
bool AssetImporterManager::OnOverwriteFiles(QString relativePath, QString oldAbsolutePath)
{
// this is the absolute path in the destination folder
QString destinationAbsolutePath = GenerateAbsolutePath(relativePath);
return Overwrite(relativePath, oldAbsolutePath, destinationAbsolutePath);
}
bool AssetImporterManager::OnKeepBothFiles(QString relativePath, QString oldAbsolutePath)
{
// this is the absolute path in the destination folder
QString destinationAbsolutePath = GenerateAbsolutePath(relativePath);
QString subPath = QFileInfo(destinationAbsolutePath).absoluteDir().absolutePath();
QFileInfo info(destinationAbsolutePath);
QString extension = info.completeSuffix(); // extension without '.'
QString fileName = info.baseName(); //file name without extension
int number = 1;
int index = destinationAbsolutePath.indexOf(extension);
QString newFileName = CreateFileNameWithNumber(number, fileName, index, extension);
QString newDestinationAbsolutePath = subPath + '/' + newFileName;
while (QFile(newDestinationAbsolutePath).exists())
{
number++;
newFileName = CreateFileNameWithNumber(number, fileName, index, extension);
newDestinationAbsolutePath = subPath + '/' + newFileName;
}
if (m_importMethod == ImportFilesMethod::CopyFiles)
{
return Copy(relativePath, oldAbsolutePath, newDestinationAbsolutePath);
}
else if (m_importMethod == ImportFilesMethod::MoveFiles)
{
return Move(relativePath, oldAbsolutePath, newDestinationAbsolutePath);
}
return false;
}
void AssetImporterManager::OnOpenLogDialog()
{
AzFramework::AssetSystemRequestBus::Broadcast(&AzFramework::AssetSystem::AssetSystemRequests::ShowAssetProcessor);
reject();
}
void AssetImporterManager::OnSetDestinationDirectory(QString destinationDirectory)
{
QSettings settings;
QString currentDestination = settings.value(AssetImporterManagerPrivate::g_selectDestinationFilesPath).toString();
m_destinationRootDirectory = (!destinationDirectory.isEmpty()) ? destinationDirectory : currentDestination;
settings.setValue(AssetImporterManagerPrivate::g_selectDestinationFilesPath, destinationDirectory);
}
void AssetImporterManager::OnOpenSelectDestinationDialog()
{
QWidget* mainWindow = nullptr;
AzToolsFramework::EditorRequestBus::BroadcastResult(mainWindow, &AzToolsFramework::EditorRequests::GetMainWindow);
QString numberOfFilesMessage = m_pathMap.size() == 1 ? QString(tr("Importing 1 asset")) : QString(tr("Importing %1 assets").arg(m_pathMap.size()));
SelectDestinationDialog selectDestinationDialog(numberOfFilesMessage, mainWindow);
// Browse Destination File Path
connect(&selectDestinationDialog, &SelectDestinationDialog::BrowseDestinationPath, this, &AssetImporterManager::OnBrowseDestinationFilePath);
connect(&selectDestinationDialog, &SelectDestinationDialog::DoCopyFiles, this, &AssetImporterManager::OnCopyFiles);
connect(&selectDestinationDialog, &SelectDestinationDialog::DoMoveFiles, this, &AssetImporterManager::OnMoveFiles);
connect(&selectDestinationDialog, &SelectDestinationDialog::Cancel, this, &AssetImporterManager::reject);
connect(&selectDestinationDialog, &SelectDestinationDialog::SetDestinationDirectory, this, &AssetImporterManager::OnSetDestinationDirectory);
selectDestinationDialog.exec();
}
ProcessFilesMethod AssetImporterManager::OnOpenFilesAlreadyExistDialog(QString message, int numberOfFiles)
{
ProcessFilesMethod processMethod = ProcessFilesMethod::Default;
// make sure the dialog is opened in front of the Editor main window
QWidget* mainWindow = nullptr;
AzToolsFramework::EditorRequestBus::BroadcastResult(mainWindow, &AzToolsFramework::EditorRequests::GetMainWindow);
FilesAlreadyExistDialog filesAlreadyExistDialog(message, numberOfFiles, mainWindow);
bool applyToAll = false;
connect(&filesAlreadyExistDialog, &FilesAlreadyExistDialog::ApplyActionToAllFiles, this, [&applyToAll](bool result)
{
applyToAll = result;
});
connect(&filesAlreadyExistDialog, &FilesAlreadyExistDialog::OverWriteFiles, this, [this, &processMethod, &applyToAll]()
{
processMethod = UpdateProcessFileMethod(ProcessFilesMethod::OverwriteFile, applyToAll);
});
connect(&filesAlreadyExistDialog, &FilesAlreadyExistDialog::KeepBothFiles, this, [this, &processMethod, &applyToAll]()
{
processMethod = UpdateProcessFileMethod(ProcessFilesMethod::KeepBothFile, applyToAll);
});
connect(&filesAlreadyExistDialog, &FilesAlreadyExistDialog::SkipCurrentProcess, this, [this, &processMethod, &applyToAll]()
{
processMethod = UpdateProcessFileMethod(ProcessFilesMethod::SkipProcessingFile, applyToAll);
});
connect(&filesAlreadyExistDialog, &FilesAlreadyExistDialog::CancelAllProcesses, this, [&processMethod]()
{
processMethod = ProcessFilesMethod::Cancel;
});
if (!applyToAll && processMethod != ProcessFilesMethod::Cancel)
{
filesAlreadyExistDialog.exec();
}
return processMethod;
}
ProcessFilesMethod AssetImporterManager::UpdateProcessFileMethod(ProcessFilesMethod processMethod, bool applyToAll)
{
if (applyToAll)
{
switch (processMethod)
{
case ProcessFilesMethod::OverwriteFile:
processMethod = ProcessFilesMethod::OverwriteAllFiles;
break;
case ProcessFilesMethod::KeepBothFile:
processMethod = ProcessFilesMethod::KeepBothAllFiles;
break;
case ProcessFilesMethod::SkipProcessingFile:
processMethod = ProcessFilesMethod::SkipProcessingAllFiles;
}
}
return processMethod;
}
bool AssetImporterManager::ProcessFileMethod(ProcessFilesMethod processMethod, QString relativePath, QString oldAbsolutePath)
{
switch (processMethod)
{
case ProcessFilesMethod::OverwriteFile:
case ProcessFilesMethod::OverwriteAllFiles:
return OnOverwriteFiles(relativePath, oldAbsolutePath);
case ProcessFilesMethod::KeepBothFile:
case ProcessFilesMethod::KeepBothAllFiles:
return OnKeepBothFiles(relativePath, oldAbsolutePath);
case ProcessFilesMethod::SkipProcessingAllFiles:
return false;
}
return false;
}
void AssetImporterManager::OnOpenProcessingAssetsDialog(int numberOfProcessedFiles)
{
// make sure the dialog is opened in front of the Editor main window
QWidget* mainWindow = nullptr;
AzToolsFramework::EditorRequestBus::BroadcastResult(mainWindow, &AzToolsFramework::EditorRequests::GetMainWindow);
ProcessingAssetsDialog processingAssetsDialog(numberOfProcessedFiles, mainWindow);
connect(&processingAssetsDialog, &ProcessingAssetsDialog::OpenLogDialog, this, &AssetImporterManager::OnOpenLogDialog);
connect(&processingAssetsDialog, &ProcessingAssetsDialog::CloseProcessingAssetsDialog, this, &AssetImporterManager::reject);
processingAssetsDialog.exec();
}
void AssetImporterManager::ProcessCopyFiles()
{
int numberOfFiles = m_pathMap.size();
int numberOfProcessedFiles = 0;
ProcessFilesMethod processMethod = ProcessFilesMethod::Default;
for (int i = 0; i < m_pathMap.size(); ++i)
{
QString relativePath = m_pathMap.values().at(i);
QString oldAbsolutePath = m_pathMap.keys().at(i);
// this is the absolute path in the destination folder
QString destinationAbsolutePath = GenerateAbsolutePath(relativePath);
// check if the file exists in the destination folder
if (!QFile::exists(destinationAbsolutePath))
{
if (Copy(relativePath, oldAbsolutePath, destinationAbsolutePath))
{
numberOfProcessedFiles++;
}
}
else
{
if (processMethod == ProcessFilesMethod::Default ||
processMethod == ProcessFilesMethod::OverwriteFile ||
processMethod == ProcessFilesMethod::KeepBothFile ||
processMethod == ProcessFilesMethod::SkipProcessingFile)
{
QString fileName = GetFileName(oldAbsolutePath);
QString message = QString("The destination already has a file named \"%1\". What would you like to do?").arg(fileName);
processMethod = OnOpenFilesAlreadyExistDialog(message, numberOfFiles);
}
if (ProcessFileMethod(processMethod, relativePath, oldAbsolutePath))
{
numberOfProcessedFiles++;
}
}
numberOfFiles--;
}
if (numberOfProcessedFiles > 0)
{
OnOpenProcessingAssetsDialog(numberOfProcessedFiles);
}
else
{
reject();
}
}
void AssetImporterManager::ProcessMoveFiles()
{
int numberOfFiles = m_pathMap.size();
int numberOfProcessedFiles = 0;
ProcessFilesMethod processMethod = ProcessFilesMethod::Default;
for (int i = 0; i < m_pathMap.size(); ++i)
{
QString relativePath = m_pathMap.values().at(i);
QString oldAbsolutePath = m_pathMap.keys().at(i);
// this is the absolute path in the destination folder
QString destinationAbsolutePath = GenerateAbsolutePath(relativePath);
// check if the file exists in the destination folder
if (!QFile::exists(destinationAbsolutePath))
{
if (Move(relativePath, oldAbsolutePath, destinationAbsolutePath))
{
numberOfProcessedFiles++;
}
}
else
{
if (processMethod == ProcessFilesMethod::Default ||
processMethod == ProcessFilesMethod::OverwriteFile ||
processMethod == ProcessFilesMethod::KeepBothFile ||
processMethod == ProcessFilesMethod::SkipProcessingFile)
{
QString fileName = GetFileName(oldAbsolutePath);
QString message = QString("The destination already has a file named \"%1\". What would you like to do?").arg(fileName);
processMethod = OnOpenFilesAlreadyExistDialog(message, numberOfFiles);
}
if (ProcessFileMethod(processMethod, relativePath, oldAbsolutePath))
{
numberOfProcessedFiles++;
}
}
numberOfFiles--;
}
if (numberOfProcessedFiles > 0)
{
OnOpenProcessingAssetsDialog(numberOfProcessedFiles);
}
else
{
reject();
}
}
bool AssetImporterManager::Copy(QString relativePath, QString oldAbsolutePath, QString destinationAbsolutePath)
{
QString fileName = GetFileName(destinationAbsolutePath);
QString subPath = QFileInfo(destinationAbsolutePath).absoluteDir().absolutePath();
QDir dir;
bool directoryExistedAlready = QDir(subPath).exists();
if (!directoryExistedAlready)
{
dir.mkpath(subPath);
}
QString newDestinationAbsolutePath = subPath;
newDestinationAbsolutePath = newDestinationAbsolutePath.append('/' + fileName);
// Copy the file from the old path to the new path
if (!QFile::copy(oldAbsolutePath, newDestinationAbsolutePath))
{
QString reason = tr("an unknown issue occurred.");
if (!directoryExistedAlready)
{
dir.rmdir(subPath);
}
// if the original files got deleted at this condition
if (!QFile(oldAbsolutePath).exists())
{
reason = tr("%1 no longer exists.").arg(fileName);
}
// if users manually copy the file into the destination folder at this condition
if (QFile(newDestinationAbsolutePath).exists())
{
reason = tr("%1 already exists in the target directory.").arg(fileName);
}
QMessageBox::critical(AzToolsFramework::GetActiveWindow(), AssetImporterManagerPrivate::g_errorMessageBoxTitle, QObject::tr("We're sorry, but the file failed to process because %1").arg(reason));
return false;
}
// set the destination file to be writable by user himself/herself if it's read-only
QFile destinationFile(newDestinationAbsolutePath);
SetDestinationFileWritable(destinationFile);
return true;
}
bool AssetImporterManager::Move(QString relativePath, QString oldAbsolutePath, QString destinationAbsolutePath)
{
QString fileName = GetFileName(destinationAbsolutePath);
QString subPath = QFileInfo(destinationAbsolutePath).absoluteDir().absolutePath();
QString newDestinationAbsolutePath = subPath;
QDir dir;
bool directoryExistedAlready = QDir(subPath).exists();
if (!directoryExistedAlready)
{
dir.mkpath(subPath);
}
if (QFile::rename(oldAbsolutePath, newDestinationAbsolutePath.append('/' + fileName)))
{
QString oldFileName = GetFileName(oldAbsolutePath);
// Only remove the old directory if the relative path is the file name itself.
// That also means users are dragging and dropping files, but not a folder containing those files.
if (oldFileName.compare(relativePath) != 0)
{
RemoveOldPath(oldAbsolutePath, relativePath);
}
// set the destination file to be writable by user himself/herself if it's read-only
QFile destinationFile(newDestinationAbsolutePath);
SetDestinationFileWritable(destinationFile);
return true;
}
else
{
QString reason = tr("an unknown issue occurred.");
QString oldFileName = GetFileName(oldAbsolutePath);
if (!directoryExistedAlready)
{
dir.rmdir(subPath);
}
// if the original files got deleted at this condition
if (!QFile(oldAbsolutePath).exists())
{
reason = tr("%1 no longer exists.").arg(oldFileName);
}
// if users manually copy the file into the destination folder at this condition
if (QFile(newDestinationAbsolutePath).exists())
{
reason = tr("%1 already exists in the target directory.").arg(oldFileName);
}
QMessageBox::critical(AzToolsFramework::GetActiveWindow(), AssetImporterManagerPrivate::g_errorMessageBoxTitle, QObject::tr("We're sorry, but the file failed to process because %1").arg(reason));
return false;
}
}
bool AssetImporterManager::Overwrite(QString relativePath, QString oldAbsolutePath, QString destinationAbsolutePath)
{
QFile newFile(destinationAbsolutePath);
QFile oldFile(oldAbsolutePath);
// double check if paths are valid
if ((!oldFile.open(QIODevice::ReadOnly)))
{
QString fileName = GetFileName(oldAbsolutePath);
QString reason = tr("%1 no longer exists.").arg(fileName);
QMessageBox::critical(AzToolsFramework::GetActiveWindow(), AssetImporterManagerPrivate::g_errorMessageBoxTitle, QObject::tr("We're sorry, but the file failed to process because %1").arg(reason));
return false;
}
if (!newFile.open(QIODevice::WriteOnly | QIODevice::Truncate))
{
QString reason = tr("We're sorry, but the file failed to process.");
QString fileName = GetFileName(destinationAbsolutePath);
if (!newFile.exists())
{
reason = tr("%1 from the destination directory is removed.").arg(fileName);
}
else
{
reason = tr("%1 from the destination directory cannot be overwritten.").arg(fileName);
}
QMessageBox::critical(AzToolsFramework::GetActiveWindow(), AssetImporterManagerPrivate::g_errorMessageBoxTitle, QObject::tr("We're sorry, but the file failed to process because %1").arg(reason));
return false;
}
QDataStream dataStream(&oldFile);
QDataStream out(&newFile);
int bufferSize = 1024 * 1024;
char* buffer = new char[bufferSize];
while (!dataStream.atEnd())
{
int bytesRead = dataStream.readRawData(buffer, bufferSize);
out.writeRawData(buffer, bytesRead);
}
delete[] buffer;
oldFile.close();
newFile.close();
// if it's the move file method, got to remove the original files
if (m_importMethod == ImportFilesMethod::MoveFiles)
{
QString fileName = GetFileName(oldAbsolutePath);
QFile file(oldAbsolutePath);
QDir absoluteDir = QFileInfo(oldAbsolutePath).absoluteDir();
if (file.exists())
{
// if the original file is read-only,
// then it got to be writable in order to be deleted successfully
SetDestinationFileWritable(file);
absoluteDir.remove(fileName);
}
// Only remove the old directory if the relative path is the file name itself.
// That also means users are dragging and dropping files, but not a folder containing those files.
if (fileName.compare(relativePath) != 0)
{
RemoveOldPath(oldAbsolutePath, relativePath);
}
}
return true;
}
bool AssetImporterManager::GetAndCheckAllFilesInFolder(QString path)
{
QString formattedPath = path;
// Paths ending with '/' return from QFileInfo().fileName() with a value of ""
// Strip a trailing slash so we can correctly get rootFolderName on all platforms
if (formattedPath.endsWith("/"))
{
formattedPath.truncate(formattedPath.lastIndexOf(QChar('/')));
}
QString rootFolderName = GetFileName(formattedPath);
QDirIterator it(formattedPath, QDir::NoDotAndDotDot | QDir::Files, QDirIterator::Subdirectories);
QFileInfo info(formattedPath);
if (!info.isDir() && !it.hasNext() && info.exists())
{
m_pathMap[formattedPath] = rootFolderName;
return true;
}
// Get the index of the last sub folder name in the path
QStringList directoryNameList = formattedPath.split('/');
int lastFolderIndex = directoryNameList.size() - 1;
QString pathToBeRelativeTo = directoryNameList.mid(0, lastFolderIndex).join('/');
while (it.hasNext())
{
QString absolutePath = it.next();
QFileInfo absoluteInfo(absolutePath);
QString extension = absoluteInfo.completeSuffix();
if (QString(AssetImporterManagerPrivate::s_crateFileExtension).compare(extension, Qt::CaseInsensitive) == 0)
{
return false;
}
Q_ASSERT(absolutePath.startsWith(pathToBeRelativeTo));
QString relativePath = absolutePath.mid(pathToBeRelativeTo.size() + 1);
m_pathMap[absolutePath] = relativePath;
}
return true;
}
void AssetImporterManager::RemoveOldPath(QString oldAbsolutePath, QString oldRelativePath)
{
QDir absoluteDir = QFileInfo(oldAbsolutePath).absoluteDir();
QStringList directoryList = oldRelativePath.split('/');
// remove each folder from the leave to the root, based on the relative path
for (int i = 0; i < directoryList.size(); ++i)
{
QString currentDir = absoluteDir.path();
absoluteDir.rmpath(currentDir);
absoluteDir.cdUp();
}
}
void AssetImporterManager::SetDestinationFileWritable(QFile& destinationFile)
{
if (destinationFile.open(QIODevice::ReadOnly))
{
destinationFile.setPermissions(QFile::WriteOwner | destinationFile.permissions());
}
destinationFile.close();
}
QString AssetImporterManager::CreateFileNameWithNumber(int number, QString fileName, int index, QString extension)
{
QString newFileName;
newFileName = fileName.isEmpty() ? fileName : fileName.left(index);
newFileName += "(" + QString::number(number) + ")";
if (extension.size() > 0)
{
newFileName += "." + extension;
}
return newFileName;
}
QString AssetImporterManager::GenerateAbsolutePath(QString relativePath)
{
return QDir(m_destinationRootDirectory).absoluteFilePath(relativePath);
}
QString AssetImporterManager::GetFileName(QString path)
{
return QFileInfo(path).fileName();
}
#include <AssetImporter/AssetImporterManager/moc_AssetImporterManager.cpp>
@@ -0,0 +1,94 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <QMainWindow>
#include <AssetImporter/UI/SelectDestinationDialog.h>
#endif
class QStringList;
class QFile;
enum class ImportFilesMethod
{
CopyFiles,
MoveFiles
};
enum class ProcessFilesMethod
{
OverwriteFile,
KeepBothFile,
SkipProcessingFile,
OverwriteAllFiles,
KeepBothAllFiles,
SkipProcessingAllFiles,
Cancel,
Default
};
class AssetImporterManager
: public QObject
{
Q_OBJECT
public:
explicit AssetImporterManager(QWidget* parent = nullptr);
~AssetImporterManager();
// Modal, but blocking.
void Exec(); // for browsing files
void Exec(const QStringList& dragAndDropFileList); // for drag and drop
Q_SIGNALS:
void StartAssetImporter();
void StopAssetImporter();
private Q_SLOTS:
void reject();
void OnDragAndDropFiles(const QStringList* fileList);
bool OnBrowseFiles();
void OnBrowseDestinationFilePath(QLineEdit* destinationLineEdit);
void OnCopyFiles();
void OnMoveFiles();
bool OnOverwriteFiles(QString relativePath, QString oldAbsolutePath);
bool OnKeepBothFiles(QString relativePath, QString oldAbsolutePath);
void OnOpenLogDialog();
void OnSetDestinationDirectory(QString destinationDirectory);
private:
void OnOpenSelectDestinationDialog();
ProcessFilesMethod OnOpenFilesAlreadyExistDialog(QString message, int numberOfFiles);
ProcessFilesMethod UpdateProcessFileMethod(ProcessFilesMethod processMethod, bool applyToAll);
bool ProcessFileMethod(ProcessFilesMethod processMethod, QString relativePath, QString oldAbsolutePath);
void OnOpenProcessingAssetsDialog(int numberOfProcessedFiles);
void ProcessCopyFiles();
void ProcessMoveFiles();
bool Copy(QString relativePath, QString oldAbsolutePath, QString destinationAbsolutePath);
bool Move(QString relativePath, QString oldAbsolutePath, QString destinationAbsolutePath);
bool Overwrite(QString relativePath, QString oldAbsolutePath, QString destinationAbsolutePath);
bool GetAndCheckAllFilesInFolder(QString path);
void RemoveOldPath(QString oldAbsolutePath, QString oldRelativePath);
void SetDestinationFileWritable(QFile& destinationFile);
QString CreateFileNameWithNumber(int number, QString fileName, int index, QString extension);
QString GenerateAbsolutePath(QString relativePath);
QString GetFileName(QString path);
ImportFilesMethod m_importMethod;
// Key = absolute path, Value = relative path
QMap<QString, QString> m_pathMap;
QString m_destinationRootDirectory;
};
@@ -0,0 +1,100 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "FilesAlreadyExistDialog.h"
// Qt
#include <QPushButton>
#include <QStyle>
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
#include <AssetImporter/UI/ui_FilesAlreadyExistDialog.h>
AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
FilesAlreadyExistDialog::FilesAlreadyExistDialog(QString message ,int numberOfFiles, QWidget* parent)
: QDialog(parent)
, m_ui(new Ui::FilesAlreadyExistDialog)
{
m_ui->setupUi(this);
UpdateMessage(message);
InitializeButtons();
UpdateCheckBoxState(numberOfFiles);
}
FilesAlreadyExistDialog::~FilesAlreadyExistDialog()
{
}
void FilesAlreadyExistDialog::InitializeButtons()
{
m_ui->buttonBox->setContentsMargins(0, 0, 16, 16);
QPushButton* overwriteButton = m_ui->buttonBox->addButton(tr("Overwrite"), QDialogButtonBox::AcceptRole);
QPushButton* keepBothButton = m_ui->buttonBox->addButton(tr("Keep Both"), QDialogButtonBox::AcceptRole);
QPushButton* skipButton = m_ui->buttonBox->addButton(tr("Skip"), QDialogButtonBox::AcceptRole);
overwriteButton->setProperty("class", "Primary");
overwriteButton->setDefault(true);
keepBothButton->setProperty("class", "AssetImporterLargerButton");
keepBothButton->style()->unpolish(keepBothButton);
keepBothButton->style()->polish(keepBothButton);
keepBothButton->update();
skipButton->setProperty("class", "AssetImporterButton");
skipButton->style()->unpolish(skipButton);
skipButton->style()->polish(skipButton);
skipButton->update();
connect(overwriteButton, &QPushButton::clicked, this, &FilesAlreadyExistDialog::DoOverwrite);
connect(keepBothButton, &QPushButton::clicked, this, &FilesAlreadyExistDialog::DoKeepBoth);
connect(skipButton, &QPushButton::clicked, this, &FilesAlreadyExistDialog::DoSkipCurrentProcess);
}
void FilesAlreadyExistDialog::UpdateMessage(QString message)
{
m_ui->message->setText(message);
}
void FilesAlreadyExistDialog::DoSkipCurrentProcess()
{
QDialog::accept();
Q_EMIT SkipCurrentProcess();
}
void FilesAlreadyExistDialog::DoOverwrite()
{
QDialog::accept();
Q_EMIT OverWriteFiles();
}
void FilesAlreadyExistDialog::DoKeepBoth()
{
QDialog::accept();
Q_EMIT KeepBothFiles();
}
void FilesAlreadyExistDialog::DoApplyActionToAllFiles()
{
Q_EMIT ApplyActionToAllFiles(m_ui->applyToAllCheckBox->isChecked());
}
void FilesAlreadyExistDialog::UpdateCheckBoxState(int numberOfFiles)
{
m_ui->applyToAllCheckBox->setVisible((numberOfFiles > 1));
connect(m_ui->applyToAllCheckBox, &QCheckBox::stateChanged, this, &FilesAlreadyExistDialog::DoApplyActionToAllFiles);
}
void FilesAlreadyExistDialog::closeEvent([[maybe_unused]] QCloseEvent* ev)
{
QDialog::reject();
Q_EMIT CancelAllProcesses();
}
#include <AssetImporter/UI/moc_FilesAlreadyExistDialog.cpp>
@@ -0,0 +1,47 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <QDialog>
#endif
class QTreeView;
namespace Ui {
class FilesAlreadyExistDialog;
}
class FilesAlreadyExistDialog
: public QDialog
{
Q_OBJECT
public:
FilesAlreadyExistDialog(QString message, int numberOfFiles, QWidget* parent = nullptr);
~FilesAlreadyExistDialog();
Q_SIGNALS:
void OverWriteFiles();
void KeepBothFiles();
void SkipCurrentProcess();
void CancelAllProcesses();
void ApplyActionToAllFiles(bool result);
public Q_SLOTS:
void DoSkipCurrentProcess();
void DoOverwrite();
void DoKeepBoth();
void DoApplyActionToAllFiles();
private:
void InitializeButtons();
void UpdateMessage(QString message);
void UpdateCheckBoxState(int numberOfFiles);
void closeEvent(QCloseEvent* ev) override;
QScopedPointer<Ui::FilesAlreadyExistDialog> m_ui;
};
@@ -0,0 +1,193 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FilesAlreadyExistDialog</class>
<widget class="QWidget" name="FilesAlreadyExistDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>587</width>
<height>159</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>571</width>
<height>0</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="windowTitle">
<string>Replace or Skip Files</string>
</property>
<property name="class" stdset="0">
<string>AssetImporterDialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</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>
<layout class="QVBoxLayout" name="mainVerticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>16</number>
</property>
<property name="topMargin">
<number>16</number>
</property>
<property name="rightMargin">
<number>16</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="message">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>10</pointsize>
</font>
</property>
<property name="text">
<string>The destination already has a file named &quot;&quot;. What would you like to do?</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="class" stdset="0">
<string>AssetImporterLabel</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Minimum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>16</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Minimum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>16</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="Line" name="line">
<property name="minimumSize">
<size>
<width>584</width>
<height>2</height>
</size>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Minimum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>16</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>16</number>
</property>
<item>
<widget class="QCheckBox" name="applyToAllCheckBox">
<property name="font">
<font>
<pointsize>12</pointsize>
</font>
</property>
<property name="text">
<string>Apply to all</string>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::NoButton</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
@@ -0,0 +1,79 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "ProcessingAssetsDialog.h"
// Qt
#include <QPushButton>
#include <QStyle>
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
#include <AssetImporter/UI/ui_ProcessingAssetsDialog.h>
AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
ProcessingAssetsDialog::ProcessingAssetsDialog(int numberOfProcessedFiles, QWidget* parent)
: QDialog(parent)
, m_ui(new Ui::ProcessingAssetsDialog)
{
m_ui->setupUi(this);
UpdateTextsAndTitle(numberOfProcessedFiles);
InitializeButtons();
}
ProcessingAssetsDialog::~ProcessingAssetsDialog()
{
}
void ProcessingAssetsDialog::InitializeButtons()
{
QPushButton* viewStatusButton = m_ui->buttonBox->addButton(tr("View status"), QDialogButtonBox::AcceptRole);
QPushButton* closeButton = m_ui->buttonBox->addButton(tr("Close"), QDialogButtonBox::RejectRole);
viewStatusButton->setDefault(true);
viewStatusButton->setProperty("class", "AssetImporterLargerButton");
viewStatusButton->style()->unpolish(viewStatusButton);
viewStatusButton->style()->polish(viewStatusButton);
viewStatusButton->update();
closeButton->setProperty("class", "AssetImporterButton");
closeButton->style()->unpolish(closeButton);
closeButton->style()->polish(closeButton);
closeButton->update();
connect(viewStatusButton, &QPushButton::clicked, this, &ProcessingAssetsDialog::accept);
connect(closeButton, &QPushButton::clicked, this, &ProcessingAssetsDialog::reject);
}
void ProcessingAssetsDialog::accept()
{
Q_EMIT OpenLogDialog();
QDialog::accept();
}
void ProcessingAssetsDialog::reject()
{
Q_EMIT CloseProcessingAssetsDialog();
QDialog::reject();
}
void ProcessingAssetsDialog::UpdateTextsAndTitle(int numberOfProcessedFiles)
{
if (numberOfProcessedFiles > 1)
{
setWindowTitle("Processing assets");
m_ui->label->setText("The Asset Processor will process your assets and when they are finished they will appear in the Asset Browser. You can view the status of your assets in the Asset Processor.");
}
else
{
setWindowTitle("Processing asset");
m_ui->label->setText("The Asset Processor will process your asset and when it is finished it will appear in the Asset Browser. You can view the status of your asset in the Asset Processor.");
}
}
#include <AssetImporter/UI/moc_ProcessingAssetsDialog.cpp>
@@ -0,0 +1,39 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <QDialog>
#endif
namespace Ui {
class ProcessingAssetsDialog;
}
class ProcessingAssetsDialog
: public QDialog
{
Q_OBJECT
public:
ProcessingAssetsDialog(int numberOfProcessedFiles, QWidget* parent = nullptr);
~ProcessingAssetsDialog();
void InitializeButtons();
Q_SIGNALS:
void CloseProcessingAssetsDialog();
void OpenLogDialog();
public Q_SLOTS:
void accept();
void reject();
private:
void UpdateTextsAndTitle(int numberOfProcessedFiles);
QScopedPointer<Ui::ProcessingAssetsDialog> m_ui;
};
@@ -0,0 +1,133 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ProcessingAssetsDialog</class>
<widget class="QWidget" name="ProcessingAssetsDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>540</width>
<height>187</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>440</width>
<height>187</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="windowTitle">
<string>Processing assets</string>
</property>
<property name="class" stdset="0">
<string>AssetImporterDialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
<property name="leftMargin">
<number>16</number>
</property>
<property name="topMargin">
<number>12</number>
</property>
<property name="rightMargin">
<number>16</number>
</property>
<property name="bottomMargin">
<number>12</number>
</property>
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>408</width>
<height>76</height>
</size>
</property>
<property name="font">
<font>
<pointsize>9</pointsize>
<kerning>false</kerning>
</font>
</property>
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="class" stdset="0">
<string>AssetImporterLabel</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>405</width>
<height>24</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>408</width>
<height>28</height>
</size>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::NoButton</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
@@ -0,0 +1,251 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "SelectDestinationDialog.h"
// Qt
#include <QValidator>
#include <QSettings>
#include <QStyle>
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
#include <AssetImporter/UI/ui_SelectDestinationDialog.h>
AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
static const char* g_assetProcessorLink = "<a href=\"https://o3de.org/docs/user-guide/assets/pipeline/processor-ui/\">Asset Processor</a>";
static const char* g_copyFilesMessage = "The original file will remain outside of the project and the %1 will not monitor the file.";
static const char* g_moveFilesMessage = "The original file will be moved inside of the project and the %1 will monitor the file for changes.";
static const char* g_selectDestinationFilesPath = "AssetImporter/SelectDestinationFilesPath";
static const char* g_toolTipInvalidRoot = "<p style='white-space:pre'> <span style=\"color:#cccccc;\">Invalid directory. Please choose a destination directory within your game project: %1.</span> </p>";
static const char* g_toolTipPathMustExist = "<p style='white-space:pre'> <span style=\"color:#cccccc;\">Invalid directory. Please choose a destination directory that exists.</span> </p>";
static const char* g_toolTipPathMustBeDirectory = "<p style='white-space:pre'> <span style=\"color:#cccccc;\">Invalid directory. Please choose a valid destination directory.</span> </p>";
static const char* g_toolTipInvalidLength = "<p style='white-space:pre'> <span style=\"color:#cccccc;\">Invalid directory name length. Please choose a destination path that has fewer than %1 characters.</span> </p>";
namespace
{
static QString GetAbsoluteRootDirectoryPath()
{
QDir gameRoot(Path::GetEditingGameDataFolder().c_str());
return gameRoot.absolutePath();
}
}
class DestinationDialogValidator
: public QValidator
{
public:
DestinationDialogValidator(QObject* parent)
: QValidator(parent)
, m_gameRootAbsolutePath(GetAbsoluteRootDirectoryPath())
{
}
State validate(QString& input, [[maybe_unused]] int& pos) const override
{
m_toolTip = "";
if (input.isEmpty())
{
return QValidator::Acceptable;
}
// The underlying file system code can't cope with long file paths, regardless of platform
if (input.length() > (AZ_MAX_PATH_LEN - 1))
{
m_toolTip = QString(g_toolTipInvalidLength).arg(AZ_MAX_PATH_LEN);
return QValidator::Intermediate;
}
QString normalizedInput = QDir::fromNativeSeparators(input);
// Note: the check for the root directory is case insensitive.
// We check if the directory actually exists after this, and at that point,
// if the file path has to be case sensitive, the directory won't exist anyways
// and QFileInfo::exists() will tell us so this should still work even on
// case sensitive file systems (such as Mac and Linux)
if (!normalizedInput.startsWith(m_gameRootAbsolutePath, Qt::CaseInsensitive))
{
m_toolTip = QString(g_toolTipInvalidRoot).arg(m_gameRootAbsolutePath);
return QValidator::Intermediate;
}
QFileInfo fileInfo(normalizedInput);
if (!fileInfo.exists())
{
m_toolTip = g_toolTipPathMustExist;
return QValidator::Intermediate;
}
if (!fileInfo.isDir())
{
m_toolTip = g_toolTipPathMustBeDirectory;
return QValidator::Intermediate;
}
return QValidator::Acceptable;
}
QString infoToolTip() const
{
return m_toolTip;
}
private:
QString m_gameRootAbsolutePath;
mutable QString m_toolTip;
};
SelectDestinationDialog::SelectDestinationDialog(QString message, QWidget* parent)
: QDialog(parent)
, m_ui(new Ui::SelectDestinationDialog)
, m_validator(new DestinationDialogValidator(this))
{
m_ui->setupUi(this);
QString radioButtonMessage = QString(g_copyFilesMessage).arg(g_assetProcessorLink);
m_ui->RaidoButtonMessage->setText(radioButtonMessage);
SetPreviousDestinationDirectory();
m_ui->DestinationLineEdit->setValidator(m_validator);
m_ui->DestinationLineEdit->setAlignment(Qt::AlignVCenter);
// Based on the current code structure, in order to prevent texts from overlapping the
// invalid icon, intentionally insert an empty icon at the end of the line edit field can do the trick.
m_ui->DestinationLineEdit->addAction(QIcon(""), QLineEdit::TrailingPosition);
connect(m_ui->DestinationLineEdit, &QLineEdit::textChanged, this, &SelectDestinationDialog::ValidatePath);
connect(m_ui->BrowseButton, &QPushButton::clicked, this, &SelectDestinationDialog::OnBrowseDestinationFilePath, Qt::UniqueConnection);
connect(m_ui->CopyFileRadioButton, &QRadioButton::toggled, this, &SelectDestinationDialog::ShowMessage);
UpdateMessage(message);
InitializeButtons();
}
SelectDestinationDialog::~SelectDestinationDialog()
{
}
void SelectDestinationDialog::InitializeButtons()
{
m_ui->CopyFileRadioButton->setChecked(true);
m_ui->buttonBox->setContentsMargins(0, 0, 16, 16);
QPushButton* importButton = m_ui->buttonBox->addButton(tr("Import"), QDialogButtonBox::AcceptRole);
QPushButton* cancelButton = m_ui->buttonBox->addButton(QDialogButtonBox::Cancel);
importButton->setProperty("class", "Primary");
importButton->setDefault(true);
cancelButton->setProperty("class", "AssetImporterButton");
cancelButton->style()->unpolish(cancelButton);
cancelButton->style()->polish(cancelButton);
cancelButton->update();
connect(importButton, &QPushButton::clicked, this, &SelectDestinationDialog::accept);
connect(cancelButton, &QPushButton::clicked, this, &SelectDestinationDialog::reject);
connect(this, &SelectDestinationDialog::UpdateImportButtonState, importButton, &QPushButton::setEnabled);
// To make sure the import button state is up to date
ValidatePath();
importButton->setAutoDefault(true);
}
void SelectDestinationDialog::SetPreviousDestinationDirectory()
{
QString gameRootAbsPath = GetAbsoluteRootDirectoryPath();
QSettings settings;
QString previousDestination = settings.value(g_selectDestinationFilesPath).toString();
// Case 1: if currentDestination is empty at this point, that means this is the first time
// users using the Asset Importer, set the default directory to be the current game project's root folder
// Case 2: if the current folder directory stored in the registry doesn't exist anymore,
// that means users have removed the directory already (deleted or use the Move feature).
// Case 3: if it's a directory outside of the game root folder, then in general,
// users have modified the folder directory in the registry. It should not be happening.
if (previousDestination.isEmpty() || !QDir(previousDestination).exists() || !previousDestination.startsWith(gameRootAbsPath, Qt::CaseInsensitive))
{
previousDestination = gameRootAbsPath;
}
m_ui->DestinationLineEdit->setText(QDir::toNativeSeparators(previousDestination));
}
void SelectDestinationDialog::accept()
{
QDialog::accept();
// This prevent users from not editing the destination line edit (manually type the directory or browse for the directory)
Q_EMIT SetDestinationDirectory(DestinationDirectory());
if (m_ui->CopyFileRadioButton->isChecked())
{
Q_EMIT DoCopyFiles();
}
else if (m_ui->MoveFileRadioButton->isChecked())
{
Q_EMIT DoMoveFiles();
}
}
void SelectDestinationDialog::reject()
{
Q_EMIT Cancel();
QDialog::reject();
}
void SelectDestinationDialog::ShowMessage()
{
QString message = m_ui->CopyFileRadioButton->isChecked() ? g_copyFilesMessage : g_moveFilesMessage;
m_ui->RaidoButtonMessage->setText(message.arg(g_assetProcessorLink));
}
void SelectDestinationDialog::OnBrowseDestinationFilePath()
{
Q_EMIT BrowseDestinationPath(m_ui->DestinationLineEdit);
}
void SelectDestinationDialog::UpdateMessage(QString message)
{
m_ui->NumberOfFilesMessage->setText(message);
}
void SelectDestinationDialog::ValidatePath()
{
if (!m_ui->DestinationLineEdit->hasAcceptableInput())
{
m_ui->DestinationLineEdit->setToolTip(m_validator->infoToolTip());
Q_EMIT UpdateImportButtonState(false);
}
else
{
QString destinationDirectory = DestinationDirectory();
int strLength = destinationDirectory.length();
// store the updated acceptable destination directory into the registry,
// so that when users manually modify the directory,
// the Asset Importer will remember it
Q_EMIT SetDestinationDirectory(destinationDirectory);
m_ui->DestinationLineEdit->setToolTip("");
Q_EMIT UpdateImportButtonState(strLength > 0);
}
}
QString SelectDestinationDialog::DestinationDirectory() const
{
return QDir::fromNativeSeparators(m_ui->DestinationLineEdit->text());
}
#include <AssetImporter/UI/moc_SelectDestinationDialog.cpp>
@@ -0,0 +1,58 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzQtComponents/Components/StyledLineEdit.h>
#include <QLabel>
#include <QDialog>
#include <QDialogButtonBox>
#endif
class StyledLineEdit;
class QValidator;
class DestinationDialogValidator;
namespace Ui {
class SelectDestinationDialog;
}
class SelectDestinationDialog
: public QDialog
{
Q_OBJECT
public:
SelectDestinationDialog(QString message, QWidget* parent = nullptr);
~SelectDestinationDialog();
Q_SIGNALS:
void GoBack();
void DoCopyFiles();
void DoMoveFiles();
void BrowseDestinationPath(QLineEdit* destinationLineEdit);
void Cancel();
void UpdateImportButtonState(bool enabled);
void SetDestinationDirectory(QString destinationDirectory);
public Q_SLOTS:
void accept();
void reject();
void ShowMessage();
void OnBrowseDestinationFilePath();
void ValidatePath();
private:
void UpdateMessage(QString message);
void InitializeButtons();
void SetPreviousDestinationDirectory();
QString DestinationDirectory() const;
QScopedPointer<Ui::SelectDestinationDialog> m_ui;
DestinationDialogValidator* m_validator;
};
@@ -0,0 +1,500 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SelectDestinationDialog</class>
<widget class="QWidget" name="SelectDestinationDialog">
<property name="windowModality">
<enum>Qt::NonModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>587</width>
<height>409</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>587</width>
<height>409</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="windowTitle">
<string>Import Asset(s)</string>
</property>
<property name="class" stdset="0">
<string>AssetImporterDialog</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>
<layout class="QVBoxLayout" name="mainVerticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>16</number>
</property>
<property name="topMargin">
<number>16</number>
</property>
<property name="rightMargin">
<number>16</number>
</property>
<item>
<widget class="QLabel" name="NumberOfFilesMessage">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>329</width>
<height>19</height>
</size>
</property>
<property name="font">
<font>
<pointsize>9</pointsize>
</font>
</property>
<property name="text">
<string>Importing 0 asset(s).</string>
</property>
<property name="class" stdset="0">
<string>AssetImporterLabel</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>16</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="DestinationFolderLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>9</pointsize>
</font>
</property>
<property name="text">
<string>Destination Folder</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="class" stdset="0">
<string>AssetImporterLabel</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>8</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="AzQtComponents::StyledLineEdit" name="DestinationLineEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>300</width>
<height>28</height>
</size>
</property>
<property name="font">
<font>
<pointsize>10</pointsize>
</font>
</property>
<property name="cursor">
<cursorShape>IBeamCursor</cursorShape>
</property>
<property name="frame">
<bool>true</bool>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="class" stdset="0">
<string>AssetImporterLineEdit</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>8</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="BrowseButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>76</width>
<height>28</height>
</size>
</property>
<property name="font">
<font>
<pointsize>8</pointsize>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<property name="text">
<string>Browse</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>24</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="QuestionMessage">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>250</width>
<height>19</height>
</size>
</property>
<property name="font">
<font>
<pointsize>9</pointsize>
</font>
</property>
<property name="text">
<string>How should we import these file(s)?</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="class" stdset="0">
<string>AssetImporterLabel</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>8</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="radioButtonHorizontalLayout">
<property name="spacing">
<number>16</number>
</property>
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
<item>
<widget class="QRadioButton" name="CopyFileRadioButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>9</pointsize>
</font>
</property>
<property name="text">
<string>Copy files</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="class" stdset="0">
<string>AssetImporterRadioButton</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="MoveFileRadioButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>9</pointsize>
</font>
</property>
<property name="text">
<string>Move files</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
<property name="class" stdset="0">
<string>AssetImporterRadioButton</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<spacer name="CopySpacer1">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>13</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="RaidoButtonMessage">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>500</width>
<height>72</height>
</size>
</property>
<property name="font">
<font>
<pointsize>9</pointsize>
</font>
</property>
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="class" stdset="0">
<string>AssetImporterLabel</string>
</property>
</widget>
</item>
<item>
<spacer name="MoveSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>584</width>
<height>2</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Minimum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>16</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::NoButton</set>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>AzQtComponents::StyledLineEdit</class>
<extends>QLineEdit</extends>
<header>AzQtComponents/Components/StyledLineEdit.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
@@ -0,0 +1,72 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "AzAssetBrowserWindow.h"
#include "AzAssetBrowser/ui_AssetBrowserWindow.h"
#include <AzToolsFramework/AssetBrowser/UI/AssetTreeView.h>
#include <AzToolsFramework/AssetBrowser/UI/SortFilterProxyModel.hxx>
#include <AzToolsFramework/AssetBrowser/UI/AssetBrowserModel.h>
#include <AzToolsFramework/AssetBrowser/AssetCache/AssetCacheBus.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserRequestBus.h>
const char* ASSET_BROWSER_PREVIEW_NAME = "Asset Browser (PREVIEW)";
AzAssetBrowserWindow::AzAssetBrowserWindow(const QString& name, QWidget* parent)
: QDialog(parent)
, m_ui(new Ui::AssetBrowserWindowClass())
, m_assetDatabaseSortFilterProxyModel(new AssetBrowser::UI::SortFilterProxyModel(parent))
, m_name(name)
, m_assetBrowser(new AssetBrowser::UI::AssetTreeView(name, this))
{
EBUS_EVENT_RESULT(m_assetBrowserModel, AssetBrowser::AssetCache::AssetCacheRequestsBus, GetAssetBrowserModel);
AZ_Assert(m_assetBrowserModel, "Failed to get filebrowser model");
m_assetDatabaseSortFilterProxyModel->setSourceModel(m_assetBrowserModel);
m_ui->setupUi(this);
connect(m_ui->searchCriteriaWidget,
&AzToolsFramework::SearchCriteriaWidget::SearchCriteriaChanged,
m_assetDatabaseSortFilterProxyModel.data(),
&AssetBrowser::UI::SortFilterProxyModel::OnSearchCriteriaChanged);
connect(m_assetBrowser, &QTreeView::customContextMenuRequested, this, &AzAssetBrowserWindow::OnContextMenu);
}
AzAssetBrowserWindow::~AzAssetBrowserWindow()
{
m_assetBrowser->SaveState();
}
//////////////////////////////////////////////////////////////////////////
const AZ::Uuid& AzAssetBrowserWindow::GetClassID()
{
return AZ::AzTypeInfo<AzAssetBrowserWindow>::Uuid();
}
void AzAssetBrowserWindow::OnContextMenu(const QPoint& point)
{
(void)point;
//get the selected entries
QModelIndexList sourceIndexes;
for (const auto& index : m_assetBrowser->selectedIndexes())
{
sourceIndexes.push_back(m_assetDatabaseSortFilterProxyModel->mapToSource(index));
}
AZStd::vector<AssetBrowser::UI::Entry*> entries;
m_assetBrowserModel->SourceIndexesToAssetDatabaseEntries(sourceIndexes, entries);
if (entries.empty() || entries.size() > 1)
{
return;
}
auto entry = entries.front();
EBUS_EVENT(AssetBrowser::AssetBrowserRequestBus::Bus, OnItemContextMenu, this, entry);
}
#include <AzAssetBrowser/moc_AzAssetBrowserWindow.cpp>
@@ -0,0 +1,56 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Math/Uuid.h>
#include <QDialog>
#endif
namespace Ui
{
class AssetBrowserWindowClass;
}
namespace AssetBrowser
{
namespace UI
{
class AssetTreeView;
class SortFilterProxyModel;
class AssetBrowserModel;
}
}
class AzAssetBrowserWindow
: public QDialog
{
Q_OBJECT
public:
AZ_CLASS_ALLOCATOR(AzAssetBrowserWindow, AZ::SystemAllocator, 0);
AZ_TYPE_INFO(AzAssetBrowserWindow, "{20238D23-2670-44BC-9110-A51374C18B5A}");
explicit AzAssetBrowserWindow(const QString& name = "default", QWidget* parent = nullptr);
virtual ~AzAssetBrowserWindow();
static const AZ::Uuid& GetClassID();
protected Q_SLOTS:
void OnContextMenu(const QPoint& point);
private:
QScopedPointer<Ui::AssetBrowserWindowClass> m_ui;
QScopedPointer<AssetBrowser::UI::AssetBrowserModel> m_assetDatabaseModel;
QScopedPointer<AssetBrowser::UI::SortFilterProxyModel> m_assetDatabaseSortFilterProxyModel;
QString m_name;
AssetBrowser::UI::AssetTreeView* m_assetBrowser;
AssetBrowser::UI::AssetBrowserModel* m_assetBrowserModel;
};
extern const char* ASSET_BROWSER_PREVIEW_NAME;
@@ -0,0 +1,718 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "AzAssetBrowserRequestHandler.h"
// Qt
#include <QMenu>
#include <QDesktopServices>
// AzCore
#include <AzCore/std/string/wildcard.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Asset/AssetTypeInfoBus.h>
// AzFramework
#include <AzFramework/API/ApplicationAPI.h>
#include <AzFramework/Asset/GenericAssetHandler.h>
// AzToolsFramework
#include <AzToolsFramework/AssetBrowser/AssetBrowserSourceDropBus.h>
#include <AzToolsFramework/AssetBrowser/Entries/ProductAssetBrowserEntry.h>
#include <AzToolsFramework/AssetBrowser/Entries/SourceAssetBrowserEntry.h>
#include <AzToolsFramework/AssetEditor/AssetEditorBus.h>
#include <AzToolsFramework/Commands/EntityStateCommand.h>
#include <AzToolsFramework/Entity/SliceEditorEntityOwnershipServiceBus.h>
#include <AzToolsFramework/Slice/SliceUtilities.h>
#include <AzToolsFramework/ToolsComponents/EditorComponentBase.h>
#include <AzToolsFramework/ToolsComponents/EditorLayerComponent.h>
#include <AzToolsFramework/ToolsComponents/GenericComponentWrapper.h>
#include <AzToolsFramework/ToolsComponents/TransformComponent.h>
#include <AzToolsFramework/UI/Slice/SliceRelationshipBus.h>
// AzQtComponents
#include <AzQtComponents/DragAndDrop/ViewportDragAndDrop.h>
// Editor
#include "IEditor.h"
#include "Include/IObjectManager.h"
#include "CryEditDoc.h"
#include "QtViewPaneManager.h"
namespace AzAssetBrowserRequestHandlerPrivate
{
using namespace AzToolsFramework;
using namespace AzToolsFramework::AssetBrowser;
// return true ONLY if we can handle the drop request in the viewport.
bool CanSpawnEntityForProduct(const ProductAssetBrowserEntry* product,
AZStd::optional<const AZStd::vector<AZ::Data::AssetType>> optionalProductAssetTypes = AZStd::nullopt)
{
if (!product)
{
return false;
}
if (product->GetAssetType() == AZ::AzTypeInfo<AZ::SliceAsset>::Uuid())
{
return true; // we can always spawn slices.
}
bool canCreateComponent = false;
AZ::AssetTypeInfoBus::EventResult(canCreateComponent, product->GetAssetType(), &AZ::AssetTypeInfo::CanCreateComponent, product->GetAssetId());
if (!canCreateComponent)
{
return false;
}
AZ::Uuid componentTypeId = AZ::Uuid::CreateNull();
AZ::AssetTypeInfoBus::EventResult(componentTypeId, product->GetAssetType(), &AZ::AssetTypeInfo::GetComponentTypeId);
if (componentTypeId.IsNull())
{
// we have a component type that handles this asset.
return false;
}
if (optionalProductAssetTypes.has_value())
{
bool hasConflictingProducts = false;
AZ::AssetTypeInfoBus::EventResult(hasConflictingProducts, product->GetAssetType(), &AZ::AssetTypeInfo::HasConflictingProducts, optionalProductAssetTypes.value());
if (hasConflictingProducts)
{
return false;
}
}
// additional operations can be added here.
return true;
}
void SpawnEntityAtPoint(const ProductAssetBrowserEntry* product, AzQtComponents::ViewportDragContext* viewportDragContext, EntityIdList& spawnList, AzFramework::SliceInstantiationTicket& spawnTicket)
{
// Calculate the drop location.
if ((!viewportDragContext) || (!product))
{
return;
}
const AZ::Transform worldTransform = AZ::Transform::CreateTranslation(viewportDragContext->m_hitLocation);
// Handle instantiation of slices.
if (product->GetAssetType() == AZ::AzTypeInfo<AZ::SliceAsset>::Uuid())
{
// Instantiate the slice at the specified location.
AZ::Data::Asset<AZ::SliceAsset> asset = AZ::Data::AssetManager::Instance().FindOrCreateAsset<AZ::SliceAsset>(product->GetAssetId(), AZ::Data::AssetLoadBehavior::Default);
if (asset)
{
SliceEditorEntityOwnershipServiceRequestBus::BroadcastResult(spawnTicket,
&SliceEditorEntityOwnershipServiceRequests::InstantiateEditorSlice, asset, worldTransform);
}
}
else
{
ScopedUndoBatch undo("Create entities from asset");
// Add the component(s).
AZ::Uuid componentTypeId = AZ::Uuid::CreateNull();
AZ::AssetTypeInfoBus::EventResult(componentTypeId, product->GetAssetType(), &AZ::AssetTypeInfo::GetComponentTypeId);
if (!componentTypeId.IsNull())
{
AZStd::string entityName;
// If the entity is being created from an asset, name it after said asset.
const AZ::Data::AssetId assetId = product->GetAssetId();
AZStd::string assetPath;
AZ::Data::AssetCatalogRequestBus::BroadcastResult(assetPath, &AZ::Data::AssetCatalogRequests::GetAssetPathById, assetId);
if (!assetPath.empty())
{
AzFramework::StringFunc::Path::GetFileName(assetPath.c_str(), entityName);
}
// If not sourced from an asset, generate a generic name.
if (entityName.empty())
{
entityName = AZStd::string::format("Entity%d", GetIEditor()->GetObjectManager()->GetObjectCount());
}
AZ::EntityId targetEntityId;
EditorRequests::Bus::BroadcastResult(targetEntityId, &EditorRequests::CreateNewEntityAtPosition, worldTransform.GetTranslation(), AZ::EntityId());
AZ::Entity* newEntity = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(newEntity, &AZ::ComponentApplicationRequests::FindEntity, targetEntityId);
if (newEntity == nullptr)
{
return;
}
newEntity->SetName(entityName);
newEntity->Deactivate();
// Create component.
AZ::Component* newComponent = newEntity->CreateComponent(componentTypeId);
// If it's not an "editor component" then wrap it in a GenericComponentWrapper.
bool needsGenericWrapper = azrtti_cast<AzToolsFramework::Components::EditorComponentBase*>(newComponent) == nullptr;
if (needsGenericWrapper)
{
newEntity->RemoveComponent(newComponent);
newComponent = aznew AzToolsFramework::Components::GenericComponentWrapper(newComponent);
newEntity->AddComponent(newComponent);
}
newEntity->Activate();
// set asset after components have been activated in AddEditorEntity method
if (newComponent)
{
Components::EditorComponentBase* asEditorComponent =
azrtti_cast<Components::EditorComponentBase*>(newComponent);
if (asEditorComponent)
{
asEditorComponent->SetPrimaryAsset(assetId);
}
}
bool isPrefabSystemEnabled = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
isPrefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
if (!isPrefabSystemEnabled)
{
// Prepare undo command last so it captures the final state of the entity.
EntityCreateCommand* command = aznew EntityCreateCommand(static_cast<AZ::u64>(newEntity->GetId()));
command->Capture(newEntity);
command->SetParent(undo.GetUndoBatch());
}
ToolsApplicationRequests::Bus::Broadcast(&ToolsApplicationRequests::AddDirtyEntity, newEntity->GetId());
spawnList.push_back(newEntity->GetId());
}
}
}
// Helper utility - determines if the thing being dragged is a FBX from the scene import pipeline
// This is important to differentiate.
// when someone drags a MTL file directly into the viewport, even from a FBX, we want to spawn it as a decal
// but when someone drags a FBX that contains MTL files, we want only to spawn the meshes.
// so we have to specifically differentiate here between the mimeData type that contains the source as the root
// (dragging the fbx file itself)
// and one which contains the actual product at its root.
bool IsDragOfFBX(const QMimeData* mimeData)
{
AZStd::vector<AssetBrowserEntry*> entries;
if (!AssetBrowserEntry::FromMimeData(mimeData, entries))
{
// if mimedata does not even contain entries, no point in proceeding.
return false;
}
for (auto entry : entries)
{
if (entry->GetEntryType() != AssetBrowserEntry::AssetEntryType::Source)
{
continue;
}
// this is a source file. Is it the filetype we're looking for?
if (SourceAssetBrowserEntry* source = azrtti_cast<SourceAssetBrowserEntry*>(entry))
{
if (AzFramework::StringFunc::Equal(source->GetExtension().c_str(), ".fbx", false))
{
return true;
}
}
}
return false;
}
}
AzAssetBrowserRequestHandler::AzAssetBrowserRequestHandler()
{
using namespace AzToolsFramework::AssetBrowser;
AssetBrowserInteractionNotificationBus::Handler::BusConnect();
AzQtComponents::DragAndDropEventsBus::Handler::BusConnect(AzQtComponents::DragAndDropContexts::EditorViewport);
}
AzAssetBrowserRequestHandler::~AzAssetBrowserRequestHandler()
{
AzToolsFramework::AssetBrowser::AssetBrowserInteractionNotificationBus::Handler::BusDisconnect();
AzQtComponents::DragAndDropEventsBus::Handler::BusDisconnect();
}
void AzAssetBrowserRequestHandler::AddContextMenuActions(QWidget* caller, QMenu* menu, const AZStd::vector<AzToolsFramework::AssetBrowser::AssetBrowserEntry*>& entries)
{
using namespace AzToolsFramework::AssetBrowser;
AssetBrowserEntry* entry = entries.empty() ? nullptr : entries.front();
if (!entry)
{
return;
}
AZStd::string fullFileDirectory;
AZStd::string fullFilePath;
AZStd::string fileName;
AZStd::string extension;
switch (entry->GetEntryType())
{
case AssetBrowserEntry::AssetEntryType::Product:
// if its a product, we actually want to perform these operations on the source
// which will be the parent of the product.
entry = entry->GetParent();
if ((!entry) || (entry->GetEntryType() != AssetBrowserEntry::AssetEntryType::Source))
{
AZ_Assert(false, "Asset Browser entry product has a non-source parent?");
break; // no valid parent.
}
// the fall through to the next case is intentional here.
case AssetBrowserEntry::AssetEntryType::Source:
{
AZ::Uuid sourceID = azrtti_cast<SourceAssetBrowserEntry*>(entry)->GetSourceUuid();
fullFilePath = entry->GetFullPath();
fullFileDirectory = fullFilePath.substr(0, fullFilePath.find_last_of(AZ_CORRECT_DATABASE_SEPARATOR));
fileName = entry->GetName();
AzFramework::StringFunc::Path::GetExtension(fullFilePath.c_str(), extension);
// Add the "Open" menu item.
// Note that source file openers are allowed to "veto" the showing of the "Open" menu if it is 100% known that they aren't openable!
// for example, custom data formats that are made by Open 3D Engine that can not have a program associated in the operating system to view them.
// If the only opener that can open that file has no m_opener, then it is not openable.
SourceFileOpenerList openers;
AssetBrowserInteractionNotificationBus::Broadcast(&AssetBrowserInteractionNotificationBus::Events::AddSourceFileOpeners, fullFilePath.c_str(), sourceID, openers);
bool validOpenersFound = false;
bool vetoOpenerFound = false;
for (const SourceFileOpenerDetails& openerDetails : openers)
{
if (openerDetails.m_opener)
{
// we found a valid opener (non-null). This means that the system is saying that it knows how to internally
// edit this source file and has a custom editor for it.
validOpenersFound = true;
}
else
{
// if we get here it means someone intentionally registered a callback with a null function pointer
// the API treats this as a 'veto' opener - meaning that the system wants us NOT to allow the operating system
// to open this source file as a default fallback.
vetoOpenerFound = true;
}
}
if (validOpenersFound)
{
// if we get here then there is an opener installed for this kind of asset
// and it is not null, meaning that it is not vetoing our ability to open the file.
for (const SourceFileOpenerDetails& openerDetails : openers)
{
// bind that function to the current loop element.
if (openerDetails.m_opener) // only VALID openers with an actual callback.
{
menu->addAction(openerDetails.m_iconToUse, QObject::tr(openerDetails.m_displayText.c_str()), [sourceID, fullFilePath, openerDetails]()
{
openerDetails.m_opener(fullFilePath.c_str(), sourceID);
});
}
}
}
// we always add the default "open with your operating system" unless a veto opener is found
if (!vetoOpenerFound)
{
// if we found no valid openers and no veto openers then just allow it to be opened with the operating system itself.
menu->addAction(QObject::tr("Open with associated application..."), [this, fullFilePath]()
{
OpenWithOS(fullFilePath);
});
}
AZStd::vector<const ProductAssetBrowserEntry*> products;
entry->GetChildrenRecursively<ProductAssetBrowserEntry>(products);
// slice source files need to react by adding additional menu items, regardless of status of compile or presence of products.
if (AzFramework::StringFunc::Equal(extension.c_str(), AzToolsFramework::SliceUtilities::GetSliceFileExtension().c_str(), false))
{
AzToolsFramework::SliceUtilities::CreateSliceAssetContextMenu(menu, fullFilePath);
// SliceUtilities is in AZToolsFramework and can't open viewports, so add the relationship view open command here.
if (!products.empty())
{
const ProductAssetBrowserEntry* productEntry = products[0];
menu->addAction("Open in Slice Relationship View", [productEntry]()
{
QtViewPaneManager::instance()->OpenPane(LyViewPane::SliceRelationships);
const ProductAssetBrowserEntry* product = azrtti_cast<const ProductAssetBrowserEntry*>(productEntry);
AzToolsFramework::SliceRelationshipRequestBus::Broadcast(&AzToolsFramework::SliceRelationshipRequests::OnSliceRelationshipViewRequested, product->GetAssetId());
});
}
}
else if (AzFramework::StringFunc::Equal(extension.c_str(), AzToolsFramework::Layers::EditorLayerComponent::GetLayerExtensionWithDot().c_str(), false))
{
QString levelPath = Path::GetPath(GetIEditor()->GetDocument()->GetActivePathName());
AzToolsFramework::Layers::EditorLayerComponent::CreateLayerAssetContextMenu(menu, fullFilePath, levelPath);
}
if (products.empty())
{
if (entry->GetEntryType() == AssetBrowserEntry::AssetEntryType::Source)
{
CFileUtil::PopulateQMenu(caller, menu, fileName.c_str(), fullFileDirectory.c_str());
}
return;
}
CFileUtil::PopulateQMenu(caller, menu, fileName.c_str(), fullFileDirectory.c_str());
}
break;
case AssetBrowserEntry::AssetEntryType::Folder:
{
fullFileDirectory = entry->GetFullPath();
// we are sending an empty filename to indicate that it is a folder and not a file
CFileUtil::PopulateQMenu(caller, menu, fileName.c_str(), fullFileDirectory.c_str());
}
break;
default:
break;
}
}
bool AzAssetBrowserRequestHandler::CanAcceptDragAndDropEvent(
QDropEvent* event,
AzQtComponents::DragAndDropContextBase& context,
AZStd::optional<AZStd::vector<const AzToolsFramework::AssetBrowser::SourceAssetBrowserEntry*>*> outSources,
AZStd::optional<AZStd::vector<const AzToolsFramework::AssetBrowser::ProductAssetBrowserEntry*>*> outProducts
) const
{
using namespace AzQtComponents;
using namespace AzToolsFramework;
using namespace AzToolsFramework::AssetBrowser;
using namespace AzAssetBrowserRequestHandlerPrivate;
// if a listener with a higher priority already claimed this event, do not touch it.
ViewportDragContext* viewportDragContext = azrtti_cast<ViewportDragContext*>(&context);
if ((!event) || (!event->mimeData()) || (event->isAccepted()) || (!viewportDragContext))
{
return false;
}
bool canAcceptEvent = false;
// Detects Source Asset Entries whose extensions are handled by a system
AzToolsFramework::AssetBrowser::AssetBrowserEntry::ForEachEntryInMimeData<SourceAssetBrowserEntry>(
event->mimeData(), [&](const AzToolsFramework::AssetBrowser::SourceAssetBrowserEntry* source) {
if (AssetBrowser::AssetBrowserSourceDropBus::HasHandlers(source->GetExtension()))
{
if (outSources.has_value())
{
outSources.value()->push_back(source);
}
canAcceptEvent = true;
}
});
// Detects Product Assets that are dragged directly, or child Products of other entry types.
AzToolsFramework::AssetBrowser::AssetBrowserEntry::ForEachEntryInMimeData<ProductAssetBrowserEntry>(
event->mimeData(), [&](const AzToolsFramework::AssetBrowser::ProductAssetBrowserEntry* product)
{
// Skip if this product is a child of a source file that is handled
if (outSources.has_value() && !outSources.value()->empty())
{
auto parent = azrtti_cast<const SourceAssetBrowserEntry*>(product->GetParent());
if (parent != nullptr && AZStd::find(outSources.value()->begin(), outSources.value()->end(), parent) != outSources.value()->end())
{
return;
}
}
if (CanSpawnEntityForProduct(product))
{
if (outProducts.has_value())
{
outProducts.value()->push_back(product);
}
canAcceptEvent = true;
}
});
return canAcceptEvent;
}
void AzAssetBrowserRequestHandler::DragEnter(QDragEnterEvent* event, AzQtComponents::DragAndDropContextBase& context)
{
if (CanAcceptDragAndDropEvent(event, context))
{
event->setDropAction(Qt::CopyAction);
event->setAccepted(true);
}
}
void AzAssetBrowserRequestHandler::DragMove(QDragMoveEvent* event, AzQtComponents::DragAndDropContextBase& context)
{
if (CanAcceptDragAndDropEvent(event, context))
{
event->setDropAction(Qt::CopyAction);
event->setAccepted(true);
}
}
void AzAssetBrowserRequestHandler::DragLeave(QDragLeaveEvent* /*event*/)
{
// opportunities to show ghosted entities or previews here.
}
void AzAssetBrowserRequestHandler::Drop(QDropEvent* event, AzQtComponents::DragAndDropContextBase& context)
{
using namespace AzToolsFramework;
using namespace AzToolsFramework::AssetBrowser;
using namespace AzQtComponents;
using namespace AzAssetBrowserRequestHandlerPrivate;
AZStd::vector<const SourceAssetBrowserEntry*> sources;
AZStd::vector<const ProductAssetBrowserEntry*> products;
if (!CanAcceptDragAndDropEvent(event, context, &sources, &products))
{
// ALWAYS CHECK - you are not the only one connected to this bus, and someone else may have already
// handled the event or accepted the drop - it might not contain types relevant to you.
// you still get informed about the drop event in case you did some stuff in your gui and need to clean it up.
return;
}
// we wouldn't reach this code if the following cast is null or the event was null or accepted was already true.
ViewportDragContext* viewportDragContext = azrtti_cast<ViewportDragContext*>(&context);
event->setDropAction(Qt::CopyAction);
event->setAccepted(true);
EntityIdList spawnedEntities;
AzFramework::SliceInstantiationTicket spawnTicket;
// Make a scoped undo that covers the ENTIRE operation.
ScopedUndoBatch undo("Create entities from asset");
// Handle sources
for (const SourceAssetBrowserEntry* source : sources)
{
AssetBrowser::AssetBrowserSourceDropBus::Event(
source->GetExtension(),
&AssetBrowser::AssetBrowserSourceDropEvents::HandleSourceFileType,
source->GetFullPath(),
AZ::EntityId(),
viewportDragContext->m_hitLocation
);
}
// Handle products
AZStd::vector<AZ::Data::AssetType> productAssetTypes;
productAssetTypes.reserve(products.size());
for (const AzToolsFramework::AssetBrowser::ProductAssetBrowserEntry* entry : products)
{
productAssetTypes.emplace_back(entry->GetAssetType());
}
for (const ProductAssetBrowserEntry* product : products)
{
if (CanSpawnEntityForProduct(product, productAssetTypes))
{
SpawnEntityAtPoint(product, viewportDragContext, spawnedEntities, spawnTicket);
}
}
// Select the new entity (and deselect others).
if (!spawnedEntities.empty())
{
ToolsApplicationRequests::Bus::Broadcast(&ToolsApplicationRequests::SetSelectedEntities, spawnedEntities);
}
}
void AzAssetBrowserRequestHandler::AddSourceFileOpeners(const char* fullSourceFileName, const AZ::Uuid& sourceUUID, AzToolsFramework::AssetBrowser::SourceFileOpenerList& openers)
{
using namespace AzToolsFramework;
//Get asset group to support a variety of file extensions
const AzToolsFramework::AssetBrowser::SourceAssetBrowserEntry* fullDetails =
AzToolsFramework::AssetBrowser::SourceAssetBrowserEntry::GetSourceByUuid(sourceUUID);
if (!fullDetails)
{
return;
}
QString assetGroup;
AZ::AssetTypeInfoBus::EventResult(assetGroup, fullDetails->GetPrimaryAssetType(), &AZ::AssetTypeInfo::GetGroup);
if (AZStd::wildcard_match("*.lua", fullSourceFileName))
{
AZStd::string fullName(fullSourceFileName);
// LUA files can be opened with the O3DE LUA editor.
openers.push_back(
{
"O3DE_LUA_Editor",
"Open in Open 3D Engine LUA Editor...",
QIcon(),
[](const char* fullSourceFileNameInCallback, const AZ::Uuid& /*sourceUUID*/)
{
// we know how to handle LUA files (open with the lua Editor.
EditorRequestBus::Broadcast(&EditorRequests::LaunchLuaEditor, fullSourceFileNameInCallback);
}
});
}
if (!openers.empty())
{
return; // we found one
}
// if we still havent found one, check to see if it is a default "generic" serializable asset
// and open the asset editor if so. Check whether the Generic Asset handler handles this kind of asset.
// to do so we need the actual type of that asset, which requires an asset type, not a source type.
AZ::Data::AssetManager& manager = AZ::Data::AssetManager::Instance();
// find a product type to query against.
AZStd::vector<const AssetBrowser::ProductAssetBrowserEntry*> candidates;
fullDetails->GetChildrenRecursively<AssetBrowser::ProductAssetBrowserEntry>(candidates);
// find the first one that is handled by something:
for (const AssetBrowser::ProductAssetBrowserEntry* productEntry : candidates)
{
// is there a Generic Asset Handler for it?
AZ::Data::AssetType productAssetType = productEntry->GetAssetType();
if ((productAssetType == AZ::Data::s_invalidAssetType) || (!productEntry->GetAssetId().IsValid()))
{
continue;
}
if (const AZ::Data::AssetHandler* assetHandler = manager.GetHandler(productAssetType))
{
if (!azrtti_istypeof<AzFramework::GenericAssetHandlerBase*>(assetHandler))
{
// it is not the generic asset handler.
continue;
}
// yes, it is the generic asset handler, so install an opener that sends it to the Asset Editor.
AZ::Data::AssetId assetId = productEntry->GetAssetId();
AZ::Data::AssetType assetType = productEntry->GetAssetType();
openers.push_back(
{
"Open_In_Asset_Editor",
"Open in Asset Editor...",
QIcon(),
[assetId, assetType](const char* /*fullSourceFileNameInCallback*/, const AZ::Uuid& /*sourceUUID*/)
{
AZ::Data::Asset<AZ::Data::AssetData> asset = AZ::Data::AssetManager::Instance().FindOrCreateAsset(assetId, assetType, AZ::Data::AssetLoadBehavior::Default);
AzToolsFramework::AssetEditor::AssetEditorRequestsBus::Broadcast(&AzToolsFramework::AssetEditor::AssetEditorRequests::OpenAssetEditor, asset);
}
});
break; // no need to proceed further
}
}
}
void AzAssetBrowserRequestHandler::OpenAssetInAssociatedEditor(const AZ::Data::AssetId& assetId, bool& alreadyHandled)
{
using namespace AzToolsFramework::AssetBrowser;
if (alreadyHandled)
{
// a higher priority listener has already taken this request.
return;
}
const SourceAssetBrowserEntry* source = SourceAssetBrowserEntry::GetSourceByUuid(assetId.m_guid);
if (!source)
{
return;
}
AZStd::string fullEntryPath = source->GetFullPath();
AZ::Uuid sourceID = source->GetSourceUuid();
if (fullEntryPath.empty())
{
return;
}
QWidget* mainWindow = nullptr;
AzToolsFramework::EditorRequestBus::BroadcastResult(mainWindow, &AzToolsFramework::EditorRequests::GetMainWindow);
SourceFileOpenerList openers;
AssetBrowserInteractionNotificationBus::Broadcast(&AssetBrowserInteractionNotificationBus::Events::AddSourceFileOpeners, fullEntryPath.c_str(), sourceID, openers);
// did anyone actually accept it?
if (!openers.empty())
{
// yes, call the opener and return.
// are there more than one opener(s)?
const SourceFileOpenerDetails* openerToUse = nullptr;
// a function which reassigns openerToUse to be the selected one.
AZStd::function<void(const SourceFileOpenerDetails*)> switchToOpener = [&openerToUse](const SourceFileOpenerDetails* switchTo)
{
openerToUse = switchTo;
};
// callers are allowed to add nullptr to openers. So we only evaluate the valid ones.
// and if there is only one valid one, we use that one.
const SourceFileOpenerDetails* firstValidOpener = nullptr;
int numValidOpeners = 0;
QMenu menu(mainWindow);
for (const SourceFileOpenerDetails& openerDetails : openers)
{
// bind that function to the current loop element.
if (openerDetails.m_opener) // only VALID openers with an actual callback.
{
++numValidOpeners;
if (!firstValidOpener)
{
firstValidOpener = &openerDetails;
}
// bind a callback such that when the menu item is clicked, it sets that as the opener to use.
menu.addAction(openerDetails.m_iconToUse, QObject::tr(openerDetails.m_displayText.c_str()), mainWindow, AZStd::bind(switchToOpener, &openerDetails));
}
}
if (numValidOpeners > 1) // more than one option was added
{
menu.addSeparator();
menu.addAction(QObject::tr("Cancel"), AZStd::bind(switchToOpener, nullptr)); // just something to click on to avoid doing anything.
menu.exec(QCursor::pos());
}
else if (numValidOpeners == 1)
{
openerToUse = firstValidOpener;
}
// did we select one and did it have a function to call?
if ((openerToUse) && (openerToUse->m_opener))
{
openerToUse->m_opener(fullEntryPath.c_str(), sourceID);
}
alreadyHandled = true;
return; // an opener handled this, no need to proceed further.
}
// if we get here, nothing handled it, so try the operating system.
alreadyHandled = OpenWithOS(fullEntryPath);
}
bool AzAssetBrowserRequestHandler::OpenWithOS(const AZStd::string& fullEntryPath)
{
bool openedSuccessfully = QDesktopServices::openUrl(QUrl::fromLocalFile(QString::fromUtf8(fullEntryPath.c_str())));
if (!openedSuccessfully)
{
AZ_Printf("Asset Browser", "Unable to open '%s' using the operating system. There might be no editor associated with this kind of file.\n", fullEntryPath.c_str());
}
return openedSuccessfully;
}
@@ -0,0 +1,65 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzQtComponents/Buses/DragAndDrop.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserBus.h>
#include <AzToolsFramework/AssetBrowser/Previewer/PreviewerBus.h>
namespace AZ
{
class Entity;
}
namespace AzQtComponents
{
class DragAndDropContextBase;
}
namespace AzToolsFramework
{
namespace AssetBrowser
{
class AssetBrowserEntry;
class PreviewerFactory;
class ProductAssetBrowserEntry;
class SourceAssetBrowserEntry;
}
}
class AzAssetBrowserRequestHandler
: protected AzToolsFramework::AssetBrowser::AssetBrowserInteractionNotificationBus::Handler
, protected AzQtComponents::DragAndDropEventsBus::Handler
{
public:
AzAssetBrowserRequestHandler();
~AzAssetBrowserRequestHandler() override;
//////////////////////////////////////////////////////////////////////////
// AssetBrowserInteractionNotificationBus
//////////////////////////////////////////////////////////////////////////
void AddContextMenuActions(QWidget* caller, QMenu* menu, const AZStd::vector<AzToolsFramework::AssetBrowser::AssetBrowserEntry*>& entries) override;
void AddSourceFileOpeners(const char* fullSourceFileName, const AZ::Uuid& sourceUUID, AzToolsFramework::AssetBrowser::SourceFileOpenerList& openers) override;
void OpenAssetInAssociatedEditor(const AZ::Data::AssetId& assetId, bool& alreadyHandled) override;
static bool OpenWithOS(const AZStd::string& fullEntryPath);
protected:
//////////////////////////////////////////////////////////////////////////
// AzQtComponents::DragAndDropEventsBus::Handler
//////////////////////////////////////////////////////////////////////////
void DragEnter(QDragEnterEvent* event, AzQtComponents::DragAndDropContextBase& context) override;
void DragMove(QDragMoveEvent* event, AzQtComponents::DragAndDropContextBase& context) override;
void DragLeave(QDragLeaveEvent* event) override;
void Drop(QDropEvent* event, AzQtComponents::DragAndDropContextBase& context) override;
bool CanAcceptDragAndDropEvent(
QDropEvent* event, AzQtComponents::DragAndDropContextBase& context,
AZStd::optional<AZStd::vector<const AzToolsFramework::AssetBrowser::SourceAssetBrowserEntry*>*> outSources = AZStd::nullopt,
AZStd::optional<AZStd::vector<const AzToolsFramework::AssetBrowser::ProductAssetBrowserEntry*>*> outProducts = AZStd::nullopt) const;
};
@@ -0,0 +1,218 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "AzAssetBrowserWindow.h"
// AzToolsFramework
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/API/ViewPaneOptions.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserModel.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserEntry.h>
// AzQtComponents
#include <AzQtComponents/Utilities/QtWindowUtilities.h>
// Editor
#include "AzAssetBrowser/AzAssetBrowserRequestHandler.h"
#include "LyViewPaneNames.h"
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
#include <AzAssetBrowser/ui_AzAssetBrowserWindow.h>
AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
class ListenerForShowAssetEditorEvent
: public QObject
, private AzToolsFramework::EditorEvents::Bus::Handler
{
public:
ListenerForShowAssetEditorEvent(QObject* parent = nullptr)
: QObject(parent)
{
AzToolsFramework::EditorEvents::Bus::Handler::BusConnect();
}
~ListenerForShowAssetEditorEvent()
{
AzToolsFramework::EditorEvents::Bus::Handler::BusDisconnect();
}
void SelectAsset(const QString& assetPath) override
{
AzToolsFramework::OpenViewPane(LyViewPane::AssetBrowser);
AzAssetBrowserWindow* assetBrowser = AzToolsFramework::GetViewPaneWidget<AzAssetBrowserWindow>(LyViewPane::AssetBrowser);
if (assetBrowser)
{
AzQtComponents::bringWindowToTop(assetBrowser);
assetBrowser->SelectAsset(assetPath);
}
}
};
AzAssetBrowserWindow::AzAssetBrowserWindow(QWidget* parent)
: QWidget(parent)
, m_ui(new Ui::AzAssetBrowserWindowClass())
, m_filterModel(new AzToolsFramework::AssetBrowser::AssetBrowserFilterModel(parent))
{
m_ui->setupUi(this);
m_ui->m_searchWidget->Setup(true, true);
using namespace AzToolsFramework::AssetBrowser;
AssetBrowserComponentRequestBus::BroadcastResult(m_assetBrowserModel, &AssetBrowserComponentRequests::GetAssetBrowserModel);
AZ_Assert(m_assetBrowserModel, "Failed to get filebrowser model");
m_filterModel->setSourceModel(m_assetBrowserModel);
m_filterModel->SetFilter(m_ui->m_searchWidget->GetFilter());
m_ui->m_assetBrowserTreeViewWidget->setModel(m_filterModel.data());
connect(m_ui->m_searchWidget->GetFilter().data(), &AssetBrowserEntryFilter::updatedSignal,
m_filterModel.data(), &AssetBrowserFilterModel::filterUpdatedSlot);
connect(m_filterModel.data(), &AssetBrowserFilterModel::filterChanged, this, [this]()
{
const bool hasFilter = !m_ui->m_searchWidget->GetFilterString().isEmpty();
const bool selectFirstFilteredIndex = false;
m_ui->m_assetBrowserTreeViewWidget->UpdateAfterFilter(hasFilter, selectFirstFilteredIndex);
});
connect(m_ui->m_assetBrowserTreeViewWidget, &AssetBrowserTreeView::selectionChangedSignal,
this, &AzAssetBrowserWindow::SelectionChangedSlot);
connect(m_ui->m_assetBrowserTreeViewWidget, &QAbstractItemView::doubleClicked, this, &AzAssetBrowserWindow::DoubleClickedItem);
connect(m_ui->m_assetBrowserTreeViewWidget, &AssetBrowserTreeView::ClearStringFilter, m_ui->m_searchWidget, &SearchWidget::ClearStringFilter);
connect(m_ui->m_assetBrowserTreeViewWidget, &AssetBrowserTreeView::ClearTypeFilter, m_ui->m_searchWidget, &SearchWidget::ClearTypeFilter);
m_ui->m_assetBrowserTreeViewWidget->SetName("AssetBrowserTreeView_main");
}
AzAssetBrowserWindow::~AzAssetBrowserWindow()
{
m_ui->m_assetBrowserTreeViewWidget->SaveState();
}
void AzAssetBrowserWindow::RegisterViewClass()
{
AzToolsFramework::ViewPaneOptions options;
options.preferedDockingArea = Qt::LeftDockWidgetArea;
AzToolsFramework::RegisterViewPane<AzAssetBrowserWindow>(LyViewPane::AssetBrowser, LyViewPane::CategoryTools, options);
}
QObject* AzAssetBrowserWindow::createListenerForShowAssetEditorEvent(QObject* parent)
{
auto* listener = new ListenerForShowAssetEditorEvent(parent);
// the listener is attached to the parent and will get cleaned up then
return listener;
}
void AzAssetBrowserWindow::UpdatePreview() const
{
auto selectedAssets = m_ui->m_assetBrowserTreeViewWidget->GetSelectedAssets();
if (selectedAssets.size() != 1)
{
m_ui->m_previewerFrame->Clear();
return;
}
m_ui->m_previewerFrame->Display(selectedAssets.front());
}
static void ExpandTreeToIndex(QTreeView* treeView, const QModelIndex& index)
{
treeView->collapseAll();
// Note that we deliberately don't expand the index passed in
// collapseAll above will close all but the top level nodes.
// treeView->expand(index) marks a node as expanded, but if it's parent isn't expanded,
// there won't be any paint updates because it doesn't expand parent nodes.
// So, to minimize paint updates, we expand everything in reverse order (leaf up to root), so that
// painting will only actually occur once the top level parent is expanded.
QModelIndex parentIndex = index.parent();
while (parentIndex.isValid())
{
treeView->expand(parentIndex);
parentIndex = parentIndex.parent();
}
}
void AzAssetBrowserWindow::SelectAsset(const QString& assetPath)
{
using namespace AzToolsFramework::AssetBrowser;
QModelIndex index = m_assetBrowserModel->findIndex(assetPath);
if (index.isValid())
{
m_ui->m_searchWidget->ClearTextFilter();
m_ui->m_searchWidget->ClearTypeFilter();
// Queue the expand and select stuff, so that it doesn't get processed the same
// update as the search widget clearing - something with the search widget clearing
// interferes with the update from the select and expand, and if you don't
// queue it, the tree doesn't expand reliably.
QTimer::singleShot(0, this, [this, filteredIndex = index] {
// the treeview has a filter model so we have to backwards go from that
QModelIndex index = m_filterModel->mapFromSource(filteredIndex);
QTreeView* treeView = m_ui->m_assetBrowserTreeViewWidget;
ExpandTreeToIndex(treeView, index);
treeView->scrollTo(index);
treeView->setCurrentIndex(index);
treeView->selectionModel()->select(index, QItemSelectionModel::ClearAndSelect);
});
}
}
void AzAssetBrowserWindow::SelectionChangedSlot(const QItemSelection& /*selected*/, const QItemSelection& /*deselected*/) const
{
UpdatePreview();
}
// while its tempting to use Activated here, we dont actually want it to count as activation
// just becuase on some OS clicking once is activation.
void AzAssetBrowserWindow::DoubleClickedItem([[maybe_unused]] const QModelIndex& element)
{
using namespace AzToolsFramework;
using namespace AzToolsFramework::AssetBrowser;
// assumption: Double clicking an item selects it before telling us we double clicked it.
auto selectedAssets = m_ui->m_assetBrowserTreeViewWidget->GetSelectedAssets();
for (const AssetBrowserEntry* entry : selectedAssets)
{
AZ::Data::AssetId assetIdToOpen;
AZStd::string fullFilePath;
if (const ProductAssetBrowserEntry* productEntry = azrtti_cast<const ProductAssetBrowserEntry*>(entry))
{
assetIdToOpen = productEntry->GetAssetId();
fullFilePath = entry->GetFullPath();
}
else if (const SourceAssetBrowserEntry* sourceEntry = azrtti_cast<const SourceAssetBrowserEntry*>(entry))
{
// manufacture an empty AssetID with the source's UUID
assetIdToOpen = AZ::Data::AssetId(sourceEntry->GetSourceUuid(), 0);
fullFilePath = entry->GetFullPath();
}
bool handledBySomeone = false;
if (assetIdToOpen.IsValid())
{
AssetBrowserInteractionNotificationBus::Broadcast(&AssetBrowserInteractionNotifications::OpenAssetInAssociatedEditor, assetIdToOpen, handledBySomeone);
}
if (!handledBySomeone && !fullFilePath.empty())
{
AzAssetBrowserRequestHandler::OpenWithOS(fullFilePath);
}
}
}
#include <AzAssetBrowser/moc_AzAssetBrowserWindow.cpp>
@@ -0,0 +1,60 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzCore/Memory/SystemAllocator.h>
#include <QWidget>
#endif
class QItemSelection;
namespace Ui
{
class AzAssetBrowserWindowClass;
}
namespace AzToolsFramework
{
namespace AssetBrowser
{
class AssetBrowserFilterModel;
class AssetBrowserModel;
}
}
class AzAssetBrowserWindow
: public QWidget
{
Q_OBJECT
public:
AZ_CLASS_ALLOCATOR(AzAssetBrowserWindow, AZ::SystemAllocator, 0);
explicit AzAssetBrowserWindow(QWidget* parent = nullptr);
virtual ~AzAssetBrowserWindow();
void SelectAsset(const QString& assetPath);
static void RegisterViewClass();
static QObject* createListenerForShowAssetEditorEvent(QObject* parent);
private:
QScopedPointer<Ui::AzAssetBrowserWindowClass> m_ui;
QScopedPointer<AzToolsFramework::AssetBrowser::AssetBrowserFilterModel> m_filterModel;
AzToolsFramework::AssetBrowser::AssetBrowserModel* m_assetBrowserModel;
void UpdatePreview() const;
private Q_SLOTS:
void SelectionChangedSlot(const QItemSelection& selected, const QItemSelection& deselected) const;
void DoubleClickedItem(const QModelIndex& element);
};
extern const char* AZ_ASSET_BROWSER_PREVIEW_NAME;
@@ -0,0 +1,168 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AzAssetBrowserWindowClass</class>
<widget class="QWidget" name="AzAssetBrowserWindowClass">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>691</width>
<height>554</height>
</rect>
</property>
<property name="windowTitle">
<string>Asset Browser</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QScrollArea" name="scrollArea">
<property name="minimumSize">
<size>
<width>1</width>
<height>1</height>
</size>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>671</width>
<height>534</height>
</rect>
</property>
<layout class="QVBoxLayout" name="scrollAreaVerticalLayout">
<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>
<layout class="QVBoxLayout" name="m_headerLayout">
<item>
<widget class="AzToolsFramework::AssetBrowser::SearchWidget" name="m_searchWidget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QSplitter" name="m_splitter">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="childrenCollapsible">
<bool>false</bool>
</property>
<widget class="QWidget" name="m_leftLayout" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="styleSheet">
<string notr="true">vertical-align: top</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_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="AzToolsFramework::AssetBrowser::AssetBrowserTreeView" name="m_assetBrowserTreeViewWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="dragDropMode">
<enum>QAbstractItemView::DragOnly</enum>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="previewWidgetWrapper">
<layout class="QVBoxLayout" name="m_rightLayout">
<item>
<widget class="AzToolsFramework::AssetBrowser::PreviewerFrame" name="m_previewerFrame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>AzToolsFramework::AssetBrowser::SearchWidget</class>
<extends>QWidget</extends>
<header>AzToolsFramework/AssetBrowser/Search/SearchWidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>AzToolsFramework::AssetBrowser::AssetBrowserTreeView</class>
<extends>QTreeView</extends>
<header>AzToolsFramework/AssetBrowser/Views/AssetBrowserTreeView.h</header>
</customwidget>
<customwidget>
<class>AzToolsFramework::AssetBrowser::PreviewerFrame</class>
<extends>QFrame</extends>
<header>AzToolsFramework/AssetBrowser/Previewer/PreviewerFrame.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
+292
View File
@@ -0,0 +1,292 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "BaseLibrary.h"
#include "BaseLibraryItem.h"
#include "Include/IBaseLibraryManager.h"
#include <Util/PathUtil.h>
#include <IFileUtil.h>
#include "Undo/IUndoObject.h"
//////////////////////////////////////////////////////////////////////////
// Undo functionality for libraries.
//////////////////////////////////////////////////////////////////////////
class CUndoBaseLibrary
: public IUndoObject
{
public:
CUndoBaseLibrary(CBaseLibrary* pLib, const QString& description, const QString& selectedItem = 0)
: m_pLib(pLib)
, m_description(description)
, m_redo(0)
, m_selectedItem(selectedItem)
{
assert(m_pLib);
m_undo = GetIEditor()->GetSystem()->CreateXmlNode("Undo");
m_pLib->Serialize(m_undo, false);
}
virtual QString GetEditorObjectName()
{
return m_selectedItem;
}
protected:
virtual int GetSize() { return sizeof(CUndoBaseLibrary); }
virtual QString GetDescription() { return m_description; };
virtual void Undo(bool bUndo)
{
if (bUndo)
{
m_redo = GetIEditor()->GetSystem()->CreateXmlNode("Redo");
m_pLib->Serialize(m_redo, false);
}
m_pLib->Serialize(m_undo, true);
m_pLib->SetModified();
GetIEditor()->Notify(eNotify_OnDataBaseUpdate);
}
virtual void Redo()
{
m_pLib->Serialize(m_redo, true);
m_pLib->SetModified();
GetIEditor()->Notify(eNotify_OnDataBaseUpdate);
}
private:
QString m_description;
QString m_selectedItem;
_smart_ptr<CBaseLibrary> m_pLib;
XmlNodeRef m_undo;
XmlNodeRef m_redo;
};
//////////////////////////////////////////////////////////////////////////
// CBaseLibrary implementation.
//////////////////////////////////////////////////////////////////////////
CBaseLibrary::CBaseLibrary(IBaseLibraryManager* pManager)
: m_pManager(pManager)
, m_bModified(false)
, m_bLevelLib(false)
, m_bNewLibrary(true)
{
}
//////////////////////////////////////////////////////////////////////////
CBaseLibrary::~CBaseLibrary()
{
m_items.clear();
}
//////////////////////////////////////////////////////////////////////////
IBaseLibraryManager* CBaseLibrary::GetManager()
{
return m_pManager;
}
//////////////////////////////////////////////////////////////////////////
void CBaseLibrary::RemoveAllItems()
{
AddRef();
for (int i = 0; i < m_items.size(); i++)
{
// Unregister item in case it was registered. It is ok if it wasn't. This is still safe to call.
m_pManager->UnregisterItem(m_items[i]);
// Clear library item.
m_items[i]->m_library = NULL;
}
m_items.clear();
Release();
}
//////////////////////////////////////////////////////////////////////////
void CBaseLibrary::SetName(const QString& name)
{
//the fullname of the items in the library will be changed due to library's name change
//so we need unregistered them and register them after their name changed.
for (int i = 0; i < m_items.size(); i++)
{
m_pManager->UnregisterItem(m_items[i]);
}
m_name = name;
for (int i = 0; i < m_items.size(); i++)
{
m_pManager->RegisterItem(m_items[i]);
}
SetModified();
}
//////////////////////////////////////////////////////////////////////////
const QString& CBaseLibrary::GetName() const
{
return m_name;
}
//////////////////////////////////////////////////////////////////////////
bool CBaseLibrary::Save()
{
return true;
}
//////////////////////////////////////////////////////////////////////////
bool CBaseLibrary::Load(const QString& filename)
{
m_filename = filename;
SetModified(false);
m_bNewLibrary = false;
return true;
}
//////////////////////////////////////////////////////////////////////////
void CBaseLibrary::SetModified(bool bModified)
{
if (bModified != m_bModified)
{
m_bModified = bModified;
emit Modified(bModified);
}
}
//////////////////////////////////////////////////////////////////////////
void CBaseLibrary::AddItem(IDataBaseItem* item, bool bRegister)
{
CBaseLibraryItem* pLibItem = (CBaseLibraryItem*)item;
// Check if item is already assigned to this library.
if (pLibItem->m_library != this)
{
pLibItem->m_library = this;
m_items.push_back(pLibItem);
SetModified();
if (bRegister)
{
m_pManager->RegisterItem(pLibItem);
}
}
}
//////////////////////////////////////////////////////////////////////////
IDataBaseItem* CBaseLibrary::GetItem(int index)
{
assert(index >= 0 && index < m_items.size());
return m_items[index];
}
//////////////////////////////////////////////////////////////////////////
void CBaseLibrary::RemoveItem(IDataBaseItem* item)
{
for (int i = 0; i < m_items.size(); i++)
{
if (m_items[i] == item)
{
// Unregister item in case it was registered. It is ok if it wasn't. This is still safe to call.
m_pManager->UnregisterItem(m_items[i]);
m_items.erase(m_items.begin() + i);
SetModified();
break;
}
}
}
//////////////////////////////////////////////////////////////////////////
IDataBaseItem* CBaseLibrary::FindItem(const QString& name)
{
for (int i = 0; i < m_items.size(); i++)
{
if (QString::compare(m_items[i]->GetName(), name, Qt::CaseInsensitive) == 0)
{
return m_items[i];
}
}
return NULL;
}
bool CBaseLibrary::AddLibraryToSourceControl(const QString& fullPathName) const
{
IEditor* pEditor = GetIEditor();
IFileUtil* pFileUtil = pEditor ? pEditor->GetFileUtil() : nullptr;
if (pFileUtil)
{
return pFileUtil->CheckoutFile(fullPathName.toUtf8().data(), nullptr);
}
return false;
}
bool CBaseLibrary::SaveLibrary(const char* name, bool saveEmptyLibrary)
{
assert(name != NULL);
if (name == NULL)
{
CryFatalError("The library you are attempting to save has no name specified.");
return false;
}
QString fileName(GetFilename());
if (fileName.isEmpty() && !saveEmptyLibrary)
{
return false;
}
fileName = Path::GamePathToFullPath(fileName);
XmlNodeRef root = GetIEditor()->GetSystem()->CreateXmlNode(name);
Serialize(root, false);
bool bRes = XmlHelpers::SaveXmlNode(GetIEditor()->GetFileUtil(), root, fileName.toUtf8().data());
if (m_bNewLibrary)
{
AddLibraryToSourceControl(fileName);
m_bNewLibrary = false;
}
if (!bRes)
{
string strMessage;
QByteArray filenameUtf8 = fileName.toUtf8();
strMessage.Format("The file %s is read-only and the save of the library couldn't be performed. Try to remove the \"read-only\" flag or check-out the file and then try again.", filenameUtf8.data());
CryMessageBox(strMessage.c_str(), "Saving Error", MB_OK | MB_ICONWARNING);
}
return bRes;
}
//CONFETTI BEGIN
void CBaseLibrary::ChangeItemOrder(CBaseLibraryItem* item, unsigned int newLocation)
{
std::vector<_smart_ptr<CBaseLibraryItem> > temp;
for (unsigned int i = 0; i < m_items.size(); i++)
{
if (i == newLocation)
{
temp.push_back(_smart_ptr<CBaseLibraryItem>(item));
}
if (m_items[i] != item)
{
temp.push_back(m_items[i]);
}
}
// If newLocation is greater than the original size, append the item to end of the list
if (newLocation >= m_items.size())
{
temp.push_back(_smart_ptr<CBaseLibraryItem>(item));
}
m_items = temp;
}
//CONFETTI END
#include <moc_BaseLibrary.cpp>
+128
View File
@@ -0,0 +1,128 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef CRYINCLUDE_EDITOR_BASELIBRARY_H
#define CRYINCLUDE_EDITOR_BASELIBRARY_H
#pragma once
#if !defined(Q_MOC_RUN)
#include "Include/IDataBaseLibrary.h"
#include "Include/IBaseLibraryManager.h"
#include "Include/EditorCoreAPI.h"
#include "Util/TRefCountBase.h"
#include <QObject>
#endif
// Ensure we don't try to dllimport when moc includes us
#if defined(Q_MOC_BUILD) && !defined(EDITOR_CORE)
#define EDITOR_CORE
#endif
/** This a base class for all Libraries used by Editor.
*/
class EDITOR_CORE_API CBaseLibrary
: public QObject
, public TRefCountBase<IDataBaseLibrary>
{
Q_OBJECT
public:
explicit CBaseLibrary(IBaseLibraryManager* pManager);
~CBaseLibrary();
//! Set library name.
virtual void SetName(const QString& name);
//! Get library name.
const QString& GetName() const;
//! Set new filename for this library.
virtual bool SetFilename(const QString& filename, [[maybe_unused]] bool checkForUnique = true) { m_filename = filename.toLower(); return true; };
const QString& GetFilename() const { return m_filename; };
virtual bool Save() = 0;
virtual bool Load(const QString& filename) = 0;
virtual void Serialize(XmlNodeRef& node, bool bLoading) = 0;
//! Mark library as modified.
void SetModified(bool bModified = true);
//! Check if library was modified.
bool IsModified() const { return m_bModified; };
//////////////////////////////////////////////////////////////////////////
// Working with items.
//////////////////////////////////////////////////////////////////////////
//! Add a new prototype to library.
void AddItem(IDataBaseItem* item, bool bRegister = true);
//! Get number of known prototypes.
int GetItemCount() const { return static_cast<int>(m_items.size()); }
//! Get prototype by index.
IDataBaseItem* GetItem(int index);
//! Delete item by pointer of item.
void RemoveItem(IDataBaseItem* item);
//! Delete all items from library.
void RemoveAllItems();
//! Find library item by name.
//! Using linear search.
IDataBaseItem* FindItem(const QString& name);
//! Check if this library is local level library.
bool IsLevelLibrary() const { return m_bLevelLib; };
//! Set library to be level library.
void SetLevelLibrary(bool bEnable) { m_bLevelLib = bEnable; };
//////////////////////////////////////////////////////////////////////////
//! Return manager for this library.
IBaseLibraryManager* GetManager();
// Saves the library with the main tag defined by the parameter name
bool SaveLibrary(const char* name, bool saveEmptyLibrary = false);
//CONFETTI BEGIN
// Used to change the library item order
virtual void ChangeItemOrder(CBaseLibraryItem* item, unsigned int newLocation) override;
//CONFETTI END
signals:
void Modified(bool bModified);
private:
// Add the library to the source control
bool AddLibraryToSourceControl(const QString& fullPathName) const;
protected:
//! Name of the library.
QString m_name;
//! Filename of the library.
QString m_filename;
//! Flag set when library was modified.
bool m_bModified;
// Flag set when the library is just created and it's not yet saved for the first time.
bool m_bNewLibrary;
//! Level library is saved within the level .ly file and is local for this level.
bool m_bLevelLib;
//////////////////////////////////////////////////////////////////////////
// Manager.
IBaseLibraryManager* m_pManager;
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
// Array of all our library items.
std::vector<_smart_ptr<CBaseLibraryItem> > m_items;
AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
};
#endif // CRYINCLUDE_EDITOR_BASELIBRARY_H
+272
View File
@@ -0,0 +1,272 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "BaseLibraryItem.h"
#include "BaseLibrary.h"
#include "BaseLibraryManager.h"
#include "Undo/IUndoObject.h"
#include <AzCore/Math/Uuid.h>
//undo object for multi-changes inside library item. such as set all variables to default values.
//For example: change particle emitter shape will lead to multiple variable changes
class CUndoBaseLibraryItem
: public IUndoObject
{
public:
CUndoBaseLibraryItem(IBaseLibraryManager *libMgr, CBaseLibraryItem* libItem, bool ignoreChild)
: m_libMgr(libMgr)
{
assert(libItem);
assert(libMgr);
m_itemPath = libItem->GetFullName();
m_description = "Lib item changed: " + m_itemPath;
//serialize the lib item to undo
m_undoCtx.node = GetIEditor()->GetSystem()->CreateXmlNode("Undo");
m_undoCtx.bIgnoreChilds = ignoreChild;
m_undoCtx.bLoading = false; //saving
m_undoCtx.bUniqName = false; //don't generate new name
m_undoCtx.bCopyPaste = true; //so it won't override guid
m_undoCtx.bUndo = true;
libItem->Serialize(m_undoCtx);
//evaluate size
XmlString xmlStr = m_undoCtx.node->getXML();
m_size = sizeof(CUndoBaseLibraryItem);
m_size += xmlStr.GetAllocatedMemory();
m_size += m_itemPath.length();
m_size += m_description.length();
}
QString GetEditorObjectName() override
{
return m_itemPath;
}
protected:
virtual int GetSize()
{
return m_size;
}
QString GetDescription() override
{
return m_description;
}
virtual void Undo(bool bUndo)
{
//find the libItem
IDataBaseItem *libItem = m_libMgr->FindItemByName(m_itemPath);
if (libItem == nullptr)
{
//the undo stack is not reliable any more..
assert(false);
return;
}
//save for redo
if (bUndo)
{
m_redoCtx.node = GetIEditor()->GetSystem()->CreateXmlNode("Redo");
m_redoCtx.bIgnoreChilds = m_undoCtx.bIgnoreChilds;
m_redoCtx.bLoading = false; //saving
m_redoCtx.bUniqName = false;
m_redoCtx.bCopyPaste = true;
m_redoCtx.bUndo = true;
libItem->Serialize(m_redoCtx);
XmlString xmlStr = m_redoCtx.node->getXML();
m_size += xmlStr.GetAllocatedMemory();
}
//load previous saved data
m_undoCtx.bLoading = true;
libItem->Serialize(m_undoCtx);
}
virtual void Redo()
{
//find the libItem
IDataBaseItem *libItem = m_libMgr->FindItemByName(m_itemPath);
if (libItem == nullptr || m_redoCtx.node == nullptr)
{
//the undo stack is not reliable any more..
assert(false);
return;
}
m_redoCtx.bLoading = true;
libItem->Serialize(m_redoCtx);
}
private:
QString m_description;
QString m_itemPath;
IDataBaseItem::SerializeContext m_undoCtx; //saved before operation
IDataBaseItem::SerializeContext m_redoCtx; //saved after operation so used for redo
IBaseLibraryManager* m_libMgr;
int m_size;
};
//////////////////////////////////////////////////////////////////////////
// CBaseLibraryItem implementation.
//////////////////////////////////////////////////////////////////////////
CBaseLibraryItem::CBaseLibraryItem()
{
m_library = 0;
GenerateId();
m_bModified = false;
}
CBaseLibraryItem::~CBaseLibraryItem()
{
}
//////////////////////////////////////////////////////////////////////////
QString CBaseLibraryItem::GetFullName() const
{
QString name;
if (m_library)
{
name = m_library->GetName() + ".";
}
name += m_name;
return name;
}
//////////////////////////////////////////////////////////////////////////
QString CBaseLibraryItem::GetGroupName()
{
QString str = GetName();
int p = str.lastIndexOf('.');
if (p >= 0)
{
return str.mid(0, p);
}
return "";
}
//////////////////////////////////////////////////////////////////////////
QString CBaseLibraryItem::GetShortName()
{
QString str = GetName();
int p = str.lastIndexOf('.');
if (p >= 0)
{
return str.mid(p + 1);
}
p = str.lastIndexOf('/');
if (p >= 0)
{
return str.mid(p + 1);
}
return str;
}
//////////////////////////////////////////////////////////////////////////
void CBaseLibraryItem::SetName(const QString& name)
{
assert(m_library);
if (name == m_name)
{
return;
}
QString oldName = GetFullName();
m_name = name;
((CBaseLibraryManager*)m_library->GetManager())->OnRenameItem(this, oldName);
}
//////////////////////////////////////////////////////////////////////////
const QString& CBaseLibraryItem::GetName() const
{
return m_name;
}
//////////////////////////////////////////////////////////////////////////
void CBaseLibraryItem::GenerateId()
{
GUID guid = AZ::Uuid::CreateRandom();
SetGUID(guid);
}
//////////////////////////////////////////////////////////////////////////
void CBaseLibraryItem::SetGUID(REFGUID guid)
{
if (m_library)
{
((CBaseLibraryManager*)m_library->GetManager())->RegisterItem(this, guid);
}
m_guid = guid;
}
//////////////////////////////////////////////////////////////////////////
void CBaseLibraryItem::Serialize(SerializeContext& ctx)
{
assert(m_library);
XmlNodeRef node = ctx.node;
if (ctx.bLoading)
{
QString name = m_name;
// Loading
node->getAttr("Name", name);
if (!ctx.bUniqName)
{
SetName(name);
}
else
{
SetName(GetLibrary()->GetManager()->MakeUniqueItemName(name));
}
if (!ctx.bCopyPaste)
{
GUID guid;
if (node->getAttr("Id", guid))
{
SetGUID(guid);
}
}
}
else
{
// Saving.
node->setAttr("Name", m_name.toUtf8().data());
node->setAttr("Id", m_guid);
node->setAttr("Library", GetLibrary()->GetName().toUtf8().data());
}
m_bModified = false;
}
//////////////////////////////////////////////////////////////////////////
IDataBaseLibrary* CBaseLibraryItem::GetLibrary() const
{
return m_library;
}
//////////////////////////////////////////////////////////////////////////
void CBaseLibraryItem::SetLibrary(CBaseLibrary* pLibrary)
{
m_library = pLibrary;
}
//! Mark library as modified.
void CBaseLibraryItem::SetModified(bool bModified)
{
m_bModified = bModified;
if (m_bModified && m_library != NULL)
{
m_library->SetModified(bModified);
}
}
+113
View File
@@ -0,0 +1,113 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef CRYINCLUDE_EDITOR_BASELIBRARYITEM_H
#define CRYINCLUDE_EDITOR_BASELIBRARYITEM_H
#pragma once
#include "Include/IDataBaseItem.h"
#include "BaseLibrary.h"
#include <QMetaType>
class CBaseLibrary;
//////////////////////////////////////////////////////////////////////////
AZ_PUSH_DISABLE_DLL_EXPORT_BASECLASS_WARNING
/** Base class for all items contained in BaseLibraray.
*/
class EDITOR_CORE_API CBaseLibraryItem
: public TRefCountBase<IDataBaseItem>
{
AZ_POP_DISABLE_DLL_EXPORT_BASECLASS_WARNING
public:
CBaseLibraryItem();
~CBaseLibraryItem();
//! Set item name.
//! Its virtual, in case you want to override it in derrived item.
virtual void SetName(const QString& name);
//! Get item name.
const QString& GetName() const;
//! Get full item name, including name of library.
//! Name formed by adding dot after name of library
//! eg. library Pickup and item PickupRL form full item name: "Pickups.PickupRL".
QString GetFullName() const;
//! Get only nameof group from prototype.
QString GetGroupName();
//! Get short name of prototype without group.
QString GetShortName();
//! Return Library this item are contained in.
//! Item can only be at one library.
IDataBaseLibrary* GetLibrary() const;
void SetLibrary(CBaseLibrary* pLibrary);
//////////////////////////////////////////////////////////////////////////
//! Serialize library item to archive.
virtual void Serialize(SerializeContext& ctx);
//////////////////////////////////////////////////////////////////////////
//! Generate new unique id for this item.
void GenerateId();
//! Returns GUID of this material.
const GUID& GetGUID() const { return m_guid; }
//! Mark library as modified.
void SetModified(bool bModified = true);
//! Check if library was modified.
bool IsModified() const { return m_bModified; };
//! Returns true if the item is registered, otherwise false
bool IsRegistered() const { return m_bRegistered; };
//! Validate item for errors.
virtual void Validate() {};
//! Get number of sub childs.
virtual int GetChildCount() const { return 0; }
//! Get sub child by index.
virtual CBaseLibraryItem* GetChild([[maybe_unused]] int index) const { return nullptr; }
//////////////////////////////////////////////////////////////////////////
//! Gathers resources by this item.
virtual void GatherUsedResources([[maybe_unused]] CUsedResources& resources) {};
//! Get if stored item is enabled
virtual bool GetIsEnabled() { return true; };
int IsParticleItem = -1;
protected:
void SetGUID(REFGUID guid);
friend class CBaseLibrary;
friend class CBaseLibraryManager;
// Name of this prototype.
QString m_name;
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
//! Reference to prototype library who contains this prototype.
_smart_ptr<CBaseLibrary> m_library;
AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
//! Every base library item have unique id.
GUID m_guid;
// True when item modified by editor.
bool m_bModified;
// True when item registered in manager.
bool m_bRegistered = false;
};
Q_DECLARE_METATYPE(CBaseLibraryItem*);
TYPEDEF_AUTOPTR(CBaseLibraryItem);
#endif // CRYINCLUDE_EDITOR_BASELIBRARYITEM_H
+937
View File
@@ -0,0 +1,937 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "BaseLibraryManager.h"
// Editor
#include "BaseLibraryItem.h"
#include "ErrorReport.h"
#include "Undo/IUndoObject.h"
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Undo functionality for Managers, including add library, remove library, and rename library -- Vera, Confetti
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class CUndoBaseLibraryManager
: public IUndoObject
{
public:
CUndoBaseLibraryManager(CBaseLibraryManager* pMngr, const QString& description, const QString& modifiedManager = 0)
: m_pMngr(pMngr)
, m_description(description)
, m_editorObject(modifiedManager)
{
assert(m_pMngr);
SerializeTo(m_undos);
}
virtual QString GetEditorObjectName()
{
return m_editorObject;
}
protected:
virtual int GetSize() { return sizeof(CUndoBaseLibraryManager); }
virtual QString GetDescription() { return m_description; };
virtual void Undo(bool bUndo)
{
if (bUndo)
{
SerializeTo(m_redos);
}
m_pMngr->ClearAll();
UnserializeFrom(m_undos);
GetIEditor()->Notify(eNotify_OnDataBaseUpdate);
}
virtual void Redo()
{
m_pMngr->ClearAll();
UnserializeFrom(m_redos);
GetIEditor()->Notify(eNotify_OnDataBaseUpdate);
}
private:
struct LibUndoNode
: public _i_reference_target_t
{
LibUndoNode()
{
node = nullptr;
fileName = "";
}
XmlNodeRef node;
QString fileName;
};
static const char* const LIBRARY_TAG;
static const char* const LEVEL_LIBRARY_TAG;
void SerializeTo(std::vector<_smart_ptr<LibUndoNode> >& undos) // Save Library Undo
{
undos.clear();
for (int i = 0; i < m_pMngr->GetLibraryCount(); i++)
{
IDataBaseLibrary* library = m_pMngr->GetLibrary(i);
const char* tag = library->IsLevelLibrary() ? LEVEL_LIBRARY_TAG : LIBRARY_TAG;
XmlNodeRef node = GetIEditor()->GetSystem()->CreateXmlNode(tag);
QString file = library->GetFilename().isEmpty() ? library->GetFilename() : library->GetName();
library->Serialize(node, false);
if (node && !file.isEmpty())
{
_smart_ptr<LibUndoNode> undo = new LibUndoNode();
undo->fileName = file;
undo->node = node;
undos.push_back(undo);
}
}
}
void UnserializeFrom(std::vector<_smart_ptr<LibUndoNode> >& undos) // Load Library Undo
{
for (int i = 0; i < undos.size(); i++)
{
_smart_ptr<LibUndoNode> undo = undos[i];
if (undo->node && !undo->fileName.isEmpty())
{
//AddLibrary adds a .xml to the end of the library path, this will remove the extra for compatibility
undo->fileName.replace(m_pMngr->GetLibsPath().toLower(), "");
undo->fileName.replace(".xml", "");
const bool isLevelLibrary = (strcmp(undo->node->getTag(), LEVEL_LIBRARY_TAG) == 0);
IDataBaseLibrary* library = m_pMngr->AddLibrary(undo->fileName, isLevelLibrary);
library->Serialize(undo->node, true);
}
}
}
QString m_description;
QString m_editorObject;
CBaseLibraryManager* m_pMngr;
std::vector<_smart_ptr<LibUndoNode> > m_undos;
std::vector<_smart_ptr<LibUndoNode> > m_redos;
};
const char* const CUndoBaseLibraryManager::LIBRARY_TAG = "UndoLibrary";
const char* const CUndoBaseLibraryManager::LEVEL_LIBRARY_TAG = "UndoLevelLibrary";
//////////////////////////////////////////////////////////////////////////
// CBaseLibraryManager implementation.
//////////////////////////////////////////////////////////////////////////
CBaseLibraryManager::CBaseLibraryManager()
{
m_bUniqNameMap = false;
m_bUniqGuidMap = true;
GetIEditor()->RegisterNotifyListener(this);
}
//////////////////////////////////////////////////////////////////////////
CBaseLibraryManager::~CBaseLibraryManager()
{
ClearAll();
GetIEditor()->UnregisterNotifyListener(this);
}
//////////////////////////////////////////////////////////////////////////
void CBaseLibraryManager::ClearAll()
{
// Delete all items from all libraries.
for (int i = 0; i < m_libs.size(); i++)
{
m_libs[i]->RemoveAllItems();
}
// if we will not copy maps locally then destructors of the elements of
// the map will operate on the already invalid map object
// see:
// CBaseLibraryManager::UnregisterItem()
// CBaseLibraryManager::DeleteItem()
// CMaterial::~CMaterial()
ItemsGUIDMap itemsGuidMap;
ItemsNameMap itemsNameMap;
{
AZStd::lock_guard<AZStd::mutex> lock(m_itemsNameMapMutex);
std::swap(itemsGuidMap, m_itemsGuidMap);
std::swap(itemsNameMap, m_itemsNameMap);
m_libs.clear();
}
}
//////////////////////////////////////////////////////////////////////////
IDataBaseLibrary* CBaseLibraryManager::FindLibrary(const QString& library)
{
const int index = FindLibraryIndex(library);
return index == -1 ? nullptr : m_libs[index];
}
//////////////////////////////////////////////////////////////////////////
int CBaseLibraryManager::FindLibraryIndex(const QString& library)
{
QString lib = library;
lib.replace('\\', '/');
for (int i = 0; i < m_libs.size(); i++)
{
QString _lib = m_libs[i]->GetFilename();
_lib.replace('\\', '/');
if (QString::compare(lib, m_libs[i]->GetName(), Qt::CaseInsensitive) == 0 || QString::compare(lib, _lib, Qt::CaseInsensitive) == 0)
{
return i;
}
}
return -1;
}
//////////////////////////////////////////////////////////////////////////
IDataBaseItem* CBaseLibraryManager::FindItem(REFGUID guid) const
{
CBaseLibraryItem* pMtl = stl::find_in_map(m_itemsGuidMap, guid, (CBaseLibraryItem*)0);
return pMtl;
}
//////////////////////////////////////////////////////////////////////////
void CBaseLibraryManager::SplitFullItemName(const QString& fullItemName, QString& libraryName, QString& itemName)
{
int p;
p = fullItemName.indexOf('.');
if (p < 0 || !QString::compare(fullItemName.mid(p + 1), "mtl", Qt::CaseInsensitive))
{
libraryName = "";
itemName = fullItemName;
return;
}
libraryName = fullItemName.mid(0, p);
itemName = fullItemName.mid(p + 1);
}
//////////////////////////////////////////////////////////////////////////
IDataBaseItem* CBaseLibraryManager::FindItemByName(const QString& fullItemName)
{
AZStd::lock_guard<AZStd::mutex> lock(m_itemsNameMapMutex);
return stl::find_in_map(m_itemsNameMap, fullItemName, 0);
}
//////////////////////////////////////////////////////////////////////////
IDataBaseItem* CBaseLibraryManager::LoadItemByName(const QString& fullItemName)
{
QString libraryName, itemName;
SplitFullItemName(fullItemName, libraryName, itemName);
if (!FindLibrary(libraryName))
{
LoadLibrary(MakeFilename(libraryName));
}
return FindItemByName(fullItemName);
}
//////////////////////////////////////////////////////////////////////////
IDataBaseItem* CBaseLibraryManager::FindItemByName(const char* fullItemName)
{
return FindItemByName(QString(fullItemName));
}
//////////////////////////////////////////////////////////////////////////
IDataBaseItem* CBaseLibraryManager::LoadItemByName(const char* fullItemName)
{
return LoadItemByName(QString(fullItemName));
}
//////////////////////////////////////////////////////////////////////////
IDataBaseItem* CBaseLibraryManager::CreateItem(IDataBaseLibrary* pLibrary)
{
assert(pLibrary);
// Add item to this library.
TSmartPtr<CBaseLibraryItem> pItem = MakeNewItem();
pLibrary->AddItem(pItem);
return pItem;
}
//////////////////////////////////////////////////////////////////////////
void CBaseLibraryManager::DeleteItem(IDataBaseItem* pItem)
{
assert(pItem);
UnregisterItem((CBaseLibraryItem*)pItem);
if (pItem->GetLibrary())
{
pItem->GetLibrary()->RemoveItem(pItem);
}
}
//////////////////////////////////////////////////////////////////////////
IDataBaseLibrary* CBaseLibraryManager::LoadLibrary(const QString& inFilename, [[maybe_unused]] bool bReload)
{
if (auto lib = FindLibrary(inFilename))
{
return lib;
}
TSmartPtr<CBaseLibrary> pLib = MakeNewLibrary();
if (!pLib->Load(MakeFilename(inFilename)))
{
Error(QObject::tr("Failed to Load Item Library: %1").arg(inFilename).toUtf8().data());
return nullptr;
}
m_libs.push_back(pLib);
return pLib;
}
//////////////////////////////////////////////////////////////////////////
int CBaseLibraryManager::GetModifiedLibraryCount() const
{
int count = 0;
for (int i = 0; i < m_libs.size(); i++)
{
if (m_libs[i]->IsModified())
{
count++;
}
}
return count;
}
//////////////////////////////////////////////////////////////////////////
IDataBaseLibrary* CBaseLibraryManager::AddLibrary(const QString& library, bool bIsLevelLibrary, bool bIsLoading)
{
// Make a filename from name of library.
QString filename = library;
if (filename.indexOf(".xml") == -1) // if its already a filename, we don't do anything
{
filename.replace(' ', '_');
if (!bIsLevelLibrary)
{
filename = MakeFilename(library);
}
else
{
// if its the level library it gets saved in the level and should not be concatenated with any other file name
filename = filename + ".xml";
}
}
IDataBaseLibrary* pBaseLib = FindLibrary(library); //library name
if (!pBaseLib)
{
pBaseLib = FindLibrary(filename); //library file name
}
if (pBaseLib)
{
return pBaseLib;
}
CBaseLibrary* lib = MakeNewLibrary();
lib->SetName(library);
lib->SetLevelLibrary(bIsLevelLibrary);
lib->SetFilename(filename, !bIsLoading);
// set modified to true, so even empty particle libraries get saved
lib->SetModified(true);
m_libs.push_back(lib);
return lib;
}
//////////////////////////////////////////////////////////////////////////
QString CBaseLibraryManager::MakeFilename(const QString& library)
{
QString filename = library;
filename.replace(' ', '_');
filename.replace(".xml", "");
// make it contain the canonical libs path:
Path::ConvertBackSlashToSlash(filename);
QString LibsPath(GetLibsPath());
Path::ConvertBackSlashToSlash(LibsPath);
if (filename.left(LibsPath.length()).compare(LibsPath, Qt::CaseInsensitive) == 0)
{
filename = filename.mid(LibsPath.length());
}
return LibsPath + filename + ".xml";
}
//////////////////////////////////////////////////////////////////////////
bool CBaseLibraryManager::IsUniqueFilename(const QString& library)
{
QString resultPath = MakeFilename(library);
CCryFile xmlFile;
// If we can find a file for the path
return !xmlFile.Open(resultPath.toUtf8().data(), "rb");
}
//////////////////////////////////////////////////////////////////////////
void CBaseLibraryManager::DeleteLibrary(const QString& library, bool forceDeleteLevel)
{
for (int i = 0; i < m_libs.size(); i++)
{
if (QString::compare(library, m_libs[i]->GetName(), Qt::CaseInsensitive) == 0)
{
CBaseLibrary* pLibrary = m_libs[i];
// Check if not level library, they cannot be deleted.
if (!pLibrary->IsLevelLibrary() || forceDeleteLevel)
{
for (int j = 0; j < pLibrary->GetItemCount(); j++)
{
UnregisterItem((CBaseLibraryItem*)pLibrary->GetItem(j));
}
pLibrary->RemoveAllItems();
if (pLibrary->IsLevelLibrary())
{
m_pLevelLibrary = nullptr;
}
m_libs.erase(m_libs.begin() + i);
}
break;
}
}
}
//////////////////////////////////////////////////////////////////////////
IDataBaseLibrary* CBaseLibraryManager::GetLibrary(int index) const
{
assert(index >= 0 && index < m_libs.size());
return m_libs[index];
};
//////////////////////////////////////////////////////////////////////////
IDataBaseLibrary* CBaseLibraryManager::GetLevelLibrary() const
{
IDataBaseLibrary* pLevelLib = NULL;
for (int i = 0; i < GetLibraryCount(); i++)
{
if (GetLibrary(i)->IsLevelLibrary())
{
pLevelLib = GetLibrary(i);
break;
}
}
return pLevelLib;
}
//////////////////////////////////////////////////////////////////////////
void CBaseLibraryManager::SaveAllLibs()
{
for (int i = 0; i < GetLibraryCount(); i++)
{
// Check if library is modified.
IDataBaseLibrary* pLibrary = GetLibrary(i);
//Level library is saved when the level is saved
if (pLibrary->IsLevelLibrary())
{
continue;
}
if (pLibrary->IsModified())
{
if (pLibrary->Save())
{
pLibrary->SetModified(false);
}
}
}
}
//////////////////////////////////////////////////////////////////////////
void CBaseLibraryManager::Serialize(XmlNodeRef& node, bool bLoading)
{
static const char* const LEVEL_LIBRARY_TAG = "LevelLibrary";
QString rootNodeName = GetRootNodeName();
if (bLoading)
{
XmlNodeRef libs = node->findChild(rootNodeName.toUtf8().data());
if (libs)
{
for (int i = 0; i < libs->getChildCount(); i++)
{
// Load only library name.
XmlNodeRef libNode = libs->getChild(i);
if (strcmp(libNode->getTag(), LEVEL_LIBRARY_TAG) == 0)
{
if (!m_pLevelLibrary)
{
QString libName;
libNode->getAttr("Name", libName);
m_pLevelLibrary = static_cast<CBaseLibrary*>(AddLibrary(libName, true));
}
m_pLevelLibrary->Serialize(libNode, bLoading);
}
else
{
QString libName;
if (libNode->getAttr("Name", libName))
{
// Load this library.
if (!FindLibrary(libName))
{
LoadLibrary(MakeFilename(libName));
}
}
}
}
}
}
else
{
// Save all libraries.
XmlNodeRef libs = node->newChild(rootNodeName.toUtf8().data());
for (int i = 0; i < GetLibraryCount(); i++)
{
IDataBaseLibrary* pLib = GetLibrary(i);
if (pLib->IsLevelLibrary())
{
// Level libraries are saved in in level.
XmlNodeRef libNode = libs->newChild(LEVEL_LIBRARY_TAG);
pLib->Serialize(libNode, bLoading);
}
else
{
// Save only library name.
XmlNodeRef libNode = libs->newChild("Library");
libNode->setAttr("Name", pLib->GetName().toUtf8().data());
}
}
SaveAllLibs();
}
}
//////////////////////////////////////////////////////////////////////////
QString CBaseLibraryManager::MakeUniqueItemName(const QString& srcName, const QString& libName)
{
// unlikely we'll ever encounter more than 16
std::vector<string> possibleDuplicates;
possibleDuplicates.reserve(16);
// search for strings in the database that might have a similar name (ignore case)
IDataBaseItemEnumerator* pEnum = GetItemEnumerator();
for (IDataBaseItem* pItem = pEnum->GetFirst(); pItem != NULL; pItem = pEnum->GetNext())
{
//Check if the item is in the target library first.
IDataBaseLibrary* itemLibrary = pItem->GetLibrary();
QString itemLibraryName;
if (itemLibrary)
{
itemLibraryName = itemLibrary->GetName();
}
// Item is not in the library so there cannot be a naming conflict.
if (!libName.isEmpty() && !itemLibraryName.isEmpty() && itemLibraryName != libName)
{
continue;
}
const QString& name = pItem->GetName();
if (name.startsWith(srcName, Qt::CaseInsensitive))
{
possibleDuplicates.push_back(string(name.toUtf8().data()));
}
}
pEnum->Release();
if (possibleDuplicates.empty())
{
return srcName;
}
std::sort(possibleDuplicates.begin(), possibleDuplicates.end(), [](const string& strOne, const string& strTwo)
{
// I can assume size sorting since if the length is different, either one of the two strings doesn't
// closely match the string we are trying to duplicate, or it's a bigger number (X1 vs X10)
if (strOne.size() != strTwo.size())
{
return strOne.size() < strTwo.size();
}
else
{
return azstricmp(strOne.c_str(), strTwo.c_str()) < 0;
}
}
);
int num = 0;
QString returnValue = srcName;
while (num < possibleDuplicates.size() && QString::compare(possibleDuplicates[num].c_str(), returnValue, Qt::CaseInsensitive) == 0)
{
returnValue = QStringLiteral("%1%2%3").arg(srcName).arg("_").arg(num);
++num;
}
return returnValue;
}
//////////////////////////////////////////////////////////////////////////
void CBaseLibraryManager::Validate()
{
IDataBaseItemEnumerator* pEnum = GetItemEnumerator();
for (IDataBaseItem* pItem = pEnum->GetFirst(); pItem != NULL; pItem = pEnum->GetNext())
{
pItem->Validate();
}
pEnum->Release();
}
//////////////////////////////////////////////////////////////////////////
void CBaseLibraryManager::RegisterItem(CBaseLibraryItem* pItem, REFGUID newGuid)
{
assert(pItem);
bool bNotify = false;
if (m_bUniqGuidMap)
{
bool bNewItem = true;
REFGUID oldGuid = pItem->GetGUID();
if (!GuidUtil::IsEmpty(oldGuid))
{
bNewItem = false;
m_itemsGuidMap.erase(oldGuid);
}
if (GuidUtil::IsEmpty(newGuid))
{
return;
}
CBaseLibraryItem* pOldItem = stl::find_in_map(m_itemsGuidMap, newGuid, (CBaseLibraryItem*)0);
if (!pOldItem)
{
pItem->m_guid = newGuid;
m_itemsGuidMap[newGuid] = pItem;
pItem->m_bRegistered = true;
bNotify = true;
}
else
{
if (pOldItem != pItem)
{
ReportDuplicateItem(pItem, pOldItem);
}
}
}
if (m_bUniqNameMap)
{
QString fullName = pItem->GetFullName();
if (!pItem->GetName().isEmpty())
{
CBaseLibraryItem* pOldItem = static_cast<CBaseLibraryItem*>(FindItemByName(fullName));
if (!pOldItem)
{
AZStd::lock_guard<AZStd::mutex> lock(m_itemsNameMapMutex);
m_itemsNameMap[fullName] = pItem;
pItem->m_bRegistered = true;
bNotify = true;
}
else
{
if (pOldItem != pItem)
{
ReportDuplicateItem(pItem, pOldItem);
}
}
}
}
// Notify listeners.
if (bNotify)
{
NotifyItemEvent(pItem, EDB_ITEM_EVENT_ADD);
}
}
//////////////////////////////////////////////////////////////////////////
void CBaseLibraryManager::RegisterItem(CBaseLibraryItem* pItem)
{
assert(pItem);
bool bNotify = false;
if (m_bUniqGuidMap)
{
if (GuidUtil::IsEmpty(pItem->GetGUID()))
{
return;
}
CBaseLibraryItem* pOldItem = stl::find_in_map(m_itemsGuidMap, pItem->GetGUID(), (CBaseLibraryItem*)0);
if (!pOldItem)
{
m_itemsGuidMap[pItem->GetGUID()] = pItem;
pItem->m_bRegistered = true;
bNotify = true;
}
else
{
if (pOldItem != pItem)
{
ReportDuplicateItem(pItem, pOldItem);
}
}
}
if (m_bUniqNameMap)
{
QString fullName = pItem->GetFullName();
if (!fullName.isEmpty())
{
CBaseLibraryItem* pOldItem = static_cast<CBaseLibraryItem*>(FindItemByName(fullName));
if (!pOldItem)
{
AZStd::lock_guard<AZStd::mutex> lock(m_itemsNameMapMutex);
m_itemsNameMap[fullName] = pItem;
pItem->m_bRegistered = true;
bNotify = true;
}
else
{
if (pOldItem != pItem)
{
ReportDuplicateItem(pItem, pOldItem);
}
}
}
}
// Notify listeners.
if (bNotify)
{
NotifyItemEvent(pItem, EDB_ITEM_EVENT_ADD);
}
}
//////////////////////////////////////////////////////////////////////////
void CBaseLibraryManager::SetRegisteredFlag(CBaseLibraryItem* pItem, bool bFlag)
{
pItem->m_bRegistered = bFlag;
}
//////////////////////////////////////////////////////////////////////////
void CBaseLibraryManager::ReportDuplicateItem(CBaseLibraryItem* pItem, CBaseLibraryItem* pOldItem)
{
QString sLibName;
if (pOldItem->GetLibrary())
{
sLibName = pOldItem->GetLibrary()->GetName();
}
CErrorRecord err;
err.pItem = pItem;
err.error = QStringLiteral("Item %1 with duplicate GUID to loaded item %2 ignored").arg(pItem->GetFullName(), pOldItem->GetFullName());
GetIEditor()->GetErrorReport()->ReportError(err);
}
//////////////////////////////////////////////////////////////////////////
void CBaseLibraryManager::UnregisterItem(CBaseLibraryItem* pItem)
{
// Notify listeners.
NotifyItemEvent(pItem, EDB_ITEM_EVENT_DELETE);
if (!pItem)
{
return;
}
if (m_bUniqGuidMap)
{
m_itemsGuidMap.erase(pItem->GetGUID());
}
if (m_bUniqNameMap && !pItem->GetFullName().isEmpty())
{
AZStd::lock_guard<AZStd::mutex> lock(m_itemsNameMapMutex);
auto findIter = m_itemsNameMap.find(pItem->GetFullName());
if (findIter != m_itemsNameMap.end())
{
_smart_ptr<CBaseLibraryItem> item = findIter->second;
m_itemsNameMap.erase(findIter);
}
}
pItem->m_bRegistered = false;
}
//////////////////////////////////////////////////////////////////////////
QString CBaseLibraryManager::MakeFullItemName(IDataBaseLibrary* pLibrary, const QString& group, const QString& itemName)
{
assert(pLibrary);
QString name = pLibrary->GetName() + ".";
if (!group.isEmpty())
{
name += group + ".";
}
name += itemName;
return name;
}
//////////////////////////////////////////////////////////////////////////
void CBaseLibraryManager::GatherUsedResources(CUsedResources& resources)
{
IDataBaseItemEnumerator* pEnum = GetItemEnumerator();
for (IDataBaseItem* pItem = pEnum->GetFirst(); pItem != NULL; pItem = pEnum->GetNext())
{
pItem->GatherUsedResources(resources);
}
pEnum->Release();
}
//////////////////////////////////////////////////////////////////////////
IDataBaseItemEnumerator* CBaseLibraryManager::GetItemEnumerator()
{
if (m_bUniqNameMap)
{
return new CDataBaseItemEnumerator<ItemsNameMap>(&m_itemsNameMap);
}
else
{
return new CDataBaseItemEnumerator<ItemsGUIDMap>(&m_itemsGuidMap);
}
}
//////////////////////////////////////////////////////////////////////////
void CBaseLibraryManager::OnEditorNotifyEvent(EEditorNotifyEvent event)
{
switch (event)
{
case eNotify_OnBeginNewScene:
SetSelectedItem(0);
ClearAll();
break;
case eNotify_OnBeginSceneOpen:
SetSelectedItem(0);
ClearAll();
break;
case eNotify_OnCloseScene:
SetSelectedItem(0);
ClearAll();
break;
}
}
//////////////////////////////////////////////////////////////////////////
void CBaseLibraryManager::OnRenameItem(CBaseLibraryItem* pItem, const QString& oldName)
{
m_itemsNameMapMutex.lock();
if (!oldName.isEmpty())
{
m_itemsNameMap.erase(oldName);
}
if (!pItem->GetFullName().isEmpty())
{
m_itemsNameMap[pItem->GetFullName()] = pItem;
}
m_itemsNameMapMutex.unlock();
OnItemChanged(pItem);
}
//////////////////////////////////////////////////////////////////////////
void CBaseLibraryManager::AddListener(IDataBaseManagerListener* pListener)
{
stl::push_back_unique(m_listeners, pListener);
}
//////////////////////////////////////////////////////////////////////////
void CBaseLibraryManager::RemoveListener(IDataBaseManagerListener* pListener)
{
stl::find_and_erase(m_listeners, pListener);
}
//////////////////////////////////////////////////////////////////////////
void CBaseLibraryManager::NotifyItemEvent(IDataBaseItem* pItem, EDataBaseItemEvent event)
{
// Notify listeners.
if (!m_listeners.empty())
{
for (int i = 0; i < m_listeners.size(); i++)
{
m_listeners[i]->OnDataBaseItemEvent(pItem, event);
}
}
}
//////////////////////////////////////////////////////////////////////////
void CBaseLibraryManager::OnItemChanged(IDataBaseItem* pItem)
{
NotifyItemEvent(pItem, EDB_ITEM_EVENT_CHANGED);
}
//////////////////////////////////////////////////////////////////////////
void CBaseLibraryManager::OnUpdateProperties(IDataBaseItem* pItem, bool bRefresh)
{
NotifyItemEvent(pItem, bRefresh ? EDB_ITEM_EVENT_UPDATE_PROPERTIES
: EDB_ITEM_EVENT_UPDATE_PROPERTIES_NO_EDITOR_REFRESH);
}
//////////////////////////////////////////////////////////////////////////
void CBaseLibraryManager::SetSelectedItem(IDataBaseItem* pItem)
{
if (m_pSelectedItem == pItem)
{
return;
}
m_pSelectedItem = (CBaseLibraryItem*)pItem;
NotifyItemEvent(m_pSelectedItem, EDB_ITEM_EVENT_SELECTED);
}
//////////////////////////////////////////////////////////////////////////
IDataBaseItem* CBaseLibraryManager::GetSelectedItem() const
{
return m_pSelectedItem;
}
//////////////////////////////////////////////////////////////////////////
IDataBaseItem* CBaseLibraryManager::GetSelectedParentItem() const
{
return m_pSelectedParent;
}
void CBaseLibraryManager::ChangeLibraryOrder(IDataBaseLibrary* lib, unsigned int newLocation)
{
if (!lib || newLocation >= m_libs.size() || lib == m_libs[newLocation])
{
return;
}
for (int i = 0; i < m_libs.size(); i++)
{
if (lib == m_libs[i])
{
_smart_ptr<CBaseLibrary> curLib = m_libs[i];
m_libs.erase(m_libs.begin() + i);
m_libs.insert(m_libs.begin() + newLocation, curLib);
return;
}
}
}
bool CBaseLibraryManager::SetLibraryName(CBaseLibrary* lib, const QString& name)
{
// SetFilename will validate if the name is duplicate with exist libraries.
if (lib->SetFilename(MakeFilename(name)))
{
lib->SetName(name);
return true;
}
return false;
}
+225
View File
@@ -0,0 +1,225 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef CRYINCLUDE_EDITOR_BASELIBRARYMANAGER_H
#define CRYINCLUDE_EDITOR_BASELIBRARYMANAGER_H
#pragma once
#include "Include/IBaseLibraryManager.h"
#include "Include/IDataBaseItem.h"
#include "Include/IDataBaseLibrary.h"
#include "Include/IDataBaseManager.h"
#include "Util/TRefCountBase.h"
#include "Util/GuidUtil.h"
#include "BaseLibrary.h"
#include "Util/smartptr.h"
#include <EditorDefs.h>
#include <QtUtil.h>
AZ_PUSH_DISABLE_DLL_EXPORT_BASECLASS_WARNING
/** Manages all Libraries and Items.
*/
class SANDBOX_API CBaseLibraryManager
: public IBaseLibraryManager
{
AZ_POP_DISABLE_DLL_EXPORT_BASECLASS_WARNING
public:
CBaseLibraryManager();
~CBaseLibraryManager();
//! Clear all libraries.
virtual void ClearAll() override;
//////////////////////////////////////////////////////////////////////////
// IDocListener implementation.
//////////////////////////////////////////////////////////////////////////
virtual void OnEditorNotifyEvent(EEditorNotifyEvent event) override;
//////////////////////////////////////////////////////////////////////////
// Library items.
//////////////////////////////////////////////////////////////////////////
//! Make a new item in specified library.
virtual IDataBaseItem* CreateItem(IDataBaseLibrary* pLibrary) override;
//! Delete item from library and manager.
virtual void DeleteItem(IDataBaseItem* pItem) override;
//! Find Item by its GUID.
virtual IDataBaseItem* FindItem(REFGUID guid) const;
virtual IDataBaseItem* FindItemByName(const QString& fullItemName);
virtual IDataBaseItem* LoadItemByName(const QString& fullItemName);
virtual IDataBaseItem* FindItemByName(const char* fullItemName);
virtual IDataBaseItem* LoadItemByName(const char* fullItemName);
virtual IDataBaseItemEnumerator* GetItemEnumerator() override;
//////////////////////////////////////////////////////////////////////////
// Set item currently selected.
virtual void SetSelectedItem(IDataBaseItem* pItem) override;
// Get currently selected item.
virtual IDataBaseItem* GetSelectedItem() const override;
virtual IDataBaseItem* GetSelectedParentItem() const override;
//////////////////////////////////////////////////////////////////////////
// Libraries.
//////////////////////////////////////////////////////////////////////////
//! Add Item library.
virtual IDataBaseLibrary* AddLibrary(const QString& library, bool bIsLevelLibrary = false, bool bIsLoading = true) override;
virtual void DeleteLibrary(const QString& library, bool forceDeleteLevel = false) override;
//! Get number of libraries.
virtual int GetLibraryCount() const override { return m_libs.size(); };
//! Get number of modified libraries.
virtual int GetModifiedLibraryCount() const override;
//! Get Item library by index.
virtual IDataBaseLibrary* GetLibrary(int index) const override;
//! Get Level Item library.
virtual IDataBaseLibrary* GetLevelLibrary() const override;
//! Find Items Library by name.
virtual IDataBaseLibrary* FindLibrary(const QString& library) override;
//! Find Items Library's index by name.
int FindLibraryIndex(const QString& library) override;
//! Load Items library.
virtual IDataBaseLibrary* LoadLibrary(const QString& filename, bool bReload = false) override;
//! Save all modified libraries.
virtual void SaveAllLibs() override;
//! Serialize property manager.
virtual void Serialize(XmlNodeRef& node, bool bLoading) override;
//! Export items to game.
virtual void Export([[maybe_unused]] XmlNodeRef& node) override {};
//! Returns unique name base on input name.
virtual QString MakeUniqueItemName(const QString& name, const QString& libName = "") override;
virtual QString MakeFullItemName(IDataBaseLibrary* pLibrary, const QString& group, const QString& itemName) override;
//! Root node where this library will be saved.
virtual QString GetRootNodeName() override = 0;
//! Path to libraries in this manager.
virtual QString GetLibsPath() override = 0;
//////////////////////////////////////////////////////////////////////////
//! Validate library items for errors.
virtual void Validate() override;
//////////////////////////////////////////////////////////////////////////
virtual void GatherUsedResources(CUsedResources& resources) override;
virtual void AddListener(IDataBaseManagerListener* pListener) override;
virtual void RemoveListener(IDataBaseManagerListener* pListener) override;
//////////////////////////////////////////////////////////////////////////
virtual void RegisterItem(CBaseLibraryItem* pItem, REFGUID newGuid) override;
virtual void RegisterItem(CBaseLibraryItem* pItem) override;
virtual void UnregisterItem(CBaseLibraryItem* pItem) override;
// Only Used internally.
virtual void OnRenameItem(CBaseLibraryItem* pItem, const QString& oldName) override;
// Called by items to indicated that they have been modified.
// Sends item changed event to listeners.
virtual void OnItemChanged(IDataBaseItem* pItem) override;
virtual void OnUpdateProperties(IDataBaseItem* pItem, bool bRefresh) override;
QString MakeFilename(const QString& library);
virtual bool IsUniqueFilename(const QString& library) override;
//CONFETTI BEGIN
// Used to change the library item order
virtual void ChangeLibraryOrder(IDataBaseLibrary* lib, unsigned int newLocation) override;
virtual bool SetLibraryName(CBaseLibrary* lib, const QString& name) override;
protected:
void SplitFullItemName(const QString& fullItemName, QString& libraryName, QString& itemName);
void NotifyItemEvent(IDataBaseItem* pItem, EDataBaseItemEvent event);
void SetRegisteredFlag(CBaseLibraryItem* pItem, bool bFlag);
//////////////////////////////////////////////////////////////////////////
// Must be overriden.
//! Makes a new Item.
virtual CBaseLibraryItem* MakeNewItem() = 0;
virtual CBaseLibrary* MakeNewLibrary() = 0;
//////////////////////////////////////////////////////////////////////////
virtual void ReportDuplicateItem(CBaseLibraryItem* pItem, CBaseLibraryItem* pOldItem);
protected:
bool m_bUniqGuidMap;
bool m_bUniqNameMap;
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
//! Array of all loaded entity items libraries.
std::vector<_smart_ptr<CBaseLibrary> > m_libs;
// There is always one current level library.
TSmartPtr<CBaseLibrary> m_pLevelLibrary;
// GUID to item map.
typedef std::map<GUID, _smart_ptr<CBaseLibraryItem>, guid_less_predicate> ItemsGUIDMap;
ItemsGUIDMap m_itemsGuidMap;
// Case insensitive name to items map.
typedef std::map<QString, _smart_ptr<CBaseLibraryItem>, stl::less_stricmp<QString>> ItemsNameMap;
ItemsNameMap m_itemsNameMap;
AZStd::mutex m_itemsNameMapMutex;
std::vector<IDataBaseManagerListener*> m_listeners;
// Currently selected item.
_smart_ptr<CBaseLibraryItem> m_pSelectedItem;
_smart_ptr<CBaseLibraryItem> m_pSelectedParent;
AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
};
//////////////////////////////////////////////////////////////////////////
template <class TMap>
class CDataBaseItemEnumerator
: public IDataBaseItemEnumerator
{
TMap* m_pMap;
typename TMap::iterator m_iterator;
public:
CDataBaseItemEnumerator(TMap* pMap)
{
assert(pMap);
m_pMap = pMap;
m_iterator = m_pMap->begin();
}
virtual void Release() { delete this; };
virtual IDataBaseItem* GetFirst()
{
m_iterator = m_pMap->begin();
if (m_iterator == m_pMap->end())
{
return 0;
}
return m_iterator->second;
}
virtual IDataBaseItem* GetNext()
{
if (m_iterator != m_pMap->end())
{
m_iterator++;
}
if (m_iterator == m_pMap->end())
{
return 0;
}
return m_iterator->second;
}
};
#endif // CRYINCLUDE_EDITOR_BASELIBRARYMANAGER_H
+247
View File
@@ -0,0 +1,247 @@
#
# Copyright (c) Contributors to the Open 3D Engine Project
#
# SPDX-License-Identifier: Apache-2.0 OR MIT
#
#
if(NOT PAL_TRAIT_BUILD_HOST_TOOLS)
return()
endif()
ly_add_target(
NAME EditorCore SHARED
NAMESPACE Legacy
AUTOMOC
AUTOUIC
FILES_CMAKE
editor_core_files.cmake
Platform/${PAL_PLATFORM_NAME}/editor_core_files_${PAL_PLATFORM_NAME_LOWERCASE}.cmake
INCLUDE_DIRECTORIES
PUBLIC
Include
.
..
COMPILE_DEFINITIONS
PRIVATE
EDITOR_CORE
PUBLIC
EDITOR
BUILD_DEPENDENCIES
PRIVATE
Legacy::CryCommon
3rdParty::zlib
PUBLIC
3rdParty::Qt::Core
3rdParty::Qt::Gui
3rdParty::Qt::Widgets
AZ::AzToolsFramework
)
# Header only target to prevent linkage against editor libraries when is not needed. Eventually the targets that depend
# on editor headers should cleanup dependencies and interact with the editor through buses or other mechanisms
ly_add_target(
NAME Editor.Headers HEADERONLY
NAMESPACE Legacy
FILES_CMAKE
editor_headers_files.cmake
INCLUDE_DIRECTORIES
INTERFACE
Include
.
..
BUILD_DEPENDENCIES
INTERFACE
Legacy::EditorCommon.Headers
)
################################################################################
# EditorLib
################################################################################
set(pal_cmake_files "")
foreach(enabled_platform ${LY_PAL_TOOLS_ENABLED})
string(TOLOWER ${enabled_platform} enabled_platform_lowercase)
ly_get_list_relative_pal_filename(pal_cmake_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${enabled_platform})
list(APPEND pal_cmake_files ${pal_cmake_dir}/editor_lib_${enabled_platform_lowercase}_files.cmake)
endforeach()
ly_get_pal_tool_dirs(pal_tool_dirs ${CMAKE_CURRENT_LIST_DIR}/Platform)
ly_add_target(
NAME EditorLib SHARED
NAMESPACE Legacy
AUTOMOC
AUTOUIC
AUTORCC
FILES_CMAKE
editor_lib_files.cmake
Platform/${PAL_PLATFORM_NAME}/platform_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake
PLATFORM_INCLUDE_FILES
Platform/${PAL_PLATFORM_NAME}/editor_lib_${PAL_PLATFORM_NAME_LOWERCASE}.cmake
Platform/Common/${PAL_TRAIT_COMPILER_ID}/editor_lib_${PAL_TRAIT_COMPILER_ID_LOWERCASE}.cmake
${pal_cmake_files}
INCLUDE_DIRECTORIES
PUBLIC
Include
PRIVATE
.
..
AssetBrowser/AssetTypes/Character
${pal_tool_dirs}
COMPILE_DEFINITIONS
PRIVATE
SANDBOX_EXPORTS
${LY_LEGACY_TERRAIN_EDITOR_DEFINES}
INTERFACE
SANDBOX_IMPORTS
BUILD_DEPENDENCIES
PRIVATE
3rdParty::Qt::Core
3rdParty::Qt::Gui
3rdParty::Qt::Widgets
3rdParty::Qt::Concurrent
3rdParty::tiff
3rdParty::squish-ccr
3rdParty::zlib
3rdParty::AWSNativeSDK::STS
Legacy::CryCommon
Legacy::EditorCommon
AZ::AzCore
AZ::AzToolsFramework
Gem::LmbrCentral.Static
Legacy::NewsShared
AZ::AWSNativeSDKInit
AZ::AtomCore
Gem::Atom_RPI.Edit
Gem::Atom_RPI.Public
Gem::Atom_Feature_Common.Static
Gem::AtomToolsFramework.Static
Gem::AtomViewportDisplayInfo
${additional_dependencies}
PUBLIC
3rdParty::AWSNativeSDK::Core
3rdParty::Qt::Network
Legacy::EditorCore
RUNTIME_DEPENDENCIES
Gem::AtomViewportDisplayInfo
Legacy::EditorCommon
)
ly_add_source_properties(
SOURCES CryEdit.cpp
PROPERTY COMPILE_DEFINITIONS
VALUES
O3DE_COPYRIGHT_YEAR=${LY_VERSION_COPYRIGHT_YEAR}
LY_BUILD=${LY_VERSION_BUILD_NUMBER}
${LY_PAL_TOOLS_DEFINES}
)
ly_add_source_properties(
SOURCES
Core/LevelEditorMenuHandler.cpp
GraphicsSettingsDialog.cpp
MainWindow.cpp
PROPERTY COMPILE_DEFINITIONS
VALUES ${LY_PAL_TOOLS_DEFINES}
)
get_property(editor_plugins GLOBAL PROPERTY LY_EDITOR_PLUGINS)
string (REPLACE ";" "," editor_plugins "${editor_plugins}")
ly_add_source_properties(
SOURCES PluginManager.cpp
PROPERTY COMPILE_DEFINITIONS
VALUES LY_EDITOR_PLUGINS="${editor_plugins}"
)
################################################################################
# Editor
################################################################################
ly_add_target(
NAME Editor APPLICATION
NAMESPACE Legacy
AUTORCC
FILES_CMAKE
editor_files.cmake
PLATFORM_INCLUDE_FILES
Platform/${PAL_PLATFORM_NAME}/editor_${PAL_PLATFORM_NAME_LOWERCASE}.cmake
BUILD_DEPENDENCIES
PRIVATE
3rdParty::Qt::Core
Legacy::CryCommon
RUNTIME_DEPENDENCIES
Legacy::CrySystem
Legacy::EditorLib
ProjectManager
)
set_property(SOURCE
CryEdit.cpp
APPEND PROPERTY
COMPILE_DEFINITIONS LY_CMAKE_TARGET="Editor"
)
ly_add_translations(
TARGETS Editor
PREFIX Translations
FILES
Translations/editor_en-us.ts
Translations/assetbrowser_en-us.ts
)
ly_add_dependencies(Editor AssetProcessor)
if(LY_DEFAULT_PROJECT_PATH)
set_property(TARGET Editor APPEND PROPERTY VS_DEBUGGER_COMMAND_ARGUMENTS "--project-path=\"${LY_DEFAULT_PROJECT_PATH}\"")
endif()
################################################################################
# Tests
################################################################################
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
ly_add_target(
NAME EditorCore.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
NAMESPACE Legacy
AUTOMOC
FILES_CMAKE
editor_core_test_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
.
BUILD_DEPENDENCIES
PRIVATE
AZ::AzTest
3rdParty::Qt::Gui
3rdParty::Qt::Widgets
Legacy::EditorCore
Legacy::CryCommon
AZ::AzCore
)
ly_add_googletest(
NAME Legacy::EditorCore.Tests
)
ly_add_target(
NAME EditorLib.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
NAMESPACE Legacy
AUTOMOC
FILES_CMAKE
editor_lib_test_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
Include
.
COMPILE_DEFINITIONS
PRIVATE
${LY_LEGACY_TERRAIN_EDITOR_DEFINES}
BUILD_DEPENDENCIES
PRIVATE
AZ::AzTest
3rdParty::Qt::Core
3rdParty::Qt::Gui
3rdParty::Qt::Widgets
Legacy::CryCommon
AZ::AzToolsFramework
Legacy::EditorLib
RUNTIME_DEPENDENCIES
Gem::LmbrCentral
)
ly_add_googletest(
NAME Legacy::EditorLib.Tests
)
endif()
+185
View File
@@ -0,0 +1,185 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "CVarMenu.h"
CVarMenu::CVarMenu(QWidget* parent)
: QMenu(parent)
{
}
void CVarMenu::AddCVarToggleItem(CVarToggle cVarToggle)
{
// Add CVar toggle action
QAction* action = addAction(cVarToggle.m_displayName);
connect(action, &QAction::triggered, [this, cVarToggle](bool checked)
{
// Update the CVar's value based on the action's new checked state
ICVar* cVar = gEnv->pConsole->GetCVar(cVarToggle.m_cVarName.toUtf8().data());
if (cVar)
{
SetCVar(cVar, checked ? cVarToggle.m_onValue : cVarToggle.m_offValue);
}
});
action->setCheckable(true);
// Initialize the action's checked state based on the associated CVar's value
ICVar* cVar = gEnv->pConsole->GetCVar(cVarToggle.m_cVarName.toUtf8().data());
bool checked = (cVar && cVar->GetFVal() == cVarToggle.m_onValue);
action->setChecked(checked);
}
void CVarMenu::AddCVarValuesItem(QString cVarName,
QString displayName,
CVarDisplayNameValuePairs availableCVarValues,
float offValue)
{
// Add a submenu offering multiple values for one CVar
QMenu* menu = addMenu(displayName);
QActionGroup* group = new QActionGroup(menu);
group->setExclusive(true);
ICVar* cVar = gEnv->pConsole->GetCVar(cVarName.toUtf8().data());
float cVarValue = cVar ? cVar->GetFVal() : 0.0f;
for (const auto& availableCVarValue : availableCVarValues)
{
QAction* action = menu->addAction(availableCVarValue.first);
action->setCheckable(true);
group->addAction(action);
float availableOnValue = availableCVarValue.second;
connect(action, &QAction::triggered, [this, action, cVarName, availableOnValue, offValue](bool checked)
{
ICVar* cVar = gEnv->pConsole->GetCVar(cVarName.toUtf8().data());
if (cVar)
{
if (!checked)
{
SetCVar(cVar, offValue);
}
else
{
// Toggle the CVar and update the action's checked state to
// allow none of the items to be checked in the exclusive group.
// Otherwise we could have just used the action's currently checked
// state and updated the CVar's value only
bool cVarOn = (cVar->GetFVal() == availableOnValue);
checked = !cVarOn;
SetCVar(cVar, checked ? availableOnValue : offValue);
action->setChecked(checked);
}
}
});
// Initialize the action's checked state based on the CVar's current value
bool checked = (cVarValue == availableOnValue);
action->setChecked(checked);
}
}
void CVarMenu::AddUniqueCVarsItem(QString displayName,
AZStd::vector<CVarToggle> availableCVars)
{
// Add a submenu of actions offering values for unique CVars
QMenu* menu = addMenu(displayName);
QActionGroup* group = new QActionGroup(menu);
group->setExclusive(true);
for (const CVarToggle& availableCVar : availableCVars)
{
QAction* action = menu->addAction(availableCVar.m_displayName);
action->setCheckable(true);
group->addAction(action);
connect(action, &QAction::triggered, [this, action, availableCVar, availableCVars](bool checked)
{
ICVar* cVar = gEnv->pConsole->GetCVar(availableCVar.m_cVarName.toUtf8().data());
if (cVar)
{
if (!checked)
{
SetCVar(cVar, availableCVar.m_offValue);
}
else
{
// Toggle the CVar and update the action's checked state to
// allow none of the items to be checked in the exclusive group.
// Otherwise we could have just used the action's currently checked
// state and updated the CVar's value only
bool cVarOn = (cVar->GetFVal() == availableCVar.m_onValue);
bool cVarChecked = !cVarOn;
SetCVar(cVar, cVarChecked ? availableCVar.m_onValue : availableCVar.m_offValue);
action->setChecked(cVarChecked);
if (cVarChecked)
{
// Set the rest of the CVars in the group to their off values
SetCVarsToOffValue(availableCVars, availableCVar);
}
}
}
});
// Initialize the action's checked state based on its associated CVar's current value
ICVar* cVar = gEnv->pConsole->GetCVar(availableCVar.m_cVarName.toUtf8().data());
bool cVarChecked = (cVar && cVar->GetFVal() == availableCVar.m_onValue);
action->setChecked(cVarChecked);
if (cVarChecked)
{
// Set the rest of the CVars in the group to their off values
SetCVarsToOffValue(availableCVars, availableCVar);
}
}
}
void CVarMenu::AddResetCVarsItem()
{
QAction* action = addAction(tr("Reset to Default"));
connect(action, &QAction::triggered, this, [this]()
{
for (auto it : m_originalCVarValues)
{
ICVar* cVar = gEnv->pConsole->GetCVar(it.first.c_str());
if (cVar)
{
cVar->Set(it.second);
}
}
});
}
void CVarMenu::SetCVarsToOffValue(const AZStd::vector<CVarToggle>& cVarToggles, const CVarToggle& excludeCVarToggle)
{
// Set all but the specified CVars to their off values
for (const CVarToggle& cVarToggle : cVarToggles)
{
if (cVarToggle.m_cVarName != excludeCVarToggle.m_cVarName
|| cVarToggle.m_onValue != excludeCVarToggle.m_onValue)
{
ICVar* cVar = gEnv->pConsole->GetCVar(cVarToggle.m_cVarName.toUtf8().data());
if (cVar)
{
SetCVar(cVar, cVarToggle.m_offValue);
}
}
}
}
void CVarMenu::SetCVar(ICVar* cVar, float newValue)
{
float oldValue = cVar->GetFVal();
cVar->Set(newValue);
// Store original value for CVar if not already in the list
m_originalCVarValues.emplace(AZStd::string(cVar->GetName()), oldValue);
}
void CVarMenu::AddSeparator()
{
addSeparator();
}
+61
View File
@@ -0,0 +1,61 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <QMenu>
#include <QString>
#include <AzCore/std/string/string.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/utils.h>
class CVarMenu
: public QMenu
{
public:
// CVar that can be toggled on and off
struct CVarToggle
{
QString m_cVarName;
QString m_displayName;
float m_onValue;
float m_offValue;
};
// List of a CVar's available values and their descriptions
using CVarDisplayNameValuePairs = AZStd::vector<AZStd::pair<QString, float>>;
CVarMenu(QWidget* parent = nullptr);
// Add an action that turns a CVar on/off
void AddCVarToggleItem(CVarToggle cVarToggle);
// Add a submenu of actions for a CVar that offers multiple values for exclusive selection
void AddCVarValuesItem(QString cVarName,
QString displayName,
CVarDisplayNameValuePairs availableCVarValues,
float offValue);
// Add a submenu of actions for exclusively turning unique CVars on/off
void AddUniqueCVarsItem(QString displayName,
AZStd::vector<CVarToggle> availableCVars);
// Add an action to reset all CVars to their original values before they
// were modified by this menu
void AddResetCVarsItem();
void AddSeparator();
private:
void SetCVarsToOffValue(const AZStd::vector<CVarToggle>& cVarToggles, const CVarToggle& excludeCVarToggle);
void SetCVar(ICVar* cVar, float newValue);
// Original CVar values before they were modified by this menu
AZStd::unordered_map<AZStd::string, float> m_originalCVarValues;
};
+134
View File
@@ -0,0 +1,134 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "CheckOutDialog.h"
// Qt
#include <QStyle>
// AzToolsFramework
#include <AzToolsFramework/SourceControl/SourceControlAPI.h> // for AzToolsFramework::SourceControlConnectionRequestBus
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
#include "ui_CheckOutDialog.h"
AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
// CCheckOutDialog dialog
int CCheckOutDialog::m_lastResult = CCheckOutDialog::CANCEL;
CCheckOutDialog::CCheckOutDialog(const QString& file, QWidget* pParent)
: QDialog(pParent)
, m_ui(new Ui::CheckOutDialog)
{
m_ui->setupUi(this);
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
m_file = file;
m_ui->icon->setPixmap(style()->standardIcon(QStyle::SP_MessageBoxQuestion).pixmap(m_ui->icon->width()));
OnInitDialog();
connect(m_ui->buttonCancel, &QPushButton::clicked, this, &CCheckOutDialog::OnBnClickedCancel);
connect(m_ui->buttonCheckout, &QPushButton::clicked, this, &CCheckOutDialog::OnBnClickedCheckout);
connect(m_ui->buttonOverwrite, &QPushButton::clicked, this, &CCheckOutDialog::OnBnClickedOverwrite);
}
CCheckOutDialog::~CCheckOutDialog()
{
}
//////////////////////////////////////////////////////////////////////////
void CCheckOutDialog::OnBnClickedCancel()
{
// Cancel operation
HandleResult(CANCEL);
}
//////////////////////////////////////////////////////////////////////////
// CCheckOutDialog message handlers
void CCheckOutDialog::OnBnClickedCheckout()
{
// Check out this file.
HandleResult(CHECKOUT);
}
//////////////////////////////////////////////////////////////////////////
void CCheckOutDialog::OnBnClickedOverwrite()
{
// Overwrite this file.
HandleResult(OVERWRITE);
}
//////////////////////////////////////////////////////////////////////////
void CCheckOutDialog::HandleResult(int result)
{
m_lastResult = result;
InstanceIsForAll() = m_ui->chkForAll->isChecked();
done(result);
}
//////////////////////////////////////////////////////////////////////////
void CCheckOutDialog::OnInitDialog()
{
setWindowTitle(tr("Source Control"));
using namespace AzToolsFramework;
SourceControlState state = SourceControlState::Disabled;
SourceControlConnectionRequestBus::BroadcastResult(state, &SourceControlConnectionRequestBus::Events::GetSourceControlState);
bool sccAvailable = state == SourceControlState::Active ? true : false;
QString text(tr("%1\n\nis read-only, and needs to be writable to continue.").arg(m_file));
if (!sccAvailable)
{
text.append("\nEnable and connect to source control for more options.");
}
m_ui->m_text->setText(text);
m_ui->chkForAll->setEnabled(InstanceEnableForAll());
m_ui->chkForAll->setChecked(InstanceIsForAll());
m_ui->buttonCheckout->setEnabled(sccAvailable);
adjustSize();
}
//static ////////////////////////////////////////////////////////////////
bool& CCheckOutDialog::InstanceEnableForAll()
{
static bool isEnableForAll = false;
return isEnableForAll;
}
//static ////////////////////////////////////////////////////////////////
bool& CCheckOutDialog::InstanceIsForAll()
{
static bool isForAll = false;
return isForAll;
}
//static ////////////////////////////////////////////////////////////////
bool CCheckOutDialog::EnableForAll(bool isEnable)
{
bool bPrevEnable = InstanceEnableForAll();
InstanceEnableForAll() = isEnable;
if (!bPrevEnable || !isEnable)
{
InstanceIsForAll() = false;
m_lastResult = CANCEL;
}
return bPrevEnable;
}
#include <moc_CheckOutDialog.cpp>
+82
View File
@@ -0,0 +1,82 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
//////////////////////////////////////////////////////////////////////////
// CCheckOutDialog dialog
#if !defined(Q_MOC_RUN)
#include <QDialog>
#endif
namespace Ui
{
class CheckOutDialog;
}
class CCheckOutDialog
: public QDialog
{
Q_OBJECT
public:
// Checkout dialog result.
enum EResult
{
CHECKOUT = QDialog::Accepted,
OVERWRITE,
CANCEL = QDialog::Rejected
};
CCheckOutDialog(const QString& file, QWidget* pParent = NULL); // standard constructor
virtual ~CCheckOutDialog();
// Dialog Data
void OnInitDialog();
// Enable functionality For All. In the end call with false to return it in init state.
// Returns previous enable state
static bool EnableForAll(bool isEnable);
static bool IsForAll() { return InstanceIsForAll(); }
static int LastResult() { return m_lastResult; }
protected:
void OnBnClickedCancel();
void OnBnClickedCheckout();
void OnBnClickedOverwrite();
private:
static bool& InstanceEnableForAll();
static bool& InstanceIsForAll();
void HandleResult(int result);
QString m_file;
QScopedPointer<Ui::CheckOutDialog> m_ui;
static int m_lastResult;
};
//////////////////////////////////////////////////////////////////////////
class CAutoCheckOutDialogEnableForAll
{
public:
CAutoCheckOutDialogEnableForAll()
{
m_bPrevState = CCheckOutDialog::EnableForAll(true);
}
~CAutoCheckOutDialogEnableForAll()
{
CCheckOutDialog::EnableForAll(m_bPrevState);
}
private:
bool m_bPrevState;
};
+133
View File
@@ -0,0 +1,133 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CheckOutDialog</class>
<widget class="QDialog" name="CheckOutDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>451</width>
<height>121</height>
</rect>
</property>
<property name="windowTitle">
<string>Check Out File</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="sizeConstraint">
<enum>QLayout::SetMinimumSize</enum>
</property>
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="icon">
<property name="minimumSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="m_text">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>WWWWWWWWWW WWWWWWWWW WWWWWWWWWW WWWWWWWWWW WWWWWWWWWWW WWWWWWWWW WWWWW WWWWWWW! WWWWWW WWWWWWWWW! WWWWWWWWW WWWWWWW WWWWWW WWWW WWWW WWWWW WWWWW</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>6</number>
</property>
<item>
<widget class="QCheckBox" name="chkForAll">
<property name="text">
<string>Apply to all</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonCheckout">
<property name="text">
<string>Check Out</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonOverwrite">
<property name="text">
<string>Overwrite</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="buttonCancel">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<tabstops>
<tabstop>buttonCheckout</tabstop>
<tabstop>buttonOverwrite</tabstop>
<tabstop>buttonCancel</tabstop>
</tabstops>
<resources/>
<connections>
</connections>
</ui>
+134
View File
@@ -0,0 +1,134 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "Clipboard.h"
#include "Util/Image.h"
#include <QClipboard>
#include <QMessageBox>
#include <QVariant>
XmlNodeRef CClipboard::m_node;
QString CClipboard::m_title;
QVariant CClipboard::s_pendingPut;
//////////////////////////////////////////////////////////////////////////
// Clipboard implementation.
//////////////////////////////////////////////////////////////////////////
CClipboard::CClipboard(QWidget* parent)
: m_parent(parent != nullptr ? parent : QApplication::activeWindow())
{
m_putDebounce.setSingleShot(true);
m_putDebounce.setInterval(0);
// Wait one frame before setting clipboard contents, in case we're updated frequently
QObject::connect(&m_putDebounce, &QTimer::timeout, [this](){SendPendingPut();});
}
void CClipboard::Put(XmlNodeRef& node, const QString& title)
{
m_title = title;
if (m_title.isEmpty())
{
m_title = node->getTag();
}
m_node = node;
PutString(m_node->getXML().c_str(), title);
}
//////////////////////////////////////////////////////////////////////////
XmlNodeRef CClipboard::Get() const
{
QString str = GetString();
return XmlHelpers::LoadXmlFromBuffer(str.toUtf8().data(), str.toUtf8().length(), true);
}
//////////////////////////////////////////////////////////////////////////
void CClipboard::PutString(const QString& text, [[maybe_unused]] const QString& title /* = "" */)
{
s_pendingPut = text;
m_putDebounce.start();
}
//////////////////////////////////////////////////////////////////////////
QString CClipboard::GetString() const
{
if (s_pendingPut.type() == QVariant::String)
{
return s_pendingPut.toString();
}
return QApplication::clipboard()->text();
}
//////////////////////////////////////////////////////////////////////////
bool CClipboard::IsEmpty() const
{
return GetString().isEmpty();
}
//////////////////////////////////////////////////////////////////////////
void CClipboard::PutImage(const CImageEx& img)
{
QImage image(img.GetWidth(), img.GetHeight(), QImage::Format_RGBA8888);
s_pendingPut = image;
m_putDebounce.start();
}
//////////////////////////////////////////////////////////////////////////
bool CClipboard::GetImage(CImageEx& img)
{
QImage image;
if (s_pendingPut.type() == QVariant::Image)
{
image = s_pendingPut.value<QImage>();
}
else
{
image = QApplication::clipboard()->image();
}
img.Allocate(image.width(), image.height());
unsigned char* pSrc = (unsigned char*)image.scanLine(0);
unsigned char* pDst = (unsigned char*)img.GetData();
int stSrc = (image.depth() == 24) ? 3 : 4;
for (int y = 0; y < image.height(); y++)
{
for (int x = 0; x < image.width(); x++)
{
pDst[x * 4 + (image.height() - y - 1) * image.width() * 4] = pSrc[x * stSrc + y * image.bytesPerLine()];
pDst[x * 4 + (image.height() - y - 1) * image.width() * 4 + 1] = pSrc[x * stSrc + y * image.bytesPerLine() + 1];
pDst[x * 4 + (image.height() - y - 1) * image.width() * 4 + 2] = pSrc[x * stSrc + y * image.bytesPerLine() + 2];
pDst[x * 4 + (image.height() - y - 1) * image.width() * 4 + 3] = 0;
}
}
return true;
}
//////////////////////////////////////////////////////////////////////////
void CClipboard::SendPendingPut()
{
if (s_pendingPut.type() == QVariant::String)
{
QString text = s_pendingPut.toString();
QApplication::clipboard()->setText(text);
}
else if (s_pendingPut.type() == QVariant::Image)
{
QImage image = s_pendingPut.value<QImage>();
QApplication::clipboard()->setImage(image);
}
s_pendingPut = {};
}
+66
View File
@@ -0,0 +1,66 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef CRYINCLUDE_EDITOR_CLIPBOARD_H
#define CRYINCLUDE_EDITOR_CLIPBOARD_H
#pragma once
#include "Include/EditorCoreAPI.h"
#include <QTimer>
class CImageEx;
class QVariant;
class QWidget;
/** Use this class to put and get stuff from windows clipboard.
*/
class EDITOR_CORE_API CClipboard
{
public:
CClipboard(QWidget* parent);
//! Put xml node into clipboard
void Put(XmlNodeRef& node, const QString& title = QString());
//! Get xml node to clipboard.
XmlNodeRef Get() const;
//! Put string into Windows clipboard.
void PutString(const QString& text, const QString& title = QString());
//! Get string from Windows clipboard.
QString GetString() const;
//! Return name of what is in clipboard now.
QString GetTitle() const { return m_title; };
//! Put image into Windows clipboard.
void PutImage(const CImageEx& img);
//! Get image from Windows clipboard.
bool GetImage(CImageEx& img);
//! Return true if clipboard is empty.
bool IsEmpty() const;
private:
// Resolves the last request Put operation
void SendPendingPut();
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
static XmlNodeRef m_node;
AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
static QString m_title;
static QVariant s_pendingPut;
QWidget* m_parent;
QTimer m_putDebounce;
};
#endif // CRYINCLUDE_EDITOR_CLIPBOARD_H
+569
View File
@@ -0,0 +1,569 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "CommandManager.h"
#include <AzToolsFramework/PythonTerminal/ScriptTermDialog.h>
// Editor
#include "QtViewPaneManager.h"
#include "Include/IIconManager.h"
// AzToolsFramework
#include <AzToolsFramework/PythonTerminal/ScriptTermDialog.h>
CAutoRegisterCommandHelper* CAutoRegisterCommandHelper::s_pFirst = 0;
CAutoRegisterCommandHelper* CAutoRegisterCommandHelper::s_pLast = 0;
CAutoRegisterCommandHelper* CAutoRegisterCommandHelper::GetFirst()
{
return s_pFirst;
}
CAutoRegisterCommandHelper::CAutoRegisterCommandHelper(void(*registerFunc)(CEditorCommandManager &))
{
m_registerFunc = registerFunc;
m_pNext = 0;
if (!s_pLast)
{
s_pFirst = this;
}
else
{
s_pLast->m_pNext = this;
}
s_pLast = this;
}
CEditorCommandManager::CEditorCommandManager()
: m_bWarnDuplicate(true) {}
void CEditorCommandManager::RegisterAutoCommands()
{
CAutoRegisterCommandHelper* pHelper = CAutoRegisterCommandHelper::GetFirst();
while (pHelper)
{
pHelper->m_registerFunc(*this);
pHelper = pHelper->m_pNext;
}
}
CEditorCommandManager::~CEditorCommandManager()
{
CommandTable::const_iterator iter = m_commands.begin(), end = m_commands.end();
for (; iter != end; ++iter)
{
if (iter->second.deleter)
{
iter->second.deleter(iter->second.pCommand);
}
else
{
delete iter->second.pCommand;
}
}
m_commands.clear();
m_uiCommands.clear();
}
string CEditorCommandManager::GetFullCommandName(const string& module, const string& name)
{
string fullName = module;
fullName += ".";
fullName += name;
return fullName;
}
bool CEditorCommandManager::AddCommand(CCommand* pCommand, TPfnDeleter deleter)
{
assert(pCommand);
string module = pCommand->GetModule();
string name = pCommand->GetName();
if (IsRegistered(module, name) && m_bWarnDuplicate)
{
QString errMsg;
errMsg = QStringLiteral("Error: Command %1.%2 already registered!").arg(module.c_str(), name.c_str());
Warning(errMsg.toUtf8().data());
return false;
}
SCommandTableEntry entry;
entry.pCommand = pCommand;
entry.deleter = deleter;
m_commands.insert(
CommandTable::value_type(GetFullCommandName(module, name),
entry));
return true;
}
bool CEditorCommandManager::UnregisterCommand(const char* module, const char* name)
{
string fullName = GetFullCommandName(module, name);
CommandTable::iterator itr = m_commands.find(fullName);
if (itr != m_commands.end())
{
if (itr->second.deleter)
{
itr->second.deleter(itr->second.pCommand);
}
else
{
delete itr->second.pCommand;
}
m_commands.erase(itr);
return true;
}
return false;
}
bool CEditorCommandManager::RegisterUICommand(
const char* module,
const char* name,
const char* description,
const char* example,
const AZStd::function<void()>& functor,
const CCommand0::SUIInfo& uiInfo)
{
bool ok = CommandManagerHelper::RegisterCommand(this, module, name, description, example, functor);
if (ok == false)
{
return false;
}
return AttachUIInfo(GetFullCommandName(module, name), uiInfo);
}
bool CEditorCommandManager::AttachUIInfo(const char* fullCmdName, const CCommand0::SUIInfo& uiInfo)
{
CommandTable::iterator iter = m_commands.find(fullCmdName);
if (iter == m_commands.end())
{
return false;
}
if (iter->second.pCommand->CanBeUICommand() == false)
{
return false;
}
CCommand0* pCommand = static_cast<CCommand0*>(iter->second.pCommand);
pCommand->m_uiInfo = uiInfo;
if (pCommand->m_uiInfo.commandId == 0)
{
pCommand->m_uiInfo.commandId = GenNewCommandId();
}
m_uiCommands.insert(UICommandTable::value_type(pCommand->m_uiInfo.commandId, pCommand));
if (uiInfo.iconFilename.empty() == false)
{
GetIEditor()->GetIconManager()->RegisterCommandIcon(uiInfo.iconFilename.c_str(), pCommand->m_uiInfo.commandId);
}
return true;
}
bool CEditorCommandManager::GetUIInfo(const string& module, const string& name, CCommand0::SUIInfo& uiInfo) const
{
string fullName = GetFullCommandName(module, name);
return GetUIInfo(fullName, uiInfo);
}
bool CEditorCommandManager::GetUIInfo(const string& fullCmdName, CCommand0::SUIInfo& uiInfo) const
{
CommandTable::const_iterator iter = m_commands.find(fullCmdName);
if (iter == m_commands.end())
{
return false;
}
if (iter->second.pCommand->CanBeUICommand() == false)
{
return false;
}
CCommand0* pCommand = static_cast<CCommand0*>(iter->second.pCommand);
uiInfo = pCommand->m_uiInfo;
return true;
}
int CEditorCommandManager::GenNewCommandId()
{
static int uniqueId = CUSTOM_COMMAND_ID_FIRST;
return uniqueId++;
}
QString CEditorCommandManager::Execute(const string& module, const string& name, const CCommand::CArgs& args)
{
string fullName = GetFullCommandName(module, name);
CommandTable::iterator iter = m_commands.find(fullName);
if (iter != m_commands.end())
{
LogCommand(fullName, args);
return ExecuteAndLogReturn(iter->second.pCommand, args);
}
else
{
QString errMsg;
errMsg = QStringLiteral("Error: Trying to execute a unknown command, '%1'!").arg(fullName.c_str());
CryLogAlways(errMsg.toUtf8().data());
}
return "";
}
QString CEditorCommandManager::Execute(const string& cmdLine)
{
string cmdTxt, argsTxt;
size_t argStart = cmdLine.find_first_of(' ');
cmdTxt = cmdLine.substr(0, argStart);
argsTxt = "";
if (argStart != string::npos)
{
argsTxt = cmdLine.substr(argStart + 1);
argsTxt.Trim();
}
CommandTable::iterator itr = m_commands.find(cmdTxt);
if (itr != m_commands.end())
{
CCommand::CArgs argList;
GetArgsFromString(argsTxt, argList);
LogCommand(cmdTxt, argList);
return ExecuteAndLogReturn(itr->second.pCommand, argList);
}
else
{
QString errMsg;
errMsg = QStringLiteral("Error: Trying to execute a unknown command, '%1'!").arg(cmdLine.c_str());
CryLogAlways(errMsg.toUtf8().data());
}
return "";
}
void CEditorCommandManager::Execute(int commandId)
{
UICommandTable::iterator iter = m_uiCommands.find(commandId);
if (iter != m_uiCommands.end())
{
LogCommand(
GetFullCommandName(iter->second->GetModule(), iter->second->GetName()),
CCommand::CArgs());
iter->second->Execute(CCommand::CArgs());
}
else
{
QString errMsg;
errMsg = QStringLiteral("Error: Trying to execute a unknown command of ID '%1'!").arg(commandId);
CryLogAlways(errMsg.toUtf8().data());
}
}
void CEditorCommandManager::GetCommandList(std::vector<string>& cmds) const
{
cmds.clear();
cmds.reserve(m_commands.size());
CommandTable::const_iterator iter = m_commands.begin(), end = m_commands.end();
for (; iter != end; ++iter)
{
cmds.push_back(iter->first);
}
std::sort(cmds.begin(), cmds.end());
}
string CEditorCommandManager::AutoComplete(const string& substr) const
{
std::vector<string> cmds;
GetCommandList(cmds);
// If substring is empty return first command.
if (substr.empty() && (cmds.empty() == false))
{
return cmds[0];
}
size_t substrLen = substr.length();
for (size_t i = 0; i < cmds.size(); ++i)
{
size_t cmdLen = cmds[i].length();
if (cmdLen >= substrLen && !strncmp(cmds[i].c_str(), substr.c_str(), substrLen))
{
if (substrLen == cmdLen)
{
++i;
if (i < cmds.size())
{
return cmds[i];
}
else
{
return cmds[i - 1];
}
}
return cmds[i];
}
}
// Not found
return "";
}
bool CEditorCommandManager::IsRegistered(const char* module, const char* name) const
{
string fullName = GetFullCommandName(module, name);
CommandTable::const_iterator iter = m_commands.find(fullName);
if (iter != m_commands.end())
{
return true;
}
else
{
return false;
}
}
bool CEditorCommandManager::IsRegistered(const char* cmdLine_) const
{
string cmdTxt, argsTxt, cmdLine(cmdLine_);
size_t argStart = cmdLine.find_first_of(' ');
cmdTxt = cmdLine.substr(0, argStart);
CommandTable::const_iterator iter = m_commands.find(cmdTxt);
if (iter != m_commands.end())
{
return true;
}
else
{
return false;
}
}
bool CEditorCommandManager::IsRegistered(int commandId) const
{
if (CUSTOM_COMMAND_ID_FIRST <= commandId && commandId < CUSTOM_COMMAND_ID_LAST)
{
UICommandTable::const_iterator iter = m_uiCommands.find(commandId);
if (iter != m_uiCommands.end())
{
return true;
}
}
return false;
}
void CEditorCommandManager::SetCommandAvailableInScripting(const string& module, const string& name)
{
string fullName = GetFullCommandName(module, name);
CommandTable::iterator iter = m_commands.find(fullName);
if (iter != m_commands.end())
{
iter->second.pCommand->SetAvailableInScripting();
}
}
bool CEditorCommandManager::IsCommandAvailableInScripting(const string& fullCmdName) const
{
CommandTable::const_iterator iter = m_commands.find(fullCmdName);
if (iter != m_commands.end())
{
return iter->second.pCommand->IsAvailableInScripting();
}
return false;
}
bool CEditorCommandManager::IsCommandAvailableInScripting(const string& module, const string& name) const
{
string fullName = GetFullCommandName(module, name);
return IsCommandAvailableInScripting(fullName);
}
void CEditorCommandManager::LogCommand(const string& fullCmdName, const CCommand::CArgs& args) const
{
string cmdLine = fullCmdName;
for (int i = 0; i < args.GetArgCount(); ++i)
{
cmdLine += " ";
bool bString = args.IsStringArg(i);
if (bString)
{
cmdLine += "'";
}
cmdLine += args.GetArg(i);
if (bString)
{
cmdLine += "'";
}
}
CLogFile::WriteLine(cmdLine.c_str());
if (IsCommandAvailableInScripting(fullCmdName) == false)
{
return;
}
/// If this same command is also available in the script system,
/// log this to the script terminal, too.
// First, recreate a command line to be compatible to the script system.
cmdLine = fullCmdName;
cmdLine += "(";
for (int i = 0; i < args.GetArgCount(); ++i)
{
bool bString = args.IsStringArg(i);
if (bString)
{
cmdLine += "\"";
}
cmdLine += args.GetArg(i);
if (bString)
{
cmdLine += "\"";
}
if (i < args.GetArgCount() - 1)
{
cmdLine += ",";
}
}
cmdLine += ")";
// If it's not SandBox main editor (one case is the standalone material editor triggered by 3ds Max exporter),
// we should not cast it into main editor for further operation.
if (GetIEditor()->IsInMatEditMode())
{
return;
}
// Then, register it to the terminal.
QtViewPane* scriptTermPane = QtViewPaneManager::instance()->GetPane(SCRIPT_TERM_WINDOW_NAME);
if (!scriptTermPane)
{
return;
}
AzToolsFramework::CScriptTermDialog* pScriptTermDialog = qobject_cast<AzToolsFramework::CScriptTermDialog*>(scriptTermPane->Widget());
if (pScriptTermDialog)
{
string text = "> ";
text += cmdLine;
text += "\r\n";
pScriptTermDialog->AppendText(text.c_str());
}
}
QString CEditorCommandManager::ExecuteAndLogReturn(CCommand* pCommand, const CCommand::CArgs& args)
{
const QString result = pCommand->Execute(args);
const QString returnMsg = QString("Returned: %1").arg(result);
CLogFile::WriteLine(returnMsg.toUtf8().constData());
return result;
}
void CEditorCommandManager::GetArgsFromString(const string& argsTxt, CCommand::CArgs& argList)
{
const char quoteSymbol = '\'';
int curPos = 0;
int prevPos = 0;
string arg = argsTxt.Tokenize(" ", curPos);
while (!arg.empty())
{
if (arg[0] == quoteSymbol) // A special consideration for a quoted string
{
if (arg.length() < 2 || arg[arg.length() - 1] != quoteSymbol)
{
size_t openingQuotePos = argsTxt.find(quoteSymbol, prevPos);
size_t closingQuotePos = argsTxt.find(quoteSymbol, curPos);
if (closingQuotePos != string::npos)
{
arg = argsTxt.substr(openingQuotePos + 1, closingQuotePos - openingQuotePos - 1);
size_t nextArgPos = argsTxt.find(' ', closingQuotePos + 1);
curPos = nextArgPos != string::npos ? nextArgPos + 1 : argsTxt.length();
for (; curPos < argsTxt.length(); ++curPos) // Skip spaces.
{
if (argsTxt[curPos] != ' ')
{
break;
}
}
}
}
else
{
arg = arg.substr(1, arg.length() - 2);
}
}
argList.Add(arg.c_str());
prevPos = curPos;
arg = argsTxt.Tokenize(" ", curPos);
}
}
+117
View File
@@ -0,0 +1,117 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
// Description : the command manager
#ifndef CRYINCLUDE_EDITOR_COMMANDS_COMMANDMANAGER_H
#define CRYINCLUDE_EDITOR_COMMANDS_COMMANDMANAGER_H
#pragma once
#include "platform.h"
#include <ISystem.h>
#include "Include/SandboxAPI.h"
#include "Include/ICommandManager.h"
AZ_PUSH_DISABLE_DLL_EXPORT_BASECLASS_WARNING
class SANDBOX_API CEditorCommandManager
: public ICommandManager
{
AZ_POP_DISABLE_DLL_EXPORT_BASECLASS_WARNING
public:
enum
{
CUSTOM_COMMAND_ID_FIRST = 10000,
CUSTOM_COMMAND_ID_LAST = 15000
};
CEditorCommandManager();
~CEditorCommandManager();
void RegisterAutoCommands();
bool AddCommand(CCommand* pCommand, TPfnDeleter deleter = NULL);
bool UnregisterCommand(const char* module, const char* name);
bool RegisterUICommand(
const char* module,
const char* name,
const char* description,
const char* example,
const AZStd::function<void()>& functor,
const CCommand0::SUIInfo& uiInfo);
bool AttachUIInfo(const char* fullCmdName, const CCommand0::SUIInfo& uiInfo);
bool GetUIInfo(const string& module, const string& name, CCommand0::SUIInfo& uiInfo) const;
bool GetUIInfo(const string& fullCmdName, CCommand0::SUIInfo& uiInfo) const;
QString Execute(const string& cmdLine);
QString Execute(const string& module, const string& name, const CCommand::CArgs& args);
void Execute(int commandId);
void GetCommandList(std::vector<string>& cmds) const;
//! Used in the console dialog
string AutoComplete(const string& substr) const;
bool IsRegistered(const char* module, const char* name) const;
bool IsRegistered(const char* cmdLine) const;
bool IsRegistered(int commandId) const;
void SetCommandAvailableInScripting(const string& module, const string& name);
bool IsCommandAvailableInScripting(const string& module, const string& name) const;
bool IsCommandAvailableInScripting(const string& fullCmdName) const;
//! Turning off the warning is needed for reloading the ribbon bar.
void TurnDuplicateWarningOn() { m_bWarnDuplicate = true; }
void TurnDuplicateWarningOff() { m_bWarnDuplicate = false; }
protected:
struct SCommandTableEntry
{
CCommand* pCommand;
TPfnDeleter deleter;
};
//! A full command name to an actual command mapping
typedef std::map<string, SCommandTableEntry> CommandTable;
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
CommandTable m_commands;
//! A command ID to an actual UI command mapping
//! This table will contain a subset of commands among all registered to the above table.
typedef std::map<int, CCommand0*> UICommandTable;
UICommandTable m_uiCommands;
AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
bool m_bWarnDuplicate;
static int GenNewCommandId();
static string GetFullCommandName(const string& module, const string& name);
static void GetArgsFromString(const string& argsTxt, CCommand::CArgs& argList);
void LogCommand(const string& fullCmdName, const CCommand::CArgs& args) const;
QString ExecuteAndLogReturn(CCommand* pCommand, const CCommand::CArgs& args);
};
//! A helper class for an automatic command registration
class SANDBOX_API CAutoRegisterCommandHelper
{
public:
static CAutoRegisterCommandHelper* GetFirst();
CAutoRegisterCommandHelper(void(*registerFunc)(CEditorCommandManager &));
void (* m_registerFunc)(CEditorCommandManager&);
CAutoRegisterCommandHelper* m_pNext;
private:
static CAutoRegisterCommandHelper* s_pFirst;
static CAutoRegisterCommandHelper* s_pLast;
};
#define REGISTER_EDITOR_COMMAND(boundFunction, moduleName, functionName, description, example) \
void RegisterCommand##moduleName##functionName(CEditorCommandManager & cmdMgr) \
{ \
CommandManagerHelper::RegisterCommand(&cmdMgr, #moduleName, #functionName, description, example, boundFunction); \
} \
CAutoRegisterCommandHelper g_AutoRegCmdHelper##moduleName##functionName(RegisterCommand##moduleName##functionName)
#endif // CRYINCLUDE_EDITOR_COMMANDS_COMMANDMANAGER_H
+29
View File
@@ -0,0 +1,29 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/EBus/EBus.h>
class CommandManagerRequests : public AZ::EBusTraits
{
public:
struct CommandDetails
{
AZStd::string m_name;
AZStd::vector<AZStd::string> m_arguments;
};
virtual AZStd::vector<AZStd::string> GetCommands() const = 0;
virtual void ExecuteCommand(const AZStd::string& commandLine) {}
virtual void GetCommandDetails(AZStd::string commandName, CommandDetails& outArguments) const = 0;
};
using CommandManagerRequestBus = AZ::EBus<CommandManagerRequests>;
+3
View File
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:58ef978b31b31df9aaf715a0e9b006fde414a17a3ff15a3bf680eaad7418867a
size 364
+3
View File
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:98a681ec3d89ee57c5d1057fe984dcf8ad45721f47ae4df57fa358fbee85e616
size 385
+3
View File
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:24a2b2c9242a841c20e7815dab0d80a575844055328aea413d28b7283b65a92e
size 386
+3
View File
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ce23a276fec849b8f832fab96d3b738793335c27d37ae3813158387f3415b508
size 377
+3
View File
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c03befab41765200f4f28dbf1e0b2a702d2244bfa79b0d463f5d58d0a26095fc
size 386
+3
View File
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:418c3f0f27854b3795841359014d87686a7bf94daf2568d9cfd3ffac22675f69
size 386
+3
View File
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d3a831f34ac53c9b1f20037290e8a2b62a3cfb8a4f86467591f44fd2a0e3c15b
size 379
+3
View File
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4267102ca7a889c34eff905480a68878d4d56e15bc723a5b0575cd472e259f5d
size 389
+3
View File
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a0df013dd102b87348fba18b4da5443591309e9c40166d27ae928636924154ea
size 388
+3
View File
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e713076ab5abbbb2cf28da431a339e9905acc790e35295f025aa2e79e1c04141
size 376
+3
View File
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9e5af9d62ceafc3b8a1dfc36772350cd623fcc86c68711b299e143ff133f79b6
size 387
+3
View File
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:17c5fb3d7b87ea87a98934954c721573c641bc44005a34f1e16589d7f39b71e8
size 409
+3
View File
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6f5c78d9f764b62fb7dcf400c91c1edea9d7f88a426ba513fbf70825c6bcd2ac
size 383
+3
View File
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:376b549602afffca407525b77c1a9821bf6a0e279792ae2e52fe0a4f7c3c5bd4
size 364
+3
View File
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e7dc48f8d324b7563b168f27ebde1e00ee2bd11ba462f114a05b297913e285c5
size 374
+3
View File
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:66b73afbd6dba1caaedfaae161b277b460b5198f7fc00bec414530116c567276
size 375
+3
View File
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ae6e6714acf495246f4e59f6e5640f3a4417ea50100d37a116950d2b859aed0c
size 417
+202
View File
@@ -0,0 +1,202 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "ConfigGroup.h"
namespace Config
{
CConfigGroup::CConfigGroup()
{
}
CConfigGroup::~CConfigGroup()
{
for (TConfigVariables::const_iterator it = m_vars.begin();
it != m_vars.end(); ++it)
{
delete (*it);
}
}
void CConfigGroup::AddVar(IConfigVar* var)
{
m_vars.push_back(var);
}
uint32 CConfigGroup::GetVarCount()
{
return m_vars.size();
}
IConfigVar* CConfigGroup::GetVar(const char* szName)
{
for (TConfigVariables::const_iterator it = m_vars.begin();
it != m_vars.end(); ++it)
{
IConfigVar* var = (*it);
if (0 == _stricmp(szName, var->GetName().c_str()))
{
return var;
}
}
return NULL;
}
const IConfigVar* CConfigGroup::GetVar(const char* szName) const
{
for (TConfigVariables::const_iterator it = m_vars.begin();
it != m_vars.end(); ++it)
{
IConfigVar* var = (*it);
if (0 == _stricmp(szName, var->GetName().c_str()))
{
return var;
}
}
return NULL;
}
IConfigVar* CConfigGroup::GetVar(uint index)
{
if (index < m_vars.size())
{
return m_vars[index];
}
return NULL;
}
const IConfigVar* CConfigGroup::GetVar(uint index) const
{
if (index < m_vars.size())
{
return m_vars[index];
}
return NULL;
}
void CConfigGroup::SaveToXML(XmlNodeRef node)
{
// save only values that don't have default values
for (TConfigVariables::const_iterator it = m_vars.begin();
it != m_vars.end(); ++it)
{
IConfigVar* var = (*it);
if (!var->IsFlagSet(IConfigVar::eFlag_DoNotSave))
{
if (!var->IsDefault())
{
const char* szName = var->GetName().c_str();
switch (var->GetType())
{
case IConfigVar::eType_BOOL:
{
bool currentValue = false;
var->Get(&currentValue);
node->setAttr(szName, currentValue);
break;
}
case IConfigVar::eType_INT:
{
int currentValue = 0;
var->Get(&currentValue);
node->setAttr(szName, currentValue);
break;
}
case IConfigVar::eType_FLOAT:
{
float currentValue = 0;
var->Get(&currentValue);
node->setAttr(szName, currentValue);
break;
}
case IConfigVar::eType_STRING:
{
string currentValue = 0;
var->Get(&currentValue);
node->setAttr(szName, currentValue);
break;
}
}
}
}
}
}
void CConfigGroup::LoadFromXML(XmlNodeRef node)
{
// save only values that don't have default values
for (TConfigVariables::const_iterator it = m_vars.begin();
it != m_vars.end(); ++it)
{
IConfigVar* var = (*it);
if (!var->IsFlagSet(IConfigVar::eFlag_DoNotSave))
{
const char* szName = var->GetName().c_str();
switch (var->GetType())
{
case IConfigVar::eType_BOOL:
{
bool currentValue = false;
var->GetDefault(&currentValue);
if (node->getAttr(szName, currentValue))
{
var->Set(&currentValue);
}
break;
}
case IConfigVar::eType_INT:
{
int currentValue = 0;
var->GetDefault(&currentValue);
if (node->getAttr(szName, currentValue))
{
var->Set(&currentValue);
}
break;
}
case IConfigVar::eType_FLOAT:
{
float currentValue = 0;
var->GetDefault(&currentValue);
if (node->getAttr(szName, currentValue))
{
var->Set(&currentValue);
}
break;
}
case IConfigVar::eType_STRING:
{
string currentValue = 0;
var->GetDefault(&currentValue);
QString readValue(currentValue.c_str());
if (node->getAttr(szName, readValue))
{
currentValue = readValue.toUtf8().data();
var->Set(&currentValue);
}
break;
}
}
}
}
}
}
+160
View File
@@ -0,0 +1,160 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#ifndef CRYINCLUDE_EDITOR_CONFIGGROUP_H
#define CRYINCLUDE_EDITOR_CONFIGGROUP_H
namespace Config
{
// Abstract configurable variable
struct IConfigVar
{
public:
enum EType
{
eType_BOOL,
eType_INT,
eType_FLOAT,
eType_STRING,
};
enum EFlags
{
eFlag_NoUI = 1 << 0,
eFlag_NoCVar = 1 << 1,
eFlag_DoNotSave = 1 << 2,
};
IConfigVar(const char* szName, const char* szDescription, EType varType, uint8 flags)
: m_name(szName)
, m_description(szDescription)
, m_type(varType)
, m_flags(flags)
, m_ptr(NULL)
{};
virtual ~IConfigVar() = default;
ILINE EType GetType() const
{
return m_type;
}
ILINE const string& GetName() const
{
return m_name;
}
ILINE const string& GetDescription() const
{
return m_description;
}
ILINE bool IsFlagSet(EFlags flag) const
{
return 0 != (m_flags & flag);
}
virtual void Get(void* outPtr) const = 0;
virtual void Set(const void* ptr) = 0;
virtual bool IsDefault() const = 0;
virtual void GetDefault(void* outPtr) const = 0;
virtual void Reset() = 0;
static EType TranslateType(const bool&) { return eType_BOOL; }
static EType TranslateType(const int&) { return eType_INT; }
static EType TranslateType(const float&) { return eType_FLOAT; }
static EType TranslateType(const string&) { return eType_STRING; }
protected:
EType m_type;
uint8 m_flags;
string m_name;
string m_description;
void* m_ptr;
ICVar* m_pCVar;
};
// Typed wrapper for config variable
template<class T>
class TConfigVar
: public IConfigVar
{
private:
T m_default;
public:
TConfigVar(const char* szName, const char* szDescription, uint8 flags, T& ptr, const T& defaultValue)
: IConfigVar(szName, szDescription, IConfigVar::TranslateType(ptr), flags)
, m_default(defaultValue)
{
m_ptr = &ptr;
// reset to default value on initializations
ptr = defaultValue;
}
virtual void Get(void* outPtr) const
{
*reinterpret_cast<T*>(outPtr) = *reinterpret_cast<const T*>(m_ptr);
}
virtual void Set(const void* ptr)
{
*reinterpret_cast<T*>(m_ptr) = *reinterpret_cast<const T*>(ptr);
}
virtual void Reset()
{
*reinterpret_cast<T*>(m_ptr) = m_default;
}
virtual void GetDefault(void* outPtr) const
{
*reinterpret_cast<T*>(outPtr) = m_default;
}
virtual bool IsDefault() const
{
return *reinterpret_cast<const T*>(m_ptr) == m_default;
}
};
// Group of configuration variables with optional mapping to CVars
class CConfigGroup
{
private:
typedef std::vector<IConfigVar*> TConfigVariables;
TConfigVariables m_vars;
typedef std::vector<ICVar*> TConsoleVariables;
TConsoleVariables m_consoleVars;
public:
CConfigGroup();
virtual ~CConfigGroup();
void AddVar(IConfigVar* var);
uint32 GetVarCount();
IConfigVar* GetVar(const char* szName);
IConfigVar* GetVar(uint index);
const IConfigVar* GetVar(const char* szName) const;
const IConfigVar* GetVar(uint index) const;
void SaveToXML(XmlNodeRef node);
void LoadFromXML(XmlNodeRef node);
template<class T>
void AddVar(const char* szName, const char* szDescription, T& var, const T& defaultValue, uint8 flags = 0)
{
AddVar(new TConfigVar<T>(szName, szDescription, flags, var, defaultValue));
}
};
};
#endif // CRYINCLUDE_EDITOR_CONFIGGROUP_H
+51
View File
@@ -0,0 +1,51 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "ConsoleDialog.h"
// Qt
#include <QVBoxLayout>
// Editor
#include "Controls/ConsoleSCB.h" // For CConsoleSCB
#include "LyViewPaneNames.h" // for LyViewPane::
CConsoleDialog::CConsoleDialog(QWidget* parent)
: QDialog(parent)
, m_consoleWidget(new CConsoleSCB(this))
{
QVBoxLayout* outterLayout = new QVBoxLayout(this);
outterLayout->addWidget(m_consoleWidget);
outterLayout->setMargin(0);
setWindowTitle(LyViewPane::Console);
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
resize(842, 480);
}
void CConsoleDialog::SetInfoText(const char* text)
{
if (gEnv && gEnv->pLog) // before log system was initialized
{
CryLogAlways(text);
}
}
void CConsoleDialog::closeEvent(QCloseEvent* ev)
{
if (GetISystem())
{
GetISystem()->Quit();
}
QDialog::closeEvent(ev);
}
#include <moc_ConsoleDialog.cpp>
+34
View File
@@ -0,0 +1,34 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef CRYINCLUDE_EDITOR_CONSOLEDIALOG_H
#define CRYINCLUDE_EDITOR_CONSOLEDIALOG_H
#pragma once
#if !defined(Q_MOC_RUN)
#include <QDialog>
#endif
class CConsoleSCB;
class CConsoleDialog
: public QDialog
, public IInitializeUIInfo
{
Q_OBJECT
public:
explicit CConsoleDialog(QWidget* parent = nullptr);
void SetInfoText(const char* text) override;
void closeEvent(QCloseEvent*) override;
private:
CConsoleSCB* const m_consoleWidget;
};
#endif // CRYINCLUDE_EDITOR_CONSOLEDIALOG_H
+134
View File
@@ -0,0 +1,134 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "ControlMRU.h"
IMPLEMENT_XTP_CONTROL(CControlMRU, CXTPControlRecentFileList)
bool CControlMRU::DoesFileExist(CString& sFileName)
{
return (_access(sFileName.GetBuffer(), 0) == 0);
}
void CControlMRU::OnCalcDynamicSize(DWORD dwMode)
{
CRecentFileList* pRecentFileList = GetRecentFileList();
if (!pRecentFileList)
{
return;
}
CString* pArrNames = pRecentFileList->m_arrNames;
assert(pArrNames != NULL);
if (!pArrNames)
{
return;
}
while (m_nIndex + 1 < m_pControls->GetCount())
{
CXTPControl* pControl = m_pControls->GetAt(m_nIndex + 1);
assert(pControl);
if (pControl->GetID() >= GetFirstMruID()
&& pControl->GetID() <= GetFirstMruID() + pRecentFileList->m_nSize)
{
m_pControls->Remove(pControl);
}
else
{
break;
}
}
if (m_pParent->IsCustomizeMode())
{
m_dwHideFlags = 0;
SetEnabled(TRUE);
return;
}
if (pArrNames[0].IsEmpty())
{
SetCaption(CString(MAKEINTRESOURCE(IDS_NORECENTFILE_CAPTION)));
SetDescription("No recently opened files");
m_dwHideFlags = 0;
SetEnabled(FALSE);
return;
}
else
{
SetCaption(CString(MAKEINTRESOURCE(IDS_RECENTFILE_CAPTION)));
SetDescription("Open this document");
}
m_dwHideFlags |= xtpHideGeneric;
CString sCurDir = (Path::GetEditingGameDataFolder() + "\\").c_str();
int nCurDir = sCurDir.GetLength();
CString strName;
CString strTemp;
int iLastValidMRU = 0;
for (int iMRU = 0; iMRU < pRecentFileList->m_nSize; iMRU++)
{
if (!pRecentFileList->GetDisplayName(strName, iMRU, sCurDir.GetBuffer(), nCurDir))
{
break;
}
if (DoesFileExist(pArrNames[iMRU]))
{
CString sCurEntryDir = pArrNames[iMRU].Left(nCurDir);
if (sCurEntryDir.CompareNoCase(sCurDir) != 0)
{
//unavailable entry (wrong directory)
continue;
}
}
else
{
//invalid entry (not existing)
continue;
}
int nId = iMRU + GetFirstMruID();
CXTPControl* pControl = m_pControls->Add(xtpControlButton, nId, _T(""), m_nIndex + iLastValidMRU + 1, TRUE);
assert(pControl);
pControl->SetCaption(CXTPControlWindowList::ConstructCaption(strName, iLastValidMRU + 1));
pControl->SetFlags(xtpFlagManualUpdate);
pControl->SetBeginGroup(iLastValidMRU == 0 && m_nIndex != 0);
pControl->SetParameter(pArrNames[iMRU]);
CString sDescription = "Open file: " + pArrNames[iMRU];
pControl->SetDescription(sDescription);
if ((GetFlags() & xtpFlagWrapRow) && iMRU == 0)
{
pControl->SetFlags(pControl->GetFlags() | xtpFlagWrapRow);
}
++iLastValidMRU;
}
//if no entry was valid, treat as none would exist
if (iLastValidMRU == 0)
{
SetCaption(CString(MAKEINTRESOURCE(IDS_NORECENTFILE_CAPTION)));
SetDescription("No recently opened files");
m_dwHideFlags = 0;
SetEnabled(FALSE);
}
}
+23
View File
@@ -0,0 +1,23 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#ifndef CRYINCLUDE_EDITOR_CONTROLMRU_H
#define CRYINCLUDE_EDITOR_CONTROLMRU_H
class CControlMRU
: public CXTPControlRecentFileList
{
protected:
virtual void OnCalcDynamicSize(DWORD dwMode);
private:
DECLARE_XTP_CONTROL(CControlMRU)
bool DoesFileExist(CString& sFileName);
};
#endif // CRYINCLUDE_EDITOR_CONTROLMRU_H
+368
View File
@@ -0,0 +1,368 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* 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>
+87
View File
@@ -0,0 +1,87 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* 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
+927
View File
@@ -0,0 +1,927 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "ColorGradientCtrl.h"
// Qt
#include <QPainter>
#include <QToolTip>
// AzQtComponents
#include <AzQtComponents/Components/Widgets/ColorPicker.h>
#define MIN_TIME_EPSILON 0.01f
//////////////////////////////////////////////////////////////////////////
CColorGradientCtrl::CColorGradientCtrl(QWidget* parent)
: QWidget(parent)
{
m_nActiveKey = -1;
m_nHitKeyIndex = -1;
m_nKeyDrawRadius = 3;
m_bTracking = false;
m_pSpline = 0;
m_fMinTime = -1;
m_fMaxTime = 1;
m_fMinValue = -1;
m_fMaxValue = 1;
m_fTooltipScaleX = 1;
m_fTooltipScaleY = 1;
m_bNoTimeMarker = true;
m_bLockFirstLastKey = false;
m_bNoZoom = true;
ClearSelection();
m_bSelectedKeys.reserve(0);
m_fTimeMarker = -10;
m_grid.zoom.x = 100;
setMouseTracking(true);
}
CColorGradientCtrl::~CColorGradientCtrl()
{
}
/////////////////////////////////////////////////////////////////////////////
// QColorGradientCtrl message handlers
//////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::resizeEvent(QResizeEvent* event)
{
QWidget::resizeEvent(event);
QRect rc(QPoint(0, 0), event->size());
m_rcGradient = rc;
m_rcGradient.setHeight(m_rcGradient.height() - 11);
//m_rcGradient.DeflateRect(4,4);
m_grid.rect = m_rcGradient;
if (m_bNoZoom)
{
m_grid.zoom.x = m_grid.rect.width();
}
m_rcKeys = rc;
m_rcKeys.setTop(m_rcKeys.bottom() - 10);
}
//////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::SetZoom(float fZoom)
{
m_grid.zoom.x = fZoom;
}
//////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::SetOrigin(float fOffset)
{
m_grid.origin.x = fOffset;
}
//////////////////////////////////////////////////////////////////////////
QPoint CColorGradientCtrl::KeyToPoint(int nKey)
{
if (nKey >= 0)
{
return TimeToPoint(m_pSpline->GetKeyTime(nKey));
}
return QPoint(0, 0);
}
//////////////////////////////////////////////////////////////////////////
QPoint CColorGradientCtrl::TimeToPoint(float time)
{
return QPoint(m_grid.WorldToClient(Vec2(time, 0)).x(), m_rcGradient.height() / 2);
QPoint point;
point.rx() = (time - m_fMinTime) * (m_rcGradient.width() / (m_fMaxTime - m_fMinTime)) + m_rcGradient.left();
point.ry() = m_rcGradient.height() / 2;
return point;
}
//////////////////////////////////////////////////////////////////////////
AZ::Color CColorGradientCtrl::TimeToColor(float time)
{
ISplineInterpolator::ValueType val;
m_pSpline->Interpolate(time, val);
const AZ::Color col = ValueToColor(val);
return col;
}
//////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::PointToTimeValue(QPoint point, float& time, ISplineInterpolator::ValueType& val)
{
time = XOfsToTime(point.x());
ColorToValue(TimeToColor(time), val);
}
//////////////////////////////////////////////////////////////////////////
float CColorGradientCtrl::XOfsToTime(int x)
{
return m_grid.ClientToWorld(QPoint(x, 0)).x;
}
//////////////////////////////////////////////////////////////////////////
QPoint CColorGradientCtrl::XOfsToPoint(int x)
{
return TimeToPoint(XOfsToTime(x));
}
//////////////////////////////////////////////////////////////////////////
AZ::Color CColorGradientCtrl::XOfsToColor(int x)
{
return TimeToColor(XOfsToTime(x));
}
//////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::paintEvent(QPaintEvent* e)
{
QPainter painter(this);
QRect rcClient = rect();
if (m_pSpline)
{
m_bSelectedKeys.resize(m_pSpline->GetKeyCount());
}
{
if (!isEnabled())
{
painter.setBrush(palette().button());
painter.drawRect(rcClient);
return;
}
//////////////////////////////////////////////////////////////////////////
// Fill keys backgound.
//////////////////////////////////////////////////////////////////////////
QRect rcKeys = m_rcKeys.intersected(e->rect());
painter.setBrush(palette().button());
painter.drawRect(rcKeys);
//////////////////////////////////////////////////////////////////////////
//Draw Keys and Curve
if (m_pSpline)
{
DrawGradient(e, &painter);
DrawKeys(e, &painter);
}
}
}
//////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::DrawGradient(QPaintEvent* e, QPainter* painter)
{
//Draw Curve
// create and select a thick, white pen
painter->setPen(QPen(QColor(128, 255, 128), 1, Qt::SolidLine));
const QRect rcClip = e->rect().intersected(m_rcGradient);
const int right = rcClip.left() + rcClip.width();
for (int x = rcClip.left(); x < right; x++)
{
const AZ::Color col = XOfsToColor(x);
QPen pen(QColor(col.GetR8(), col.GetG8(), col.GetR8(), col.GetA8()), 1, Qt::SolidLine);
painter->setPen(pen);
painter->drawLine(x, m_rcGradient.top(), x, m_rcGradient.top() + m_rcGradient.height());
}
}
//////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::DrawKeys(QPaintEvent* e, QPainter* painter)
{
if (!m_pSpline)
{
return;
}
// create and select a white pen
painter->setPen(QPen(QColor(0, 0, 0), 1, Qt::SolidLine));
QRect rcClip = e->rect();
m_bSelectedKeys.resize(m_pSpline->GetKeyCount());
for (int i = 0; i < m_pSpline->GetKeyCount(); i++)
{
float time = m_pSpline->GetKeyTime(i);
QPoint pt = TimeToPoint(time);
if (pt.x() < rcClip.left() - 8 || pt.x() > rcClip.left() + rcClip.width() + 8)
{
continue;
}
const AZ::Color clr = TimeToColor(time);
QBrush brush(QColor(clr.GetR8(), clr.GetG8(), clr.GetB8(), clr.GetA8()));
painter->setBrush(brush);
// Find the midpoints of the top, right, left, and bottom
// of the client area. They will be the vertices of our polygon.
QPoint pts[3];
pts[0].rx() = pt.x();
pts[0].ry() = m_rcKeys.top() + 1;
pts[1].rx() = pt.x() - 5;
pts[1].ry() = m_rcKeys.top() + 8;
pts[2].rx() = pt.x() + 5;
pts[2].ry() = m_rcKeys.top() + 8;
painter->drawPolygon(pts, 3);
if (m_bSelectedKeys[i])
{
QPen pen(QColor(200, 0, 0), 1, Qt::SolidLine);
QPen oldPen = painter->pen();
painter->setPen(pen);
painter->drawPolygon(pts, 3);
painter->setPen(oldPen);
}
}
if (!m_bNoTimeMarker)
{
QPen timePen(QColor(255, 0, 255), 1, Qt::SolidLine);
painter->setPen(timePen);
QPoint pt = TimeToPoint(m_fTimeMarker);
painter->drawLine(pt.x(), m_rcGradient.top() + 1, pt.x(), m_rcGradient.bottom() - 1);
}
}
void CColorGradientCtrl::UpdateTooltip(QPoint pos)
{
if (m_nHitKeyIndex >= 0)
{
float time = m_pSpline->GetKeyTime(m_nHitKeyIndex);
ISplineInterpolator::ValueType val;
m_pSpline->GetKeyValue(m_nHitKeyIndex, val);
AZ::Color col = TimeToColor(time);
int cont_s = (m_pSpline->GetKeyFlags(m_nHitKeyIndex) >> SPLINE_KEY_TANGENT_IN_SHIFT) & SPLINE_KEY_TANGENT_LINEAR ? 1 : 2;
int cont_d = (m_pSpline->GetKeyFlags(m_nHitKeyIndex) >> SPLINE_KEY_TANGENT_OUT_SHIFT) & SPLINE_KEY_TANGENT_LINEAR ? 1 : 2;
QString tipText(tr("%1 : %2,%3,%4 [%5,%6]").arg(time * m_fTooltipScaleX, 0, 'f', 2).arg(col.GetR8()).arg(col.GetG8()).arg(col.GetB8()).arg(cont_s).arg(cont_d));
const QPoint globalPos = mapToGlobal(pos);
QToolTip::showText(mapToGlobal(pos), tipText, this, QRect(globalPos, QSize(1, 1)));
}
}
/////////////////////////////////////////////////////////////////////////////
//Mouse Message Handlers
//////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::mousePressEvent(QMouseEvent* event)
{
if (event->button() == Qt::LeftButton)
{
OnLButtonDown(event);
}
else if (event->button() == Qt::RightButton)
{
OnRButtonDown(event);
}
}
void CColorGradientCtrl::OnLButtonDown([[maybe_unused]] QMouseEvent* event)
{
if (m_bTracking)
{
return;
}
if (!m_pSpline)
{
return;
}
setFocus();
switch (m_hitCode)
{
case HIT_KEY:
StartTracking();
SetActiveKey(m_nHitKeyIndex);
break;
/*
case HIT_SPLINE:
{
// Cycle the spline slope of the nearest key.
int flags = m_pSpline->GetKeyFlags(m_nHitKeyIndex);
if (m_nHitKeyDist < 0)
// Toggle left side.
flags ^= SPLINE_KEY_TANGENT_LINEAR << SPLINE_KEY_TANGENT_IN_SHIFT;
if (m_nHitKeyDist > 0)
// Toggle right side.
flags ^= SPLINE_KEY_TANGENT_LINEAR << SPLINE_KEY_TANGENT_OUT_SHIFT;
m_pSpline->SetKeyFlags(m_nHitKeyIndex, flags);
m_pSpline->Update();
SetActiveKey(-1);
SendNotifyEvent( CLRGRDN_CHANGE );
if (m_updateCallback)
m_updateCallback(this);
break;
}
*/
case HIT_NOTHING:
SetActiveKey(-1);
break;
}
update();
}
//////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::OnRButtonDown([[maybe_unused]] QMouseEvent* event)
{
}
//////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::mouseDoubleClickEvent(QMouseEvent* event)
{
if (!m_pSpline)
{
return;
}
if (event->button() != Qt::LeftButton)
{
return;
}
switch (m_hitCode)
{
case HIT_SPLINE:
{
int iIndex = InsertKey(event->pos());
SetActiveKey(iIndex);
EditKey(iIndex);
update();
}
break;
case HIT_KEY:
{
EditKey(m_nHitKeyIndex);
}
break;
}
}
//////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::mouseMoveEvent(QMouseEvent* event)
{
if (!m_pSpline)
{
return;
}
if (!m_bTracking)
{
switch (HitTest(event->pos()))
{
case HIT_SPLINE:
{
setCursor(CMFCUtils::LoadCursor(IDC_ARRWHITE));
} break;
case HIT_KEY:
{
setCursor(CMFCUtils::LoadCursor(IDC_ARRBLCK));
} break;
default:
break;
}
}
if (m_bTracking)
{
TrackKey(event->pos());
}
if (m_bTracking || m_nHitKeyIndex >= 0)
{
UpdateTooltip(event->pos());
}
else
{
QToolTip::hideText();
}
}
void CColorGradientCtrl::mouseReleaseEvent(QMouseEvent* event)
{
if (event->button() == Qt::LeftButton)
{
OnLButtonUp(event);
}
else if (event->button() == Qt::RightButton)
{
OnRButtonUp(event);
}
}
//////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::OnLButtonUp(QMouseEvent* event)
{
if (!m_pSpline)
{
return;
}
if (m_bTracking)
{
StopTracking(event->pos());
}
}
//////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::OnRButtonUp([[maybe_unused]] QMouseEvent* event)
{
if (!m_pSpline)
{
return;
}
}
/////////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::SetActiveKey(int nIndex)
{
ClearSelection();
//Activate New Key
if (nIndex >= 0)
{
m_bSelectedKeys[nIndex] = true;
}
m_nActiveKey = nIndex;
update();
SendNotifyEvent(CLRGRDN_ACTIVE_KEY_CHANGE);
}
/////////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::SetSpline(ISplineInterpolator* pSpline, BOOL bRedraw)
{
if (pSpline != m_pSpline)
{
//if (pSpline && pSpline->GetNumDimensions() != 3)
//return;
m_pSpline = pSpline;
m_nActiveKey = -1;
}
ClearSelection();
if (bRedraw)
{
update();
}
}
//////////////////////////////////////////////////////////////////////////
ISplineInterpolator* CColorGradientCtrl::GetSpline()
{
return m_pSpline;
}
/////////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::keyPressEvent(QKeyEvent* event)
{
BOOL bProcessed = false;
if (m_nActiveKey != -1 && m_pSpline)
{
switch (event->key())
{
case Qt::Key_Delete:
{
RemoveKey(m_nActiveKey);
bProcessed = true;
} break;
case Qt::Key_Up:
{
CUndo undo("Move Spline Key");
QPoint point = KeyToPoint(m_nActiveKey);
point.rx() -= 1;
SendNotifyEvent(CLRGRDN_BEFORE_CHANGE);
TrackKey(point);
bProcessed = true;
} break;
case Qt::Key_Down:
{
CUndo undo("Move Spline Key");
QPoint point = KeyToPoint(m_nActiveKey);
point.rx() += 1;
SendNotifyEvent(CLRGRDN_BEFORE_CHANGE);
TrackKey(point);
bProcessed = true;
} break;
case Qt::Key_Left:
{
CUndo undo("Move Spline Key");
QPoint point = KeyToPoint(m_nActiveKey);
point.rx() -= 1;
SendNotifyEvent(CLRGRDN_BEFORE_CHANGE);
TrackKey(point);
bProcessed = true;
} break;
case Qt::Key_Right:
{
CUndo undo("Move Spline Key");
QPoint point = KeyToPoint(m_nActiveKey);
point.rx() += 1;
SendNotifyEvent(CLRGRDN_BEFORE_CHANGE);
TrackKey(point);
bProcessed = true;
} break;
default:
break; //do nothing
}
update();
}
event->setAccepted(bProcessed);
}
//////////////////////////////////////////////////////////////////////////////
CColorGradientCtrl::EHitCode CColorGradientCtrl::HitTest(QPoint point)
{
if (!m_pSpline)
{
return HIT_NOTHING;
}
ISplineInterpolator::ValueType val;
float time;
PointToTimeValue(point, time, val);
QRect rc = rect();
m_nHitKeyIndex = -1;
if (rc.contains(point))
{
m_nHitKeyDist = 0xFFFF;
m_hitCode = HIT_SPLINE;
for (int i = 0; i < m_pSpline->GetKeyCount(); i++)
{
QPoint splinePt = TimeToPoint(m_pSpline->GetKeyTime(i));
if (abs(point.x() - splinePt.x()) < abs(m_nHitKeyDist))
{
m_nHitKeyIndex = i;
m_nHitKeyDist = point.x() - splinePt.x();
}
}
if (abs(m_nHitKeyDist) < 4)
{
m_hitCode = HIT_KEY;
}
}
else
{
m_hitCode = HIT_NOTHING;
}
return m_hitCode;
}
///////////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::StartTracking()
{
m_bTracking = true;
GetIEditor()->BeginUndo();
SendNotifyEvent(CLRGRDN_BEFORE_CHANGE);
setCursor(CMFCUtils::LoadCursor(IDC_ARRBLCKCROSS));
}
//////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::TrackKey(QPoint point)
{
if (point.x() < m_rcGradient.left() || point.y() > m_rcGradient.right())
{
return;
}
int nKey = m_nHitKeyIndex;
if (nKey >= 0)
{
ISplineInterpolator::ValueType val;
float time;
PointToTimeValue(point, time, val);
// Clamp to min/max time.
if (time < m_fMinTime || time > m_fMaxTime)
{
return;
}
int i;
for (i = 0; i < m_pSpline->GetKeyCount(); i++)
{
// Switch to next key.
if ((m_pSpline->GetKeyTime(i) < time && i > nKey) ||
(m_pSpline->GetKeyTime(i) > time && i < nKey))
{
m_pSpline->SetKeyTime(nKey, time);
m_pSpline->Update();
SetActiveKey(i);
m_nHitKeyIndex = i;
return;
}
}
if (!m_bLockFirstLastKey || (nKey != 0 && nKey != m_pSpline->GetKeyCount() - 1))
{
m_pSpline->SetKeyTime(nKey, time);
m_pSpline->Update();
}
SendNotifyEvent(CLRGRDN_CHANGE);
if (m_updateCallback)
{
m_updateCallback(this);
}
update();
}
}
//////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::StopTracking(QPoint point)
{
if (!m_bTracking)
{
return;
}
GetIEditor()->AcceptUndo("Spline Move");
if (m_nHitKeyIndex >= 0)
{
QRect rc = rect();
rc = rc.marginsAdded(QMargins(100, 100, 100, 100));
if (!rc.contains(point))
{
RemoveKey(m_nHitKeyIndex);
}
}
m_bTracking = false;
}
//////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::EditKey(int nKey)
{
if (!m_pSpline)
{
return;
}
if (nKey < 0 || nKey >= m_pSpline->GetKeyCount())
{
return;
}
SetActiveKey(nKey);
ISplineInterpolator::ValueType val;
m_pSpline->GetKeyValue(nKey, val);
SendNotifyEvent(CLRGRDN_BEFORE_CHANGE);
AzQtComponents::ColorPicker dlg(AzQtComponents::ColorPicker::Configuration::RGB);
dlg.setCurrentColor(ValueToColor(val));
dlg.setSelectedColor(ValueToColor(val));
connect(&dlg, &AzQtComponents::ColorPicker::currentColorChanged, this, &CColorGradientCtrl::OnKeyColorChanged);
if (dlg.exec() == QDialog::Accepted)
{
CUndo undo("Modify Gradient Color");
OnKeyColorChanged(dlg.selectedColor());
}
else
{
OnKeyColorChanged(ValueToColor(val));
}
}
//////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::OnKeyColorChanged(const AZ::Color& color)
{
int nKey = m_nActiveKey;
if (!m_pSpline)
{
return;
}
if (nKey < 0 || nKey >= m_pSpline->GetKeyCount())
{
return;
}
ISplineInterpolator::ValueType val;
ColorToValue(color, val);
m_pSpline->SetKeyValue(nKey, val);
update();
if (m_bLockFirstLastKey)
{
if (nKey == 0)
{
m_pSpline->SetKeyValue(m_pSpline->GetKeyCount() - 1, val);
}
else if (nKey == m_pSpline->GetKeyCount() - 1)
{
m_pSpline->SetKeyValue(0, val);
}
}
m_pSpline->Update();
SendNotifyEvent(CLRGRDN_CHANGE);
if (m_updateCallback)
{
m_updateCallback(this);
}
GetIEditor()->UpdateViews(eRedrawViewports);
}
//////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::RemoveKey(int nKey)
{
if (!m_pSpline)
{
return;
}
if (m_bLockFirstLastKey)
{
if (nKey == 0 || nKey == m_pSpline->GetKeyCount() - 1)
{
return;
}
}
CUndo undo("Remove Spline Key");
SendNotifyEvent(CLRGRDN_BEFORE_CHANGE);
m_nActiveKey = -1;
m_nHitKeyIndex = -1;
if (m_pSpline)
{
m_pSpline->RemoveKey(nKey);
m_pSpline->Update();
}
SendNotifyEvent(CLRGRDN_CHANGE);
if (m_updateCallback)
{
m_updateCallback(this);
}
update();
}
//////////////////////////////////////////////////////////////////////////
int CColorGradientCtrl::InsertKey(QPoint point)
{
CUndo undo("Spline Insert Key");
ISplineInterpolator::ValueType val;
float time;
PointToTimeValue(point, time, val);
if (time < m_fMinTime || time > m_fMaxTime)
{
return -1;
}
int i;
for (i = 0; i < m_pSpline->GetKeyCount(); i++)
{
// Skip if any key already have time that is very close.
if (fabs(m_pSpline->GetKeyTime(i) - time) < MIN_TIME_EPSILON)
{
return i;
}
}
SendNotifyEvent(CLRGRDN_BEFORE_CHANGE);
m_pSpline->InsertKey(time, val);
m_pSpline->Interpolate(time, val);
ClearSelection();
update();
SendNotifyEvent(CLRGRDN_CHANGE);
if (m_updateCallback)
{
m_updateCallback(this);
}
for (i = 0; i < m_pSpline->GetKeyCount(); i++)
{
// Find key with added time.
if (m_pSpline->GetKeyTime(i) == time)
{
return i;
}
}
return -1;
}
//////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::ClearSelection()
{
m_nActiveKey = -1;
if (m_pSpline)
{
m_bSelectedKeys.resize(m_pSpline->GetKeyCount());
}
for (int i = 0; i < (int)m_bSelectedKeys.size(); i++)
{
m_bSelectedKeys[i] = false;
}
}
//////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::SetTimeMarker(float fTime)
{
if (!m_pSpline)
{
return;
}
{
QPoint pt = TimeToPoint(m_fTimeMarker);
QRect rc = QRect(pt.x(), m_rcGradient.top(), 0, m_rcGradient.bottom() - m_rcGradient.top()).normalized();
rc += QMargins(1, 0, 1, 0);
update(rc);
}
{
QPoint pt = TimeToPoint(fTime);
QRect rc = QRect(pt.x(), m_rcGradient.top(), 0, m_rcGradient.bottom() - m_rcGradient.top()).normalized();
rc += QMargins(1, 0, 1, 0);
update(rc);
}
m_fTimeMarker = fTime;
}
//////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::SendNotifyEvent(int nEvent)
{
switch (nEvent)
{
case CLRGRDN_BEFORE_CHANGE:
emit beforeChange();
break;
case CLRGRDN_CHANGE:
emit change();
break;
case CLRGRDN_ACTIVE_KEY_CHANGE:
emit activeKeyChange();
break;
}
}
//////////////////////////////////////////////////////////////////////////
AZ::Color CColorGradientCtrl::ValueToColor(ISplineInterpolator::ValueType val)
{
const AZ::Color color(val[0], val[1], val[2], 1.0);
return color.LinearToGamma();
}
//////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::ColorToValue(const AZ::Color& col, ISplineInterpolator::ValueType& val)
{
const AZ::Color colLin = col.GammaToLinear();
val[0] = colLin.GetR();
val[1] = colLin.GetG();
val[2] = colLin.GetB();
val[3] = 0;
}
void CColorGradientCtrl::SetNoTimeMarker(bool noTimeMarker)
{
m_bNoTimeMarker = noTimeMarker;
update();
}
#include <Controls/moc_ColorGradientCtrl.cpp>
+167
View File
@@ -0,0 +1,167 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef CRYINCLUDE_EDITOR_CONTROLS_COLORGRADIENTCTRL_H
#define CRYINCLUDE_EDITOR_CONTROLS_COLORGRADIENTCTRL_H
#pragma once
#if !defined(Q_MOC_RUN)
#include <QWidget>
#include <ISplines.h>
#include "Controls/WndGridHelper.h"
#endif
namespace AZ
{
class Color;
}
// Notify event sent when spline is being modified.
#define CLRGRDN_CHANGE (0x0001)
// Notify event sent just before when spline is modified.
#define CLRGRDN_BEFORE_CHANGE (0x0002)
// Notify event sent when the active key changes
#define CLRGRDN_ACTIVE_KEY_CHANGE (0x0003)
//////////////////////////////////////////////////////////////////////////
// Spline control.
//////////////////////////////////////////////////////////////////////////
class CColorGradientCtrl
: public QWidget
{
Q_OBJECT
public:
CColorGradientCtrl(QWidget* parent = nullptr);
virtual ~CColorGradientCtrl();
//Key functions
int GetActiveKey() { return m_nActiveKey; };
void SetActiveKey(int nIndex);
int InsertKey(QPoint point);
// Turns on/off zooming and scroll support.
void SetNoZoom([[maybe_unused]] bool bNoZoom) { m_bNoZoom = false; };
void SetTimeRange(float tmin, float tmax) { m_fMinTime = tmin; m_fMaxTime = tmax; }
void SetValueRange(float tmin, float tmax) { m_fMinValue = tmin; m_fMaxValue = tmax; }
void SetTooltipValueScale(float x, float y) { m_fTooltipScaleX = x; m_fTooltipScaleY = y; };
// Lock value of first and last key to be the same.
void LockFirstAndLastKeys(bool bLock) { m_bLockFirstLastKey = bLock; }
void SetSpline(ISplineInterpolator* pSpline, BOOL bRedraw = FALSE);
ISplineInterpolator* GetSpline();
void SetTimeMarker(float fTime);
// Zoom in pixels per time unit.
void SetZoom(float fZoom);
void SetOrigin(float fOffset);
typedef AZStd::function<void(CColorGradientCtrl*)> UpdateCallback;
void SetUpdateCallback(const UpdateCallback& cb) { m_updateCallback = cb; };
void SetNoTimeMarker(bool noTimeMarker);
signals:
void change();
void beforeChange();
void activeKeyChange();
protected:
enum EHitCode
{
HIT_NOTHING,
HIT_KEY,
HIT_SPLINE,
};
void paintEvent(QPaintEvent* e);
void resizeEvent(QResizeEvent* event) override;
void mousePressEvent(QMouseEvent* event) override;
void mouseReleaseEvent(QMouseEvent* event) override;
void OnLButtonDown(QMouseEvent* event);
void mouseMoveEvent(QMouseEvent* event) override;
void OnLButtonUp(QMouseEvent* event);
void OnRButtonUp(QMouseEvent* event);
void mouseDoubleClickEvent(QMouseEvent* event) override;
void OnRButtonDown(QMouseEvent* event);
void keyPressEvent(QKeyEvent* event) override;
// Drawing functions
void DrawGradient(QPaintEvent* e, QPainter* painter);
void DrawKeys(QPaintEvent* e, QPainter* painter);
void UpdateTooltip(QPoint pos);
EHitCode HitTest(QPoint point);
//Tracking support helper functions
void StartTracking();
void TrackKey(QPoint point);
void StopTracking(QPoint point);
void RemoveKey(int nKey);
void EditKey(int nKey);
QPoint KeyToPoint(int nKey);
QPoint TimeToPoint(float time);
void PointToTimeValue(QPoint point, float& time, ISplineInterpolator::ValueType& val);
float XOfsToTime(int x);
QPoint XOfsToPoint(int x);
AZ::Color XOfsToColor(int x);
AZ::Color TimeToColor(float time);
void ClearSelection();
void SendNotifyEvent(int nEvent);
AZ::Color ValueToColor(ISplineInterpolator::ValueType val);
void ColorToValue(const AZ::Color& col, ISplineInterpolator::ValueType& val);
private:
void OnKeyColorChanged(const AZ::Color& color);
private:
ISplineInterpolator* m_pSpline;
bool m_bAutoDelete;
bool m_bNoZoom;
QRect m_rcClipRect;
QRect m_rcGradient;
QRect m_rcKeys;
QPoint m_hitPoint;
EHitCode m_hitCode;
int m_nHitKeyIndex;
int m_nHitKeyDist;
QPoint m_curvePoint;
float m_fTimeMarker;
int m_nActiveKey;
int m_nKeyDrawRadius;
bool m_bTracking;
float m_fMinTime, m_fMaxTime;
float m_fMinValue, m_fMaxValue;
float m_fTooltipScaleX, m_fTooltipScaleY;
bool m_bLockFirstLastKey;
bool m_bNoTimeMarker;
std::vector<int> m_bSelectedKeys;
UpdateCallback m_updateCallback;
CWndGridHelper m_grid;
};
#endif // CRYINCLUDE_EDITOR_CONTROLS_COLORGRADIENTCTRL_H
File diff suppressed because it is too large Load Diff
+203
View File
@@ -0,0 +1,203 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef CRYINCLUDE_EDITOR_CONTROLS_CONSOLESCB_H
#define CRYINCLUDE_EDITOR_CONTROLS_CONSOLESCB_H
#pragma once
#if !defined(Q_MOC_RUN)
#include "Settings.h"
#include <AzToolsFramework/Editor/EditorSettingsAPIBus.h>
#include <QLineEdit>
#include <QPlainTextEdit>
#include <QAbstractTableModel>
#include <QDialog>
#include <QLineEdit>
#include <QPlainTextEdit>
#include <QPushButton>
#include <QScopedPointer>
#include <QStyledItemDelegate>
#endif
class QMenu;
class ConsoleWidget;
class QFocusEvent;
class QTableView;
class CVarBlock;
namespace Ui {
class Console;
}
struct ConsoleLine
{
QString text;
bool newLine;
};
typedef std::deque<ConsoleLine> Lines;
class ConsoleLineEdit
: public QLineEdit
{
Q_OBJECT
public:
explicit ConsoleLineEdit(QWidget* parent = nullptr);
protected:
void mouseDoubleClickEvent(QMouseEvent* ev) override;
void keyPressEvent(QKeyEvent* ev) override;
bool event(QEvent* ev) override;
signals:
void variableEditorRequested();
private:
void DisplayHistory(bool bForward);
void ResetHistoryIndex();
QStringList m_history;
unsigned int m_historyIndex;
bool m_bReusedHistory;
};
class ConsoleTextEdit
: public QPlainTextEdit
{
Q_OBJECT
public:
explicit ConsoleTextEdit(QWidget* parent = nullptr);
virtual bool event(QEvent* theEvent) override;
signals:
void searchBarRequested();
private:
void showContextMenu(const QPoint& pt);
QScopedPointer<QMenu> m_contextMenu;
};
class ConsoleVariableItemDelegate
: public QStyledItemDelegate
{
Q_OBJECT
public:
explicit ConsoleVariableItemDelegate(QObject* parent = nullptr);
// Item delegate overrides for creating the custom editor widget and
// setting/retrieving data to/from it
void setEditorData(QWidget* editor, const QModelIndex& index) const override;
void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override;
QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
void SetVarBlock(CVarBlock* varBlock);
private:
CVarBlock* m_varBlock;
};
class ConsoleVariableModel
: public QAbstractTableModel
{
Q_OBJECT
public:
enum CustomRoles
{
VariableCustomRole = Qt::UserRole
};
explicit ConsoleVariableModel(QObject* parent = nullptr);
// Table model overrides
QVariant data(const QModelIndex& index, int role) const override;
bool setData(const QModelIndex& index, const QVariant& value, int role) override;
int rowCount(const QModelIndex& = {}) const override;
int columnCount(const QModelIndex& = {}) const override;
Qt::ItemFlags flags(const QModelIndex& index) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
void SetVarBlock(CVarBlock* varBlock);
void ClearModifiedRows();
private:
CVarBlock* m_varBlock;
QList<int> m_modifiedRows;
};
class ConsoleVariableEditor
: public QWidget
{
Q_OBJECT
public:
explicit ConsoleVariableEditor(QWidget* parent = nullptr);
static void RegisterViewClass();
void HandleVariableRowUpdated(ICVar* pCVar);
protected:
void showEvent(QShowEvent* event) override;
private:
void SetVarBlock(CVarBlock* varBlock);
QTableView* m_tableView;
ConsoleVariableModel* m_model;
ConsoleVariableItemDelegate* m_itemDelegate;
CVarBlock* m_varBlock;
};
class CConsoleSCB
: public QWidget
, private AzToolsFramework::EditorPreferencesNotificationBus::Handler
{
Q_OBJECT
public:
explicit CConsoleSCB(QWidget* parent = nullptr);
~CConsoleSCB();
static void RegisterViewClass();
void SetInputFocus();
void AddToConsole(const QString& text, bool bNewLine);
void FlushText();
QSize sizeHint() const override;
QSize minimumSizeHint() const override;
static CConsoleSCB* GetCreatedInstance();
static void AddToPendingLines(const QString& text, bool bNewLine); // call this function instead of AddToConsole() until an instance of CConsoleSCB exists to prevent messages from getting lost
// EditorPreferencesNotificationBus...
void OnEditorPreferencesChanged() override;
void RefreshStyle();
private Q_SLOTS:
void showVariableEditor();
void toggleConsoleSearch();
void findPrevious();
void findNext();
private:
QScopedPointer<Ui::Console> ui;
int m_richEditTextLength;
Lines m_lines;
static Lines s_pendingLines;
QList<QColor> m_colorTable;
AzToolsFramework::ConsoleColorTheme m_backgroundTheme;
class SearchHighlighter;
SearchHighlighter* m_highlighter;
};
#endif // CRYINCLUDE_EDITOR_CONTROLS_CONSOLESCB_H
+5
View File
@@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/controls/img">
<file alias="cvar_dark.bmp">../res/cvar_dark.bmp</file>
</qresource>
</RCC>
+238
View File
@@ -0,0 +1,238 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Console</class>
<widget class="QWidget" name="Console">
<property name="enabled">
<bool>true</bool>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>120</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Ignored">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Console</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="ConsoleTextEdit" name="textEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<property name="undoRedoEnabled">
<bool>false</bool>
</property>
<property name="lineWrapMode">
<enum>QPlainTextEdit::NoWrap</enum>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="findBar" native="true">
<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="label">
<property name="text">
<string>Find: </string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEditFind">
<property name="enabled">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="findPrevButton">
<property name="text">
<string>Find Previous</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="findNextButton">
<property name="text">
<string>Find Next</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="closeButton">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="container2" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>20</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>20</height>
</size>
</property>
<property name="autoFillBackground">
<bool>true</bool>
</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="QToolButton" name="button">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>20</width>
<height>30</height>
</size>
</property>
<property name="baseSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="findButton">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="ConsoleLineEdit" name="lineEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>ConsoleLineEdit</class>
<extends>QLineEdit</extends>
<header>Controls/ConsoleSCB.h</header>
</customwidget>
<customwidget>
<class>ConsoleTextEdit</class>
<extends>QPlainTextEdit</extends>
<header>Controls/ConsoleSCB.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>lineEdit</tabstop>
<tabstop>button</tabstop>
<tabstop>textEdit</tabstop>
</tabstops>
<resources>
<include location="ConsoleSCB.qrc"/>
</resources>
<connections/>
</ui>
+542
View File
@@ -0,0 +1,542 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "ConsoleSCBMFC.h"
#include "PropertiesDialog.h"
#include "QtViewPaneManager.h"
#include "Core/QtEditorApplication.h"
#include <Controls/ui_ConsoleSCBMFC.h>
#include <QtUtil.h>
#include <QtUtilWin.h>
#include <QtCore/QStringList>
#include <QtCore/QScopedPointer>
#include <QtCore/QPoint>
#include <QtGui/QCursor>
#include <QtGui/QMouseEvent>
#include <QtWidgets/QStyle>
#include <QtWidgets/QStyleFactory>
#include <QtWidgets/QMenu>
#include <QtWidgets/QScrollBar>
#include <QtWidgets/QVBoxLayout>
#include <vector>
#include <iostream>
namespace MFC
{
static CPropertiesDialog* gPropertiesDlg = nullptr;
static CString mfc_popup_helper(HWND hwnd, int x, int y);
static CConsoleSCB* s_consoleSCB = nullptr;
static QString RemoveColorCode(const QString& text, int& iColorCode)
{
QString cleanString;
cleanString.reserve(text.size());
const int textSize = text.size();
for (int i = 0; i < textSize; ++i)
{
QChar c = text.at(i);
bool isLast = i == textSize - 1;
if (c == '$' && !isLast && text.at(i + 1).isDigit())
{
if (iColorCode == 0)
{
iColorCode = text.at(i + 1).digitValue();
}
++i;
continue;
}
if (c == '\r' || c == '\n')
{
++i;
continue;
}
cleanString.append(c);
}
return cleanString;
}
ConsoleLineEdit::ConsoleLineEdit(QWidget* parent)
: QLineEdit(parent)
, m_historyIndex(0)
, m_bReusedHistory(false)
{
}
void ConsoleLineEdit::mousePressEvent(QMouseEvent* ev)
{
if (ev->type() == QEvent::MouseButtonPress && ev->button() & Qt::RightButton)
{
Q_EMIT variableEditorRequested();
}
QLineEdit::mousePressEvent(ev);
}
void ConsoleLineEdit::mouseDoubleClickEvent(QMouseEvent* ev)
{
Q_EMIT variableEditorRequested();
}
bool ConsoleLineEdit::event(QEvent* ev)
{
// Tab key doesn't go to keyPressEvent(), must be processed here
if (ev->type() != QEvent::KeyPress)
{
return QLineEdit::event(ev);
}
QKeyEvent* ke = static_cast<QKeyEvent*>(ev);
if (ke->key() != Qt::Key_Tab)
{
return QLineEdit::event(ev);
}
QString inputStr = text();
QString newStr;
QStringList tokens = inputStr.split(" ");
inputStr = tokens.isEmpty() ? QString() : tokens.first();
IConsole* console = GetIEditor()->GetSystem()->GetIConsole();
const bool ctrlPressed = ke->modifiers() & Qt::ControlModifier;
CString cstring = QtUtil::ToCString(inputStr); // TODO: Use QString once the backend stops using QString
if (ctrlPressed)
{
newStr = QtUtil::ToString(console->AutoCompletePrev(cstring));
}
else
{
newStr = QtUtil::ToString(console->ProcessCompletion(cstring));
newStr = QtUtil::ToString(console->AutoComplete(cstring));
if (newStr.isEmpty())
{
newStr = QtUtil::ToQString(GetIEditor()->GetCommandManager()->AutoComplete(QtUtil::ToString(newStr)));
}
}
if (!newStr.isEmpty())
{
newStr += " ";
setText(newStr);
}
deselect();
return true;
}
void ConsoleLineEdit::keyPressEvent(QKeyEvent* ev)
{
IConsole* console = GetIEditor()->GetSystem()->GetIConsole();
auto commandManager = GetIEditor()->GetCommandManager();
console->ResetAutoCompletion();
switch (ev->key())
{
case Qt::Key_Enter:
case Qt::Key_Return:
{
QString str = text().trimmed();
if (!str.isEmpty())
{
if (commandManager->IsRegistered(QtUtil::ToCString(str)))
{
commandManager->Execute(QtUtil::ToString(str));
}
else
{
CLogFile::WriteLine(QtUtil::ToCString(str));
GetIEditor()->GetSystem()->GetIConsole()->ExecuteString(QtUtil::ToCString(str));
}
// If a history command was reused directly via up arrow enter, do not reset history index
if (m_history.size() > 0 && m_historyIndex < m_history.size() && m_history[m_historyIndex] == str)
{
m_bReusedHistory = true;
}
else
{
m_historyIndex = m_history.size();
}
// Do not add the same string if it is the top of the stack, but allow duplicate entries otherwise
if (m_history.isEmpty() || m_history.back() != str)
{
m_history.push_back(str);
if (!m_bReusedHistory)
{
m_historyIndex = m_history.size();
}
}
}
else
{
m_historyIndex = m_history.size();
}
setText(QString());
break;
}
case Qt::Key_AsciiTilde: // ~
case Qt::Key_Agrave: // `
// disable log.
GetIEditor()->ShowConsole(false);
setText(QString());
m_historyIndex = m_history.size();
break;
case Qt::Key_Escape:
setText(QString());
m_historyIndex = m_history.size();
break;
case Qt::Key_Up:
DisplayHistory(false /*bForward*/);
break;
case Qt::Key_Down:
DisplayHistory(true /*bForward*/);
break;
default:
QLineEdit::keyPressEvent(ev);
}
}
void ConsoleLineEdit::DisplayHistory(bool bForward)
{
if (m_history.isEmpty())
{
return;
}
// Immediately after reusing a history entry, ensure up arrow re-displays command just used
if (!m_bReusedHistory || bForward)
{
m_historyIndex = static_cast<unsigned int>(clamp_tpl(static_cast<int>(m_historyIndex) + (bForward ? 1 : -1), 0, m_history.size() - 1));
}
m_bReusedHistory = false;
setText(m_history[m_historyIndex]);
}
ConsoleTextEdit::ConsoleTextEdit(QWidget* parent)
: QTextEdit(parent)
{
}
Lines CConsoleSCB::s_pendingLines;
CConsoleSCB::CConsoleSCB(QWidget* parent)
: QWidget(parent)
, ui(new Ui::ConsoleMFC())
, m_richEditTextLength(0)
, m_backgroundTheme(gSettings.consoleBackgroundColorTheme)
{
m_lines = s_pendingLines;
s_pendingLines.clear();
s_consoleSCB = this;
ui->setupUi(this);
setMinimumHeight(120);
// Setup the color table for the default (light) theme
m_colorTable << QColor(0, 0, 0)
<< QColor(0, 0, 0)
<< QColor(0, 0, 200) // blue
<< QColor(0, 200, 0) // green
<< QColor(200, 0, 0) // red
<< QColor(0, 200, 200) // cyan
<< QColor(128, 112, 0) // yellow
<< QColor(200, 0, 200) // red+blue
<< QColor(0x000080ff)
<< QColor(0x008f8f8f);
OnStyleSettingsChanged();
connect(ui->button, &QPushButton::clicked, this, &CConsoleSCB::showVariableEditor);
connect(ui->lineEdit, &MFC::ConsoleLineEdit::variableEditorRequested, this, &MFC::CConsoleSCB::showVariableEditor);
connect(Editor::EditorQtApplication::instance(), &Editor::EditorQtApplication::skinChanged, this, &MFC::CConsoleSCB::OnStyleSettingsChanged);
if (GetIEditor()->IsInConsolewMode())
{
// Attach / register edit box
//CLogFile::AttachEditBox(m_edit.GetSafeHwnd()); // FIXME
}
}
CConsoleSCB::~CConsoleSCB()
{
s_consoleSCB = nullptr;
delete gPropertiesDlg;
gPropertiesDlg = nullptr;
CLogFile::AttachEditBox(nullptr);
}
void CConsoleSCB::RegisterViewClass()
{
QtViewOptions opts;
opts.preferedDockingArea = Qt::BottomDockWidgetArea;
opts.isDeletable = false;
opts.isStandard = true;
opts.showInMenu = true;
opts.builtInActionId = ID_VIEW_CONSOLEWINDOW;
opts.sendViewPaneNameBackToAmazonAnalyticsServers = true;
RegisterQtViewPane<CConsoleSCB>(GetIEditor(), LyViewPane::Console, LyViewPane::CategoryTools, opts);
}
void CConsoleSCB::OnStyleSettingsChanged()
{
ui->button->setIcon(QIcon(QString(":/controls/img/cvar_dark.bmp")));
// Set the debug/warning text colors appropriately for the background theme
// (e.g. not have black text on black background)
QColor textColor = Qt::black;
m_backgroundTheme = gSettings.consoleBackgroundColorTheme;
if (m_backgroundTheme == SEditorSettings::ConsoleColorTheme::Dark)
{
textColor = Qt::white;
}
m_colorTable[0] = textColor;
m_colorTable[1] = textColor;
QColor bgColor;
if (!GetIEditor()->IsInConsolewMode() && CConsoleSCB::GetCreatedInstance() && m_backgroundTheme == SEditorSettings::ConsoleColorTheme::Dark)
{
bgColor = Qt::black;
}
else
{
bgColor = Qt::white;
}
ui->textEdit->setStyleSheet(QString("QTextEdit{ background: %1 }").arg(bgColor.name(QColor::HexRgb)));
// Clear out the console text when we change our background color since
// some of the previous text colors may not be appropriate for the
// new background color
ui->textEdit->clear();
}
void CConsoleSCB::showVariableEditor()
{
const QPoint cursorPos = QCursor::pos();
CString str = mfc_popup_helper(0, cursorPos.x(), cursorPos.y());
if (!str.IsEmpty())
{
ui->lineEdit->setText(QtUtil::ToQString(str));
}
}
void CConsoleSCB::SetInputFocus()
{
ui->lineEdit->setFocus();
ui->lineEdit->setText(QString());
}
void CConsoleSCB::AddToConsole(const QString& text, bool bNewLine)
{
m_lines.push_back({ text, bNewLine });
FlushText();
}
void CConsoleSCB::FlushText()
{
if (m_lines.empty())
{
return;
}
// Store our current cursor in case we need to restore it, and check if
// the user has scrolled the text edit away from the bottom
const QTextCursor oldCursor = ui->textEdit->textCursor();
QScrollBar* scrollBar = ui->textEdit->verticalScrollBar();
const int oldScrollValue = scrollBar->value();
bool scrolledOffBottom = oldScrollValue != scrollBar->maximum();
ui->textEdit->moveCursor(QTextCursor::End);
QTextCursor textCursor = ui->textEdit->textCursor();
while (!m_lines.empty())
{
ConsoleLine line = m_lines.front();
m_lines.pop_front();
int iColor = 0;
QString text = MFC::RemoveColorCode(line.text, iColor);
if (iColor < 0 || iColor >= m_colorTable.size())
{
iColor = 0;
}
if (line.newLine)
{
text = QtUtil::trimRight(text);
text = "\r\n" + text;
}
QTextCharFormat format;
const QColor color(m_colorTable[iColor]);
format.setForeground(color);
if (iColor != 0)
{
format.setFontWeight(QFont::Bold);
}
textCursor.setCharFormat(format);
textCursor.insertText(text);
}
// If the user has selected some text in the text edit area or has scrolled
// away from the bottom, then restore the previous cursor and keep the scroll
// bar in the same location
if (oldCursor.hasSelection() || scrolledOffBottom)
{
ui->textEdit->setTextCursor(oldCursor);
scrollBar->setValue(oldScrollValue);
}
// Otherwise scroll to the bottom so the latest text can be seen
else
{
scrollBar->setValue(scrollBar->maximum());
}
}
QSize CConsoleSCB::minimumSizeHint() const
{
return QSize(-1, -1);
}
QSize CConsoleSCB::sizeHint() const
{
return QSize(100, 100);
}
/** static */
void CConsoleSCB::AddToPendingLines(const QString& text, bool bNewLine)
{
s_pendingLines.push_back({ text, bNewLine });
}
static CVarBlock* VarBlockFromConsoleVars()
{
IConsole* console = GetIEditor()->GetSystem()->GetIConsole();
std::vector<const char*> cmds;
cmds.resize(console->GetNumVars());
size_t cmdCount = console->GetSortedVars(&cmds[0], cmds.size());
CVarBlock* vb = new CVarBlock;
IVariable* pVariable = 0;
for (int i = 0; i < cmdCount; i++)
{
ICVar* pCVar = console->GetCVar(cmds[i]);
if (!pCVar)
{
continue;
}
int varType = pCVar->GetType();
switch (varType)
{
case CVAR_INT:
pVariable = new CVariable<int>();
pVariable->Set(pCVar->GetIVal());
break;
case CVAR_FLOAT:
pVariable = new CVariable<float>();
pVariable->Set(pCVar->GetFVal());
break;
case CVAR_STRING:
pVariable = new CVariable<CString>();
pVariable->Set(pCVar->GetString());
break;
default:
assert(0);
}
pVariable->SetDescription(pCVar->GetHelp());
pVariable->SetName(cmds[i]);
if (pVariable)
{
vb->AddVariable(pVariable);
}
}
return vb;
}
static void OnConsoleVariableUpdated(IVariable* pVar)
{
if (!pVar)
{
return;
}
CString varName = pVar->GetName();
ICVar* pCVar = GetIEditor()->GetSystem()->GetIConsole()->GetCVar(varName);
if (!pCVar)
{
return;
}
if (pVar->GetType() == IVariable::INT)
{
int val;
pVar->Get(val);
pCVar->Set(val);
}
else if (pVar->GetType() == IVariable::FLOAT)
{
float val;
pVar->Get(val);
pCVar->Set(val);
}
else if (pVar->GetType() == IVariable::STRING)
{
CString val;
pVar->Get(val);
pCVar->Set(val);
}
}
static CString mfc_popup_helper(HWND hwnd, int x, int y)
{
IConsole* console = GetIEditor()->GetSystem()->GetIConsole();
TSmartPtr<CVarBlock> vb = VarBlockFromConsoleVars();
XmlNodeRef node;
if (!gPropertiesDlg)
{
gPropertiesDlg = new CPropertiesDialog("Console Variables", node, AfxGetMainWnd(), true);
}
if (!gPropertiesDlg->m_hWnd)
{
gPropertiesDlg->Create(CPropertiesDialog::IDD, AfxGetMainWnd());
gPropertiesDlg->SetUpdateCallback(AZStd::bind(OnConsoleVariableUpdated, AZStd::placeholders::_1));
}
gPropertiesDlg->ShowWindow(SW_SHOW);
gPropertiesDlg->BringWindowToTop();
gPropertiesDlg->GetPropertyCtrl()->AddVarBlock(vb);
return "";
}
CConsoleSCB* CConsoleSCB::GetCreatedInstance()
{
return s_consoleSCB;
}
} // namespace MFC
#include <Controls/moc_ConsoleSCBMFC.cpp>
+110
View File
@@ -0,0 +1,110 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef CRYINCLUDE_EDITOR_CONTROLS_CONSOLESCBMFC_H
#define CRYINCLUDE_EDITOR_CONTROLS_CONSOLESCBMFC_H
#pragma once
#if !defined(Q_MOC_RUN)
#include <QtWidgets/QLineEdit>
#include <QtWidgets/QTextEdit>
#include <QtWidgets/QPushButton>
#include "ConsoleSCB.h"
#endif
class QMenu;
class ConsoleWidget;
class QFocusEvent;
namespace Ui {
class ConsoleMFC;
}
namespace MFC
{
struct ConsoleLine
{
QString text;
bool newLine;
};
typedef std::deque<ConsoleLine> Lines;
class ConsoleLineEdit
: public QLineEdit
{
Q_OBJECT
public:
explicit ConsoleLineEdit(QWidget* parent = nullptr);
protected:
void mousePressEvent(QMouseEvent* ev) override;
void mouseDoubleClickEvent(QMouseEvent* ev) override;
void keyPressEvent(QKeyEvent* ev) override;
bool event(QEvent* ev) override;
signals:
void variableEditorRequested();
void setWindowTitle(const QString&);
private:
void DisplayHistory(bool bForward);
QStringList m_history;
unsigned int m_historyIndex;
bool m_bReusedHistory;
};
class ConsoleTextEdit
: public QTextEdit
{
Q_OBJECT
public:
explicit ConsoleTextEdit(QWidget* parent = nullptr);
};
class CConsoleSCB
: public QWidget
{
Q_OBJECT
public:
explicit CConsoleSCB(QWidget* parent = nullptr);
~CConsoleSCB();
static void RegisterViewClass();
void SetInputFocus();
void AddToConsole(const QString& text, bool bNewLine);
void FlushText();
void showPopupAndSetTitle();
QSize sizeHint() const override;
QSize minimumSizeHint() const override;
static CConsoleSCB* GetCreatedInstance();
static void AddToPendingLines(const QString& text, bool bNewLine); // call this function instead of AddToConsole() until an instance of CConsoleSCB exists to prevent messages from getting lost
public Q_SLOTS:
void OnStyleSettingsChanged();
private Q_SLOTS:
void showVariableEditor();
private:
QScopedPointer<Ui::ConsoleMFC> ui;
int m_richEditTextLength;
Lines m_lines;
static Lines s_pendingLines;
QList<QColor> m_colorTable;
SEditorSettings::ConsoleColorTheme m_backgroundTheme;
};
} // namespace MFC
#endif // CRYINCLUDE_EDITOR_CONTROLS_CONSOLESCB_H
+132
View File
@@ -0,0 +1,132 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConsoleMFC</class>
<widget class="QWidget" name="Console">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>120</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Ignored">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Console</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QTextEdit" name="textEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="container2" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>20</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>20</height>
</size>
</property>
<property name="autoFillBackground">
<bool>true</bool>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QToolButton" name="button">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>20</width>
<height>30</height>
</size>
</property>
<property name="baseSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="MFC::ConsoleLineEdit" name="lineEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>MFC::ConsoleLineEdit</class>
<extends>QLineEdit</extends>
<header>ConsoleSCBMFC.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="ConsoleSCB.qrc"/>
</resources>
<connections/>
</ui>
+502
View File
@@ -0,0 +1,502 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "FolderTreeCtrl.h"
// Qt
#include <QMenu>
#include <QSortFilterProxyModel>
#include <QStandardItemModel>
// AzQtComponents
#include <AzQtComponents/Utilities/DesktopUtilities.h> // for AzQtComponents::ShowFileOnDesktop
enum ETreeImage
{
eTreeImage_Folder = 0,
eTreeImage_File = 2
};
enum CustomRoles
{
IsFolderRole = Qt::UserRole
};
//////////////////////////////////////////////////////////////////////////
// CFolderTreeCtrl
//////////////////////////////////////////////////////////////////////////
CFolderTreeCtrl::CFolderTreeCtrl(const QStringList& folders, const QString& fileNameSpec,
const QString& rootName, bool bDisableMonitor, bool bFlatTree, QWidget* parent)
: QTreeView(parent)
, m_rootTreeItem(nullptr)
, m_folders(folders)
, m_fileNameSpec(fileNameSpec)
, m_rootName(rootName)
, m_bDisableMonitor(bDisableMonitor)
, m_bFlatStyle(bFlatTree)
{
init(folders, fileNameSpec, rootName, bDisableMonitor, bFlatTree);
}
CFolderTreeCtrl::CFolderTreeCtrl(QWidget* parent)
: QTreeView(parent)
, m_rootTreeItem(nullptr)
, m_bDisableMonitor(false)
, m_bFlatStyle(true)
{
}
void CFolderTreeCtrl::init(const QStringList& folders, const QString& fileNameSpec, const QString& rootName, bool bDisableMonitor /*= false*/, bool bFlatTree /*= true*/)
{
m_model = new QStandardItemModel(this);
m_proxyModel = new QSortFilterProxyModel(this);
m_proxyModel->setRecursiveFilteringEnabled(true);
m_proxyModel->setSourceModel(m_model);
setModel(m_proxyModel);
m_folders = folders;
m_fileNameSpec = fileNameSpec;
m_rootName = rootName;
m_bDisableMonitor = bDisableMonitor;
m_bFlatStyle = bFlatTree;
m_fileIcon = QIcon(":/TreeView/default-icon.svg");
m_folderIcon = QIcon(":/TreeView/folder-icon.svg");
for (auto item = m_folders.begin(), end = m_folders.end(); item != end; ++item)
{
(*item) = Path::RemoveBackslash(Path::ToUnixPath((*item)));
if (CFileUtil::PathExists(*item))
{
m_foldersSegments.insert(std::make_pair((*item), Path::SplitIntoSegments((*item)).size()));
}
else if (Path::IsFolder((*item).toLocal8Bit().constData()))
{
m_foldersSegments.insert(std::make_pair((*item), Path::SplitIntoSegments((*item)).size()));
}
else
{
(*item).clear();
}
}
setHeaderHidden(true);
QObject::connect(this, &QTreeView::doubleClicked, this, &CFolderTreeCtrl::OnIndexDoubleClicked);
InitTree();
setSortingEnabled(true);
}
QString CFolderTreeCtrl::GetPath(QStandardItem* item) const
{
CTreeItem* treeItem = static_cast<CTreeItem*>(item);
if (treeItem)
{
return treeItem->GetPath();
}
return "";
}
bool CFolderTreeCtrl::IsFolder(QStandardItem* item) const
{
return item->data(IsFolderRole).toBool();
}
bool CFolderTreeCtrl::IsFile(QStandardItem* item) const
{
return !IsFolder(item);
}
CFolderTreeCtrl::~CFolderTreeCtrl()
{
// Obliterate tree items before destroying the controls
m_rootTreeItem.reset(nullptr);
// Unsubscribe from file change notifications
if (!m_bDisableMonitor)
{
CFileChangeMonitor::Instance()->Unsubscribe(this);
}
}
void CFolderTreeCtrl::OnIndexDoubleClicked(const QModelIndex& index)
{
if (!m_proxyModel || !m_model)
{
return;
}
QStandardItem* item = GetSourceItemByIndex(index);
if (item)
{
Q_EMIT ItemDoubleClicked(item);
}
}
void CFolderTreeCtrl::OnFileMonitorChange(const SFileChangeInfo& rChange)
{
const QString filePath = Path::ToUnixPath(Path::GetRelativePath(rChange.filename));
for (auto item = m_folders.begin(), end = m_folders.end(); item != end; ++item)
{
// Only look for changes in folder
if (filePath.indexOf((*item)) != 0)
{
return;
}
if (rChange.changeType == rChange.eChangeType_Created || rChange.changeType == rChange.eChangeType_RenamedNewName)
{
if (CFileUtil::PathExists(filePath))
{
LoadTreeRec(filePath);
}
else
{
AddItem(filePath);
}
}
else if (rChange.changeType == rChange.eChangeType_Deleted || rChange.changeType == rChange.eChangeType_RenamedOldName)
{
RemoveItem(filePath);
}
}
}
void CFolderTreeCtrl::contextMenuEvent(QContextMenuEvent* e)
{
if (!m_model)
{
return;
}
auto index = indexAt(e->pos());
QStandardItem* item = GetSourceItemByIndex(index);
if (!item)
{
return;
}
const QString path = GetPath(item);
QMenu menu;
QAction* editAction = menu.addAction(tr("Edit"));
connect(editAction, &QAction::triggered, this, [=]()
{
this->Edit(path);
});
QAction* showInExplorerAction = menu.addAction(tr("Show In Explorer"));
connect(showInExplorerAction, &QAction::triggered, this, [=]()
{
this->ShowInExplorer(path);
});
menu.exec(QCursor::pos());
}
void CFolderTreeCtrl::InitTree()
{
m_rootTreeItem.reset(new CTreeItem(*this, m_rootName));
for (auto item = m_folders.begin(), end = m_folders.end(); item != end; ++item)
{
if (!(*item).isEmpty())
{
LoadTreeRec((*item));
}
}
if (!m_bDisableMonitor)
{
CFileChangeMonitor::Instance()->Subscribe(this);
}
expandAll();
}
void CFolderTreeCtrl::LoadTreeRec(const QString& currentFolder)
{
CFileEnum fileEnum;
QFileInfo fileData;
QString currentFolderSlash = Path::AddSlash(currentFolder);
QString targetFolder = currentFolder;
if (currentFolder.startsWith('@'))
{
char resolvedPath[AZ_MAX_PATH_LEN] = { 0 };
if (AZ::IO::FileIOBase::GetDirectInstance()->ResolvePath(currentFolder.toLocal8Bit().constData(), resolvedPath, AZ_MAX_PATH_LEN))
{
targetFolder = resolvedPath;
}
// update the base folder name
QStringList parts = Path::SplitIntoSegments(currentFolderSlash);
if (parts.size() > 1)
{
parts.removeFirst();
currentFolderSlash = Path::AddSlash(parts.join(QDir::separator()));
}
}
for (bool bFoundFile = fileEnum.StartEnumeration(targetFolder, "*", &fileData);
bFoundFile; bFoundFile = fileEnum.GetNextFile(&fileData))
{
const QString fileName = fileData.fileName();
// Have we found a folder?
if (fileData.isDir())
{
// Skip the parent folder entries
if (fileName == "." || fileName == "..")
{
continue;
}
LoadTreeRec(currentFolderSlash + fileName);
}
AddItem(currentFolderSlash + fileName);
}
}
void CFolderTreeCtrl::AddItem(const QString& path)
{
QString folder;
QString fileNameWithoutExtension;
QString ext;
Path::Split(path, folder, fileNameWithoutExtension, ext);
auto regex = QRegExp(m_fileNameSpec, Qt::CaseInsensitive, QRegExp::Wildcard);
if (regex.exactMatch(path))
{
CTreeItem* folderTreeItem = CreateFolderItems(folder);
folderTreeItem->AddChild(fileNameWithoutExtension, path, eTreeImage_File);
}
}
void CFolderTreeCtrl::RemoveItem(const QString& path)
{
if (!CFileUtil::FileExists(path))
{
auto findIter = m_pathToTreeItem.find(path);
if (findIter != m_pathToTreeItem.end())
{
CTreeItem* foundItem = findIter->second;
foundItem->Remove();
RemoveEmptyFolderItems(Path::GetPath(path));
}
}
}
CFolderTreeCtrl::CTreeItem* CFolderTreeCtrl::GetItem(const QString& path)
{
auto findIter = m_pathToTreeItem.find(path);
if (findIter == m_pathToTreeItem.end())
{
return nullptr;
}
return findIter->second;
}
QStandardItem* CFolderTreeCtrl::GetSourceItemByIndex(const QModelIndex& index) const
{
if (!m_proxyModel || !m_model)
{
return nullptr;
}
// Since our tree view has a proxy model to handle the sorting/filtering, any index
// found on the tree view (e.g. the selected index) needs to be mapped back to the source
// model to find the actual item.
auto sourceIndex = m_proxyModel->mapToSource(index);
return m_model->itemFromIndex(sourceIndex);
}
QString CFolderTreeCtrl::CalculateFolderFullPath(const QStringList& splittedFolder, int idx)
{
QString path;
for (int segIdx = 0; segIdx <= idx; ++segIdx)
{
if (segIdx != 0)
{
path.append(QLatin1Char('/'));
}
path.append(splittedFolder[segIdx]);
}
return path;
}
CFolderTreeCtrl::CTreeItem* CFolderTreeCtrl::CreateFolderItems(const QString& folder)
{
QStringList splittedFolder = Path::SplitIntoSegments(folder);
CTreeItem* currentTreeItem = m_rootTreeItem.get();
if (!m_bFlatStyle)
{
QString currentFolder;
QString fullpath;
const int splittedFoldersCount = splittedFolder.size();
for (int idx = 0; idx < splittedFoldersCount; ++idx)
{
currentFolder = Path::RemoveBackslash(splittedFolder[idx]);
fullpath = CalculateFolderFullPath(splittedFolder, idx);
CTreeItem* folderItem = GetItem(fullpath);
if (!folderItem)
{
currentTreeItem = currentTreeItem->AddChild(currentFolder, fullpath, eTreeImage_Folder);
}
else
{
currentTreeItem = folderItem;
}
}
}
return currentTreeItem;
}
void CFolderTreeCtrl::RemoveEmptyFolderItems(const QString& folder)
{
QStringList splittedFolder = Path::SplitIntoSegments(folder);
const int splittedFoldersCount = splittedFolder.size();
QString fullpath;
for (int idx = 0; idx < splittedFoldersCount; ++idx)
{
fullpath = CalculateFolderFullPath(splittedFolder, idx);
CTreeItem* folderItem = GetItem(fullpath);
if (!folderItem)
{
continue;
}
if (!folderItem->hasChildren())
{
folderItem->Remove();
}
}
}
void CFolderTreeCtrl::Edit(const QString& path)
{
CFileUtil::EditTextFile(QtUtil::ToString(path), 0, IFileUtil::FILE_TYPE_SCRIPT);
}
void CFolderTreeCtrl::ShowInExplorer(const QString& path)
{
QString absolutePath = QDir::currentPath();
CTreeItem* root = m_rootTreeItem.get();
CTreeItem* item = GetItem(path);
if (item != root)
{
absolutePath += QStringLiteral("/%1").arg(path);
}
AzQtComponents::ShowFileOnDesktop(absolutePath);
}
QIcon CFolderTreeCtrl::GetItemIcon(int image) const
{
return image == eTreeImage_File ? m_fileIcon : m_folderIcon;
}
QList<QStandardItem*> CFolderTreeCtrl::GetSelectedItems() const
{
QList<QStandardItem*> items;
for (auto index : selectedIndexes())
{
QStandardItem* item = GetSourceItemByIndex(index);
if (item)
{
items.append(item);
}
}
return items;
}
void CFolderTreeCtrl::SetSearchFilter(const QString& searchText)
{
if (m_proxyModel)
{
m_proxyModel->setFilterFixedString(searchText);
}
}
//////////////////////////////////////////////////////////////////////////
// CFolderTreeCtrl::CTreeItem
//////////////////////////////////////////////////////////////////////////
CFolderTreeCtrl::CTreeItem::CTreeItem(CFolderTreeCtrl& folderTreeCtrl, const QString& path)
: QStandardItem(folderTreeCtrl.GetItemIcon(eTreeImage_Folder), folderTreeCtrl.m_rootName)
, m_folderTreeCtrl(folderTreeCtrl)
, m_path(path)
{
setData(true, IsFolderRole);
m_folderTreeCtrl.m_model->invisibleRootItem()->appendRow(this);
m_folderTreeCtrl.m_pathToTreeItem[ m_path ] = this;
}
CFolderTreeCtrl::CTreeItem::CTreeItem(CFolderTreeCtrl& folderTreeCtrl, CFolderTreeCtrl::CTreeItem* parent,
const QString& name, const QString& path, const int image)
: QStandardItem(folderTreeCtrl.GetItemIcon(image), name)
, m_folderTreeCtrl(folderTreeCtrl)
, m_path(path)
{
parent->appendRow(this);
setData(image == eTreeImage_Folder, IsFolderRole);
m_folderTreeCtrl.m_pathToTreeItem[ m_path ] = this;
}
CFolderTreeCtrl::CTreeItem::~CTreeItem()
{
m_folderTreeCtrl.m_pathToTreeItem.erase(m_path);
}
void CFolderTreeCtrl::CTreeItem::Remove()
{
// Root can't be deleted this way
if (auto parentItem = parent())
{
int numRows = parentItem->rowCount();
for (int i = 0; i < numRows; ++i)
{
if (parentItem->child(i) == this)
{
parentItem->removeRow(i);
break;
}
}
}
}
CFolderTreeCtrl::CTreeItem* CFolderTreeCtrl::CTreeItem::AddChild(const QString& name, const QString& path, const int image)
{
CTreeItem* newItem = new CTreeItem(m_folderTreeCtrl, this, name, path, image);
return newItem;
}
+119
View File
@@ -0,0 +1,119 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef CRYINCLUDE_EDITOR_CONTROLS_FOLDERTREECTRL_H
#define CRYINCLUDE_EDITOR_CONTROLS_FOLDERTREECTRL_H
#pragma once
#include "Util/FileChangeMonitor.h"
#include <QList>
#include <QStandardItem>
#include <QTreeView>
#include <QIcon>
class QSortFilterProxyModel;
class QStandardItemModel;
//! Case insensetive less key for any type convertable to const char*.
struct qstring_icmp
{
bool operator()(const QString& left, const QString& right) const
{
return QString::compare(left, right, Qt::CaseInsensitive) < 0;
}
};
class CFolderTreeCtrl
: public QTreeView
, public CFileChangeMonitorListener
{
Q_OBJECT // AUTOMOC
friend class CTreeItem;
class CTreeItem
: public QStandardItem
{
// Only allow destruction through std::unique_ptr
friend struct std::default_delete<CTreeItem>;
public:
explicit CTreeItem(CFolderTreeCtrl& folderTreeCtrl, const QString& path);
explicit CTreeItem(CFolderTreeCtrl& folderTreeCtrl, CTreeItem* parent,
const QString& name, const QString& path, const int image);
void Remove();
CTreeItem* AddChild(const QString& name, const QString& path, const int image);
QString GetPath() const { return m_path; }
private:
~CTreeItem();
CFolderTreeCtrl& m_folderTreeCtrl;
QString m_path;
};
public:
CFolderTreeCtrl(QWidget* parent = 0);
CFolderTreeCtrl(const QStringList& folders, const QString& fileNameSpec,
const QString& rootName, bool bDisableMonitor = false, bool bFlatTree = true, QWidget* parent = 0);
virtual ~CFolderTreeCtrl();
void init(const QStringList& folders, const QString& fileNameSpec,
const QString& rootName, bool bDisableMonitor = false, bool bFlatTree = true);
QString GetPath(QStandardItem* item) const;
bool IsFolder(QStandardItem* item) const;
bool IsFile(QStandardItem* item) const;
QIcon GetItemIcon(int image) const;
QList<QStandardItem*> GetSelectedItems() const;
void SetSearchFilter(const QString& searchText);
Q_SIGNALS:
void ItemDoubleClicked(QStandardItem* item);
protected Q_SLOTS:
void OnIndexDoubleClicked(const QModelIndex& index);
protected:
virtual void OnFileMonitorChange(const SFileChangeInfo& rChange);
void contextMenuEvent(QContextMenuEvent* e) override;
void InitTree();
void LoadTreeRec(const QString& currentFolder);
void AddItem(const QString& path);
void RemoveItem(const QString& path);
CTreeItem* GetItem(const QString& path);
QStandardItem* GetSourceItemByIndex(const QModelIndex& index) const;
QString CalculateFolderFullPath(const QStringList& splittedFolder, int idx);
CTreeItem* CreateFolderItems(const QString& folder);
void RemoveEmptyFolderItems(const QString& folder);
void Edit(const QString& path);
void ShowInExplorer(const QString& path);
bool m_bDisableMonitor;
bool m_bFlatStyle;
std::unique_ptr< CTreeItem > m_rootTreeItem;
QString m_fileNameSpec;
QStringList m_folders;
QString m_rootName;
std::map<QString, unsigned int> m_foldersSegments;
QIcon m_folderIcon;
QIcon m_fileIcon;
QSortFilterProxyModel* m_proxyModel = nullptr;
QStandardItemModel* m_model = nullptr;
std::map< QString, CTreeItem*, qstring_icmp > m_pathToTreeItem;
};
#endif // CRYINCLUDE_EDITOR_CONTROLS_FOLDERTREECTRL_H
@@ -0,0 +1,47 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "HotTrackingTreeCtrl.h"
// Qt
#include <QMouseEvent>
CHotTrackingTreeCtrl::CHotTrackingTreeCtrl(QWidget* parent)
: QTreeWidget(parent)
{
setMouseTracking(true);
m_hHoverItem = NULL;
}
void CHotTrackingTreeCtrl::mouseMoveEvent(QMouseEvent* event)
{
QTreeWidgetItem* hItem = itemAt(event->pos());
if (m_hHoverItem != NULL)
{
QFont font = m_hHoverItem->font(0);
font.setBold(false);
m_hHoverItem->setFont(0, font);
m_hHoverItem = NULL;
}
if (hItem != NULL)
{
QFont font = hItem->font(0);
font.setBold(true);
hItem->setFont(0, font);
m_hHoverItem = hItem;
}
QTreeWidget::mouseMoveEvent(event);
}
#include <Controls/moc_HotTrackingTreeCtrl.cpp>
@@ -0,0 +1,32 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef CRYINCLUDE_EDITOR_CONTROLS_HOTTRACKINGTREECTRL_H
#define CRYINCLUDE_EDITOR_CONTROLS_HOTTRACKINGTREECTRL_H
#pragma once
#if !defined(Q_MOC_RUN)
#include <QTreeWidget>
#endif
class CHotTrackingTreeCtrl
: public QTreeWidget
{
Q_OBJECT
public:
CHotTrackingTreeCtrl(QWidget* parent = 0);
virtual ~CHotTrackingTreeCtrl(){};
protected:
void mouseMoveEvent(QMouseEvent* event) override;
private:
QTreeWidgetItem* m_hHoverItem;
};
#endif // CRYINCLUDE_EDITOR_CONTROLS_HOTTRACKINGTREECTRL_H

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