diff --git a/src/app/GUI/Expressions/expressiondialog.cpp b/src/app/GUI/Expressions/expressiondialog.cpp index 905c4f366..cd8a59254 100644 --- a/src/app/GUI/Expressions/expressiondialog.cpp +++ b/src/app/GUI/Expressions/expressiondialog.cpp @@ -307,6 +307,9 @@ void addBasicDefs(QsciAPIs* const target) target->add("Math.tanh(x)"); target->add("Math.trunc(x)"); + target->add("valueAtTime(time)"); + target->add("valueAtTime(\"bindingName\", time)"); + // add expressions presets const auto expressions = eSettings::sInstance->fExpressions.getAll(); for (const auto &expr : expressions) { @@ -647,7 +650,7 @@ bool ExpressionDialog::apply(const bool action) QJSValue eEvaluate; try { Expression::sAddScriptTo(scriptStr, bindings, *engine, eEvaluate, - Expression::sQrealAnimatorTester); + Expression::sQrealAnimatorTester, mTarget); } catch (const std::exception& e) { mScriptError->setText(e.what()); mBindingsButton->setIcon(mRedDotIcon); diff --git a/src/app/GUI/Expressions/expressionhighlighter.cpp b/src/app/GUI/Expressions/expressionhighlighter.cpp index 5e4534f1c..37920ff09 100644 --- a/src/app/GUI/Expressions/expressionhighlighter.cpp +++ b/src/app/GUI/Expressions/expressionhighlighter.cpp @@ -50,7 +50,7 @@ ExpressionHighlighter::ExpressionHighlighter( "\\s*=\\s*" + propPath); mFrameValueSetRegex = QRegularExpression("^\\s*" "([A-Za-z_][A-Za-z0-9_]*)" - "\\s*=\\s*(\\$frame|\\$scene.fps|\\$scene.width|\\$scene.height|\\$scene.rangeMin|\\$scene.rangeMax|\\$value)"); + "\\s*=\\s*(\\$frame|\\$time|\\$scene.fps|\\$scene.width|\\$scene.height|\\$scene.rangeMin|\\$scene.rangeMax|\\$value)"); // const auto propPathRegex = QRegularExpression(propPath); mPropPathFormat.setFontWeight(QFont::Bold); @@ -62,6 +62,7 @@ ExpressionHighlighter::ExpressionHighlighter( const QStringList specs = { QStringLiteral("$value"), QStringLiteral("$frame"), + QStringLiteral("$time"), QStringLiteral("$scene.fps"), QStringLiteral("$scene.width"), QStringLiteral("$scene.height"), diff --git a/src/app/GUI/graphboxeslist.cpp b/src/app/GUI/graphboxeslist.cpp index ade62e0a8..aee793bf4 100644 --- a/src/app/GUI/graphboxeslist.cpp +++ b/src/app/GUI/graphboxeslist.cpp @@ -123,7 +123,7 @@ bool KeysView::graphEasingApplyExpression(QrealAnimator *anim, QJSValue eEvaluate; try { Expression::sAddScriptTo(script, bindings, *engine, eEvaluate, - Expression::sQrealAnimatorTester); + Expression::sQrealAnimatorTester, anim); } catch(const std::exception& e) { return false; } try { diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 0747e965c..0aa5c0b2a 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -81,9 +81,11 @@ set( grid.cpp Boxes/nullobject.cpp Expressions/expression.cpp + Expressions/expressioncontext.cpp Expressions/expressionpresets.cpp Expressions/framebinding.cpp Expressions/scenebinding.cpp + Expressions/timebinding.cpp Expressions/propertybinding.cpp Animators/SmartPath/listofnodes.cpp Animators/SmartPath/smartpath.cpp diff --git a/src/core/Expressions/expression.cpp b/src/core/Expressions/expression.cpp index 48e95823e..b32d62336 100644 --- a/src/core/Expressions/expression.cpp +++ b/src/core/Expressions/expression.cpp @@ -25,8 +25,14 @@ #include "expression.h" +#include "expressioncontext.h" #include "exceptions.h" #include "Private/esettings.h" +#include "canvas.h" + +namespace { +const QString gExpressionContextName = "__frictionExpressionContext"; +} Expression::ResultTester Expression::sQrealAnimatorTester = [](const QJSValue& val) { @@ -42,12 +48,24 @@ Expression::Expression(const QString& definitionsStr, mScriptStr(scriptStr), mEEvaluate(std::move(eEvaluate)), mBindings(std::move(bindings)), - mEngine(std::move(engine)) { + mEngine(std::move(engine)), + mExpressionContext(qobject_cast( + mEngine->globalObject().property(gExpressionContextName).toQObject())) { for(const auto& binding : mBindings) { connect(binding.second.get(), &PropertyBinding::currentValueChanged, this, &Expression::currentValueChanged); connect(binding.second.get(), &PropertyBinding::relRangeChanged, - this, &Expression::relRangeChanged); + this, [this](const FrameRange& range) { + emit relRangeChanged(mExpressionContext && + mExpressionContext->usesTemporalSampling() ? + FrameRange::EMINMAX : range); + }); + } + if(mExpressionContext) { + connect(mExpressionContext, &ExpressionContext::temporalSamplingUsed, + this, [this]() { + emit relRangeChanged(FrameRange::EMINMAX); + }); } } @@ -75,10 +93,45 @@ void Expression::sAddDefinitionsTo(const QString& definitionsStr, void Expression::sAddScriptTo(const QString& scriptStr, const PropertyBindingMap& bindings, QJSEngine& e, QJSValue& eEvaluate, - const ResultTester& resultTester) { + const ResultTester& resultTester, + const Property* const context) { + if(bindings.find("valueAtTime") != bindings.end()) { + PrettyRuntimeThrow("'valueAtTime' is a reserved expression function"); + } + + const auto expressionContext = + new ExpressionContext(context, bindings, &e); + expressionContext->setParent(&e); + e.globalObject().setProperty( + gExpressionContextName, e.newQObject(expressionContext)); + + const auto builtinsResult = e.evaluate( + "var valueAtTime = function(sourceOrTime, time) {" + " if (arguments.length === 1) {" + " if (typeof sourceOrTime !== 'number' || !isFinite(sourceOrTime)) {" + " throw new TypeError(\"valueAtTime(): time must be a finite number\");" + " }" + " return __frictionExpressionContext.valueAtTime(sourceOrTime);" + " }" + " if (arguments.length === 2) {" + " if (typeof sourceOrTime !== 'string') {" + " throw new TypeError(\"valueAtTime(): the first argument must be a binding name\");" + " }" + " if (typeof time !== 'number' || !isFinite(time)) {" + " throw new TypeError(\"valueAtTime(): time must be a finite number\");" + " }" + " return __frictionExpressionContext.bindingValueAtTime(sourceOrTime, time);" + " }" + " throw new TypeError(\"valueAtTime() expects one or two arguments\");" + "};"); + throwIfError(builtinsResult, "Expression built-ins"); + QStringList bindingVars; QJSValueList testArgs; + const auto scene = context ? context->getParentScene() : nullptr; + const int testAbsFrame = scene ? scene->getCurrentFrame() : 0; for(const auto& binding : bindings) { + binding.second->setAbsFrame(testAbsFrame); bindingVars << binding.first; testArgs << binding.second->getJSValue(e); } @@ -108,7 +161,8 @@ qsptr Expression::sCreate(const QString& bindingsStr, auto engine = std::make_unique(); sAddDefinitionsTo(definitionsStr, *engine); QJSValue eEvaluate; - sAddScriptTo(scriptStr, bindings, *engine, eEvaluate, resultTester); + sAddScriptTo(scriptStr, bindings, *engine, eEvaluate, + resultTester, context); return sCreate(definitionsStr, scriptStr, std::move(bindings), std::move(engine), @@ -138,6 +192,8 @@ bool Expression::setAbsFrame(const int absFrame) { } bool Expression::isStatic() const { + if(mExpressionContext && mExpressionContext->usesTemporalSampling()) + return false; return identicalRelRange(0) == FrameRange::EMINMAX; } @@ -169,16 +225,18 @@ QJSValue Expression::evaluate(const qreal relFrame) { QJSValueList values; for (const auto& binding : mBindings) { - QString path = binding.second->path(); - QJSValue val = binding.second->getJSValue(*mEngine, relFrame); - if (path == "$frame") { values << QJSValue(relFrame); } - else { values << val; } + values << binding.second->getJSValue(*mEngine, relFrame); } QJSValue res = mEEvaluate.call(values); return res; } FrameRange Expression::identicalRelRange(const int absFrame) const { + if(mExpressionContext && mExpressionContext->usesTemporalSampling()) { + const int relFrame = qRound( + mExpressionContext->contextRelFrame(absFrame)); + return {relFrame, relFrame}; + } FrameRange result{FrameRange::EMINMAX}; for(const auto& binding : mBindings) { const auto prop = binding.second.get(); @@ -190,6 +248,10 @@ FrameRange Expression::identicalRelRange(const int absFrame) const { FrameRange Expression::nextNonUnaryIdenticalRelRange(const int absFrame) const { + if(mExpressionContext && mExpressionContext->usesTemporalSampling()) { + Q_UNUSED(absFrame) + return {FrameRange::EMAX/2, FrameRange::EMAX}; + } for (int i = absFrame; i < FrameRange::EMAX; i++) { FrameRange result{FrameRange::EMINMAX}; int lowestMax = INT_MAX; diff --git a/src/core/Expressions/expression.h b/src/core/Expressions/expression.h index 0f18b8d1f..2ebd7270e 100644 --- a/src/core/Expressions/expression.h +++ b/src/core/Expressions/expression.h @@ -31,6 +31,8 @@ #include "propertybindingparser.h" +class ExpressionContext; + class CORE_EXPORT Expression : public QObject { Q_OBJECT Expression(const QString& definitionsStr, @@ -45,7 +47,8 @@ class CORE_EXPORT Expression : public QObject { static void sAddScriptTo(const QString& scriptStr, const PropertyBindingMap& bindings, QJSEngine& e, QJSValue& eEvaluate, - const ResultTester& resultTester); + const ResultTester& resultTester, + const Property* context = nullptr); static qsptr sCreate(const QString& definitionsStr, const QString& scriptStr, PropertyBindingMap&& bindings, @@ -88,6 +91,7 @@ class CORE_EXPORT Expression : public QObject { QJSValue mEEvaluate; const PropertyBindingMap mBindings; const std::unique_ptr mEngine; + ExpressionContext* const mExpressionContext; }; #endif // EXPRESSION_H diff --git a/src/core/Expressions/expressioncontext.cpp b/src/core/Expressions/expressioncontext.cpp new file mode 100644 index 000000000..c1581449d --- /dev/null +++ b/src/core/Expressions/expressioncontext.cpp @@ -0,0 +1,104 @@ +/* +# +# Friction - https://friction.graphics +# +# Copyright (c) Ole-André Rodlie and contributors +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +*/ + +#include "expressioncontext.h" + +#include +#include + +ExpressionContext::ExpressionContext( + const Property* const context, + const PropertyBindingMap& bindings, + QJSEngine* const engine) : + mContext(context), + mBindings(bindings), + mEngine(engine) {} + +QJSValue ExpressionContext::valueAtTime(const qreal time) { + markTemporalSamplingUsed(); + if(!mContext) { + return throwReferenceError( + "valueAtTime() has no expression property context"); + } + + qreal absFrame; + if(!absFrameAtTime(time, absFrame)) return QJSValue(); + const qreal relFrame = mContext->prp_absFrameToRelFrameF(absFrame); + return mContext->prp_getBaseJSValue(*mEngine, relFrame); +} + +QJSValue ExpressionContext::bindingValueAtTime( + const QString& bindingName, const qreal time) { + markTemporalSamplingUsed(); + const auto binding = mBindings.find(bindingName); + if(binding == mBindings.end()) { + return throwReferenceError( + "valueAtTime(): unknown binding '" + bindingName + "'"); + } + + qreal absFrame; + if(!absFrameAtTime(time, absFrame)) return QJSValue(); + return binding->second->getJSValueAtAbsFrame(*mEngine, absFrame); +} + +qreal ExpressionContext::contextRelFrame(const qreal absFrame) const { + if(!mContext) return absFrame; + return mContext->prp_absFrameToRelFrameF(absFrame); +} + +bool ExpressionContext::absFrameAtTime(const qreal time, qreal& absFrame) { + if(!qIsFinite(time)) { + throwTypeError("valueAtTime(): time must be a finite number"); + return false; + } + if(!mContext) { + throwReferenceError( + "valueAtTime() has no expression property context"); + return false; + } + + const qreal fps = mContext->prp_getSceneFPS(); + if(!qIsFinite(fps) || fps <= 0) { + throwTypeError("valueAtTime(): scene FPS must be greater than zero"); + return false; + } + + absFrame = time*fps; + if(!qIsFinite(absFrame) || + absFrame <= FrameRange::EMIN || absFrame >= FrameRange::EMAX) { + throwRangeError("valueAtTime(): sampled frame is outside the valid range"); + return false; + } + return true; +} + +QJSValue ExpressionContext::throwTypeError(const QString& message) { + mEngine->throwError(QJSValue::TypeError, message); + return QJSValue(); +} + +QJSValue ExpressionContext::throwRangeError(const QString& message) { + mEngine->throwError(QJSValue::RangeError, message); + return QJSValue(); +} + +QJSValue ExpressionContext::throwReferenceError(const QString& message) { + mEngine->throwError(QJSValue::ReferenceError, message); + return QJSValue(); +} + +void ExpressionContext::markTemporalSamplingUsed() { + if(mUsesTemporalSampling) return; + mUsesTemporalSampling = true; + emit temporalSamplingUsed(); +} diff --git a/src/core/Expressions/expressioncontext.h b/src/core/Expressions/expressioncontext.h new file mode 100644 index 000000000..f8089858f --- /dev/null +++ b/src/core/Expressions/expressioncontext.h @@ -0,0 +1,54 @@ +/* +# +# Friction - https://friction.graphics +# +# Copyright (c) Ole-André Rodlie and contributors +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +*/ + +#ifndef EXPRESSIONCONTEXT_H +#define EXPRESSIONCONTEXT_H + +#include +#include + +#include "propertybindingparser.h" + +class QJSEngine; + +class ExpressionContext : public QObject { + Q_OBJECT +public: + ExpressionContext(const Property* context, + const PropertyBindingMap& bindings, + QJSEngine* engine); + + Q_INVOKABLE QJSValue valueAtTime(qreal time); + Q_INVOKABLE QJSValue bindingValueAtTime(const QString& bindingName, + qreal time); + + bool usesTemporalSampling() const { return mUsesTemporalSampling; } + qreal contextRelFrame(const qreal absFrame) const; + +signals: + void temporalSamplingUsed(); + +private: + bool absFrameAtTime(qreal time, qreal& absFrame); + QJSValue throwTypeError(const QString& message); + QJSValue throwRangeError(const QString& message); + QJSValue throwReferenceError(const QString& message); + void markTemporalSamplingUsed(); + + const qptr mContext; + const PropertyBindingMap mBindings; + QJSEngine* const mEngine; + bool mUsesTemporalSampling = false; +}; + +#endif // EXPRESSIONCONTEXT_H diff --git a/src/core/Expressions/framebinding.cpp b/src/core/Expressions/framebinding.cpp index 41f082124..a23d752b2 100644 --- a/src/core/Expressions/framebinding.cpp +++ b/src/core/Expressions/framebinding.cpp @@ -40,8 +40,7 @@ QJSValue FrameBinding::getJSValue(QJSEngine& e) { QJSValue FrameBinding::getJSValue(QJSEngine& e, const qreal relFrame) { Q_UNUSED(e) - Q_UNUSED(relFrame) - return this->relFrame(); + return relFrame; } FrameRange FrameBinding::identicalRelRange(const int absFrame) { diff --git a/src/core/Expressions/presets/valueAtTime.fexpr b/src/core/Expressions/presets/valueAtTime.fexpr new file mode 100644 index 000000000..08ae2b372 --- /dev/null +++ b/src/core/Expressions/presets/valueAtTime.fexpr @@ -0,0 +1,13 @@ +[General] +author=Friction +bindings="// $value samples this property's pre-expression value.\n// Replace it with any mapped property when needed.\n// Example: source = RectangleBox.transform.translation.x;\nsource = $value;\n// $time is the absolute scene time in seconds.\ntime = $time;" +categories= +definitions= +description="Samples a property at another time. Map 'source' to any property, then change delaySeconds to make the current property follow it with a delay. The script also shows how to sample the expression property's own pre-expression value." +highlighters=valueAtTime +id=graphics.friction.valueAtTime +license= +script="// Delay the mapped source by two seconds.\nvar delaySeconds = 2;\nvar delayedTime = time - delaySeconds;\n\n// To sample this property's own pre-expression value instead, use:\n// return valueAtTime(delayedTime);\n\nreturn valueAtTime(\"source\", delayedTime)" +title=Value at Time (Delay) +url= +version=1 diff --git a/src/core/Expressions/propertybinding.cpp b/src/core/Expressions/propertybinding.cpp index 3092f6149..a6d4c662e 100644 --- a/src/core/Expressions/propertybinding.cpp +++ b/src/core/Expressions/propertybinding.cpp @@ -78,9 +78,22 @@ QJSValue PropertyBinding::getJSValue(QJSEngine& e) { } QJSValue PropertyBinding::getJSValue(QJSEngine& e, const qreal relFrame) { - if(mBindPathValid && mBindProperty) - return mBindProperty->prp_getEffectiveJSValue(e, relFrame); - else return QJSValue::NullValue; + if(!mBindPathValid || !mBindProperty) return QJSValue::NullValue; + + qreal bindRelFrame = relFrame; + if(mContext) { + const qreal absFrame = mContext->prp_relFrameToAbsFrameF(relFrame); + bindRelFrame = mBindProperty->prp_absFrameToRelFrameF(absFrame); + } + return mBindProperty->prp_getEffectiveJSValue(e, bindRelFrame); +} + +QJSValue PropertyBinding::getJSValueAtAbsFrame(QJSEngine& e, + const qreal absFrame) { + if(!mBindPathValid || !mBindProperty) return QJSValue::NullValue; + const qreal bindRelFrame = + mBindProperty->prp_absFrameToRelFrameF(absFrame); + return mBindProperty->prp_getEffectiveJSValue(e, bindRelFrame); } bool PropertyBinding::dependsOn(const Property* const prop) { diff --git a/src/core/Expressions/propertybinding.h b/src/core/Expressions/propertybinding.h index e6cbbd16f..6754f5111 100644 --- a/src/core/Expressions/propertybinding.h +++ b/src/core/Expressions/propertybinding.h @@ -52,6 +52,7 @@ class CORE_EXPORT PropertyBinding : public PropertyBindingBase { QJSValue getJSValue(QJSEngine& e); QJSValue getJSValue(QJSEngine& e, const qreal relFrame); + QJSValue getJSValueAtAbsFrame(QJSEngine& e, const qreal absFrame); FrameRange identicalRelRange(const int absFrame); FrameRange nextNonUnaryIdenticalRelRange(const int absFrame); diff --git a/src/core/Expressions/propertybindingbase.cpp b/src/core/Expressions/propertybindingbase.cpp index b0298e721..7979d6a7e 100644 --- a/src/core/Expressions/propertybindingbase.cpp +++ b/src/core/Expressions/propertybindingbase.cpp @@ -28,6 +28,13 @@ PropertyBindingBase::PropertyBindingBase(const Property* const context) : mContext(context) {} +QJSValue PropertyBindingBase::getJSValueAtAbsFrame( + QJSEngine& e, const qreal absFrame) { + if(!mContext) return QJSValue::NullValue; + const qreal relFrame = mContext->prp_absFrameToRelFrameF(absFrame); + return getJSValue(e, relFrame); +} + bool PropertyBindingBase::setAbsFrame(const int absFrame) { if(mContext) { const qreal oldRelFrame = mRelFrame; diff --git a/src/core/Expressions/propertybindingbase.h b/src/core/Expressions/propertybindingbase.h index d4c745adf..d0a5ddf35 100644 --- a/src/core/Expressions/propertybindingbase.h +++ b/src/core/Expressions/propertybindingbase.h @@ -40,6 +40,8 @@ class CORE_EXPORT PropertyBindingBase : public QObject { public: virtual QJSValue getJSValue(QJSEngine& e) = 0; virtual QJSValue getJSValue(QJSEngine& e, const qreal relFrame) = 0; + virtual QJSValue getJSValueAtAbsFrame(QJSEngine& e, + const qreal absFrame); virtual FrameRange identicalRelRange(const int absFrame) = 0; virtual FrameRange nextNonUnaryIdenticalRelRange(const int absFrame) = 0; virtual QString path() const = 0; diff --git a/src/core/Expressions/propertybindingparser.cpp b/src/core/Expressions/propertybindingparser.cpp index 085000dfb..e1b1102cd 100644 --- a/src/core/Expressions/propertybindingparser.cpp +++ b/src/core/Expressions/propertybindingparser.cpp @@ -27,6 +27,7 @@ #include "exceptions.h" #include "framebinding.h" +#include "timebinding.h" #include "valuebinding.h" #include "scenebinding.h" #include "appsupport.h" @@ -103,6 +104,10 @@ bool parseFrame(const QString& exp, int& pos) { return parse(exp, pos, "$frame"); } +bool parseTime(const QString& exp, int& pos) { + return parse(exp, pos, "$time"); +} + bool parseSceneFPS(const QString& exp, int& pos) { return parse(exp, pos, "$scene.fps"); } @@ -148,6 +153,8 @@ qsptr PropertyBindingParser::parseBinding( skipSpaces(exp, pos); if(parseFrame(exp, pos)) { result = FrameBinding::sCreate(context); + } else if(parseTime(exp, pos)) { + result = TimeBinding::sCreate(context); } else if(parseSceneFPS(exp, pos)) { result = SceneBinding::sCreate(context, SceneBinding::SceneBindingFps); diff --git a/src/core/Expressions/timebinding.cpp b/src/core/Expressions/timebinding.cpp new file mode 100644 index 000000000..b33e1e224 --- /dev/null +++ b/src/core/Expressions/timebinding.cpp @@ -0,0 +1,55 @@ +/* +# +# Friction - https://friction.graphics +# +# Copyright (c) Ole-André Rodlie and contributors +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +*/ + +#include "timebinding.h" + +#include + +TimeBinding::TimeBinding(const Property* const context) : + PropertyBindingBase(context) {} + +qsptr TimeBinding::sCreate(const Property* const context) { + return qsptr(new TimeBinding(context)); +} + +QJSValue TimeBinding::getJSValue(QJSEngine& e) { + Q_UNUSED(e) + return valueForRelFrame(relFrame()); +} + +QJSValue TimeBinding::getJSValue(QJSEngine& e, const qreal relFrame) { + Q_UNUSED(e) + return valueForRelFrame(relFrame); +} + +FrameRange TimeBinding::identicalRelRange(const int absFrame) { + if(mContext) { + const int relFrame = mContext->prp_absFrameToRelFrame(absFrame); + return {relFrame, relFrame}; + } + return FrameRange::EMINMAX; +} + +FrameRange TimeBinding::nextNonUnaryIdenticalRelRange(const int absFrame) { + Q_UNUSED(absFrame) + if(mContext) return {FrameRange::EMAX/2, FrameRange::EMAX}; + return FrameRange::EMINMAX; +} + +QJSValue TimeBinding::valueForRelFrame(const qreal relFrame) const { + if(!mContext) return QJSValue::NullValue; + const qreal fps = mContext->prp_getSceneFPS(); + if(!qIsFinite(fps) || fps <= 0) return QJSValue::NullValue; + const qreal absFrame = mContext->prp_relFrameToAbsFrameF(relFrame); + return absFrame/fps; +} diff --git a/src/core/Expressions/timebinding.h b/src/core/Expressions/timebinding.h new file mode 100644 index 000000000..33f8483fd --- /dev/null +++ b/src/core/Expressions/timebinding.h @@ -0,0 +1,35 @@ +/* +# +# Friction - https://friction.graphics +# +# Copyright (c) Ole-André Rodlie and contributors +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +*/ + +#ifndef TIMEBINDING_H +#define TIMEBINDING_H + +#include "propertybindingbase.h" + +class CORE_EXPORT TimeBinding : public PropertyBindingBase { + TimeBinding(const Property* context); +public: + static qsptr sCreate(const Property* context); + + QJSValue getJSValue(QJSEngine& e); + QJSValue getJSValue(QJSEngine& e, qreal relFrame); + + FrameRange identicalRelRange(int absFrame); + FrameRange nextNonUnaryIdenticalRelRange(int absFrame); + QString path() const { return "$time"; } + +private: + QJSValue valueForRelFrame(qreal relFrame) const; +}; + +#endif // TIMEBINDING_H diff --git a/src/core/appsupport.cpp b/src/core/appsupport.cpp index 2b9178feb..0b4d7cb21 100644 --- a/src/core/appsupport.cpp +++ b/src/core/appsupport.cpp @@ -941,6 +941,7 @@ void AppSupport::installExprPresets(const bool force, << "rotation.fexpr" << "time.fexpr" << "trackObject.fexpr" + << "valueAtTime.fexpr" << "wave.fexpr" << "wiggle.fexpr" << "frameRemapLoop.fexpr" diff --git a/src/core/coreresources.qrc b/src/core/coreresources.qrc index 86053b83a..fae065218 100755 --- a/src/core/coreresources.qrc +++ b/src/core/coreresources.qrc @@ -35,6 +35,7 @@ Expressions/presets/rotation.fexpr Expressions/presets/time.fexpr Expressions/presets/trackObject.fexpr + Expressions/presets/valueAtTime.fexpr Expressions/presets/wave.fexpr Expressions/presets/wiggle.fexpr Expressions/presets/easeInBack.fexpr diff --git a/src/ui/wizards/quicksetuppresets.cpp b/src/ui/wizards/quicksetuppresets.cpp index 06481d523..a5079120c 100644 --- a/src/ui/wizards/quicksetuppresets.cpp +++ b/src/ui/wizards/quicksetuppresets.cpp @@ -66,6 +66,7 @@ QuickSetupPresetsPage::QuickSetupPresetsPage(QWidget *parent) {"Rotation", "rotation.fexpr"}, {"Time", "time.fexpr"}, {"Track Object", "trackObject.fexpr"}, + {"Value at Time (Delay)", "valueAtTime.fexpr"}, {"Wave", "wave.fexpr"}, {"Wiggle", "wiggle.fexpr"}, {"Frame Remap Loop", "frameRemapLoop.fexpr"},