Explorar o código

Add scripted scene layer runtime (3/5) (#569)

* Add scripted scene layer runtime

* Address script layer review issues

* Address remaining script runtime review issues

---------

Co-authored-by: Gaiser147 <Gaiser147@users.noreply.github.com>
Graiser147 hai 4 meses
pai
achega
ac84f1262d

+ 25 - 3
src/WallpaperEngine/Data/Model/DynamicValue.cpp

@@ -21,6 +21,8 @@ DynamicValue::DynamicValue (int value) { this->DynamicValue::update (value); }
 
 DynamicValue::DynamicValue (bool value) { this->DynamicValue::update (value); }
 
+DynamicValue::DynamicValue (const std::string& value) { this->DynamicValue::update (value); }
+
 DynamicValue::~DynamicValue () {
     if (this->m_aliveFlag) {
 	*this->m_aliveFlag = false;
@@ -240,7 +242,17 @@ void DynamicValue::update (const std::string& newValue) {
     this->m_type = UnderlyingType::String;
 
     if (this->m_condition.has_value ()) {
-	this->m_bool = this->m_condition.value ().condition == newValue;
+	const bool boolValue = this->m_condition.value ().condition == newValue;
+	this->m_ivec4 = glm::ivec4 (boolValue ? 1 : 0);
+	this->m_ivec3 = glm::ivec3 (boolValue ? 1 : 0);
+	this->m_ivec2 = glm::ivec2 (boolValue ? 1 : 0);
+	this->m_vec2 = glm::vec2 (boolValue ? 1.0f : 0.0f);
+	this->m_vec3 = glm::vec3 (boolValue ? 1.0f : 0.0f);
+	this->m_vec4 = glm::vec4 (boolValue ? 1.0f : 0.0f);
+	this->m_float = boolValue ? 1.0f : 0.0f;
+	this->m_int = boolValue ? 1 : 0;
+	this->m_bool = boolValue;
+	this->m_type = UnderlyingType::Boolean;
     }
 
     this->propagate ();
@@ -256,11 +268,21 @@ void DynamicValue::update (const DynamicValue& other) {
     this->m_float = other.getFloat ();
     this->m_int = other.getInt ();
     this->m_bool = other.getBool ();
+    this->m_string = other.getString ();
     this->m_type = other.getType ();
 
     if (this->m_condition.has_value () && other.getType () == UnderlyingType::String) {
-	// TODO: DOES THIS NEED TO HAPPEN WITH OTHER TYPES TOO?
-	this->m_bool = this->m_condition.value ().condition == other.getString ();
+	const bool boolValue = this->m_condition.value ().condition == other.getString ();
+	this->m_ivec4 = glm::ivec4 (boolValue ? 1 : 0);
+	this->m_ivec3 = glm::ivec3 (boolValue ? 1 : 0);
+	this->m_ivec2 = glm::ivec2 (boolValue ? 1 : 0);
+	this->m_vec2 = glm::vec2 (boolValue ? 1.0f : 0.0f);
+	this->m_vec3 = glm::vec3 (boolValue ? 1.0f : 0.0f);
+	this->m_vec4 = glm::vec4 (boolValue ? 1.0f : 0.0f);
+	this->m_float = boolValue ? 1.0f : 0.0f;
+	this->m_int = boolValue ? 1 : 0;
+	this->m_bool = boolValue;
+	this->m_type = UnderlyingType::Boolean;
     }
 
     this->propagate ();

+ 1 - 0
src/WallpaperEngine/Data/Model/DynamicValue.h

@@ -42,6 +42,7 @@ public:
     explicit DynamicValue (float value);
     explicit DynamicValue (int value);
     explicit DynamicValue (bool value);
+    explicit DynamicValue (const std::string& value);
     virtual ~DynamicValue ();
 
     [[nodiscard]] const glm::ivec4& getIVec4 () const;

+ 34 - 2
src/WallpaperEngine/Data/Model/ScriptedDynamicValue.cpp

@@ -26,15 +26,47 @@ ScriptedDynamicValue::ScriptedDynamicValue (
     this->reevaluate ();
 }
 
-void ScriptedDynamicValue::reevaluate () {
+ScriptedDynamicValue::~ScriptedDynamicValue () {
+    WallpaperEngine::Scripting::ScriptEngine::instance ().releaseBinding (this);
+}
+
+void ScriptedDynamicValue::setBindingContext (ScriptBindingContext context) {
+    this->m_bindingContext = std::move (context);
+}
+
+const std::optional<ScriptBindingContext>& ScriptedDynamicValue::getBindingContext () const {
+    return this->m_bindingContext;
+}
+
+void ScriptedDynamicValue::reevaluate () { this->reevaluate (nullptr); }
+
+void ScriptedDynamicValue::reevaluate (WallpaperEngine::Render::Wallpapers::CScene* scene) {
+    if (this->m_evaluating) {
+	return;
+    }
+
+    this->m_evaluating = true;
+    struct EvaluationGuard {
+	bool& evaluating;
+	~EvaluationGuard () { evaluating = false; }
+    } guard { this->m_evaluating };
+
     // Build raw pointer map for the engine
     std::map<std::string, DynamicValue*> propsMap;
     for (const auto& [name, prop] : this->m_scriptProps) {
 	propsMap[name] = prop.get ();
     }
 
+    const DynamicValue& currentValue = this->getType () == DynamicValue::Null ? this->m_baseValue : *this;
+
     auto result = WallpaperEngine::Scripting::ScriptEngine::instance ().evaluate (
-	this->m_scriptSource, propsMap, this->m_baseValue);
+	this,
+	this->m_scriptSource,
+	propsMap,
+	currentValue,
+	scene,
+	this->m_bindingContext.has_value () ? &this->m_bindingContext.value () : nullptr
+    );
 
     if (result) {
 	this->update (*result);

+ 18 - 1
src/WallpaperEngine/Data/Model/ScriptedDynamicValue.h

@@ -1,13 +1,24 @@
 #pragma once
 
 #include <map>
+#include <optional>
 #include <string>
 
 #include "DynamicValue.h"
 #include "Types.h"
 
+namespace WallpaperEngine::Render::Wallpapers {
+class CScene;
+}
+
 namespace WallpaperEngine::Data::Model {
 
+struct ScriptBindingContext {
+    int objectId = -1;
+    std::string objectName;
+    std::string propertyName;
+};
+
 /**
  * A DynamicValue whose value is computed by evaluating a WallpaperEngine script.
  *
@@ -23,7 +34,11 @@ public:
 	DynamicValue baseValue
     );
 
-    ~ScriptedDynamicValue () override = default;
+    ~ScriptedDynamicValue () override;
+
+    void setBindingContext (ScriptBindingContext context);
+    [[nodiscard]] const std::optional<ScriptBindingContext>& getBindingContext () const;
+    void reevaluate (WallpaperEngine::Render::Wallpapers::CScene* scene);
 
 private:
     void reevaluate ();
@@ -31,5 +46,7 @@ private:
     std::string m_scriptSource;
     std::map<std::string, DynamicValueUniquePtr> m_scriptProps;
     DynamicValue m_baseValue;
+    std::optional<ScriptBindingContext> m_bindingContext = std::nullopt;
+    bool m_evaluating = false;
 };
 } // namespace WallpaperEngine::Data::Model

+ 97 - 21
src/WallpaperEngine/Data/Parsers/ObjectParser.cpp

@@ -7,6 +7,7 @@
 #include "UserSettingParser.h"
 #include "WallpaperEngine/Data/Model/Object.h"
 #include "WallpaperEngine/Data/Model/Project.h"
+#include "WallpaperEngine/Data/Model/ScriptedDynamicValue.h"
 #include "WallpaperEngine/Logging/Log.h"
 
 #include <glm/gtc/constants.hpp>
@@ -85,6 +86,28 @@ void widenColorToVec4 (DynamicValue& v) {
 	v.update (glm::vec4 (glm::vec3 (icolor), 255.0f) / 255.0f);
     }
 }
+
+static void bindScriptContext (
+    const UserSettingUniquePtr& setting,
+    int objectId,
+    const std::string& objectName,
+    const std::string& propertyName
+) {
+    if (!setting || !setting->value) {
+	return;
+    }
+
+    auto* scripted = dynamic_cast<ScriptedDynamicValue*> (setting->value.get ());
+    if (!scripted) {
+	return;
+    }
+
+    scripted->setBindingContext (ScriptBindingContext {
+	.objectId = objectId,
+	.objectName = objectName,
+	.propertyName = propertyName,
+    });
+}
 } // namespace
 
 ObjectUniquePtr ObjectParser::parse (const JSON& it, const Project& project) {
@@ -103,13 +126,13 @@ ObjectUniquePtr ObjectParser::parse (const JSON& it, const Project& project) {
 	basedata = ObjectData {
 	    .id = it.require<int> ("id", "Object must have an id"),
 	    .name = it.require<std::string> ("name", "Object must have a name"),
-		.dependencies = parseDependencies (it),
-		.parent = it.optional<int> ("parent"),
-		.origin = it.user ("origin", project.properties, glm::vec3 (0.0f)),
-		.groupScale = it.user ("scale", project.properties, glm::vec3 (1.0f)),
-		.groupAngles = it.user ("angles", project.properties, glm::vec3 (0.0f)),
-		.groupVisible = it.user ("visible", project.properties, true),
-	    };
+	    .dependencies = parseDependencies (it),
+	    .parent = it.optional<int> ("parent"),
+	    .origin = it.user ("origin", project.properties, glm::vec3 (0.0f)),
+	    .groupScale = it.user ("scale", project.properties, glm::vec3 (1.0f)),
+	    .groupAngles = it.user ("angles", project.properties, glm::vec3 (0.0f)),
+	    .groupVisible = it.user ("visible", project.properties, true),
+	};
     } catch (const std::exception& e) {
 	sLog.error ("Error parsing object base data: ", e.what ());
 	const auto idIt = it.find ("id");
@@ -123,15 +146,22 @@ ObjectUniquePtr ObjectParser::parse (const JSON& it, const Project& project) {
 		name = std::to_string (nameIt->get<int> ());
 	    }
 	}
-	    basedata = ObjectData {
-		.id = id,
-		.name = name,
-		.dependencies = {},
-		.groupScale = it.user ("scale", project.properties, glm::vec3 (1.0f)),
-		.groupAngles = it.user ("angles", project.properties, glm::vec3 (0.0f)),
-		.groupVisible = it.user ("visible", project.properties, true),
-	    };
-	}
+	basedata = ObjectData {
+	    .id = id,
+	    .name = name,
+	    .dependencies = parseDependencies (it),
+	    .parent = it.optional<int> ("parent"),
+	    .origin = it.user ("origin", project.properties, glm::vec3 (0.0f)),
+	    .groupScale = it.user ("scale", project.properties, glm::vec3 (1.0f)),
+	    .groupAngles = it.user ("angles", project.properties, glm::vec3 (0.0f)),
+	    .groupVisible = it.user ("visible", project.properties, true),
+	};
+    }
+
+    bindScriptContext (basedata.origin, basedata.id, basedata.name, "origin");
+    bindScriptContext (basedata.groupScale, basedata.id, basedata.name, "scale");
+    bindScriptContext (basedata.groupAngles, basedata.id, basedata.name, "angles");
+    bindScriptContext (basedata.groupVisible, basedata.id, basedata.name, "visible");
 
     if (imageIt != it.end () && imageIt->is_string ()) {
 	return parseImage (it, project, std::move (basedata), *imageIt);
@@ -146,10 +176,12 @@ ObjectUniquePtr ObjectParser::parse (const JSON& it, const Project& project) {
     } else if (shapeIt != it.end ()) {
 	sLog.error ("VolumeLight objects are not supported yet");
     } else {
-	// dump the object for now, might want to change later
-	// TODO: RE-EVALUATE IF THIS MAKES SENSE, THERE'S OBJECTS THAT CONTAIN OTHER OBJECTS AND THUS AREN'T REALLY
-	// ANYTHING SPECIAL
-	sLog.error ("Unknown object type found: ", it.dump ());
+	if (!it.optional ("solid", false)) {
+	    // dump the object for now, might want to change later
+	    // TODO: RE-EVALUATE IF THIS MAKES SENSE, THERE'S OBJECTS THAT CONTAIN OTHER OBJECTS AND THUS AREN'T REALLY
+	    // ANYTHING SPECIAL
+	    sLog.error ("Unknown object type found: ", it.dump ());
+	}
     }
 
     return std::make_unique<Object> (std::move (basedata));
@@ -231,6 +263,10 @@ TextUniquePtr ObjectParser::parseText (const JSON& it, const Project& project, O
     );
 
     widenColorToVec4 (*result->color->value);
+    bindScriptContext (result->visible, result->id, result->name, "visible");
+    bindScriptContext (result->color, result->id, result->name, "color");
+    bindScriptContext (result->alpha, result->id, result->name, "alpha");
+    bindScriptContext (result->scale, result->id, result->name, "scale");
 
     return result;
 }
@@ -268,6 +304,36 @@ ObjectParser::parseImage (const JSON& it, const Project& project, ObjectData bas
 	result->color->value->update (glm::vec4 (result->color->value->getIVec3 (), 255));
     }
 
+    bindScriptContext (result->scale, result->id, result->name, "scale");
+    bindScriptContext (result->angles, result->id, result->name, "angles");
+    bindScriptContext (result->visible, result->id, result->name, "visible");
+    bindScriptContext (result->alpha, result->id, result->name, "alpha");
+    bindScriptContext (result->color, result->id, result->name, "color");
+    bindScriptContext (result->parallaxDepth, result->id, result->name, "parallaxDepth");
+
+    const auto instance = it.optional ("instance");
+    if (instance.has_value () && instance->is_object () && !result->model->material->passes.empty ()) {
+	auto& firstPass = **result->model->material->passes.begin ();
+	const auto instanceTextures = instance->optional ("textures");
+	if (instanceTextures.has_value ()) {
+	    const auto parsed = parseTextureMap (*instanceTextures);
+	    firstPass.textures.insert (parsed.begin (), parsed.end ());
+	}
+	const auto instanceUserTextures = instance->optional ("usertextures");
+	if (instanceUserTextures.has_value ()) {
+	    const auto parsed = parseTextureMap (*instanceUserTextures);
+	    firstPass.usertextures.insert (parsed.begin (), parsed.end ());
+	}
+    }
+
+    for (const auto& effect : result->effects) {
+	for (const auto& pass : effect->passOverrides) {
+	    for (const auto& [name, constant] : pass->constants) {
+		bindScriptContext (constant, result->id, result->name, name);
+	    }
+	}
+    }
+
     return result;
 }
 
@@ -339,6 +405,11 @@ TextureMap ObjectParser::parseTextureMap (const JSON& it) {
 
 	if (cur.is_null ()) {
 	    continue;
+	} else if (cur.is_object ()) {
+	    const auto nameIt = cur.find ("name");
+	    if (nameIt != cur.end () && nameIt->is_string ()) {
+		result.emplace (textureIndex, nameIt->get<std::string> ());
+	    }
 	} else {
 	    std::string texName = cur;
 	    if (!texName.empty ()) {
@@ -646,7 +717,7 @@ ParticleUniquePtr ObjectParser::parseParticle (const JSON& it, const Project& pr
 	    flags = flagsIt->get<uint32_t> ();
 	}
 
-	return std::make_unique<Particle> (
+	auto result = std::make_unique<Particle> (
 	    std::move (base),
 	    ParticleData {
 		.scale = it.user ("scale", properties, glm::vec3 (1.0f)),
@@ -669,6 +740,11 @@ ParticleUniquePtr ObjectParser::parseParticle (const JSON& it, const Project& pr
 		.instanceOverride = std::move (instanceOverride),
 	    }
 	);
+	bindScriptContext (result->scale, result->id, result->name, "scale");
+	bindScriptContext (result->angles, result->id, result->name, "angles");
+	bindScriptContext (result->visible, result->id, result->name, "visible");
+	bindScriptContext (result->parallaxDepth, result->id, result->name, "parallaxDepth");
+	return result;
     } catch (nlohmann::json::exception& e) {
 	sLog.error ("Error parsing particle '", base.name, "': ", e.what ());
 	sLog.error ("Particle JSON: ", it.dump ());

+ 4 - 1
src/WallpaperEngine/Data/Parsers/PropertyParser.cpp

@@ -32,6 +32,9 @@ PropertySharedPtr PropertyParser::parse (const JSON& it, const std::string& name
     if (type == "textinput") {
 	return parseTextInput (it, name);
     }
+    if (type == "usershortcut") {
+	return parseTextInput (it, name);
+    }
 
     if (type.has_value () && type != "group") {
 	// show the error and ignore this property
@@ -146,4 +149,4 @@ PropertySharedPtr PropertyParser::parseTextInput (const JSON& it, const std::str
 	},
 	it.require ("value", "Property must have a value").dump ()
     );
-}
+}

+ 16 - 7
src/WallpaperEngine/Render/Objects/CImage.cpp

@@ -550,11 +550,15 @@ void CImage::setupPuppetGeometryCallback (Effects::CPass* pass) const {
 	    }
 	},
 	[this] () {
-	    GLfloat previousClearColor[4] = {};
-	    glGetFloatv (GL_COLOR_CLEAR_VALUE, previousClearColor);
-	    glClearColor (0.0f, 0.0f, 0.0f, 0.0f);
-	    glClear (GL_COLOR_BUFFER_BIT);
-	    glClearColor (previousClearColor[0], previousClearColor[1], previousClearColor[2], previousClearColor[3]);
+	    GLint currentFramebuffer = 0;
+	    glGetIntegerv (GL_DRAW_FRAMEBUFFER_BINDING, &currentFramebuffer);
+	    if (currentFramebuffer != static_cast<GLint> (this->getScene ().getFBO ()->getFramebuffer ())) {
+		GLfloat previousClearColor[4] = {};
+		glGetFloatv (GL_COLOR_CLEAR_VALUE, previousClearColor);
+		glClearColor (0.0f, 0.0f, 0.0f, 0.0f);
+		glClear (GL_COLOR_BUFFER_BIT);
+		glClearColor (previousClearColor[0], previousClearColor[1], previousClearColor[2], previousClearColor[3]);
+	    }
 	    glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, this->m_puppetIndices);
 	    glDrawElements (GL_TRIANGLES, this->m_puppetIndexCount, GL_UNSIGNED_SHORT, nullptr);
 	},
@@ -890,12 +894,13 @@ void CImage::pinpongFramebuffer (std::shared_ptr<const CFBO>* drawTo, std::share
 
 void CImage::render () {
     // do not try to render something that did not initialize successfully
-    // non-visible materials do need to be rendered
     if (!this->m_initialized) {
 	return;
     }
 
-    // TODO: DO NOT DRAW IMAGES THAT ARE NOT VISIBLE AND NOTHING DEPENDS ON THEM
+    if (!this->getImage ().visible->value->getBool ()) {
+	return;
+    }
 
     glColorMask (true, true, true, true);
 
@@ -1103,6 +1108,10 @@ void CImage::updateScreenSpacePosition () {
 
     this->m_modelViewProjectionScreen = mvp;
     this->m_modelViewProjectionScreenInverse = glm::inverse (mvp);
+    if (this->getImage ().model->passthrough) {
+	this->m_modelViewProjectionCopy = this->m_modelViewProjectionScreen;
+	this->m_modelViewProjectionCopyInverse = this->m_modelViewProjectionScreenInverse;
+    }
 }
 
 const Image& CImage::getImage () const { return this->m_image; }

+ 20 - 6
src/WallpaperEngine/Render/Shaders/ShaderUnit.cpp

@@ -1,6 +1,7 @@
 #include "ShaderUnit.h"
 
 #include "WallpaperEngine/Logging/Log.h"
+#include <exception>
 #include <regex>
 #include <stack>
 #include <string>
@@ -417,16 +418,17 @@ std::string ShaderUnit::applyFragmentTexCoordCompatibility (std::string source)
 	return source;
     }
 
+    const std::regex texCoordBeforeCast2 (R"(\bv_TexCoord\b(\s*[-+*/]\s*CAST2\s*\())");
+    const std::regex cast2BeforeTexCoord (R"((CAST2\s*\([^)]+\)\s*[-+*/]\s*)\bv_TexCoord\b)");
+
     const std::regex wideTexCoordDecl (R"(\bvarying\s+vec[34]\s+v_TexCoord\s*;)");
-    if (!std::regex_search (source, wideTexCoordDecl) || source.find ("v_TexCoord.xy") == std::string::npos) {
+    if (!std::regex_search (source, wideTexCoordDecl)
+	|| (!std::regex_search (source, texCoordBeforeCast2) && !std::regex_search (source, cast2BeforeTexCoord))) {
 	return source;
     }
 
     const std::string original = source;
-    const std::regex texCoordBeforeCast2 (R"(\bv_TexCoord\b(\s*[-+*/]\s*CAST2\s*\())");
     source = std::regex_replace (source, texCoordBeforeCast2, "v_TexCoord.xy$1");
-
-    const std::regex cast2BeforeTexCoord (R"((CAST2\s*\([^)]+\)\s*[-+*/]\s*)\bv_TexCoord\b)");
     source = std::regex_replace (source, cast2BeforeTexCoord, "$1v_TexCoord.xy");
 
     if (source != original) {
@@ -438,7 +440,13 @@ std::string ShaderUnit::applyFragmentTexCoordCompatibility (std::string source)
 
 void ShaderUnit::parseComboConfiguration (const std::string& content, const int defaultValue) {
     // TODO: SUPPORT REQUIRES SO WE PROPERLY FOLLOW THE REQUIRED CHAIN
-    const auto data = JSON::parse (content);
+    JSON data;
+    try {
+	data = JSON::parse (content);
+    } catch (const std::exception& e) {
+	sLog.error ("Cannot parse combo metadata in shader ", this->m_file, ": ", e.what ());
+	return;
+    }
     const auto combo = data.require<std::string> ("combo", "cannot parse combo information");
     // ignore type as it seems to be used only on the editor
     // const auto type = data.find ("type");
@@ -473,7 +481,13 @@ void ShaderUnit::parseComboConfiguration (const std::string& content, const int
 void ShaderUnit::parseParameterConfiguration (
     const std::string& type, const std::string& name, const std::string& content
 ) {
-    const auto data = JSON::parse (content);
+    JSON data;
+    try {
+	data = JSON::parse (content);
+    } catch (const std::exception& e) {
+	sLog.error ("Cannot parse parameter metadata for ", name, " in shader ", this->m_file, ": ", e.what ());
+	return;
+    }
     const auto material = data.optional ("material");
     const auto defvalue = data.optional ("default");
     // auto range = data.find ("range");

+ 129 - 42
src/WallpaperEngine/Render/Wallpapers/CScene.cpp

@@ -11,6 +11,8 @@
 #include "WallpaperEngine/Data/Model/Wallpaper.h"
 #include "WallpaperEngine/Data/Parsers/ObjectParser.h"
 
+#include <ranges>
+
 extern float g_Time;
 extern float g_TimeLast;
 
@@ -21,6 +23,37 @@ using namespace WallpaperEngine::Data::Parsers;
 using namespace WallpaperEngine::Render::Wallpapers;
 using JSON = WallpaperEngine::Data::JSON::JSON;
 
+namespace {
+Render::CObject* createImageObject (CScene& scene, const Image& imageData) {
+    auto* image = new Objects::CImage (scene, imageData);
+    try {
+	image->setup ();
+    } catch (std::runtime_error&) {
+	sLog.error ("Cannot setup image ", image->getImage ().name);
+	delete image;
+	return nullptr;
+    }
+    return image;
+}
+
+Render::CObject* createParticleObject (CScene& scene, const Particle& particleData) {
+    if (scene.getContext ().getApp ().getContext ().settings.general.disableParticles == true) {
+	sLog.debug ("Ignoring particle system (disabled in settings): ", particleData.name);
+	return nullptr;
+    }
+
+    auto* particle = new Objects::CParticle (scene, particleData);
+    try {
+	particle->setup ();
+    } catch (std::runtime_error&) {
+	sLog.error ("Cannot setup particle ", particle->getParticle ().name);
+	delete particle;
+	return nullptr;
+    }
+    return particle;
+}
+}
+
 CScene::CScene (
     const Wallpaper& wallpaper, RenderContext& context, AudioContext& audioContext,
     const WallpaperState::TextureUVsScaling& scalingMode, const uint32_t& clampMode
@@ -97,6 +130,8 @@ CScene::CScene (
 	this->addObjectToRenderOrder (*object);
     }
 
+    this->collectScriptedValues ();
+
     // create extra framebuffers for the bloom effect
     this->_rt_4FrameBuffer = this->create (
 	"_rt_4FrameBuffer", TextureFormat_ARGB8888, TextureFlags_ClampUVs, 1.0, { sceneWidth / 4, sceneHeight / 4 },
@@ -223,42 +258,14 @@ Render::CObject* CScene::createObject (const Object& object) {
 }
 
 Render::CObject* CScene::dispatchObjectType (const Object& object) {
-    if (object.is<Image> ()) {
-	auto* image = new Objects::CImage (*this, *object.as<Image> ());
-
-	try {
-	    image->setup ();
-	} catch (std::runtime_error&) {
-	    sLog.error ("Cannot setup image ", image->getImage ().name);
-	}
-
-	return image;
-    }
-
-    if (object.is<Sound> ()) {
-	return new Objects::CSound (*this, *object.as<Sound> ());
-    }
-
-    if (object.is<Particle> ()) {
-	if (this->getContext ().getApp ().getContext ().settings.general.disableParticles == true) {
-	    sLog.debug ("Ignoring particle system (disabled in settings): ", object.as<Particle> ()->name);
-	    return nullptr;
-	}
-
-	auto* particle = new Objects::CParticle (*this, *object.as<Particle> ());
-
-	try {
-	    particle->setup ();
-	} catch (std::runtime_error&) {
-	    sLog.error ("Cannot setup particle ", particle->getParticle ().name);
-	}
-
-	return particle;
-    }
+    Render::CObject* renderObject = nullptr;
 
-    if (object.is<Text> ()) {
+    if (object.is<Image> ()) {
+	renderObject = createImageObject (*this, *object.as<Image> ());
+    } else if (object.is<Sound> ()) {
+	renderObject = new Objects::CSound (*this, *object.as<Sound> ());
+    } else if (object.is<Text> ()) {
 	auto* text = new Objects::CText (*this, *object.as<Text> ());
-
 	try {
 	    text->setup ();
 	} catch (std::runtime_error&) {
@@ -266,12 +273,15 @@ Render::CObject* CScene::dispatchObjectType (const Object& object) {
 	    delete text;
 	    return nullptr;
 	}
-
-	return text;
+	renderObject = text;
+    } else if (object.is<Particle> ()) {
+	renderObject = createParticleObject (*this, *object.as<Particle> ());
+    } else {
+	sLog.debug ("Unknown object type, creating placeholder, empty object: ", object.id);
+	renderObject = new CObject (*this, object);
     }
 
-    sLog.debug ("Unknown object type, creating placeholder, empty object: ", object.id);
-    return new CObject (*this, object);
+    return renderObject;
 }
 
 void CScene::addObjectToRenderOrder (const Object& object) {
@@ -309,23 +319,89 @@ void CScene::addObjectToRenderOrder (const Object& object) {
     }
 }
 
+void CScene::registerScriptedValue (const UserSettingUniquePtr& setting) {
+    if (!setting || !setting->value) {
+	return;
+    }
+
+    auto* scripted = dynamic_cast<ScriptedDynamicValue*> (setting->value.get ());
+    if (!scripted) {
+	return;
+    }
+
+    if (std::ranges::find (this->m_scriptedValues, scripted) == this->m_scriptedValues.end ()) {
+	this->m_scriptedValues.emplace_back (scripted);
+    }
+}
+
+void CScene::collectScriptedValues () {
+    this->m_scriptedValues.clear ();
+
+    for (const auto& object : this->getScene ().objects) {
+	this->registerScriptedValue (object->origin);
+	this->registerScriptedValue (object->groupScale);
+	this->registerScriptedValue (object->groupAngles);
+	this->registerScriptedValue (object->groupVisible);
+
+	if (object->is<Image> ()) {
+	    const auto* image = object->as<Image> ();
+	    this->registerScriptedValue (image->scale);
+	    this->registerScriptedValue (image->angles);
+	    this->registerScriptedValue (image->visible);
+	    this->registerScriptedValue (image->alpha);
+	    this->registerScriptedValue (image->color);
+	    this->registerScriptedValue (image->parallaxDepth);
+
+	    for (const auto& effect : image->effects) {
+		this->registerScriptedValue (effect->visible);
+		for (const auto& pass : effect->passOverrides) {
+		    for (const auto& constant : pass->constants | std::views::values) {
+			this->registerScriptedValue (constant);
+		    }
+		}
+	    }
+	} else if (object->is<Particle> ()) {
+	    const auto* particle = object->as<Particle> ();
+	    this->registerScriptedValue (particle->scale);
+	    this->registerScriptedValue (particle->angles);
+	    this->registerScriptedValue (particle->visible);
+	    this->registerScriptedValue (particle->parallaxDepth);
+	} else if (object->is<Text> ()) {
+	    const auto* text = object->as<Text> ();
+	    this->registerScriptedValue (text->visible);
+	    this->registerScriptedValue (text->color);
+	    this->registerScriptedValue (text->alpha);
+	    this->registerScriptedValue (text->scale);
+	}
+    }
+}
+
+void CScene::updateScriptedValues () {
+    for (const auto& scripted : this->m_scriptedValues) {
+	scripted->reevaluate (this);
+    }
+}
+
 Camera& CScene::getCamera () const { return *this->m_camera; }
 
 void CScene::renderFrame (const glm::ivec4& viewport) {
     // ensure the virtual mouse position is up to date
     this->updateMouse (viewport);
 
+    this->updateScriptedValues ();
+
     // update the parallax position if required
     if (this->getScene ().camera.parallax.enabled->value->getBool ()
 	&& !this->getContext ().getApp ().getContext ().settings.mouse.disableparallax) {
 	const float influence = this->getScene ().camera.parallax.mouseInfluence->value->getFloat ();
 	const float amount = this->getScene ().camera.parallax.amount->value->getFloat ();
-	const float delay = glm::min (
-	    static_cast<float> (this->getScene ().camera.parallax.delay->value->getBool ()), g_Time - g_TimeLast
+	const float delay = glm::clamp (
+	    this->getScene ().camera.parallax.delay->value->getFloat () * (g_Time - g_TimeLast), 0.0f, 1.0f
 	);
 
+	const glm::vec2 centeredMouse = this->m_mousePosition - glm::vec2 (0.5f, 0.5f);
 	this->m_parallaxDisplacement
-	    = glm::mix (this->m_parallaxDisplacement, (this->m_mousePosition * amount) * influence, delay);
+	    = glm::mix (this->m_parallaxDisplacement, (centeredMouse * amount) * influence, delay);
     }
 
     // update main textures for images
@@ -359,6 +435,14 @@ void CScene::renderFrame (const glm::ivec4& viewport) {
     glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 
     for (const auto& cur : this->m_objectsByRenderOrder) {
+	const auto& debug = this->getContext ().getApp ().getContext ().settings.render.debug;
+	if (debug.objectFilter.has_value () && cur->getId () != debug.objectFilter.value ()) {
+	    continue;
+	}
+	if (std::ranges::find (debug.skipObjects, cur->getId ()) != debug.skipObjects.end ()) {
+	    continue;
+	}
+
 	cur->render ();
     }
 }
@@ -422,4 +506,7 @@ const glm::vec2* CScene::getParallaxDisplacement () const { return &this->m_para
 
 const std::vector<CObject*>& CScene::getObjectsByRenderOrder () const { return this->m_objectsByRenderOrder; }
 
-const CObject* CScene::getObject (int id) const { return this->m_objects.at (id); }
+const CObject* CScene::getObject (int id) const {
+    const auto object = this->m_objects.find (id);
+    return object == this->m_objects.end () ? nullptr : object->second;
+}

+ 5 - 0
src/WallpaperEngine/Render/Wallpapers/CScene.h

@@ -2,6 +2,7 @@
 
 #include "WallpaperEngine/Render/Camera.h"
 
+#include "WallpaperEngine/Data/Model/ScriptedDynamicValue.h"
 #include "WallpaperEngine/Render/CWallpaper.h"
 
 namespace WallpaperEngine::Render {
@@ -53,12 +54,16 @@ private:
     Render::CObject* createObject (const Object& object);
     Render::CObject* dispatchObjectType (const Object& object);
     void addObjectToRenderOrder (const Object& object);
+    void collectScriptedValues ();
+    void registerScriptedValue (const UserSettingUniquePtr& setting);
+    void updateScriptedValues ();
 
     std::unique_ptr<Camera> m_camera;
     ObjectUniquePtr m_bloomObjectData;
     CObject* m_bloomObject = nullptr;
     std::map<int, CObject*> m_objects = {};
     std::vector<CObject*> m_objectsByRenderOrder = {};
+    std::vector<ScriptedDynamicValue*> m_scriptedValues = {};
     glm::vec2 m_mousePosition = {};
     glm::vec2 m_mousePositionLast = {};
     glm::vec2 m_mousePositionNormalized = {};

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 1264 - 138
src/WallpaperEngine/Scripting/ScriptEngine.cpp


+ 67 - 3
src/WallpaperEngine/Scripting/ScriptEngine.h

@@ -1,16 +1,26 @@
 #pragma once
 
+#include <chrono>
+#include <future>
 #include <map>
 #include <memory>
+#include <optional>
 #include <string>
+#include <unordered_map>
+#include <unordered_set>
 
 #include "WallpaperEngine/Data/Model/DynamicValue.h"
+#include "WallpaperEngine/Data/Model/ScriptedDynamicValue.h"
 #include "WallpaperEngine/Data/Model/Types.h"
 
 extern "C" {
 #include "quickjs.h"
 }
 
+namespace WallpaperEngine::Render::Wallpapers {
+class CScene;
+}
+
 namespace WallpaperEngine::Scripting {
 using namespace WallpaperEngine::Data::Model;
 
@@ -35,9 +45,12 @@ public:
      * @return The modified value from update(), or a copy of currentValue on error
      */
     DynamicValueUniquePtr evaluate (
+	const void* bindingKey,
 	const std::string& scriptSource,
 	const std::map<std::string, DynamicValue*>& scriptProperties,
-	const DynamicValue& currentValue
+	const DynamicValue& currentValue,
+	WallpaperEngine::Render::Wallpapers::CScene* scene = nullptr,
+	const ScriptBindingContext* bindingContext = nullptr
     );
 
     // -------------------------------------------------------------------
@@ -94,11 +107,52 @@ public:
      */
     void destroyLayer (ScriptLayerHandle handle);
 
+    JSValue dynamicValueToJS (const DynamicValue& value) const;
+    DynamicValueUniquePtr jsToDynamicValue (JSValue val, DynamicValue::UnderlyingType hint) const;
+    void releaseBinding (const void* bindingKey);
+
 private:
     ScriptEngine ();
 
-    JSValue dynamicValueToJS (const DynamicValue& value) const;
-    DynamicValueUniquePtr jsToDynamicValue (JSValue val, DynamicValue::UnderlyingType hint) const;
+    JSValue ensureModule (const void* bindingKey, const std::string& scriptSource);
+    void installBuiltins ();
+    void refreshMediaState ();
+    void dispatchMediaEvents (JSValue module, const void* bindingKey);
+    void updateRuntimeGlobals (JSContext* ctx, JSValue globalObj) const;
+    void updateSceneInputGlobals (JSContext* ctx, JSValue globalObj, WallpaperEngine::Render::Wallpapers::CScene* scene);
+    void callModuleWithProps (JSContext* ctx, JSValue module, const char* name, JSValue propsObj) const;
+    void initializeModuleIfNeeded (JSContext* ctx, JSValue module, const void* bindingKey, const DynamicValue& currentValue, bool hasScene);
+    void runIntervals (JSContext* ctx, JSValue globalObj, const std::string& bindingKeyString) const;
+    DynamicValueUniquePtr fallbackTextValue (const std::string& scriptSource) const;
+    void applyTextFallback (DynamicValue& value, const std::string& scriptSource) const;
+    void logTextEvaluationDebug (const ScriptBindingContext* bindingContext) const;
+    void logTextResultDebug (const ScriptBindingContext* bindingContext, const DynamicValueUniquePtr& dynResult) const;
+    JSValue callUpdate (JSContext* ctx, JSValue module, const DynamicValue& currentValue) const;
+    DynamicValueUniquePtr resolveUndefinedResult (
+	const std::string& scriptSource,
+	const DynamicValue& currentValue,
+	WallpaperEngine::Render::Wallpapers::CScene* scene,
+	const ScriptBindingContext* bindingContext
+    ) const;
+    DynamicValueUniquePtr resolveEvaluationResult (
+	JSContext* ctx,
+	JSValue result,
+	const std::string& scriptSource,
+	const DynamicValue& currentValue,
+	WallpaperEngine::Render::Wallpapers::CScene* scene,
+	const ScriptBindingContext* bindingContext
+    ) const;
+
+    struct MediaState {
+	int playbackState = 0;
+	std::string status = "Stopped";
+	std::string title;
+	std::string artist;
+	std::string artUrl;
+	double duration = 0.0;
+	double position = 0.0;
+	bool available = false;
+    };
 
     // Installs globalThis.__layers and related helpers. Called lazily.
     void ensureLayerRegistry ();
@@ -108,5 +162,15 @@ private:
     ScriptLayerHandle m_nextLayerId = 1;
     bool m_layerRegistryReady = false;
     std::map<ScriptLayerHandle, bool> m_layerInitialized;
+    std::unordered_map<const void*, JSValue> m_modules = {};
+    std::unordered_set<const void*> m_initializedModules = {};
+    std::unordered_map<const void*, std::string> m_lastMediaProperties = {};
+    std::unordered_map<const void*, std::string> m_lastMediaPlayback = {};
+    std::unordered_map<const void*, std::string> m_lastMediaTimeline = {};
+    std::unordered_map<const void*, std::string> m_lastMediaThumbnail = {};
+    std::chrono::steady_clock::time_point m_lastMediaPoll = {};
+    std::future<std::optional<MediaState>> m_mediaPollFuture = {};
+    MediaState m_mediaState = {};
+    bool m_builtinsInstalled = false;
 };
 } // namespace WallpaperEngine::Scripting

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio