42 ossia::net::parameter_base* fps_param{};
43 ossia::net::parameter_base* rendersize_param{};
45 struct PerWindowParams
47 ossia::net::parameter_base* scaled_cursor{};
48 ossia::net::parameter_base* gl_cursor{};
49 ossia::net::parameter_base* clip_cursor{};
50 ossia::net::parameter_base* abs_cursor{};
51 ossia::net::parameter_base* size_param{};
52 ossia::net::parameter_base* pos_param{};
53 ossia::net::parameter_base* source_pos{};
54 ossia::net::parameter_base* source_size{};
55 ossia::net::parameter_base* key_press_code{};
56 ossia::net::parameter_base* key_press_text{};
57 ossia::net::parameter_base* key_release_code{};
58 ossia::net::parameter_base* key_release_text{};
60 std::vector<QMetaObject::Connection> connections;
62 std::vector<PerWindowParams> m_perWindow;
64 void update_viewport(PerWindowParams& pw)
66 if(!rendersize_param || !pw.abs_cursor)
69 auto v = rendersize_param->value();
70 if(
auto val = v.target<ossia::vec2f>())
72 if((*val)[0] >= 1.f && (*val)[1] >= 1.f)
74 auto dom = pw.abs_cursor->get_domain();
75 ossia::set_max(dom, *val);
76 pw.abs_cursor->set_domain(std::move(dom));
78 else if(pw.size_param)
80 v = pw.size_param->value();
81 if(
auto sz = v.target<ossia::vec2f>())
83 if((*sz)[0] >= 1.f && (*sz)[1] >= 1.f)
85 auto dom = pw.abs_cursor->get_domain();
86 ossia::set_max(dom, *sz);
87 pw.abs_cursor->set_domain(std::move(dom));
96 const std::vector<OutputMapping>& mappings,
97 std::unique_ptr<gfx_protocol_base> proto, std::string name,
98 SwapchainFlag swapFlag = SwapchainFlag::NoFlag,
99 SwapchainFormat swapFormat = SwapchainFormat::SDR)
100 : ossia::net::device_base{std::move(proto)}
101 , m_node{createMultiWindowNode(mappings, swapFlag, swapFormat)}
102 , m_root{*
this, *
static_cast<gfx_protocol_base*
>(m_protocol.get()), m_node, name}
104 this->m_capabilities.change_tree =
true;
107 m_node->onWindowsCreated = [
this] { connectWindowSignals(); };
111 auto fps_node = std::make_unique<ossia::net::generic_node>(
"fps", *
this, m_root);
112 fps_param = fps_node->create_parameter(ossia::val_type::FLOAT);
113 m_node->onFps = [
this](
float fps) { fps_param->push_value(fps); };
114 m_root.add_child(std::move(fps_node));
120 = std::make_unique<ossia::net::generic_node>(
"rendersize", *
this, m_root);
121 ossia::net::set_description(*rs_node,
"Set to [0, 0] to use the viewport's size");
122 rendersize_param = rs_node->create_parameter(ossia::val_type::VEC2F);
123 rendersize_param->push_value(ossia::vec2f{0.f, 0.f});
124 rendersize_param->add_callback([
this](
const ossia::value& v) {
125 if(
auto val = v.target<ossia::vec2f>())
129 ossia::qt::run_async(&m_qtContext, [this, v = *val] {
130 m_node->setRenderSize({(int)v[0], (int)v[1]});
131 for(auto& pw : m_perWindow)
136 m_root.add_child(std::move(rs_node));
140 m_perWindow.resize(mappings.size());
142 for(
int i = 0; i < (int)mappings.size(); ++i)
144 auto& pw = m_perWindow[i];
145 const auto& mapping = mappings[i];
146 const auto idx_str = std::to_string(i);
148 auto win_node = std::make_unique<ossia::net::generic_node>(idx_str, *
this, m_root);
153 = std::make_unique<ossia::net::generic_node>(
"cursor", *
this, *win_node);
155 auto scale_node = std::make_unique<ossia::net::generic_node>(
156 "scaled", *
this, *cursor_node);
157 pw.scaled_cursor = scale_node->create_parameter(ossia::val_type::VEC2F);
158 pw.scaled_cursor->set_domain(ossia::make_domain(0.f, 1.f));
159 pw.scaled_cursor->push_value(ossia::vec2f{0.f, 0.f});
160 cursor_node->add_child(std::move(scale_node));
164 = std::make_unique<ossia::net::generic_node>(
"gl", *
this, *cursor_node);
165 pw.gl_cursor = scale_node->create_parameter(ossia::val_type::VEC2F);
166 pw.gl_cursor->set_domain(ossia::make_domain(0.f, 1.f));
167 pw.gl_cursor->push_value(ossia::vec2f{0.f, 0.f});
168 cursor_node->add_child(std::move(scale_node));
173 = std::make_unique<ossia::net::generic_node>(
"clip", *
this, *cursor_node);
174 pw.clip_cursor = clip_node->create_parameter(ossia::val_type::VEC2F);
175 pw.clip_cursor->set_domain(ossia::make_domain(-1.f, 1.f));
176 pw.clip_cursor->push_value(ossia::vec2f{0.f, 0.f});
177 cursor_node->add_child(std::move(clip_node));
180 auto abs_node = std::make_unique<ossia::net::generic_node>(
181 "absolute", *
this, *cursor_node);
182 pw.abs_cursor = abs_node->create_parameter(ossia::val_type::VEC2F);
183 pw.abs_cursor->set_domain(
185 ossia::vec2f{0.f, 0.f}, ossia::vec2f{
186 (float)mapping.windowSize.width(),
187 (
float)mapping.windowSize.height()}));
188 pw.abs_cursor->push_value(ossia::vec2f{0.f, 0.f});
189 cursor_node->add_child(std::move(abs_node));
191 win_node->add_child(std::move(cursor_node));
197 = std::make_unique<ossia::net::generic_node>(
"size", *
this, *win_node);
198 pw.size_param = size_node->create_parameter(ossia::val_type::VEC2F);
199 pw.size_param->push_value(
201 (float)mapping.windowSize.width(), (float)mapping.windowSize.height()});
202 pw.size_param->add_callback([
this, i](
const ossia::value& v) {
203 if(
auto val = v.target<ossia::vec2f>())
205 ossia::qt::run_async(&m_qtContext, [
this, i, v = *val] {
206 const auto& outputs = m_node->windowOutputs();
207 if(i < (
int)outputs.size())
209 if(auto& w = outputs[i].window)
210 w->resize(QSize{(int)v[0], (int)v[1]});
213 if(i < (
int)m_perWindow.size())
214 update_viewport(m_perWindow[i]);
217 win_node->add_child(std::move(size_node));
223 = std::make_unique<ossia::net::generic_node>(
"position", *
this, *win_node);
224 pw.pos_param = pos_node->create_parameter(ossia::val_type::VEC2F);
225 pw.pos_param->push_value(
227 (float)mapping.windowPosition.x(), (float)mapping.windowPosition.y()});
228 pw.pos_param->add_callback([
this, i](
const ossia::value& v) {
229 if(
auto val = v.target<ossia::vec2f>())
231 ossia::qt::run_async(&m_qtContext, [
this, i, v = *val] {
232 const auto& outputs = m_node->windowOutputs();
233 if(i < (
int)outputs.size())
235 if(auto& w = outputs[i].window)
236 w->setPosition(QPoint{(int)v[0], (int)v[1]});
241 win_node->add_child(std::move(pos_node));
247 = std::make_unique<ossia::net::generic_node>(
"fullscreen", *
this, *win_node);
248 auto fs_param = fs_node->create_parameter(ossia::val_type::BOOL);
249 fs_param->push_value(mapping.fullscreen);
250 fs_param->add_callback([
this, i](
const ossia::value& v) {
251 if(
auto val = v.target<
bool>())
253 ossia::qt::run_async(&m_qtContext, [this, i, v = *val] {
254 const auto& outputs = m_node->windowOutputs();
255 if(i < (int)outputs.size())
257 if(auto& w = outputs[i].window)
268 win_node->add_child(std::move(fs_node));
274 = std::make_unique<ossia::net::generic_node>(
"screen", *
this, *win_node);
275 auto screen_param = screen_node->create_parameter(ossia::val_type::STRING);
276 screen_param->push_value(
277 mapping.screenIndex >= 0 ? std::to_string(mapping.screenIndex)
279 screen_param->add_callback([
this, i](
const ossia::value& v) {
280 auto apply = [
this, i](
const std::string& scr) {
281 const auto& outputs = m_node->windowOutputs();
282 if(i >= (
int)outputs.size())
284 auto& w = outputs[i].window;
288 const auto& cur_screens = qGuiApp->screens();
292 int idx = QString::fromStdString(scr).toInt(&ok);
295 if(ossia::valid_index(idx, cur_screens))
296 w->setScreen(cur_screens[idx]);
301 for(
auto s : cur_screens)
303 if(s->name().toStdString() == scr)
311 if(
auto val = v.target<std::string>())
313 ossia::qt::run_async(
314 &m_qtContext, [apply, scr = *val] { apply(scr); });
316 else if(
auto val = v.target<
int>())
318 ossia::qt::run_async(
319 &m_qtContext, [apply, scr = std::to_string(*val)] { apply(scr); });
322 win_node->add_child(std::move(screen_node));
328 = std::make_unique<ossia::net::generic_node>(
"source", *
this, *win_node);
330 auto pos_node = std::make_unique<ossia::net::generic_node>(
331 "position", *
this, *source_node);
332 ossia::net::set_description(*pos_node,
"UV position of source rect (0-1)");
333 pw.source_pos = pos_node->create_parameter(ossia::val_type::VEC2F);
334 pw.source_pos->set_domain(ossia::make_domain(0.f, 1.f));
335 pw.source_pos->push_value(
337 (float)mapping.sourceRect.x(), (float)mapping.sourceRect.y()});
338 pw.source_pos->add_callback([
this, i](
const ossia::value& v) {
339 if(
auto val = v.target<ossia::vec2f>())
341 const auto& outputs = m_node->windowOutputs();
342 if(i < (
int)outputs.size())
344 auto r = outputs[i].sourceRect;
345 r.moveTopLeft(QPointF{(*val)[0], (*val)[1]});
346 m_node->setSourceRect(i, r);
350 source_node->add_child(std::move(pos_node));
354 = std::make_unique<ossia::net::generic_node>(
"size", *
this, *source_node);
355 ossia::net::set_description(*sz_node,
"UV size of source rect (0-1)");
356 pw.source_size = sz_node->create_parameter(ossia::val_type::VEC2F);
357 pw.source_size->set_domain(ossia::make_domain(0.f, 1.f));
358 pw.source_size->push_value(
360 (float)mapping.sourceRect.width(),
361 (float)mapping.sourceRect.height()});
362 pw.source_size->add_callback([
this, i](
const ossia::value& v) {
363 if(
auto val = v.target<ossia::vec2f>())
365 const auto& outputs = m_node->windowOutputs();
366 if(i < (
int)outputs.size())
368 auto r = outputs[i].sourceRect;
369 r.setSize(QSizeF{(*val)[0], (*val)[1]});
370 m_node->setSourceRect(i, r);
374 source_node->add_child(std::move(sz_node));
376 win_node->add_child(std::move(source_node));
382 = std::make_unique<ossia::net::generic_node>(
"blend", *
this, *win_node);
392 = {{
"left", 0, mapping.blendLeft.width, mapping.blendLeft.gamma},
393 {
"right", 1, mapping.blendRight.width, mapping.blendRight.gamma},
394 {
"top", 2, mapping.blendTop.width, mapping.blendTop.gamma},
395 {
"bottom", 3, mapping.blendBottom.width, mapping.blendBottom.gamma}};
397 for(
auto& [sname, side, initW, initG] : sides)
400 = std::make_unique<ossia::net::generic_node>(sname, *
this, *blend_node);
403 = std::make_unique<ossia::net::generic_node>(
"width", *
this, *side_node);
404 ossia::net::set_description(*w_node,
"Blend width in UV space (0-0.5)");
405 auto w_param = w_node->create_parameter(ossia::val_type::FLOAT);
406 w_param->set_domain(ossia::make_domain(0.f, 0.5f));
407 w_param->push_value(initW);
408 w_param->add_callback([
this, i, side](
const ossia::value& v) {
409 if(
auto val = v.target<
float>())
411 const auto& outputs = m_node->windowOutputs();
412 if(i < (int)outputs.size())
418 gamma = outputs[i].blendLeft.gamma;
421 gamma = outputs[i].blendRight.gamma;
424 gamma = outputs[i].blendTop.gamma;
427 gamma = outputs[i].blendBottom.gamma;
430 m_node->setEdgeBlend(i, side, *val, gamma);
434 side_node->add_child(std::move(w_node));
438 = std::make_unique<ossia::net::generic_node>(
"gamma", *
this, *side_node);
439 ossia::net::set_description(*g_node,
"Blend curve exponent (0.1-4.0)");
440 auto g_param = g_node->create_parameter(ossia::val_type::FLOAT);
441 g_param->set_domain(ossia::make_domain(0.1f, 4.f));
442 g_param->push_value(initG);
443 g_param->add_callback([
this, i, side](
const ossia::value& v) {
444 if(
auto val = v.target<
float>())
446 const auto& outputs = m_node->windowOutputs();
447 if(i < (int)outputs.size())
453 width = outputs[i].blendLeft.width;
456 width = outputs[i].blendRight.width;
459 width = outputs[i].blendTop.width;
462 width = outputs[i].blendBottom.width;
465 m_node->setEdgeBlend(i, side, width, *val);
469 side_node->add_child(std::move(g_node));
471 blend_node->add_child(std::move(side_node));
473 win_node->add_child(std::move(blend_node));
479 = std::make_unique<ossia::net::generic_node>(
"key", *
this, *win_node);
484 = std::make_unique<ossia::net::generic_node>(
"press", *
this, *key_node);
487 = std::make_unique<ossia::net::generic_node>(
"code", *
this, *press_node);
488 pw.key_press_code = code_node->create_parameter(ossia::val_type::INT);
489 press_node->add_child(std::move(code_node));
493 = std::make_unique<ossia::net::generic_node>(
"text", *
this, *press_node);
494 pw.key_press_text = text_node->create_parameter(ossia::val_type::STRING);
495 press_node->add_child(std::move(text_node));
497 key_node->add_child(std::move(press_node));
503 = std::make_unique<ossia::net::generic_node>(
"release", *
this, *key_node);
505 auto code_node = std::make_unique<ossia::net::generic_node>(
506 "code", *
this, *release_node);
507 pw.key_release_code = code_node->create_parameter(ossia::val_type::INT);
508 release_node->add_child(std::move(code_node));
511 auto text_node = std::make_unique<ossia::net::generic_node>(
512 "text", *
this, *release_node);
513 pw.key_release_text = text_node->create_parameter(ossia::val_type::STRING);
514 release_node->add_child(std::move(text_node));
516 key_node->add_child(std::move(release_node));
519 win_node->add_child(std::move(key_node));
522 m_root.add_child(std::move(win_node));
527 void connectWindowSignals()
529 const auto& outputs = m_node->windowOutputs();
530 for(
int i = 0; i < (int)outputs.size() && i < (int)m_perWindow.size(); ++i)
532 auto& pw = m_perWindow[i];
533 const auto& wo = outputs[i];
537 auto* w = wo.window.get();
540 pw.connections.push_back(
542 w, &score::gfx::Window::mouseMove,
543 [&pw, w](
const QPointF screen,
const QPointF win) {
545 if(sz.width() > 0 && sz.height() > 0)
547 pw.scaled_cursor->push_value(
548 ossia::vec2f{float(win.x() / sz.width()), float(win.y() / sz.height())});
549 pw.gl_cursor->push_value(
551 float(win.x() / sz.width()), 1.f - float(win.y() / sz.height())});
552 pw.clip_cursor->push_value(
554 float(win.x() / sz.width()) * 2.f - 1.f,
555 float(win.y() / sz.height()) * -2.f + 1.f});
556 pw.abs_cursor->push_value(ossia::vec2f{float(win.x()), float(win.y())});
571 pw.connections.push_back(
572 QObject::connect(w, &score::gfx::Window::key, [&pw](
int k,
const QString& t) {
573 pw.key_press_code->push_value(k);
574 pw.key_press_text->push_value(t.toStdString());
576 pw.connections.push_back(
578 w, &score::gfx::Window::keyRelease, [&pw](
int k,
const QString& t) {
579 pw.key_release_code->push_value(k);
580 pw.key_release_text->push_value(t.toStdString());
585 ~multiwindow_device()
588 for(
auto& pw : m_perWindow)
589 for(auto& c : pw.connections)
590 QObject::disconnect(c);
593 m_node->onWindowsCreated = [] { };
594 m_node->onFps = [](float) { };
596 m_root.clear_children();
600 const gfx_node_base& get_root_node()
const override {
return m_root; }
601 gfx_node_base& get_root_node()
override {
return m_root; }