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:
Miikka Heikkinen
2024-02-22 17:43:58 +02:00
parent 3d16642dfd
commit fb27084f38
5 changed files with 352 additions and 17 deletions

View File

@@ -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());

View File

@@ -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);

View File

@@ -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

View File

@@ -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);