804 lines
25 KiB
C++
804 lines
25 KiB
C++
// This file is part of VSTGUI. It is subject to the license terms
|
|
// in the LICENSE file found in the top-level directory of this
|
|
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
|
|
|
|
#include "uiscripting.h"
|
|
#include "detail/converters.h"
|
|
#include "detail/scriptobject.h"
|
|
#include "detail/uidescscriptobject.h"
|
|
#include "detail/drawcontextobject.h"
|
|
#include "detail/drawable.h"
|
|
#include "detail/iscriptcontextinternal.h"
|
|
#include "detail/scriptingviewfactory.h"
|
|
#include "../uidescription/uiattributes.h"
|
|
#include "../uidescription/uiviewfactory.h"
|
|
#include "../uidescription/uidescriptionaddonregistry.h"
|
|
#include "../lib/iviewlistener.h"
|
|
#include "../lib/cresourcedescription.h"
|
|
#include "../lib/cvstguitimer.h"
|
|
#include "../lib/platform/platformfactory.h"
|
|
#include "../lib/platform/iplatformresourceinputstream.h"
|
|
|
|
#include "tiny-js/TinyJS.h"
|
|
#include "tiny-js/TinyJS_Functions.h"
|
|
#include "tiny-js/TinyJS_MathFunctions.h"
|
|
|
|
#include <iostream>
|
|
#include <sstream>
|
|
|
|
//------------------------------------------------------------------------
|
|
namespace VSTGUI {
|
|
using namespace std::literals;
|
|
using namespace TJS;
|
|
|
|
namespace ScriptingInternal {
|
|
|
|
struct ViewScriptObject;
|
|
|
|
//------------------------------------------------------------------------
|
|
class ScriptContext : public IScriptContextInternal
|
|
{
|
|
public:
|
|
using OnScriptExceptionFunc = UIScripting::OnScriptExceptionFunc;
|
|
using ReadScriptContentsFunc = UIScripting::ReadScriptContentsFunc;
|
|
|
|
ScriptContext (IUIDescription* uiDesc, OnScriptExceptionFunc&& onExceptionFunc,
|
|
ReadScriptContentsFunc&& readContentsFunc);
|
|
~ScriptContext () noexcept;
|
|
|
|
void init (const std::string& initScript);
|
|
void onViewCreated (CView* view, const std::string& script) override;
|
|
|
|
void reset ();
|
|
|
|
private:
|
|
std::string eval (std::string_view script) const override;
|
|
|
|
struct Impl;
|
|
std::unique_ptr<Impl> impl;
|
|
};
|
|
|
|
//------------------------------------------------------------------------
|
|
struct TimerScriptObject : ScriptObject
|
|
{
|
|
template<typename Proc>
|
|
TimerScriptObject (uint64_t fireTime, CScriptVar* _callback, Proc timerProc)
|
|
{
|
|
auto cb = owning (_callback);
|
|
auto t = makeOwned<CVSTGUITimer> (
|
|
[timerProc, cb] (auto timer) {
|
|
if (!timerProc (cb))
|
|
timer->stop ();
|
|
},
|
|
static_cast<uint32_t> (fireTime), false);
|
|
scriptVar->setCustomData (t);
|
|
addFunc ("invalid"sv, [t] (auto v) mutable {
|
|
t = nullptr;
|
|
v->setCustomData (nullptr);
|
|
});
|
|
addFunc ("start"sv, [t] (auto) { t->start (); });
|
|
addFunc ("stop"sv, [t] (auto) { t->stop (); });
|
|
setOnDestroy ([cb] (auto) { cb->release (); });
|
|
}
|
|
};
|
|
|
|
//------------------------------------------------------------------------
|
|
struct ScriptContext::Impl : ViewListenerAdapter,
|
|
ViewEventListenerAdapter,
|
|
ViewContainerListenerAdapter,
|
|
IControlListener,
|
|
ScriptingInternal::IViewScriptObjectContext
|
|
{
|
|
using ViewScriptObject = ScriptingInternal::ViewScriptObject;
|
|
using ScriptAddChildScoped = ScriptingInternal::ScriptAddChildScoped;
|
|
using ScriptObject = ScriptingInternal::ScriptObject;
|
|
using TimerScriptObject = ScriptingInternal::TimerScriptObject;
|
|
using UIDescScriptObject = ScriptingInternal::UIDescScriptObject;
|
|
|
|
IUIDescription* uiDesc {nullptr};
|
|
|
|
std::unique_ptr<CTinyJS> jsContext;
|
|
OnScriptExceptionFunc onScriptException;
|
|
ReadScriptContentsFunc readScriptContents;
|
|
using ViewScriptMap = ScriptingInternal::ViewScriptMap;
|
|
ViewScriptMap viewScriptMap;
|
|
|
|
UIDescScriptObject uiDescObject;
|
|
|
|
Impl (IUIDescription* uiDesc, OnScriptExceptionFunc&& onExceptionFunc,
|
|
ReadScriptContentsFunc&& readContentsFunc)
|
|
: uiDesc (uiDesc)
|
|
, onScriptException (std::move (onExceptionFunc))
|
|
, readScriptContents (std::move (readContentsFunc))
|
|
{
|
|
init ();
|
|
}
|
|
|
|
~Impl () noexcept { reset (true); }
|
|
|
|
void init ()
|
|
{
|
|
jsContext = std::make_unique<CTinyJS> ();
|
|
uiDescObject = UIDescScriptObject (uiDesc, jsContext.get ());
|
|
|
|
registerFunctions (jsContext.get ());
|
|
registerMathFunctions (jsContext.get ());
|
|
|
|
jsContext->getRoot ()->addChild ("uiDesc"sv, uiDescObject);
|
|
|
|
jsContext->addNative (
|
|
"function createTimer(context, fireTime, callback)"sv, [this] (CScriptVar* var) {
|
|
auto context = var->getParameter ("context"sv);
|
|
auto fireTime = var->getParameter ("fireTime"sv);
|
|
auto callback = var->getParameter ("callback"sv);
|
|
if (!fireTime->isInt ())
|
|
{
|
|
throw CScriptException ("Expect integer as first parameter on timer creation");
|
|
}
|
|
if (!callback->isFunction ())
|
|
{
|
|
throw CScriptException (
|
|
"Expect function as second parameter on timer creation");
|
|
}
|
|
auto timerObj = TimerScriptObject (
|
|
fireTime->getInt (), callback->deepCopy (), [this, context] (auto callback) {
|
|
using namespace ScriptingInternal;
|
|
ScriptAddChildScoped scs (*jsContext->getRoot (), "timerContext"sv,
|
|
context);
|
|
return evalScript (callback, "timerCallback (timerContext);"sv,
|
|
"timerCallback");
|
|
});
|
|
var->setReturnVar (timerObj);
|
|
});
|
|
jsContext->addNative (
|
|
"function iterateSubViews(view, context, callback)"sv, [this] (CScriptVar* var) {
|
|
auto view = var->getParameter ("view"sv);
|
|
auto context = var->getParameter ("context"sv);
|
|
auto callback = var->getParameter ("callback"sv);
|
|
if (!view->isObject ())
|
|
{
|
|
throw CScriptException ("Expect object as first parameter on iterateSubViews");
|
|
}
|
|
if (!callback->isFunction ())
|
|
{
|
|
throw CScriptException (
|
|
"Expect function as second parameter on iterateSubViews");
|
|
}
|
|
auto it =
|
|
std::find_if (viewScriptMap.begin (), viewScriptMap.end (),
|
|
[&] (const auto& el) { return el.second->getVar () == view; });
|
|
if (it == viewScriptMap.end ())
|
|
throw CScriptException ("View not found in iterateSubViews");
|
|
auto container = it->first->asViewContainer ();
|
|
if (!container)
|
|
return; // no sub views
|
|
container->forEachChild ([&] (auto child) {
|
|
using namespace ScriptingInternal;
|
|
auto childScriptObject = viewScriptMap.find (child);
|
|
if (childScriptObject != viewScriptMap.end ())
|
|
{
|
|
ScriptAddChildScoped scs (*jsContext->getRoot (), "child"sv,
|
|
childScriptObject->second->getVar ());
|
|
ScriptAddChildScoped scs2 (*jsContext->getRoot (), "context"sv, context);
|
|
evalScript (callback, "callback (child, context);"sv, "callback");
|
|
}
|
|
else
|
|
{
|
|
auto scriptObj =
|
|
addView (child, std::make_unique<ViewScriptObject> (child, this));
|
|
ScriptAddChildScoped scs (*jsContext->getRoot (), "child"sv,
|
|
scriptObj->getVar ());
|
|
ScriptAddChildScoped scs2 (*jsContext->getRoot (), "context"sv, context);
|
|
evalScript (callback, "callback (child, context);"sv, "callback");
|
|
}
|
|
});
|
|
});
|
|
jsContext->addNative ("function log(obj)",
|
|
[this] (CScriptVar* var) { log (var->getParameter ("obj"sv)); });
|
|
|
|
jsContext->addNative ("function makeTransformMatrix()", [] (CScriptVar* var) {
|
|
auto tm = makeTransformMatrixObject ();
|
|
var->setReturnVar (tm);
|
|
tm->release ();
|
|
});
|
|
}
|
|
|
|
void reset (bool terminate = false)
|
|
{
|
|
for (auto it = viewScriptMap.begin (); it != viewScriptMap.end ();)
|
|
{
|
|
viewRemoved (it->first);
|
|
uninstallListeners (it->first);
|
|
it = eraseViewFromMap (it);
|
|
}
|
|
if (!terminate)
|
|
init ();
|
|
}
|
|
|
|
void initWithScript (const std::string* script)
|
|
{
|
|
if (script)
|
|
{
|
|
auto scriptContent = getScriptFileContent (*script);
|
|
if (!scriptContent.empty ())
|
|
script = &scriptContent;
|
|
evalScript (*script);
|
|
}
|
|
}
|
|
CScriptVar* getRoot () const override { return jsContext->getRoot (); }
|
|
IUIDescription* getUIDescription () const override { return uiDesc; }
|
|
|
|
void installListeners (CView* view)
|
|
{
|
|
view->registerViewListener (this);
|
|
view->registerViewEventListener (this);
|
|
if (auto viewContainer = view->asViewContainer ())
|
|
viewContainer->registerViewContainerListener (this);
|
|
else if (auto control = dynamic_cast<CControl*> (view))
|
|
control->registerControlListener (this);
|
|
}
|
|
|
|
void uninstallListeners (CView* view)
|
|
{
|
|
view->unregisterViewListener (this);
|
|
view->unregisterViewEventListener (this);
|
|
if (auto viewContainer = view->asViewContainer ())
|
|
viewContainer->unregisterViewContainerListener (this);
|
|
if (auto control = dynamic_cast<CControl*> (view))
|
|
control->unregisterControlListener (this);
|
|
}
|
|
|
|
ScriptObject evalScript (std::string_view script) noexcept override
|
|
{
|
|
try
|
|
{
|
|
auto result = jsContext->evaluateComplex (script);
|
|
#if 0 // DEBUG
|
|
if (result.getVar () && !result.getVar ()->isUndefined ())
|
|
{
|
|
DebugPrint ("%s\n", result.getVar ()->getString ().data ());
|
|
}
|
|
#endif
|
|
return result.getVar ();
|
|
}
|
|
catch (const CScriptException& exc)
|
|
{
|
|
#if DEBUG
|
|
DebugPrint ("Scripting Exception: %s\n", exc.text.data ());
|
|
#endif
|
|
if (onScriptException)
|
|
onScriptException (exc.text);
|
|
return {};
|
|
}
|
|
}
|
|
|
|
ScriptObject evalScript (CScriptVar* object, std::string_view script,
|
|
const std::string& objectName = "view") noexcept
|
|
{
|
|
if (!object || script.empty ())
|
|
return {};
|
|
vstgui_assert (object->getRefs () > 0);
|
|
object->addRef ();
|
|
ScriptAddChildScoped scs (*jsContext->getRoot (), objectName, object);
|
|
auto result = evalScript (script);
|
|
object->release ();
|
|
return result;
|
|
}
|
|
|
|
template<typename Proc>
|
|
void callWhenScriptHasFunction (CView* view, std::string_view funcName, Proc proc)
|
|
{
|
|
auto it = viewScriptMap.find (view);
|
|
if (it == viewScriptMap.end ())
|
|
return;
|
|
if (it->second->getVar ()->findChild (funcName))
|
|
proc (this, it->second);
|
|
}
|
|
|
|
void viewAttached (CView* view) override
|
|
{
|
|
callWhenScriptHasFunction (view, "onAttached"sv, [] (auto This, auto& obj) {
|
|
static constexpr auto script = R"(view.onAttached(view);)"sv;
|
|
This->evalScript (obj->getVar (), script);
|
|
});
|
|
}
|
|
|
|
void viewRemoved (CView* view) override
|
|
{
|
|
callWhenScriptHasFunction (view, "onRemoved"sv, [] (auto This, auto& obj) {
|
|
static constexpr auto script = R"(view.onRemoved(view);)"sv;
|
|
This->evalScript (obj->getVar (), script);
|
|
});
|
|
}
|
|
|
|
void viewSizeChanged (CView* view, const CRect& oldSize) override
|
|
{
|
|
callWhenScriptHasFunction (view, "onSizeChanged"sv, [&] (auto This, auto& obj) {
|
|
auto newSize = view->getViewSize ();
|
|
ScriptObject newSizeObject = ScriptingInternal::makeScriptRect (newSize);
|
|
ScriptAddChildScoped scs (*jsContext->getRoot (), "newSize"sv, newSizeObject);
|
|
static constexpr auto script = R"(view.onSizeChanged(view, newSize);)"sv;
|
|
This->evalScript (obj->getVar (), script);
|
|
});
|
|
}
|
|
|
|
void viewLostFocus (CView* view) override
|
|
{
|
|
callWhenScriptHasFunction (view, "onLostFocus"sv, [&] (auto This, auto& obj) {
|
|
static constexpr auto script = R"(view.onLostFocus(view);)"sv;
|
|
This->evalScript (obj->getVar (), script);
|
|
});
|
|
}
|
|
|
|
void viewTookFocus (CView* view) override
|
|
{
|
|
callWhenScriptHasFunction (view, "onTookFocus"sv, [&] (auto This, auto& obj) {
|
|
static constexpr auto script = R"(view.onTookFocus(view);)"sv;
|
|
This->evalScript (obj->getVar (), script);
|
|
});
|
|
}
|
|
|
|
void viewOnMouseEnabled (CView* view, bool state) override
|
|
{
|
|
callWhenScriptHasFunction (view, "onMouseEnabled"sv, [&] (auto This, auto& obj) {
|
|
static constexpr auto scriptEnabled = R"(view.onMouseEnabled(view, true);)"sv;
|
|
static constexpr auto scriptDisabled = R"(view.onMouseEnabled(view, false);)"sv;
|
|
This->evalScript (obj->getVar (), state ? scriptEnabled : scriptDisabled);
|
|
});
|
|
}
|
|
|
|
void callViewAddedOrRemoved (CViewContainer* container, CView* view, std::string_view function,
|
|
std::string_view script)
|
|
{
|
|
callWhenScriptHasFunction (container, function, [&] (auto This, auto& obj) {
|
|
auto childScriptObject = viewScriptMap.find (view);
|
|
if (childScriptObject != viewScriptMap.end ())
|
|
{
|
|
ScriptAddChildScoped scs (*jsContext->getRoot (), "child"sv,
|
|
childScriptObject->second->getVar ());
|
|
This->evalScript (obj->getVar (), script);
|
|
}
|
|
else
|
|
{
|
|
auto scriptObj = addView (view, std::make_unique<ViewScriptObject> (view, this));
|
|
ScriptAddChildScoped scs (*jsContext->getRoot (), "child"sv, scriptObj->getVar ());
|
|
This->evalScript (obj->getVar (), script);
|
|
}
|
|
});
|
|
}
|
|
|
|
void viewContainerViewAdded (CViewContainer* container, CView* view) override
|
|
{
|
|
callViewAddedOrRemoved (container, view, "onViewAdded"sv,
|
|
R"(view.onViewAdded(view, child);)"sv);
|
|
}
|
|
|
|
void viewContainerViewRemoved (CViewContainer* container, CView* view) override
|
|
{
|
|
callViewAddedOrRemoved (container, view, "onViewRemoved"sv,
|
|
R"(view.onViewRemoved(view, child);)"sv);
|
|
}
|
|
|
|
void checkEventConsumed (ScriptObject& obj, Event& event)
|
|
{
|
|
if (auto consume = obj->findChild ("consumed"))
|
|
{
|
|
if (consume->getVar ()->isInt () && consume->getVar ()->getInt ())
|
|
event.consumed = true;
|
|
}
|
|
}
|
|
|
|
void callEventFunction (CScriptVar* var, Event& event, std::string_view script) noexcept
|
|
{
|
|
auto scriptEvent = ScriptingInternal::makeScriptEvent (event);
|
|
ScriptAddChildScoped scs (*jsContext->getRoot (), "event"sv, scriptEvent);
|
|
evalScript (var, script);
|
|
checkEventConsumed (scriptEvent, event);
|
|
}
|
|
|
|
void viewOnEvent (CView* view, Event& event) override
|
|
{
|
|
auto applyEventMouseLocalPosition = [&] (auto proc) {
|
|
auto& mouseEvent = castMousePositionEvent (event);
|
|
auto oldPos = mouseEvent.mousePosition;
|
|
mouseEvent.mousePosition -= view->getViewSize ().getTopLeft ();
|
|
proc ();
|
|
mouseEvent.mousePosition = oldPos;
|
|
};
|
|
switch (event.type)
|
|
{
|
|
case EventType::MouseEnter:
|
|
{
|
|
applyEventMouseLocalPosition ([&] () {
|
|
callWhenScriptHasFunction (view, "onMouseEnter"sv, [&] (auto This, auto& obj) {
|
|
static constexpr auto script = R"(view.onMouseEnter(view, event);)"sv;
|
|
callEventFunction (obj->getVar (), event, script);
|
|
});
|
|
});
|
|
break;
|
|
}
|
|
case EventType::MouseExit:
|
|
{
|
|
applyEventMouseLocalPosition ([&] () {
|
|
callWhenScriptHasFunction (view, "onMouseExit"sv, [&] (auto This, auto& obj) {
|
|
static constexpr auto script = R"(view.onMouseExit(view, event);)"sv;
|
|
callEventFunction (obj->getVar (), event, script);
|
|
});
|
|
});
|
|
break;
|
|
}
|
|
case EventType::MouseDown:
|
|
{
|
|
applyEventMouseLocalPosition ([&] () {
|
|
callWhenScriptHasFunction (view, "onMouseDown"sv, [&] (auto This, auto& obj) {
|
|
static constexpr auto script = R"(view.onMouseDown(view, event);)"sv;
|
|
callEventFunction (obj->getVar (), event, script);
|
|
});
|
|
});
|
|
break;
|
|
}
|
|
case EventType::MouseUp:
|
|
{
|
|
applyEventMouseLocalPosition ([&] () {
|
|
callWhenScriptHasFunction (view, "onMouseUp"sv, [&] (auto This, auto& obj) {
|
|
static constexpr auto script = R"(view.onMouseUp(view, event);)"sv;
|
|
callEventFunction (obj->getVar (), event, script);
|
|
});
|
|
});
|
|
break;
|
|
}
|
|
case EventType::MouseMove:
|
|
{
|
|
applyEventMouseLocalPosition ([&] () {
|
|
callWhenScriptHasFunction (view, "onMouseMove"sv, [&] (auto This, auto& obj) {
|
|
static constexpr auto script = R"(view.onMouseMove(view, event);)"sv;
|
|
callEventFunction (obj->getVar (), event, script);
|
|
});
|
|
});
|
|
break;
|
|
}
|
|
case EventType::MouseWheel:
|
|
{
|
|
applyEventMouseLocalPosition ([&] () {
|
|
callWhenScriptHasFunction (view, "onMouseWheel"sv, [&] (auto This, auto& obj) {
|
|
static constexpr auto script = R"(view.onMouseWheel(view, event);)"sv;
|
|
callEventFunction (obj->getVar (), event, script);
|
|
});
|
|
});
|
|
break;
|
|
}
|
|
case EventType::KeyDown:
|
|
{
|
|
callWhenScriptHasFunction (view, "onKeyDown"sv, [&] (auto This, auto& obj) {
|
|
static constexpr auto script = R"(view.onKeyDown(view, event);)"sv;
|
|
callEventFunction (obj->getVar (), event, script);
|
|
});
|
|
break;
|
|
}
|
|
case EventType::KeyUp:
|
|
{
|
|
callWhenScriptHasFunction (view, "onKeyUp"sv, [&] (auto This, auto& obj) {
|
|
static constexpr auto script = R"(view.onKeyUp(view, event);)"sv;
|
|
callEventFunction (obj->getVar (), event, script);
|
|
});
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void valueChanged (CControl* control) override
|
|
{
|
|
callWhenScriptHasFunction (control, "onValueChanged"sv, [&] (auto This, auto& obj) {
|
|
ScriptObject controlValue;
|
|
controlValue->setDouble (control->getValue ());
|
|
ScriptAddChildScoped scs (*jsContext->getRoot (), "value"sv, controlValue);
|
|
static constexpr auto script = R"(view.onValueChanged(view, value);)"sv;
|
|
This->evalScript (obj->getVar (), script);
|
|
});
|
|
}
|
|
|
|
void controlBeginEdit (CControl* control) override
|
|
{
|
|
callWhenScriptHasFunction (control, "onBeginEdit"sv, [] (auto This, auto& obj) {
|
|
static constexpr auto script = R"(view.onBeginEdit(view);)"sv;
|
|
This->evalScript (obj->getVar (), script);
|
|
});
|
|
}
|
|
void controlEndEdit (CControl* control) override
|
|
{
|
|
callWhenScriptHasFunction (control, "onEndEdit"sv, [] (auto This, auto& obj) {
|
|
static constexpr auto script = R"(view.onEndEdit(view);)"sv;
|
|
This->evalScript (obj->getVar (), script);
|
|
});
|
|
}
|
|
|
|
void viewWillDelete (CView* view) override { removeView (view); }
|
|
|
|
ViewScriptObject* addView (CView* view, std::unique_ptr<ViewScriptObject>&& scriptObject)
|
|
{
|
|
installListeners (view);
|
|
viewScriptMap[view] = std::move (scriptObject);
|
|
return viewScriptMap[view].get ();
|
|
}
|
|
|
|
ViewScriptMap::iterator eraseViewFromMap (ViewScriptMap::iterator el)
|
|
{
|
|
return viewScriptMap.erase (el);
|
|
}
|
|
|
|
ViewScriptMap::iterator removeView (CView* view) override
|
|
{
|
|
uninstallListeners (view);
|
|
auto it = viewScriptMap.find (view);
|
|
if (it != viewScriptMap.end ())
|
|
return eraseViewFromMap (it);
|
|
return viewScriptMap.end ();
|
|
}
|
|
|
|
ViewScriptObject* addView (CView* view) override
|
|
{
|
|
auto it = viewScriptMap.find (view);
|
|
if (it != viewScriptMap.end ())
|
|
return it->second.get ();
|
|
return addView (view, std::make_unique<ViewScriptObject> (view, this));
|
|
}
|
|
|
|
void addView (CView* view, const std::string* script) noexcept
|
|
{
|
|
addView (view);
|
|
if (script)
|
|
{
|
|
auto scriptContent = getScriptFileContent (*script);
|
|
if (!scriptContent.empty ())
|
|
script = &scriptContent;
|
|
evalScript (viewScriptMap[view]->getVar (), *script);
|
|
}
|
|
}
|
|
|
|
std::string getScriptFileContent (const std::string& script) const
|
|
{
|
|
constexpr std::string_view scriptFileCommand ("//scriptfile ");
|
|
std::string_view scriptView (script);
|
|
if (scriptView.length () > scriptFileCommand.length ())
|
|
{
|
|
if (scriptView.substr (0, scriptFileCommand.length ()) == scriptFileCommand)
|
|
{
|
|
auto scriptFileStr = scriptView.substr (scriptFileCommand.length ());
|
|
if (scriptFileStr.length () < 255)
|
|
{
|
|
return readScriptContents (scriptFileStr);
|
|
}
|
|
}
|
|
}
|
|
return {};
|
|
}
|
|
|
|
void log (CScriptVar* var) const
|
|
{
|
|
#if DEBUG
|
|
static constexpr size_t bufferSize = 512;
|
|
|
|
struct DebugOutputStream : private std::streambuf,
|
|
public std::ostream
|
|
{
|
|
|
|
DebugOutputStream () : std::ostream (this) { data.reserve (bufferSize); }
|
|
~DebugOutputStream () noexcept
|
|
{
|
|
if (!data.empty ())
|
|
flush ();
|
|
}
|
|
|
|
private:
|
|
int overflow (int c) override
|
|
{
|
|
if (data.length () >= bufferSize)
|
|
flush ();
|
|
data += static_cast<char> (c);
|
|
return 0;
|
|
}
|
|
void flush ()
|
|
{
|
|
DebugPrint ("%s", data.data ());
|
|
data.clear ();
|
|
data.reserve (bufferSize);
|
|
}
|
|
std::string data;
|
|
};
|
|
DebugOutputStream logStream;
|
|
var->getJSON (logStream);
|
|
logStream << "\n";
|
|
#endif
|
|
}
|
|
};
|
|
|
|
//------------------------------------------------------------------------
|
|
//------------------------------------------------------------------------
|
|
//------------------------------------------------------------------------
|
|
ScriptContext::ScriptContext (IUIDescription* uiDesc, OnScriptExceptionFunc&& onExceptionFunc,
|
|
ReadScriptContentsFunc&& readContentsFunc)
|
|
{
|
|
impl =
|
|
std::make_unique<Impl> (uiDesc, std::move (onExceptionFunc), std::move (readContentsFunc));
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
ScriptContext::~ScriptContext () noexcept {}
|
|
|
|
//------------------------------------------------------------------------
|
|
void ScriptContext::init (const std::string& initScript) { impl->initWithScript (&initScript); }
|
|
|
|
//------------------------------------------------------------------------
|
|
void ScriptContext::onViewCreated (CView* view, const std::string& script)
|
|
{
|
|
impl->addView (view, &script);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void ScriptContext::reset () { impl->reset (); }
|
|
|
|
//------------------------------------------------------------------------
|
|
std::string ScriptContext::eval (std::string_view script) const
|
|
{
|
|
if (!impl->jsContext)
|
|
return {};
|
|
try
|
|
{
|
|
auto result = impl->jsContext->evaluateComplex (script);
|
|
std::stringstream stream;
|
|
result.getVar ()->getJSON (stream);
|
|
return stream.str ();
|
|
}
|
|
catch (const CScriptException& exc)
|
|
{
|
|
if (impl->onScriptException)
|
|
impl->onScriptException (exc.text);
|
|
}
|
|
return {};
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
} // ScriptingInternal
|
|
|
|
//------------------------------------------------------------------------
|
|
//------------------------------------------------------------------------
|
|
//------------------------------------------------------------------------
|
|
struct UIScripting::Impl
|
|
{
|
|
using JSViewFactoryPtr = std::unique_ptr<ScriptingInternal::JavaScriptViewFactory>;
|
|
using ScriptContextPtr = std::unique_ptr<ScriptingInternal::ScriptContext>;
|
|
|
|
std::unordered_map<const IUIDescription*, std::pair<JSViewFactoryPtr, ScriptContextPtr>> map;
|
|
|
|
static OnScriptExceptionFunc onScriptExceptionFunc;
|
|
static ReadScriptContentsFunc readScriptContentsFunc;
|
|
|
|
static std::string readScriptContentsFromResource (std::string_view filename);
|
|
};
|
|
|
|
//------------------------------------------------------------------------
|
|
UIScripting::OnScriptExceptionFunc UIScripting::Impl::onScriptExceptionFunc;
|
|
UIScripting::ReadScriptContentsFunc UIScripting::Impl::readScriptContentsFunc;
|
|
|
|
//------------------------------------------------------------------------
|
|
std::string UIScripting::Impl::readScriptContentsFromResource (std::string_view filename)
|
|
{
|
|
std::string fileStr (filename);
|
|
CResourceDescription desc (fileStr.data ());
|
|
if (auto stream = getPlatformFactory ().createResourceInputStream (desc))
|
|
{
|
|
std::string fileContent;
|
|
while (true)
|
|
{
|
|
char buffer[256];
|
|
auto numRead = stream->readRaw (buffer, static_cast<uint32_t> (std::size (buffer)));
|
|
if (numRead == kStreamIOError)
|
|
return {};
|
|
fileContent.append (buffer, numRead);
|
|
if (numRead < std::size (buffer))
|
|
break;
|
|
}
|
|
return fileContent;
|
|
}
|
|
return {};
|
|
};
|
|
|
|
//------------------------------------------------------------------------
|
|
UIScripting::UIScripting () { impl = std::make_unique<Impl> (); }
|
|
|
|
//------------------------------------------------------------------------
|
|
UIScripting::~UIScripting () noexcept = default;
|
|
|
|
//------------------------------------------------------------------------
|
|
void UIScripting::afterParsing (IUIDescription* desc) {}
|
|
|
|
//------------------------------------------------------------------------
|
|
void UIScripting::beforeSaving (IUIDescription* desc) {}
|
|
|
|
//------------------------------------------------------------------------
|
|
void UIScripting::onDestroy (IUIDescription* desc)
|
|
{
|
|
auto it = impl->map.find (desc);
|
|
if (it != impl->map.end ())
|
|
impl->map.erase (it);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
auto UIScripting::onCreateTemplateView (const IUIDescription* desc, const CreateTemplateViewFunc& f)
|
|
-> CreateTemplateViewFunc
|
|
{
|
|
return [=] (auto name, auto controller) {
|
|
return f (name, controller);
|
|
};
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
IViewFactory* UIScripting::getViewFactory (IUIDescription* desc, IViewFactory* originalFactory)
|
|
{
|
|
using namespace ScriptingInternal;
|
|
|
|
auto it = impl->map.find (desc);
|
|
if (it != impl->map.end ())
|
|
return it->second.first.get ();
|
|
auto onScriptException = Impl::onScriptExceptionFunc;
|
|
if (!onScriptException)
|
|
onScriptException = [] (std::string_view reason) {
|
|
std::cerr << reason << '\n';
|
|
};
|
|
auto readScriptContentsFunc = [] (auto filename) {
|
|
std::string result;
|
|
if (Impl::readScriptContentsFunc)
|
|
result = Impl::readScriptContentsFunc (filename);
|
|
if (result.empty ())
|
|
{
|
|
result = Impl::readScriptContentsFromResource (filename);
|
|
}
|
|
return result;
|
|
};
|
|
auto scripting = std::make_unique<ScriptContext> (desc, std::move (onScriptException),
|
|
std::move (readScriptContentsFunc));
|
|
auto viewFactory = std::make_unique<JavaScriptViewFactory> (scripting.get (), originalFactory);
|
|
auto result =
|
|
impl->map.emplace (desc, std::make_pair (std::move (viewFactory), std::move (scripting)));
|
|
return result.first->second.first.get ();
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void UIScripting::onEditingStart (IUIDescription* desc)
|
|
{
|
|
auto it = impl->map.find (desc);
|
|
if (it != impl->map.end ())
|
|
{
|
|
it->second.second->reset ();
|
|
it->second.first->setScriptingDisabled (true);
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void UIScripting::onEditingEnd (IUIDescription* desc)
|
|
{
|
|
auto it = impl->map.find (desc);
|
|
if (it != impl->map.end ())
|
|
it->second.first->setScriptingDisabled (false);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void UIScripting::init (const OnScriptExceptionFunc& onExceptionFunc,
|
|
const ReadScriptContentsFunc& readScriptContentsFunc)
|
|
{
|
|
if (onExceptionFunc)
|
|
Impl::onScriptExceptionFunc = onExceptionFunc;
|
|
if (readScriptContentsFunc)
|
|
Impl::readScriptContentsFunc = readScriptContentsFunc;
|
|
UIDescriptionAddOnRegistry::add (std::make_unique<UIScripting> ());
|
|
static ScriptingInternal::JavaScriptDrawableViewCreator jsViewCreator;
|
|
UIViewFactory::registerViewCreator (jsViewCreator);
|
|
static ScriptingInternal::JavaScriptDrawableControlCreator jsControlCreator;
|
|
UIViewFactory::registerViewCreator (jsControlCreator);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
} // VSTGUI
|