forked from qt-creator/qt-creator
EffectComposer: Generate property specifics for effects
With proper specifics sheet, the effect will show the available properties nicely in property view. Also introduced a new concept of FooBarSpecificsDynamic.qml to provide property specifics. The difference to regular FooBarSpecifics.qml file is that the dynamic version will be reloaded always, which allows us to change it at runtime. Fixes: QDS-11995 Change-Id: I744124182428c5c4607856e7970700d9a5e9972e Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io> Reviewed-by: Qt CI Patch Build Bot <ci_patchbuild_bot@qt.io> Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
@@ -569,16 +569,154 @@ QJsonObject nodeToJson(const CompositionNode &node)
|
||||
return nodeObject;
|
||||
}
|
||||
|
||||
QString EffectComposerModel::getGeneratedMessage() const
|
||||
{
|
||||
QString s;
|
||||
|
||||
QString header {
|
||||
R"(
|
||||
// Created with Qt Design Studio (version %1), %2
|
||||
// Do not manually edit this file, it will be overwritten if effect is modified in Qt Design Studio.
|
||||
)"
|
||||
};
|
||||
|
||||
s += header.arg(qApp->applicationVersion(), QDateTime::currentDateTime().toString());
|
||||
return s;
|
||||
}
|
||||
|
||||
QString EffectComposerModel::getDesignerSpecifics(const QString &name) const
|
||||
{
|
||||
QString s;
|
||||
|
||||
s += getGeneratedMessage();
|
||||
|
||||
s +=
|
||||
R"(
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import HelperWidgets
|
||||
import StudioTheme as StudioTheme
|
||||
|
||||
Column {
|
||||
)";
|
||||
|
||||
if (m_shaderFeatures.enabled(ShaderFeatures::Time)
|
||||
|| m_shaderFeatures.enabled(ShaderFeatures::Frame)) {
|
||||
QString animSec =
|
||||
R"(
|
||||
Section {
|
||||
caption: "%1"
|
||||
width: parent.width
|
||||
|
||||
SectionLayout {
|
||||
PropertyLabel {
|
||||
text: "%2"
|
||||
tooltip: "%3"
|
||||
}
|
||||
|
||||
SecondColumnLayout {
|
||||
CheckBox {
|
||||
text: backendValues.timeRunning.valueToString
|
||||
backendValue: backendValues.timeRunning
|
||||
implicitWidth: StudioTheme.Values.twoControlColumnWidth
|
||||
+ StudioTheme.Values.actionIndicatorWidth
|
||||
}
|
||||
ExpandingSpacer {}
|
||||
}
|
||||
)";
|
||||
s += animSec.arg(tr("Animation"), tr("Running"), tr("Set this property to animate the effect."));
|
||||
|
||||
if (m_shaderFeatures.enabled(ShaderFeatures::Time)) {
|
||||
QString timeProp =
|
||||
R"(
|
||||
PropertyLabel {
|
||||
text: "%1"
|
||||
tooltip: "%2"
|
||||
}
|
||||
|
||||
SecondColumnLayout {
|
||||
SpinBox {
|
||||
minimumValue: 0
|
||||
maximumValue: 9999999
|
||||
decimals: 2
|
||||
stepSize: .01
|
||||
backendValue: backendValues.animatedTime
|
||||
implicitWidth: StudioTheme.Values.singleControlColumnWidth
|
||||
+ StudioTheme.Values.actionIndicatorWidth
|
||||
}
|
||||
ExpandingSpacer {}
|
||||
}
|
||||
)";
|
||||
s += timeProp.arg(tr("Time"), tr("This property allows explicit control of current animation time."));
|
||||
}
|
||||
|
||||
if (m_shaderFeatures.enabled(ShaderFeatures::Frame)) {
|
||||
QString frameProp =
|
||||
R"(
|
||||
PropertyLabel {
|
||||
text: "%1"
|
||||
tooltip: "%2"
|
||||
}
|
||||
|
||||
SecondColumnLayout {
|
||||
SpinBox {
|
||||
minimumValue: 0
|
||||
maximumValue: 99999999
|
||||
decimals: 0
|
||||
stepSize: 1
|
||||
backendValue: backendValues.animatedFrame
|
||||
implicitWidth: StudioTheme.Values.singleControlColumnWidth
|
||||
+ StudioTheme.Values.actionIndicatorWidth
|
||||
}
|
||||
ExpandingSpacer {}
|
||||
}
|
||||
)";
|
||||
s += frameProp.arg(tr("Frame"), tr("This property allows explicit control of current animation frame."));
|
||||
}
|
||||
s += " }\n";
|
||||
s += " }\n";
|
||||
}
|
||||
|
||||
for (const auto &node : std::as_const(m_nodes)) {
|
||||
const QList<Uniform *> uniforms = static_cast<EffectComposerUniformsModel *>(
|
||||
node->uniformsModel())->uniforms();
|
||||
QString secStr =
|
||||
R"(
|
||||
Section {
|
||||
caption: "%1"
|
||||
width: parent.width
|
||||
|
||||
SectionLayout {
|
||||
)";
|
||||
secStr = secStr.arg(node->name());
|
||||
|
||||
const QString oldSecStr = secStr;
|
||||
|
||||
for (Uniform *uniform : uniforms)
|
||||
secStr += uniform->getDesignerSpecifics();
|
||||
|
||||
// Only add the section if it has actual content
|
||||
if (oldSecStr != secStr) {
|
||||
secStr += " }\n";
|
||||
secStr += " }\n";
|
||||
s += secStr;
|
||||
}
|
||||
}
|
||||
|
||||
s += "}\n";
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
QString EffectComposerModel::getQmlEffectString()
|
||||
{
|
||||
QString s;
|
||||
|
||||
// _isEffectItem is type var to hide it from property view
|
||||
QString header{
|
||||
R"(
|
||||
// Created with Qt Design Studio (version %1), %2
|
||||
// Do not manually edit this file, it will be overwritten if effect is modified in Qt Design Studio.
|
||||
s += getGeneratedMessage();
|
||||
|
||||
// _isEffectItem is type var to hide it from property view
|
||||
QString header {
|
||||
R"(
|
||||
import QtQuick
|
||||
|
||||
Item {
|
||||
@@ -588,14 +726,14 @@ Item {
|
||||
visible: true
|
||||
|
||||
// This is an internal property used by tooling to identify effect items. Do not modify.
|
||||
property var _isEffectItem
|
||||
property bool _isEffectItem
|
||||
|
||||
// This is an internal property used to manage the effect. Do not modify.
|
||||
property Item _oldParent: null
|
||||
)"
|
||||
};
|
||||
|
||||
s += header.arg(qApp->applicationVersion(), QDateTime::currentDateTime().toString());
|
||||
s += header;
|
||||
|
||||
if (m_shaderFeatures.enabled(ShaderFeatures::Source)) {
|
||||
s += " // This is the main source for the effect. Set internally to the current parent item. Do not modify.\n";
|
||||
@@ -849,12 +987,10 @@ void EffectComposerModel::saveResources(const QString &name)
|
||||
QStringList newFileNames;
|
||||
|
||||
// Create effect folder if not created
|
||||
if (!effectPath.exists()) {
|
||||
QDir effectDir(effectsResDir.toString());
|
||||
effectDir.mkdir(name);
|
||||
} else {
|
||||
if (!effectPath.exists())
|
||||
effectPath.createDir();
|
||||
else
|
||||
oldFiles = effectPath.dirEntries(QDir::Files);
|
||||
}
|
||||
|
||||
// Create effect qmldir
|
||||
newFileNames.append(qmldirFileName);
|
||||
@@ -871,6 +1007,19 @@ void EffectComposerModel::saveResources(const QString &name)
|
||||
qmldirPath.writeFileContents(qmldirContent.toUtf8());
|
||||
}
|
||||
|
||||
// Create designer folder if not created
|
||||
const Utils::FilePath designerPath = effectPath.pathAppended("designer");
|
||||
if (!designerPath.exists())
|
||||
designerPath.createDir();
|
||||
|
||||
// Create designer property sheet
|
||||
// Since this is in subdir, no need to add it to newFileNames
|
||||
QString specContent = getDesignerSpecifics(name);
|
||||
QString specFileName("%1SpecificsDynamic.qml");
|
||||
specFileName = specFileName.arg(name);
|
||||
Utils::FilePath specPath = designerPath.resolvePath(specFileName);
|
||||
specPath.writeFileContents(specContent.toUtf8());
|
||||
|
||||
// Create the qml file
|
||||
QString qmlComponentString = getQmlEffectString();
|
||||
QStringList qmlStringList = qmlComponentString.split('\n');
|
||||
@@ -946,6 +1095,7 @@ void EffectComposerModel::saveResources(const QString &name)
|
||||
}
|
||||
|
||||
// Delete old content that was not overwritten
|
||||
// We ignore subdirectories, as currently subdirs only contain fixed content
|
||||
for (const Utils::FilePath &oldFile : oldFiles) {
|
||||
if (!newFileNames.contains(oldFile.fileName()))
|
||||
oldFile.removeFile();
|
||||
@@ -1580,17 +1730,21 @@ QString EffectComposerModel::getQmlImagesString(bool localFiles)
|
||||
QString imagePath = uniform->value().toString();
|
||||
// For preview, generate image element even if path is empty, as changing uniform values
|
||||
// will not trigger qml code regeneration
|
||||
if (localFiles && imagePath.isEmpty())
|
||||
continue;
|
||||
if (localFiles) {
|
||||
if (imagePath.isEmpty())
|
||||
continue;
|
||||
QFileInfo fi(imagePath);
|
||||
imagePath = fi.fileName();
|
||||
imagesString += QString(" property url %1Url: \"%2\"\n")
|
||||
.arg(uniform->name(), imagePath);
|
||||
}
|
||||
imagesString += " Image {\n";
|
||||
QString simplifiedName = getImageElementName(*uniform, localFiles);
|
||||
imagesString += QString(" id: %1\n").arg(simplifiedName);
|
||||
imagesString += " anchors.fill: parent\n";
|
||||
// File paths are absolute, return as local when requested
|
||||
if (localFiles) {
|
||||
QFileInfo fi(imagePath);
|
||||
imagePath = fi.fileName();
|
||||
imagesString += QString(" source: \"%1\"\n").arg(imagePath);
|
||||
imagesString += QString(" source: rootItem.%1Url\n").arg(uniform->name());
|
||||
} else {
|
||||
imagesString += QString(" source: g_propertyData.%1\n").arg(uniform->name());
|
||||
|
||||
|
||||
@@ -172,6 +172,8 @@ private:
|
||||
|
||||
QString getQmlImagesString(bool localFiles);
|
||||
QString getQmlComponentString(bool localFiles);
|
||||
QString getGeneratedMessage() const;
|
||||
QString getDesignerSpecifics(const QString &name) const;
|
||||
|
||||
void connectCompositionNode(CompositionNode *node);
|
||||
|
||||
|
||||
@@ -170,6 +170,175 @@ bool Uniform::enableMipmap() const
|
||||
return m_enableMipmap;
|
||||
}
|
||||
|
||||
QString Uniform::getDesignerSpecifics() const
|
||||
{
|
||||
QString specs;
|
||||
|
||||
// Uniforms with custom values or define type do not result in exported properties
|
||||
if (!m_customValue.isEmpty() || m_type == Type::Define)
|
||||
return specs;
|
||||
|
||||
auto appendVectorSpinbox = [this, &specs](const QString subProp, const QString &label,
|
||||
float minVal, float maxVal, bool firstCol) {
|
||||
QString vecSpec =
|
||||
R"(
|
||||
SpinBox {
|
||||
minimumValue: %4
|
||||
maximumValue: %5
|
||||
decimals: 2
|
||||
stepSize: .01
|
||||
backendValue: backendValues.%1_%2
|
||||
implicitWidth: StudioTheme.Values.twoControlColumnWidth
|
||||
+ StudioTheme.Values.actionIndicatorWidth
|
||||
}
|
||||
|
||||
Spacer { implicitWidth: StudioTheme.Values.controlLabelGap }
|
||||
|
||||
ControlLabel {
|
||||
text: "%3"
|
||||
}
|
||||
)";
|
||||
specs += vecSpec.arg(m_name).arg(subProp).arg(label).arg(minVal).arg(maxVal);
|
||||
if (firstCol)
|
||||
specs += " Spacer { implicitWidth: StudioTheme.Values.controlGap }\n";
|
||||
};
|
||||
|
||||
auto appendVectorSeparator = [&specs]() {
|
||||
specs +=
|
||||
R"(
|
||||
ExpandingSpacer {}
|
||||
}
|
||||
|
||||
PropertyLabel {}
|
||||
|
||||
SecondColumnLayout {
|
||||
)";
|
||||
|
||||
};
|
||||
|
||||
specs +=
|
||||
R"(
|
||||
PropertyLabel {
|
||||
text: "%1"
|
||||
tooltip: "%2"
|
||||
}
|
||||
|
||||
SecondColumnLayout {
|
||||
)";
|
||||
QString desc = m_description;
|
||||
desc.replace("\n", "\\n");
|
||||
desc.replace("\"", "\\\"");
|
||||
specs = specs.arg(m_displayName, desc);
|
||||
|
||||
switch (m_type) {
|
||||
case Type::Bool: {
|
||||
QString typeSpec =
|
||||
R"(
|
||||
CheckBox {
|
||||
text: backendValues.%1.valueToString
|
||||
backendValue: backendValues.%1
|
||||
implicitWidth: StudioTheme.Values.twoControlColumnWidth
|
||||
+ StudioTheme.Values.actionIndicatorWidth
|
||||
}
|
||||
)";
|
||||
specs += typeSpec.arg(m_name);
|
||||
break;
|
||||
}
|
||||
case Type::Int: {
|
||||
QString typeSpec =
|
||||
R"(
|
||||
SpinBox {
|
||||
minimumValue: %1
|
||||
maximumValue: %2
|
||||
decimals: 0
|
||||
stepSize: 1
|
||||
sliderIndicatorVisible: true
|
||||
backendValue: backendValues.%3
|
||||
implicitWidth: StudioTheme.Values.singleControlColumnWidth
|
||||
+ StudioTheme.Values.actionIndicatorWidth
|
||||
}
|
||||
)";
|
||||
specs += typeSpec.arg(m_minValue.toString(), m_maxValue.toString(), m_name);
|
||||
break;
|
||||
}
|
||||
case Type::Float: {
|
||||
QString typeSpec =
|
||||
R"(
|
||||
SpinBox {
|
||||
minimumValue: %1
|
||||
maximumValue: %2
|
||||
decimals: 2
|
||||
stepSize: .01
|
||||
sliderIndicatorVisible: true
|
||||
backendValue: backendValues.%3
|
||||
implicitWidth: StudioTheme.Values.singleControlColumnWidth
|
||||
+ StudioTheme.Values.actionIndicatorWidth
|
||||
}
|
||||
)";
|
||||
specs += typeSpec.arg(m_minValue.toString(), m_maxValue.toString(), m_name);
|
||||
break;
|
||||
}
|
||||
case Type::Vec2: {
|
||||
QVector2D minVal = m_minValue.value<QVector2D>();
|
||||
QVector2D maxVal = m_maxValue.value<QVector2D>();
|
||||
appendVectorSpinbox("x", tr("X"), minVal.x(), maxVal.x(), true);
|
||||
appendVectorSpinbox("y", tr("Y"), minVal.y(), maxVal.y(), false);
|
||||
break;
|
||||
}
|
||||
case Type::Vec3: {
|
||||
QVector3D minVal = m_minValue.value<QVector3D>();
|
||||
QVector3D maxVal = m_maxValue.value<QVector3D>();
|
||||
appendVectorSpinbox("x", tr("X"), minVal.x(), maxVal.x(), true);
|
||||
appendVectorSpinbox("y", tr("Y"), minVal.y(), maxVal.y(), false);
|
||||
appendVectorSeparator();
|
||||
appendVectorSpinbox("z", tr("Z"), minVal.z(), maxVal.z(), true);
|
||||
break;
|
||||
}
|
||||
case Type::Vec4: {
|
||||
QVector4D minVal = m_minValue.value<QVector4D>();
|
||||
QVector4D maxVal = m_maxValue.value<QVector4D>();
|
||||
appendVectorSpinbox("x", tr("X"), minVal.x(), maxVal.x(), true);
|
||||
appendVectorSpinbox("y", tr("Y"), minVal.y(), maxVal.y(), false);
|
||||
appendVectorSeparator();
|
||||
appendVectorSpinbox("z", tr("Z"), minVal.z(), maxVal.z(), true);
|
||||
appendVectorSpinbox("w", tr("W"), minVal.w(), maxVal.w(), false);
|
||||
break;
|
||||
}
|
||||
case Type::Color: {
|
||||
QString typeSpec =
|
||||
R"(
|
||||
ColorEditor {
|
||||
backendValue: backendValues.%1
|
||||
supportGradient: false
|
||||
}
|
||||
)";
|
||||
specs += typeSpec.arg(m_name);
|
||||
break;
|
||||
}
|
||||
case Type::Sampler: {
|
||||
QString typeSpec =
|
||||
R"(
|
||||
UrlChooser {
|
||||
backendValue: backendValues.%1
|
||||
}
|
||||
)";
|
||||
specs += typeSpec.arg(m_name + "Url");
|
||||
break;
|
||||
}
|
||||
case Type::Define:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
specs +=
|
||||
R"(
|
||||
ExpandingSpacer {}
|
||||
}
|
||||
)";
|
||||
|
||||
return specs;
|
||||
}
|
||||
|
||||
// Returns name for image mipmap property.
|
||||
// e.g. "myImage" -> "myImageMipmap".
|
||||
QString Uniform::mipmapPropertyName(const QString &name) const
|
||||
|
||||
@@ -74,6 +74,7 @@ public:
|
||||
void setEnabled(bool newEnabled);
|
||||
|
||||
bool enableMipmap() const;
|
||||
QString getDesignerSpecifics() const;
|
||||
|
||||
static QString stringFromType(Uniform::Type type, bool isShader = false);
|
||||
static Uniform::Type typeFromString(const QString &typeString);
|
||||
|
||||
Reference in New Issue
Block a user