Merge branch 'upstream/development' into LYN-6769_TestingRPCs_AutonToAuth_ServerToAuth
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6d6204c6730e5675791765ca194e9b1cbec282208e280507de830afc2805e5fa
|
||||
size 41127
|
||||
oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7
|
||||
size 1232
|
||||
|
||||
+2
-3
@@ -93,7 +93,7 @@ def Terrain_World_ConfigurationWorks():
|
||||
# 5) Set the base Terrain World values
|
||||
world_bounds_max = azmath.Vector3(1100.0, 1100.0, 1100.0)
|
||||
world_bounds_min = azmath.Vector3(10.0, 10.0, 10.0)
|
||||
height_query_resolution = azmath.Vector2(1.0, 1.0)
|
||||
height_query_resolution = 1.0
|
||||
hydra.set_component_property_value(terrain_world_component, "Configuration|World Bounds (Max)", world_bounds_max)
|
||||
hydra.set_component_property_value(terrain_world_component, "Configuration|World Bounds (Min)", world_bounds_min)
|
||||
hydra.set_component_property_value(terrain_world_component, "Configuration|Height Query Resolution (m)", height_query_resolution)
|
||||
@@ -148,7 +148,7 @@ def Terrain_World_ConfigurationWorks():
|
||||
|
||||
# 13) Check height value is the expected one when query resolution is changed
|
||||
testpoint = terrain.TerrainDataRequestBus(bus.Broadcast, 'GetHeightFromFloats', 10.5, 10.5, CLAMP)
|
||||
height_query_resolution = azmath.Vector2(0.5, 0.5)
|
||||
height_query_resolution = 0.5
|
||||
hydra.set_component_property_value(terrain_world_component, "Configuration|Height Query Resolution (m)", height_query_resolution)
|
||||
general.idle_wait_frames(1)
|
||||
testpoint2 = terrain.TerrainDataRequestBus(bus.Broadcast, 'GetHeightFromFloats', 10.5, 10.5, CLAMP)
|
||||
@@ -165,4 +165,3 @@ if __name__ == "__main__":
|
||||
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(Terrain_World_ConfigurationWorks)
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7
|
||||
size 1232
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6d6204c6730e5675791765ca194e9b1cbec282208e280507de830afc2805e5fa
|
||||
size 41127
|
||||
oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7
|
||||
size 1232
|
||||
|
||||
@@ -42,6 +42,16 @@
|
||||
"RayTracingAccelerationStructurePass"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "TerrainDetailTextureComputePass",
|
||||
"TemplateName": "TerrainDetailTextureComputePassTemplate",
|
||||
"Enabled": false
|
||||
},
|
||||
{
|
||||
"Name": "TerrainMacroTextureComputePass",
|
||||
"TemplateName": "TerrainMacroTextureComputePassTemplate",
|
||||
"Enabled": false
|
||||
},
|
||||
{
|
||||
"Name": "DepthPrePass",
|
||||
"TemplateName": "DepthMSAAParentTemplate",
|
||||
@@ -215,7 +225,7 @@
|
||||
// Note: The following two lines represent the choice of rendering pipeline for the hair.
|
||||
// You can either choose to use PPLL or ShortCut and accordingly change the flag
|
||||
// 'm_usePPLLRenderTechnique' in the class 'HairFeatureProcessor.cpp'
|
||||
// "TemplateName": "HairParentPassTemplate",
|
||||
// "TemplateName": "HairParentPassTemplate",
|
||||
"TemplateName": "HairParentShortCutPassTemplate",
|
||||
"Enabled": true,
|
||||
"Connections": [
|
||||
|
||||
@@ -78,10 +78,7 @@
|
||||
<enum>Qt::ClickFocus</enum>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string extracomment="Collapse All"/>
|
||||
</property>
|
||||
<property name="toolTipDuration">
|
||||
<number>3</number>
|
||||
<string extracomment="Collapse All">Collapse All</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
|
||||
@@ -1,369 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
// Description : Tooltip that displays bitmap.
|
||||
|
||||
|
||||
#include "EditorDefs.h"
|
||||
|
||||
#include "BitmapToolTip.h"
|
||||
|
||||
// Qt
|
||||
#include <QVBoxLayout>
|
||||
|
||||
// Editor
|
||||
#include "Util/Image.h"
|
||||
#include "Util/ImageUtil.h"
|
||||
|
||||
|
||||
static const int STATIC_TEXT_C_HEIGHT = 42;
|
||||
static const int HISTOGRAM_C_HEIGHT = 130;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// CBitmapToolTip
|
||||
CBitmapToolTip::CBitmapToolTip(QWidget* parent)
|
||||
: QWidget(parent, Qt::ToolTip)
|
||||
, m_staticBitmap(new QLabel(this))
|
||||
, m_staticText(new QLabel(this))
|
||||
, m_rgbaHistogram(new CImageHistogramCtrl(this))
|
||||
, m_alphaChannelHistogram(new CImageHistogramCtrl(this))
|
||||
{
|
||||
m_nTimer = 0;
|
||||
m_hToolWnd = nullptr;
|
||||
m_bShowHistogram = true;
|
||||
m_bShowFullsize = false;
|
||||
m_eShowMode = ESHOW_RGB;
|
||||
|
||||
connect(&m_timer, &QTimer::timeout, this, &CBitmapToolTip::OnTimer);
|
||||
|
||||
auto* layout = new QVBoxLayout(this);
|
||||
layout->setSizeConstraint(QLayout::SetFixedSize);
|
||||
|
||||
layout->addWidget(m_staticBitmap);
|
||||
layout->addWidget(m_staticText);
|
||||
|
||||
auto* histogramLayout = new QHBoxLayout();
|
||||
histogramLayout->addWidget(m_rgbaHistogram);
|
||||
histogramLayout->addWidget(m_alphaChannelHistogram);
|
||||
m_alphaChannelHistogram->setVisible(false);
|
||||
|
||||
layout->addLayout(histogramLayout);
|
||||
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
CBitmapToolTip::~CBitmapToolTip()
|
||||
{
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void CBitmapToolTip::GetShowMode(EShowMode& eShowMode, bool& bShowInOriginalSize) const
|
||||
{
|
||||
bShowInOriginalSize = CheckVirtualKey(Qt::Key_Space);
|
||||
eShowMode = ESHOW_RGB;
|
||||
|
||||
if (m_bHasAlpha)
|
||||
{
|
||||
if (CheckVirtualKey(Qt::Key_Control))
|
||||
{
|
||||
eShowMode = ESHOW_RGB_ALPHA;
|
||||
}
|
||||
else if (CheckVirtualKey(Qt::Key_Alt))
|
||||
{
|
||||
eShowMode = ESHOW_ALPHA;
|
||||
}
|
||||
else if (CheckVirtualKey(Qt::Key_Shift))
|
||||
{
|
||||
eShowMode = ESHOW_RGBA;
|
||||
}
|
||||
}
|
||||
else if (m_bIsLimitedHDR)
|
||||
{
|
||||
if (CheckVirtualKey(Qt::Key_Shift))
|
||||
{
|
||||
eShowMode = ESHOW_RGBE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char* CBitmapToolTip::GetShowModeDescription(EShowMode eShowMode, [[maybe_unused]] bool bShowInOriginalSize) const
|
||||
{
|
||||
switch (eShowMode)
|
||||
{
|
||||
case ESHOW_RGB:
|
||||
return "RGB";
|
||||
case ESHOW_RGB_ALPHA:
|
||||
return "RGB+A";
|
||||
case ESHOW_ALPHA:
|
||||
return "Alpha";
|
||||
case ESHOW_RGBA:
|
||||
return "RGBA";
|
||||
case ESHOW_RGBE:
|
||||
return "RGBExp";
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
void CBitmapToolTip::RefreshViewmode()
|
||||
{
|
||||
LoadImage(m_filename);
|
||||
|
||||
if (m_eShowMode == ESHOW_RGB_ALPHA || m_eShowMode == ESHOW_RGBA)
|
||||
{
|
||||
m_rgbaHistogram->setVisible(true);
|
||||
m_alphaChannelHistogram->setVisible(true);
|
||||
}
|
||||
else if (m_eShowMode == ESHOW_ALPHA)
|
||||
{
|
||||
m_rgbaHistogram->setVisible(false);
|
||||
m_alphaChannelHistogram->setVisible(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_rgbaHistogram->setVisible(true);
|
||||
m_alphaChannelHistogram->setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
bool CBitmapToolTip::LoadImage(const QString& imageFilename)
|
||||
{
|
||||
EShowMode eShowMode = ESHOW_RGB;
|
||||
const char* pShowModeDescription = "RGB";
|
||||
bool bShowInOriginalSize = false;
|
||||
|
||||
GetShowMode(eShowMode, bShowInOriginalSize);
|
||||
pShowModeDescription = GetShowModeDescription(eShowMode, bShowInOriginalSize);
|
||||
|
||||
QString convertedFileName = Path::GamePathToFullPath(Path::ReplaceExtension(imageFilename, ".dds"));
|
||||
|
||||
// We need to check against both the image filename and the converted filename as it is possible that the
|
||||
// converted file existed but failed to load previously and we reverted to loading the source asset.
|
||||
bool alreadyLoadedImage = ((m_filename == convertedFileName) || (m_filename == imageFilename));
|
||||
if (alreadyLoadedImage && (m_eShowMode == eShowMode) && (m_bShowFullsize == bShowInOriginalSize))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
CCryFile fileCheck;
|
||||
if (!fileCheck.Open(convertedFileName.toUtf8().data(), "rb"))
|
||||
{
|
||||
// if we didn't find it, then default back to just using what we can find (if any)
|
||||
convertedFileName = imageFilename;
|
||||
}
|
||||
else
|
||||
{
|
||||
fileCheck.Close();
|
||||
}
|
||||
|
||||
m_eShowMode = eShowMode;
|
||||
m_bShowFullsize = bShowInOriginalSize;
|
||||
|
||||
CImageEx image;
|
||||
image.SetHistogramEqualization(CheckVirtualKey(Qt::Key_Shift));
|
||||
bool loadedRequestedAsset = true;
|
||||
if (!CImageUtil::LoadImage(convertedFileName, image))
|
||||
{
|
||||
//Failed to load the requested asset, let's try loading the source asset if available.
|
||||
loadedRequestedAsset = false;
|
||||
if (!CImageUtil::LoadImage(imageFilename, image))
|
||||
{
|
||||
m_staticBitmap->clear();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
QString imginfo;
|
||||
|
||||
m_filename = loadedRequestedAsset ? convertedFileName : imageFilename;
|
||||
m_bHasAlpha = image.HasAlphaChannel();
|
||||
m_bIsLimitedHDR = image.IsLimitedHDR();
|
||||
|
||||
GetShowMode(eShowMode, bShowInOriginalSize);
|
||||
pShowModeDescription = GetShowModeDescription(eShowMode, bShowInOriginalSize);
|
||||
|
||||
if (m_bHasAlpha)
|
||||
{
|
||||
imginfo = tr("%1x%2 %3\nShowing %4 (ALT=Alpha, SHIFT=RGBA, CTRL=RGB+A, SPACE=see in original size)");
|
||||
}
|
||||
else if (m_bIsLimitedHDR)
|
||||
{
|
||||
imginfo = tr("%1x%2 %3\nShowing %4 (SHIFT=see hist.-equalized, SPACE=see in original size)");
|
||||
}
|
||||
else
|
||||
{
|
||||
imginfo = tr("%1x%2 %3\nShowing %4 (SPACE=see in original size)");
|
||||
}
|
||||
|
||||
imginfo = imginfo.arg(image.GetWidth()).arg(image.GetHeight()).arg(image.GetFormatDescription()).arg(pShowModeDescription);
|
||||
|
||||
m_staticText->setText(imginfo);
|
||||
|
||||
int w = image.GetWidth();
|
||||
int h = image.GetHeight();
|
||||
int multiplier = (m_eShowMode == ESHOW_RGB_ALPHA ? 2 : 1);
|
||||
int originalW = w * multiplier;
|
||||
int originalH = h;
|
||||
|
||||
if (!bShowInOriginalSize || (w == 0))
|
||||
{
|
||||
w = 256;
|
||||
}
|
||||
if (!bShowInOriginalSize || (h == 0))
|
||||
{
|
||||
h = 256;
|
||||
}
|
||||
|
||||
w *= multiplier;
|
||||
|
||||
resize(w + 4, h + 4 + STATIC_TEXT_C_HEIGHT + HISTOGRAM_C_HEIGHT);
|
||||
setVisible(true);
|
||||
|
||||
CImageEx scaledImage;
|
||||
|
||||
if (bShowInOriginalSize && (originalW < w))
|
||||
{
|
||||
w = originalW;
|
||||
}
|
||||
if (bShowInOriginalSize && (originalH < h))
|
||||
{
|
||||
h = originalH;
|
||||
}
|
||||
|
||||
scaledImage.Allocate(w, h);
|
||||
|
||||
if (m_eShowMode == ESHOW_RGB_ALPHA)
|
||||
{
|
||||
CImageUtil::ScaleToDoubleFit(image, scaledImage);
|
||||
}
|
||||
else
|
||||
{
|
||||
CImageUtil::ScaleToFit(image, scaledImage);
|
||||
}
|
||||
|
||||
if (m_eShowMode == ESHOW_RGB || m_eShowMode == ESHOW_RGBE)
|
||||
{
|
||||
scaledImage.SwapRedAndBlue();
|
||||
scaledImage.FillAlpha();
|
||||
}
|
||||
else if (m_eShowMode == ESHOW_ALPHA)
|
||||
{
|
||||
for (int hh = 0; hh < scaledImage.GetHeight(); hh++)
|
||||
{
|
||||
for (int ww = 0; ww < scaledImage.GetWidth(); ww++)
|
||||
{
|
||||
int a = scaledImage.ValueAt(ww, hh) >> 24;
|
||||
scaledImage.ValueAt(ww, hh) = RGB(a, a, a);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (m_eShowMode == ESHOW_RGB_ALPHA)
|
||||
{
|
||||
int halfWidth = scaledImage.GetWidth() / 2;
|
||||
for (int hh = 0; hh < scaledImage.GetHeight(); hh++)
|
||||
{
|
||||
for (int ww = 0; ww < halfWidth; ww++)
|
||||
{
|
||||
int r = GetRValue(scaledImage.ValueAt(ww, hh));
|
||||
int g = GetGValue(scaledImage.ValueAt(ww, hh));
|
||||
int b = GetBValue(scaledImage.ValueAt(ww, hh));
|
||||
int a = scaledImage.ValueAt(ww, hh) >> 24;
|
||||
scaledImage.ValueAt(ww, hh) = RGB(b, g, r);
|
||||
scaledImage.ValueAt(ww + halfWidth, hh) = RGB(a, a, a);
|
||||
}
|
||||
}
|
||||
}
|
||||
else //if (m_showMode == ESHOW_RGBA)
|
||||
{
|
||||
scaledImage.SwapRedAndBlue();
|
||||
}
|
||||
|
||||
QImage qImage(scaledImage.GetWidth(), scaledImage.GetHeight(), QImage::Format_RGB32);
|
||||
memcpy(qImage.bits(), scaledImage.GetData(), qImage.sizeInBytes());
|
||||
m_staticBitmap->setPixmap(QPixmap::fromImage(qImage));
|
||||
|
||||
if (m_bShowHistogram && scaledImage.GetData())
|
||||
{
|
||||
m_rgbaHistogram->ComputeHistogram(image, CImageHistogram::eImageFormat_32BPP_BGRA);
|
||||
m_rgbaHistogram->setDrawMode(EHistogramDrawMode::OverlappedRGB);
|
||||
|
||||
m_alphaChannelHistogram->histogramDisplay()->CopyComputedDataFrom(m_rgbaHistogram->histogramDisplay());
|
||||
m_alphaChannelHistogram->setDrawMode(EHistogramDrawMode::AlphaChannel);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CBitmapToolTip::OnTimer()
|
||||
{
|
||||
/*
|
||||
if (IsWindowVisible())
|
||||
{
|
||||
if (m_bHaveAnythingToRender)
|
||||
Invalidate();
|
||||
}
|
||||
*/
|
||||
if (m_hToolWnd)
|
||||
{
|
||||
QRect toolRc(m_toolRect);
|
||||
QRect rc = geometry();
|
||||
QPoint cursorPos = QCursor::pos();
|
||||
toolRc.moveTopLeft(m_hToolWnd->mapToGlobal(toolRc.topLeft()));
|
||||
if (!toolRc.contains(cursorPos) && !rc.contains(cursorPos))
|
||||
{
|
||||
setVisible(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
RefreshViewmode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void CBitmapToolTip::showEvent([[maybe_unused]] QShowEvent* event)
|
||||
{
|
||||
QPoint cursorPos = QCursor::pos();
|
||||
move(cursorPos);
|
||||
m_timer.start(500);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void CBitmapToolTip::hideEvent([[maybe_unused]] QHideEvent* event)
|
||||
{
|
||||
m_timer.stop();
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void CBitmapToolTip::keyPressEvent(QKeyEvent* event)
|
||||
{
|
||||
if (event->key() == Qt::Key_Control || event->key() == Qt::Key_Alt || event->key() == Qt::Key_Shift)
|
||||
{
|
||||
RefreshViewmode();
|
||||
}
|
||||
}
|
||||
|
||||
void CBitmapToolTip::keyReleaseEvent(QKeyEvent* event)
|
||||
{
|
||||
if (event->key() == Qt::Key_Control || event->key() == Qt::Key_Alt || event->key() == Qt::Key_Shift)
|
||||
{
|
||||
RefreshViewmode();
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void CBitmapToolTip::SetTool(QWidget* pWnd, const QRect& rect)
|
||||
{
|
||||
assert(pWnd);
|
||||
m_hToolWnd = pWnd;
|
||||
m_toolRect = rect;
|
||||
}
|
||||
|
||||
#include <Controls/moc_BitmapToolTip.cpp>
|
||||
@@ -1,88 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
// Description : Tooltip that displays bitmap.
|
||||
|
||||
|
||||
#ifndef CRYINCLUDE_EDITOR_CONTROLS_BITMAPTOOLTIP_H
|
||||
#define CRYINCLUDE_EDITOR_CONTROLS_BITMAPTOOLTIP_H
|
||||
#pragma once
|
||||
|
||||
|
||||
#if !defined(Q_MOC_RUN)
|
||||
#include "Controls/ImageHistogramCtrl.h"
|
||||
|
||||
#include <QLabel>
|
||||
#include <QTimer>
|
||||
#endif
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
class CBitmapToolTip
|
||||
: public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
// Construction
|
||||
public:
|
||||
|
||||
enum EShowMode
|
||||
{
|
||||
ESHOW_RGB = 0,
|
||||
ESHOW_ALPHA,
|
||||
ESHOW_RGBA,
|
||||
ESHOW_RGB_ALPHA,
|
||||
ESHOW_RGBE
|
||||
};
|
||||
|
||||
CBitmapToolTip(QWidget* parent = nullptr);
|
||||
virtual ~CBitmapToolTip();
|
||||
|
||||
bool Create(const RECT& rect);
|
||||
|
||||
// Attributes
|
||||
public:
|
||||
|
||||
// Operations
|
||||
public:
|
||||
void RefreshViewmode();
|
||||
|
||||
bool LoadImage(const QString& imageFilename);
|
||||
void SetTool(QWidget* pWnd, const QRect& rect);
|
||||
|
||||
// Generated message map functions
|
||||
protected:
|
||||
void OnTimer();
|
||||
|
||||
void keyPressEvent(QKeyEvent* event) override;
|
||||
void keyReleaseEvent(QKeyEvent* event) override;
|
||||
|
||||
void showEvent(QShowEvent* event) override;
|
||||
void hideEvent(QHideEvent* event) override;
|
||||
|
||||
private:
|
||||
void GetShowMode(EShowMode& showMode, bool& showInOriginalSize) const;
|
||||
const char* GetShowModeDescription(EShowMode showMode, bool showInOriginalSize) const;
|
||||
|
||||
QLabel* m_staticBitmap;
|
||||
QLabel* m_staticText;
|
||||
QString m_filename;
|
||||
bool m_bShowHistogram;
|
||||
EShowMode m_eShowMode;
|
||||
bool m_bShowFullsize;
|
||||
bool m_bHasAlpha;
|
||||
bool m_bIsLimitedHDR;
|
||||
CImageHistogramCtrl* m_rgbaHistogram;
|
||||
CImageHistogramCtrl* m_alphaChannelHistogram;
|
||||
int m_nTimer;
|
||||
QWidget* m_hToolWnd;
|
||||
QRect m_toolRect;
|
||||
QTimer m_timer;
|
||||
};
|
||||
|
||||
|
||||
#endif // CRYINCLUDE_EDITOR_CONTROLS_BITMAPTOOLTIP_H
|
||||
@@ -1,172 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#include "EditorDefs.h"
|
||||
|
||||
#include "QBitmapPreviewDialog.h"
|
||||
#include <Controls/ui_QBitmapPreviewDialog.h>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDesktopWidget>
|
||||
#include <QPainter>
|
||||
#include <QScreen>
|
||||
|
||||
void QBitmapPreviewDialog::ImageData::setRgba8888(const void* buffer, const int& w, const int& h)
|
||||
{
|
||||
const unsigned long bytes = w * h * 4;
|
||||
m_buffer.resize(bytes);
|
||||
memcpy(m_buffer.data(), buffer, bytes);
|
||||
m_image = QImage((uchar*)m_buffer.constData(), w, h, QImage::Format::Format_RGBA8888);
|
||||
}
|
||||
|
||||
static void fillChecker(int w, int h, unsigned int* dst)
|
||||
{
|
||||
for (int y = 0; y < h; y++)
|
||||
{
|
||||
for (int x = 0; x < w; x++)
|
||||
{
|
||||
dst[y * w + x] = 0xFF000000 | (((x >> 2) + (y >> 2)) % 2 == 0 ? 0x007F7F7F : 0x00000000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QBitmapPreviewDialog::QBitmapPreviewDialog(QWidget* parent)
|
||||
: QWidget(parent)
|
||||
, ui(new Ui::QBitmapTooltip)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
setAttribute(Qt::WA_TranslucentBackground);
|
||||
setAttribute(Qt::WA_ShowWithoutActivating);
|
||||
|
||||
// Clear label text
|
||||
ui->m_placeholderBitmap->setText("");
|
||||
ui->m_placeholderHistogram->setText("");
|
||||
|
||||
ui->m_bitmapSize->setProperty("tableRow", "Odd");
|
||||
ui->m_Mips->setProperty("tableRow", "Even");
|
||||
ui->m_Mean->setProperty("tableRow", "Odd");
|
||||
ui->m_StdDev->setProperty("tableRow", "Even");
|
||||
ui->m_Median->setProperty("tableRow", "Odd");
|
||||
ui->m_labelForBitmapSize->setProperty("tooltipLabel", "content");
|
||||
ui->m_labelForMean->setProperty("tooltipLabel", "content");
|
||||
ui->m_labelForMedian->setProperty("tooltipLabel", "content");
|
||||
ui->m_labelForMips->setProperty("tooltipLabel", "content");
|
||||
ui->m_labelForStdDev->setProperty("tooltipLabel", "content");
|
||||
ui->m_vBitmapSize->setProperty("tooltipLabel", "content");
|
||||
ui->m_vMean->setProperty("tooltipLabel", "content");
|
||||
ui->m_vMedian->setProperty("tooltipLabel", "content");
|
||||
ui->m_vMips->setProperty("tooltipLabel", "content");
|
||||
ui->m_vStdDev->setProperty("tooltipLabel", "content");
|
||||
|
||||
// Initialize placeholder images
|
||||
const int w = 64;
|
||||
const int h = 64;
|
||||
QByteArray buffer;
|
||||
buffer.resize(w * h * 4);
|
||||
unsigned int* dst = (unsigned int*)buffer.data();
|
||||
fillChecker(w, h, dst);
|
||||
m_checker.setRgba8888(buffer.constData(), w, h);
|
||||
|
||||
m_initialSize = window()->window()->geometry().size();
|
||||
}
|
||||
|
||||
QBitmapPreviewDialog::~QBitmapPreviewDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void QBitmapPreviewDialog::setImageRgba8888(const void* buffer, const int& w, const int& h, [[maybe_unused]] const QString& info)
|
||||
{
|
||||
m_imageMain.setRgba8888(buffer, w, h);
|
||||
}
|
||||
|
||||
|
||||
QRect QBitmapPreviewDialog::getHistogramArea()
|
||||
{
|
||||
return QRect(ui->m_placeholderHistogram->pos(), ui->m_placeholderHistogram->size());
|
||||
}
|
||||
|
||||
void QBitmapPreviewDialog::setFullSize(const bool& fullSize)
|
||||
{
|
||||
if (fullSize)
|
||||
{
|
||||
QSize desktop = QApplication::screenAt(ui->m_placeholderBitmap->pos())->availableGeometry().size();
|
||||
QSize image = m_imageMain.m_image.size();
|
||||
QPoint location = mapToGlobal(ui->m_placeholderBitmap->pos());
|
||||
QSize finalSize;
|
||||
finalSize.setWidth((image.width() < (desktop.width() - location.x())) ? image.width() : (desktop.width() - location.x()));
|
||||
finalSize.setHeight((image.height() < (desktop.height() - location.y())) ? image.height() : (desktop.height() - location.y()));
|
||||
float scale = (finalSize.width() < finalSize.height()) ? finalSize.width() / float(m_imageMain.m_image.width()) : finalSize.height() / float(m_imageMain.m_image.height());
|
||||
ui->m_placeholderBitmap->setFixedSize(scale * m_imageMain.m_image.size());
|
||||
}
|
||||
else
|
||||
{
|
||||
ui->m_placeholderBitmap->setFixedSize(256, 256);
|
||||
}
|
||||
|
||||
adjustSize();
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
void QBitmapPreviewDialog::paintEvent(QPaintEvent* e)
|
||||
{
|
||||
QWidget::paintEvent(e);
|
||||
QRect rect(ui->m_placeholderBitmap->pos(), ui->m_placeholderBitmap->size());
|
||||
drawImageData(rect, m_imageMain);
|
||||
}
|
||||
|
||||
void QBitmapPreviewDialog::drawImageData(const QRect& rect, const ImageData& imgData)
|
||||
{
|
||||
// Draw the
|
||||
QPainter p(this);
|
||||
p.drawImage(rect.topLeft(), m_checker.m_image.scaled(rect.size()));
|
||||
p.drawImage(rect.topLeft(), imgData.m_image.scaled(rect.size()));
|
||||
|
||||
// Draw border
|
||||
QPen pen;
|
||||
pen.setColor(QColor(0, 0, 0));
|
||||
p.drawRect(rect.top(), rect.left(), rect.width() - 1, rect.height());
|
||||
}
|
||||
|
||||
void QBitmapPreviewDialog::setSize(QString _value)
|
||||
{
|
||||
ui->m_vBitmapSize->setText(_value);
|
||||
}
|
||||
|
||||
void QBitmapPreviewDialog::setMips(QString _value)
|
||||
{
|
||||
ui->m_vMips->setText(_value);
|
||||
}
|
||||
|
||||
void QBitmapPreviewDialog::setMean(QString _value)
|
||||
{
|
||||
ui->m_vMean->setText(_value);
|
||||
}
|
||||
|
||||
void QBitmapPreviewDialog::setMedian(QString _value)
|
||||
{
|
||||
ui->m_vMedian->setText(_value);
|
||||
}
|
||||
|
||||
void QBitmapPreviewDialog::setStdDev(QString _value)
|
||||
{
|
||||
ui->m_vStdDev->setText(_value);
|
||||
}
|
||||
|
||||
QSize QBitmapPreviewDialog::GetCurrentBitmapSize()
|
||||
{
|
||||
return ui->m_placeholderBitmap->size();
|
||||
}
|
||||
|
||||
QSize QBitmapPreviewDialog::GetOriginalImageSize()
|
||||
{
|
||||
return m_imageMain.m_image.size();
|
||||
}
|
||||
|
||||
|
||||
#include <Controls/moc_QBitmapPreviewDialog.cpp>
|
||||
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#ifndef QBITMAPPREVIEWDIALOG_H
|
||||
#define QBITMAPPREVIEWDIALOG_H
|
||||
|
||||
#if !defined(Q_MOC_RUN)
|
||||
#include <QWidget>
|
||||
#include <QPixmap>
|
||||
#include <QImage>
|
||||
#endif
|
||||
|
||||
class QLabel;
|
||||
|
||||
namespace Ui {
|
||||
class QBitmapTooltip;
|
||||
}
|
||||
|
||||
class QBitmapPreviewDialog
|
||||
: public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
struct ImageData
|
||||
{
|
||||
QByteArray m_buffer;
|
||||
QImage m_image;
|
||||
|
||||
void setRgba8888(const void* buffer, const int& w, const int& h);
|
||||
};
|
||||
|
||||
public:
|
||||
explicit QBitmapPreviewDialog(QWidget* parent = 0);
|
||||
virtual ~QBitmapPreviewDialog();
|
||||
QSize GetCurrentBitmapSize();
|
||||
QSize GetOriginalImageSize();
|
||||
|
||||
protected:
|
||||
void setImageRgba8888(const void* buffer, const int& w, const int& h, const QString& info);
|
||||
void setSize(QString _value);
|
||||
void setMips(QString _value);
|
||||
void setMean(QString _value);
|
||||
void setMedian(QString _value);
|
||||
void setStdDev(QString _value);
|
||||
QRect getHistogramArea();
|
||||
void setFullSize(const bool& fullSize);
|
||||
|
||||
void paintEvent(QPaintEvent* e) override;
|
||||
|
||||
private:
|
||||
void drawImageData(const QRect& rect, const ImageData& imgData);
|
||||
|
||||
protected:
|
||||
Ui::QBitmapTooltip* ui;
|
||||
QSize m_initialSize;
|
||||
ImageData m_checker;
|
||||
ImageData m_imageMain;
|
||||
};
|
||||
|
||||
#endif // QBITMAPPREVIEWDIALOG_H
|
||||
@@ -1,390 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>QBitmapTooltip</class>
|
||||
<widget class="QWidget" name="QBitmapTooltip">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>256</width>
|
||||
<height>510</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>256</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="m_placeholderBitmap">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>256</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="autoFillBackground">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Bitmap Area</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="m_placeholderHistogram">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>128</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Histogram Area</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="m_bitmapSize" native="true">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>24</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>24</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="m_labelForBitmapSize">
|
||||
<property name="text">
|
||||
<string>Size:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="m_vBitmapSize">
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::RightToLeft</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Size Value</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="m_Mips" native="true">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>24</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>24</height>
|
||||
</size>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="m_labelForMips">
|
||||
<property name="text">
|
||||
<string>DXT5 Mips:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="m_vMips">
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::RightToLeft</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Size Value</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="m_Mean" native="true">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>24</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>24</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="m_labelForMean">
|
||||
<property name="text">
|
||||
<string>Mean:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="m_vMean">
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::RightToLeft</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Size Value</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="m_StdDev" native="true">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>24</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>24</height>
|
||||
</size>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="m_labelForStdDev">
|
||||
<property name="text">
|
||||
<string>StdDev:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="m_vStdDev">
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::RightToLeft</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Size Value</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="m_Median" native="true">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>24</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>24</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="m_labelForMedian">
|
||||
<property name="text">
|
||||
<string>Median:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="m_vMedian">
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::RightToLeft</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Size Value</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -1,528 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#include "EditorDefs.h"
|
||||
|
||||
#include "QBitmapPreviewDialogImp.h"
|
||||
|
||||
// Cry
|
||||
#include <ITexture.h>
|
||||
|
||||
// EditorCore
|
||||
#include <Util/Image.h>
|
||||
#include <Include/IImageUtil.h>
|
||||
|
||||
// QT
|
||||
AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // 4251: class '...' needs to have dll-interface to be used by clients of class '...'
|
||||
#include <QEvent>
|
||||
#include <QKeyEvent>
|
||||
#include <QPainter>
|
||||
#include <QPainterPath>
|
||||
#include <qmath.h>
|
||||
AZ_POP_DISABLE_WARNING
|
||||
|
||||
#include <Controls/ui_QBitmapPreviewDialog.h>
|
||||
|
||||
static const int kDefaultWidth = 256;
|
||||
static const int kDefaultHeight = 256;
|
||||
|
||||
QBitmapPreviewDialogImp::QBitmapPreviewDialogImp(QWidget* parent)
|
||||
: QBitmapPreviewDialog(parent)
|
||||
, m_image(new CImageEx())
|
||||
, m_showOriginalSize(false)
|
||||
, m_showMode(ESHOW_RGB)
|
||||
, m_histrogramMode(eHistogramMode_OverlappedRGB)
|
||||
{
|
||||
setMouseTracking(true);
|
||||
setImage("");
|
||||
ui->m_placeholderBitmap->setStyleSheet("background-color: rgba(0, 0, 0, 0);");
|
||||
ui->m_placeholderHistogram->setStyleSheet("background-color: rgba(0, 0, 0, 0);");
|
||||
|
||||
ui->m_labelForBitmapSize->setProperty("tooltipLabel", "Content");
|
||||
ui->m_labelForMean->setProperty("tooltipLabel", "Content");
|
||||
ui->m_labelForMedian->setProperty("tooltipLabel", "Content");
|
||||
ui->m_labelForMips->setProperty("tooltipLabel", "Content");
|
||||
ui->m_labelForStdDev->setProperty("tooltipLabel", "Content");
|
||||
|
||||
ui->m_vBitmapSize->setProperty("tooltipLabel", "Content");
|
||||
ui->m_vMean->setProperty("tooltipLabel", "Content");
|
||||
ui->m_vMedian->setProperty("tooltipLabel", "Content");
|
||||
ui->m_vMips->setProperty("tooltipLabel", "Content");
|
||||
ui->m_vStdDev->setProperty("tooltipLabel", "Content");
|
||||
|
||||
setUIStyleMode(EUISTYLE_IMAGE_ONLY);
|
||||
}
|
||||
|
||||
QBitmapPreviewDialogImp::~QBitmapPreviewDialogImp()
|
||||
{
|
||||
SAFE_DELETE(m_image);
|
||||
}
|
||||
|
||||
void QBitmapPreviewDialogImp::setImage(const QString path)
|
||||
{
|
||||
if (path.isEmpty()
|
||||
|| m_path == path
|
||||
|| !GetIEditor()->GetImageUtil()->LoadImage(path.toUtf8().data(), *m_image))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_showOriginalSize = isSizeSmallerThanDefault();
|
||||
m_path = path;
|
||||
refreshData();
|
||||
}
|
||||
|
||||
void QBitmapPreviewDialogImp::setShowMode(EShowMode mode)
|
||||
{
|
||||
if (mode == ESHOW_NumModes)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_showMode = mode;
|
||||
refreshData();
|
||||
update();
|
||||
}
|
||||
|
||||
void QBitmapPreviewDialogImp::toggleShowMode()
|
||||
{
|
||||
m_showMode = (EShowMode)(((int)m_showMode + 1) % ESHOW_NumModes);
|
||||
refreshData();
|
||||
update();
|
||||
}
|
||||
|
||||
void QBitmapPreviewDialogImp::setUIStyleMode(EUIStyle mode)
|
||||
{
|
||||
if (mode >= EUISTYLE_NumModes)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_uiStyle = mode;
|
||||
if (m_uiStyle == EUISTYLE_IMAGE_ONLY)
|
||||
{
|
||||
ui->m_placeholderHistogram->hide();
|
||||
|
||||
ui->m_labelForBitmapSize->hide();
|
||||
ui->m_labelForMean->hide();
|
||||
ui->m_labelForMedian->hide();
|
||||
ui->m_labelForMips->hide();
|
||||
ui->m_labelForStdDev->hide();
|
||||
|
||||
ui->m_vBitmapSize->hide();
|
||||
ui->m_vMean->hide();
|
||||
ui->m_vMedian->hide();
|
||||
ui->m_vMips->hide();
|
||||
ui->m_vStdDev->hide();
|
||||
}
|
||||
else
|
||||
{
|
||||
ui->m_placeholderHistogram->show();
|
||||
|
||||
ui->m_labelForBitmapSize->show();
|
||||
ui->m_labelForMean->show();
|
||||
ui->m_labelForMedian->show();
|
||||
ui->m_labelForMips->show();
|
||||
ui->m_labelForStdDev->show();
|
||||
|
||||
ui->m_vBitmapSize->show();
|
||||
ui->m_vMean->show();
|
||||
ui->m_vMedian->show();
|
||||
ui->m_vMips->show();
|
||||
ui->m_vStdDev->show();
|
||||
}
|
||||
}
|
||||
|
||||
const QBitmapPreviewDialogImp::EShowMode& QBitmapPreviewDialogImp::getShowMode() const
|
||||
{
|
||||
return m_showMode;
|
||||
}
|
||||
|
||||
void QBitmapPreviewDialogImp::setHistogramMode(EHistogramMode mode)
|
||||
{
|
||||
if (mode == eHistogramMode_NumModes)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_histrogramMode = mode;
|
||||
}
|
||||
|
||||
void QBitmapPreviewDialogImp::toggleHistrogramMode()
|
||||
{
|
||||
m_histrogramMode = (EHistogramMode)(((int)m_histrogramMode + 1) % eHistogramMode_NumModes);
|
||||
update();
|
||||
}
|
||||
|
||||
const QBitmapPreviewDialogImp::EHistogramMode& QBitmapPreviewDialogImp::getHistogramMode() const
|
||||
{
|
||||
return m_histrogramMode;
|
||||
}
|
||||
|
||||
void QBitmapPreviewDialogImp::toggleOriginalSize()
|
||||
{
|
||||
m_showOriginalSize = !m_showOriginalSize;
|
||||
|
||||
refreshData();
|
||||
update();
|
||||
}
|
||||
|
||||
bool QBitmapPreviewDialogImp::isSizeSmallerThanDefault()
|
||||
{
|
||||
return m_image->GetWidth() < kDefaultWidth && m_image->GetHeight() < kDefaultHeight;
|
||||
}
|
||||
|
||||
void QBitmapPreviewDialogImp::setOriginalSize(bool value)
|
||||
{
|
||||
m_showOriginalSize = value;
|
||||
|
||||
refreshData();
|
||||
update();
|
||||
}
|
||||
|
||||
|
||||
const char* QBitmapPreviewDialogImp::GetShowModeDescription(EShowMode eShowMode, [[maybe_unused]] bool bShowInOriginalSize) const
|
||||
{
|
||||
switch (eShowMode)
|
||||
{
|
||||
case ESHOW_RGB:
|
||||
return "RGB";
|
||||
case ESHOW_RGB_ALPHA:
|
||||
return "RGB+A";
|
||||
case ESHOW_ALPHA:
|
||||
return "Alpha";
|
||||
case ESHOW_RGBA:
|
||||
return "RGBA";
|
||||
case ESHOW_RGBE:
|
||||
return "RGBExp";
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
const char* getHistrogramModeStr(QBitmapPreviewDialogImp::EHistogramMode mode, bool shortName)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case QBitmapPreviewDialogImp::eHistogramMode_Luminosity:
|
||||
return shortName ? "Lum" : "Luminosity";
|
||||
case QBitmapPreviewDialogImp::eHistogramMode_OverlappedRGB:
|
||||
return shortName ? "Overlap" : "Overlapped RGBA";
|
||||
case QBitmapPreviewDialogImp::eHistogramMode_SplitRGB:
|
||||
return shortName ? "R|G|B" : "Split RGB";
|
||||
case QBitmapPreviewDialogImp::eHistogramMode_RedChannel:
|
||||
return shortName ? "Red" : "Red Channel";
|
||||
case QBitmapPreviewDialogImp::eHistogramMode_GreenChannel:
|
||||
return shortName ? "Green" : "Green Channel";
|
||||
case QBitmapPreviewDialogImp::eHistogramMode_BlueChannel:
|
||||
return shortName ? "Blue" : "Blue Channel";
|
||||
case QBitmapPreviewDialogImp::eHistogramMode_AlphaChannel:
|
||||
return shortName ? "Alpha" : "Alpha Channel";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
void QBitmapPreviewDialogImp::refreshData()
|
||||
{
|
||||
// Check if we have some usefull data loaded
|
||||
if (m_image->GetWidth() * m_image->GetHeight() == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int w = m_image->GetWidth();
|
||||
int h = m_image->GetHeight();
|
||||
|
||||
int multiplier = (m_showMode == ESHOW_RGB_ALPHA ? 2 : 1);
|
||||
int originalW = w * multiplier;
|
||||
int originalH = h;
|
||||
|
||||
if (!m_showOriginalSize || (w == 0))
|
||||
{
|
||||
w = kDefaultWidth;
|
||||
}
|
||||
if (!m_showOriginalSize || (h == 0))
|
||||
{
|
||||
h = kDefaultHeight;
|
||||
}
|
||||
|
||||
w *= multiplier;
|
||||
|
||||
CImageEx scaledImage;
|
||||
|
||||
if (m_showOriginalSize && (originalW < w))
|
||||
{
|
||||
w = originalW;
|
||||
}
|
||||
if (m_showOriginalSize && (originalH < h))
|
||||
{
|
||||
h = originalH;
|
||||
}
|
||||
|
||||
scaledImage.Allocate(w, h);
|
||||
|
||||
if (m_showMode == ESHOW_RGB_ALPHA)
|
||||
{
|
||||
GetIEditor()->GetImageUtil()->ScaleToDoubleFit(*m_image, scaledImage);
|
||||
}
|
||||
else
|
||||
{
|
||||
GetIEditor()->GetImageUtil()->ScaleToFit(*m_image, scaledImage);
|
||||
}
|
||||
|
||||
if (m_showMode == ESHOW_RGB || m_showMode == ESHOW_RGBE)
|
||||
{
|
||||
scaledImage.FillAlpha();
|
||||
}
|
||||
else if (m_showMode == ESHOW_ALPHA)
|
||||
{
|
||||
for (int h2 = 0; h2 < scaledImage.GetHeight(); h2++)
|
||||
{
|
||||
for (int w2 = 0; w2 < scaledImage.GetWidth(); w2++)
|
||||
{
|
||||
int a = scaledImage.ValueAt(w2, h2) >> 24;
|
||||
scaledImage.ValueAt(w2, h2) = RGB(a, a, a) | (a << 24);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (m_showMode == ESHOW_RGB_ALPHA)
|
||||
{
|
||||
int halfWidth = scaledImage.GetWidth() / 2;
|
||||
for (int h2 = 0; h2 < scaledImage.GetHeight(); h2++)
|
||||
{
|
||||
for (int w2 = 0; w2 < halfWidth; w2++)
|
||||
{
|
||||
int r = GetRValue(scaledImage.ValueAt(w2, h2));
|
||||
int g = GetGValue(scaledImage.ValueAt(w2, h2));
|
||||
int b = GetBValue(scaledImage.ValueAt(w2, h2));
|
||||
int a = scaledImage.ValueAt(w2, h2) >> 24;
|
||||
scaledImage.ValueAt(w2, h2) = RGB(r, g, b) | (a << 24);
|
||||
scaledImage.ValueAt(w2 + halfWidth, h2) = RGB(a, a, a) | (a << 24);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
setImageRgba8888(scaledImage.GetData(), w, h, "");
|
||||
setSize(QString().asprintf("%d x %d", m_image->GetWidth(), m_image->GetHeight()));
|
||||
setMips(QString().asprintf("%d", m_image->GetNumberOfMipMaps()));
|
||||
|
||||
setFullSize(m_showOriginalSize);
|
||||
|
||||
// Compute histogram
|
||||
m_histogram.ComputeHistogram((BYTE*)scaledImage.GetData(), w, h, CImageHistogram::eImageFormat_32BPP_RGBA);
|
||||
}
|
||||
|
||||
void QBitmapPreviewDialogImp::paintEvent(QPaintEvent* e)
|
||||
{
|
||||
QBitmapPreviewDialog::paintEvent(e);
|
||||
|
||||
//if showing original size hide other information so it's easier to see
|
||||
if (m_showOriginalSize)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (m_uiStyle == EUISTYLE_IMAGE_ONLY)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
QPainter p(this);
|
||||
QPen pen;
|
||||
QPainterPath path[4];
|
||||
|
||||
// Fill background color
|
||||
QRect histogramRect = getHistogramArea();
|
||||
p.fillRect(histogramRect, QColor(255, 255, 255));
|
||||
|
||||
// Draw borders
|
||||
pen.setColor(QColor(0, 0, 0));
|
||||
p.setPen(pen);
|
||||
p.drawRect(histogramRect);
|
||||
|
||||
// Draw histogram
|
||||
|
||||
QVector<int> drawChannels;
|
||||
|
||||
switch (m_histrogramMode)
|
||||
{
|
||||
case eHistogramMode_Luminosity:
|
||||
drawChannels.push_back(3);
|
||||
break;
|
||||
case eHistogramMode_SplitRGB:
|
||||
drawChannels.push_back(0);
|
||||
drawChannels.push_back(1);
|
||||
drawChannels.push_back(2);
|
||||
break;
|
||||
case eHistogramMode_OverlappedRGB:
|
||||
drawChannels.push_back(0);
|
||||
drawChannels.push_back(1);
|
||||
drawChannels.push_back(2);
|
||||
break;
|
||||
case eHistogramMode_RedChannel:
|
||||
drawChannels.push_back(0);
|
||||
break;
|
||||
case eHistogramMode_GreenChannel:
|
||||
drawChannels.push_back(1);
|
||||
break;
|
||||
case eHistogramMode_BlueChannel:
|
||||
drawChannels.push_back(2);
|
||||
break;
|
||||
case eHistogramMode_AlphaChannel:
|
||||
drawChannels.push_back(3);
|
||||
break;
|
||||
}
|
||||
|
||||
int graphWidth = qMax(histogramRect.width(), 1);
|
||||
int graphHeight = qMax(histogramRect.height() - 2, 0);
|
||||
int graphBottom = histogramRect.bottom() + 1;
|
||||
int currX[4] = {0, 0, 0, 0};
|
||||
int prevX[4] = {0, 0, 0, 0};
|
||||
float scale = 0.0f;
|
||||
static const int numSubGraphs = 3;
|
||||
const int subGraph = qCeil(graphWidth / numSubGraphs);
|
||||
|
||||
// Fill background for Split RGB histogram
|
||||
if (m_histrogramMode == eHistogramMode_SplitRGB)
|
||||
{
|
||||
const static QColor backgroundColor[numSubGraphs] =
|
||||
{
|
||||
QColor(255, 220, 220),
|
||||
QColor(220, 255, 220),
|
||||
QColor(220, 220, 255)
|
||||
};
|
||||
|
||||
for (int i = 0; i < numSubGraphs; i++)
|
||||
{
|
||||
p.fillRect(histogramRect.left() + subGraph * i,
|
||||
histogramRect.top(),
|
||||
subGraph + (i == numSubGraphs - 1 ? 1 : 0),
|
||||
histogramRect.height(), backgroundColor[i]);
|
||||
}
|
||||
}
|
||||
|
||||
int lastHeight[CImageHistogram::kNumChannels] = { INT_MAX, INT_MAX, INT_MAX, INT_MAX };
|
||||
|
||||
for (int x = 0; x < graphWidth; ++x)
|
||||
{
|
||||
for (int j = 0; j < drawChannels.size(); j++)
|
||||
{
|
||||
const int c = drawChannels[j];
|
||||
int& curr_x = currX[c];
|
||||
int& prev_x = prevX[c];
|
||||
int& last_height = lastHeight[c];
|
||||
QPainterPath& curr_path = path[c];
|
||||
|
||||
|
||||
curr_x = histogramRect.left() + x + 1;
|
||||
int i = static_cast<int>(((float)x / (graphWidth - 1)) * (CImageHistogram::kNumColorLevels - 1));
|
||||
if (m_histrogramMode == eHistogramMode_SplitRGB)
|
||||
{
|
||||
// Filter out to area which we are interested
|
||||
const int k = x / subGraph;
|
||||
if (k != c)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
i = qCeil((i - (subGraph * c)) * numSubGraphs);
|
||||
i = qMin(i, CImageHistogram::kNumColorLevels - 1);
|
||||
i = qMax(i, 0);
|
||||
}
|
||||
|
||||
if (m_histrogramMode == eHistogramMode_Luminosity)
|
||||
{
|
||||
scale = (float)m_histogram.m_lumCount[i] / m_histogram.m_maxLumCount;
|
||||
}
|
||||
else if (m_histogram.m_maxCount[c])
|
||||
{
|
||||
scale = (float)m_histogram.m_count[c][i] / m_histogram.m_maxCount[c];
|
||||
}
|
||||
|
||||
int height = static_cast<int>(graphBottom - graphHeight * scale);
|
||||
if (last_height == INT_MAX)
|
||||
{
|
||||
last_height = height;
|
||||
}
|
||||
|
||||
curr_path.moveTo(prev_x, last_height);
|
||||
curr_path.lineTo(curr_x, height);
|
||||
last_height = height;
|
||||
|
||||
if (prev_x == INT_MAX)
|
||||
{
|
||||
prev_x = curr_x;
|
||||
}
|
||||
|
||||
prev_x = curr_x;
|
||||
}
|
||||
}
|
||||
|
||||
static const QColor kChannelColor[4] =
|
||||
{
|
||||
QColor(255, 0, 0),
|
||||
QColor(0, 255, 0),
|
||||
QColor(0, 0, 255),
|
||||
QColor(120, 120, 120)
|
||||
};
|
||||
|
||||
for (int i = 0; i < drawChannels.size(); i++)
|
||||
{
|
||||
const int c = drawChannels[i];
|
||||
pen.setColor(kChannelColor[c]);
|
||||
p.setPen(pen);
|
||||
p.drawPath(path[c]);
|
||||
}
|
||||
|
||||
// Update histogram info
|
||||
{
|
||||
float mean = 0, stdDev = 0, median = 0;
|
||||
|
||||
switch (m_histrogramMode)
|
||||
{
|
||||
case eHistogramMode_Luminosity:
|
||||
case eHistogramMode_SplitRGB:
|
||||
case eHistogramMode_OverlappedRGB:
|
||||
mean = m_histogram.m_meanAvg;
|
||||
stdDev = m_histogram.m_stdDevAvg;
|
||||
median = m_histogram.m_medianAvg;
|
||||
break;
|
||||
case eHistogramMode_RedChannel:
|
||||
mean = m_histogram.m_mean[0];
|
||||
stdDev = m_histogram.m_stdDev[0];
|
||||
median = m_histogram.m_median[0];
|
||||
break;
|
||||
case eHistogramMode_GreenChannel:
|
||||
mean = m_histogram.m_mean[1];
|
||||
stdDev = m_histogram.m_stdDev[1];
|
||||
median = m_histogram.m_median[1];
|
||||
break;
|
||||
case eHistogramMode_BlueChannel:
|
||||
mean = m_histogram.m_mean[2];
|
||||
stdDev = m_histogram.m_stdDev[2];
|
||||
median = m_histogram.m_median[2];
|
||||
break;
|
||||
case eHistogramMode_AlphaChannel:
|
||||
mean = m_histogram.m_mean[3];
|
||||
stdDev = m_histogram.m_stdDev[3];
|
||||
median = m_histogram.m_median[3];
|
||||
break;
|
||||
}
|
||||
QString val;
|
||||
val.setNum(mean);
|
||||
setMean(val);
|
||||
val.setNum(stdDev);
|
||||
setStdDev(val);
|
||||
val.setNum(median);
|
||||
setMedian(val);
|
||||
}
|
||||
}
|
||||
|
||||
#include <Controls/moc_QBitmapPreviewDialogImp.cpp>
|
||||
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#ifndef QBITMAPPREVIEWDIALOG_IMP_H
|
||||
#define QBITMAPPREVIEWDIALOG_IMP_H
|
||||
|
||||
#if !defined(Q_MOC_RUN)
|
||||
#include "QBitmapPreviewDialog.h"
|
||||
#include <Util/ImageHistogram.h>
|
||||
#endif
|
||||
|
||||
class CImageEx;
|
||||
|
||||
class QBitmapPreviewDialogImp
|
||||
: public QBitmapPreviewDialog
|
||||
{
|
||||
Q_OBJECT;
|
||||
public:
|
||||
|
||||
enum EUIStyle
|
||||
{
|
||||
EUISTYLE_IMAGE_ONLY,
|
||||
EUISTYLE_IMAGE_HISTOGRAM,
|
||||
EUISTYLE_NumModes
|
||||
};
|
||||
|
||||
enum EShowMode
|
||||
{
|
||||
ESHOW_RGB = 0,
|
||||
ESHOW_ALPHA,
|
||||
ESHOW_RGBA,
|
||||
ESHOW_RGB_ALPHA,
|
||||
ESHOW_RGBE,
|
||||
ESHOW_NumModes,
|
||||
};
|
||||
|
||||
enum EHistogramMode
|
||||
{
|
||||
eHistogramMode_Luminosity,
|
||||
eHistogramMode_OverlappedRGB,
|
||||
eHistogramMode_SplitRGB,
|
||||
eHistogramMode_RedChannel,
|
||||
eHistogramMode_GreenChannel,
|
||||
eHistogramMode_BlueChannel,
|
||||
eHistogramMode_AlphaChannel,
|
||||
eHistogramMode_NumModes,
|
||||
};
|
||||
|
||||
explicit QBitmapPreviewDialogImp(QWidget* parent = 0);
|
||||
virtual ~QBitmapPreviewDialogImp();
|
||||
|
||||
void setImage(const QString path);
|
||||
|
||||
void setShowMode(EShowMode mode);
|
||||
void toggleShowMode();
|
||||
void setUIStyleMode(EUIStyle mode);
|
||||
const EShowMode& getShowMode() const;
|
||||
|
||||
void setHistogramMode(EHistogramMode mode);
|
||||
void toggleHistrogramMode();
|
||||
const EHistogramMode& getHistogramMode() const;
|
||||
|
||||
void setOriginalSize(bool value);
|
||||
void toggleOriginalSize();
|
||||
|
||||
bool isSizeSmallerThanDefault();
|
||||
void paintEvent(QPaintEvent* e) override;
|
||||
|
||||
protected:
|
||||
void refreshData();
|
||||
|
||||
private:
|
||||
const char* GetShowModeDescription(EShowMode eShowMode, bool bShowInOriginalSize) const;
|
||||
|
||||
private:
|
||||
CImageEx* m_image;
|
||||
QString m_path;
|
||||
CImageHistogram m_histogram;
|
||||
bool m_showOriginalSize;
|
||||
EShowMode m_showMode;
|
||||
EHistogramMode m_histrogramMode;
|
||||
EUIStyle m_uiStyle;
|
||||
};
|
||||
|
||||
#endif // QBITMAPPREVIEWDIALOG_IMP_H
|
||||
@@ -1,642 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#include "EditorDefs.h"
|
||||
|
||||
#include <Controls/QToolTipWidget.h>
|
||||
|
||||
#include "QBitmapPreviewDialogImp.h"
|
||||
#include "qcoreapplication.h"
|
||||
#include "qguiapplication.h"
|
||||
#include "qapplication.h"
|
||||
#include <QDesktopWidget>
|
||||
#include <QPainter>
|
||||
#include <QtGlobal>
|
||||
#include <qgraphicseffect.h>
|
||||
|
||||
void QToolTipWidget::RebuildLayout()
|
||||
{
|
||||
if (m_title != nullptr)
|
||||
{
|
||||
m_title->hide();
|
||||
}
|
||||
if (m_content != nullptr)
|
||||
{
|
||||
m_content->hide();
|
||||
}
|
||||
if (m_specialContent != nullptr)
|
||||
{
|
||||
m_specialContent->hide();
|
||||
}
|
||||
|
||||
//empty layout
|
||||
while (m_layout->count() > 0)
|
||||
{
|
||||
m_layout->takeAt(0);
|
||||
}
|
||||
qDeleteAll(m_currentShortcuts);
|
||||
m_currentShortcuts.clear();
|
||||
if (m_includeTextureShortcuts)
|
||||
{
|
||||
m_currentShortcuts.append(new QLabel(tr("Alt - Alpha"), this));
|
||||
m_currentShortcuts.back()->setProperty("tooltipLabel", "Shortcut");
|
||||
m_currentShortcuts.append(new QLabel(tr("Shift - RGBA"), this));
|
||||
m_currentShortcuts.back()->setProperty("tooltipLabel", "Shortcut");
|
||||
}
|
||||
|
||||
if (m_title != nullptr && !m_title->text().isEmpty())
|
||||
{
|
||||
m_layout->addWidget(m_title);
|
||||
m_title->show();
|
||||
}
|
||||
|
||||
for (QLabel* var : m_currentShortcuts)
|
||||
{
|
||||
if (var != nullptr)
|
||||
{
|
||||
m_layout->addWidget(var);
|
||||
var->show();
|
||||
}
|
||||
}
|
||||
if (m_specialContent != nullptr)
|
||||
{
|
||||
m_layout->addWidget(m_specialContent);
|
||||
m_specialContent->show();
|
||||
}
|
||||
if (m_content != nullptr && !m_content->text().isEmpty())
|
||||
{
|
||||
m_layout->addWidget(m_content);
|
||||
m_content->show();
|
||||
}
|
||||
m_background->adjustSize();
|
||||
adjustSize();
|
||||
}
|
||||
|
||||
void QToolTipWidget::Hide()
|
||||
{
|
||||
m_currentShortcuts.clear();
|
||||
hide();
|
||||
}
|
||||
|
||||
void QToolTipWidget::Show(QPoint pos, ArrowDirection dir)
|
||||
{
|
||||
if (!IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_arrow->m_direction = dir;
|
||||
pos = AdjustTipPosByArrowSize(pos, dir);
|
||||
m_normalPos = pos;
|
||||
move(pos);
|
||||
RebuildLayout();
|
||||
show();
|
||||
m_arrow->show();
|
||||
}
|
||||
|
||||
void QToolTipWidget::Display(QRect targetRect, ArrowDirection preferredArrowDir)
|
||||
{
|
||||
if (!IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
KeepTipOnScreen(targetRect, preferredArrowDir);
|
||||
|
||||
RebuildLayout();
|
||||
show();
|
||||
m_arrow->show();
|
||||
}
|
||||
|
||||
void QToolTipWidget::TryDisplay(QPoint mousePos, const QRect& rect, [[maybe_unused]] ArrowDirection preferredArrowDir)
|
||||
{
|
||||
if (rect.contains(mousePos))
|
||||
{
|
||||
Display(rect, QToolTipWidget::ArrowDirection::ARROW_RIGHT);
|
||||
}
|
||||
else
|
||||
{
|
||||
hide();
|
||||
}
|
||||
}
|
||||
|
||||
void QToolTipWidget::TryDisplay(QPoint mousePos, const QWidget* widget, ArrowDirection preferredArrowDir)
|
||||
{
|
||||
const QRect rect(widget->mapToGlobal(QPoint(0,0)), widget->size());
|
||||
TryDisplay(mousePos, rect, preferredArrowDir);
|
||||
}
|
||||
|
||||
void QToolTipWidget::SetTitle(QString title)
|
||||
{
|
||||
if (!title.isEmpty())
|
||||
{
|
||||
m_title->setText(title);
|
||||
}
|
||||
m_title->setProperty("tooltipLabel", "Title");
|
||||
|
||||
setWindowTitle("ToolTip - " + title);
|
||||
}
|
||||
|
||||
void QToolTipWidget::SetContent(QString content)
|
||||
{
|
||||
m_content->setWordWrap(true);
|
||||
|
||||
m_content->setProperty("tooltipLabel", "Content");
|
||||
//line-height is not supported via stylesheet so we use the html rich-text subset in QT for it.
|
||||
m_content->setText(QString("<span style=\"line-height: 14px;\">%1</span>").arg(content));
|
||||
}
|
||||
|
||||
void QToolTipWidget::AppendContent(QString content)
|
||||
{
|
||||
m_content->setText(m_content->text() + "\n\n" + content);
|
||||
update();
|
||||
RebuildLayout();
|
||||
m_content->update();
|
||||
m_content->repaint();
|
||||
}
|
||||
|
||||
QToolTipWidget::QToolTipWidget(QWidget* parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
m_background = new QWidget(this);
|
||||
m_background->setProperty("tooltip", "Background");
|
||||
m_background->stackUnder(this);
|
||||
m_title = new QLabel(this);
|
||||
m_currentShortcuts = QVector<QLabel*>();
|
||||
m_content = new QLabel(this);
|
||||
m_specialContent = nullptr;
|
||||
setWindowTitle("ToolTip");
|
||||
setObjectName("ToolTip");
|
||||
m_layout = new QVBoxLayout(this);
|
||||
m_normalPos = QPoint(0, 0);
|
||||
m_arrow = new QArrow(m_background);
|
||||
setWindowFlags(Qt::ToolTip | Qt::FramelessWindowHint);
|
||||
m_arrow->setWindowFlags(Qt::ToolTip | Qt::FramelessWindowHint);
|
||||
m_arrow->setAttribute(Qt::WA_TranslucentBackground, true);
|
||||
m_background->setLayout(m_layout);
|
||||
m_arrow->setObjectName("ToolTipArrow");
|
||||
m_background->setObjectName("ToolTipBackground");
|
||||
|
||||
//we need a drop shadow for the background
|
||||
QGraphicsDropShadowEffect* dropShadow = new QGraphicsDropShadowEffect(this);
|
||||
dropShadow->setBlurRadius(m_shadowRadius);
|
||||
dropShadow->setColor(Qt::black);
|
||||
dropShadow->setOffset(0);
|
||||
dropShadow->setEnabled(true);
|
||||
m_background->setGraphicsEffect(dropShadow);
|
||||
//we need a second drop shadow effect for the arrow
|
||||
dropShadow = new QGraphicsDropShadowEffect(m_arrow);
|
||||
dropShadow->setBlurRadius(m_shadowRadius);
|
||||
dropShadow->setColor(Qt::black);
|
||||
dropShadow->setOffset(0);
|
||||
dropShadow->setEnabled(true);
|
||||
m_arrow->setGraphicsEffect(dropShadow);
|
||||
}
|
||||
|
||||
QToolTipWidget::~QToolTipWidget()
|
||||
{
|
||||
}
|
||||
|
||||
void QToolTipWidget::AddSpecialContent(QString type, QString dataStream)
|
||||
{
|
||||
if (type.isEmpty())
|
||||
{
|
||||
m_includeTextureShortcuts = false;
|
||||
if (m_specialContent != nullptr)
|
||||
{
|
||||
delete m_specialContent;
|
||||
m_specialContent = nullptr;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (type == "TEXTURE")
|
||||
{
|
||||
if (m_specialContent == nullptr)
|
||||
{
|
||||
QCoreApplication::instance()->installEventFilter(this); //grab the event filter while displaying the advanced texture tooltip
|
||||
m_specialContent = new QBitmapPreviewDialogImp(this);
|
||||
}
|
||||
QString path(dataStream);
|
||||
qobject_cast<QBitmapPreviewDialogImp*>(m_specialContent)->setImage(path);
|
||||
// set default showmode to RGB
|
||||
qobject_cast<QBitmapPreviewDialogImp*>(m_specialContent)->setShowMode(QBitmapPreviewDialogImp::EShowMode::ESHOW_RGB);
|
||||
QString dir = (path.split("/").count() > path.split("\\").count()) ? path.split("/").back() : path.split("\\").back();
|
||||
SetTitle(dir);
|
||||
//always use default size but not image size
|
||||
qobject_cast<QBitmapPreviewDialogImp*>(m_specialContent)->setOriginalSize(false);
|
||||
m_includeTextureShortcuts = true;
|
||||
}
|
||||
else if (type == "ADD TO CONTENT")
|
||||
{
|
||||
AppendContent(dataStream);
|
||||
|
||||
m_includeTextureShortcuts = false;
|
||||
if (m_specialContent != nullptr)
|
||||
{
|
||||
delete m_specialContent;
|
||||
m_specialContent = nullptr;
|
||||
}
|
||||
}
|
||||
else if (type == "REPLACE TITLE")
|
||||
{
|
||||
SetTitle(dataStream);
|
||||
m_includeTextureShortcuts = false;
|
||||
if (m_specialContent != nullptr)
|
||||
{
|
||||
delete m_specialContent;
|
||||
m_specialContent = nullptr;
|
||||
}
|
||||
}
|
||||
else if (type == "REPLACE CONTENT")
|
||||
{
|
||||
SetContent(dataStream);
|
||||
m_includeTextureShortcuts = false;
|
||||
if (m_specialContent != nullptr)
|
||||
{
|
||||
delete m_specialContent;
|
||||
m_specialContent = nullptr;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_includeTextureShortcuts = false;
|
||||
if (m_specialContent != nullptr)
|
||||
{
|
||||
delete m_specialContent;
|
||||
m_specialContent = nullptr;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
m_special = type;
|
||||
}
|
||||
|
||||
|
||||
bool QToolTipWidget::eventFilter(QObject* obj, QEvent* event)
|
||||
{
|
||||
if (event->type() == QEvent::KeyPress)
|
||||
{
|
||||
if (m_special == "TEXTURE" && m_specialContent != nullptr)
|
||||
{
|
||||
const QKeyEvent* ke = static_cast<QKeyEvent*>(event);
|
||||
Qt::KeyboardModifiers mods = ke->modifiers();
|
||||
if (mods & Qt::KeyboardModifier::AltModifier)
|
||||
{
|
||||
((QBitmapPreviewDialogImp*)m_specialContent)->setShowMode(QBitmapPreviewDialogImp::ESHOW_ALPHA);
|
||||
}
|
||||
else if (mods & Qt::KeyboardModifier::ShiftModifier && !(mods & Qt::KeyboardModifier::ControlModifier))
|
||||
{
|
||||
((QBitmapPreviewDialogImp*)m_specialContent)->setShowMode(QBitmapPreviewDialogImp::ESHOW_RGBA);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (event->type() == QEvent::KeyRelease)
|
||||
{
|
||||
if (m_special == "TEXTURE" && m_specialContent != nullptr)
|
||||
{
|
||||
const QKeyEvent* ke = static_cast<QKeyEvent*>(event);
|
||||
Qt::KeyboardModifiers mods = ke->modifiers();
|
||||
if (!(mods& Qt::KeyboardModifier::AltModifier) && !(mods & Qt::KeyboardModifier::ShiftModifier))
|
||||
{
|
||||
((QBitmapPreviewDialogImp*)m_specialContent)->setShowMode(QBitmapPreviewDialogImp::ESHOW_RGB);
|
||||
}
|
||||
}
|
||||
}
|
||||
return QWidget::eventFilter(obj, event);
|
||||
}
|
||||
|
||||
void QToolTipWidget::hideEvent(QHideEvent* event)
|
||||
{
|
||||
QWidget::hideEvent(event);
|
||||
m_arrow->hide();
|
||||
}
|
||||
|
||||
void QToolTipWidget::UpdateOptionalData(QString optionalData)
|
||||
{
|
||||
AddSpecialContent(m_special, optionalData);
|
||||
}
|
||||
|
||||
|
||||
|
||||
QPoint QToolTipWidget::AdjustTipPosByArrowSize(QPoint pos, ArrowDirection dir)
|
||||
{
|
||||
switch (dir)
|
||||
{
|
||||
case QToolTipWidget::ArrowDirection::ARROW_UP:
|
||||
{
|
||||
m_arrow->move(pos);
|
||||
pos.setY(pos.y() + 10);
|
||||
m_arrow->setFixedSize(20, 10);
|
||||
pos -= QPoint(m_shadowRadius, m_shadowRadius);
|
||||
break;
|
||||
}
|
||||
case QToolTipWidget::ArrowDirection::ARROW_LEFT:
|
||||
{
|
||||
m_arrow->move(pos);
|
||||
pos.setX(pos.x() + 10);
|
||||
m_arrow->setFixedSize(10, 20);
|
||||
pos -= QPoint(m_shadowRadius, m_shadowRadius);
|
||||
break;
|
||||
}
|
||||
case QToolTipWidget::ArrowDirection::ARROW_RIGHT:
|
||||
{
|
||||
pos.setX(pos.x() - 10);
|
||||
m_arrow->move(QPoint(pos.x() + width(), pos.y()));
|
||||
m_arrow->setFixedSize(10, 20);
|
||||
pos -= QPoint(-m_shadowRadius, m_shadowRadius);
|
||||
break;
|
||||
}
|
||||
case QToolTipWidget::ArrowDirection::ARROW_DOWN:
|
||||
{
|
||||
pos.setY(pos.y() - 10);
|
||||
m_arrow->move(QPoint(pos.x(), pos.y() + height()));
|
||||
m_arrow->setFixedSize(20, 10);
|
||||
pos -= QPoint(m_shadowRadius, -m_shadowRadius);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
m_arrow->move(-10, -10);
|
||||
break;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
bool QToolTipWidget::IsValid()
|
||||
{
|
||||
if (m_title->text().isEmpty() ||
|
||||
(m_content->text().isEmpty() && m_specialContent == nullptr))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void QToolTipWidget::KeepTipOnScreen(QRect targetRect, ArrowDirection preferredArrowDir)
|
||||
{
|
||||
QRect desktop = QApplication::desktop()->availableGeometry(this);
|
||||
|
||||
if (this->isHidden())
|
||||
{
|
||||
setAttribute(Qt::WA_DontShowOnScreen, true);
|
||||
Show(QPoint(0, 0), preferredArrowDir);
|
||||
hide();
|
||||
setAttribute(Qt::WA_DontShowOnScreen, false);
|
||||
}
|
||||
//else assume the size is right
|
||||
|
||||
//calculate initial rect
|
||||
QRect tipRect = QRect(0, 0, 0, 0);
|
||||
switch (preferredArrowDir)
|
||||
{
|
||||
case QToolTipWidget::ArrowDirection::ARROW_UP:
|
||||
{
|
||||
//tip is below the widget with a left alignment
|
||||
tipRect.setTopLeft(AdjustTipPosByArrowSize(targetRect.bottomLeft(), preferredArrowDir));
|
||||
break;
|
||||
}
|
||||
case QToolTipWidget::ArrowDirection::ARROW_LEFT:
|
||||
{
|
||||
//tip is on the right with the top being even
|
||||
tipRect.setTopLeft(AdjustTipPosByArrowSize(targetRect.topRight(), preferredArrowDir));
|
||||
break;
|
||||
}
|
||||
case QToolTipWidget::ArrowDirection::ARROW_RIGHT:
|
||||
{
|
||||
//tip is on the left with the top being even
|
||||
tipRect.setY(targetRect.top());
|
||||
tipRect.setX(targetRect.left() - width());
|
||||
tipRect.setTopLeft(AdjustTipPosByArrowSize(tipRect.topLeft(), preferredArrowDir));
|
||||
break;
|
||||
}
|
||||
case QToolTipWidget::ArrowDirection::ARROW_DOWN:
|
||||
{
|
||||
//tip is above the widget with a left alignment
|
||||
tipRect.setX(targetRect.left());
|
||||
tipRect.setY(targetRect.top() - height());
|
||||
tipRect.setTopLeft(AdjustTipPosByArrowSize(tipRect.topLeft(), preferredArrowDir));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
//tip is on the right with the top being even
|
||||
preferredArrowDir = QToolTipWidget::ArrowDirection::ARROW_LEFT;
|
||||
tipRect.setTopLeft(AdjustTipPosByArrowSize(targetRect.topRight(), QToolTipWidget::ArrowDirection::ARROW_LEFT));
|
||||
break;
|
||||
}
|
||||
}
|
||||
tipRect.setSize(size());
|
||||
|
||||
//FixPositioning
|
||||
if (preferredArrowDir == ArrowDirection::ARROW_LEFT || preferredArrowDir == ArrowDirection::ARROW_RIGHT)
|
||||
{
|
||||
if (tipRect.left() <= desktop.left())
|
||||
{
|
||||
m_arrow->m_direction = ArrowDirection::ARROW_LEFT;
|
||||
tipRect.setTopLeft(AdjustTipPosByArrowSize(targetRect.topRight(), m_arrow->m_direction));
|
||||
}
|
||||
else if (tipRect.right() >= desktop.right())
|
||||
{
|
||||
m_arrow->m_direction = ArrowDirection::ARROW_RIGHT;
|
||||
tipRect.setLeft(targetRect.left() - width());
|
||||
tipRect.setTopLeft(AdjustTipPosByArrowSize(tipRect.topLeft(), m_arrow->m_direction));
|
||||
}
|
||||
}
|
||||
else if (preferredArrowDir == ArrowDirection::ARROW_UP || preferredArrowDir == ArrowDirection::ARROW_DOWN)
|
||||
{
|
||||
if (tipRect.top() <= desktop.top())
|
||||
{
|
||||
m_arrow->m_direction = ArrowDirection::ARROW_UP;
|
||||
tipRect.setTopLeft(AdjustTipPosByArrowSize(targetRect.bottomLeft(), m_arrow->m_direction));
|
||||
}
|
||||
else if (tipRect.bottom() >= desktop.bottom())
|
||||
{
|
||||
m_arrow->m_direction = ArrowDirection::ARROW_DOWN;
|
||||
tipRect.setY(targetRect.top() - height());
|
||||
tipRect.setTopLeft(AdjustTipPosByArrowSize(tipRect.topLeft(), m_arrow->m_direction));
|
||||
}
|
||||
}
|
||||
|
||||
//Nudge tip without arrow
|
||||
if (preferredArrowDir == ArrowDirection::ARROW_UP || preferredArrowDir == ArrowDirection::ARROW_DOWN)
|
||||
{
|
||||
if (tipRect.left() <= desktop.left())
|
||||
{
|
||||
tipRect.setLeft(desktop.left());
|
||||
}
|
||||
else if (tipRect.right() >= desktop.right())
|
||||
{
|
||||
tipRect.setLeft(desktop.right() - width());
|
||||
}
|
||||
}
|
||||
else if (preferredArrowDir == ArrowDirection::ARROW_RIGHT || preferredArrowDir == ArrowDirection::ARROW_LEFT)
|
||||
{
|
||||
if (tipRect.top() <= desktop.top())
|
||||
{
|
||||
tipRect.setTop(desktop.top());
|
||||
}
|
||||
else if (tipRect.bottom() >= desktop.bottom())
|
||||
{
|
||||
tipRect.setTop(desktop.bottom() - height());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
m_normalPos = tipRect.topLeft();
|
||||
move(m_normalPos);
|
||||
}
|
||||
|
||||
QPolygonF QToolTipWidget::QArrow::CreateArrow()
|
||||
{
|
||||
QVector<QPointF> vertex;
|
||||
//3 points in triangle
|
||||
vertex.reserve(3);
|
||||
//all magic number below are given in order to draw smooth transitions between tooltip and arrow
|
||||
if (m_direction == ArrowDirection::ARROW_UP)
|
||||
{
|
||||
vertex.push_back(QPointF(10, 1));
|
||||
vertex.push_back(QPointF(19, 10));
|
||||
vertex.push_back(QPointF(0, 10));
|
||||
}
|
||||
else if (m_direction == ArrowDirection::ARROW_RIGHT)
|
||||
{
|
||||
vertex.push_back(QPointF(9, 10));
|
||||
vertex.push_back(QPointF(0, 19));
|
||||
vertex.push_back(QPointF(0, 1));
|
||||
}
|
||||
else if (m_direction == ArrowDirection::ARROW_LEFT)
|
||||
{
|
||||
vertex.push_back(QPointF(1, 10));
|
||||
vertex.push_back(QPointF(10, 19));
|
||||
vertex.push_back(QPointF(10, 0));
|
||||
}
|
||||
else //ArrowDirection::ARROW_DOWN
|
||||
{
|
||||
vertex.push_back(QPointF(10, 10));
|
||||
vertex.push_back(QPointF(19, 0));
|
||||
vertex.push_back(QPointF(0, 0));
|
||||
}
|
||||
return QPolygonF(vertex);
|
||||
}
|
||||
|
||||
void QToolTipWidget::QArrow::paintEvent([[maybe_unused]] QPaintEvent* event)
|
||||
{
|
||||
QColor color(255, 255, 255, 255);
|
||||
QPainter painter(this);
|
||||
painter.fillRect(rect(), Qt::transparent); //force transparency
|
||||
painter.setRenderHint(QPainter::Antialiasing, false);
|
||||
painter.setBrush(color);
|
||||
painter.setPen(Qt::NoPen);
|
||||
painter.drawPolygon(CreateArrow());
|
||||
//painter.setRenderHint(QPainter::Antialiasing, false);
|
||||
}
|
||||
|
||||
QToolTipWrapper::QToolTipWrapper(QWidget* parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void QToolTipWrapper::SetTitle(QString title)
|
||||
{
|
||||
m_title = title;
|
||||
}
|
||||
|
||||
void QToolTipWrapper::SetContent(QString content)
|
||||
{
|
||||
AddSpecialContent("REPLACE CONTENT", content);
|
||||
}
|
||||
|
||||
void QToolTipWrapper::AppendContent(QString content)
|
||||
{
|
||||
AddSpecialContent("ADD TO CONTENT", content);
|
||||
}
|
||||
|
||||
void QToolTipWrapper::AddSpecialContent(QString type, QString dataStream)
|
||||
{
|
||||
if (type == "REPLACE CONTENT")
|
||||
{
|
||||
m_contentOperations.clear();
|
||||
}
|
||||
m_contentOperations.push_back({type, dataStream});
|
||||
}
|
||||
|
||||
void QToolTipWrapper::UpdateOptionalData(QString optionalData)
|
||||
{
|
||||
m_contentOperations.push_back({"UPDATE OPTIONAL", optionalData});
|
||||
}
|
||||
|
||||
void QToolTipWrapper::Display(QRect targetRect, QToolTipWidget::ArrowDirection preferredArrowDir)
|
||||
{
|
||||
GetOrCreateToolTip()->Display(targetRect, preferredArrowDir);
|
||||
}
|
||||
|
||||
void QToolTipWrapper::TryDisplay(QPoint mousePos, const QWidget * widget, QToolTipWidget::ArrowDirection preferredArrowDir)
|
||||
{
|
||||
GetOrCreateToolTip()->TryDisplay(mousePos, widget, preferredArrowDir);
|
||||
}
|
||||
|
||||
void QToolTipWrapper::TryDisplay(QPoint mousePos, const QRect & widget, QToolTipWidget::ArrowDirection preferredArrowDir)
|
||||
{
|
||||
GetOrCreateToolTip()->TryDisplay(mousePos, widget, preferredArrowDir);
|
||||
}
|
||||
|
||||
void QToolTipWrapper::hide()
|
||||
{
|
||||
DestroyToolTip();
|
||||
}
|
||||
|
||||
void QToolTipWrapper::show()
|
||||
{
|
||||
GetOrCreateToolTip()->show();
|
||||
}
|
||||
|
||||
bool QToolTipWrapper::isVisible() const
|
||||
{
|
||||
return m_actualTooltip && m_actualTooltip->isVisible();
|
||||
}
|
||||
|
||||
void QToolTipWrapper::update()
|
||||
{
|
||||
if (m_actualTooltip)
|
||||
{
|
||||
m_actualTooltip->update();
|
||||
}
|
||||
}
|
||||
|
||||
void QToolTipWrapper::ReplayContentOperations(QToolTipWidget* tooltipWidget)
|
||||
{
|
||||
tooltipWidget->SetTitle(m_title);
|
||||
for (const auto& operation : m_contentOperations)
|
||||
{
|
||||
if (operation.first == "UPDATE OPTIONAL")
|
||||
{
|
||||
tooltipWidget->UpdateOptionalData(operation.second);
|
||||
}
|
||||
else
|
||||
{
|
||||
tooltipWidget->AddSpecialContent(operation.first, operation.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QToolTipWidget * QToolTipWrapper::GetOrCreateToolTip()
|
||||
{
|
||||
if (!m_actualTooltip)
|
||||
{
|
||||
QToolTipWidget* tooltipWidget = new QToolTipWidget(static_cast<QWidget*>(parent()));
|
||||
tooltipWidget->setAttribute(Qt::WA_DeleteOnClose);
|
||||
ReplayContentOperations(tooltipWidget);
|
||||
m_actualTooltip = tooltipWidget;
|
||||
}
|
||||
return m_actualTooltip.data();
|
||||
}
|
||||
|
||||
void QToolTipWrapper::DestroyToolTip()
|
||||
{
|
||||
if (m_actualTooltip)
|
||||
{
|
||||
m_actualTooltip->deleteLater();
|
||||
}
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#ifndef QToolTipWidget_h__
|
||||
#define QToolTipWidget_h__
|
||||
|
||||
#include "EditorCoreAPI.h"
|
||||
|
||||
#include <QPointer>
|
||||
#include <QWidget>
|
||||
#include <QLabel>
|
||||
#include <QString>
|
||||
#include <QMap>
|
||||
#include <QMapIterator>
|
||||
#include <QVector>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include <memory>
|
||||
|
||||
class IQToolTip
|
||||
{
|
||||
public:
|
||||
virtual void SetTitle(QString title) = 0;
|
||||
virtual void SetContent(QString content) = 0;
|
||||
virtual void AppendContent(QString content) = 0;
|
||||
virtual void AddSpecialContent(QString type, QString dataStream) = 0;
|
||||
virtual void UpdateOptionalData(QString optionalData) = 0;
|
||||
};
|
||||
|
||||
class EDITOR_CORE_API QToolTipWidget
|
||||
: public QWidget
|
||||
, public IQToolTip
|
||||
{
|
||||
public:
|
||||
enum class ArrowDirection
|
||||
{
|
||||
ARROW_UP,
|
||||
ARROW_LEFT,
|
||||
ARROW_RIGHT,
|
||||
ARROW_DOWN
|
||||
};
|
||||
class QArrow
|
||||
: public QWidget
|
||||
{
|
||||
public:
|
||||
ArrowDirection m_direction;
|
||||
QPoint m_pos;
|
||||
QArrow(QWidget* parent)
|
||||
: QWidget(parent){ setWindowFlags(Qt::ToolTip); }
|
||||
virtual ~QArrow(){}
|
||||
|
||||
QPolygonF CreateArrow();
|
||||
virtual void paintEvent(QPaintEvent*) override;
|
||||
};
|
||||
QToolTipWidget(QWidget* parent);
|
||||
~QToolTipWidget();
|
||||
void SetTitle(QString title) override;
|
||||
void SetContent(QString content) override;
|
||||
void AppendContent(QString content) override;
|
||||
void AddSpecialContent(QString type, QString dataStream) override;
|
||||
void UpdateOptionalData(QString optionalData) override;
|
||||
void Display(QRect targetRect, ArrowDirection preferredArrowDir);
|
||||
|
||||
//! Displays the tooltip on the given widget, only if the mouse is over it.
|
||||
void TryDisplay(QPoint mousePos, const QWidget* widget, ArrowDirection preferredArrowDir);
|
||||
|
||||
//! Displays the tooltip on the given rect, only if the mouse is over it.
|
||||
void TryDisplay(QPoint mousePos, const QRect& widget, ArrowDirection preferredArrowDir);
|
||||
|
||||
void Hide();
|
||||
|
||||
protected:
|
||||
void Show(QPoint pos, ArrowDirection dir);
|
||||
bool IsValid();
|
||||
void KeepTipOnScreen(QRect targetRect, ArrowDirection preferredArrowDir);
|
||||
QPoint AdjustTipPosByArrowSize(QPoint pos, ArrowDirection dir);
|
||||
virtual bool eventFilter(QObject* obj, QEvent* event) override;
|
||||
void RebuildLayout();
|
||||
virtual void hideEvent(QHideEvent*) override;
|
||||
|
||||
QLabel* m_title;
|
||||
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
|
||||
QVector<QLabel*> m_currentShortcuts;
|
||||
AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
|
||||
//can be anything from QLabel to QBitMapPreviewDialog
|
||||
//must allow movement, and show/hide calls
|
||||
QLabel* m_content;
|
||||
QWidget* m_specialContent;
|
||||
QWidget* m_background;
|
||||
QVBoxLayout* m_layout;
|
||||
QString m_special;
|
||||
QPoint m_normalPos;
|
||||
QArrow* m_arrow;
|
||||
const int m_shadowRadius = 5;
|
||||
bool m_includeTextureShortcuts; //added since Qt does not support modifier only shortcuts
|
||||
};
|
||||
|
||||
// HACK: The EditorUI_QT classes all were keeping persistent references to QToolTipWidgets around
|
||||
// This led to many, many top-level widget creations, which led to many platform-side window allocations
|
||||
// which led to crashes in Qt5.15. As this is legacy code, this is a drop-in replacement that only
|
||||
// allocates the actual QToolTipWidget (and thus platform window) while the tooltip is visible
|
||||
class EDITOR_CORE_API QToolTipWrapper
|
||||
: public QObject
|
||||
, public IQToolTip
|
||||
{
|
||||
public:
|
||||
QToolTipWrapper(QWidget* parent);
|
||||
|
||||
void SetTitle(QString title) override;
|
||||
void SetContent(QString content) override;
|
||||
void AppendContent(QString content) override;
|
||||
void AddSpecialContent(QString type, QString dataStream) override;
|
||||
void UpdateOptionalData(QString optionalData) override;
|
||||
|
||||
void Display(QRect targetRect, QToolTipWidget::ArrowDirection preferredArrowDir);
|
||||
void TryDisplay(QPoint mousePos, const QWidget* widget, QToolTipWidget::ArrowDirection preferredArrowDir);
|
||||
void TryDisplay(QPoint mousePos, const QRect& widget, QToolTipWidget::ArrowDirection preferredArrowDir);
|
||||
void hide();
|
||||
void show();
|
||||
bool isVisible() const;
|
||||
void update();
|
||||
void repaint(){update();} //Things really shouldn't be calling repaint on these...
|
||||
|
||||
void Hide(){hide();}
|
||||
void close(){hide();}
|
||||
|
||||
private:
|
||||
void ReplayContentOperations(QToolTipWidget* tooltipWidget);
|
||||
|
||||
QToolTipWidget* GetOrCreateToolTip();
|
||||
void DestroyToolTip();
|
||||
|
||||
AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // conditional expression is constant, needs to have dll-interface to be used by clients of class 'AzQtComponents::FilteredSearchWidget'
|
||||
QPointer<QToolTipWidget> m_actualTooltip;
|
||||
AZ_POP_DISABLE_WARNING
|
||||
|
||||
QString m_title;
|
||||
AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // conditional expression is constant, needs to have dll-interface to be used by clients of class 'AzQtComponents::FilteredSearchWidget'
|
||||
QVector<QPair<QString, QString>> m_contentOperations;
|
||||
AZ_POP_DISABLE_WARNING
|
||||
};
|
||||
|
||||
|
||||
#endif // QToolTipWidget_h__
|
||||
@@ -10,7 +10,6 @@
|
||||
|
||||
// Editor
|
||||
#include "PropertyCtrl.h"
|
||||
#include "PropertyResourceCtrl.h"
|
||||
#include "PropertyGenericCtrl.h"
|
||||
#include "PropertyMiscCtrl.h"
|
||||
#include "PropertyMotionCtrl.h"
|
||||
@@ -21,7 +20,6 @@ void RegisterReflectedVarHandlers()
|
||||
if (!registered)
|
||||
{
|
||||
registered = true;
|
||||
EBUS_EVENT(AzToolsFramework::PropertyTypeRegistrationMessages::Bus, RegisterPropertyType, aznew FileResourceSelectorWidgetHandler());
|
||||
EBUS_EVENT(AzToolsFramework::PropertyTypeRegistrationMessages::Bus, RegisterPropertyType, aznew SequencePropertyHandler());
|
||||
EBUS_EVENT(AzToolsFramework::PropertyTypeRegistrationMessages::Bus, RegisterPropertyType, aznew SequenceIdPropertyHandler());
|
||||
EBUS_EVENT(AzToolsFramework::PropertyTypeRegistrationMessages::Bus, RegisterPropertyType, aznew LocalStringPropertyHandler());
|
||||
|
||||
@@ -1,383 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include "EditorDefs.h"
|
||||
|
||||
#include "PropertyResourceCtrl.h"
|
||||
|
||||
// Qt
|
||||
#include <QHBoxLayout>
|
||||
#include <QLineEdit>
|
||||
|
||||
// AzToolsFramework
|
||||
#include <AzToolsFramework/AssetBrowser/AssetSelectionModel.h>
|
||||
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
|
||||
#include <AzToolsFramework/UI/PropertyEditor/PropertyAudioCtrl.h>
|
||||
|
||||
// Editor
|
||||
#include "Controls/QToolTipWidget.h"
|
||||
#include "Controls/BitmapToolTip.h"
|
||||
|
||||
|
||||
BrowseButton::BrowseButton(PropertyType type, QWidget* parent /*= nullptr*/)
|
||||
: QToolButton(parent)
|
||||
, m_propertyType(type)
|
||||
{
|
||||
setAutoRaise(true);
|
||||
setIcon(QIcon(QStringLiteral(":/stylesheet/img/UI20/browse-edit.svg")));
|
||||
connect(this, &QAbstractButton::clicked, this, &BrowseButton::OnClicked);
|
||||
}
|
||||
|
||||
void BrowseButton::SetPathAndEmit(const QString& path)
|
||||
{
|
||||
//only emit if path changes. Old property control
|
||||
if (path != m_path)
|
||||
{
|
||||
m_path = path;
|
||||
emit PathChanged(m_path);
|
||||
}
|
||||
}
|
||||
|
||||
class FileBrowseButton
|
||||
: public BrowseButton
|
||||
{
|
||||
public:
|
||||
AZ_CLASS_ALLOCATOR(FileBrowseButton, AZ::SystemAllocator, 0);
|
||||
FileBrowseButton(PropertyType type, QWidget* pParent = nullptr)
|
||||
: BrowseButton(type, pParent)
|
||||
{
|
||||
setToolTip("Browse...");
|
||||
}
|
||||
|
||||
private:
|
||||
void OnClicked() override
|
||||
{
|
||||
QString tempValue("");
|
||||
if (!m_path.isEmpty() && !Path::GetExt(m_path).isEmpty())
|
||||
{
|
||||
tempValue = m_path;
|
||||
}
|
||||
|
||||
AssetSelectionModel selection;
|
||||
|
||||
if (m_propertyType == ePropertyTexture)
|
||||
{
|
||||
// Filters for texture.
|
||||
selection = AssetSelectionModel::AssetGroupSelection("Texture");
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AzToolsFramework::EditorRequests::Bus::Broadcast(&AzToolsFramework::EditorRequests::BrowseForAssets, selection);
|
||||
if (selection.IsValid())
|
||||
{
|
||||
QString newPath = Path::FullPathToGamePath(selection.GetResult()->GetFullPath().c_str()).c_str();
|
||||
|
||||
switch (m_propertyType)
|
||||
{
|
||||
case ePropertyTexture:
|
||||
newPath.replace("\\\\", "/");
|
||||
if (newPath.size() > MAX_PATH)
|
||||
{
|
||||
newPath.resize(MAX_PATH);
|
||||
}
|
||||
}
|
||||
|
||||
SetPathAndEmit(newPath);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class AudioControlSelectorButton
|
||||
: public BrowseButton
|
||||
{
|
||||
public:
|
||||
AZ_CLASS_ALLOCATOR(AudioControlSelectorButton, AZ::SystemAllocator, 0);
|
||||
|
||||
AudioControlSelectorButton(PropertyType type, QWidget* pParent = nullptr)
|
||||
: BrowseButton(type, pParent)
|
||||
{
|
||||
setToolTip(tr("Select Audio Control"));
|
||||
}
|
||||
|
||||
private:
|
||||
void OnClicked() override
|
||||
{
|
||||
AZStd::string resourceResult;
|
||||
auto ConvertLegacyAudioPropertyType = [](const PropertyType type) -> AzToolsFramework::AudioPropertyType
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case ePropertyAudioTrigger:
|
||||
return AzToolsFramework::AudioPropertyType::Trigger;
|
||||
case ePropertyAudioRTPC:
|
||||
return AzToolsFramework::AudioPropertyType::Rtpc;
|
||||
case ePropertyAudioSwitch:
|
||||
return AzToolsFramework::AudioPropertyType::Switch;
|
||||
case ePropertyAudioSwitchState:
|
||||
return AzToolsFramework::AudioPropertyType::SwitchState;
|
||||
case ePropertyAudioEnvironment:
|
||||
return AzToolsFramework::AudioPropertyType::Environment;
|
||||
case ePropertyAudioPreloadRequest:
|
||||
return AzToolsFramework::AudioPropertyType::Preload;
|
||||
default:
|
||||
return AzToolsFramework::AudioPropertyType::NumTypes;
|
||||
}
|
||||
};
|
||||
|
||||
auto propType = ConvertLegacyAudioPropertyType(m_propertyType);
|
||||
if (propType != AzToolsFramework::AudioPropertyType::NumTypes)
|
||||
{
|
||||
AzToolsFramework::AudioControlSelectorRequestBus::EventResult(
|
||||
resourceResult, propType, &AzToolsFramework::AudioControlSelectorRequestBus::Events::SelectResource,
|
||||
AZStd::string_view{ m_path.toUtf8().constData() });
|
||||
SetPathAndEmit(QString{ resourceResult.c_str() });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class TextureEditButton
|
||||
: public BrowseButton
|
||||
{
|
||||
public:
|
||||
AZ_CLASS_ALLOCATOR(TextureEditButton, AZ::SystemAllocator, 0);
|
||||
TextureEditButton(QWidget* pParent = nullptr)
|
||||
: BrowseButton(ePropertyTexture, pParent)
|
||||
{
|
||||
setIcon(QIcon(QStringLiteral(":/stylesheet/img/UI20/open-in-internal-app.svg")));
|
||||
setToolTip(tr("Launch default editor"));
|
||||
}
|
||||
|
||||
private:
|
||||
void OnClicked() override
|
||||
{
|
||||
CFileUtil::EditTextureFile(m_path.toUtf8().data(), true);
|
||||
}
|
||||
};
|
||||
|
||||
FileResourceSelectorWidget::FileResourceSelectorWidget(QWidget* pParent /*= nullptr*/)
|
||||
: QWidget(pParent)
|
||||
, m_propertyType(ePropertyInvalid)
|
||||
, m_tooltip(nullptr)
|
||||
{
|
||||
m_pathEdit = new QLineEdit;
|
||||
m_mainLayout = new QHBoxLayout(this);
|
||||
m_mainLayout->addWidget(m_pathEdit, 1);
|
||||
|
||||
m_mainLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
// KDAB just ported the MFC texture preview tooltip, but looks like Amazon added their own. Not sure which to use.
|
||||
// To switch to Amazon QToolTipWidget, remove FileResourceSelectorWidget::event and m_previewTooltip
|
||||
#ifdef USE_QTOOLTIPWIDGET
|
||||
m_tooltip = new QToolTipWidget(this);
|
||||
|
||||
installEventFilter(this);
|
||||
#endif
|
||||
connect(m_pathEdit, &QLineEdit::editingFinished, this, [this]() { OnPathChanged(m_pathEdit->text()); });
|
||||
}
|
||||
|
||||
bool FileResourceSelectorWidget::eventFilter([[maybe_unused]] QObject* obj, QEvent* event)
|
||||
{
|
||||
if (m_propertyType == ePropertyTexture)
|
||||
{
|
||||
if (event->type() == QEvent::ToolTip)
|
||||
{
|
||||
QHelpEvent* e = (QHelpEvent*)event;
|
||||
|
||||
m_tooltip->AddSpecialContent("TEXTURE", m_path);
|
||||
m_tooltip->TryDisplay(e->globalPos(), m_pathEdit, QToolTipWidget::ArrowDirection::ARROW_RIGHT);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (event->type() == QEvent::Leave)
|
||||
{
|
||||
m_tooltip->hide();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void FileResourceSelectorWidget::SetPropertyType(PropertyType type)
|
||||
{
|
||||
if (m_propertyType == type)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//if the property type changed for some reason, delete all the existing widgets
|
||||
if (!m_buttons.isEmpty())
|
||||
{
|
||||
qDeleteAll(m_buttons.begin(), m_buttons.end());
|
||||
m_buttons.clear();
|
||||
}
|
||||
|
||||
m_previewToolTip.reset();
|
||||
m_propertyType = type;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case ePropertyTexture:
|
||||
AddButton(new FileBrowseButton(type));
|
||||
AddButton(new TextureEditButton);
|
||||
m_previewToolTip.reset(new CBitmapToolTip);
|
||||
break;
|
||||
case ePropertyAudioTrigger:
|
||||
case ePropertyAudioSwitch:
|
||||
case ePropertyAudioSwitchState:
|
||||
case ePropertyAudioRTPC:
|
||||
case ePropertyAudioEnvironment:
|
||||
case ePropertyAudioPreloadRequest:
|
||||
AddButton(new AudioControlSelectorButton(type));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
m_mainLayout->invalidate();
|
||||
}
|
||||
|
||||
void FileResourceSelectorWidget::AddButton(BrowseButton* button)
|
||||
{
|
||||
m_mainLayout->addWidget(button);
|
||||
m_buttons.push_back(button);
|
||||
connect(button, &BrowseButton::PathChanged, this, &FileResourceSelectorWidget::OnPathChanged);
|
||||
}
|
||||
|
||||
void FileResourceSelectorWidget::OnPathChanged(const QString& path)
|
||||
{
|
||||
bool changed = SetPath(path);
|
||||
if (changed)
|
||||
{
|
||||
emit PathChanged(m_path);
|
||||
}
|
||||
}
|
||||
|
||||
bool FileResourceSelectorWidget::SetPath(const QString& path)
|
||||
{
|
||||
bool changed = false;
|
||||
|
||||
const QString newPath = path.toLower();
|
||||
if (m_path != newPath)
|
||||
{
|
||||
m_path = newPath;
|
||||
UpdateWidgets();
|
||||
|
||||
changed = true;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
|
||||
void FileResourceSelectorWidget::UpdateWidgets()
|
||||
{
|
||||
m_pathEdit->setText(m_path);
|
||||
|
||||
foreach(BrowseButton * button, m_buttons)
|
||||
{
|
||||
button->SetPath(m_path);
|
||||
}
|
||||
|
||||
if (m_previewToolTip)
|
||||
{
|
||||
m_previewToolTip->SetTool(this, rect());
|
||||
}
|
||||
}
|
||||
|
||||
QString FileResourceSelectorWidget::GetPath() const
|
||||
{
|
||||
return m_path;
|
||||
}
|
||||
|
||||
|
||||
|
||||
QWidget* FileResourceSelectorWidget::GetLastInTabOrder()
|
||||
{
|
||||
return m_buttons.empty() ? nullptr : m_buttons.last();
|
||||
}
|
||||
|
||||
QWidget* FileResourceSelectorWidget::GetFirstInTabOrder()
|
||||
{
|
||||
return m_buttons.empty() ? nullptr : m_buttons.first();
|
||||
}
|
||||
|
||||
void FileResourceSelectorWidget::UpdateTabOrder()
|
||||
{
|
||||
if (m_buttons.count() >= 2)
|
||||
{
|
||||
for (int i = 0; i < m_buttons.count() - 1; ++i)
|
||||
{
|
||||
setTabOrder(m_buttons[i], m_buttons[i + 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool FileResourceSelectorWidget::event(QEvent* event)
|
||||
{
|
||||
if (event->type() == QEvent::ToolTip && m_previewToolTip && !m_previewToolTip->isVisible())
|
||||
{
|
||||
if (!m_path.isEmpty())
|
||||
{
|
||||
m_previewToolTip->LoadImage(m_path);
|
||||
m_previewToolTip->setVisible(true);
|
||||
}
|
||||
event->accept();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (event->type() == QEvent::Resize && m_previewToolTip)
|
||||
{
|
||||
m_previewToolTip->SetTool(this, rect());
|
||||
}
|
||||
|
||||
return QWidget::event(event);
|
||||
}
|
||||
|
||||
QWidget* FileResourceSelectorWidgetHandler::CreateGUI(QWidget* pParent)
|
||||
{
|
||||
FileResourceSelectorWidget* newCtrl = aznew FileResourceSelectorWidget(pParent);
|
||||
connect(newCtrl, &FileResourceSelectorWidget::PathChanged, newCtrl, [newCtrl]()
|
||||
{
|
||||
EBUS_EVENT(AzToolsFramework::PropertyEditorGUIMessages::Bus, RequestWrite, newCtrl);
|
||||
});
|
||||
return newCtrl;
|
||||
}
|
||||
|
||||
void FileResourceSelectorWidgetHandler::ConsumeAttribute(FileResourceSelectorWidget* GUI, AZ::u32 attrib, AzToolsFramework::PropertyAttributeReader* attrValue, const char* debugName)
|
||||
{
|
||||
Q_UNUSED(GUI);
|
||||
Q_UNUSED(attrib);
|
||||
Q_UNUSED(attrValue);
|
||||
Q_UNUSED(debugName);
|
||||
}
|
||||
|
||||
void FileResourceSelectorWidgetHandler::WriteGUIValuesIntoProperty(size_t index, FileResourceSelectorWidget* GUI, property_t& instance, AzToolsFramework::InstanceDataNode* node)
|
||||
{
|
||||
Q_UNUSED(index);
|
||||
Q_UNUSED(node);
|
||||
CReflectedVarResource val = instance;
|
||||
val.m_propertyType = GUI->GetPropertyType();
|
||||
val.m_path = GUI->GetPath().toUtf8().data();
|
||||
instance = static_cast<property_t>(val);
|
||||
}
|
||||
|
||||
bool FileResourceSelectorWidgetHandler::ReadValuesIntoGUI(size_t index, FileResourceSelectorWidget* GUI, const property_t& instance, AzToolsFramework::InstanceDataNode* node)
|
||||
{
|
||||
Q_UNUSED(index);
|
||||
Q_UNUSED(node);
|
||||
CReflectedVarResource val = instance;
|
||||
GUI->SetPropertyType(val.m_propertyType);
|
||||
GUI->SetPath(val.m_path.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
#include <Controls/ReflectedPropertyControl/moc_PropertyResourceCtrl.cpp>
|
||||
@@ -1,118 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CRYINCLUDE_EDITOR_UTILS_PROPERTYRESOURCECTRL_H
|
||||
#define CRYINCLUDE_EDITOR_UTILS_PROPERTYRESOURCECTRL_H
|
||||
#pragma once
|
||||
|
||||
#if !defined(Q_MOC_RUN)
|
||||
#include <AzCore/base.h>
|
||||
#include <AzCore/Memory/SystemAllocator.h>
|
||||
#include <AzToolsFramework/UI/PropertyEditor/PropertyEditorAPI.h>
|
||||
#include "ReflectedVar.h"
|
||||
#include "Util/VariablePropertyType.h"
|
||||
#include <QWidget>
|
||||
#include <QtWidgets/QToolButton>
|
||||
#include <QtCore/QVector>
|
||||
#endif
|
||||
|
||||
class QLineEdit;
|
||||
class QHBoxLayout;
|
||||
class CBitmapToolTip;
|
||||
class QToolTipWidget;
|
||||
|
||||
class BrowseButton
|
||||
: public QToolButton
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
AZ_CLASS_ALLOCATOR(BrowseButton, AZ::SystemAllocator, 0);
|
||||
|
||||
BrowseButton(PropertyType type, QWidget* parent = nullptr);
|
||||
|
||||
void SetPath(const QString& path) { m_path = path; }
|
||||
QString GetPath() const { return m_path; }
|
||||
|
||||
PropertyType GetPropertyType() const {return m_propertyType; }
|
||||
|
||||
signals:
|
||||
void PathChanged(const QString& path);
|
||||
|
||||
protected:
|
||||
void SetPathAndEmit(const QString& path);
|
||||
virtual void OnClicked() = 0;
|
||||
|
||||
PropertyType m_propertyType;
|
||||
QString m_path;
|
||||
};
|
||||
|
||||
class FileResourceSelectorWidget
|
||||
: public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
AZ_CLASS_ALLOCATOR(FileResourceSelectorWidget, AZ::SystemAllocator, 0);
|
||||
FileResourceSelectorWidget(QWidget* pParent = nullptr);
|
||||
|
||||
bool SetPath(const QString& path);
|
||||
QString GetPath() const;
|
||||
void SetPropertyType(PropertyType type);
|
||||
PropertyType GetPropertyType() const { return m_propertyType; }
|
||||
|
||||
QWidget* GetFirstInTabOrder();
|
||||
QWidget* GetLastInTabOrder();
|
||||
void UpdateTabOrder();
|
||||
|
||||
bool eventFilter(QObject* obj, QEvent* event) override;
|
||||
|
||||
signals:
|
||||
void PathChanged(const QString& path);
|
||||
|
||||
protected:
|
||||
bool event(QEvent* event) override;
|
||||
|
||||
private:
|
||||
void OnAssignClicked();
|
||||
void OnMaterialClicked();
|
||||
|
||||
void UpdateWidgets();
|
||||
void AddButton(BrowseButton* button);
|
||||
void OnPathChanged(const QString& path);
|
||||
|
||||
private:
|
||||
QLineEdit* m_pathEdit;
|
||||
PropertyType m_propertyType;
|
||||
QString m_path;
|
||||
|
||||
QHBoxLayout* m_mainLayout;
|
||||
QVector<BrowseButton*> m_buttons;
|
||||
QScopedPointer<CBitmapToolTip> m_previewToolTip;
|
||||
QToolTipWidget* m_tooltip;
|
||||
};
|
||||
|
||||
class FileResourceSelectorWidgetHandler
|
||||
: QObject
|
||||
, public AzToolsFramework::PropertyHandler < CReflectedVarResource, FileResourceSelectorWidget >
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
AZ_CLASS_ALLOCATOR(FileResourceSelectorWidgetHandler, AZ::SystemAllocator, 0);
|
||||
|
||||
virtual AZ::u32 GetHandlerName(void) const override { return AZ_CRC("Resource", 0xbc91f416); }
|
||||
virtual bool IsDefaultHandler() const override { return true; }
|
||||
virtual QWidget* GetFirstInTabOrder(FileResourceSelectorWidget* widget) override { return widget->GetFirstInTabOrder(); }
|
||||
virtual QWidget* GetLastInTabOrder(FileResourceSelectorWidget* widget) override { return widget->GetLastInTabOrder(); }
|
||||
virtual void UpdateWidgetInternalTabbing(FileResourceSelectorWidget* widget) override { widget->UpdateTabOrder(); }
|
||||
|
||||
virtual QWidget* CreateGUI(QWidget* pParent) override;
|
||||
virtual void ConsumeAttribute(FileResourceSelectorWidget* GUI, AZ::u32 attrib, AzToolsFramework::PropertyAttributeReader* attrValue, const char* debugName) override;
|
||||
virtual void WriteGUIValuesIntoProperty(size_t index, FileResourceSelectorWidget* GUI, property_t& instance, AzToolsFramework::InstanceDataNode* node) override;
|
||||
virtual bool ReadValuesIntoGUI(size_t index, FileResourceSelectorWidget* GUI, const property_t& instance, AzToolsFramework::InstanceDataNode* node) override;
|
||||
};
|
||||
|
||||
#endif // CRYINCLUDE_EDITOR_UTILS_PROPERTYRESOURCECTRL_H
|
||||
@@ -70,12 +70,6 @@ void ReflectedVarInit::setupReflection(AZ::SerializeContext* serializeContext)
|
||||
AZ::EditContext* ec = serializeContext->GetEditContext();
|
||||
if (ec)
|
||||
{
|
||||
ec->Class< CReflectedVarResource >("VarResource", "Resource")
|
||||
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
|
||||
->Attribute(AZ::Edit::Attributes::NameLabelOverride, &CReflectedVarResource::varName)
|
||||
->Attribute(AZ::Edit::Attributes::DescriptionTextOverride, &CReflectedVarResource::description)
|
||||
;
|
||||
|
||||
ec->Class< CReflectedVarUser >("VarUser", "")
|
||||
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
|
||||
->Attribute(AZ::Edit::Attributes::NameLabelOverride, &CReflectedVarUser::varName)
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#include <QMap>
|
||||
#include <QTranslator>
|
||||
#include <QSet>
|
||||
#include "IEventLoopHook.h"
|
||||
#include <unordered_map>
|
||||
|
||||
#include <AzCore/PlatformDef.h>
|
||||
|
||||
@@ -1828,34 +1828,6 @@ bool CCryEditApp::InitInstance()
|
||||
return true;
|
||||
}
|
||||
|
||||
void CCryEditApp::RegisterEventLoopHook(IEventLoopHook* pHook)
|
||||
{
|
||||
pHook->pNextHook = m_pEventLoopHook;
|
||||
m_pEventLoopHook = pHook;
|
||||
}
|
||||
|
||||
void CCryEditApp::UnregisterEventLoopHook(IEventLoopHook* pHookToRemove)
|
||||
{
|
||||
IEventLoopHook* pPrevious = nullptr;
|
||||
for (IEventLoopHook* pHook = m_pEventLoopHook; pHook != nullptr; pHook = pHook->pNextHook)
|
||||
{
|
||||
if (pHook == pHookToRemove)
|
||||
{
|
||||
if (pPrevious)
|
||||
{
|
||||
pPrevious->pNextHook = pHookToRemove->pNextHook;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_pEventLoopHook = pHookToRemove->pNextHook;
|
||||
}
|
||||
|
||||
pHookToRemove->pNextHook = nullptr;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void CCryEditApp::LoadFile(QString fileName)
|
||||
{
|
||||
|
||||
@@ -30,7 +30,6 @@ class CConsoleDialog;
|
||||
struct mg_connection;
|
||||
struct mg_request_info;
|
||||
struct mg_context;
|
||||
struct IEventLoopHook;
|
||||
class QAction;
|
||||
class MainWindow;
|
||||
class QSharedMemory;
|
||||
@@ -153,8 +152,6 @@ public:
|
||||
int IdleProcessing(bool bBackground);
|
||||
bool IsWindowInForeground();
|
||||
void RunInitPythonScript(CEditCommandLineInfo& cmdInfo);
|
||||
void RegisterEventLoopHook(IEventLoopHook* pHook);
|
||||
void UnregisterEventLoopHook(IEventLoopHook* pHook);
|
||||
|
||||
void DisableIdleProcessing() override;
|
||||
void EnableIdleProcessing() override;
|
||||
@@ -344,7 +341,6 @@ private:
|
||||
|
||||
QString m_lastOpenLevelPath;
|
||||
CQuickAccessBar* m_pQuickAccessBar = nullptr;
|
||||
IEventLoopHook* m_pEventLoopHook = nullptr;
|
||||
QString m_rootEnginePath;
|
||||
|
||||
int m_disableIdleProcessingCounter = 0; //!< Counts requests to disable idle processing. When non-zero, idle processing will be disabled.
|
||||
|
||||
@@ -101,7 +101,7 @@ void CEditorPreferencesPage_AWS::SaveSettingsRegistryFile()
|
||||
return;
|
||||
}
|
||||
|
||||
[[maybe_unused]] bool saved{};
|
||||
[[maybe_unused]] bool saved = false;
|
||||
constexpr auto configurationMode =
|
||||
AZ::IO::SystemFile::SF_OPEN_CREATE | AZ::IO::SystemFile::SF_OPEN_CREATE_PATH | AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY;
|
||||
if (AZ::IO::SystemFile outputFile; outputFile.Open(resolvedPath.data(), configurationMode))
|
||||
|
||||
@@ -586,6 +586,7 @@ void EditorViewportWidget::OnEditorNotifyEvent(EEditorNotifyEvent event)
|
||||
break;
|
||||
|
||||
case eNotify_OnEndLoad:
|
||||
case eNotify_OnEndCreate:
|
||||
UpdateScene();
|
||||
SetDefaultCamera();
|
||||
break;
|
||||
|
||||
@@ -317,8 +317,8 @@ void CGameExporter::ExportLevelInfo(const QString& path)
|
||||
root->setAttr("Name", levelName.toUtf8().data());
|
||||
auto terrain = AzFramework::Terrain::TerrainDataRequestBus::FindFirstHandler();
|
||||
const AZ::Aabb terrainAabb = terrain ? terrain->GetTerrainAabb() : AZ::Aabb::CreateFromPoint(AZ::Vector3::CreateZero());
|
||||
const AZ::Vector2 terrainGridResolution = terrain ? terrain->GetTerrainHeightQueryResolution() : AZ::Vector2::CreateOne();
|
||||
const int compiledHeightmapSize = static_cast<int>(terrainAabb.GetXExtent() / terrainGridResolution.GetX());
|
||||
const float terrainGridResolution = terrain ? terrain->GetTerrainHeightQueryResolution() : 1.0f;
|
||||
const int compiledHeightmapSize = static_cast<int>(terrainAabb.GetXExtent() / terrainGridResolution);
|
||||
root->setAttr("HeightmapSize", compiledHeightmapSize);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -66,7 +66,6 @@ class IAWSResourceManager;
|
||||
struct ISystem;
|
||||
struct IRenderer;
|
||||
struct AABB;
|
||||
struct IEventLoopHook;
|
||||
struct IErrorReport; // Vladimir@conffx
|
||||
struct IFileUtil; // Vladimir@conffx
|
||||
struct IEditorLog; // Vladimir@conffx
|
||||
@@ -509,11 +508,6 @@ struct IEditor
|
||||
virtual void SetActiveView(CViewport* viewport) = 0;
|
||||
virtual struct IEditorFileMonitor* GetFileMonitor() = 0;
|
||||
|
||||
// These are needed for Qt integration:
|
||||
virtual void RegisterEventLoopHook(IEventLoopHook* pHook) = 0;
|
||||
virtual void UnregisterEventLoopHook(IEventLoopHook* pHook) = 0;
|
||||
// ^^^
|
||||
|
||||
//! QMimeData is used by the Qt clipboard.
|
||||
//! IMPORTANT: Any QMimeData allocated for the clipboard will be deleted
|
||||
//! when the editor exists. If a QMimeData is allocated by a different
|
||||
|
||||
@@ -789,16 +789,6 @@ IEditorFileMonitor* CEditorImpl::GetFileMonitor()
|
||||
return m_pEditorFileMonitor.get();
|
||||
}
|
||||
|
||||
void CEditorImpl::RegisterEventLoopHook(IEventLoopHook* pHook)
|
||||
{
|
||||
CCryEditApp::instance()->RegisterEventLoopHook(pHook);
|
||||
}
|
||||
|
||||
void CEditorImpl::UnregisterEventLoopHook(IEventLoopHook* pHook)
|
||||
{
|
||||
CCryEditApp::instance()->UnregisterEventLoopHook(pHook);
|
||||
}
|
||||
|
||||
float CEditorImpl::GetTerrainElevation(float x, float y)
|
||||
{
|
||||
float terrainElevation = AzFramework::Terrain::TerrainDataRequests::GetDefaultTerrainHeight();
|
||||
|
||||
@@ -155,8 +155,6 @@ public:
|
||||
CMusicManager* GetMusicManager() override { return m_pMusicManager; };
|
||||
|
||||
IEditorFileMonitor* GetFileMonitor() override;
|
||||
void RegisterEventLoopHook(IEventLoopHook* pHook) override;
|
||||
void UnregisterEventLoopHook(IEventLoopHook* pHook) override;
|
||||
IIconManager* GetIconManager() override;
|
||||
float GetTerrainElevation(float x, float y) override;
|
||||
Editor::EditorQtApplication* GetEditorQtApplication() override { return m_QtApplication; }
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef CRYINCLUDE_EDITOR_INCLUDE_IEVENTLOOPHOOK_H
|
||||
#define CRYINCLUDE_EDITOR_INCLUDE_IEVENTLOOPHOOK_H
|
||||
#pragma once
|
||||
|
||||
struct IEventLoopHook
|
||||
{
|
||||
IEventLoopHook* pNextHook;
|
||||
|
||||
IEventLoopHook()
|
||||
: pNextHook(0) {}
|
||||
|
||||
virtual bool PrePumpMessage() { return false; }
|
||||
};
|
||||
|
||||
#endif // CRYINCLUDE_EDITOR_INCLUDE_IEVENTLOOPHOOK_H
|
||||
@@ -97,8 +97,6 @@ public:
|
||||
MOCK_METHOD0(GetActiveView, class CViewport* ());
|
||||
MOCK_METHOD1(SetActiveView, void(CViewport*));
|
||||
MOCK_METHOD0(GetFileMonitor, struct IEditorFileMonitor* ());
|
||||
MOCK_METHOD1(RegisterEventLoopHook, void(IEventLoopHook* ));
|
||||
MOCK_METHOD1(UnregisterEventLoopHook, void(IEventLoopHook* ));
|
||||
MOCK_CONST_METHOD0(CreateQMimeData, QMimeData* ());
|
||||
MOCK_CONST_METHOD1(DestroyQMimeData, void(QMimeData*));
|
||||
MOCK_METHOD0(GetLevelIndependentFileMan, class CLevelIndependentFileMan* ());
|
||||
|
||||
@@ -499,14 +499,12 @@ void CEntityObject::AdjustLightProperties(CVarBlockPtr& properties, const char*
|
||||
pAreaLight->SetHumanName("PlanarLight");
|
||||
}
|
||||
|
||||
bool bCastShadowLegacy = false; // Backward compatibility for existing shadow casting lights
|
||||
if (IVariable* pCastShadowVarLegacy = FindVariableInSubBlock(properties, pSubBlockVar, "bCastShadow"))
|
||||
{
|
||||
pCastShadowVarLegacy->SetFlags(pCastShadowVarLegacy->GetFlags() | IVariable::UI_INVISIBLE);
|
||||
const QString zeroPrefix("0");
|
||||
if (!pCastShadowVarLegacy->GetDisplayValue().startsWith(zeroPrefix))
|
||||
{
|
||||
bCastShadowLegacy = true;
|
||||
pCastShadowVarLegacy->SetDisplayValue(zeroPrefix);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1084,7 +1084,7 @@ void SEditorSettings::SaveSettingsRegistryFile()
|
||||
return;
|
||||
}
|
||||
|
||||
[[maybe_unused]] bool saved{};
|
||||
[[maybe_unused]] bool saved = false;
|
||||
constexpr auto configurationMode = AZ::IO::SystemFile::SF_OPEN_CREATE
|
||||
| AZ::IO::SystemFile::SF_OPEN_CREATE_PATH
|
||||
| AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY;
|
||||
|
||||
@@ -22,13 +22,6 @@ set(FILES
|
||||
Controls/ReflectedPropertyControl/ReflectedVar.h
|
||||
Controls/ReflectedPropertyControl/ReflectedVarWrapper.cpp
|
||||
Controls/ReflectedPropertyControl/ReflectedVarWrapper.h
|
||||
Controls/QBitmapPreviewDialog.cpp
|
||||
Controls/QBitmapPreviewDialog.h
|
||||
Controls/QBitmapPreviewDialog.ui
|
||||
Controls/QBitmapPreviewDialogImp.cpp
|
||||
Controls/QBitmapPreviewDialogImp.h
|
||||
Controls/QToolTipWidget.h
|
||||
Controls/QToolTipWidget.cpp
|
||||
UsedResources.cpp
|
||||
LyViewPaneNames.h
|
||||
QtViewPaneManager.cpp
|
||||
|
||||
@@ -270,7 +270,6 @@ set(FILES
|
||||
Include/ICommandManager.h
|
||||
Include/IDisplayViewport.h
|
||||
Include/IEditorClassFactory.h
|
||||
Include/IEventLoopHook.h
|
||||
Include/IExportManager.h
|
||||
Include/IGizmoManager.h
|
||||
Include/IIconManager.h
|
||||
@@ -314,8 +313,6 @@ set(FILES
|
||||
AssetEditor/AssetEditorWindow.ui
|
||||
Commands/CommandManager.cpp
|
||||
Commands/CommandManager.h
|
||||
Controls/BitmapToolTip.cpp
|
||||
Controls/BitmapToolTip.h
|
||||
Controls/ConsoleSCB.cpp
|
||||
Controls/ConsoleSCB.h
|
||||
Controls/ConsoleSCB.ui
|
||||
@@ -337,8 +334,6 @@ set(FILES
|
||||
Controls/ReflectedPropertyControl/PropertyMiscCtrl.h
|
||||
Controls/ReflectedPropertyControl/PropertyMotionCtrl.cpp
|
||||
Controls/ReflectedPropertyControl/PropertyMotionCtrl.h
|
||||
Controls/ReflectedPropertyControl/PropertyResourceCtrl.cpp
|
||||
Controls/ReflectedPropertyControl/PropertyResourceCtrl.h
|
||||
Controls/ReflectedPropertyControl/PropertyCtrl.cpp
|
||||
Controls/ReflectedPropertyControl/PropertyCtrl.h
|
||||
MainStatusBar.cpp
|
||||
|
||||
@@ -36,5 +36,12 @@ namespace AZStd
|
||||
{
|
||||
pthread_setname_np(tId, name);
|
||||
}
|
||||
|
||||
uint8_t GetDefaultThreadPriority()
|
||||
{
|
||||
// pthread priority is an integer between >=1 and <=99 (although only range 1<=>32 is guaranteed)
|
||||
// Don't use a scheduling policy value (e.g. SCHED_OTHER or SCHED_FIFO) here.
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+14
-1
@@ -35,7 +35,7 @@ namespace AZStd
|
||||
|
||||
void SetThreadPriority(int priority, pthread_attr_t& attr)
|
||||
{
|
||||
if (priority == -1)
|
||||
if (priority <= -1)
|
||||
{
|
||||
pthread_attr_setinheritsched(&attr, PTHREAD_INHERIT_SCHED);
|
||||
}
|
||||
@@ -59,5 +59,18 @@ namespace AZStd
|
||||
thread_policy_set(mach_thread, THREAD_AFFINITY_POLICY, (thread_policy_t)& policyData, 1);
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
// Apple pthread -> NSThread quality of service level map
|
||||
// QOS class name | min pthread priority | max pthread priority | comment
|
||||
// QOS_CLASS_USER_INTERACTIVE | 38 | 47 | Per-frame work
|
||||
// QOS_CLASS_USER_INITIATED | 32 | 37 | Asynchronous / Cross frame work
|
||||
// QOS_CLASS_DEFAULT | 21 | 31 | Streaming / Multiple frames deadline
|
||||
// QOS_CLASS_UTILITY | 5 | 20 | Background asset download
|
||||
// QOS_CLASS_BACKGROUN | 0 | 4 | Will be prevented from using whole core.
|
||||
uint8_t GetDefaultThreadPriority()
|
||||
{
|
||||
return 10;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -21,6 +21,7 @@ namespace AZStd
|
||||
void PreCreateSetThreadAffinity(int cpuId, pthread_attr_t& attr);
|
||||
void SetThreadPriority(int priority, pthread_attr_t& attr);
|
||||
void PostCreateThread(pthread_t tId, const char * name, int cpuId);
|
||||
uint8_t GetDefaultThreadPriority();
|
||||
}
|
||||
|
||||
namespace Internal
|
||||
@@ -60,12 +61,13 @@ namespace AZStd
|
||||
}
|
||||
else
|
||||
{
|
||||
priority = SCHED_OTHER;
|
||||
priority = Platform::GetDefaultThreadPriority();
|
||||
}
|
||||
if (desc->m_name)
|
||||
{
|
||||
name = desc->m_name;
|
||||
}
|
||||
ti->m_name = name;
|
||||
cpuId = desc->m_cpuId;
|
||||
|
||||
pthread_attr_setdetachstate(&attr, desc->m_isJoinable ? PTHREAD_CREATE_JOINABLE : PTHREAD_CREATE_DETACHED);
|
||||
|
||||
@@ -55,5 +55,12 @@ namespace AZStd
|
||||
{
|
||||
pthread_setname_np(tId, name);
|
||||
}
|
||||
|
||||
uint8_t GetDefaultThreadPriority()
|
||||
{
|
||||
// pthread priority is an integer between >=1 and <=99 (although only range 1<=>32 is guaranteed)
|
||||
// Don't use a scheduling policy value (e.g. SCHED_OTHER or SCHED_FIFO) here.
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -447,13 +447,13 @@ namespace UnitTest
|
||||
{
|
||||
x -= 1;
|
||||
});
|
||||
|
||||
// a <-- Root
|
||||
// / \
|
||||
// b c
|
||||
// \ /
|
||||
// d
|
||||
|
||||
/*
|
||||
a <-- Root
|
||||
/ \
|
||||
b c
|
||||
\ /
|
||||
d
|
||||
*/
|
||||
a.Precedes(b, c);
|
||||
d.Follows(b, c);
|
||||
|
||||
@@ -522,20 +522,20 @@ namespace UnitTest
|
||||
{
|
||||
x -= 1;
|
||||
});
|
||||
|
||||
// NOTE: The ideal way to express this topology is without the wait on the subgraph
|
||||
// at task g, but this is more an illustrative test. Better is to express the entire
|
||||
// graph in a single larger graph.
|
||||
// a <-- Root
|
||||
// / \
|
||||
// b c - f
|
||||
// \ \ \
|
||||
// \ e - g
|
||||
// \ /
|
||||
// \ /
|
||||
// \ /
|
||||
// d
|
||||
|
||||
/*
|
||||
NOTE: The ideal way to express this topology is without the wait on the subgraph
|
||||
at task g, but this is more an illustrative test. Better is to express the entire
|
||||
graph in a single larger graph.
|
||||
a <-- Root
|
||||
/ \
|
||||
b c - f
|
||||
\ \ \
|
||||
\ e - g
|
||||
\ /
|
||||
\ /
|
||||
\ /
|
||||
d
|
||||
*/
|
||||
a.Precedes(b);
|
||||
a.Precedes(c);
|
||||
b.Precedes(d);
|
||||
@@ -593,17 +593,17 @@ namespace UnitTest
|
||||
{
|
||||
x += 0b1000;
|
||||
});
|
||||
|
||||
// a <-- Root
|
||||
// / \
|
||||
// b c - f
|
||||
// \ \ \
|
||||
// \ e - g
|
||||
// \ /
|
||||
// \ /
|
||||
// \ /
|
||||
// d
|
||||
|
||||
/*
|
||||
a <-- Root
|
||||
/ \
|
||||
b c - f
|
||||
\ \ \
|
||||
\ e - g
|
||||
\ /
|
||||
\ /
|
||||
\ /
|
||||
d
|
||||
*/
|
||||
a.Precedes(b, c);
|
||||
b.Precedes(d);
|
||||
c.Precedes(e, f);
|
||||
|
||||
@@ -378,7 +378,7 @@ namespace Physics
|
||||
m_cachedNativeHeightfield = cachedNativeHeightfield;
|
||||
}
|
||||
|
||||
AZ::Vector2 HeightfieldShapeConfiguration::GetGridResolution() const
|
||||
const AZ::Vector2& HeightfieldShapeConfiguration::GetGridResolution() const
|
||||
{
|
||||
return m_gridResolution;
|
||||
}
|
||||
|
||||
@@ -221,7 +221,7 @@ namespace Physics
|
||||
const void* GetCachedNativeHeightfield() const;
|
||||
void* GetCachedNativeHeightfield();
|
||||
void SetCachedNativeHeightfield(void* cachedNativeHeightfield);
|
||||
AZ::Vector2 GetGridResolution() const;
|
||||
const AZ::Vector2& GetGridResolution() const;
|
||||
void SetGridResolution(const AZ::Vector2& gridSpacing);
|
||||
int32_t GetNumColumns() const;
|
||||
void SetNumColumns(int32_t numColumns);
|
||||
@@ -235,7 +235,7 @@ namespace Physics
|
||||
void SetMaxHeightBounds(float maxBounds);
|
||||
|
||||
private:
|
||||
//! The number of meters between each heightfield sample.
|
||||
//! The number of meters between each heightfield sample in x and y.
|
||||
AZ::Vector2 m_gridResolution{ 1.0f };
|
||||
//! The number of columns in the heightfield sample grid.
|
||||
int32_t m_numColumns{ 0 };
|
||||
|
||||
@@ -50,8 +50,8 @@ namespace AzFramework
|
||||
static AZ::Vector3 GetDefaultTerrainNormal() { return AZ::Vector3::CreateAxisZ(); }
|
||||
|
||||
// System-level queries to understand world size and resolution
|
||||
virtual AZ::Vector2 GetTerrainHeightQueryResolution() const = 0;
|
||||
virtual void SetTerrainHeightQueryResolution(AZ::Vector2 queryResolution) = 0;
|
||||
virtual float GetTerrainHeightQueryResolution() const = 0;
|
||||
virtual void SetTerrainHeightQueryResolution(float queryResolution) = 0;
|
||||
|
||||
virtual AZ::Aabb GetTerrainAabb() const = 0;
|
||||
virtual void SetTerrainAabb(const AZ::Aabb& worldBounds) = 0;
|
||||
|
||||
@@ -49,8 +49,8 @@ namespace UnitTest
|
||||
AzFramework::Terrain::TerrainDataRequestBus::Handler::BusDisconnect();
|
||||
}
|
||||
|
||||
MOCK_CONST_METHOD0(GetTerrainHeightQueryResolution, AZ::Vector2());
|
||||
MOCK_METHOD1(SetTerrainHeightQueryResolution, void(AZ::Vector2));
|
||||
MOCK_CONST_METHOD0(GetTerrainHeightQueryResolution, float());
|
||||
MOCK_METHOD1(SetTerrainHeightQueryResolution, void(float));
|
||||
MOCK_CONST_METHOD0(GetTerrainAabb, AZ::Aabb());
|
||||
MOCK_METHOD1(SetTerrainAabb, void(const AZ::Aabb&));
|
||||
MOCK_CONST_METHOD3(GetHeight, float(const AZ::Vector3&, Sampler, bool*));
|
||||
|
||||
-2
@@ -47,8 +47,6 @@ namespace AzManipulatorTestFramework
|
||||
virtual void UpdateVisibility() = 0;
|
||||
//! Sets if sticky select is enabled or not.
|
||||
virtual void SetStickySelect(bool enabled) = 0;
|
||||
//! Gets default Editor Camera Position.
|
||||
virtual AZ::Vector3 DefaultEditorCameraPosition() const = 0;
|
||||
//! Sets if icons are visible in the viewport.
|
||||
virtual void SetIconsVisible(bool visible) = 0;
|
||||
//! Sets if helpers are visible in the viewport.
|
||||
|
||||
+2
-21
@@ -10,6 +10,7 @@
|
||||
|
||||
#include <AzFramework/Visibility/EntityVisibilityQuery.h>
|
||||
#include <AzManipulatorTestFramework/AzManipulatorTestFramework.h>
|
||||
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
@@ -22,7 +23,7 @@ namespace AzManipulatorTestFramework
|
||||
class ViewportInteraction
|
||||
: public ViewportInteractionInterface
|
||||
, public AzToolsFramework::ViewportInteraction::ViewportInteractionRequestBus::Handler
|
||||
, public AzToolsFramework::ViewportInteraction::ViewportSettingsRequestBus::Handler
|
||||
, public UnitTest::ViewportSettingsTestImpl
|
||||
, private AzToolsFramework::ViewportInteraction::EditorEntityViewportInteractionRequestBus::Handler
|
||||
{
|
||||
public:
|
||||
@@ -50,19 +51,6 @@ namespace AzManipulatorTestFramework
|
||||
const AzFramework::ScreenPoint& screenPosition) override;
|
||||
float DeviceScalingFactor() override;
|
||||
|
||||
// ViewportSettingsRequestBus overrides ...
|
||||
bool GridSnappingEnabled() const override;
|
||||
float GridSize() const override;
|
||||
bool ShowGrid() const override;
|
||||
bool AngleSnappingEnabled() const override;
|
||||
float AngleStep() const override;
|
||||
float ManipulatorLineBoundWidth() const override;
|
||||
float ManipulatorCircleBoundWidth() const override;
|
||||
bool StickySelectEnabled() const override;
|
||||
AZ::Vector3 DefaultEditorCameraPosition() const override;
|
||||
bool IconsVisible() const override;
|
||||
bool HelpersVisible() const override;
|
||||
|
||||
// EditorEntityViewportInteractionRequestBus overrides ...
|
||||
void FindVisibleEntities(AZStd::vector<AZ::EntityId>& visibleEntities) override;
|
||||
|
||||
@@ -72,12 +60,5 @@ namespace AzManipulatorTestFramework
|
||||
AzFramework::EntityVisibilityQuery m_entityVisibilityQuery;
|
||||
AZStd::shared_ptr<AzFramework::DebugDisplayRequests> m_debugDisplayRequests;
|
||||
AzFramework::CameraState m_cameraState;
|
||||
float m_gridSize = 1.0f;
|
||||
float m_angularStep = 0.0f;
|
||||
bool m_gridSnapping = false;
|
||||
bool m_angularSnapping = false;
|
||||
bool m_stickySelect = true;
|
||||
bool m_iconsVisible = true;
|
||||
bool m_helpersVisible = true;
|
||||
};
|
||||
} // namespace AzManipulatorTestFramework
|
||||
|
||||
@@ -37,46 +37,6 @@ namespace AzManipulatorTestFramework
|
||||
return m_cameraState;
|
||||
}
|
||||
|
||||
bool ViewportInteraction::GridSnappingEnabled() const
|
||||
{
|
||||
return m_gridSnapping;
|
||||
}
|
||||
|
||||
float ViewportInteraction::GridSize() const
|
||||
{
|
||||
return m_gridSize;
|
||||
}
|
||||
|
||||
bool ViewportInteraction::ShowGrid() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ViewportInteraction::AngleSnappingEnabled() const
|
||||
{
|
||||
return m_angularSnapping;
|
||||
}
|
||||
|
||||
float ViewportInteraction::AngleStep() const
|
||||
{
|
||||
return m_angularStep;
|
||||
}
|
||||
|
||||
float ViewportInteraction::ManipulatorLineBoundWidth() const
|
||||
{
|
||||
return 0.1f;
|
||||
}
|
||||
|
||||
float ViewportInteraction::ManipulatorCircleBoundWidth() const
|
||||
{
|
||||
return 0.1f;
|
||||
}
|
||||
|
||||
bool ViewportInteraction::StickySelectEnabled() const
|
||||
{
|
||||
return m_stickySelect;
|
||||
}
|
||||
|
||||
void ViewportInteraction::FindVisibleEntities(AZStd::vector<AZ::EntityId>& visibleEntitiesOut)
|
||||
{
|
||||
visibleEntitiesOut.assign(m_entityVisibilityQuery.Begin(), m_entityVisibilityQuery.End());
|
||||
@@ -127,11 +87,6 @@ namespace AzManipulatorTestFramework
|
||||
m_helpersVisible = visible;
|
||||
}
|
||||
|
||||
AZ::Vector3 ViewportInteraction::DefaultEditorCameraPosition() const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
void ViewportInteraction::SetGridSize(float size)
|
||||
{
|
||||
m_gridSize = size;
|
||||
@@ -162,14 +117,4 @@ namespace AzManipulatorTestFramework
|
||||
{
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
bool ViewportInteraction::IconsVisible() const
|
||||
{
|
||||
return m_iconsVisible;
|
||||
}
|
||||
|
||||
bool ViewportInteraction::HelpersVisible() const
|
||||
{
|
||||
return m_helpersVisible;
|
||||
}
|
||||
} // namespace AzManipulatorTestFramework
|
||||
|
||||
+1
-12
@@ -80,7 +80,6 @@ namespace AzToolsFramework
|
||||
EntityOutlinerListModel::EntityOutlinerListModel(QObject* parent)
|
||||
: QAbstractItemModel(parent)
|
||||
, m_entitySelectQueue()
|
||||
, m_entityExpandQueue()
|
||||
, m_entityChangeQueue()
|
||||
, m_entityChangeQueued(false)
|
||||
, m_entityLayoutQueued(false)
|
||||
@@ -1275,7 +1274,6 @@ namespace AzToolsFramework
|
||||
void EntityOutlinerListModel::QueueEntityToExpand(AZ::EntityId entityId, bool expand)
|
||||
{
|
||||
m_entityExpansionState[entityId] = expand;
|
||||
m_entityExpandQueue.insert(entityId);
|
||||
QueueEntityUpdate(entityId);
|
||||
}
|
||||
|
||||
@@ -1300,16 +1298,7 @@ namespace AzToolsFramework
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
AZ_PROFILE_SCOPE(Editor, "EntityOutlinerListModel::ProcessEntityUpdates:ExpandQueue");
|
||||
for (auto entityId : m_entityExpandQueue)
|
||||
{
|
||||
emit ExpandEntity(entityId, IsExpanded(entityId));
|
||||
};
|
||||
m_entityExpandQueue.clear();
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
AZ_PROFILE_SCOPE(Editor, "EntityOutlinerListModel::ProcessEntityUpdates:SelectQueue");
|
||||
for (auto entityId : m_entitySelectQueue)
|
||||
|
||||
-2
@@ -156,7 +156,6 @@ namespace AzToolsFramework
|
||||
void ProcessEntityUpdates();
|
||||
|
||||
Q_SIGNALS:
|
||||
void ExpandEntity(const AZ::EntityId& entityId, bool expand);
|
||||
void SelectEntity(const AZ::EntityId& entityId, bool select);
|
||||
void EnableSelectionUpdates(bool enable);
|
||||
void ResetFilter();
|
||||
@@ -190,7 +189,6 @@ namespace AzToolsFramework
|
||||
void QueueEntityToExpand(AZ::EntityId entityId, bool expand);
|
||||
void ProcessEntityInfoResetEnd();
|
||||
AZStd::unordered_set<AZ::EntityId> m_entitySelectQueue;
|
||||
AZStd::unordered_set<AZ::EntityId> m_entityExpandQueue;
|
||||
AZStd::unordered_set<AZ::EntityId> m_entityChangeQueue;
|
||||
bool m_entityChangeQueued;
|
||||
bool m_entityLayoutQueued;
|
||||
|
||||
+55
@@ -81,6 +81,61 @@ namespace AzToolsFramework
|
||||
update();
|
||||
}
|
||||
|
||||
void EntityOutlinerTreeView::dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector<int>& roles)
|
||||
{
|
||||
AzQtComponents::StyledTreeView::dataChanged(topLeft, bottomRight, roles);
|
||||
|
||||
if (topLeft.isValid() && topLeft.parent() == bottomRight.parent() && topLeft.row() <= bottomRight.row() &&
|
||||
topLeft.column() <= bottomRight.column())
|
||||
{
|
||||
for (int i = topLeft.row(); i <= bottomRight.row(); i++)
|
||||
{
|
||||
auto modelRow = topLeft.sibling(i, EntityOutlinerListModel::ColumnName);
|
||||
if (modelRow.isValid())
|
||||
{
|
||||
checkExpandedState(modelRow);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EntityOutlinerTreeView::rowsInserted(const QModelIndex& parent, int start, int end)
|
||||
{
|
||||
if (parent.isValid())
|
||||
{
|
||||
for (int i = start; i <= end; i++)
|
||||
{
|
||||
auto modelRow = model()->index(i, EntityOutlinerListModel::ColumnName, parent);
|
||||
if (modelRow.isValid())
|
||||
{
|
||||
checkExpandedState(modelRow);
|
||||
recursiveCheckExpandedStates(modelRow);
|
||||
}
|
||||
}
|
||||
}
|
||||
AzQtComponents::StyledTreeView::rowsInserted(parent, start, end);
|
||||
}
|
||||
|
||||
void EntityOutlinerTreeView::recursiveCheckExpandedStates(const QModelIndex& current)
|
||||
{
|
||||
const int rowCount = model()->rowCount(current);
|
||||
for (int i = 0; i < rowCount; i++)
|
||||
{
|
||||
auto modelRow = model()->index(i, EntityOutlinerListModel::ColumnName, current);
|
||||
if (modelRow.isValid())
|
||||
{
|
||||
checkExpandedState(modelRow);
|
||||
recursiveCheckExpandedStates(modelRow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EntityOutlinerTreeView::checkExpandedState(const QModelIndex& current)
|
||||
{
|
||||
const bool expandState = current.data(EntityOutlinerListModel::ExpandedRole).template value<bool>();
|
||||
setExpanded(current, expandState);
|
||||
}
|
||||
|
||||
void EntityOutlinerTreeView::mousePressEvent(QMouseEvent* event)
|
||||
{
|
||||
//postponing normal mouse pressed logic until mouse is released or dragged
|
||||
|
||||
+6
@@ -51,6 +51,10 @@ namespace AzToolsFramework
|
||||
Q_SIGNALS:
|
||||
void ItemDropped();
|
||||
|
||||
protected Q_SLOTS:
|
||||
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles = QVector<int>()) override;
|
||||
void rowsInserted(const QModelIndex &parent, int start, int end) override;
|
||||
|
||||
protected:
|
||||
// Qt overrides
|
||||
void mousePressEvent(QMouseEvent* event) override;
|
||||
@@ -75,6 +79,8 @@ namespace AzToolsFramework
|
||||
void ClearQueuedMouseEvent();
|
||||
|
||||
void processQueuedMousePressedEvent(QMouseEvent* event);
|
||||
void recursiveCheckExpandedStates(const QModelIndex& parent);
|
||||
void checkExpandedState(const QModelIndex& current);
|
||||
|
||||
void StartCustomDrag(const QModelIndexList& indexList, Qt::DropActions supportedActions) override;
|
||||
|
||||
|
||||
@@ -224,7 +224,6 @@ namespace AzToolsFramework
|
||||
connect(m_gui->m_objectTree, &QTreeView::expanded, this, &EntityOutlinerWidget::OnTreeItemExpanded);
|
||||
connect(m_gui->m_objectTree, &QTreeView::collapsed, this, &EntityOutlinerWidget::OnTreeItemCollapsed);
|
||||
connect(m_gui->m_objectTree, &EntityOutlinerTreeView::ItemDropped, this, &EntityOutlinerWidget::OnDropEvent);
|
||||
connect(m_listModel, &EntityOutlinerListModel::ExpandEntity, this, &EntityOutlinerWidget::OnExpandEntity);
|
||||
connect(m_listModel, &EntityOutlinerListModel::SelectEntity, this, &EntityOutlinerWidget::OnSelectEntity);
|
||||
connect(m_listModel, &EntityOutlinerListModel::EnableSelectionUpdates, this, &EntityOutlinerWidget::OnEnableSelectionUpdates);
|
||||
connect(m_listModel, &EntityOutlinerListModel::ResetFilter, this, &EntityOutlinerWidget::ClearFilter);
|
||||
@@ -972,10 +971,6 @@ namespace AzToolsFramework
|
||||
m_listModel->OnEntityCollapsed(entityId);
|
||||
}
|
||||
|
||||
void EntityOutlinerWidget::OnExpandEntity(const AZ::EntityId& entityId, bool expand)
|
||||
{
|
||||
m_gui->m_objectTree->setExpanded(GetIndexFromEntityId(entityId), expand);
|
||||
}
|
||||
|
||||
void EntityOutlinerWidget::OnSelectEntity(const AZ::EntityId& entityId, bool selected)
|
||||
{
|
||||
|
||||
@@ -155,7 +155,6 @@ namespace AzToolsFramework
|
||||
void OnTreeItemDoubleClicked(const QModelIndex& index);
|
||||
void OnTreeItemExpanded(const QModelIndex& index);
|
||||
void OnTreeItemCollapsed(const QModelIndex& index);
|
||||
void OnExpandEntity(const AZ::EntityId& entityId, bool expand);
|
||||
void OnSelectEntity(const AZ::EntityId& entityId, bool selected);
|
||||
void OnEnableSelectionUpdates(bool enable);
|
||||
void OnDropEvent();
|
||||
|
||||
@@ -306,41 +306,37 @@ namespace AzToolsFramework
|
||||
{
|
||||
// Only show the close icon if the prefab is expanded.
|
||||
// This allows the prefab container to be opened if it was collapsed during propagation.
|
||||
if (!isExpanded)
|
||||
if (isExpanded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Use the same color as the background.
|
||||
QColor backgroundColor = m_backgroundColor;
|
||||
if (isSelected)
|
||||
{
|
||||
backgroundColor = m_backgroundSelectedColor;
|
||||
}
|
||||
else if (isHovered)
|
||||
{
|
||||
backgroundColor = m_backgroundHoverColor;
|
||||
}
|
||||
|
||||
// Use the same color as the background.
|
||||
QColor backgroundColor = m_backgroundColor;
|
||||
if (isSelected)
|
||||
{
|
||||
backgroundColor = m_backgroundSelectedColor;
|
||||
}
|
||||
else if (isHovered)
|
||||
{
|
||||
backgroundColor = m_backgroundHoverColor;
|
||||
}
|
||||
// Paint a rect to cover up the expander.
|
||||
QRect rect = QRect(0, 0, 16, 16);
|
||||
rect.translate(option.rect.topLeft() + offset);
|
||||
painter->fillRect(rect, backgroundColor);
|
||||
|
||||
// Paint a rect to cover up the expander.
|
||||
QRect rect = QRect(0, 0, 16, 16);
|
||||
rect.translate(option.rect.topLeft() + offset);
|
||||
painter->fillRect(rect, backgroundColor);
|
||||
|
||||
// Paint the icon.
|
||||
QIcon closeIcon = QIcon(m_prefabEditCloseIconPath);
|
||||
painter->drawPixmap(option.rect.topLeft() + offset, closeIcon.pixmap(iconSize));
|
||||
// Paint the icon.
|
||||
QIcon closeIcon = QIcon(m_prefabEditCloseIconPath);
|
||||
painter->drawPixmap(option.rect.topLeft() + offset, closeIcon.pixmap(iconSize));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only show the edit icon on hover.
|
||||
if (!isHovered)
|
||||
if (isHovered)
|
||||
{
|
||||
return;
|
||||
QIcon openIcon = QIcon(m_prefabEditOpenIconPath);
|
||||
painter->drawPixmap(option.rect.topLeft() + offset, openIcon.pixmap(iconSize));
|
||||
}
|
||||
|
||||
QIcon openIcon = QIcon(m_prefabEditOpenIconPath);
|
||||
painter->drawPixmap(option.rect.topLeft() + offset, openIcon.pixmap(iconSize));
|
||||
}
|
||||
|
||||
painter->restore();
|
||||
|
||||
+1
-1
@@ -122,7 +122,7 @@ namespace AzToolsFramework
|
||||
|
||||
EditorInteractionSystemViewportSelectionRequestBus::Event(
|
||||
GetEntityContextId(), &EditorInteractionSystemViewportSelection::SetHandler,
|
||||
[](const EditorVisibleEntityDataCache* entityDataCache, ViewportEditorModeTrackerInterface* viewportEditorModeTracker)
|
||||
[](const EditorVisibleEntityDataCacheInterface* entityDataCache, ViewportEditorModeTrackerInterface* viewportEditorModeTracker)
|
||||
{
|
||||
return AZStd::make_unique<EditorPickEntitySelection>(entityDataCache, viewportEditorModeTracker);
|
||||
});
|
||||
|
||||
+55
@@ -90,6 +90,61 @@ namespace UnitTest
|
||||
return AZStd::string(keyText.toUtf8().data());
|
||||
}
|
||||
|
||||
bool ViewportSettingsTestImpl::GridSnappingEnabled() const
|
||||
{
|
||||
return m_gridSnapping;
|
||||
}
|
||||
|
||||
float ViewportSettingsTestImpl::GridSize() const
|
||||
{
|
||||
return m_gridSize;
|
||||
}
|
||||
|
||||
bool ViewportSettingsTestImpl::ShowGrid() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ViewportSettingsTestImpl::AngleSnappingEnabled() const
|
||||
{
|
||||
return m_angularSnapping;
|
||||
}
|
||||
|
||||
float ViewportSettingsTestImpl::AngleStep() const
|
||||
{
|
||||
return m_angularStep;
|
||||
}
|
||||
|
||||
float ViewportSettingsTestImpl::ManipulatorLineBoundWidth() const
|
||||
{
|
||||
return 0.1f;
|
||||
}
|
||||
|
||||
float ViewportSettingsTestImpl::ManipulatorCircleBoundWidth() const
|
||||
{
|
||||
return 0.1f;
|
||||
}
|
||||
|
||||
bool ViewportSettingsTestImpl::StickySelectEnabled() const
|
||||
{
|
||||
return m_stickySelect;
|
||||
}
|
||||
|
||||
bool ViewportSettingsTestImpl::IconsVisible() const
|
||||
{
|
||||
return m_iconsVisible;
|
||||
}
|
||||
|
||||
bool ViewportSettingsTestImpl::HelpersVisible() const
|
||||
{
|
||||
return m_helpersVisible;
|
||||
}
|
||||
|
||||
AZ::Vector3 ViewportSettingsTestImpl::DefaultEditorCameraPosition() const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
bool TestWidget::eventFilter(QObject* watched, QEvent* event)
|
||||
{
|
||||
AZ_UNUSED(watched);
|
||||
|
||||
+38
-1
@@ -91,6 +91,43 @@ namespace UnitTest
|
||||
/// @param modifiers Optional keyboard modifiers to include during the wheel events, defaults to Qt::NoModifier
|
||||
AZStd::string QtKeyToAzString(Qt::Key key, Qt::KeyboardModifiers modifiers = Qt::NoModifier);
|
||||
|
||||
//! Test implementation of the ViewportSettingsRequestBus.
|
||||
//! @note Can be used to customize viewport settings during test execution.
|
||||
class ViewportSettingsTestImpl : public AzToolsFramework::ViewportInteraction::ViewportSettingsRequestBus::Handler
|
||||
{
|
||||
public:
|
||||
void Connect(const AzFramework::ViewportId viewportId)
|
||||
{
|
||||
AzToolsFramework::ViewportInteraction::ViewportSettingsRequestBus::Handler::BusConnect(viewportId);
|
||||
}
|
||||
|
||||
void Disconnect()
|
||||
{
|
||||
AzToolsFramework::ViewportInteraction::ViewportSettingsRequestBus::Handler::BusDisconnect();
|
||||
}
|
||||
|
||||
// ViewportSettingsRequestBus overrides ...
|
||||
bool GridSnappingEnabled() const override;
|
||||
float GridSize() const override;
|
||||
bool ShowGrid() const override;
|
||||
bool AngleSnappingEnabled() const override;
|
||||
float AngleStep() const override;
|
||||
float ManipulatorLineBoundWidth() const override;
|
||||
float ManipulatorCircleBoundWidth() const override;
|
||||
bool StickySelectEnabled() const override;
|
||||
AZ::Vector3 DefaultEditorCameraPosition() const override;
|
||||
bool IconsVisible() const override;
|
||||
bool HelpersVisible() const override;
|
||||
|
||||
float m_gridSize = 1.0f;
|
||||
float m_angularStep = 0.0f;
|
||||
bool m_gridSnapping = false;
|
||||
bool m_angularSnapping = false;
|
||||
bool m_stickySelect = true;
|
||||
bool m_iconsVisible = true;
|
||||
bool m_helpersVisible = true;
|
||||
};
|
||||
|
||||
/// Test widget to store QActions generated by EditorTransformComponentSelection.
|
||||
class TestWidget : public QWidget
|
||||
{
|
||||
@@ -207,7 +244,7 @@ namespace UnitTest
|
||||
m_editorActions.Connect();
|
||||
|
||||
const auto viewportHandlerBuilder =
|
||||
[this](const AzToolsFramework::EditorVisibleEntityDataCache* entityDataCache,
|
||||
[this](const AzToolsFramework::EditorVisibleEntityDataCacheInterface* entityDataCache,
|
||||
[[maybe_unused]] AzToolsFramework::ViewportEditorModeTrackerInterface* viewportEditorModeTracker)
|
||||
{
|
||||
// create the default viewport (handles ComponentMode)
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzToolsFramework/API/EditorViewportIconDisplayInterface.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
class MockEditorViewportIconDisplayInterface : public AZ::Interface<AzToolsFramework::EditorViewportIconDisplayInterface>::Registrar
|
||||
{
|
||||
public:
|
||||
virtual ~MockEditorViewportIconDisplayInterface() = default;
|
||||
|
||||
//! AzToolsFramework::EditorViewportIconDisplayInterface overrides ...
|
||||
MOCK_METHOD1(DrawIcon, void(const DrawParameters&));
|
||||
MOCK_METHOD1(GetOrLoadIconForPath, IconId(AZStd::string_view path));
|
||||
MOCK_METHOD1(GetIconLoadStatus, IconLoadStatus(IconId icon));
|
||||
};
|
||||
} // namespace UnitTest
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzToolsFramework/ViewportSelection/EditorVisibleEntityDataCache.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
class MockEditorVisibleEntityDataCacheInterface : public AzToolsFramework::EditorVisibleEntityDataCacheInterface
|
||||
{
|
||||
using ComponentEntityAccentType = AzToolsFramework::Components::EditorSelectionAccentSystemComponent::ComponentEntityAccentType;
|
||||
|
||||
public:
|
||||
virtual ~MockEditorVisibleEntityDataCacheInterface() = default;
|
||||
|
||||
// AzToolsFramework::EditorVisibleEntityDataCacheInterface overrides ...
|
||||
MOCK_CONST_METHOD0(VisibleEntityDataCount, size_t());
|
||||
MOCK_CONST_METHOD1(GetVisibleEntityPosition, AZ::Vector3(size_t));
|
||||
MOCK_CONST_METHOD1(GetVisibleEntityTransform, const AZ::Transform&(size_t));
|
||||
MOCK_CONST_METHOD1(GetVisibleEntityId, AZ::EntityId(size_t));
|
||||
MOCK_CONST_METHOD1(GetVisibleEntityAccent, ComponentEntityAccentType(size_t));
|
||||
MOCK_CONST_METHOD1(IsVisibleEntityLocked, bool(size_t));
|
||||
MOCK_CONST_METHOD1(IsVisibleEntityVisible, bool(size_t));
|
||||
MOCK_CONST_METHOD1(IsVisibleEntitySelected, bool(size_t));
|
||||
MOCK_CONST_METHOD1(IsVisibleEntityIconHidden, bool(size_t));
|
||||
MOCK_CONST_METHOD1(IsVisibleEntityIndividuallySelectableInViewport, bool(size_t));
|
||||
MOCK_CONST_METHOD1(GetVisibleEntityIndexFromId, AZStd::optional<size_t>(AZ::EntityId entityId));
|
||||
};
|
||||
} // namespace UnitTest
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzToolsFramework/FocusMode/FocusModeInterface.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
class MockFocusModeInterface : public AZ::Interface<AzToolsFramework::FocusModeInterface>::Registrar
|
||||
{
|
||||
public:
|
||||
virtual ~MockFocusModeInterface() = default;
|
||||
|
||||
// AzToolsFramework::FocusModeInterface overrides ...
|
||||
MOCK_METHOD1(SetFocusRoot, void(AZ::EntityId entityId));
|
||||
MOCK_METHOD1(ClearFocusRoot, void(AzFramework::EntityContextId entityContextId));
|
||||
MOCK_METHOD1(GetFocusRoot, AZ::EntityId(AzFramework::EntityContextId entityContextId));
|
||||
MOCK_METHOD1(GetFocusedEntities, AzToolsFramework::EntityIdList(AzFramework::EntityContextId entityContextId));
|
||||
MOCK_CONST_METHOD1(IsInFocusSubTree, bool(AZ::EntityId entityId));
|
||||
};
|
||||
} // namespace UnitTest
|
||||
+1
-2
@@ -21,9 +21,8 @@ namespace AzToolsFramework
|
||||
AZ_CLASS_ALLOCATOR_IMPL(EditorDefaultSelection, AZ::SystemAllocator, 0)
|
||||
|
||||
EditorDefaultSelection::EditorDefaultSelection(
|
||||
const EditorVisibleEntityDataCache* entityDataCache, ViewportEditorModeTrackerInterface* viewportEditorModeTracker)
|
||||
const EditorVisibleEntityDataCacheInterface* entityDataCache, ViewportEditorModeTrackerInterface* viewportEditorModeTracker)
|
||||
: m_phantomWidget(nullptr)
|
||||
, m_entityDataCache(entityDataCache)
|
||||
, m_viewportEditorModeTracker(viewportEditorModeTracker)
|
||||
, m_componentModeCollection(viewportEditorModeTracker)
|
||||
{
|
||||
|
||||
+6
-7
@@ -27,7 +27,8 @@ namespace AzToolsFramework
|
||||
AZ_CLASS_ALLOCATOR_DECL
|
||||
|
||||
//! @cond
|
||||
EditorDefaultSelection(const EditorVisibleEntityDataCache* entityDataCache, ViewportEditorModeTrackerInterface* viewportEditorModeTracker);
|
||||
EditorDefaultSelection(
|
||||
const EditorVisibleEntityDataCacheInterface* entityDataCache, ViewportEditorModeTrackerInterface* viewportEditorModeTracker);
|
||||
EditorDefaultSelection(const EditorDefaultSelection&) = delete;
|
||||
EditorDefaultSelection& operator=(const EditorDefaultSelection&) = delete;
|
||||
virtual ~EditorDefaultSelection();
|
||||
@@ -85,10 +86,8 @@ namespace AzToolsFramework
|
||||
QWidget m_phantomWidget; //!< The phantom widget responsible for holding QActions while in ComponentMode.
|
||||
QWidget* m_phantomOverrideWidget = nullptr; //!< It's possible to override the phantom widget in special circumstances (eg testing).
|
||||
ComponentModeFramework::ComponentModeCollection m_componentModeCollection; //!< Handles all active ComponentMode types.
|
||||
AZStd::unique_ptr<EditorTransformComponentSelection> m_transformComponentSelection =
|
||||
nullptr; //!< Viewport selection (responsible for
|
||||
//!< manipulators and transform modifications).
|
||||
const EditorVisibleEntityDataCache* m_entityDataCache = nullptr; //!< Reference to cached visible EntityData.
|
||||
//! Viewport selection (responsible for manipulators and transform modifications).
|
||||
AZStd::unique_ptr<EditorTransformComponentSelection> m_transformComponentSelection = nullptr;
|
||||
|
||||
//! Mapping between passed ActionOverride (AddActionOverride) and allocated QAction.
|
||||
struct ActionOverrideMapping
|
||||
@@ -112,7 +111,7 @@ namespace AzToolsFramework
|
||||
|
||||
AZStd::shared_ptr<AzToolsFramework::ManipulatorManager> m_manipulatorManager; //!< The default manipulator manager.
|
||||
ViewportInteraction::MouseInteraction m_currentInteraction; //!< Current mouse interaction to be used for drawing manipulators.
|
||||
ViewportEditorModeTrackerInterface* m_viewportEditorModeTracker = nullptr; //!< Tracker for activating/deactivating viewport editor modes.
|
||||
|
||||
//! Tracker for activating/deactivating viewport editor modes.
|
||||
ViewportEditorModeTrackerInterface* m_viewportEditorModeTracker = nullptr;
|
||||
};
|
||||
} // namespace AzToolsFramework
|
||||
|
||||
+20
-10
@@ -159,7 +159,7 @@ namespace AzToolsFramework
|
||||
return false;
|
||||
}
|
||||
|
||||
EditorHelpers::EditorHelpers(const EditorVisibleEntityDataCache* entityDataCache)
|
||||
EditorHelpers::EditorHelpers(const EditorVisibleEntityDataCacheInterface* entityDataCache)
|
||||
: m_entityDataCache(entityDataCache)
|
||||
{
|
||||
m_focusModeInterface = AZ::Interface<FocusModeInterface>::Get();
|
||||
@@ -344,9 +344,19 @@ namespace AzToolsFramework
|
||||
continue;
|
||||
}
|
||||
|
||||
int iconTextureId = 0;
|
||||
EditorEntityIconComponentRequestBus::EventResult(
|
||||
iconTextureId, entityId, &EditorEntityIconComponentRequests::GetEntityIconTextureId);
|
||||
const AZ::Vector3& entityPosition = m_entityDataCache->GetVisibleEntityPosition(entityCacheIndex);
|
||||
const AZ::Vector3 entityCameraVector = entityPosition - cameraState.m_position;
|
||||
|
||||
if (const float directionFromCamera = entityCameraVector.Dot(cameraState.m_forward); directionFromCamera < 0.0f)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const float distanceFromCamera = entityCameraVector.GetLength();
|
||||
if (distanceFromCamera < cameraState.m_nearClip)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
using ComponentEntityAccentType = Components::EditorSelectionAccentSystemComponent::ComponentEntityAccentType;
|
||||
const AZ::Color iconHighlight = [this, entityCacheIndex]()
|
||||
@@ -364,13 +374,13 @@ namespace AzToolsFramework
|
||||
return AZ::Color(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
}();
|
||||
|
||||
const AZ::Vector3& entityPosition = m_entityDataCache->GetVisibleEntityPosition(entityCacheIndex);
|
||||
const float distanceFromCamera = cameraState.m_position.GetDistance(entityPosition);
|
||||
const float iconSize = GetIconSize(distanceFromCamera);
|
||||
int iconTextureId = 0;
|
||||
EditorEntityIconComponentRequestBus::EventResult(
|
||||
iconTextureId, entityId, &EditorEntityIconComponentRequestBus::Events::GetEntityIconTextureId);
|
||||
|
||||
editorViewportIconDisplay->DrawIcon({ viewportInfo.m_viewportId, iconTextureId, iconHighlight, entityPosition,
|
||||
EditorViewportIconDisplayInterface::CoordinateSpace::WorldSpace,
|
||||
AZ::Vector2{ iconSize, iconSize } });
|
||||
editorViewportIconDisplay->DrawIcon(EditorViewportIconDisplayInterface::DrawParameters{
|
||||
viewportInfo.m_viewportId, iconTextureId, iconHighlight, entityPosition,
|
||||
EditorViewportIconDisplayInterface::CoordinateSpace::WorldSpace, AZ::Vector2(GetIconSize(distanceFromCamera)) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace AzFramework
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
class EditorVisibleEntityDataCache;
|
||||
class EditorVisibleEntityDataCacheInterface;
|
||||
class FocusModeInterface;
|
||||
|
||||
namespace ViewportInteraction
|
||||
@@ -64,7 +64,7 @@ namespace AzToolsFramework
|
||||
|
||||
//! An EditorVisibleEntityDataCache must be passed to EditorHelpers to allow it to
|
||||
//! efficiently read entity data without resorting to EBus calls.
|
||||
explicit EditorHelpers(const EditorVisibleEntityDataCache* entityDataCache);
|
||||
explicit EditorHelpers(const EditorVisibleEntityDataCacheInterface* entityDataCache);
|
||||
EditorHelpers(const EditorHelpers&) = delete;
|
||||
EditorHelpers& operator=(const EditorHelpers&) = delete;
|
||||
~EditorHelpers() = default;
|
||||
@@ -103,7 +103,7 @@ namespace AzToolsFramework
|
||||
|
||||
AZStd::unique_ptr<InvalidClicks> m_invalidClicks; //!< Display for invalid click behavior.
|
||||
|
||||
const EditorVisibleEntityDataCache* m_entityDataCache = nullptr; //!< Entity Data queried by the EditorHelpers.
|
||||
const EditorVisibleEntityDataCacheInterface* m_entityDataCache = nullptr; //!< Entity Data queried by the EditorHelpers.
|
||||
const FocusModeInterface* m_focusModeInterface = nullptr; //!< API to interact with focus mode functionality.
|
||||
};
|
||||
|
||||
|
||||
+1
-1
@@ -84,7 +84,7 @@ namespace AzToolsFramework
|
||||
void EditorInteractionSystemComponent::SetDefaultHandler()
|
||||
{
|
||||
SetHandler(
|
||||
[](const EditorVisibleEntityDataCache* entityDataCache, ViewportEditorModeTrackerInterface* viewportEditorModeTracker)
|
||||
[](const EditorVisibleEntityDataCacheInterface* entityDataCache, ViewportEditorModeTrackerInterface* viewportEditorModeTracker)
|
||||
{
|
||||
return AZStd::make_unique<EditorDefaultSelection>(entityDataCache, viewportEditorModeTracker);
|
||||
});
|
||||
|
||||
+2
-2
@@ -16,7 +16,7 @@
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
class EditorVisibleEntityDataCache;
|
||||
class EditorVisibleEntityDataCacheInterface;
|
||||
class ViewportEditorModeTrackerInterface;
|
||||
|
||||
//! Bus to handle all mouse events originating from the viewport.
|
||||
@@ -34,7 +34,7 @@ namespace AzToolsFramework
|
||||
|
||||
//! Alias for factory function to create a new type implementing the ViewportSelectionRequests interface.
|
||||
using ViewportSelectionRequestsBuilderFn = AZStd::function<AZStd::unique_ptr<ViewportInteraction::InternalViewportSelectionRequests>(
|
||||
const EditorVisibleEntityDataCache*, ViewportEditorModeTrackerInterface*)>;
|
||||
const EditorVisibleEntityDataCacheInterface*, ViewportEditorModeTrackerInterface*)>;
|
||||
|
||||
//! Interface for system component implementing the ViewportSelectionRequests interface.
|
||||
//! This interface also includes a setter to set a custom handler also implementing
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ namespace AzToolsFramework
|
||||
AZ_CLASS_ALLOCATOR_IMPL(EditorPickEntitySelection, AZ::SystemAllocator, 0)
|
||||
|
||||
EditorPickEntitySelection::EditorPickEntitySelection(
|
||||
const EditorVisibleEntityDataCache* entityDataCache, ViewportEditorModeTrackerInterface* viewportEditorModeTracker)
|
||||
const EditorVisibleEntityDataCacheInterface* entityDataCache, ViewportEditorModeTrackerInterface* viewportEditorModeTracker)
|
||||
: m_editorHelpers(AZStd::make_unique<EditorHelpers>(entityDataCache))
|
||||
, m_viewportEditorModeTracker(viewportEditorModeTracker)
|
||||
{
|
||||
|
||||
+4
-2
@@ -13,6 +13,7 @@
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
class EditorVisibleEntityDataCacheInterface;
|
||||
class ViewportEditorModeTrackerInterface;
|
||||
|
||||
//! Viewport interaction that will handle assigning an entity in the viewport to
|
||||
@@ -23,7 +24,7 @@ namespace AzToolsFramework
|
||||
AZ_CLASS_ALLOCATOR_DECL
|
||||
|
||||
EditorPickEntitySelection(
|
||||
const EditorVisibleEntityDataCache* entityDataCache, ViewportEditorModeTrackerInterface* viewportEditorModeTracker);
|
||||
const EditorVisibleEntityDataCacheInterface* entityDataCache, ViewportEditorModeTrackerInterface* viewportEditorModeTracker);
|
||||
~EditorPickEntitySelection();
|
||||
|
||||
private:
|
||||
@@ -35,6 +36,7 @@ namespace AzToolsFramework
|
||||
AZStd::unique_ptr<EditorHelpers> m_editorHelpers; //!< Editor visualization of entities (icons, shapes, debug visuals etc).
|
||||
AZ::EntityId m_hoveredEntityId; //!< What EntityId is the mouse currently hovering over (if any).
|
||||
AZ::EntityId m_cachedEntityIdUnderCursor; //!< Store the EntityId on each mouse move for use in Display.
|
||||
ViewportEditorModeTrackerInterface* m_viewportEditorModeTracker = nullptr; //!< Tracker for activating/deactivating viewport editor modes.
|
||||
//! Tracker for activating/deactivating viewport editor modes.
|
||||
ViewportEditorModeTrackerInterface* m_viewportEditorModeTracker = nullptr;
|
||||
};
|
||||
} // namespace AzToolsFramework
|
||||
|
||||
+3
-3
@@ -381,7 +381,7 @@ namespace AzToolsFramework
|
||||
EntityIdContainer& selectedEntityIdsBeforeBoxSelect,
|
||||
EntityIdContainer& potentialSelectedEntityIds,
|
||||
EntityIdContainer& potentialDeselectedEntityIds,
|
||||
const EditorVisibleEntityDataCache& entityDataCache,
|
||||
const EditorVisibleEntityDataCacheInterface& entityDataCache,
|
||||
const int viewportId,
|
||||
const ViewportInteraction::KeyboardModifiers currentKeyboardModifiers,
|
||||
const ViewportInteraction::KeyboardModifiers& previousKeyboardModifiers)
|
||||
@@ -958,7 +958,7 @@ namespace AzToolsFramework
|
||||
// (useful in the context of drawing when we only care about entities we can see)
|
||||
// note: return the index if it is selectable, nullopt otherwise
|
||||
static AZStd::optional<size_t> SelectableInVisibleViewportCache(
|
||||
const EditorVisibleEntityDataCache& entityDataCache, const AZ::EntityId entityId)
|
||||
const EditorVisibleEntityDataCacheInterface& entityDataCache, const AZ::EntityId entityId)
|
||||
{
|
||||
if (auto entityIndex = entityDataCache.GetVisibleEntityIndexFromId(entityId))
|
||||
{
|
||||
@@ -1002,7 +1002,7 @@ namespace AzToolsFramework
|
||||
}
|
||||
}
|
||||
|
||||
EditorTransformComponentSelection::EditorTransformComponentSelection(const EditorVisibleEntityDataCache* entityDataCache)
|
||||
EditorTransformComponentSelection::EditorTransformComponentSelection(const EditorVisibleEntityDataCacheInterface* entityDataCache)
|
||||
: m_entityDataCache(entityDataCache)
|
||||
{
|
||||
const AzFramework::EntityContextId entityContextId = GetEntityContextId();
|
||||
|
||||
+4
-6
@@ -34,7 +34,7 @@
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
class EditorVisibleEntityDataCache;
|
||||
class EditorVisibleEntityDataCacheInterface;
|
||||
|
||||
using EntityIdSet = AZStd::unordered_set<AZ::EntityId>; //!< Alias for unordered_set of EntityIds.
|
||||
|
||||
@@ -167,7 +167,7 @@ namespace AzToolsFramework
|
||||
AZ_CLASS_ALLOCATOR_DECL
|
||||
|
||||
EditorTransformComponentSelection() = default;
|
||||
explicit EditorTransformComponentSelection(const EditorVisibleEntityDataCache* entityDataCache);
|
||||
explicit EditorTransformComponentSelection(const EditorVisibleEntityDataCacheInterface* entityDataCache);
|
||||
EditorTransformComponentSelection(const EditorTransformComponentSelection&) = delete;
|
||||
EditorTransformComponentSelection& operator=(const EditorTransformComponentSelection&) = delete;
|
||||
virtual ~EditorTransformComponentSelection();
|
||||
@@ -325,10 +325,8 @@ namespace AzToolsFramework
|
||||
AZ::EntityId m_currentEntityIdUnderCursor; //!< Store the EntityId on each mouse move for use in Display.
|
||||
AZ::EntityId m_editorCameraComponentEntityId; //!< The EditorCameraComponent EntityId if it is set.
|
||||
EntityIdSet m_selectedEntityIds; //!< Represents the current entities in the selection.
|
||||
|
||||
const EditorVisibleEntityDataCache* m_entityDataCache = nullptr; //!< A cache of packed EntityData that can be
|
||||
//!< iterated over efficiently without the need
|
||||
//!< to make individual EBus calls.
|
||||
//! A cache of packed EntityData that can be iterated over efficiently without the need to make individual EBus calls.
|
||||
const EditorVisibleEntityDataCacheInterface* m_entityDataCache = nullptr;
|
||||
AZStd::unique_ptr<EditorHelpers> m_editorHelpers; //!< Editor visualization of entities (icons, shapes, debug visuals etc).
|
||||
EntityIdManipulators m_entityIdManipulators; //!< Mapping from a Manipulator to potentially many EntityIds.
|
||||
|
||||
|
||||
+39
-17
@@ -20,10 +20,36 @@
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
//! Read-only interface for EditorVisibleEntityDataCache to be used by systems that want to efficiently
|
||||
//! query the state of visible entities in the viewport.
|
||||
class EditorVisibleEntityDataCacheInterface
|
||||
{
|
||||
using ComponentEntityAccentType = Components::EditorSelectionAccentSystemComponent::ComponentEntityAccentType;
|
||||
|
||||
public:
|
||||
virtual ~EditorVisibleEntityDataCacheInterface() = default;
|
||||
|
||||
virtual size_t VisibleEntityDataCount() const = 0;
|
||||
virtual AZ::Vector3 GetVisibleEntityPosition(size_t index) const = 0;
|
||||
virtual const AZ::Transform& GetVisibleEntityTransform(size_t index) const = 0;
|
||||
virtual AZ::EntityId GetVisibleEntityId(size_t index) const = 0;
|
||||
virtual ComponentEntityAccentType GetVisibleEntityAccent(size_t index) const = 0;
|
||||
virtual bool IsVisibleEntityLocked(size_t index) const = 0;
|
||||
virtual bool IsVisibleEntityVisible(size_t index) const = 0;
|
||||
virtual bool IsVisibleEntitySelected(size_t index) const = 0;
|
||||
virtual bool IsVisibleEntityIconHidden(size_t index) const = 0;
|
||||
//! Returns true if the entity is individually selectable (none of its ancestors are a closed container entity).
|
||||
//! @note It may still be desirable to be able to 'click' an entity that is a descendant of a closed container
|
||||
//! to select the container itself, not the individual entity.
|
||||
virtual bool IsVisibleEntityIndividuallySelectableInViewport(size_t index) const = 0;
|
||||
virtual AZStd::optional<size_t> GetVisibleEntityIndexFromId(AZ::EntityId entityId) const = 0;
|
||||
};
|
||||
|
||||
//! A cache of packed EntityData that can be iterated over efficiently without
|
||||
//! the need to make individual EBus calls
|
||||
class EditorVisibleEntityDataCache
|
||||
: private EditorEntityVisibilityNotificationBus::Router
|
||||
: public EditorVisibleEntityDataCacheInterface
|
||||
, private EditorEntityVisibilityNotificationBus::Router
|
||||
, private EditorEntityLockComponentNotificationBus::Router
|
||||
, private AZ::TransformNotificationBus::Router
|
||||
, private EditorComponentSelectionNotificationsBus::Router
|
||||
@@ -45,22 +71,18 @@ namespace AzToolsFramework
|
||||
|
||||
void CalculateVisibleEntityDatas(const AzFramework::ViewportInfo& viewportInfo);
|
||||
|
||||
//! EditorVisibleEntityDataCache interface
|
||||
size_t VisibleEntityDataCount() const;
|
||||
AZ::Vector3 GetVisibleEntityPosition(size_t index) const;
|
||||
const AZ::Transform& GetVisibleEntityTransform(size_t index) const;
|
||||
AZ::EntityId GetVisibleEntityId(size_t index) const;
|
||||
ComponentEntityAccentType GetVisibleEntityAccent(size_t index) const;
|
||||
bool IsVisibleEntityLocked(size_t index) const;
|
||||
bool IsVisibleEntityVisible(size_t index) const;
|
||||
bool IsVisibleEntitySelected(size_t index) const;
|
||||
bool IsVisibleEntityIconHidden(size_t index) const;
|
||||
//! Returns true if the entity is individually selectable (none of its ancestors are a closed container entity).
|
||||
//! @note It may still be desirable to be able to 'click' an entity that is a descendant of a closed container
|
||||
//! to select the container itself, not the individual entity.
|
||||
bool IsVisibleEntityIndividuallySelectableInViewport(size_t index) const;
|
||||
|
||||
AZStd::optional<size_t> GetVisibleEntityIndexFromId(AZ::EntityId entityId) const;
|
||||
//! EditorVisibleEntityDataCacheInterface overrides ...
|
||||
size_t VisibleEntityDataCount() const override;
|
||||
AZ::Vector3 GetVisibleEntityPosition(size_t index) const override;
|
||||
const AZ::Transform& GetVisibleEntityTransform(size_t index) const override;
|
||||
AZ::EntityId GetVisibleEntityId(size_t index) const override;
|
||||
ComponentEntityAccentType GetVisibleEntityAccent(size_t index) const override;
|
||||
bool IsVisibleEntityLocked(size_t index) const override;
|
||||
bool IsVisibleEntityVisible(size_t index) const override;
|
||||
bool IsVisibleEntitySelected(size_t index) const override;
|
||||
bool IsVisibleEntityIconHidden(size_t index) const override;
|
||||
bool IsVisibleEntityIndividuallySelectableInViewport(size_t index) const override;
|
||||
AZStd::optional<size_t> GetVisibleEntityIndexFromId(AZ::EntityId entityId) const override;
|
||||
|
||||
void AddEntityIds(const EntityIdList& entityIds);
|
||||
|
||||
|
||||
+3
@@ -9,6 +9,9 @@
|
||||
set(FILES
|
||||
UnitTest/AzToolsFrameworkTestHelpers.cpp
|
||||
UnitTest/AzToolsFrameworkTestHelpers.h
|
||||
UnitTest/Mocks/MockFocusModeInterface.h
|
||||
UnitTest/Mocks/MockEditorVisibleEntityDataCacheInterface.h
|
||||
UnitTest/Mocks/MockEditorViewportIconDisplayInterface.h
|
||||
UnitTest/ToolsTestApplication.cpp
|
||||
UnitTest/ToolsTestApplication.h
|
||||
)
|
||||
|
||||
@@ -265,7 +265,7 @@ namespace UnitTest
|
||||
using AzToolsFramework::EditorInteractionSystemViewportSelectionRequestBus;
|
||||
EditorInteractionSystemViewportSelectionRequestBus::Event(
|
||||
AzToolsFramework::GetEntityContextId(), &EditorInteractionSystemViewportSelectionRequestBus::Events::SetHandler,
|
||||
[](const AzToolsFramework::EditorVisibleEntityDataCache* entityDataCache,
|
||||
[](const AzToolsFramework::EditorVisibleEntityDataCacheInterface* entityDataCache,
|
||||
[[maybe_unused]] AzToolsFramework::ViewportEditorModeTrackerInterface* viewportEditorModeTracker)
|
||||
{
|
||||
return AZStd::make_unique<AzToolsFramework::EditorPickEntitySelection>(entityDataCache, viewportEditorModeTracker);
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
#include <AzFramework/UnitTest/TestDebugDisplayRequests.h>
|
||||
#include <AzFramework/Viewport/CameraState.h>
|
||||
#include <AzTest/AzTest.h>
|
||||
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
|
||||
#include <AzToolsFramework/UnitTest/Mocks/MockEditorViewportIconDisplayInterface.h>
|
||||
#include <AzToolsFramework/UnitTest/Mocks/MockEditorVisibleEntityDataCacheInterface.h>
|
||||
#include <AzToolsFramework/UnitTest/Mocks/MockFocusModeInterface.h>
|
||||
#include <AzToolsFramework/ViewportSelection/EditorHelpers.h>
|
||||
#include <AzToolsFramework/ViewportSelection/EditorVisibleEntityDataCache.h>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
class EditorViewportIconFixture : public AllocatorsTestFixture
|
||||
{
|
||||
public:
|
||||
inline static constexpr AzFramework::ViewportId TestViewportId = 2468;
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
AllocatorsTestFixture::SetUp();
|
||||
|
||||
m_focusModeMock = AZStd::make_unique<::testing::NiceMock<MockFocusModeInterface>>();
|
||||
m_editorViewportIconDisplayMock = AZStd::make_unique<::testing::NiceMock<MockEditorViewportIconDisplayInterface>>();
|
||||
m_entityVisibleEntityDataCacheMock = AZStd::make_unique<::testing::NiceMock<MockEditorVisibleEntityDataCacheInterface>>();
|
||||
m_editorHelpers = AZStd::make_unique<AzToolsFramework::EditorHelpers>(m_entityVisibleEntityDataCacheMock.get());
|
||||
m_viewportSettings = AZStd::make_unique<ViewportSettingsTestImpl>();
|
||||
|
||||
m_viewportSettings->Connect(TestViewportId);
|
||||
m_viewportSettings->m_helpersVisible = false;
|
||||
m_viewportSettings->m_iconsVisible = true;
|
||||
|
||||
m_cameraState = AzFramework::CreateDefaultCamera(AZ::Transform::CreateIdentity(), AZ::Vector2(1024.0f, 768.0f));
|
||||
|
||||
using ::testing::_;
|
||||
using ::testing::Return;
|
||||
ON_CALL(*m_entityVisibleEntityDataCacheMock, VisibleEntityDataCount()).WillByDefault(Return(1));
|
||||
ON_CALL(*m_entityVisibleEntityDataCacheMock, GetVisibleEntityId(_)).WillByDefault(Return(AZ::EntityId()));
|
||||
ON_CALL(*m_entityVisibleEntityDataCacheMock, IsVisibleEntityIconHidden(_)).WillByDefault(Return(false));
|
||||
ON_CALL(*m_entityVisibleEntityDataCacheMock, IsVisibleEntityVisible(_)).WillByDefault(Return(true));
|
||||
ON_CALL(*m_focusModeMock, IsInFocusSubTree(_)).WillByDefault(Return(true));
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
m_viewportSettings->Disconnect();
|
||||
m_viewportSettings.reset();
|
||||
m_editorHelpers.reset();
|
||||
m_entityVisibleEntityDataCacheMock.reset();
|
||||
m_editorViewportIconDisplayMock.reset();
|
||||
m_focusModeMock.reset();
|
||||
|
||||
AllocatorsTestFixture::TearDown();
|
||||
}
|
||||
|
||||
AZStd::unique_ptr<ViewportSettingsTestImpl> m_viewportSettings;
|
||||
AZStd::unique_ptr<AzToolsFramework::EditorHelpers> m_editorHelpers;
|
||||
AZStd::unique_ptr<::testing::NiceMock<MockFocusModeInterface>> m_focusModeMock;
|
||||
AZStd::unique_ptr<::testing::NiceMock<MockEditorVisibleEntityDataCacheInterface>> m_entityVisibleEntityDataCacheMock;
|
||||
AZStd::unique_ptr<::testing::NiceMock<MockEditorViewportIconDisplayInterface>> m_editorViewportIconDisplayMock;
|
||||
AzFramework::CameraState m_cameraState;
|
||||
};
|
||||
|
||||
TEST_F(EditorViewportIconFixture, ViewportIconsAreNotDisplayedWhenInBetweenCameraAndNearClipPlane)
|
||||
{
|
||||
NullDebugDisplayRequests nullDebugDisplayRequests;
|
||||
|
||||
const auto insideNearClip = m_cameraState.m_nearClip * 0.5f;
|
||||
|
||||
using ::testing::_;
|
||||
using ::testing::Return;
|
||||
// given
|
||||
// entity position (where icon will be drawn) is in between near clip plane and camera position
|
||||
ON_CALL(*m_entityVisibleEntityDataCacheMock, GetVisibleEntityPosition(_))
|
||||
.WillByDefault(Return(AZ::Vector3(0.0f, insideNearClip, 0.0f)));
|
||||
|
||||
EXPECT_CALL(*m_editorViewportIconDisplayMock, DrawIcon(_)).Times(0);
|
||||
|
||||
// when
|
||||
m_editorHelpers->DisplayHelpers(
|
||||
AzFramework::ViewportInfo{ TestViewportId }, m_cameraState, nullDebugDisplayRequests,
|
||||
[](AZ::EntityId)
|
||||
{
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(EditorViewportIconFixture, ViewportIconsAreNotDisplayedWhenBehindCamera)
|
||||
{
|
||||
NullDebugDisplayRequests nullDebugDisplayRequests;
|
||||
|
||||
using ::testing::_;
|
||||
using ::testing::Return;
|
||||
// given
|
||||
// entity position (where icon will be drawn) behind the camera position
|
||||
ON_CALL(*m_entityVisibleEntityDataCacheMock, GetVisibleEntityPosition(_)).WillByDefault(Return(AZ::Vector3(0.0f, -1.0f, 0.0f)));
|
||||
|
||||
EXPECT_CALL(*m_editorViewportIconDisplayMock, DrawIcon(_)).Times(0);
|
||||
|
||||
// when
|
||||
m_editorHelpers->DisplayHelpers(
|
||||
AzFramework::ViewportInfo{ TestViewportId }, m_cameraState, nullDebugDisplayRequests,
|
||||
[](AZ::EntityId)
|
||||
{
|
||||
return true;
|
||||
});
|
||||
}
|
||||
} // namespace UnitTest
|
||||
@@ -573,7 +573,7 @@ namespace UnitTest
|
||||
using AzToolsFramework::EditorInteractionSystemViewportSelectionRequestBus;
|
||||
EditorInteractionSystemViewportSelectionRequestBus::Event(
|
||||
AzToolsFramework::GetEntityContextId(), &EditorInteractionSystemViewportSelectionRequestBus::Events::SetHandler,
|
||||
[](const AzToolsFramework::EditorVisibleEntityDataCache* entityDataCache,
|
||||
[](const AzToolsFramework::EditorVisibleEntityDataCacheInterface* entityDataCache,
|
||||
[[maybe_unused]] AzToolsFramework::ViewportEditorModeTrackerInterface* viewportEditorModeTracker)
|
||||
{
|
||||
return AZStd::make_unique<AzToolsFramework::EditorPickEntitySelection>(entityDataCache, viewportEditorModeTracker);
|
||||
@@ -591,7 +591,7 @@ namespace UnitTest
|
||||
using AzToolsFramework::EditorInteractionSystemViewportSelectionRequestBus;
|
||||
EditorInteractionSystemViewportSelectionRequestBus::Event(
|
||||
AzToolsFramework::GetEntityContextId(), &EditorInteractionSystemViewportSelectionRequestBus::Events::SetHandler,
|
||||
[](const AzToolsFramework::EditorVisibleEntityDataCache* entityDataCache,
|
||||
[](const AzToolsFramework::EditorVisibleEntityDataCacheInterface* entityDataCache,
|
||||
[[maybe_unused]] AzToolsFramework::ViewportEditorModeTrackerInterface* viewportEditorModeTracker)
|
||||
{
|
||||
return AZStd::make_unique<AzToolsFramework::EditorPickEntitySelection>(entityDataCache, viewportEditorModeTracker);
|
||||
@@ -599,7 +599,7 @@ namespace UnitTest
|
||||
|
||||
EditorInteractionSystemViewportSelectionRequestBus::Event(
|
||||
AzToolsFramework::GetEntityContextId(), &EditorInteractionSystemViewportSelectionRequestBus::Events::SetHandler,
|
||||
[](const AzToolsFramework::EditorVisibleEntityDataCache* entityDataCache,
|
||||
[](const AzToolsFramework::EditorVisibleEntityDataCacheInterface* entityDataCache,
|
||||
[[maybe_unused]] AzToolsFramework::ViewportEditorModeTrackerInterface* viewportEditorModeTracker)
|
||||
{
|
||||
return AZStd::make_unique<AzToolsFramework::EditorDefaultSelection>(entityDataCache, viewportEditorModeTracker);
|
||||
|
||||
@@ -24,6 +24,7 @@ set(FILES
|
||||
ComponentModeTests.cpp
|
||||
EditorTransformComponentSelectionTests.cpp
|
||||
EditorVertexSelectionTests.cpp
|
||||
EditorViewportIconTests.cpp
|
||||
Entity/EditorEntityContextComponentTests.cpp
|
||||
Entity/EditorEntityHelpersTests.cpp
|
||||
Entity/EditorEntitySearchComponentTests.cpp
|
||||
|
||||
@@ -19,13 +19,15 @@
|
||||
#define AZ_DebugSecureSocket(...)
|
||||
#define AZ_DebugSecureSocketConnection(window, fmt, ...)
|
||||
|
||||
//#define AZ_DebugUseSocketDebugLog
|
||||
//#define AZ_DebugSecureSocket AZ_TracePrintf
|
||||
//#define AZ_DebugSecureSocketConnection(window, fmt, ...) \
|
||||
//{\
|
||||
// AZStd::string line = AZStd::string::format(fmt, __VA_ARGS__);\
|
||||
// this->m_dbgLog += line;\
|
||||
//}
|
||||
/*
|
||||
#define AZ_DebugUseSocketDebugLog
|
||||
#define AZ_DebugSecureSocket AZ_TracePrintf
|
||||
#define AZ_DebugSecureSocketConnection(window, fmt, ...) \
|
||||
{\
|
||||
AZStd::string line = AZStd::string::format(fmt, __VA_ARGS__);\
|
||||
this->m_dbgLog += line;\
|
||||
}
|
||||
*/
|
||||
|
||||
#if AZ_TRAIT_GRIDMATE_SECURE_SOCKET_DRIVER_HOOK_ENABLED
|
||||
struct ssl_st;
|
||||
|
||||
@@ -856,18 +856,4 @@ DLL_EXPORT void OutputDebugString(const char* outputString)
|
||||
|
||||
#endif
|
||||
|
||||
// This code does not have a long life span and will be replaced soon
|
||||
#if defined(APPLE) || defined(LINUX) || defined(DEFINE_LEGACY_CRY_FILE_OPERATIONS)
|
||||
|
||||
bool CrySetFileAttributes(const char* lpFileName, uint32 dwFileAttributes)
|
||||
{
|
||||
//TODO: implement
|
||||
printf("CrySetFileAttributes not properly implemented yet\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endif //defined(APPLE) || defined(LINUX)
|
||||
|
||||
#endif // AZ_TRAIT_LEGACY_CRYCOMMON_USE_WINDOWS_STUBS
|
||||
|
||||
@@ -336,7 +336,6 @@ void SetFlags(T& dest, U flags, bool b)
|
||||
#include AZ_RESTRICTED_FILE(platform_h)
|
||||
#endif
|
||||
|
||||
bool CrySetFileAttributes(const char* lpFileName, uint32 dwFileAttributes);
|
||||
threadID CryGetCurrentThreadId();
|
||||
|
||||
#ifdef __GNUC__
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#define PLATFORM_IMPL_H_SECTION_TRAITS 1
|
||||
#define PLATFORM_IMPL_H_SECTION_CRYLOWLATENCYSLEEP 2
|
||||
#define PLATFORM_IMPL_H_SECTION_CRYGETFILEATTRIBUTES 3
|
||||
#define PLATFORM_IMPL_H_SECTION_CRYSETFILEATTRIBUTES 4
|
||||
#define PLATFORM_IMPL_H_SECTION_CRY_FILE_ATTRIBUTE_STUBS 5
|
||||
#define PLATFORM_IMPL_H_SECTION_CRY_SYSTEM_FUNCTIONS 6
|
||||
#define PLATFORM_IMPL_H_SECTION_VIRTUAL_ALLOCATORS 7
|
||||
@@ -238,22 +237,6 @@ void InitRootDir(char szExeFileName[], uint nExeSize, char szExeRootName[], uint
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
bool CrySetFileAttributes(const char* lpFileName, uint32 dwFileAttributes)
|
||||
{
|
||||
#if defined(AZ_RESTRICTED_PLATFORM)
|
||||
#define AZ_RESTRICTED_SECTION PLATFORM_IMPL_H_SECTION_CRYSETFILEATTRIBUTES
|
||||
#include AZ_RESTRICTED_FILE(platform_impl_h)
|
||||
#endif
|
||||
#if defined(AZ_RESTRICTED_SECTION_IMPLEMENTED)
|
||||
#undef AZ_RESTRICTED_SECTION_IMPLEMENTED
|
||||
#else
|
||||
AZStd::wstring lpFileNameW;
|
||||
AZStd::to_wstring(lpFileNameW, lpFileName);
|
||||
return SetFileAttributes(lpFileNameW.c_str(), dwFileAttributes) != 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
threadID CryGetCurrentThreadId()
|
||||
{
|
||||
|
||||
@@ -1132,7 +1132,10 @@ bool CXmlNode::saveToFile(const char* fileName)
|
||||
|
||||
bool CXmlNode::saveToFile([[maybe_unused]] const char* fileName, size_t chunkSize, AZ::IO::HandleType fileHandle)
|
||||
{
|
||||
CrySetFileAttributes(fileName, FILE_ATTRIBUTE_NORMAL);
|
||||
if (AZ::IO::SystemFile::Exists(fileName) && !AZ::IO::SystemFile::IsWritable(fileName))
|
||||
{
|
||||
AZ::IO::SystemFile::SetWritable(fileName, true);
|
||||
}
|
||||
|
||||
if (chunkSize < 256 * 1024) // make at least 256k
|
||||
{
|
||||
|
||||
@@ -30,6 +30,8 @@ set(FILES
|
||||
native/tests/assetBuilderSDK/SerializationDependenciesTests.cpp
|
||||
native/tests/assetmanager/AssetProcessorManagerTest.cpp
|
||||
native/tests/assetmanager/AssetProcessorManagerTest.h
|
||||
native/tests/assetmanager/ModtimeScanningTests.cpp
|
||||
native/tests/assetmanager/ModtimeScanningTests.h
|
||||
native/tests/utilities/assetUtilsTest.cpp
|
||||
native/tests/platformconfiguration/platformconfigurationtests.cpp
|
||||
native/tests/platformconfiguration/platformconfigurationtests.h
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace UnitTests
|
||||
}
|
||||
|
||||
struct PathDependencyBase
|
||||
: UnitTest::TraceBusRedirector
|
||||
: ::UnitTest::TraceBusRedirector
|
||||
{
|
||||
void Init();
|
||||
void Destroy();
|
||||
@@ -65,7 +65,7 @@ namespace UnitTests
|
||||
};
|
||||
|
||||
struct PathDependencyDeletionTest
|
||||
: UnitTest::ScopedAllocatorSetupFixture
|
||||
: ::UnitTest::ScopedAllocatorSetupFixture
|
||||
, PathDependencyBase
|
||||
{
|
||||
void SetUp() override
|
||||
@@ -357,7 +357,7 @@ namespace UnitTests
|
||||
}
|
||||
|
||||
struct PathDependencyBenchmarks
|
||||
: UnitTest::ScopedAllocatorFixture
|
||||
: ::UnitTest::ScopedAllocatorFixture
|
||||
, PathDependencyBase
|
||||
{
|
||||
static inline constexpr int NumTestDependencies = 4; // Must be a multiple of 4
|
||||
@@ -530,7 +530,7 @@ namespace UnitTests
|
||||
|
||||
BENCHMARK_F(PathDependencyBenchmarksWrapperClass, BM_DeferredWildcardDependencyResolution)(benchmark::State& state)
|
||||
{
|
||||
for (auto _ : state)
|
||||
for ([[maybe_unused]] auto unused : state)
|
||||
{
|
||||
m_benchmarks->m_stateData->SetProductDependencies(m_benchmarks->m_dependencies);
|
||||
|
||||
|
||||
@@ -191,7 +191,7 @@ namespace UnitTests
|
||||
|
||||
m_data->m_perforceComponent = AZStd::make_unique<MockPerforceComponent>();
|
||||
m_data->m_perforceComponent->Activate();
|
||||
m_data->m_perforceComponent->SetConnection(new UnitTest::MockPerforceConnection(m_command));
|
||||
m_data->m_perforceComponent->SetConnection(new ::UnitTest::MockPerforceConnection(m_command));
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
@@ -876,7 +876,7 @@ namespace UnitTests
|
||||
QDir tempPath(m_tempDir.path());
|
||||
|
||||
auto filePath = QDir(tempPath.absoluteFilePath(m_data->m_scanFolder1.m_scanFolder.c_str())).absoluteFilePath("duplicate/file1.tif");
|
||||
|
||||
|
||||
ASSERT_TRUE(AZ::IO::FileIOBase::GetInstance()->Exists(filePath.toUtf8().constData()));
|
||||
|
||||
auto result = m_data->m_reporter->Delete(filePath.toUtf8().constData(), false);
|
||||
|
||||
@@ -22,108 +22,6 @@
|
||||
|
||||
using namespace AssetProcessor;
|
||||
|
||||
class AssetProcessorManager_Test
|
||||
: public AssetProcessorManager
|
||||
{
|
||||
public:
|
||||
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, AssetProcessedImpl_DifferentProductDependenciesPerProduct_SavesCorrectlyToDatabase);
|
||||
|
||||
friend class GTEST_TEST_CLASS_NAME_(MultiplatformPathDependencyTest, AssetProcessed_Impl_MultiplatformDependencies);
|
||||
friend class GTEST_TEST_CLASS_NAME_(MultiplatformPathDependencyTest, AssetProcessed_Impl_MultiplatformDependencies_DeferredResolution);
|
||||
friend class GTEST_TEST_CLASS_NAME_(MultiplatformPathDependencyTest, SameFilenameForAllPlatforms);
|
||||
|
||||
friend class GTEST_TEST_CLASS_NAME_(MultiplatformPathDependencyTest, AssetProcessed_Impl_MultiplatformDependencies_SourcePath);
|
||||
|
||||
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, DeleteFolder_SignalsDeleteOfContainedFiles);
|
||||
|
||||
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, QueryAbsolutePathDependenciesRecursive_BasicTest);
|
||||
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, QueryAbsolutePathDependenciesRecursive_WithDifferentTypes_BasicTest);
|
||||
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, QueryAbsolutePathDependenciesRecursive_Reverse_BasicTest);
|
||||
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, QueryAbsolutePathDependenciesRecursive_MissingFiles_ReturnsNoPathWithPlaceholders);
|
||||
|
||||
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_BeforeComputingDirtiness_AllDirty);
|
||||
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_EmptyDatabase_AllDirty);
|
||||
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_SameAsLastTime_NoneDirty);
|
||||
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_MoreThanLastTime_NewOneIsDirty);
|
||||
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_FewerThanLastTime_Dirty);
|
||||
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_ChangedPattern_CountsAsNew);
|
||||
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_ChangedPatternType_CountsAsNew);
|
||||
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_NewPattern_CountsAsNewBuilder);
|
||||
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_NewVersionNumber_IsNotANewBuilder);
|
||||
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_NewAnalysisFingerprint_IsNotANewBuilder);
|
||||
|
||||
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_BasicTest);
|
||||
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_UpdateTest);
|
||||
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_MissingFiles_ByUuid);
|
||||
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_MissingFiles_ByName);
|
||||
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_MissingFiles_ByUuid_UpdatesWhenTheyAppear);
|
||||
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_MissingFiles_ByName_UpdatesWhenTheyAppear);
|
||||
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_WildcardMissingFiles_ByName_UpdatesWhenTheyAppear);
|
||||
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, JobDependencyOrderOnce_MultipleJobs_EmitOK);
|
||||
|
||||
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, SourceFileProcessFailure_ClearsFingerprint);
|
||||
|
||||
friend class GTEST_TEST_CLASS_NAME_(AbsolutePathProductDependencyTest, UnresolvedProductPathDependency_AssetProcessedTwice_DoesNotDuplicateDependency);
|
||||
friend class GTEST_TEST_CLASS_NAME_(AbsolutePathProductDependencyTest, AbsolutePathProductDependency_RetryDeferredDependenciesWithMatchingSource_DependencyResolves);
|
||||
friend class GTEST_TEST_CLASS_NAME_(AbsolutePathProductDependencyTest, UnresolvedProductPathDependency_AssetProcessedTwice_ValidatePathDependenciesMap);
|
||||
friend class GTEST_TEST_CLASS_NAME_(AbsolutePathProductDependencyTest, UnresolvedSourceFileTypeProductPathDependency_DependencyHasNoProductOutput_ValidatePathDependenciesMap);
|
||||
|
||||
friend class GTEST_TEST_CLASS_NAME_(ModtimeScanningTest, ModtimeSkipping_FileUnchanged_WithoutModtimeSkipping);
|
||||
|
||||
friend class GTEST_TEST_CLASS_NAME_(ModtimeScanningTest, ModtimeSkipping_FileUnchanged);
|
||||
|
||||
friend class GTEST_TEST_CLASS_NAME_(ModtimeScanningTest, ModtimeSkipping_EnablePlatform_ShouldProcessFilesForPlatform);
|
||||
|
||||
friend class GTEST_TEST_CLASS_NAME_(ModtimeScanningTest, ModtimeSkipping_ModifyFile);
|
||||
friend class GTEST_TEST_CLASS_NAME_(ModtimeScanningTest, ModtimeSkipping_ModifyFile_AndThenRevert_ProcessesAgain);
|
||||
friend class GTEST_TEST_CLASS_NAME_(ModtimeScanningTest, ModtimeSkipping_ModifyFilesSameHash_BothProcess);
|
||||
friend class GTEST_TEST_CLASS_NAME_(ModtimeScanningTest, ModtimeSkipping_ModifyTimestamp);
|
||||
friend class GTEST_TEST_CLASS_NAME_(ModtimeScanningTest, ModtimeSkipping_ModifyTimestampNoHashing_ProcessesFile);
|
||||
friend class GTEST_TEST_CLASS_NAME_(ModtimeScanningTest, ModtimeSkipping_ModifyMetadataFile);
|
||||
friend class GTEST_TEST_CLASS_NAME_(ModtimeScanningTest, ModtimeSkipping_DeleteFile);
|
||||
friend class GTEST_TEST_CLASS_NAME_(DeleteTest, DeleteFolderSharedAcrossTwoScanFolders_CorrectFileAndFolderAreDeletedFromCache);
|
||||
friend class GTEST_TEST_CLASS_NAME_(MetadataFileTest, MetadataFile_SourceFileExtensionDifferentCase);
|
||||
|
||||
friend class AssetProcessorManagerTest;
|
||||
friend struct ModtimeScanningTest;
|
||||
friend struct JobDependencyTest;
|
||||
friend struct ChainJobDependencyTest;
|
||||
friend struct DeleteTest;
|
||||
friend struct PathDependencyTest;
|
||||
friend struct DuplicateProductsTest;
|
||||
friend struct DuplicateProcessTest;
|
||||
friend struct AbsolutePathProductDependencyTest;
|
||||
friend struct WildcardSourceDependencyTest;
|
||||
|
||||
explicit AssetProcessorManager_Test(PlatformConfiguration* config, QObject* parent = nullptr);
|
||||
~AssetProcessorManager_Test() override;
|
||||
|
||||
bool CheckJobKeyToJobRunKeyMap(AZStd::string jobKey);
|
||||
|
||||
int CountDirtyBuilders() const
|
||||
{
|
||||
int numDirty = 0;
|
||||
for (const auto& element : m_builderDataCache)
|
||||
{
|
||||
if (element.second.m_isDirty)
|
||||
{
|
||||
++numDirty;
|
||||
}
|
||||
}
|
||||
return numDirty;
|
||||
}
|
||||
|
||||
bool IsBuilderDirty(const AZ::Uuid& builderBusId) const
|
||||
{
|
||||
auto finder = m_builderDataCache.find(builderBusId);
|
||||
if (finder == m_builderDataCache.end())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return finder->second.m_isDirty;
|
||||
}
|
||||
};
|
||||
|
||||
AssetProcessorManager_Test::AssetProcessorManager_Test(AssetProcessor::PlatformConfiguration* config, QObject* parent /*= 0*/)
|
||||
:AssetProcessorManager(config, parent)
|
||||
{
|
||||
@@ -3839,632 +3737,6 @@ TEST_F(AssetProcessorManagerTest, SourceFileProcessFailure_ClearsFingerprint)
|
||||
ASSERT_EQ(source.m_analysisFingerprint, "");
|
||||
}
|
||||
|
||||
void ModtimeScanningTest::SetUp()
|
||||
{
|
||||
AssetProcessorManagerTest::SetUp();
|
||||
|
||||
m_data = AZStd::make_unique<StaticData>();
|
||||
|
||||
// We don't want the mock application manager to provide builder descriptors, mockBuilderInfoHandler will provide our own
|
||||
m_mockApplicationManager->BusDisconnect();
|
||||
|
||||
m_data->m_mockBuilderInfoHandler.m_builderDesc = m_data->m_mockBuilderInfoHandler.CreateBuilderDesc("test builder", "{DF09DDC0-FD22-43B6-9E22-22C8574A6E1E}", { AssetBuilderSDK::AssetBuilderPattern("*.txt", AssetBuilderSDK::AssetBuilderPattern::Wildcard) });
|
||||
m_data->m_mockBuilderInfoHandler.BusConnect();
|
||||
|
||||
ASSERT_TRUE(m_mockApplicationManager->GetBuilderByID("txt files", m_data->m_builderTxtBuilder));
|
||||
|
||||
// Run this twice so the test builder doesn't get counted as a "new" builder and bypass the modtime skipping
|
||||
m_assetProcessorManager->ComputeBuilderDirty();
|
||||
m_assetProcessorManager->ComputeBuilderDirty();
|
||||
|
||||
auto assetConnection = QObject::connect(m_assetProcessorManager.get(), &AssetProcessorManager::AssetToProcess, [this](JobDetails details)
|
||||
{
|
||||
m_data->m_processResults.push_back(AZStd::move(details));
|
||||
});
|
||||
|
||||
auto deletedConnection = QObject::connect(m_assetProcessorManager.get(), &AssetProcessorManager::SourceDeleted, [this](QString file)
|
||||
{
|
||||
m_data->m_deletedSources.push_back(file);
|
||||
});
|
||||
|
||||
// Create the test file
|
||||
const auto& scanFolder = m_config->GetScanFolderAt(0);
|
||||
m_data->m_relativePathFromWatchFolder[0] = "modtimeTestFile.txt";
|
||||
m_data->m_absolutePath.push_back(QDir(scanFolder.ScanPath()).absoluteFilePath(m_data->m_relativePathFromWatchFolder[0]));
|
||||
|
||||
m_data->m_relativePathFromWatchFolder[1] = "modtimeTestDependency.txt";
|
||||
m_data->m_absolutePath.push_back(QDir(scanFolder.ScanPath()).absoluteFilePath(m_data->m_relativePathFromWatchFolder[1]));
|
||||
|
||||
m_data->m_relativePathFromWatchFolder[2] = "modtimeTestDependency.txt.assetinfo";
|
||||
m_data->m_absolutePath.push_back(QDir(scanFolder.ScanPath()).absoluteFilePath(m_data->m_relativePathFromWatchFolder[2]));
|
||||
|
||||
for (const auto& path : m_data->m_absolutePath)
|
||||
{
|
||||
ASSERT_TRUE(UnitTestUtils::CreateDummyFile(path, ""));
|
||||
}
|
||||
|
||||
m_data->m_mockBuilderInfoHandler.m_dependencyFilePath = m_data->m_absolutePath[1].toUtf8().data();
|
||||
|
||||
// Add file to database with no modtime
|
||||
{
|
||||
AssetDatabaseConnection connection;
|
||||
ASSERT_TRUE(connection.OpenDatabase());
|
||||
AzToolsFramework::AssetDatabase::FileDatabaseEntry fileEntry;
|
||||
fileEntry.m_fileName = m_data->m_relativePathFromWatchFolder[0].toUtf8().data();
|
||||
fileEntry.m_modTime = 0;
|
||||
fileEntry.m_isFolder = false;
|
||||
fileEntry.m_scanFolderPK = scanFolder.ScanFolderID();
|
||||
|
||||
bool entryAlreadyExists;
|
||||
ASSERT_TRUE(connection.InsertFile(fileEntry, entryAlreadyExists));
|
||||
ASSERT_FALSE(entryAlreadyExists);
|
||||
|
||||
fileEntry.m_fileID = AzToolsFramework::AssetDatabase::InvalidEntryId; // Reset the id so we make a new entry
|
||||
fileEntry.m_fileName = m_data->m_relativePathFromWatchFolder[1].toUtf8().data();
|
||||
ASSERT_TRUE(connection.InsertFile(fileEntry, entryAlreadyExists));
|
||||
ASSERT_FALSE(entryAlreadyExists);
|
||||
|
||||
fileEntry.m_fileID = AzToolsFramework::AssetDatabase::InvalidEntryId; // Reset the id so we make a new entry
|
||||
fileEntry.m_fileName = m_data->m_relativePathFromWatchFolder[2].toUtf8().data();
|
||||
ASSERT_TRUE(connection.InsertFile(fileEntry, entryAlreadyExists));
|
||||
ASSERT_FALSE(entryAlreadyExists);
|
||||
}
|
||||
|
||||
QSet<AssetFileInfo> filePaths = BuildFileSet();
|
||||
SimulateAssetScanner(filePaths);
|
||||
|
||||
ASSERT_TRUE(BlockUntilIdle(5000));
|
||||
ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 2);
|
||||
ASSERT_EQ(m_data->m_processResults.size(), 2);
|
||||
ASSERT_EQ(m_data->m_deletedSources.size(), 0);
|
||||
|
||||
ProcessAssetJobs();
|
||||
|
||||
m_data->m_processResults.clear();
|
||||
m_data->m_mockBuilderInfoHandler.m_createJobsCount = 0;
|
||||
|
||||
m_isIdling = false;
|
||||
}
|
||||
|
||||
void ModtimeScanningTest::TearDown()
|
||||
{
|
||||
m_data = nullptr;
|
||||
|
||||
AssetProcessorManagerTest::TearDown();
|
||||
}
|
||||
|
||||
void ModtimeScanningTest::ProcessAssetJobs()
|
||||
{
|
||||
m_data->m_productPaths.clear();
|
||||
|
||||
for (const auto& processResult : m_data->m_processResults)
|
||||
{
|
||||
auto file = QDir(processResult.m_destinationPath).absoluteFilePath(processResult.m_jobEntry.m_databaseSourceName.toLower() + ".arc1");
|
||||
m_data->m_productPaths.emplace(
|
||||
QDir(processResult.m_jobEntry.m_watchFolderPath)
|
||||
.absoluteFilePath(processResult.m_jobEntry.m_databaseSourceName)
|
||||
.toUtf8()
|
||||
.constData(),
|
||||
file);
|
||||
|
||||
// Create the file on disk
|
||||
ASSERT_TRUE(UnitTestUtils::CreateDummyFile(file, "products."));
|
||||
|
||||
AssetBuilderSDK::ProcessJobResponse response;
|
||||
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
|
||||
response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(file.toUtf8().constData(), AZ::Uuid::CreateNull(), 1));
|
||||
|
||||
QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, processResult.m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
|
||||
}
|
||||
|
||||
ASSERT_TRUE(BlockUntilIdle(5000));
|
||||
|
||||
m_isIdling = false;
|
||||
}
|
||||
|
||||
void ModtimeScanningTest::SimulateAssetScanner(QSet<AssetFileInfo> filePaths)
|
||||
{
|
||||
QMetaObject::invokeMethod(m_assetProcessorManager.get(), "OnAssetScannerStatusChange", Qt::QueuedConnection, Q_ARG(AssetProcessor::AssetScanningStatus, AssetProcessor::AssetScanningStatus::Started));
|
||||
QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessFilesFromScanner", Qt::QueuedConnection, Q_ARG(QSet<AssetFileInfo>, filePaths));
|
||||
QMetaObject::invokeMethod(m_assetProcessorManager.get(), "OnAssetScannerStatusChange", Qt::QueuedConnection, Q_ARG(AssetProcessor::AssetScanningStatus, AssetProcessor::AssetScanningStatus::Completed));
|
||||
}
|
||||
|
||||
QSet<AssetFileInfo> ModtimeScanningTest::BuildFileSet()
|
||||
{
|
||||
QSet<AssetFileInfo> filePaths;
|
||||
|
||||
for (const auto& path : m_data->m_absolutePath)
|
||||
{
|
||||
QFileInfo fileInfo(path);
|
||||
auto modtime = fileInfo.lastModified();
|
||||
AZ::u64 fileSize = fileInfo.size();
|
||||
filePaths.insert(AssetFileInfo(path, modtime, fileSize, m_config->GetScanFolderForFile(path), false));
|
||||
}
|
||||
|
||||
return filePaths;
|
||||
}
|
||||
|
||||
void ModtimeScanningTest::ExpectWork(int createJobs, int processJobs)
|
||||
{
|
||||
ASSERT_TRUE(BlockUntilIdle(5000));
|
||||
|
||||
EXPECT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, createJobs);
|
||||
EXPECT_EQ(m_data->m_processResults.size(), processJobs);
|
||||
EXPECT_FALSE(m_data->m_processResults[0].m_autoFail);
|
||||
EXPECT_FALSE(m_data->m_processResults[1].m_autoFail);
|
||||
EXPECT_EQ(m_data->m_deletedSources.size(), 0);
|
||||
|
||||
m_isIdling = false;
|
||||
}
|
||||
|
||||
void ModtimeScanningTest::ExpectNoWork()
|
||||
{
|
||||
// Since there's no work to do, the idle event isn't going to trigger, just process events a couple times
|
||||
for (int i = 0; i < 10; ++i)
|
||||
{
|
||||
QCoreApplication::processEvents(QEventLoop::AllEvents, 10);
|
||||
}
|
||||
|
||||
ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 0);
|
||||
ASSERT_EQ(m_data->m_processResults.size(), 0);
|
||||
ASSERT_EQ(m_data->m_deletedSources.size(), 0);
|
||||
|
||||
m_isIdling = false;
|
||||
}
|
||||
|
||||
void ModtimeScanningTest::SetFileContents(QString filePath, QString contents)
|
||||
{
|
||||
QFile file(filePath);
|
||||
file.open(QIODevice::WriteOnly | QIODevice::Truncate);
|
||||
file.write(contents.toUtf8().constData());
|
||||
file.close();
|
||||
}
|
||||
|
||||
TEST_F(ModtimeScanningTest, ModtimeSkipping_FileUnchanged_WithoutModtimeSkipping)
|
||||
{
|
||||
using namespace AzToolsFramework::AssetSystem;
|
||||
|
||||
// Make sure modtime skipping is disabled
|
||||
// We're just going to do 1 quick sanity test to make sure the files are still processed when modtime skipping is turned off
|
||||
m_assetProcessorManager->m_allowModtimeSkippingFeature = false;
|
||||
|
||||
QSet<AssetFileInfo> filePaths = BuildFileSet();
|
||||
SimulateAssetScanner(filePaths);
|
||||
|
||||
// 2 create jobs but 0 process jobs because the file has already been processed before in SetUp
|
||||
ExpectWork(2, 0);
|
||||
}
|
||||
|
||||
TEST_F(ModtimeScanningTest, ModtimeSkipping_FileUnchanged)
|
||||
{
|
||||
using namespace AzToolsFramework::AssetSystem;
|
||||
|
||||
// Enable the features we're testing
|
||||
m_assetProcessorManager->m_allowModtimeSkippingFeature = true;
|
||||
AssetUtilities::SetUseFileHashOverride(true, true);
|
||||
|
||||
QSet<AssetFileInfo> filePaths = BuildFileSet();
|
||||
SimulateAssetScanner(filePaths);
|
||||
|
||||
ExpectNoWork();
|
||||
}
|
||||
|
||||
TEST_F(ModtimeScanningTest, ModtimeSkipping_EnablePlatform_ShouldProcessFilesForPlatform)
|
||||
{
|
||||
using namespace AzToolsFramework::AssetSystem;
|
||||
|
||||
// Enable the features we're testing
|
||||
m_assetProcessorManager->m_allowModtimeSkippingFeature = true;
|
||||
AssetUtilities::SetUseFileHashOverride(true, true);
|
||||
|
||||
// Enable android platform after the initial SetUp has already processed the files for pc
|
||||
QDir tempPath(m_tempDir.path());
|
||||
AssetBuilderSDK::PlatformInfo androidPlatform("android", { "host", "renderer" });
|
||||
m_config->EnablePlatform(androidPlatform, true);
|
||||
|
||||
// There's no way to remove scanfolders and adding a new one after enabling the platform will cause the pc assets to build as well, which we don't want
|
||||
// Instead we'll just const cast the vector and modify the enabled platforms for the scanfolder
|
||||
auto& platforms = const_cast<AZStd::vector<AssetBuilderSDK::PlatformInfo>&>(m_config->GetScanFolderAt(0).GetPlatforms());
|
||||
platforms.push_back(androidPlatform);
|
||||
|
||||
// We need the builder fingerprints to be updated to reflect the newly enabled platform
|
||||
m_assetProcessorManager->ComputeBuilderDirty();
|
||||
|
||||
QSet<AssetFileInfo> filePaths = BuildFileSet();
|
||||
SimulateAssetScanner(filePaths);
|
||||
|
||||
ExpectWork(4, 2); // CreateJobs = 4, 2 files * 2 platforms. ProcessJobs = 2, just the android platform jobs (pc is already processed)
|
||||
|
||||
ASSERT_TRUE(m_data->m_processResults[0].m_destinationPath.contains("android"));
|
||||
ASSERT_TRUE(m_data->m_processResults[1].m_destinationPath.contains("android"));
|
||||
}
|
||||
|
||||
TEST_F(ModtimeScanningTest, ModtimeSkipping_ModifyTimestamp)
|
||||
{
|
||||
// Update the timestamp on a file without changing its contents
|
||||
// This should not cause any job to run since the hash of the file is the same before/after
|
||||
// Additionally, the timestamp stored in the database should be updated
|
||||
using namespace AzToolsFramework::AssetSystem;
|
||||
|
||||
uint64_t timestamp = 1594923423;
|
||||
|
||||
QString databaseName, scanfolderName;
|
||||
m_config->ConvertToRelativePath(m_data->m_absolutePath[1], databaseName, scanfolderName);
|
||||
auto* scanFolder = m_config->GetScanFolderForFile(m_data->m_absolutePath[1]);
|
||||
|
||||
AzToolsFramework::AssetDatabase::FileDatabaseEntry fileEntry;
|
||||
|
||||
m_assetProcessorManager.get()->m_stateData->GetFileByFileNameAndScanFolderId(databaseName, scanFolder->ScanFolderID(), fileEntry);
|
||||
|
||||
ASSERT_NE(fileEntry.m_modTime, timestamp);
|
||||
uint64_t existingTimestamp = fileEntry.m_modTime;
|
||||
|
||||
// Modify the timestamp on just one file
|
||||
AzToolsFramework::ToolsFileUtils::SetModificationTime(m_data->m_absolutePath[1].toUtf8().data(), timestamp);
|
||||
|
||||
// Enable the features we're testing
|
||||
m_assetProcessorManager->m_allowModtimeSkippingFeature = true;
|
||||
AssetUtilities::SetUseFileHashOverride(true, true);
|
||||
|
||||
QSet<AssetFileInfo> filePaths = BuildFileSet();
|
||||
SimulateAssetScanner(filePaths);
|
||||
|
||||
ExpectNoWork();
|
||||
|
||||
m_assetProcessorManager.get()->m_stateData->GetFileByFileNameAndScanFolderId(databaseName, scanFolder->ScanFolderID(), fileEntry);
|
||||
|
||||
// The timestamp should be updated even though nothing processed
|
||||
ASSERT_NE(fileEntry.m_modTime, existingTimestamp);
|
||||
}
|
||||
|
||||
TEST_F(ModtimeScanningTest, ModtimeSkipping_ModifyTimestampNoHashing_ProcessesFile)
|
||||
{
|
||||
// Update the timestamp on a file without changing its contents
|
||||
// This should not cause any job to run since the hash of the file is the same before/after
|
||||
// Additionally, the timestamp stored in the database should be updated
|
||||
using namespace AzToolsFramework::AssetSystem;
|
||||
|
||||
uint64_t timestamp = 1594923423;
|
||||
|
||||
// Modify the timestamp on just one file
|
||||
AzToolsFramework::ToolsFileUtils::SetModificationTime(m_data->m_absolutePath[1].toUtf8().data(), timestamp);
|
||||
|
||||
// Enable the features we're testing
|
||||
m_assetProcessorManager->m_allowModtimeSkippingFeature = true;
|
||||
AssetUtilities::SetUseFileHashOverride(true, false);
|
||||
|
||||
QSet<AssetFileInfo> filePaths = BuildFileSet();
|
||||
SimulateAssetScanner(filePaths);
|
||||
|
||||
ExpectWork(2, 2);
|
||||
}
|
||||
|
||||
TEST_F(ModtimeScanningTest, ModtimeSkipping_ModifyFile)
|
||||
{
|
||||
using namespace AzToolsFramework::AssetSystem;
|
||||
|
||||
SetFileContents(m_data->m_absolutePath[1].toUtf8().constData(), "hello world");
|
||||
|
||||
// Enable the features we're testing
|
||||
m_assetProcessorManager->m_allowModtimeSkippingFeature = true;
|
||||
AssetUtilities::SetUseFileHashOverride(true, true);
|
||||
|
||||
QSet<AssetFileInfo> filePaths = BuildFileSet();
|
||||
SimulateAssetScanner(filePaths);
|
||||
|
||||
// Even though we're only updating one file, we're expecting 2 createJob calls because our test file is a dependency that triggers the other test file to process as well
|
||||
ExpectWork(2, 2);
|
||||
}
|
||||
|
||||
TEST_F(ModtimeScanningTest, ModtimeSkipping_ModifyFile_AndThenRevert_ProcessesAgain)
|
||||
{
|
||||
using namespace AzToolsFramework::AssetSystem;
|
||||
auto theFile = m_data->m_absolutePath[1].toUtf8();
|
||||
const char* theFileString = theFile.constData();
|
||||
|
||||
SetFileContents(theFileString, "hello world");
|
||||
|
||||
// Enable the features we're testing
|
||||
m_assetProcessorManager->m_allowModtimeSkippingFeature = true;
|
||||
AssetUtilities::SetUseFileHashOverride(true, true);
|
||||
|
||||
QSet<AssetFileInfo> filePaths = BuildFileSet();
|
||||
SimulateAssetScanner(filePaths);
|
||||
|
||||
// Even though we're only updating one file, we're expecting 2 createJob calls because our test file is a dependency that triggers the other test file to process as well
|
||||
ExpectWork(2, 2);
|
||||
ProcessAssetJobs();
|
||||
|
||||
m_data->m_mockBuilderInfoHandler.m_createJobsCount = 0;
|
||||
m_data->m_processResults.clear();
|
||||
m_data->m_deletedSources.clear();
|
||||
|
||||
SetFileContents(theFileString, "");
|
||||
|
||||
filePaths = BuildFileSet();
|
||||
SimulateAssetScanner(filePaths);
|
||||
|
||||
// Expect processing to happen again
|
||||
ExpectWork(2, 2);
|
||||
}
|
||||
|
||||
struct LockedFileTest
|
||||
: ModtimeScanningTest
|
||||
, AssetProcessor::ConnectionBus::Handler
|
||||
{
|
||||
MOCK_METHOD3(SendRaw, size_t (unsigned, unsigned, const QByteArray&));
|
||||
MOCK_METHOD3(SendPerPlatform, size_t (unsigned, const AzFramework::AssetSystem::BaseAssetProcessorMessage&, const QString&));
|
||||
MOCK_METHOD4(SendRawPerPlatform, size_t (unsigned, unsigned, const QByteArray&, const QString&));
|
||||
MOCK_METHOD2(SendRequest, unsigned (const AzFramework::AssetSystem::BaseAssetProcessorMessage&, const ResponseCallback&));
|
||||
MOCK_METHOD2(SendResponse, size_t (unsigned, const AzFramework::AssetSystem::BaseAssetProcessorMessage&));
|
||||
MOCK_METHOD1(RemoveResponseHandler, void (unsigned));
|
||||
|
||||
size_t Send(unsigned, const AzFramework::AssetSystem::BaseAssetProcessorMessage& message) override
|
||||
{
|
||||
using SourceFileNotificationMessage = AzToolsFramework::AssetSystem::SourceFileNotificationMessage;
|
||||
switch (message.GetMessageType())
|
||||
{
|
||||
case SourceFileNotificationMessage::MessageType:
|
||||
if (const auto sourceFileMessage = azrtti_cast<const SourceFileNotificationMessage*>(&message); sourceFileMessage != nullptr &&
|
||||
sourceFileMessage->m_type == SourceFileNotificationMessage::NotificationType::FileRemoved)
|
||||
{
|
||||
// The File Remove message will occur before an attempt to delete the file
|
||||
// Wait for more than 1 File Remove message.
|
||||
// This indicates the AP has attempted to delete the file once, failed to do so and is now retrying
|
||||
++m_deleteCounter;
|
||||
|
||||
if(m_deleteCounter > 1 && m_callback)
|
||||
{
|
||||
m_callback();
|
||||
m_callback = {}; // Unset it to be safe, we only intend to run the callback once
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
ModtimeScanningTest::SetUp();
|
||||
|
||||
ConnectionBus::Handler::BusConnect(0);
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
ConnectionBus::Handler::BusDisconnect();
|
||||
|
||||
ModtimeScanningTest::TearDown();
|
||||
}
|
||||
|
||||
AZStd::atomic_int m_deleteCounter{ 0 };
|
||||
AZStd::function<void()> m_callback;
|
||||
};
|
||||
|
||||
TEST_F(LockedFileTest, DeleteFile_LockedProduct_DeleteFails)
|
||||
{
|
||||
auto theFile = m_data->m_absolutePath[1].toUtf8();
|
||||
const char* theFileString = theFile.constData();
|
||||
auto [sourcePath, productPath] = *m_data->m_productPaths.find(theFileString);
|
||||
|
||||
{
|
||||
QFile file(theFileString);
|
||||
file.remove();
|
||||
}
|
||||
|
||||
ASSERT_GT(m_data->m_productPaths.size(), 0);
|
||||
QFile product(productPath);
|
||||
|
||||
ASSERT_TRUE(product.open(QIODevice::ReadOnly));
|
||||
|
||||
// Check if we can delete the file now, if we can't, proceed with the test
|
||||
// If we can, it means the OS running this test doesn't lock open files so there's nothing to test
|
||||
if (!AZ::IO::SystemFile::Delete(productPath.toUtf8().constData()))
|
||||
{
|
||||
QMetaObject::invokeMethod(
|
||||
m_assetProcessorManager.get(), "AssessDeletedFile", Qt::QueuedConnection, Q_ARG(QString, QString(theFileString)));
|
||||
|
||||
EXPECT_TRUE(BlockUntilIdle(5000));
|
||||
|
||||
EXPECT_TRUE(QFile::exists(productPath));
|
||||
EXPECT_EQ(m_data->m_deletedSources.size(), 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
SUCCEED() << "Skipping test. OS does not lock open files.";
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(LockedFileTest, DeleteFile_LockedProduct_DeletesWhenReleased)
|
||||
{
|
||||
// This test is intended to verify the AP will successfully retry deleting a source asset
|
||||
// when one of its product assets is locked temporarily
|
||||
// We'll lock the file by holding it open
|
||||
|
||||
auto theFile = m_data->m_absolutePath[1].toUtf8();
|
||||
const char* theFileString = theFile.constData();
|
||||
auto [sourcePath, productPath] = *m_data->m_productPaths.find(theFileString);
|
||||
|
||||
{
|
||||
QFile file(theFileString);
|
||||
file.remove();
|
||||
}
|
||||
|
||||
ASSERT_GT(m_data->m_productPaths.size(), 0);
|
||||
QFile product(productPath);
|
||||
|
||||
// Open the file and keep it open to lock it
|
||||
// We'll start a thread later to unlock the file
|
||||
// This will allow us to test how AP handles trying to delete a locked file
|
||||
ASSERT_TRUE(product.open(QIODevice::ReadOnly));
|
||||
|
||||
// Check if we can delete the file now, if we can't, proceed with the test
|
||||
// If we can, it means the OS running this test doesn't lock open files so there's nothing to test
|
||||
if (!AZ::IO::SystemFile::Delete(productPath.toUtf8().constData()))
|
||||
{
|
||||
m_deleteCounter = 0;
|
||||
|
||||
// Set up a callback which will fire after at least 1 retry
|
||||
// Unlock the file at that point so AP can successfully delete it
|
||||
m_callback = [&product]()
|
||||
{
|
||||
product.close();
|
||||
};
|
||||
|
||||
QMetaObject::invokeMethod(
|
||||
m_assetProcessorManager.get(), "AssessDeletedFile", Qt::QueuedConnection, Q_ARG(QString, QString(theFileString)));
|
||||
|
||||
EXPECT_TRUE(BlockUntilIdle(5000));
|
||||
|
||||
EXPECT_FALSE(QFile::exists(productPath));
|
||||
EXPECT_EQ(m_data->m_deletedSources.size(), 1);
|
||||
|
||||
EXPECT_GT(m_deleteCounter, 1); // Make sure the AP tried more than once to delete the file
|
||||
m_errorAbsorber->ExpectAsserts(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
SUCCEED() << "Skipping test. OS does not lock open files.";
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ModtimeScanningTest, ModtimeSkipping_ModifyFilesSameHash_BothProcess)
|
||||
{
|
||||
using namespace AzToolsFramework::AssetSystem;
|
||||
|
||||
SetFileContents(m_data->m_absolutePath[1].toUtf8().constData(), "hello world");
|
||||
|
||||
// Enable the features we're testing
|
||||
m_assetProcessorManager->m_allowModtimeSkippingFeature = true;
|
||||
AssetUtilities::SetUseFileHashOverride(true, true);
|
||||
|
||||
QSet<AssetFileInfo> filePaths = BuildFileSet();
|
||||
SimulateAssetScanner(filePaths);
|
||||
|
||||
// Even though we're only updating one file, we're expecting 2 createJob calls because our test file is a dependency that triggers the other test file to process as well
|
||||
ExpectWork(2, 2);
|
||||
ProcessAssetJobs();
|
||||
|
||||
m_data->m_mockBuilderInfoHandler.m_createJobsCount = 0;
|
||||
m_data->m_processResults.clear();
|
||||
m_data->m_deletedSources.clear();
|
||||
|
||||
// Make file 0 have the same contents as file 1
|
||||
SetFileContents(m_data->m_absolutePath[0].toUtf8().constData(), "hello world");
|
||||
|
||||
filePaths = BuildFileSet();
|
||||
SimulateAssetScanner(filePaths);
|
||||
|
||||
ExpectWork(1, 1);
|
||||
}
|
||||
|
||||
TEST_F(ModtimeScanningTest, ModtimeSkipping_ModifyMetadataFile)
|
||||
{
|
||||
using namespace AzToolsFramework::AssetSystem;
|
||||
|
||||
SetFileContents(m_data->m_absolutePath[2].toUtf8().constData(), "hello world");
|
||||
|
||||
// Enable the features we're testing
|
||||
m_assetProcessorManager->m_allowModtimeSkippingFeature = true;
|
||||
AssetUtilities::SetUseFileHashOverride(true, true);
|
||||
|
||||
QSet<AssetFileInfo> filePaths = BuildFileSet();
|
||||
SimulateAssetScanner(filePaths);
|
||||
|
||||
// Even though we're only updating one file, we're expecting 2 createJob calls because our test file is a metadata file
|
||||
// that triggers the source file which is a dependency that triggers the other test file to process as well
|
||||
ExpectWork(2, 2);
|
||||
}
|
||||
|
||||
TEST_F(ModtimeScanningTest, ModtimeSkipping_DeleteFile)
|
||||
{
|
||||
using namespace AzToolsFramework::AssetSystem;
|
||||
|
||||
// Enable the features we're testing
|
||||
m_assetProcessorManager->m_allowModtimeSkippingFeature = true;
|
||||
AssetUtilities::SetUseFileHashOverride(true, true);
|
||||
|
||||
ASSERT_TRUE(QFile::remove(m_data->m_absolutePath[0]));
|
||||
|
||||
// Feed in ONLY one file (the one we didn't delete)
|
||||
QSet<AssetFileInfo> filePaths;
|
||||
QFileInfo fileInfo(m_data->m_absolutePath[1]);
|
||||
auto modtime = fileInfo.lastModified();
|
||||
AZ::u64 fileSize = fileInfo.size();
|
||||
filePaths.insert(AssetFileInfo(m_data->m_absolutePath[1], modtime, fileSize, &m_config->GetScanFolderAt(0), false));
|
||||
|
||||
SimulateAssetScanner(filePaths);
|
||||
|
||||
QElapsedTimer timer;
|
||||
timer.start();
|
||||
|
||||
do
|
||||
{
|
||||
QCoreApplication::processEvents(QEventLoop::AllEvents, 10);
|
||||
} while (m_data->m_deletedSources.size() < m_data->m_relativePathFromWatchFolder[0].size() && timer.elapsed() < 5000);
|
||||
|
||||
ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 0);
|
||||
ASSERT_EQ(m_data->m_processResults.size(), 0);
|
||||
ASSERT_THAT(m_data->m_deletedSources, testing::ElementsAre(m_data->m_relativePathFromWatchFolder[0]));
|
||||
}
|
||||
|
||||
TEST_F(ModtimeScanningTest, ReprocessRequest_FileNotModified_FileProcessed)
|
||||
{
|
||||
using namespace AzToolsFramework::AssetSystem;
|
||||
|
||||
m_assetProcessorManager->RequestReprocess(m_data->m_absolutePath[0]);
|
||||
|
||||
ASSERT_TRUE(BlockUntilIdle(5000));
|
||||
|
||||
ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 1);
|
||||
ASSERT_EQ(m_data->m_processResults.size(), 1);
|
||||
}
|
||||
|
||||
TEST_F(ModtimeScanningTest, ReprocessRequest_SourceWithDependency_BothWillProcess)
|
||||
{
|
||||
using namespace AzToolsFramework::AssetSystem;
|
||||
|
||||
using SourceFileDependencyEntry = AzToolsFramework::AssetDatabase::SourceFileDependencyEntry;
|
||||
|
||||
SourceFileDependencyEntry newEntry1;
|
||||
newEntry1.m_sourceDependencyID = AzToolsFramework::AssetDatabase::InvalidEntryId;
|
||||
newEntry1.m_builderGuid = AZ::Uuid::CreateRandom();
|
||||
newEntry1.m_source = m_data->m_absolutePath[0].toUtf8().constData();
|
||||
newEntry1.m_dependsOnSource = m_data->m_absolutePath[1].toUtf8().constData();
|
||||
newEntry1.m_typeOfDependency = SourceFileDependencyEntry::DEP_SourceToSource;
|
||||
|
||||
m_assetProcessorManager->RequestReprocess(m_data->m_absolutePath[0]);
|
||||
ASSERT_TRUE(BlockUntilIdle(5000));
|
||||
|
||||
ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 1);
|
||||
ASSERT_EQ(m_data->m_processResults.size(), 1);
|
||||
|
||||
m_assetProcessorManager->RequestReprocess(m_data->m_absolutePath[1]);
|
||||
ASSERT_TRUE(BlockUntilIdle(5000));
|
||||
|
||||
ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 3);
|
||||
ASSERT_EQ(m_data->m_processResults.size(), 3);
|
||||
}
|
||||
|
||||
TEST_F(ModtimeScanningTest, ReprocessRequest_RequestFolder_SourceAssetsWillProcess)
|
||||
{
|
||||
using namespace AzToolsFramework::AssetSystem;
|
||||
|
||||
const auto& scanFolder = m_config->GetScanFolderAt(0);
|
||||
|
||||
QString scanPath = scanFolder.ScanPath();
|
||||
m_assetProcessorManager->RequestReprocess(scanPath);
|
||||
ASSERT_TRUE(BlockUntilIdle(5000));
|
||||
|
||||
// two text files are source assets, assetinfo is not
|
||||
ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 2);
|
||||
ASSERT_EQ(m_data->m_processResults.size(), 2);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
MockBuilderInfoHandler::~MockBuilderInfoHandler()
|
||||
@@ -5205,130 +4477,7 @@ TEST_F(ChainJobDependencyTest, TestChainDependency_Multi)
|
||||
}
|
||||
}
|
||||
|
||||
void DeleteTest::SetUp()
|
||||
{
|
||||
AssetProcessorManagerTest::SetUp();
|
||||
|
||||
m_data = AZStd::make_unique<StaticData>();
|
||||
|
||||
m_assetProcessorManager->m_allowModtimeSkippingFeature = true;
|
||||
|
||||
// We don't want the mock application manager to provide builder descriptors, mockBuilderInfoHandler will provide our own
|
||||
m_mockApplicationManager->BusDisconnect();
|
||||
|
||||
m_data->m_mockBuilderInfoHandler.m_builderDesc = m_data->m_mockBuilderInfoHandler.CreateBuilderDesc("test builder", "{DF09DDC0-FD22-43B6-9E22-22C8574A6E1E}", { AssetBuilderSDK::AssetBuilderPattern("*.txt", AssetBuilderSDK::AssetBuilderPattern::Wildcard) });
|
||||
m_data->m_mockBuilderInfoHandler.BusConnect();
|
||||
|
||||
ASSERT_TRUE(m_mockApplicationManager->GetBuilderByID("txt files", m_data->m_builderTxtBuilder));
|
||||
|
||||
// Run this twice so the test builder doesn't get counted as a "new" builder and bypass the modtime skipping
|
||||
m_assetProcessorManager->ComputeBuilderDirty();
|
||||
m_assetProcessorManager->ComputeBuilderDirty();
|
||||
|
||||
auto setupConnectionsFunc = [this]()
|
||||
{
|
||||
QObject::connect(m_assetProcessorManager.get(), &AssetProcessorManager::AssetToProcess, [this](JobDetails details)
|
||||
{
|
||||
m_data->m_processResults.push_back(AZStd::move(details));
|
||||
});
|
||||
|
||||
QObject::connect(m_assetProcessorManager.get(), &AssetProcessorManager::SourceDeleted, [this](QString file)
|
||||
{
|
||||
m_data->m_deletedSources.push_back(file);
|
||||
});
|
||||
};
|
||||
|
||||
auto createFileAndAddToDatabaseFunc = [this](const AssetProcessor::ScanFolderInfo* scanFolder, QString file)
|
||||
{
|
||||
using namespace AzToolsFramework::AssetDatabase;
|
||||
|
||||
QString watchFolderPath = scanFolder->ScanPath();
|
||||
QString absPath(QDir(watchFolderPath).absoluteFilePath(file));
|
||||
UnitTestUtils::CreateDummyFile(absPath);
|
||||
|
||||
m_data->m_absolutePath.push_back(absPath);
|
||||
|
||||
AzToolsFramework::AssetDatabase::FileDatabaseEntry fileEntry;
|
||||
fileEntry.m_fileName = file.toUtf8().constData();
|
||||
fileEntry.m_modTime = 0;
|
||||
fileEntry.m_isFolder = false;
|
||||
fileEntry.m_scanFolderPK = scanFolder->ScanFolderID();
|
||||
|
||||
bool entryAlreadyExists;
|
||||
ASSERT_TRUE(m_assetProcessorManager->m_stateData->InsertFile(fileEntry, entryAlreadyExists));
|
||||
ASSERT_FALSE(entryAlreadyExists);
|
||||
};
|
||||
|
||||
setupConnectionsFunc();
|
||||
|
||||
// Create test files
|
||||
QDir tempPath(m_tempDir.path());
|
||||
const auto* scanFolder1 = m_config->GetScanFolderByPath(tempPath.absoluteFilePath("subfolder1"));
|
||||
const auto* scanFolder4 = m_config->GetScanFolderByPath(tempPath.absoluteFilePath("subfolder4"));
|
||||
|
||||
createFileAndAddToDatabaseFunc(scanFolder1, QString("textures/a.txt"));
|
||||
createFileAndAddToDatabaseFunc(scanFolder4, QString("textures/b.txt"));
|
||||
|
||||
// Run the test files through AP all the way to processing stage
|
||||
QSet<AssetFileInfo> filePaths = BuildFileSet();
|
||||
SimulateAssetScanner(filePaths);
|
||||
|
||||
ASSERT_TRUE(BlockUntilIdle(5000));
|
||||
ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 2);
|
||||
ASSERT_EQ(m_data->m_processResults.size(), 2);
|
||||
ASSERT_EQ(m_data->m_deletedSources.size(), 0);
|
||||
|
||||
ProcessAssetJobs();
|
||||
|
||||
m_data->m_processResults.clear();
|
||||
m_data->m_mockBuilderInfoHandler.m_createJobsCount = 0;
|
||||
|
||||
// Reboot the APM since we added stuff to the database that needs to be loaded on-startup of the APM
|
||||
m_assetProcessorManager.reset(new AssetProcessorManager_Test(m_config.get()));
|
||||
|
||||
m_idleConnection = QObject::connect(m_assetProcessorManager.get(), &AssetProcessor::AssetProcessorManager::AssetProcessorManagerIdleState, [this](bool newState)
|
||||
{
|
||||
m_isIdling = newState;
|
||||
});
|
||||
|
||||
setupConnectionsFunc();
|
||||
|
||||
m_assetProcessorManager->ComputeBuilderDirty();
|
||||
}
|
||||
|
||||
TEST_F(DeleteTest, DeleteFolderSharedAcrossTwoScanFolders_CorrectFileAndFolderAreDeletedFromCache)
|
||||
{
|
||||
// There was a bug where AP wasn't repopulating the "known folders" list when modtime skipping was enabled and no work was needed
|
||||
// As a result, deleting a folder didn't count as a "folder", so the wrong code path was taken. This test makes sure the correct deletion events fire
|
||||
|
||||
using namespace AzToolsFramework::AssetSystem;
|
||||
|
||||
// Modtime skipping has to be on for this
|
||||
m_assetProcessorManager->m_allowModtimeSkippingFeature = true;
|
||||
|
||||
// Feed in the files from the asset scanner, no jobs should run since they're already up-to-date
|
||||
QSet<AssetFileInfo> filePaths = BuildFileSet();
|
||||
SimulateAssetScanner(filePaths);
|
||||
|
||||
ExpectNoWork();
|
||||
|
||||
// Delete one of the folders
|
||||
QDir tempPath(m_tempDir.path());
|
||||
QString absPath(tempPath.absoluteFilePath("subfolder1/textures"));
|
||||
QDir(absPath).removeRecursively();
|
||||
|
||||
AZStd::vector<AZStd::string> deletedFolders;
|
||||
QObject::connect(m_assetProcessorManager.get(), &AssetProcessorManager::SourceFolderDeleted, [&deletedFolders](QString file)
|
||||
{
|
||||
deletedFolders.push_back(file.toUtf8().constData());
|
||||
});
|
||||
|
||||
m_assetProcessorManager->AssessDeletedFile(absPath);
|
||||
ASSERT_TRUE(BlockUntilIdle(5000));
|
||||
|
||||
ASSERT_THAT(m_data->m_deletedSources, testing::UnorderedElementsAre("textures/a.txt"));
|
||||
ASSERT_THAT(deletedFolders, testing::UnorderedElementsAre("textures"));
|
||||
}
|
||||
|
||||
void DuplicateProcessTest::SetUp()
|
||||
{
|
||||
|
||||
@@ -37,6 +37,114 @@ public:
|
||||
MOCK_METHOD1(GetAssetDatabaseLocation, bool(AZStd::string&));
|
||||
};
|
||||
|
||||
class AssetProcessorManager_Test : public AssetProcessor::AssetProcessorManager
|
||||
{
|
||||
public:
|
||||
friend class GTEST_TEST_CLASS_NAME_(
|
||||
AssetProcessorManagerTest, AssetProcessedImpl_DifferentProductDependenciesPerProduct_SavesCorrectlyToDatabase);
|
||||
|
||||
friend class GTEST_TEST_CLASS_NAME_(MultiplatformPathDependencyTest, AssetProcessed_Impl_MultiplatformDependencies);
|
||||
friend class GTEST_TEST_CLASS_NAME_(MultiplatformPathDependencyTest, AssetProcessed_Impl_MultiplatformDependencies_DeferredResolution);
|
||||
friend class GTEST_TEST_CLASS_NAME_(MultiplatformPathDependencyTest, SameFilenameForAllPlatforms);
|
||||
|
||||
friend class GTEST_TEST_CLASS_NAME_(MultiplatformPathDependencyTest, AssetProcessed_Impl_MultiplatformDependencies_SourcePath);
|
||||
|
||||
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, DeleteFolder_SignalsDeleteOfContainedFiles);
|
||||
|
||||
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, QueryAbsolutePathDependenciesRecursive_BasicTest);
|
||||
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, QueryAbsolutePathDependenciesRecursive_WithDifferentTypes_BasicTest);
|
||||
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, QueryAbsolutePathDependenciesRecursive_Reverse_BasicTest);
|
||||
friend class GTEST_TEST_CLASS_NAME_(
|
||||
AssetProcessorManagerTest, QueryAbsolutePathDependenciesRecursive_MissingFiles_ReturnsNoPathWithPlaceholders);
|
||||
|
||||
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_BeforeComputingDirtiness_AllDirty);
|
||||
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_EmptyDatabase_AllDirty);
|
||||
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_SameAsLastTime_NoneDirty);
|
||||
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_MoreThanLastTime_NewOneIsDirty);
|
||||
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_FewerThanLastTime_Dirty);
|
||||
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_ChangedPattern_CountsAsNew);
|
||||
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_ChangedPatternType_CountsAsNew);
|
||||
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_NewPattern_CountsAsNewBuilder);
|
||||
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_NewVersionNumber_IsNotANewBuilder);
|
||||
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_NewAnalysisFingerprint_IsNotANewBuilder);
|
||||
|
||||
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_BasicTest);
|
||||
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_UpdateTest);
|
||||
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_MissingFiles_ByUuid);
|
||||
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_MissingFiles_ByName);
|
||||
friend class GTEST_TEST_CLASS_NAME_(
|
||||
AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_MissingFiles_ByUuid_UpdatesWhenTheyAppear);
|
||||
friend class GTEST_TEST_CLASS_NAME_(
|
||||
AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_MissingFiles_ByName_UpdatesWhenTheyAppear);
|
||||
friend class GTEST_TEST_CLASS_NAME_(
|
||||
AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_WildcardMissingFiles_ByName_UpdatesWhenTheyAppear);
|
||||
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, JobDependencyOrderOnce_MultipleJobs_EmitOK);
|
||||
|
||||
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, SourceFileProcessFailure_ClearsFingerprint);
|
||||
|
||||
friend class GTEST_TEST_CLASS_NAME_(
|
||||
AbsolutePathProductDependencyTest, UnresolvedProductPathDependency_AssetProcessedTwice_DoesNotDuplicateDependency);
|
||||
friend class GTEST_TEST_CLASS_NAME_(
|
||||
AbsolutePathProductDependencyTest, AbsolutePathProductDependency_RetryDeferredDependenciesWithMatchingSource_DependencyResolves);
|
||||
friend class GTEST_TEST_CLASS_NAME_(
|
||||
AbsolutePathProductDependencyTest, UnresolvedProductPathDependency_AssetProcessedTwice_ValidatePathDependenciesMap);
|
||||
friend class GTEST_TEST_CLASS_NAME_(
|
||||
AbsolutePathProductDependencyTest,
|
||||
UnresolvedSourceFileTypeProductPathDependency_DependencyHasNoProductOutput_ValidatePathDependenciesMap);
|
||||
|
||||
friend class GTEST_TEST_CLASS_NAME_(DeleteTest, DeleteFolderSharedAcrossTwoScanFolders_CorrectFileAndFolderAreDeletedFromCache);
|
||||
friend class GTEST_TEST_CLASS_NAME_(MetadataFileTest, MetadataFile_SourceFileExtensionDifferentCase);
|
||||
|
||||
friend class AssetProcessorManagerTest;
|
||||
friend struct JobDependencyTest;
|
||||
friend struct ChainJobDependencyTest;
|
||||
friend struct DeleteTest;
|
||||
friend struct PathDependencyTest;
|
||||
friend struct DuplicateProductsTest;
|
||||
friend struct DuplicateProcessTest;
|
||||
friend struct AbsolutePathProductDependencyTest;
|
||||
friend struct WildcardSourceDependencyTest;
|
||||
|
||||
explicit AssetProcessorManager_Test(AssetProcessor::PlatformConfiguration* config, QObject* parent = nullptr);
|
||||
~AssetProcessorManager_Test() override;
|
||||
|
||||
bool CheckJobKeyToJobRunKeyMap(AZStd::string jobKey);
|
||||
|
||||
int CountDirtyBuilders() const
|
||||
{
|
||||
int numDirty = 0;
|
||||
for (const auto& element : m_builderDataCache)
|
||||
{
|
||||
if (element.second.m_isDirty)
|
||||
{
|
||||
++numDirty;
|
||||
}
|
||||
}
|
||||
return numDirty;
|
||||
}
|
||||
|
||||
bool IsBuilderDirty(const AZ::Uuid& builderBusId) const
|
||||
{
|
||||
auto finder = m_builderDataCache.find(builderBusId);
|
||||
if (finder == m_builderDataCache.end())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return finder->second.m_isDirty;
|
||||
}
|
||||
|
||||
void RecomputeDirtyBuilders()
|
||||
{
|
||||
// Run this twice so the test builder doesn't get counted as a "new" builder and bypass the modtime skipping
|
||||
ComputeBuilderDirty();
|
||||
ComputeBuilderDirty();
|
||||
}
|
||||
|
||||
using AssetProcessorManager::m_stateData;
|
||||
using AssetProcessorManager::ComputeBuilderDirty;
|
||||
};
|
||||
|
||||
|
||||
class AssetProcessorManagerTest
|
||||
: public AssetProcessor::AssetProcessorTest
|
||||
{
|
||||
@@ -165,33 +273,6 @@ struct MockBuilderInfoHandler
|
||||
int m_createJobsCount = 0;
|
||||
};
|
||||
|
||||
struct ModtimeScanningTest
|
||||
: public AssetProcessorManagerTest
|
||||
{
|
||||
void SetUp() override;
|
||||
void TearDown() override;
|
||||
|
||||
void ProcessAssetJobs();
|
||||
void SimulateAssetScanner(QSet<AssetProcessor::AssetFileInfo> filePaths);
|
||||
QSet<AssetProcessor::AssetFileInfo> BuildFileSet();
|
||||
void ExpectWork(int createJobs, int processJobs);
|
||||
void ExpectNoWork();
|
||||
void SetFileContents(QString filePath, QString contents);
|
||||
|
||||
struct StaticData
|
||||
{
|
||||
QString m_relativePathFromWatchFolder[3];
|
||||
AZStd::vector<QString> m_absolutePath;
|
||||
AZStd::vector<AssetProcessor::JobDetails> m_processResults;
|
||||
AZStd::unordered_multimap<AZStd::string, QString> m_productPaths;
|
||||
AZStd::vector<QString> m_deletedSources;
|
||||
AZStd::shared_ptr<AssetProcessor::InternalMockBuilder> m_builderTxtBuilder;
|
||||
MockBuilderInfoHandler m_mockBuilderInfoHandler;
|
||||
};
|
||||
|
||||
AZStd::unique_ptr<StaticData> m_data;
|
||||
};
|
||||
|
||||
|
||||
struct MetadataFileTest
|
||||
: public AssetProcessorManagerTest
|
||||
@@ -274,9 +355,3 @@ struct DuplicateProductsTest
|
||||
{
|
||||
void SetupDuplicateProductsTest(QString& sourceFile, QDir& tempPath, QString& productFile, AZStd::vector<AssetProcessor::JobDetails>& jobDetails, AssetBuilderSDK::ProcessJobResponse& response, bool multipleOutputs, QString extension);
|
||||
};
|
||||
|
||||
struct DeleteTest
|
||||
: public ModtimeScanningTest
|
||||
{
|
||||
void SetUp() override;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,706 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <native/tests/assetmanager/ModtimeScanningTests.h>
|
||||
#include <native/tests/assetmanager/AssetProcessorManagerTest.h>
|
||||
#include <QObject>
|
||||
#include <ToolsFileUtils/ToolsFileUtils.h>
|
||||
|
||||
namespace UnitTests
|
||||
{
|
||||
using AssetFileInfo = AssetProcessor::AssetFileInfo;
|
||||
|
||||
void ModtimeScanningTest::SetUpAssetProcessorManager()
|
||||
{
|
||||
using namespace AssetProcessor;
|
||||
|
||||
m_assetProcessorManager->SetEnableModtimeSkippingFeature(true);
|
||||
m_assetProcessorManager->RecomputeDirtyBuilders();
|
||||
|
||||
QObject::connect(
|
||||
m_assetProcessorManager.get(), &AssetProcessorManager::AssetToProcess,
|
||||
[this](JobDetails details)
|
||||
{
|
||||
m_data->m_processResults.push_back(AZStd::move(details));
|
||||
});
|
||||
|
||||
QObject::connect(
|
||||
m_assetProcessorManager.get(), &AssetProcessorManager::SourceDeleted,
|
||||
[this](QString file)
|
||||
{
|
||||
m_data->m_deletedSources.push_back(file);
|
||||
});
|
||||
|
||||
m_idleConnection = QObject::connect(
|
||||
m_assetProcessorManager.get(), &AssetProcessor::AssetProcessorManager::AssetProcessorManagerIdleState,
|
||||
[this](bool newState)
|
||||
{
|
||||
m_isIdling = newState;
|
||||
});
|
||||
}
|
||||
|
||||
void ModtimeScanningTest::SetUp()
|
||||
{
|
||||
using namespace AssetProcessor;
|
||||
|
||||
AssetProcessorManagerTest::SetUp();
|
||||
|
||||
m_data = AZStd::make_unique<StaticData>();
|
||||
|
||||
// We don't want the mock application manager to provide builder descriptors, mockBuilderInfoHandler will provide our own
|
||||
m_mockApplicationManager->BusDisconnect();
|
||||
|
||||
m_data->m_mockBuilderInfoHandler.m_builderDesc = m_data->m_mockBuilderInfoHandler.CreateBuilderDesc(
|
||||
"test builder", "{DF09DDC0-FD22-43B6-9E22-22C8574A6E1E}",
|
||||
{ AssetBuilderSDK::AssetBuilderPattern("*.txt", AssetBuilderSDK::AssetBuilderPattern::Wildcard) });
|
||||
m_data->m_mockBuilderInfoHandler.BusConnect();
|
||||
|
||||
ASSERT_TRUE(m_mockApplicationManager->GetBuilderByID("txt files", m_data->m_builderTxtBuilder));
|
||||
|
||||
SetUpAssetProcessorManager();
|
||||
|
||||
// Create the test file
|
||||
const auto& scanFolder = m_config->GetScanFolderAt(0);
|
||||
m_data->m_relativePathFromWatchFolder[0] = "modtimeTestFile.txt";
|
||||
m_data->m_absolutePath.push_back(QDir(scanFolder.ScanPath()).absoluteFilePath(m_data->m_relativePathFromWatchFolder[0]));
|
||||
|
||||
m_data->m_relativePathFromWatchFolder[1] = "modtimeTestDependency.txt";
|
||||
m_data->m_absolutePath.push_back(QDir(scanFolder.ScanPath()).absoluteFilePath(m_data->m_relativePathFromWatchFolder[1]));
|
||||
|
||||
m_data->m_relativePathFromWatchFolder[2] = "modtimeTestDependency.txt.assetinfo";
|
||||
m_data->m_absolutePath.push_back(QDir(scanFolder.ScanPath()).absoluteFilePath(m_data->m_relativePathFromWatchFolder[2]));
|
||||
|
||||
for (const auto& path : m_data->m_absolutePath)
|
||||
{
|
||||
ASSERT_TRUE(UnitTestUtils::CreateDummyFile(path, ""));
|
||||
}
|
||||
|
||||
m_data->m_mockBuilderInfoHandler.m_dependencyFilePath = m_data->m_absolutePath[1].toUtf8().data();
|
||||
|
||||
// Add file to database with no modtime
|
||||
{
|
||||
AssetDatabaseConnection connection;
|
||||
ASSERT_TRUE(connection.OpenDatabase());
|
||||
AzToolsFramework::AssetDatabase::FileDatabaseEntry fileEntry;
|
||||
fileEntry.m_fileName = m_data->m_relativePathFromWatchFolder[0].toUtf8().data();
|
||||
fileEntry.m_modTime = 0;
|
||||
fileEntry.m_isFolder = false;
|
||||
fileEntry.m_scanFolderPK = scanFolder.ScanFolderID();
|
||||
|
||||
bool entryAlreadyExists;
|
||||
ASSERT_TRUE(connection.InsertFile(fileEntry, entryAlreadyExists));
|
||||
ASSERT_FALSE(entryAlreadyExists);
|
||||
|
||||
fileEntry.m_fileID = AzToolsFramework::AssetDatabase::InvalidEntryId; // Reset the id so we make a new entry
|
||||
fileEntry.m_fileName = m_data->m_relativePathFromWatchFolder[1].toUtf8().data();
|
||||
ASSERT_TRUE(connection.InsertFile(fileEntry, entryAlreadyExists));
|
||||
ASSERT_FALSE(entryAlreadyExists);
|
||||
|
||||
fileEntry.m_fileID = AzToolsFramework::AssetDatabase::InvalidEntryId; // Reset the id so we make a new entry
|
||||
fileEntry.m_fileName = m_data->m_relativePathFromWatchFolder[2].toUtf8().data();
|
||||
ASSERT_TRUE(connection.InsertFile(fileEntry, entryAlreadyExists));
|
||||
ASSERT_FALSE(entryAlreadyExists);
|
||||
}
|
||||
|
||||
QSet<AssetFileInfo> filePaths = BuildFileSet();
|
||||
SimulateAssetScanner(filePaths);
|
||||
|
||||
ASSERT_TRUE(BlockUntilIdle(5000));
|
||||
ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 2);
|
||||
ASSERT_EQ(m_data->m_processResults.size(), 2);
|
||||
ASSERT_EQ(m_data->m_deletedSources.size(), 0);
|
||||
|
||||
ProcessAssetJobs();
|
||||
|
||||
m_data->m_processResults.clear();
|
||||
m_data->m_mockBuilderInfoHandler.m_createJobsCount = 0;
|
||||
|
||||
m_isIdling = false;
|
||||
}
|
||||
|
||||
void ModtimeScanningTest::TearDown()
|
||||
{
|
||||
m_data = nullptr;
|
||||
|
||||
AssetProcessorManagerTest::TearDown();
|
||||
}
|
||||
|
||||
void ModtimeScanningTest::ProcessAssetJobs()
|
||||
{
|
||||
m_data->m_productPaths.clear();
|
||||
|
||||
for (const auto& processResult : m_data->m_processResults)
|
||||
{
|
||||
auto file =
|
||||
QDir(processResult.m_destinationPath).absoluteFilePath(processResult.m_jobEntry.m_databaseSourceName.toLower() + ".arc1");
|
||||
m_data->m_productPaths.emplace(
|
||||
QDir(processResult.m_jobEntry.m_watchFolderPath)
|
||||
.absoluteFilePath(processResult.m_jobEntry.m_databaseSourceName)
|
||||
.toUtf8()
|
||||
.constData(),
|
||||
file);
|
||||
|
||||
// Create the file on disk
|
||||
ASSERT_TRUE(UnitTestUtils::CreateDummyFile(file, "products."));
|
||||
|
||||
AssetBuilderSDK::ProcessJobResponse response;
|
||||
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
|
||||
response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(file.toUtf8().constData(), AZ::Uuid::CreateNull(), 1));
|
||||
|
||||
using JobEntry = AssetProcessor::JobEntry;
|
||||
|
||||
QMetaObject::invokeMethod(
|
||||
m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, processResult.m_jobEntry),
|
||||
Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
|
||||
}
|
||||
|
||||
ASSERT_TRUE(BlockUntilIdle(5000));
|
||||
|
||||
m_isIdling = false;
|
||||
}
|
||||
|
||||
void ModtimeScanningTest::SimulateAssetScanner(QSet<AssetProcessor::AssetFileInfo> filePaths)
|
||||
{
|
||||
QMetaObject::invokeMethod(
|
||||
m_assetProcessorManager.get(), "OnAssetScannerStatusChange", Qt::QueuedConnection,
|
||||
Q_ARG(AssetProcessor::AssetScanningStatus, AssetProcessor::AssetScanningStatus::Started));
|
||||
QMetaObject::invokeMethod(
|
||||
m_assetProcessorManager.get(), "AssessFilesFromScanner", Qt::QueuedConnection, Q_ARG(QSet<AssetFileInfo>, filePaths));
|
||||
QMetaObject::invokeMethod(
|
||||
m_assetProcessorManager.get(), "OnAssetScannerStatusChange", Qt::QueuedConnection,
|
||||
Q_ARG(AssetProcessor::AssetScanningStatus, AssetProcessor::AssetScanningStatus::Completed));
|
||||
}
|
||||
|
||||
QSet<AssetProcessor::AssetFileInfo> ModtimeScanningTest::BuildFileSet()
|
||||
{
|
||||
QSet<AssetFileInfo> filePaths;
|
||||
|
||||
for (const auto& path : m_data->m_absolutePath)
|
||||
{
|
||||
QFileInfo fileInfo(path);
|
||||
auto modtime = fileInfo.lastModified();
|
||||
AZ::u64 fileSize = fileInfo.size();
|
||||
filePaths.insert(AssetFileInfo(path, modtime, fileSize, m_config->GetScanFolderForFile(path), false));
|
||||
}
|
||||
|
||||
return filePaths;
|
||||
}
|
||||
|
||||
void ModtimeScanningTest::ExpectWork(int createJobs, int processJobs)
|
||||
{
|
||||
ASSERT_TRUE(BlockUntilIdle(5000));
|
||||
|
||||
EXPECT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, createJobs);
|
||||
EXPECT_EQ(m_data->m_processResults.size(), processJobs);
|
||||
for (int i = 0; i < processJobs; ++i)
|
||||
{
|
||||
EXPECT_FALSE(m_data->m_processResults[i].m_autoFail);
|
||||
}
|
||||
EXPECT_EQ(m_data->m_deletedSources.size(), 0);
|
||||
|
||||
m_isIdling = false;
|
||||
}
|
||||
|
||||
void ModtimeScanningTest::ExpectNoWork()
|
||||
{
|
||||
// Since there's no work to do, the idle event isn't going to trigger, just process events a couple times
|
||||
for (int i = 0; i < 10; ++i)
|
||||
{
|
||||
QCoreApplication::processEvents(QEventLoop::AllEvents, 10);
|
||||
}
|
||||
|
||||
ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 0);
|
||||
ASSERT_EQ(m_data->m_processResults.size(), 0);
|
||||
ASSERT_EQ(m_data->m_deletedSources.size(), 0);
|
||||
|
||||
m_isIdling = false;
|
||||
}
|
||||
|
||||
void ModtimeScanningTest::SetFileContents(QString filePath, QString contents)
|
||||
{
|
||||
QFile file(filePath);
|
||||
file.open(QIODevice::WriteOnly | QIODevice::Truncate);
|
||||
file.write(contents.toUtf8().constData());
|
||||
file.close();
|
||||
}
|
||||
|
||||
TEST_F(ModtimeScanningTest, ModtimeSkipping_FileUnchanged_WithoutModtimeSkipping)
|
||||
{
|
||||
using namespace AzToolsFramework::AssetSystem;
|
||||
|
||||
// Make sure modtime skipping is disabled
|
||||
// We're just going to do 1 quick sanity test to make sure the files are still processed when modtime skipping is turned off
|
||||
m_assetProcessorManager->SetEnableModtimeSkippingFeature(false);
|
||||
|
||||
QSet<AssetProcessor::AssetFileInfo> filePaths = BuildFileSet();
|
||||
SimulateAssetScanner(filePaths);
|
||||
|
||||
// 2 create jobs but 0 process jobs because the file has already been processed before in SetUp
|
||||
ExpectWork(2, 0);
|
||||
}
|
||||
|
||||
TEST_F(ModtimeScanningTest, ModtimeSkipping_FileUnchanged)
|
||||
{
|
||||
using namespace AzToolsFramework::AssetSystem;
|
||||
|
||||
AssetUtilities::SetUseFileHashOverride(true, true);
|
||||
|
||||
QSet<AssetFileInfo> filePaths = BuildFileSet();
|
||||
SimulateAssetScanner(filePaths);
|
||||
|
||||
ExpectNoWork();
|
||||
}
|
||||
|
||||
TEST_F(ModtimeScanningTest, ModtimeSkipping_EnablePlatform_ShouldProcessFilesForPlatform)
|
||||
{
|
||||
using namespace AzToolsFramework::AssetSystem;
|
||||
|
||||
AssetUtilities::SetUseFileHashOverride(true, true);
|
||||
|
||||
// Enable android platform after the initial SetUp has already processed the files for pc
|
||||
QDir tempPath(m_tempDir.path());
|
||||
AssetBuilderSDK::PlatformInfo androidPlatform("android", { "host", "renderer" });
|
||||
m_config->EnablePlatform(androidPlatform, true);
|
||||
|
||||
// There's no way to remove scanfolders and adding a new one after enabling the platform will cause the pc assets to build as well,
|
||||
// which we don't want Instead we'll just const cast the vector and modify the enabled platforms for the scanfolder
|
||||
auto& platforms = const_cast<AZStd::vector<AssetBuilderSDK::PlatformInfo>&>(m_config->GetScanFolderAt(0).GetPlatforms());
|
||||
platforms.push_back(androidPlatform);
|
||||
|
||||
// We need the builder fingerprints to be updated to reflect the newly enabled platform
|
||||
m_assetProcessorManager->ComputeBuilderDirty();
|
||||
|
||||
QSet<AssetFileInfo> filePaths = BuildFileSet();
|
||||
SimulateAssetScanner(filePaths);
|
||||
|
||||
ExpectWork(
|
||||
4, 2); // CreateJobs = 4, 2 files * 2 platforms. ProcessJobs = 2, just the android platform jobs (pc is already processed)
|
||||
|
||||
ASSERT_TRUE(m_data->m_processResults[0].m_destinationPath.contains("android"));
|
||||
ASSERT_TRUE(m_data->m_processResults[1].m_destinationPath.contains("android"));
|
||||
}
|
||||
|
||||
TEST_F(ModtimeScanningTest, ModtimeSkipping_ModifyTimestamp)
|
||||
{
|
||||
// Update the timestamp on a file without changing its contents
|
||||
// This should not cause any job to run since the hash of the file is the same before/after
|
||||
// Additionally, the timestamp stored in the database should be updated
|
||||
using namespace AzToolsFramework::AssetSystem;
|
||||
|
||||
uint64_t timestamp = 1594923423;
|
||||
|
||||
QString databaseName, scanfolderName;
|
||||
m_config->ConvertToRelativePath(m_data->m_absolutePath[1], databaseName, scanfolderName);
|
||||
auto* scanFolder = m_config->GetScanFolderForFile(m_data->m_absolutePath[1]);
|
||||
|
||||
AzToolsFramework::AssetDatabase::FileDatabaseEntry fileEntry;
|
||||
|
||||
m_assetProcessorManager->m_stateData->GetFileByFileNameAndScanFolderId(databaseName, scanFolder->ScanFolderID(), fileEntry);
|
||||
|
||||
ASSERT_NE(fileEntry.m_modTime, timestamp);
|
||||
uint64_t existingTimestamp = fileEntry.m_modTime;
|
||||
|
||||
// Modify the timestamp on just one file
|
||||
AzToolsFramework::ToolsFileUtils::SetModificationTime(m_data->m_absolutePath[1].toUtf8().data(), timestamp);
|
||||
|
||||
AssetUtilities::SetUseFileHashOverride(true, true);
|
||||
|
||||
QSet<AssetFileInfo> filePaths = BuildFileSet();
|
||||
SimulateAssetScanner(filePaths);
|
||||
|
||||
ExpectNoWork();
|
||||
|
||||
m_assetProcessorManager->m_stateData->GetFileByFileNameAndScanFolderId(databaseName, scanFolder->ScanFolderID(), fileEntry);
|
||||
|
||||
// The timestamp should be updated even though nothing processed
|
||||
ASSERT_NE(fileEntry.m_modTime, existingTimestamp);
|
||||
}
|
||||
|
||||
TEST_F(ModtimeScanningTest, ModtimeSkipping_ModifyTimestampNoHashing_ProcessesFile)
|
||||
{
|
||||
// Update the timestamp on a file without changing its contents
|
||||
// This should not cause any job to run since the hash of the file is the same before/after
|
||||
// Additionally, the timestamp stored in the database should be updated
|
||||
using namespace AzToolsFramework::AssetSystem;
|
||||
|
||||
uint64_t timestamp = 1594923423;
|
||||
|
||||
// Modify the timestamp on just one file
|
||||
AzToolsFramework::ToolsFileUtils::SetModificationTime(m_data->m_absolutePath[1].toUtf8().data(), timestamp);
|
||||
|
||||
AssetUtilities::SetUseFileHashOverride(true, false);
|
||||
|
||||
QSet<AssetFileInfo> filePaths = BuildFileSet();
|
||||
SimulateAssetScanner(filePaths);
|
||||
|
||||
ExpectWork(2, 2);
|
||||
}
|
||||
|
||||
TEST_F(ModtimeScanningTest, ModtimeSkipping_ModifyFile)
|
||||
{
|
||||
using namespace AzToolsFramework::AssetSystem;
|
||||
|
||||
SetFileContents(m_data->m_absolutePath[1].toUtf8().constData(), "hello world");
|
||||
|
||||
AssetUtilities::SetUseFileHashOverride(true, true);
|
||||
|
||||
QSet<AssetFileInfo> filePaths = BuildFileSet();
|
||||
SimulateAssetScanner(filePaths);
|
||||
|
||||
// Even though we're only updating one file, we're expecting 2 createJob calls because our test file is a dependency that triggers
|
||||
// the other test file to process as well
|
||||
ExpectWork(2, 2);
|
||||
}
|
||||
|
||||
TEST_F(ModtimeScanningTest, ModtimeSkipping_ModifyFile_AndThenRevert_ProcessesAgain)
|
||||
{
|
||||
using namespace AzToolsFramework::AssetSystem;
|
||||
auto theFile = m_data->m_absolutePath[1].toUtf8();
|
||||
const char* theFileString = theFile.constData();
|
||||
|
||||
SetFileContents(theFileString, "hello world");
|
||||
|
||||
AssetUtilities::SetUseFileHashOverride(true, true);
|
||||
|
||||
QSet<AssetFileInfo> filePaths = BuildFileSet();
|
||||
SimulateAssetScanner(filePaths);
|
||||
|
||||
// Even though we're only updating one file, we're expecting 2 createJob calls because our test file is a dependency that triggers
|
||||
// the other test file to process as well
|
||||
ExpectWork(2, 2);
|
||||
ProcessAssetJobs();
|
||||
|
||||
m_data->m_mockBuilderInfoHandler.m_createJobsCount = 0;
|
||||
m_data->m_processResults.clear();
|
||||
m_data->m_deletedSources.clear();
|
||||
|
||||
SetFileContents(theFileString, "");
|
||||
|
||||
filePaths = BuildFileSet();
|
||||
SimulateAssetScanner(filePaths);
|
||||
|
||||
// Expect processing to happen again
|
||||
ExpectWork(2, 2);
|
||||
}
|
||||
|
||||
TEST_F(ModtimeScanningTest, ModtimeSkipping_ModifyFilesSameHash_BothProcess)
|
||||
{
|
||||
using namespace AzToolsFramework::AssetSystem;
|
||||
|
||||
SetFileContents(m_data->m_absolutePath[1].toUtf8().constData(), "hello world");
|
||||
|
||||
AssetUtilities::SetUseFileHashOverride(true, true);
|
||||
|
||||
QSet<AssetFileInfo> filePaths = BuildFileSet();
|
||||
SimulateAssetScanner(filePaths);
|
||||
|
||||
// Even though we're only updating one file, we're expecting 2 createJob calls because our test file is a dependency that triggers
|
||||
// the other test file to process as well
|
||||
ExpectWork(2, 2);
|
||||
ProcessAssetJobs();
|
||||
|
||||
m_data->m_mockBuilderInfoHandler.m_createJobsCount = 0;
|
||||
m_data->m_processResults.clear();
|
||||
m_data->m_deletedSources.clear();
|
||||
|
||||
// Make file 0 have the same contents as file 1
|
||||
SetFileContents(m_data->m_absolutePath[0].toUtf8().constData(), "hello world");
|
||||
|
||||
filePaths = BuildFileSet();
|
||||
SimulateAssetScanner(filePaths);
|
||||
|
||||
ExpectWork(1, 1);
|
||||
}
|
||||
|
||||
TEST_F(ModtimeScanningTest, ModtimeSkipping_ModifyMetadataFile)
|
||||
{
|
||||
using namespace AzToolsFramework::AssetSystem;
|
||||
|
||||
SetFileContents(m_data->m_absolutePath[2].toUtf8().constData(), "hello world");
|
||||
|
||||
AssetUtilities::SetUseFileHashOverride(true, true);
|
||||
|
||||
QSet<AssetFileInfo> filePaths = BuildFileSet();
|
||||
SimulateAssetScanner(filePaths);
|
||||
|
||||
// Even though we're only updating one file, we're expecting 2 createJob calls because our test file is a metadata file
|
||||
// that triggers the source file which is a dependency that triggers the other test file to process as well
|
||||
ExpectWork(2, 2);
|
||||
}
|
||||
|
||||
TEST_F(ModtimeScanningTest, ModtimeSkipping_DeleteFile)
|
||||
{
|
||||
using namespace AzToolsFramework::AssetSystem;
|
||||
|
||||
AssetUtilities::SetUseFileHashOverride(true, true);
|
||||
|
||||
ASSERT_TRUE(QFile::remove(m_data->m_absolutePath[0]));
|
||||
|
||||
// Feed in ONLY one file (the one we didn't delete)
|
||||
QSet<AssetFileInfo> filePaths;
|
||||
QFileInfo fileInfo(m_data->m_absolutePath[1]);
|
||||
auto modtime = fileInfo.lastModified();
|
||||
AZ::u64 fileSize = fileInfo.size();
|
||||
filePaths.insert(AssetFileInfo(m_data->m_absolutePath[1], modtime, fileSize, &m_config->GetScanFolderAt(0), false));
|
||||
|
||||
SimulateAssetScanner(filePaths);
|
||||
|
||||
QElapsedTimer timer;
|
||||
timer.start();
|
||||
|
||||
do
|
||||
{
|
||||
QCoreApplication::processEvents(QEventLoop::AllEvents, 10);
|
||||
} while (m_data->m_deletedSources.size() < m_data->m_relativePathFromWatchFolder[0].size() && timer.elapsed() < 5000);
|
||||
|
||||
ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 0);
|
||||
ASSERT_EQ(m_data->m_processResults.size(), 0);
|
||||
ASSERT_THAT(m_data->m_deletedSources, testing::ElementsAre(m_data->m_relativePathFromWatchFolder[0]));
|
||||
}
|
||||
|
||||
TEST_F(ModtimeScanningTest, ReprocessRequest_FileNotModified_FileProcessed)
|
||||
{
|
||||
using namespace AzToolsFramework::AssetSystem;
|
||||
|
||||
m_assetProcessorManager->RequestReprocess(m_data->m_absolutePath[0]);
|
||||
|
||||
ASSERT_TRUE(BlockUntilIdle(5000));
|
||||
|
||||
ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 1);
|
||||
ASSERT_EQ(m_data->m_processResults.size(), 1);
|
||||
}
|
||||
|
||||
TEST_F(ModtimeScanningTest, ReprocessRequest_SourceWithDependency_BothWillProcess)
|
||||
{
|
||||
using namespace AzToolsFramework::AssetSystem;
|
||||
|
||||
using SourceFileDependencyEntry = AzToolsFramework::AssetDatabase::SourceFileDependencyEntry;
|
||||
|
||||
SourceFileDependencyEntry newEntry1;
|
||||
newEntry1.m_sourceDependencyID = AzToolsFramework::AssetDatabase::InvalidEntryId;
|
||||
newEntry1.m_builderGuid = AZ::Uuid::CreateRandom();
|
||||
newEntry1.m_source = m_data->m_absolutePath[0].toUtf8().constData();
|
||||
newEntry1.m_dependsOnSource = m_data->m_absolutePath[1].toUtf8().constData();
|
||||
newEntry1.m_typeOfDependency = SourceFileDependencyEntry::DEP_SourceToSource;
|
||||
|
||||
m_assetProcessorManager->RequestReprocess(m_data->m_absolutePath[0]);
|
||||
ASSERT_TRUE(BlockUntilIdle(5000));
|
||||
|
||||
ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 1);
|
||||
ASSERT_EQ(m_data->m_processResults.size(), 1);
|
||||
|
||||
m_assetProcessorManager->RequestReprocess(m_data->m_absolutePath[1]);
|
||||
ASSERT_TRUE(BlockUntilIdle(5000));
|
||||
|
||||
ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 3);
|
||||
ASSERT_EQ(m_data->m_processResults.size(), 3);
|
||||
}
|
||||
|
||||
TEST_F(ModtimeScanningTest, ReprocessRequest_RequestFolder_SourceAssetsWillProcess)
|
||||
{
|
||||
using namespace AzToolsFramework::AssetSystem;
|
||||
|
||||
const auto& scanFolder = m_config->GetScanFolderAt(0);
|
||||
|
||||
QString scanPath = scanFolder.ScanPath();
|
||||
m_assetProcessorManager->RequestReprocess(scanPath);
|
||||
ASSERT_TRUE(BlockUntilIdle(5000));
|
||||
|
||||
// two text files are source assets, assetinfo is not
|
||||
ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 2);
|
||||
ASSERT_EQ(m_data->m_processResults.size(), 2);
|
||||
}
|
||||
|
||||
void DeleteTest::SetUp()
|
||||
{
|
||||
AssetProcessorManagerTest::SetUp();
|
||||
|
||||
m_data = AZStd::make_unique<StaticData>();
|
||||
|
||||
// We don't want the mock application manager to provide builder descriptors, mockBuilderInfoHandler will provide our own
|
||||
m_mockApplicationManager->BusDisconnect();
|
||||
|
||||
m_data->m_mockBuilderInfoHandler.m_builderDesc = m_data->m_mockBuilderInfoHandler.CreateBuilderDesc(
|
||||
"test builder", "{DF09DDC0-FD22-43B6-9E22-22C8574A6E1E}",
|
||||
{ AssetBuilderSDK::AssetBuilderPattern("*.txt", AssetBuilderSDK::AssetBuilderPattern::Wildcard) });
|
||||
m_data->m_mockBuilderInfoHandler.BusConnect();
|
||||
|
||||
ASSERT_TRUE(m_mockApplicationManager->GetBuilderByID("txt files", m_data->m_builderTxtBuilder));
|
||||
|
||||
SetUpAssetProcessorManager();
|
||||
|
||||
auto createFileAndAddToDatabaseFunc = [this](const AssetProcessor::ScanFolderInfo* scanFolder, QString file)
|
||||
{
|
||||
using namespace AzToolsFramework::AssetDatabase;
|
||||
|
||||
QString watchFolderPath = scanFolder->ScanPath();
|
||||
QString absPath(QDir(watchFolderPath).absoluteFilePath(file));
|
||||
UnitTestUtils::CreateDummyFile(absPath);
|
||||
|
||||
m_data->m_absolutePath.push_back(absPath);
|
||||
|
||||
AzToolsFramework::AssetDatabase::FileDatabaseEntry fileEntry;
|
||||
fileEntry.m_fileName = file.toUtf8().constData();
|
||||
fileEntry.m_modTime = 0;
|
||||
fileEntry.m_isFolder = false;
|
||||
fileEntry.m_scanFolderPK = scanFolder->ScanFolderID();
|
||||
|
||||
bool entryAlreadyExists;
|
||||
ASSERT_TRUE(m_assetProcessorManager->m_stateData->InsertFile(fileEntry, entryAlreadyExists));
|
||||
ASSERT_FALSE(entryAlreadyExists);
|
||||
};
|
||||
|
||||
// Create test files
|
||||
QDir tempPath(m_tempDir.path());
|
||||
const auto* scanFolder1 = m_config->GetScanFolderByPath(tempPath.absoluteFilePath("subfolder1"));
|
||||
const auto* scanFolder4 = m_config->GetScanFolderByPath(tempPath.absoluteFilePath("subfolder4"));
|
||||
|
||||
createFileAndAddToDatabaseFunc(scanFolder1, QString("textures/a.txt"));
|
||||
createFileAndAddToDatabaseFunc(scanFolder4, QString("textures/b.txt"));
|
||||
|
||||
// Run the test files through AP all the way to processing stage
|
||||
QSet<AssetFileInfo> filePaths = BuildFileSet();
|
||||
SimulateAssetScanner(filePaths);
|
||||
|
||||
ASSERT_TRUE(BlockUntilIdle(5000));
|
||||
ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 2);
|
||||
ASSERT_EQ(m_data->m_processResults.size(), 2);
|
||||
ASSERT_EQ(m_data->m_deletedSources.size(), 0);
|
||||
|
||||
ProcessAssetJobs();
|
||||
|
||||
m_data->m_processResults.clear();
|
||||
m_data->m_mockBuilderInfoHandler.m_createJobsCount = 0;
|
||||
|
||||
// Reboot the APM since we added stuff to the database that needs to be loaded on-startup of the APM
|
||||
m_assetProcessorManager.reset(new AssetProcessorManager_Test(m_config.get()));
|
||||
|
||||
SetUpAssetProcessorManager();
|
||||
}
|
||||
|
||||
TEST_F(DeleteTest, DeleteFolderSharedAcrossTwoScanFolders_CorrectFileAndFolderAreDeletedFromCache)
|
||||
{
|
||||
// There was a bug where AP wasn't repopulating the "known folders" list when modtime skipping was enabled and no work was needed
|
||||
// As a result, deleting a folder didn't count as a "folder", so the wrong code path was taken. This test makes sure the correct
|
||||
// deletion events fire
|
||||
|
||||
using namespace AzToolsFramework::AssetSystem;
|
||||
|
||||
// Feed in the files from the asset scanner, no jobs should run since they're already up-to-date
|
||||
QSet<AssetFileInfo> filePaths = BuildFileSet();
|
||||
SimulateAssetScanner(filePaths);
|
||||
|
||||
ExpectNoWork();
|
||||
|
||||
// Delete one of the folders
|
||||
QDir tempPath(m_tempDir.path());
|
||||
QString absPath(tempPath.absoluteFilePath("subfolder1/textures"));
|
||||
QDir(absPath).removeRecursively();
|
||||
|
||||
AZStd::vector<AZStd::string> deletedFolders;
|
||||
QObject::connect(
|
||||
m_assetProcessorManager.get(), &AssetProcessor::AssetProcessorManager::SourceFolderDeleted,
|
||||
[&deletedFolders](QString file)
|
||||
{
|
||||
deletedFolders.push_back(file.toUtf8().constData());
|
||||
});
|
||||
|
||||
m_assetProcessorManager->AssessDeletedFile(absPath);
|
||||
ASSERT_TRUE(BlockUntilIdle(5000));
|
||||
|
||||
ASSERT_THAT(m_data->m_deletedSources, testing::UnorderedElementsAre("textures/a.txt"));
|
||||
ASSERT_THAT(deletedFolders, testing::UnorderedElementsAre("textures"));
|
||||
}
|
||||
|
||||
TEST_F(LockedFileTest, DeleteFile_LockedProduct_DeleteFails)
|
||||
{
|
||||
auto theFile = m_data->m_absolutePath[1].toUtf8();
|
||||
const char* theFileString = theFile.constData();
|
||||
auto [sourcePath, productPath] = *m_data->m_productPaths.find(theFileString);
|
||||
|
||||
{
|
||||
QFile file(theFileString);
|
||||
file.remove();
|
||||
}
|
||||
|
||||
ASSERT_GT(m_data->m_productPaths.size(), 0);
|
||||
QFile product(productPath);
|
||||
|
||||
ASSERT_TRUE(product.open(QIODevice::ReadOnly));
|
||||
|
||||
// Check if we can delete the file now, if we can't, proceed with the test
|
||||
// If we can, it means the OS running this test doesn't lock open files so there's nothing to test
|
||||
if (!AZ::IO::SystemFile::Delete(productPath.toUtf8().constData()))
|
||||
{
|
||||
QMetaObject::invokeMethod(
|
||||
m_assetProcessorManager.get(), "AssessDeletedFile", Qt::QueuedConnection, Q_ARG(QString, QString(theFileString)));
|
||||
|
||||
EXPECT_TRUE(BlockUntilIdle(5000));
|
||||
|
||||
EXPECT_TRUE(QFile::exists(productPath));
|
||||
EXPECT_EQ(m_data->m_deletedSources.size(), 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
SUCCEED() << "Skipping test. OS does not lock open files.";
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(LockedFileTest, DeleteFile_LockedProduct_DeletesWhenReleased)
|
||||
{
|
||||
// This test is intended to verify the AP will successfully retry deleting a source asset
|
||||
// when one of its product assets is locked temporarily
|
||||
// We'll lock the file by holding it open
|
||||
|
||||
auto theFile = m_data->m_absolutePath[1].toUtf8();
|
||||
const char* theFileString = theFile.constData();
|
||||
auto [sourcePath, productPath] = *m_data->m_productPaths.find(theFileString);
|
||||
|
||||
{
|
||||
QFile file(theFileString);
|
||||
file.remove();
|
||||
}
|
||||
|
||||
ASSERT_GT(m_data->m_productPaths.size(), 0);
|
||||
QFile product(productPath);
|
||||
|
||||
// Open the file and keep it open to lock it
|
||||
// We'll start a thread later to unlock the file
|
||||
// This will allow us to test how AP handles trying to delete a locked file
|
||||
ASSERT_TRUE(product.open(QIODevice::ReadOnly));
|
||||
|
||||
// Check if we can delete the file now, if we can't, proceed with the test
|
||||
// If we can, it means the OS running this test doesn't lock open files so there's nothing to test
|
||||
if (!AZ::IO::SystemFile::Delete(productPath.toUtf8().constData()))
|
||||
{
|
||||
m_deleteCounter = 0;
|
||||
|
||||
// Set up a callback which will fire after at least 1 retry
|
||||
// Unlock the file at that point so AP can successfully delete it
|
||||
m_callback = [&product]()
|
||||
{
|
||||
product.close();
|
||||
};
|
||||
|
||||
QMetaObject::invokeMethod(
|
||||
m_assetProcessorManager.get(), "AssessDeletedFile", Qt::QueuedConnection, Q_ARG(QString, QString(theFileString)));
|
||||
|
||||
EXPECT_TRUE(BlockUntilIdle(5000));
|
||||
|
||||
EXPECT_FALSE(QFile::exists(productPath));
|
||||
EXPECT_EQ(m_data->m_deletedSources.size(), 1);
|
||||
|
||||
EXPECT_GT(m_deleteCounter, 1); // Make sure the AP tried more than once to delete the file
|
||||
m_errorAbsorber->ExpectAsserts(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
SUCCEED() << "Skipping test. OS does not lock open files.";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <tests/assetmanager/AssetProcessorManagerTest.h>
|
||||
|
||||
namespace UnitTests
|
||||
{
|
||||
struct ModtimeScanningTest : AssetProcessorManagerTest
|
||||
{
|
||||
void SetUpAssetProcessorManager();
|
||||
void SetUp() override;
|
||||
void TearDown() override;
|
||||
|
||||
void ProcessAssetJobs();
|
||||
void SimulateAssetScanner(QSet<AssetProcessor::AssetFileInfo> filePaths);
|
||||
QSet<AssetProcessor::AssetFileInfo> BuildFileSet();
|
||||
void ExpectWork(int createJobs, int processJobs);
|
||||
void ExpectNoWork();
|
||||
void SetFileContents(QString filePath, QString contents);
|
||||
|
||||
struct StaticData
|
||||
{
|
||||
QString m_relativePathFromWatchFolder[3];
|
||||
AZStd::vector<QString> m_absolutePath;
|
||||
AZStd::vector<AssetProcessor::JobDetails> m_processResults;
|
||||
AZStd::unordered_multimap<AZStd::string, QString> m_productPaths;
|
||||
AZStd::vector<QString> m_deletedSources;
|
||||
AZStd::shared_ptr<AssetProcessor::InternalMockBuilder> m_builderTxtBuilder;
|
||||
MockBuilderInfoHandler m_mockBuilderInfoHandler;
|
||||
};
|
||||
|
||||
AZStd::unique_ptr<StaticData> m_data;
|
||||
};
|
||||
|
||||
struct DeleteTest : ModtimeScanningTest
|
||||
{
|
||||
void SetUp() override;
|
||||
};
|
||||
|
||||
|
||||
struct LockedFileTest
|
||||
: ModtimeScanningTest
|
||||
, AssetProcessor::ConnectionBus::Handler
|
||||
{
|
||||
MOCK_METHOD3(SendRaw, size_t(unsigned, unsigned, const QByteArray&));
|
||||
MOCK_METHOD3(SendPerPlatform, size_t(unsigned, const AzFramework::AssetSystem::BaseAssetProcessorMessage&, const QString&));
|
||||
MOCK_METHOD4(SendRawPerPlatform, size_t(unsigned, unsigned, const QByteArray&, const QString&));
|
||||
MOCK_METHOD2(SendRequest, unsigned(const AzFramework::AssetSystem::BaseAssetProcessorMessage&, const ResponseCallback&));
|
||||
MOCK_METHOD2(SendResponse, size_t(unsigned, const AzFramework::AssetSystem::BaseAssetProcessorMessage&));
|
||||
MOCK_METHOD1(RemoveResponseHandler, void(unsigned));
|
||||
|
||||
size_t Send(unsigned, const AzFramework::AssetSystem::BaseAssetProcessorMessage& message) override
|
||||
{
|
||||
using SourceFileNotificationMessage = AzToolsFramework::AssetSystem::SourceFileNotificationMessage;
|
||||
switch (message.GetMessageType())
|
||||
{
|
||||
case SourceFileNotificationMessage::MessageType:
|
||||
if (const auto sourceFileMessage = azrtti_cast<const SourceFileNotificationMessage*>(&message);
|
||||
sourceFileMessage != nullptr &&
|
||||
sourceFileMessage->m_type == SourceFileNotificationMessage::NotificationType::FileRemoved)
|
||||
{
|
||||
// The File Remove message will occur before an attempt to delete the file
|
||||
// Wait for more than 1 File Remove message.
|
||||
// This indicates the AP has attempted to delete the file once, failed to do so and is now retrying
|
||||
++m_deleteCounter;
|
||||
|
||||
if (m_deleteCounter > 1 && m_callback)
|
||||
{
|
||||
m_callback();
|
||||
m_callback = {}; // Unset it to be safe, we only intend to run the callback once
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
ModtimeScanningTest::SetUp();
|
||||
|
||||
AssetProcessor::ConnectionBus::Handler::BusConnect(0);
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
AssetProcessor::ConnectionBus::Handler::BusDisconnect();
|
||||
|
||||
ModtimeScanningTest::TearDown();
|
||||
}
|
||||
|
||||
AZStd::atomic_int m_deleteCounter{ 0 };
|
||||
AZStd::function<void()> m_callback;
|
||||
};
|
||||
}
|
||||
@@ -500,6 +500,10 @@ QProgressBar::chunk {
|
||||
|
||||
/************** Gem Catalog **************/
|
||||
|
||||
#GemCatalogScreen {
|
||||
background-color: #333333;
|
||||
}
|
||||
|
||||
#GemCatalogTitle {
|
||||
font-size: 18px;
|
||||
}
|
||||
@@ -546,9 +550,8 @@ QProgressBar::chunk {
|
||||
min-height:24px;
|
||||
}
|
||||
|
||||
#GemCatalogHeaderLabel {
|
||||
font-size: 12px;
|
||||
color: #FFFFFF;
|
||||
#adjustableHeaderWidget QHeaderView::section {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
#GemCatalogHeaderShowCountLabel {
|
||||
@@ -732,15 +735,6 @@ QProgressBar::chunk {
|
||||
stop: 0 #555555, stop: 1.0 #777777);
|
||||
}
|
||||
|
||||
#gemRepoHeaderTable {
|
||||
background-color: transparent;
|
||||
max-height: 30px;
|
||||
}
|
||||
|
||||
#gemRepoListHeader {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
#gemRepoInspector {
|
||||
background: #444444;
|
||||
}
|
||||
@@ -774,4 +768,4 @@ QProgressBar::chunk {
|
||||
#gemRepoInspectorAddInfoTitleLabel {
|
||||
font-size: 16px;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AdjustableHeaderWidget.h>
|
||||
#include <AzCore/Debug/Trace.h>
|
||||
|
||||
#include <QHeaderView>
|
||||
#include <QTimer>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
AdjustableHeaderWidget::AdjustableHeaderWidget( const QStringList& headerLabels,
|
||||
const QVector<int>& defaultHeaderWidths, int minHeaderWidth,
|
||||
const QVector<QHeaderView::ResizeMode>& resizeModes, QWidget* parent)
|
||||
: QTableWidget(parent)
|
||||
{
|
||||
setObjectName("adjustableHeaderWidget");
|
||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
|
||||
setFixedHeight(s_headerWidgetHeight);
|
||||
|
||||
m_header = horizontalHeader();
|
||||
m_header->setDefaultAlignment(Qt::AlignLeft);
|
||||
|
||||
setColumnCount(headerLabels.count());
|
||||
setHorizontalHeaderLabels(headerLabels);
|
||||
|
||||
AZ_Assert(defaultHeaderWidths.count() == columnCount(), "Default header widths does not match number of columns");
|
||||
AZ_Assert(resizeModes.count() == columnCount(), "Resize modesdoes not match number of columns");
|
||||
|
||||
for (int column = 0; column < columnCount(); ++column)
|
||||
{
|
||||
m_header->resizeSection(column, defaultHeaderWidths[column]);
|
||||
m_header->setSectionResizeMode(column, resizeModes[column]);
|
||||
}
|
||||
|
||||
m_header->setMinimumSectionSize(minHeaderWidth);
|
||||
m_header->setCascadingSectionResizes(true);
|
||||
|
||||
connect(m_header, &QHeaderView::sectionResized, this, &AdjustableHeaderWidget::OnSectionResized);
|
||||
}
|
||||
|
||||
void AdjustableHeaderWidget::OnSectionResized(int logicalIndex, int oldSize, int newSize)
|
||||
{
|
||||
const int headerCount = columnCount();
|
||||
const int headerWidth = m_header->width();
|
||||
const int totalSectionWidth = m_header->length();
|
||||
|
||||
if (totalSectionWidth > headerWidth && newSize > oldSize)
|
||||
{
|
||||
int xPos = 0;
|
||||
int requiredWidth = 0;
|
||||
|
||||
for (int i = 0; i < headerCount; i++)
|
||||
{
|
||||
if (i < logicalIndex)
|
||||
{
|
||||
xPos += m_header->sectionSize(i);
|
||||
}
|
||||
else if (i == logicalIndex)
|
||||
{
|
||||
xPos += newSize;
|
||||
}
|
||||
else if (i > logicalIndex)
|
||||
{
|
||||
if (m_header->sectionResizeMode(i) == QHeaderView::ResizeMode::Fixed)
|
||||
{
|
||||
requiredWidth += m_header->sectionSize(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
requiredWidth += m_header->minimumSectionSize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (xPos + requiredWidth > headerWidth)
|
||||
{
|
||||
m_header->resizeSection(logicalIndex, oldSize);
|
||||
}
|
||||
}
|
||||
|
||||
// wait till all columns resized
|
||||
QTimer::singleShot(0, [&]()
|
||||
{
|
||||
// only re-paint when the header and section widths have settled
|
||||
const int headerWidth = m_header->width();
|
||||
const int totalSectionWidth = m_header->length();
|
||||
if (totalSectionWidth == headerWidth)
|
||||
{
|
||||
emit sectionsResized();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
QPair<int, int> AdjustableHeaderWidget::CalcColumnXBounds(int headerIndex) const
|
||||
{
|
||||
// Total the widths of all headers before this one in first and including it in second
|
||||
QPair<int, int> bounds(0, 0);
|
||||
|
||||
for (int curIndex = 0; curIndex <= headerIndex; ++curIndex)
|
||||
{
|
||||
if (curIndex == headerIndex)
|
||||
{
|
||||
bounds.first = bounds.second;
|
||||
}
|
||||
bounds.second += m_header->sectionSize(curIndex);
|
||||
}
|
||||
|
||||
return bounds;
|
||||
}
|
||||
|
||||
|
||||
} // namespace O3DE::ProjectManager
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(Q_MOC_RUN)
|
||||
#include <QTableWidget>
|
||||
|
||||
#include <QHeaderView>
|
||||
#include <QStringList>
|
||||
#include <QVector>
|
||||
#include <QPair>
|
||||
#endif
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
// Using a QTableWidget for its header
|
||||
// Using a seperate model allows the setup of a header exactly as needed
|
||||
class AdjustableHeaderWidget
|
||||
: public QTableWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit AdjustableHeaderWidget(const QStringList& headerLabels,
|
||||
const QVector<int>& defaultHeaderWidths, int minHeaderWidth,
|
||||
const QVector<QHeaderView::ResizeMode>& resizeModes,
|
||||
QWidget* parent = nullptr);
|
||||
~AdjustableHeaderWidget() = default;
|
||||
|
||||
QPair<int, int> CalcColumnXBounds(int headerIndex) const;
|
||||
|
||||
inline constexpr static int s_headerTextIndent = 7;
|
||||
inline constexpr static int s_headerWidgetHeight = 24;
|
||||
|
||||
QHeaderView* m_header;
|
||||
|
||||
signals:
|
||||
void sectionsResized();
|
||||
|
||||
protected slots:
|
||||
void OnSectionResized(int logicalIndex, int oldSize, int newSize);
|
||||
|
||||
private:
|
||||
inline constexpr static int s_headerIndentSection = 11;
|
||||
};
|
||||
} // namespace O3DE::ProjectManager
|
||||
@@ -17,7 +17,7 @@ namespace O3DE::ProjectManager
|
||||
class ExternalLinkDialog
|
||||
: public QDialog
|
||||
{
|
||||
Q_OBJECT // AUTOMOC
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ExternalLinkDialog(const QUrl& url, QWidget* parent = nullptr);
|
||||
~ExternalLinkDialog() = default;
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace O3DE::ProjectManager
|
||||
class GemCartWidget
|
||||
: public QScrollArea
|
||||
{
|
||||
Q_OBJECT // AUTOMOC
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
GemCartWidget(GemModel* gemModel, DownloadController* downloadController, QWidget* parent = nullptr);
|
||||
|
||||
@@ -19,8 +19,10 @@
|
||||
#include <GemCatalog/GemDependenciesDialog.h>
|
||||
#include <GemCatalog/GemUpdateDialog.h>
|
||||
#include <GemCatalog/GemUninstallDialog.h>
|
||||
#include <GemCatalog/GemItemDelegate.h>
|
||||
#include <DownloadController.h>
|
||||
#include <ProjectUtils.h>
|
||||
#include <AdjustableHeaderWidget.h>
|
||||
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
@@ -40,6 +42,13 @@ namespace O3DE::ProjectManager
|
||||
GemCatalogScreen::GemCatalogScreen(QWidget* parent)
|
||||
: ScreenWidget(parent)
|
||||
{
|
||||
// The width of either side panel (filters, inspector) in the catalog
|
||||
constexpr int sidePanelWidth = 240;
|
||||
// Querying qApp about styling reports the scroll bar being larger than it is so define it manually
|
||||
constexpr int verticalScrollBarWidth = 8;
|
||||
|
||||
setObjectName("GemCatalogScreen");
|
||||
|
||||
m_gemModel = new GemModel(this);
|
||||
m_proxyModel = new GemSortFilterProxyModel(m_gemModel, this);
|
||||
|
||||
@@ -69,10 +78,8 @@ namespace O3DE::ProjectManager
|
||||
hLayout->setMargin(0);
|
||||
vLayout->addLayout(hLayout);
|
||||
|
||||
m_gemListView = new GemListView(m_proxyModel, m_proxyModel->GetSelectionModel(), this);
|
||||
|
||||
m_rightPanelStack = new QStackedWidget(this);
|
||||
m_rightPanelStack->setFixedWidth(240);
|
||||
m_rightPanelStack->setFixedWidth(sidePanelWidth);
|
||||
|
||||
m_gemInspector = new GemInspector(m_gemModel, this);
|
||||
|
||||
@@ -81,18 +88,47 @@ namespace O3DE::ProjectManager
|
||||
connect(m_gemInspector, &GemInspector::UninstallGem, this, &GemCatalogScreen::UninstallGem);
|
||||
|
||||
QWidget* filterWidget = new QWidget(this);
|
||||
filterWidget->setFixedWidth(240);
|
||||
filterWidget->setFixedWidth(sidePanelWidth);
|
||||
m_filterWidgetLayout = new QVBoxLayout();
|
||||
m_filterWidgetLayout->setMargin(0);
|
||||
m_filterWidgetLayout->setSpacing(0);
|
||||
filterWidget->setLayout(m_filterWidgetLayout);
|
||||
|
||||
GemListHeaderWidget* listHeaderWidget = new GemListHeaderWidget(m_proxyModel);
|
||||
GemListHeaderWidget* catalogHeaderWidget = new GemListHeaderWidget(m_proxyModel);
|
||||
|
||||
constexpr int minHeaderSectionWidth = 100;
|
||||
AdjustableHeaderWidget* listHeaderWidget = new AdjustableHeaderWidget(
|
||||
QStringList{ tr("Gem Image"), tr("Gem Name"), tr("Gem Summary"), tr("Status") },
|
||||
QVector<int>{
|
||||
GemPreviewImageWidth + AdjustableHeaderWidget::s_headerTextIndent,
|
||||
-GemPreviewImageWidth - AdjustableHeaderWidget::s_headerTextIndent + GemItemDelegate::s_defaultSummaryStartX - 30,
|
||||
0, // Section is set to stretch to fit
|
||||
GemItemDelegate::s_statusIconSize + GemItemDelegate::s_statusButtonSpacing + GemItemDelegate::s_buttonWidth + GemItemDelegate::s_contentMargins.right()
|
||||
},
|
||||
minHeaderSectionWidth,
|
||||
QVector<QHeaderView::ResizeMode>
|
||||
{
|
||||
QHeaderView::ResizeMode::Fixed,
|
||||
QHeaderView::ResizeMode::Interactive,
|
||||
QHeaderView::ResizeMode::Stretch,
|
||||
QHeaderView::ResizeMode::Fixed
|
||||
},
|
||||
this);
|
||||
|
||||
m_gemListView = new GemListView(m_proxyModel, m_proxyModel->GetSelectionModel(), listHeaderWidget, this);
|
||||
|
||||
QHBoxLayout* listHeaderLayout = new QHBoxLayout();
|
||||
listHeaderLayout->setMargin(0);
|
||||
listHeaderLayout->setSpacing(0);
|
||||
listHeaderLayout->addSpacing(GemItemDelegate::s_itemMargins.left());
|
||||
listHeaderLayout->addWidget(listHeaderWidget);
|
||||
listHeaderLayout->addSpacing(GemItemDelegate::s_itemMargins.right() + verticalScrollBarWidth);
|
||||
|
||||
QVBoxLayout* middleVLayout = new QVBoxLayout();
|
||||
middleVLayout->setMargin(0);
|
||||
middleVLayout->setSpacing(0);
|
||||
middleVLayout->addWidget(listHeaderWidget);
|
||||
middleVLayout->addWidget(catalogHeaderWidget);
|
||||
middleVLayout->addLayout(listHeaderLayout);
|
||||
middleVLayout->addWidget(m_gemListView);
|
||||
|
||||
hLayout->addWidget(filterWidget);
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace O3DE::ProjectManager
|
||||
class GemDependenciesDialog
|
||||
: public QDialog
|
||||
{
|
||||
Q_OBJECT // AUTOMOC
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit GemDependenciesDialog(GemModel* gemModel, QWidget *parent = nullptr);
|
||||
~GemDependenciesDialog() = default;
|
||||
|
||||
@@ -116,7 +116,7 @@ namespace O3DE::ProjectManager
|
||||
// Separating line
|
||||
QFrame* hLine = new QFrame();
|
||||
hLine->setFrameShape(QFrame::HLine);
|
||||
hLine->setStyleSheet("color: #666666;");
|
||||
hLine->setObjectName("horizontalSeparatingLine");
|
||||
vLayout->addWidget(hLine);
|
||||
|
||||
UpdateCollapseState();
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace O3DE::ProjectManager
|
||||
class FilterCategoryWidget
|
||||
: public QWidget
|
||||
{
|
||||
Q_OBJECT // AUTOMOC
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit FilterCategoryWidget(const QString& header,
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
|
||||
#include <GemCatalog/GemInspector.h>
|
||||
#include <GemCatalog/GemItemDelegate.h>
|
||||
#include <ProjectManagerDefs.h>
|
||||
|
||||
#include <QDir>
|
||||
#include <QFrame>
|
||||
#include <QLabel>
|
||||
#include <QSpacerItem>
|
||||
@@ -118,10 +120,12 @@ namespace O3DE::ProjectManager
|
||||
{
|
||||
m_dependingGems->Update(tr("Depending Gems"), tr("The following Gems will be automatically enabled with this Gem."), dependingGemTags);
|
||||
m_dependingGems->show();
|
||||
m_dependingGemsSpacer->changeSize(0, 20, QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_dependingGems->hide();
|
||||
m_dependingGemsSpacer->changeSize(0, 0, QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
}
|
||||
|
||||
// Additional information
|
||||
@@ -246,7 +250,8 @@ namespace O3DE::ProjectManager
|
||||
m_dependingGems = new GemsSubWidget();
|
||||
connect(m_dependingGems, &GemsSubWidget::TagClicked, this, [this](const Tag& tag){ emit TagClicked(tag); });
|
||||
m_mainLayout->addWidget(m_dependingGems);
|
||||
m_mainLayout->addSpacing(20);
|
||||
m_dependingGemsSpacer = new QSpacerItem(0, 20, QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
m_mainLayout->addSpacerItem(m_dependingGemsSpacer);
|
||||
|
||||
// Additional information
|
||||
QLabel* additionalInfoLabel = CreateStyledLabel(m_mainLayout, 14, s_headerColor);
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace O3DE::ProjectManager
|
||||
class GemInspector
|
||||
: public QScrollArea
|
||||
{
|
||||
Q_OBJECT // AUTOMOC
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit GemInspector(GemModel* model, QWidget* parent = nullptr);
|
||||
@@ -75,8 +75,9 @@ namespace O3DE::ProjectManager
|
||||
QLabel* m_requirementsTextLabel = nullptr;
|
||||
QSpacerItem* m_requirementsMainSpacer = nullptr;
|
||||
|
||||
// Depending and conflicting gems
|
||||
// Depending gems
|
||||
GemsSubWidget* m_dependingGems = nullptr;
|
||||
QSpacerItem* m_dependingGemsSpacer = nullptr;
|
||||
|
||||
// Additional information
|
||||
QLabel* m_versionLabel = nullptr;
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
#include <GemCatalog/GemItemDelegate.h>
|
||||
#include <GemCatalog/GemModel.h>
|
||||
#include <GemCatalog/GemSortFilterProxyModel.h>
|
||||
#include <AdjustableHeaderWidget.h>
|
||||
#include <ProjectManagerDefs.h>
|
||||
|
||||
#include <AzCore/std/smart_ptr/unique_ptr.h>
|
||||
|
||||
#include <QEvent>
|
||||
@@ -22,12 +25,15 @@
|
||||
#include <QAbstractTextDocumentLayout>
|
||||
#include <QDesktopServices>
|
||||
#include <QMovie>
|
||||
#include <QHeaderView>
|
||||
#include <QDir>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
GemItemDelegate::GemItemDelegate(QAbstractItemModel* model, QObject* parent)
|
||||
GemItemDelegate::GemItemDelegate(QAbstractItemModel* model, AdjustableHeaderWidget* header, QObject* parent)
|
||||
: QStyledItemDelegate(parent)
|
||||
, m_model(model)
|
||||
, m_headerWidget(header)
|
||||
{
|
||||
AddPlatformIcon(GemInfo::Android, ":/Android.svg");
|
||||
AddPlatformIcon(GemInfo::iOS, ":/iOS.svg");
|
||||
@@ -113,15 +119,27 @@ namespace O3DE::ProjectManager
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
// Gem preview
|
||||
QString previewPath = QDir(GemModel::GetPath(modelIndex)).filePath(ProjectPreviewImagePath);
|
||||
QPixmap gemPreviewImage(previewPath);
|
||||
QRect gemPreviewRect(
|
||||
contentRect.left() + AdjustableHeaderWidget::s_headerTextIndent,
|
||||
contentRect.center().y() - GemPreviewImageHeight / 2,
|
||||
GemPreviewImageWidth, GemPreviewImageHeight);
|
||||
painter->drawPixmap(gemPreviewRect, gemPreviewImage);
|
||||
|
||||
// Gem name
|
||||
QString gemName = GemModel::GetDisplayName(modelIndex);
|
||||
QFont gemNameFont(options.font);
|
||||
const int firstColumnMaxTextWidth = s_summaryStartX - 30;
|
||||
QPair<int, int> nameXBounds = CalcColumnXBounds(HeaderOrder::Name);
|
||||
const int nameStartX = nameXBounds.first;
|
||||
const int nameColumnTextStartX = s_itemMargins.left() + nameStartX + AdjustableHeaderWidget::s_headerTextIndent;
|
||||
const int nameColumnMaxTextWidth = nameXBounds.second - nameStartX - AdjustableHeaderWidget::s_headerTextIndent;
|
||||
gemNameFont.setPixelSize(static_cast<int>(s_gemNameFontSize));
|
||||
gemNameFont.setBold(true);
|
||||
gemName = QFontMetrics(gemNameFont).elidedText(gemName, Qt::TextElideMode::ElideRight, firstColumnMaxTextWidth);
|
||||
gemName = QFontMetrics(gemNameFont).elidedText(gemName, Qt::TextElideMode::ElideRight, nameColumnMaxTextWidth);
|
||||
QRect gemNameRect = GetTextRect(gemNameFont, gemName, s_gemNameFontSize);
|
||||
gemNameRect.moveTo(contentRect.left(), contentRect.top());
|
||||
gemNameRect.moveTo(nameColumnTextStartX, contentRect.top());
|
||||
painter->setFont(gemNameFont);
|
||||
painter->setPen(m_textColor);
|
||||
gemNameRect = painter->boundingRect(gemNameRect, Qt::TextSingleLine, gemName);
|
||||
@@ -129,9 +147,9 @@ namespace O3DE::ProjectManager
|
||||
|
||||
// Gem creator
|
||||
QString gemCreator = GemModel::GetCreator(modelIndex);
|
||||
gemCreator = standardFontMetrics.elidedText(gemCreator, Qt::TextElideMode::ElideRight, firstColumnMaxTextWidth);
|
||||
gemCreator = standardFontMetrics.elidedText(gemCreator, Qt::TextElideMode::ElideRight, nameColumnMaxTextWidth);
|
||||
QRect gemCreatorRect = GetTextRect(standardFont, gemCreator, s_fontSize);
|
||||
gemCreatorRect.moveTo(contentRect.left(), contentRect.top() + gemNameRect.height());
|
||||
gemCreatorRect.moveTo(nameColumnTextStartX, contentRect.top() + gemNameRect.height());
|
||||
|
||||
painter->setFont(standardFont);
|
||||
gemCreatorRect = painter->boundingRect(gemCreatorRect, Qt::TextSingleLine, gemCreator);
|
||||
@@ -154,13 +172,16 @@ namespace O3DE::ProjectManager
|
||||
|
||||
QRect GemItemDelegate::CalcSummaryRect(const QRect& contentRect, bool hasTags) const
|
||||
{
|
||||
const int featureTagAreaHeight = 30;
|
||||
const int featureTagAreaHeight = 40;
|
||||
const int summaryHeight = contentRect.height() - (hasTags * featureTagAreaHeight);
|
||||
|
||||
const int additionalSummarySpacing = s_itemMargins.right() * 3;
|
||||
const QSize summarySize = QSize(contentRect.width() - s_summaryStartX - s_buttonWidth - additionalSummarySpacing,
|
||||
const auto [summaryStartX, summaryEndX] = CalcColumnXBounds(HeaderOrder::Summary);
|
||||
|
||||
const QSize summarySize =
|
||||
QSize(summaryEndX - summaryStartX - AdjustableHeaderWidget::s_headerTextIndent - s_extraSummarySpacing,
|
||||
summaryHeight);
|
||||
return QRect(QPoint(contentRect.left() + s_summaryStartX, contentRect.top()), summarySize);
|
||||
return QRect(
|
||||
QPoint(s_itemMargins.left() + summaryStartX + AdjustableHeaderWidget::s_headerTextIndent, contentRect.top()), summarySize);
|
||||
}
|
||||
|
||||
QSize GemItemDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& modelIndex) const
|
||||
@@ -169,7 +190,7 @@ namespace O3DE::ProjectManager
|
||||
initStyleOption(&options, modelIndex);
|
||||
|
||||
int marginsHorizontal = s_itemMargins.left() + s_itemMargins.right() + s_contentMargins.left() + s_contentMargins.right();
|
||||
return QSize(marginsHorizontal + s_buttonWidth + s_summaryStartX, s_height);
|
||||
return QSize(marginsHorizontal + s_buttonWidth + s_defaultSummaryStartX, s_height);
|
||||
}
|
||||
|
||||
bool GemItemDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& modelIndex)
|
||||
@@ -299,9 +320,17 @@ namespace O3DE::ProjectManager
|
||||
return QFontMetrics(font).boundingRect(text);
|
||||
}
|
||||
|
||||
QPair<int, int> GemItemDelegate::CalcColumnXBounds(HeaderOrder header) const
|
||||
{
|
||||
return m_headerWidget->CalcColumnXBounds(static_cast<int>(header));
|
||||
}
|
||||
|
||||
QRect GemItemDelegate::CalcButtonRect(const QRect& contentRect) const
|
||||
{
|
||||
const QPoint topLeft = QPoint(contentRect.right() - s_buttonWidth, contentRect.center().y() - s_buttonHeight / 2);
|
||||
const QPoint topLeft = QPoint(
|
||||
s_itemMargins.left() + CalcColumnXBounds(HeaderOrder::Status).first + AdjustableHeaderWidget::s_headerTextIndent + s_statusIconSize +
|
||||
s_statusButtonSpacing,
|
||||
contentRect.center().y() - s_buttonHeight / 2);
|
||||
const QSize size = QSize(s_buttonWidth, s_buttonHeight);
|
||||
return QRect(topLeft, size);
|
||||
}
|
||||
@@ -309,7 +338,7 @@ namespace O3DE::ProjectManager
|
||||
void GemItemDelegate::DrawPlatformIcons(QPainter* painter, const QRect& contentRect, const QModelIndex& modelIndex) const
|
||||
{
|
||||
const GemInfo::Platforms platforms = GemModel::GetPlatforms(modelIndex);
|
||||
int startX = 0;
|
||||
int startX = s_itemMargins.left() + CalcColumnXBounds(HeaderOrder::Name).first + AdjustableHeaderWidget::s_headerTextIndent;
|
||||
|
||||
// Iterate and draw the platforms in the order they are defined in the enum.
|
||||
for (int i = 0; i < GemInfo::NumPlatforms; ++i)
|
||||
@@ -331,18 +360,23 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
}
|
||||
|
||||
void GemItemDelegate::DrawFeatureTags(QPainter* painter, const QRect& contentRect, const QStringList& featureTags, const QFont& standardFont, const QRect& summaryRect) const
|
||||
void GemItemDelegate::DrawFeatureTags(
|
||||
QPainter* painter,
|
||||
const QRect& contentRect,
|
||||
const QStringList& featureTags,
|
||||
const QFont& standardFont,
|
||||
const QRect& summaryRect) const
|
||||
{
|
||||
QFont gemFeatureTagFont(standardFont);
|
||||
gemFeatureTagFont.setPixelSize(s_featureTagFontSize);
|
||||
gemFeatureTagFont.setBold(false);
|
||||
painter->setFont(gemFeatureTagFont);
|
||||
|
||||
int x = s_summaryStartX;
|
||||
int x = CalcColumnXBounds(HeaderOrder::Summary).first + AdjustableHeaderWidget::s_headerTextIndent;
|
||||
for (const QString& featureTag : featureTags)
|
||||
{
|
||||
QRect featureTagRect = GetTextRect(gemFeatureTagFont, featureTag, s_featureTagFontSize);
|
||||
featureTagRect.moveTo(contentRect.left() + x + s_featureTagBorderMarginX,
|
||||
featureTagRect.moveTo(s_itemMargins.left() + x + s_featureTagBorderMarginX,
|
||||
contentRect.top() + 47);
|
||||
featureTagRect = painter->boundingRect(featureTagRect, Qt::TextSingleLine, featureTag);
|
||||
|
||||
@@ -430,7 +464,7 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
else
|
||||
{
|
||||
circleCenter = buttonRect.center() + QPoint(-buttonRect.width() / 2 + s_buttonBorderRadius, 1);
|
||||
circleCenter = buttonRect.center() + QPoint(-buttonRect.width() / 2 + s_buttonBorderRadius + 1, 1);
|
||||
}
|
||||
|
||||
// Rounded rect
|
||||
|
||||
@@ -19,13 +19,15 @@ QT_FORWARD_DECLARE_CLASS(QEvent)
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
QT_FORWARD_DECLARE_CLASS(AdjustableHeaderWidget)
|
||||
|
||||
class GemItemDelegate
|
||||
: public QStyledItemDelegate
|
||||
{
|
||||
Q_OBJECT // AUTOMOC
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit GemItemDelegate(QAbstractItemModel* model, QObject* parent = nullptr);
|
||||
explicit GemItemDelegate(QAbstractItemModel* model, AdjustableHeaderWidget* header, QObject* parent = nullptr);
|
||||
~GemItemDelegate() = default;
|
||||
|
||||
void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& modelIndex) const override;
|
||||
@@ -45,12 +47,13 @@ namespace O3DE::ProjectManager
|
||||
inline constexpr static int s_height = 105; // Gem item total height
|
||||
inline constexpr static qreal s_gemNameFontSize = 13.0;
|
||||
inline constexpr static qreal s_fontSize = 12.0;
|
||||
inline constexpr static int s_summaryStartX = 150;
|
||||
inline constexpr static int s_defaultSummaryStartX = 270;
|
||||
|
||||
// Margin and borders
|
||||
inline constexpr static QMargins s_itemMargins = QMargins(/*left=*/16, /*top=*/8, /*right=*/16, /*bottom=*/8); // Item border distances
|
||||
inline constexpr static QMargins s_contentMargins = QMargins(/*left=*/20, /*top=*/12, /*right=*/20, /*bottom=*/12); // Distances of the elements within an item to the item borders
|
||||
inline constexpr static QMargins s_itemMargins = QMargins(/*left=*/16, /*top=*/5, /*right=*/16, /*bottom=*/5); // Item border distances
|
||||
inline constexpr static QMargins s_contentMargins = QMargins(/*left=*/10, /*top=*/12, /*right=*/20, /*bottom=*/12); // Distances of the elements within an item to the item borders
|
||||
inline constexpr static int s_borderWidth = 4;
|
||||
inline constexpr static int s_extraSummarySpacing = s_itemMargins.right();
|
||||
|
||||
// Button
|
||||
inline constexpr static int s_buttonWidth = 32;
|
||||
@@ -65,6 +68,18 @@ namespace O3DE::ProjectManager
|
||||
inline constexpr static int s_featureTagBorderMarginY = 3;
|
||||
inline constexpr static int s_featureTagSpacing = 7;
|
||||
|
||||
// Status icon
|
||||
inline constexpr static int s_statusIconSize = 16;
|
||||
inline constexpr static int s_statusButtonSpacing = 5;
|
||||
|
||||
enum class HeaderOrder
|
||||
{
|
||||
Preview,
|
||||
Name,
|
||||
Summary,
|
||||
Status
|
||||
};
|
||||
|
||||
signals:
|
||||
void MovieStartedPlaying(const QMovie* playingMovie) const;
|
||||
|
||||
@@ -74,13 +89,20 @@ namespace O3DE::ProjectManager
|
||||
|
||||
void CalcRects(const QStyleOptionViewItem& option, QRect& outFullRect, QRect& outItemRect, QRect& outContentRect) const;
|
||||
QRect GetTextRect(QFont& font, const QString& text, qreal fontSize) const;
|
||||
QPair<int, int> CalcColumnXBounds(HeaderOrder header) const;
|
||||
QRect CalcButtonRect(const QRect& contentRect) const;
|
||||
QRect CalcSummaryRect(const QRect& contentRect, bool hasTags) const;
|
||||
void DrawPlatformIcons(QPainter* painter, const QRect& contentRect, const QModelIndex& modelIndex) const;
|
||||
void DrawButton(QPainter* painter, const QRect& buttonRect, const QModelIndex& modelIndex) const;
|
||||
void DrawFeatureTags(QPainter* painter, const QRect& contentRect, const QStringList& featureTags, const QFont& standardFont, const QRect& summaryRect) const;
|
||||
void DrawFeatureTags(
|
||||
QPainter* painter,
|
||||
const QRect& contentRect,
|
||||
const QStringList& featureTags,
|
||||
const QFont& standardFont,
|
||||
const QRect& summaryRect) const;
|
||||
void DrawText(const QString& text, QPainter* painter, const QRect& rect, const QFont& standardFont) const;
|
||||
void DrawDownloadStatusIcon(QPainter* painter, const QRect& contentRect, const QRect& buttonRect, const QModelIndex& modelIndex) const;
|
||||
void DrawDownloadStatusIcon(
|
||||
QPainter* painter, const QRect& contentRect, const QRect& buttonRect, const QModelIndex& modelIndex) const;
|
||||
|
||||
QAbstractItemModel* m_model = nullptr;
|
||||
|
||||
@@ -92,13 +114,13 @@ namespace O3DE::ProjectManager
|
||||
|
||||
// Status icons
|
||||
void SetStatusIcon(QPixmap& m_iconPixmap, const QString& iconPath);
|
||||
inline constexpr static int s_statusIconSize = 16;
|
||||
inline constexpr static int s_statusButtonSpacing = 5;
|
||||
|
||||
QPixmap m_unknownStatusPixmap;
|
||||
QPixmap m_notDownloadedPixmap;
|
||||
QPixmap m_downloadSuccessfulPixmap;
|
||||
QPixmap m_downloadFailedPixmap;
|
||||
QMovie* m_downloadingMovie = nullptr;
|
||||
|
||||
AdjustableHeaderWidget* m_headerWidget = nullptr;
|
||||
};
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user