diff --git a/vstgui/standalone/examples/standalone/resource/test.uidesc b/vstgui/standalone/examples/standalone/resource/test.uidesc index 267d26eb0..b1cda82e7 100644 --- a/vstgui/standalone/examples/standalone/resource/test.uidesc +++ b/vstgui/standalone/examples/standalone/resource/test.uidesc @@ -202,7 +202,7 @@ "opacity": "1", "origin": "15, 200", "row-height": "16", - "script": "// Hover Opacity Animation Script\n// This example script changes the opacity of the view\n// when the mouse enters or exits the view\n\n/* the default opacity of the view is stored in view.default_opacity */\nview.default_opacity = 0.6;\n\n/* the current opacity of the view is stored in view.opacity */\nview.opacity = view.default_opacity;\n\n/* the timer to change the opacity is stored in view.opacity_timer */\nview.opacity_timer = createTimer (view, 16, function (view) {\n\tview.opacity += view.opacity_change;\n\tif (view.opacity_change > 0)\n\t{\n\t\tif (view.opacity > 1)\n\t\t{\n\t\t\tview.opacity = 1;\n\t\t\tview.opacity_timer.stop();\n\t\t}\n\t}\n\telse\n\t{\n\t\tif (view.opacity <= view.default_opacity)\n\t\t{\n\t\t\tview.opacity = view.default_opacity;\n\t\t\tview.opacity_timer.stop();\n\t\t}\n\t}\n\tview.setAttribute(\"opacity\", view.opacity);\n});\n\n/* we install a mouse enter listener\n when the mouse enters the view we start the opacity change timer \n*/\nview.onMouseEnter = function (view, event) {\n\tview.opacity_change = 0.075;\n\tview.opacity_timer.start();\n\tevent.consumed = true;\n};\n\n/* we also install a mouse exit listener\n when the mouse exits the view we start the opacity change timer again \n now with a negative opacity_change variable so that in the timer callback \n the opacity is going back to the default opacity \n*/\nview.onMouseExit = function (view, event) {\n\tview.opacity_change = -0.05;\n\tview.opacity_timer.start();\n\tevent.consumed = true;\n};\n\n/* we also install a view removed listener so that we can cleanup and stop the timer */\nview.onRemoved = function (view) {\n\t// cleanup, when the view is removed, stop the timer\n\tview.opacity_timer.stop();\n};\n\nview.setAttribute(\"mouse-enabled\", true);\nview.setAttribute(\"opacity\", view.opacity);\n\n", + "script": "// Hover Opacity Animation Script\n// This example script changes the opacity of the view\n// when the mouse enters or exits the view\n\n/* the default opacity of the view is stored in view.default_opacity */\nview.default_opacity = 0.6;\n\n/* the current opacity of the view is stored in view.opacity */\nview.opacity = view.default_opacity;\n\n/* the timer to change the opacity is stored in view.opacity_timer */\nview.opacity_timer = createTimer (view, 16, function (view) {\n\tview.opacity += view.opacity_change;\n\tif (view.opacity_change > 0)\n\t{\n\t\tif (view.opacity > 1)\n\t\t{\n\t\t\tview.opacity = 1;\n\t\t\tview.opacity_timer.stop();\n\t\t}\n\t}\n\telse\n\t{\n\t\tif (view.opacity <= view.default_opacity)\n\t\t{\n\t\t\tview.opacity = view.default_opacity;\n\t\t\tview.opacity_timer.stop();\n\t\t}\n\t}\n\tview.setAttribute(\"opacity\", view.opacity);\n});\n\n/* we install a mouse enter listener\n when the mouse enters the view we start the opacity change timer \n*/\nview.onMouseEnter = function (view, event) {\n\tview.opacity_change = 0.075;\n\tview.opacity_timer.start();\n\tevent.consumed = true;\n\tvar val = view.getControllerProperty (\"integer\");\n\tlog (val);\n};\n\n/* we also install a mouse exit listener\n when the mouse exits the view we start the opacity change timer again \n now with a negative opacity_change variable so that in the timer callback \n the opacity is going back to the default opacity \n*/\nview.onMouseExit = function (view, event) {\n\tview.opacity_change = -0.05;\n\tview.opacity_timer.start();\n\tevent.consumed = true;\n};\n\n/* we also install a view removed listener so that we can cleanup and stop the timer */\nview.onRemoved = function (view) {\n\t// cleanup, when the view is removed, stop the timer\n\tview.opacity_timer.stop();\n};\n\nview.setAttribute(\"mouse-enabled\", true);\nview.setAttribute(\"opacity\", view.opacity);\n\n", "size": "110, 144", "style-hover": "true", "sub-controller": "WeekdaysController", diff --git a/vstgui/standalone/examples/standalone/source/testappdelegate.cpp b/vstgui/standalone/examples/standalone/source/testappdelegate.cpp index fd9ad62c8..69c9be99a 100644 --- a/vstgui/standalone/examples/standalone/source/testappdelegate.cpp +++ b/vstgui/standalone/examples/standalone/source/testappdelegate.cpp @@ -34,6 +34,7 @@ #ifdef VSTGUI_UISCRIPTING #include "vstgui/uidescription-scripting/uiscripting.h" +#include #endif #include @@ -157,6 +158,10 @@ class WeekdaysListConfigurator : public StaticListControlConfigurator //------------------------------------------------------------------------ class WeekdaysController : public DelegationController +#ifdef VSTGUI_UISCRIPTING +, + public ScriptControllerExtensionAdapter +#endif { public: WeekdaysController (IController* parent) : DelegationController (parent) {} @@ -175,6 +180,30 @@ class WeekdaysController : public DelegationController } return controller->verifyView (view, attributes, description); } +#ifdef VSTGUI_UISCRIPTING + bool getProperty (CView* view, std::string_view name, PropertyValue& value) const override + { + using namespace std::literals; + if (name == "integer"sv) + value = 24; + else if (name == "double"sv) + value = 13.3333; + else if (name == "string"sv) + value = "Hello World"s; + else + value = nullptr; + return true; + } + bool setProperty (CView* view, std::string_view name, const PropertyValue& value) override + { + std::visit ([] (auto&& v) { std::cout << v << '\n'; }, value); + return true; + } + std::optional verifyScript (CView* view, const std::string& script) override + { + return {script}; + } +#endif }; //------------------------------------------------------------------------ diff --git a/vstgui/uidescription-scripting/uiscripting.cpp b/vstgui/uidescription-scripting/uiscripting.cpp index 617b709f6..a900dc065 100644 --- a/vstgui/uidescription-scripting/uiscripting.cpp +++ b/vstgui/uidescription-scripting/uiscripting.cpp @@ -980,6 +980,63 @@ ViewScriptObject::ViewScriptObject (CView* view, IViewScriptObjectContext* conte var->setReturnVar (obj->getVar ()); obj->getVar ()->unref (); })); + scriptVar->addChild ("getControllerProperty"sv, + createJSFunction ( + [view] (CScriptVar* var) { + auto viewController = getViewController (view, true); + auto controller = + dynamic_cast (viewController); + auto name = var->getParameter ("name"sv); + if (!controller || !name) + { + var->getReturnVar ()->setUndefined (); + return; + } + IScriptControllerExtension::PropertyValue value; + if (!controller->getProperty (view, name->getString (), value)) + { + var->getReturnVar ()->setUndefined (); + return; + } + std::visit ( + [&] (auto&& value) { + using T = std::decay_t; + if constexpr (std::is_same_v) + var->getReturnVar ()->setInt (value); + else if constexpr (std::is_same_v) + var->getReturnVar ()->setDouble (value); + else if constexpr (std::is_same_v) + var->getReturnVar ()->setString (value); + else if constexpr (std::is_same_v) + var->getReturnVar ()->setUndefined (); + }, + value); + }, + {"name"})); + scriptVar->addChild ( + "setControllerProperty"sv, + createJSFunction ( + [view] (CScriptVar* var) { + auto viewController = getViewController (view, true); + auto controller = dynamic_cast (viewController); + auto name = var->getParameter ("name"sv); + auto value = var->getParameter ("value"sv); + if (!controller || !name || !value || !(value->isNumeric () || value->isString ())) + { + var->getReturnVar ()->setUndefined (); + return; + } + IScriptControllerExtension::PropertyValue propValue; + if (value->isInt ()) + propValue = value->getInt (); + else if (value->isDouble ()) + propValue = value->getDouble (); + else if (value->isString ()) + propValue = value->getString (); + auto result = controller->setProperty (view, name->getString (), propValue); + var->getReturnVar ()->setInt (result); + }, + {"name", "value"})); if (auto control = dynamic_cast (view)) { scriptVar->addChild ( @@ -1069,10 +1126,17 @@ struct JavaScriptViewFactory : ViewFactoryDelegate { if (auto value = attributes.getAttributeValue (ScriptingInternal::kAttrScript)) { - view->setAttribute (scriptAttrID, static_cast (value->size () + 1), - value->data ()); + std::optional verifiedScript; + if (auto scriptViewController = + dynamic_cast (description->getController ())) + { + verifiedScript = scriptViewController->verifyScript (view, *value); + } + const auto& script = verifiedScript ? *verifiedScript : *value; + auto scriptSize = static_cast (script.size () + 1); + view->setAttribute (scriptAttrID, scriptSize, script.data ()); if (!disabled) - scriptContext->onViewCreated (view, *value); + scriptContext->onViewCreated (view, script); } return view; } diff --git a/vstgui/uidescription-scripting/uiscripting.h b/vstgui/uidescription-scripting/uiscripting.h index 71a486b05..97bc45774 100644 --- a/vstgui/uidescription-scripting/uiscripting.h +++ b/vstgui/uidescription-scripting/uiscripting.h @@ -8,7 +8,9 @@ #include "../uidescription/iviewfactory.h" #include "../uidescription/iuidescription.h" #include "../uidescription/iuidescriptionaddon.h" +#include +//------------------------------------------------------------------------ namespace VSTGUI { //------------------------------------------------------------------------ @@ -39,5 +41,62 @@ class UIScripting : public UIDescriptionAddOnAdapter friend std::unique_ptr std::make_unique (); }; +//------------------------------------------------------------------------ +/** extends IController + * + * The script controller extension adds script related methods to the controller. + * + * It can alter the scripts for the views if needed and scripts can get and set properties. + */ +struct IScriptControllerExtension +{ + /** a property value is either an integer, double, string or undefined (nullptr_t) */ + using PropertyValue = std::variant; + + /** verify the script for a view + * + * called before the script is executed + * + * @param view the view + * @param script the script + * @return optional new script. if the optional is empty the original script is used. + */ + virtual std::optional verifyScript (CView* view, const std::string& script) = 0; + + /** get a property + * + * called from a script + * + * if the propery exists, the value should be set and the return value should be true. + * Otherwise return false. + * + * @param view the view + * @param name the name of the property + * @param value the property value + * @return true on success. + */ + virtual bool getProperty (CView* view, std::string_view name, PropertyValue& value) const = 0; + + /** set a property + * + * called from a script + * + * @param view the view + * @param name the name of the property + * @param value the value of the property + * @return true on success. + */ + virtual bool setProperty (CView* view, std::string_view name, const PropertyValue& value) = 0; +}; + +//------------------------------------------------------------------------ +/** adapter for IScriptControllerExtension */ +struct ScriptControllerExtensionAdapter : IScriptControllerExtension +{ + std::optional verifyScript (CView*, const std::string&) override { return {}; } + bool getProperty (CView*, std::string_view, PropertyValue&) const override { return false; } + bool setProperty (CView*, std::string_view, const PropertyValue&) override { return false; } +}; + //------------------------------------------------------------------------ } // VSTGUI diff --git a/vstgui/uidescription-scripting/uiscripting.md b/vstgui/uidescription-scripting/uiscripting.md index 44bf7a7b1..23506a3dd 100644 --- a/vstgui/uidescription-scripting/uiscripting.md +++ b/vstgui/uidescription-scripting/uiscripting.md @@ -60,6 +60,11 @@ The following methods are implemented on that object: - `getTagForName(String: name) -> Integer` - `lookupTagName(Integer: tag) -> String` +### Interaction with c++ code + +To interact with the c++ code extend the IController with IScriptControllerExtension +and implement its methods and call from the script the view methods `getControllerProperty` or `setControllerProperty`. +A property can either be an integer, floating point, string or undefined. ### View methods and properties @@ -73,6 +78,10 @@ The following methods are implemented on that object: - get the attribute with name "key" - `setAttribute(String: key, String: value) -> Void` - set the attribute value with name "key" to "value" +- `getControllerProperty(String: name, Property: value) -> Integer` + - set a controller property. returns true if succeeded +- `setControllerProperty(String: name) -> Property` + - get a controller property. returns a property For example to set the opacity attribute of a view write: