Skip to content

Commit

Permalink
[js] Implement ability to create custom QML UIs that can control the …
Browse files Browse the repository at this point in the history
…score
  • Loading branch information
jcelerier committed Jul 29, 2024
1 parent 3951def commit edfa97a
Show file tree
Hide file tree
Showing 22 changed files with 796 additions and 33 deletions.
11 changes: 11 additions & 0 deletions src/lib/core/application/ApplicationSettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ void ApplicationSettings::parse(QStringList cargs, int& argc, char** argv)
"N", "0");
parser.addOption(waitLoadOpt);

QCommandLineOption uiOpt(
"ui", QCoreApplication::translate("main", "Specify an UI file to load."), "file",
"");
parser.addOption(uiOpt);

#if defined(__APPLE__)
// Bogus macOS gatekeeper BS:
// https://stackoverflow.com/questions/55562155/qt-application-for-mac-not-being-launched
Expand Down Expand Up @@ -106,6 +111,12 @@ void ApplicationSettings::parse(QStringList cargs, int& argc, char** argv)
tryToRestore = !parser.isSet(noRestore);
this->forceRestore = parser.isSet(forceRestore);
gui = !parser.isSet(noGUI);

if(parser.isSet(uiOpt))
{
gui = false;
ui = parser.value(uiOpt);
}
if(parser.isSet(GL))
opengl = true;
if(parser.isSet(noGL))
Expand Down
3 changes: 3 additions & 0 deletions src/lib/core/application/ApplicationSettings.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ struct SCORE_LIB_BASE_EXPORT ApplicationSettings
//! List of scenarios that should be loaded
QStringList loadList;

//! UI file to be loaded
QString ui;

//! Seconds to wait before playing
int waitAfterLoad = 0;

Expand Down
13 changes: 12 additions & 1 deletion src/plugins/score-plugin-js/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,24 @@ set(HDRS
"${CMAKE_CURRENT_SOURCE_DIR}/JS/Executor/GPUNode.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/JS/Executor/JSAPIWrapper.hpp"

"${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/AddressItem.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/DeviceContext.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/EditContext.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/QmlObjects.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/ValueTypes.${QT_PREFIX}.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/Metatypes.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/PortSource.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/Utils.hpp"

"${CMAKE_CURRENT_SOURCE_DIR}/JS/ApplicationPlugin.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/JS/ConsolePanel.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/JS/DropHandler.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/JS/JSProcessFactory.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/JS/JSProcessMetadata.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/JS/JSProcessModel.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/JS/ConsolePanel.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/JS/LibraryHandler.hpp"
)

set(SRCS
"${CMAKE_CURRENT_SOURCE_DIR}/JS/JSProcessModel.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/JS/JSProcessModelSerialization.cpp"
Expand All @@ -51,6 +58,8 @@ set(SRCS

"${CMAKE_CURRENT_SOURCE_DIR}/JS/Commands/JSCommandFactory.cpp"

"${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/AddressItem.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/DeviceContext.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/EditContext.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/EditContext.curve.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/EditContext.device.cpp"
Expand All @@ -59,10 +68,12 @@ set(SRCS
"${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/EditContext.port.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/EditContext.scenario.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/EditContext.ui.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/PortSource.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/QmlObjects.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/ValueTypes.${QT_PREFIX}.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/Utils.cpp"

"${CMAKE_CURRENT_SOURCE_DIR}/JS/ApplicationPlugin.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/JS/ConsolePanel.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/score_plugin_js.cpp"
)
Expand Down
46 changes: 46 additions & 0 deletions src/plugins/score-plugin-js/JS/ApplicationPlugin.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#include "ApplicationPlugin.hpp"

#include <JS/Qml/DeviceContext.hpp>
#include <JS/Qml/EditContext.hpp>
#include <JS/Qml/Utils.hpp>

namespace JS
{
ApplicationPlugin::ApplicationPlugin(const score::GUIApplicationContext& ctx)
: score::GUIApplicationPlugin{ctx}
{
m_engine.globalObject().setProperty("Score", m_engine.newQObject(new EditJsContext));
m_engine.globalObject().setProperty("Util", m_engine.newQObject(new JsUtils));
m_engine.globalObject().setProperty("Device", m_engine.newQObject(new DeviceContext));
}

ApplicationPlugin::~ApplicationPlugin() { }

bool ApplicationPlugin::handleStartup()
{
if(QFileInfo f{context.applicationSettings.ui}; f.isFile())
{
m_comp = new QQmlComponent{&m_engine, f.absoluteFilePath(), this};

if(auto obj = m_comp->create())
{
if(auto item = qobject_cast<QQuickItem*>(obj))
{
m_window = new QQuickWindow{};
m_window->setWidth(640);
m_window->setHeight(480);
item->setParentItem(m_window->contentItem());
m_window->show();
return true;
}
}
else
{
qDebug() << m_comp->errorString();
qGuiApp->exit(1);
}
delete m_comp;
}
return false;
}
}
28 changes: 28 additions & 0 deletions src/plugins/score-plugin-js/JS/ApplicationPlugin.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#pragma once
#include <score/plugins/application/GUIApplicationPlugin.hpp>

#include <core/application/ApplicationSettings.hpp>

#include <QFileInfo>
#include <QQmlComponent>
#include <QQmlContext>
#include <QQmlEngine>
#include <QQuickView>

namespace JS
{
class ApplicationPlugin final
: public QObject
, public score::GUIApplicationPlugin
{
public:
explicit ApplicationPlugin(const score::GUIApplicationContext& ctx);

~ApplicationPlugin() override;
bool handleStartup() override;

QQmlEngine m_engine;
QQmlComponent* m_comp{};
QQuickWindow* m_window{};
};
}
9 changes: 5 additions & 4 deletions src/plugins/score-plugin-js/JS/ConsolePanel.cpp
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
#include "ConsolePanel.hpp"

#include <JS/Qml/EditContext.hpp>
#include <JS/Qml/Utils.hpp>
#include <JS/ApplicationPlugin.hpp>

#include <QDebug>
#include <QFileInfo>
#include <QJSValueIterator>
#include <QQmlEngine>

namespace JS
{

PanelDelegate::PanelDelegate(const score::GUIApplicationContext& ctx)
: score::PanelDelegate{ctx}
, m_engine{ctx.guiApplicationPlugin<JS::ApplicationPlugin>().m_engine}
, m_widget{new QWidget}
{
m_engine.installExtensions(QJSEngine::ConsoleExtension);
m_engine.globalObject().setProperty("Score", m_engine.newQObject(new EditJsContext));
m_engine.globalObject().setProperty("Util", m_engine.newQObject(new JsUtils));
m_engine.globalObject().setProperty(
"ActionContext", m_engine.newQObject(new ActionContext));

auto lay = new QVBoxLayout;
m_widget->setLayout(lay);
m_widget->setStatusTip(
Expand Down
6 changes: 3 additions & 3 deletions src/plugins/score-plugin-js/JS/ConsolePanel.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@

#include <core/application/ApplicationSettings.hpp>

#include <QJSEngine>
#include <QJSValueIterator>
#include <QLineEdit>
#include <QMenu>
#include <QPlainTextEdit>
Expand All @@ -20,6 +18,8 @@

#include <score_plugin_js_export.h>
#include <wobjectimpl.h>
class QQmlEngine;
class QJSEngine;
namespace JS
{

Expand Down Expand Up @@ -50,10 +50,10 @@ class SCORE_PLUGIN_JS_EXPORT PanelDelegate final

const score::PanelStatus& defaultPanelStatus() const override;

QQmlEngine& m_engine;
QWidget* m_widget{};
QPlainTextEdit* m_edit{};
QLineEdit* m_lineEdit{};
QJSEngine m_engine;
};

class PanelDelegateFactory final : public score::PanelDelegateFactory
Expand Down
7 changes: 6 additions & 1 deletion src/plugins/score-plugin-js/JS/Executor/CPUNode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
#include <score/serialization/AnySerialization.hpp>
#include <score/serialization/MapSerialization.hpp>

#include <ossia/dataflow/execution_state.hpp>

#include <QCoreApplication>
#include <QDir>
#include <QEventLoop>
Expand Down Expand Up @@ -80,7 +82,10 @@ void js_node::setScript(const QString& val)
if(!m_engine)
{
m_engine = new QQmlEngine;
m_execFuncs = new ExecStateWrapper{m_st, m_engine};
m_execFuncs = new ExecStateWrapper{
m_st.exec_devices(), [&]<typename... Args>(Args&&... args) {
m_st.insert(std::forward<Args>(args)...);
}, m_engine};
m_engine->rootContext()->setContextProperty("Device", m_execFuncs);

QObject::connect(
Expand Down
36 changes: 21 additions & 15 deletions src/plugins/score-plugin-js/JS/Executor/JSAPIWrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const ossia::destination_t& ExecStateWrapper::find_address(const QString& str)
{
// Address looks like '/foo/bar'
// Try to find automatically in all devices
auto node = devices.find_node(str.toStdString());
auto node = find_node(devices, str.toStdString());
if(node)
{
if(auto addr = node->get_parameter())
Expand All @@ -56,11 +56,11 @@ const ossia::destination_t& ExecStateWrapper::find_address(const QString& str)

// Split in devices
auto dev = ossia::find_if(
devices.exec_devices(), [devname = str.mid(0, d).toStdString()](const auto& dev) {
return dev->get_name() == devname;
});
devices, [devname = str.mid(0, d).toStdString()](const auto& dev) {
return dev->get_name() == devname;
});

if(dev != devices.exec_devices().end())
if(dev != devices.end())
{
if(d == str.size() - 1)
{
Expand Down Expand Up @@ -89,7 +89,7 @@ const ossia::destination_t& ExecStateWrapper::find_address(const QString& str)
return it->second;
}

static ossia::destination_t bad_dest;
static const ossia::destination_t bad_dest;
return bad_dest;
}

Expand All @@ -101,8 +101,7 @@ QVariant ExecStateWrapper::read(const QString& address)
QVariantMap mv;

bool unique = ossia::apply_to_destination(
addr, devices.exec_devices(),
[&](ossia::net::parameter_base* addr, bool unique) {
addr, devices, [&](ossia::net::parameter_base* addr, bool unique) {
if(unique)
{
var = addr->value().apply(ossia::qt::ossia_to_qvariant{});
Expand All @@ -112,8 +111,7 @@ QVariant ExecStateWrapper::read(const QString& address)
mv[QString::fromStdString(addr->get_node().osc_address())]
= addr->value().apply(ossia::qt::ossia_to_qvariant{});
}
},
ossia::do_nothing_for_nodes{});
}, ossia::do_nothing_for_nodes{});
if(unique)
return var;
else
Expand All @@ -130,12 +128,20 @@ void ExecStateWrapper::write(const QString& address, const QVariant& value)
m_port_cache.get_data().emplace_back(ossia::qt::qt_to_ossia{}(value));

ossia::apply_to_destination(
addr, devices.exec_devices(),
[&](ossia::net::parameter_base* addr, bool unique) {
devices.insert(*addr, m_port_cache);
},
ossia::do_nothing_for_nodes{});
addr, devices, [&](ossia::net::parameter_base* addr, bool unique) {
on_push(*addr, m_port_cache);
}, ossia::do_nothing_for_nodes{});
}
}

ossia::net::node_base*
ExecStateWrapper::find_node(DeviceCache& devices, std::string_view name)
{
for(auto dev : devices)
{
if(auto res = ossia::net::find_node(dev->get_root_node(), name))
return res;
}
return nullptr;
}
}
50 changes: 47 additions & 3 deletions src/plugins/score-plugin-js/JS/Executor/JSAPIWrapper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,32 @@
#include <QObject>

#include <verdigris>
namespace Device
{
class DeviceList;
}
namespace ossia
{
struct execution_state;
namespace net
{
class device_base;
}
}
namespace JS
{
using DeviceCache = ossia::small_vector<ossia::net::device_base*, 4>;
using DevicePushFunction
= smallfun::function<void(ossia::net::parameter_base&, const ossia::value_port&)>;

class ExecStateWrapper : public QObject
{
W_OBJECT(ExecStateWrapper)
public:
ExecStateWrapper(ossia::execution_state& state, QObject* parent)
ExecStateWrapper(const DeviceCache& state, DevicePushFunction push, QObject* parent)
: QObject{parent}
, devices{state}
, on_push{std::move(push)}
{
}
~ExecStateWrapper() override;
Expand All @@ -35,12 +48,43 @@ class ExecStateWrapper : public QObject

void system(const QString& code) W_SIGNAL(system, code);

static ossia::net::node_base* find_node(DeviceCache& devices, std::string_view name);
const ossia::destination_t& find_address(const QString&);

DeviceCache devices;

private:
DevicePushFunction on_push;

ossia::hash_map<QString, ossia::destination_t> m_address_cache;
ossia::value_port m_port_cache;
// TODO share cache
};

/*
class EditStateWrapper : public QObject
{
W_OBJECT(EditStateWrapper)
public:
EditStateWrapper(Device::DeviceList& state, QObject* parent)
: QObject{parent}
, devices{state}
{
}
~EditStateWrapper() override;
QVariant read(const QString& address);
W_SLOT(read);
void write(const QString& address, const QVariant& value);
W_SLOT(write);
private:
ossia::execution_state& devices;
Device::DeviceList& devices;
const ossia::destination_t& find_address(const QString&);
ossia::hash_map<QString, ossia::destination_t> m_address_cache;
ossia::value_port m_port_cache;
// TODO share cash across
// TODO share cache
};
*/
}
Loading

0 comments on commit edfa97a

Please sign in to comment.