diff --git a/src/app/GUI/Dialogs/dialogsinterfaceimpl.cpp b/src/app/GUI/Dialogs/dialogsinterfaceimpl.cpp index ae8befc68..a19477786 100644 --- a/src/app/GUI/Dialogs/dialogsinterfaceimpl.cpp +++ b/src/app/GUI/Dialogs/dialogsinterfaceimpl.cpp @@ -97,6 +97,13 @@ void DialogsInterfaceImpl::showExpressionDialog(QrealAnimator* const target) con dialog->show(); } +void DialogsInterfaceImpl::showExpressionDialog(QStringAnimator* const target) const { + const auto parent = MainWindow::sGetInstance(); + const auto dialog = new ExpressionDialog(target, parent); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->show(); +} + void DialogsInterfaceImpl::showApplyExpressionDialog(QrealAnimator* const target) const { const auto parent = MainWindow::sGetInstance(); const auto dialog = new ApplyExpressionDialog(target, parent); diff --git a/src/app/GUI/Dialogs/dialogsinterfaceimpl.h b/src/app/GUI/Dialogs/dialogsinterfaceimpl.h index 42090a41f..d72b14a1d 100644 --- a/src/app/GUI/Dialogs/dialogsinterfaceimpl.h +++ b/src/app/GUI/Dialogs/dialogsinterfaceimpl.h @@ -34,6 +34,7 @@ class DialogsInterfaceImpl : public DialogsInterface { stdsptr execShaderChooser( const QString& name, const ShaderOptions& options) const; void showExpressionDialog(QrealAnimator* const target) const; + void showExpressionDialog(QStringAnimator* const target) const; void showApplyExpressionDialog(QrealAnimator* const target) const; void showDurationSettingsDialog(DurationRectangle* const target) const; /*bool execAnimationToPaint(const AnimationBox* const src, diff --git a/src/app/GUI/Expressions/expressiondialog.cpp b/src/app/GUI/Expressions/expressiondialog.cpp index 894014485..a0382e7a8 100644 --- a/src/app/GUI/Expressions/expressiondialog.cpp +++ b/src/app/GUI/Expressions/expressiondialog.cpp @@ -40,6 +40,8 @@ #include #include +#include "Animators/qrealanimator.h" +#include "Animators/qstringanimator.h" #include "Expressions/expression.h" #include "Boxes/boundingbox.h" #include "Private/document.h" @@ -318,16 +320,57 @@ void addBasicDefs(QsciAPIs* const target) } } +ExpressionDialog::TargetOps ExpressionDialog::makeOps(QrealAnimator* const target) { + return ExpressionDialog::TargetOps{ + target, + target->prp_getName(), + Expression::sQrealAnimatorTester, + [target]() { return target->getExpressionBindingsString(); }, + [target]() { return target->getExpressionDefinitionsString(); }, + [target]() { return target->getExpressionScriptString(); }, + [target](const qsptr& expr) { target->setExpression(expr); }, + [target](const qsptr& expr) { target->setExpressionAction(expr); } + }; +} + +ExpressionDialog::TargetOps ExpressionDialog::makeOps(QStringAnimator* const target) { + return ExpressionDialog::TargetOps{ + target, + target->prp_getName(), + Expression::sQStringAnimatorTester, + [target]() { return target->getExpressionBindingsString(); }, + [target]() { return target->getExpressionDefinitionsString(); }, + [target]() { return target->getExpressionScriptString(); }, + [target](const qsptr& expr) { target->setExpression(expr); }, + [target](const qsptr& expr) { target->setExpressionAction(expr); } + }; +} + ExpressionDialog::ExpressionDialog(QrealAnimator* const target, QWidget * const parent) + : ExpressionDialog(makeOps(target), parent) {} + +ExpressionDialog::ExpressionDialog(QStringAnimator* const target, + QWidget * const parent) + : ExpressionDialog(makeOps(target), parent) {} + +ExpressionDialog::ExpressionDialog(const TargetOps& ops, + QWidget * const parent) : Friction::Ui::Dialog(parent) - , mTarget(target) + , mContext(ops.context) + , mTargetName(ops.name) + , mResultTester(ops.tester) + , mGetBindings(ops.getBindings) + , mGetDefinitions(ops.getDefinitions) + , mGetScript(ops.getScript) + , mSetExpression(ops.setExpression) + , mSetExpressionAction(ops.setExpressionAction) , mTab(nullptr) , mTabEditor(0) , mPresetsCombo(nullptr) , mSettings(eSettings::sInstance) { - setWindowTitle(tr("Expression %1").arg(target->prp_getName())); + setWindowTitle(tr("Expression %1").arg(mTargetName)); const auto windowLayout = new QVBoxLayout(this); setLayout(windowLayout); @@ -368,7 +411,7 @@ ExpressionDialog::ExpressionDialog(QrealAnimator* const target, tabLayout->addWidget(mDefinitionsButon); mainLayout->addLayout(tabLayout); - mBindings = new ExpressionEditor(target, this); + mBindings = new ExpressionEditor(mContext, mGetBindings(), this); connect(mBindings, &ExpressionEditor::textChanged, this, [this]() { mBindingsChanged = true; @@ -396,7 +439,7 @@ ExpressionDialog::ExpressionDialog(QrealAnimator* const target, mDefinitions->setLexer(mDefsLexer); mDefinitions->setAutoCompletionSource(QsciScintilla::AcsAll); - mDefinitions->setText(target->getExpressionDefinitionsString()); + mDefinitions->setText(mGetDefinitions()); connect(mDefinitions, &QsciScintilla::textChanged, this, [this]() { mDefinitions->autoCompleteFromAll(); mDefinitionsChanged = true; @@ -424,7 +467,7 @@ ExpressionDialog::ExpressionDialog(QrealAnimator* const target, mScript->setLexer(mScriptLexer); mScript->setAutoCompletionSource(QsciScintilla::AcsAll); - mScript->setText(target->getExpressionScriptString()); + mScript->setText(mGetScript()); connect(mScript, &QsciScintilla::textChanged, mScript, &QsciScintilla::autoCompleteFromAll); mainLayout->addWidget(mScript, 2); @@ -594,7 +637,7 @@ bool ExpressionDialog::getBindings(PropertyBindingMap& bindings) try { bindings = PropertyBindingParser::parseBindings(bindingsStr, nullptr, - mTarget); + mContext); mBindingsButton->setIcon(QIcon()); return true; } catch (const std::exception& e) { @@ -648,7 +691,7 @@ bool ExpressionDialog::apply(const bool action) QJSValue eEvaluate; try { Expression::sAddScriptTo(scriptStr, bindings, *engine, eEvaluate, - Expression::sQrealAnimatorTester); + mResultTester); } catch (const std::exception& e) { mScriptError->setText(e.what()); mBindingsButton->setIcon(mRedDotIcon); @@ -662,9 +705,9 @@ bool ExpressionDialog::apply(const bool action) std::move(eEvaluate)); if (expr && !expr->isValid()) { expr = nullptr; } if (action) { - mTarget->setExpressionAction(expr); + mSetExpressionAction(expr); } else { - mTarget->setExpression(expr); + mSetExpression(expr); } } catch (const std::exception& e) { return false; diff --git a/src/app/GUI/Expressions/expressiondialog.h b/src/app/GUI/Expressions/expressiondialog.h index e3d9b9820..ecaeb6509 100644 --- a/src/app/GUI/Expressions/expressiondialog.h +++ b/src/app/GUI/Expressions/expressiondialog.h @@ -34,14 +34,20 @@ #include #include #include +#include #include "conncontext.h" #include "dialogs/dialog.h" #include "Private/esettings.h" +#include "smartPointers/selfref.h" class QrealAnimator; +class QStringAnimator; class ExpressionEditor; class PropertyBindingBase; +class Property; +class QJSValue; +class Expression; class JSLexer; class JSEditor; @@ -53,8 +59,27 @@ class ExpressionDialog : public Friction::Ui::Dialog public: ExpressionDialog(QrealAnimator* const target, QWidget * const parent = nullptr); + ExpressionDialog(QStringAnimator* const target, + QWidget * const parent = nullptr); private: + using ResultTester = std::function; + using ExpressionSetter = std::function&)>; + struct TargetOps { + Property* context = nullptr; + QString name; + ResultTester tester; + std::function getBindings; + std::function getDefinitions; + std::function getScript; + ExpressionSetter setExpression; + ExpressionSetter setExpressionAction; + }; + + ExpressionDialog(const TargetOps& ops, QWidget* const parent); + static TargetOps makeOps(QrealAnimator* const target); + static TargetOps makeOps(QStringAnimator* const target); + using PropertyBindingMap = std::map>; bool getBindings(PropertyBindingMap& bindings); void updateScriptBindings(); @@ -76,7 +101,14 @@ class ExpressionDialog : public Friction::Ui::Dialog Friction::Core::ExpressionPresets::Expr *expr, const bool &showId = true); - QrealAnimator* const mTarget; + Property* const mContext; + const QString mTargetName; + const ResultTester mResultTester; + const std::function mGetBindings; + const std::function mGetDefinitions; + const std::function mGetScript; + const ExpressionSetter mSetExpression; + const ExpressionSetter mSetExpressionAction; QTabWidget *mTab; int mTabEditor; diff --git a/src/app/GUI/Expressions/expressioneditor.cpp b/src/app/GUI/Expressions/expressioneditor.cpp index 6cc2b9e4b..098c5b5a0 100644 --- a/src/app/GUI/Expressions/expressioneditor.cpp +++ b/src/app/GUI/Expressions/expressioneditor.cpp @@ -30,11 +30,7 @@ #include "expressionhighlighter.h" #include "themesupport.h" -ExpressionEditor::ExpressionEditor(QrealAnimator * const target, - QWidget * const parent) : - ExpressionEditor(target, target->getExpressionBindingsString(), parent) {} - -ExpressionEditor::ExpressionEditor(QrealAnimator * const target, +ExpressionEditor::ExpressionEditor(Property * const target, const QString &text, QWidget * const parent) : QTextEdit(parent) @@ -197,4 +193,3 @@ QString ExpressionEditor::textUnderCursor() const { } return result; } - diff --git a/src/app/GUI/Expressions/expressioneditor.h b/src/app/GUI/Expressions/expressioneditor.h index f618756c3..311089a82 100644 --- a/src/app/GUI/Expressions/expressioneditor.h +++ b/src/app/GUI/Expressions/expressioneditor.h @@ -33,7 +33,7 @@ #include #include "GUI/global.h" -#include "Animators/qrealanimator.h" +#include "Properties/property.h" class ExpressionHighlighter; @@ -42,10 +42,7 @@ class ExpressionEditor : public QTextEdit Q_OBJECT public: - ExpressionEditor(QrealAnimator* const target, - QWidget* const parent); - - ExpressionEditor(QrealAnimator* const target, + ExpressionEditor(Property* const target, const QString& text, QWidget* const parent); void setCompleterList(const QStringList& values); diff --git a/src/app/GUI/Expressions/expressionhighlighter.cpp b/src/app/GUI/Expressions/expressionhighlighter.cpp index 5e4534f1c..41683dd01 100644 --- a/src/app/GUI/Expressions/expressionhighlighter.cpp +++ b/src/app/GUI/Expressions/expressionhighlighter.cpp @@ -32,7 +32,7 @@ #include "expressioneditor.h" ExpressionHighlighter::ExpressionHighlighter( - QrealAnimator * const target, + Property * const target, ExpressionEditor * const editor, QTextDocument *parent) : QSyntaxHighlighter(parent), diff --git a/src/app/GUI/Expressions/expressionhighlighter.h b/src/app/GUI/Expressions/expressionhighlighter.h index fb7052bf9..0733da7f6 100644 --- a/src/app/GUI/Expressions/expressionhighlighter.h +++ b/src/app/GUI/Expressions/expressionhighlighter.h @@ -28,13 +28,13 @@ #include -#include "Animators/qrealanimator.h" +#include "Properties/property.h" class ExpressionEditor; class ExpressionHighlighter : public QSyntaxHighlighter { public: - ExpressionHighlighter(QrealAnimator* const target, + ExpressionHighlighter(Property* const target, ExpressionEditor* const editor, QTextDocument *parent); @@ -68,7 +68,7 @@ class ExpressionHighlighter : public QSyntaxHighlighter { QStringList mBaseComplete; QStringList mBaseVarsComplete; - QrealAnimator* const mTarget; + Property* const mTarget; ComplexAnimator* const mSearchCtxt; ExpressionEditor* const mEditor; }; diff --git a/src/core/Animators/qstringanimator.cpp b/src/core/Animators/qstringanimator.cpp index 0d506eefa..1f10ca384 100644 --- a/src/core/Animators/qstringanimator.cpp +++ b/src/core/Animators/qstringanimator.cpp @@ -29,10 +29,46 @@ #include "simplemath.h" #include "svgexporthelpers.h" #include "appsupport.h" +#include "simpletask.h" +#include "ReadWrite/evformat.h" +#include "typemenu.h" +#include "GUI/dialogsinterface.h" +#include "Expressions/expression.h" +#include "canvas.h" QStringAnimator::QStringAnimator(const QString &name) : SteppedAnimator(name) {} +void QStringAnimator::prp_writeProperty_impl(eWriteStream& dst) const { + SteppedAnimator::prp_writeProperty_impl(dst); + dst << !!mExpression; + if(mExpression) { + dst << mExpression->bindingsString(); + dst << mExpression->definitionsString(); + dst << mExpression->scriptString(); + } +} + +void QStringAnimator::prp_readProperty_impl(eReadStream& src) { + SteppedAnimator::prp_readProperty_impl(src); + + if(src.evFileVersion() >= EvFormat::textExpression) { + bool hasExpr = false; + src >> hasExpr; + if(hasExpr) { + QString bindingsStr; src >> bindingsStr; + QString definitionsStr; src >> definitionsStr; + QString scriptStr; src >> scriptStr; + SimpleTask::sScheduleContexted(this, + [this, bindingsStr, definitionsStr, scriptStr]() { + setExpression(Expression::sCreate( + bindingsStr, definitionsStr, scriptStr, this, + Expression::sQStringAnimatorTester)); + }); + } + } +} + QDomElement createTextElement(SvgExporter& exp, const QString& text) { auto textEle = exp.createElement("text"); @@ -75,6 +111,237 @@ void QStringAnimator::saveSVG(SvgExporter& exp, QDomElement& parent, } } +QJSValue QStringAnimator::prp_getBaseJSValue(QJSEngine& e) const { + Q_UNUSED(e) + return getBaseValueAtRelFrame(anim_getCurrentRelFrame()); +} + +QJSValue QStringAnimator::prp_getBaseJSValue(QJSEngine& e, + const qreal relFrame) const { + Q_UNUSED(e) + return getBaseValueAtRelFrame(relFrame); +} + +QJSValue QStringAnimator::prp_getEffectiveJSValue(QJSEngine& e) const { + Q_UNUSED(e) + return getEffectiveValue(anim_getCurrentRelFrame()); +} + +QJSValue QStringAnimator::prp_getEffectiveJSValue(QJSEngine& e, + const qreal relFrame) const { + Q_UNUSED(e) + return getEffectiveValue(relFrame); +} + +void QStringAnimator::prp_setupTreeViewMenu(PropertyMenu * const menu) { + if(menu->hasActionsForType()) { return; } + menu->addedActionsForType(); + + const PropertyMenu::PlainSelectedOp sOp = + [](QStringAnimator * aTarget) { + const auto& iface = DialogsInterface::instance(); + iface.showExpressionDialog(aTarget); + }; + menu->addPlainAction(QIcon::fromTheme("preferences"), + tr("Set Expression"), sOp); + + const PropertyMenu::PlainSelectedOp aOp = + [](QStringAnimator * aTarget) { + const auto scene = aTarget->getFirstAncestor(); + if(!scene) return; + const auto relRange = aTarget->prp_absRangeToRelRange( + scene->getFrameRange()); + aTarget->applyExpression(relRange, true); + }; + menu->addPlainAction(QIcon::fromTheme("dialog-ok"), + tr("Apply Expression"), aOp)->setEnabled(hasExpression()); + + const PropertyMenu::PlainSelectedOp cOp = + [](QStringAnimator * aTarget) { + aTarget->clearExpressionAction(); + }; + menu->addPlainAction(QIcon::fromTheme("trash"), + tr("Clear Expression"), cOp)->setEnabled(hasExpression()); + + menu->addSeparator(); + Animator::prp_setupTreeViewMenu(menu); +} + +FrameRange QStringAnimator::prp_getIdenticalRelRange(const int relFrame) const { + const auto base = SteppedAnimator::prp_getIdenticalRelRange(relFrame); + if(mExpression) { + const int absFrame = prp_relFrameToAbsFrame(relFrame); + return base * mExpression->identicalRelRange(absFrame); + } + return base; +} + +FrameRange QStringAnimator::prp_nextNonUnaryIdenticalRelRange( + const int relFrame) const { + if(hasExpression()) { + const int absFrame = prp_relFrameToAbsFrame(relFrame); + for(int i = relFrame, j = absFrame; i < FrameRange::EMAX; i++, j++) { + FrameRange range{FrameRange::EMIN, FrameRange::EMAX}; + int lowestMax = INT_MAX; + + { + const auto childRange = + SteppedAnimator::prp_nextNonUnaryIdenticalRelRange(i); + lowestMax = qMin(lowestMax, childRange.fMax); + range *= childRange; + } + { + const auto childRange = mExpression->nextNonUnaryIdenticalRelRange(j); + lowestMax = qMin(lowestMax, childRange.fMax); + range *= childRange; + } + + if(!range.isUnary()) return range; + const int di = lowestMax - i; + i += di; + j += di; + } + return FrameRange::EMINMAX; + } + return SteppedAnimator::prp_nextNonUnaryIdenticalRelRange(relFrame); +} + +void QStringAnimator::prp_afterFrameShiftChanged( + const FrameRange& oldAbsRange, + const FrameRange& newAbsRange) { + SteppedAnimator::prp_afterFrameShiftChanged(oldAbsRange, newAbsRange); + updateExpressionRelFrame(); +} + +void QStringAnimator::anim_setAbsFrame(const int frame) { + SteppedAnimator::anim_setAbsFrame(frame); + const bool exprFrameChanged = updateExpressionRelFrame(); + const bool exprValueChanged = updateCurrentEffectiveValue(); + if(exprFrameChanged || exprValueChanged) { + prp_afterChangedCurrent(UpdateReason::frameChange); + } +} + +bool QStringAnimator::prp_dependsOn(const Property* const prop) const { + if(!mExpression) return false; + return mExpression->dependsOn(prop); +} + +bool QStringAnimator::hasValidExpression() const { + return mExpression ? mExpression->isValid() : false; +} + +QString QStringAnimator::getExpressionBindingsString() const { + if(!mExpression) return ""; + return mExpression->bindingsString(); +} + +QString QStringAnimator::getExpressionDefinitionsString() const { + if(!mExpression) return ""; + return mExpression->definitionsString(); +} + +QString QStringAnimator::getExpressionScriptString() const { + if(!mExpression) return ""; + return mExpression->scriptString(); +} + +void QStringAnimator::setExpressionAction(const qsptr& expression) { + if(expression || mExpression) { + prp_pushUndoRedoName(tr("Change Expression")); + UndoRedo ur; + const auto oldValue = mExpression.sptr(); + const auto newValue = expression; + ur.fUndo = [this, oldValue]() { setExpression(oldValue); }; + ur.fRedo = [this, newValue]() { setExpression(newValue); }; + prp_addUndoRedo(ur); + } + setExpression(expression); +} + +void QStringAnimator::setExpression(const qsptr& expression) { + auto& conn = mExpression.assign(expression); + if(expression) { + const int absFrame = anim_getCurrentAbsFrame(); + expression->setAbsFrame(absFrame); + conn << connect(expression.get(), &Expression::currentValueChanged, + this, [this]() { + if(updateCurrentEffectiveValue()) { + prp_afterChangedCurrent(UpdateReason::frameChange); + } + }); + conn << connect(expression.get(), &Expression::relRangeChanged, + this, [this](const FrameRange& range) { + prp_afterChangedRelRange(range); + }); + } + updateCurrentEffectiveValue(); + prp_afterWholeInfluenceRangeChanged(); +} + +void QStringAnimator::applyExpression(const FrameRange& relRange, + const bool action) { + if(!hasValidExpression()) return; + if(!relRange.isValid()) return; + + prp_pushUndoRedoName(tr("Apply Expression")); + + QString currentValue; + bool first = true; + for(int relFrame = relRange.fMin; relFrame <= relRange.fMax; relFrame++) { + const int absFrame = prp_relFrameToAbsFrame(relFrame); + mExpression->setAbsFrame(absFrame); + const QString value = getEffectiveValue(relFrame); + if(first || value != currentValue) { + currentValue = value; + if(auto key = anim_getKeyAtRelFrame(relFrame)) { + key->setValue(value); + } else { + const auto newKey = enve::make_shared(value, relFrame, this); + if(action) anim_appendKeyAction(newKey); + else anim_appendKey(newKey); + } + first = false; + } + } + + if(action) { setExpressionAction(nullptr); } + else { setExpression(nullptr); } +} + +QString QStringAnimator::getValueAtRelFrame(const qreal frame) const { + return getEffectiveValue(frame); +} + +QString QStringAnimator::getBaseValueAtRelFrame(const qreal frame) const { + return SteppedAnimator::getValueAtRelFrame(frame); +} + +QString QStringAnimator::getEffectiveValue(const qreal relFrame) const { + if(mExpression) { + const auto ret = mExpression->evaluate(relFrame); + if(ret.isNull() || ret.isUndefined()) return QString(); + return ret.toString(); + } + return getBaseValueAtRelFrame(relFrame); +} + +bool QStringAnimator::updateExpressionRelFrame() { + if(!mExpression) return false; + const int absFrame = anim_getCurrentAbsFrame(); + return mExpression->setAbsFrame(absFrame); +} + +bool QStringAnimator::updateCurrentEffectiveValue() { + if(!mExpression) return false; + const auto ret = mExpression->evaluate(); + if(ret.isNull() || ret.isUndefined()) return false; + const QString newValue = ret.toString(); + if(newValue == mCurrentEffectiveValue) return false; + mCurrentEffectiveValue = newValue; + return true; +} + void QStringAnimator::prp_readPropertyXEV_impl( const QDomElement& ele, const XevImporter& imp) { if(ele.hasAttribute("frames")) { @@ -99,6 +366,25 @@ void QStringAnimator::prp_readPropertyXEV_impl( setCurrentValue(value); }); } + + const auto expression = ele.firstChildElement("Expression"); + if(!expression.isNull()) { + const auto defsEle = expression.firstChildElement("Definitions"); + const QString definitions = defsEle.text(); + + const auto bindEle = expression.firstChildElement("Bindings"); + const QString bindings = bindEle.text(); + + const auto scriptEle = expression.firstChildElement("Script"); + const QString script = scriptEle.text(); + + SimpleTask::sScheduleContexted(this, + [this, bindings, definitions, script]() { + setExpression(Expression::sCreate( + bindings, definitions, script, this, + Expression::sQStringAnimatorTester)); + }); + } } void saveTextXEV(const QString& path, const XevExporter& exp, @@ -125,5 +411,29 @@ QDomElement QStringAnimator::prp_writePropertyXEV_impl(const XevExporter& exp) c saveTextXEV("value.txt", exp, getCurrentValue()); } + if(hasExpression()) { + auto expression = exp.createElement("Expression"); + + const auto definitions = mExpression->definitionsString(); + const auto defsNode = exp.createTextNode(definitions); + auto defsEle = exp.createElement("Definitions"); + defsEle.appendChild(defsNode); + expression.appendChild(defsEle); + + const auto bindings = mExpression->bindingsString(); + const auto bindNode = exp.createTextNode(bindings); + auto bindEle = exp.createElement("Bindings"); + bindEle.appendChild(bindNode); + expression.appendChild(bindEle); + + const auto script = mExpression->scriptString(); + const auto scriptNode = exp.createTextNode(script); + auto scriptEle = exp.createElement("Script"); + scriptEle.appendChild(scriptNode); + expression.appendChild(scriptEle); + + result.appendChild(expression); + } + return result; } diff --git a/src/core/Animators/qstringanimator.h b/src/core/Animators/qstringanimator.h index 480b2ab82..187e3bd31 100644 --- a/src/core/Animators/qstringanimator.h +++ b/src/core/Animators/qstringanimator.h @@ -26,20 +26,62 @@ #ifndef QSTRINGANIMATOR_H #define QSTRINGANIMATOR_H #include "Animators/steppedanimator.h" +#include "../conncontextptr.h" typedef KeyT QStringKey; +class Expression; + class CORE_EXPORT QStringAnimator : public SteppedAnimator { e_OBJECT protected: QStringAnimator(const QString& name); - void prp_readPropertyXEV_impl(const QDomElement& ele, const XevImporter& imp); - QDomElement prp_writePropertyXEV_impl(const XevExporter& exp) const; + void prp_writeProperty_impl(eWriteStream& dst) const override; + void prp_readProperty_impl(eReadStream& src) override; + + void prp_readPropertyXEV_impl(const QDomElement& ele, const XevImporter& imp) override; + QDomElement prp_writePropertyXEV_impl(const XevExporter& exp) const override; public: using PropSetter = std::function; void saveSVG(SvgExporter& exp, QDomElement& parent, const PropSetter& propSetter) const; + + QJSValue prp_getBaseJSValue(QJSEngine& e) const override; + QJSValue prp_getBaseJSValue(QJSEngine& e, const qreal relFrame) const override; + QJSValue prp_getEffectiveJSValue(QJSEngine& e) const override; + QJSValue prp_getEffectiveJSValue(QJSEngine& e, const qreal relFrame) const override; + + void prp_setupTreeViewMenu(PropertyMenu * const menu) override; + + FrameRange prp_getIdenticalRelRange(const int relFrame) const override; + FrameRange prp_nextNonUnaryIdenticalRelRange(const int relFrame) const override; + void prp_afterFrameShiftChanged(const FrameRange& oldAbsRange, + const FrameRange& newAbsRange) override; + void anim_setAbsFrame(const int frame) override; + + bool prp_dependsOn(const Property* const prop) const override; + bool hasValidExpression() const; + bool hasExpression() const { return mExpression; } + void clearExpressionAction() { setExpressionAction(nullptr); } + + QString getExpressionBindingsString() const; + QString getExpressionDefinitionsString() const; + QString getExpressionScriptString() const; + + void setExpression(const qsptr& expression); + void setExpressionAction(const qsptr& expression); + void applyExpression(const FrameRange& relRange, const bool action); + + QString getValueAtRelFrame(const qreal frame) const; +private: + QString getBaseValueAtRelFrame(const qreal frame) const; + QString getEffectiveValue(const qreal relFrame) const; + bool updateExpressionRelFrame(); + bool updateCurrentEffectiveValue(); + + QString mCurrentEffectiveValue; + ConnContextQSPtr mExpression; }; #endif // QSTRINGANIMATOR_H diff --git a/src/core/Expressions/expression.cpp b/src/core/Expressions/expression.cpp index 48e95823e..6ab3da82c 100644 --- a/src/core/Expressions/expression.cpp +++ b/src/core/Expressions/expression.cpp @@ -33,6 +33,12 @@ Expression::ResultTester Expression::sQrealAnimatorTester = if(!val.isNumber()) PrettyRuntimeThrow("Invalid return type"); }; +Expression::ResultTester Expression::sQStringAnimatorTester = + [](const QJSValue& val) { + if(val.isNull() || val.isUndefined()) + PrettyRuntimeThrow("Invalid return type"); + }; + Expression::Expression(const QString& definitionsStr, const QString& scriptStr, PropertyBindingMap&& bindings, diff --git a/src/core/Expressions/expression.h b/src/core/Expressions/expression.h index 0f18b8d1f..fece7eb98 100644 --- a/src/core/Expressions/expression.h +++ b/src/core/Expressions/expression.h @@ -58,6 +58,7 @@ class CORE_EXPORT Expression : public QObject { const ResultTester& resultTester); static ResultTester sQrealAnimatorTester; + static ResultTester sQStringAnimatorTester; bool setAbsFrame(const int absFrame); diff --git a/src/core/GUI/dialogsinterface.h b/src/core/GUI/dialogsinterface.h index cb04b405b..74c857219 100644 --- a/src/core/GUI/dialogsinterface.h +++ b/src/core/GUI/dialogsinterface.h @@ -30,6 +30,8 @@ class DurationRectangle; class AnimationBox; +class QrealAnimator; +class QStringAnimator; using ShaderOptions = QList>; @@ -44,6 +46,8 @@ class CORE_EXPORT DialogsInterface { const QString& name, const ShaderOptions& options) const = 0; virtual void showExpressionDialog( QrealAnimator* const target) const = 0; + virtual void showExpressionDialog( + QStringAnimator* const target) const = 0; virtual void showApplyExpressionDialog( QrealAnimator* const target) const = 0; virtual void showDurationSettingsDialog( diff --git a/src/core/ReadWrite/evformat.h b/src/core/ReadWrite/evformat.h index c9011d813..f9fc0eedd 100644 --- a/src/core/ReadWrite/evformat.h +++ b/src/core/ReadWrite/evformat.h @@ -47,6 +47,7 @@ namespace EvFormat { subPathOffset = 32, avStretch = 33, grid = 34, + textExpression = 35, nextVersion };