Loading...
Searching...
No Matches
MultiWindowDevice.hpp
1#pragma once
2#include <Gfx/GfxParameter.hpp>
3#include <Gfx/Graph/MultiWindowNode.hpp>
4#include <Gfx/Graph/Window.hpp>
5#include <Gfx/Settings/Model.hpp>
6
7#include <ossia/network/base/device.hpp>
8#include <ossia/network/base/protocol.hpp>
9#include <ossia/network/generic/generic_node.hpp>
10
11#include <ossia-qt/invoke.hpp>
12
13#include <QGuiApplication>
14namespace Gfx
15{
17createMultiWindowNode(const std::vector<OutputMapping>& mappings)
18{
19 const auto& gfx_settings = score::AppContext().settings<Gfx::Settings::Model>();
20
22 double rate = gfx_settings.getRate();
23 if(rate > 0)
24 conf = {.manualRenderingRate = 1000. / rate, .supportsVSync = false};
25 else
26 conf = {.manualRenderingRate = 1000. / 60., .supportsVSync = false};
27
28 return new score::gfx::MultiWindowNode{conf, mappings};
29}
30
31class multiwindow_device : public ossia::net::device_base
32{
34 gfx_node_base m_root;
35 QObject m_qtContext;
36
37 ossia::net::parameter_base* fps_param{};
38 ossia::net::parameter_base* rendersize_param{};
39
40 struct PerWindowParams
41 {
42 ossia::net::parameter_base* scaled_cursor{};
43 ossia::net::parameter_base* abs_cursor{};
44 ossia::net::parameter_base* size_param{};
45 ossia::net::parameter_base* pos_param{};
46 ossia::net::parameter_base* source_pos{};
47 ossia::net::parameter_base* source_size{};
48 ossia::net::parameter_base* key_press_code{};
49 ossia::net::parameter_base* key_press_text{};
50 ossia::net::parameter_base* key_release_code{};
51 ossia::net::parameter_base* key_release_text{};
52
53 std::vector<QMetaObject::Connection> connections;
54 };
55 std::vector<PerWindowParams> m_perWindow;
56
57 void update_viewport(PerWindowParams& pw)
58 {
59 if(!rendersize_param || !pw.abs_cursor)
60 return;
61
62 auto v = rendersize_param->value();
63 if(auto val = v.target<ossia::vec2f>())
64 {
65 if((*val)[0] >= 1.f && (*val)[1] >= 1.f)
66 {
67 auto dom = pw.abs_cursor->get_domain();
68 ossia::set_max(dom, *val);
69 pw.abs_cursor->set_domain(std::move(dom));
70 }
71 else if(pw.size_param)
72 {
73 v = pw.size_param->value();
74 if(auto sz = v.target<ossia::vec2f>())
75 {
76 if((*sz)[0] >= 1.f && (*sz)[1] >= 1.f)
77 {
78 auto dom = pw.abs_cursor->get_domain();
79 ossia::set_max(dom, *sz);
80 pw.abs_cursor->set_domain(std::move(dom));
81 }
82 }
83 }
84 }
85 }
86
87public:
89 const std::vector<OutputMapping>& mappings,
90 std::unique_ptr<gfx_protocol_base> proto, std::string name)
91 : ossia::net::device_base{std::move(proto)}
92 , m_node{createMultiWindowNode(mappings)}
93 , m_root{*this, *static_cast<gfx_protocol_base*>(m_protocol.get()), m_node, name}
94 {
95 this->m_capabilities.change_tree = true;
96
97 // Connect window signals once windows are created by createOutput()
98 m_node->onWindowsCreated = [this] { connectWindowSignals(); };
99
100 // Global FPS output parameter
101 {
102 auto fps_node = std::make_unique<ossia::net::generic_node>("fps", *this, m_root);
103 fps_param = fps_node->create_parameter(ossia::val_type::FLOAT);
104 m_node->onFps = [this](float fps) { fps_param->push_value(fps); };
105 m_root.add_child(std::move(fps_node));
106 }
107
108 // Global rendersize parameter
109 {
110 auto rs_node
111 = std::make_unique<ossia::net::generic_node>("rendersize", *this, m_root);
112 ossia::net::set_description(*rs_node, "Set to [0, 0] to use the viewport's size");
113 rendersize_param = rs_node->create_parameter(ossia::val_type::VEC2F);
114 rendersize_param->push_value(ossia::vec2f{0.f, 0.f});
115 rendersize_param->add_callback([this](const ossia::value& v) {
116 if(auto val = v.target<ossia::vec2f>())
117 {
118 m_node->setRenderSize({(int)(*val)[0], (int)(*val)[1]});
119 for(auto& pw : m_perWindow)
120 update_viewport(pw);
121 }
122 });
123 m_root.add_child(std::move(rs_node));
124 }
125
126 // Per-window subtrees: /0, /1, /2, ...
127 m_perWindow.resize(mappings.size());
128
129 for(int i = 0; i < (int)mappings.size(); ++i)
130 {
131 auto& pw = m_perWindow[i];
132 const auto& mapping = mappings[i];
133 const auto idx_str = std::to_string(i);
134
135 auto win_node = std::make_unique<ossia::net::generic_node>(idx_str, *this, m_root);
136
137 // /i/cursor
138 {
139 auto cursor_node
140 = std::make_unique<ossia::net::generic_node>("cursor", *this, *win_node);
141 {
142 auto scale_node = std::make_unique<ossia::net::generic_node>(
143 "scaled", *this, *cursor_node);
144 pw.scaled_cursor = scale_node->create_parameter(ossia::val_type::VEC2F);
145 pw.scaled_cursor->set_domain(ossia::make_domain(0.f, 1.f));
146 pw.scaled_cursor->push_value(ossia::vec2f{0.f, 0.f});
147 cursor_node->add_child(std::move(scale_node));
148 }
149 {
150 auto abs_node = std::make_unique<ossia::net::generic_node>(
151 "absolute", *this, *cursor_node);
152 pw.abs_cursor = abs_node->create_parameter(ossia::val_type::VEC2F);
153 pw.abs_cursor->set_domain(
154 ossia::make_domain(
155 ossia::vec2f{0.f, 0.f}, ossia::vec2f{
156 (float)mapping.windowSize.width(),
157 (float)mapping.windowSize.height()}));
158 pw.abs_cursor->push_value(ossia::vec2f{0.f, 0.f});
159 cursor_node->add_child(std::move(abs_node));
160 }
161 win_node->add_child(std::move(cursor_node));
162 }
163
164 // /i/size
165 {
166 auto size_node
167 = std::make_unique<ossia::net::generic_node>("size", *this, *win_node);
168 pw.size_param = size_node->create_parameter(ossia::val_type::VEC2F);
169 pw.size_param->push_value(
170 ossia::vec2f{
171 (float)mapping.windowSize.width(), (float)mapping.windowSize.height()});
172 pw.size_param->add_callback([this, i](const ossia::value& v) {
173 if(auto val = v.target<ossia::vec2f>())
174 {
175 ossia::qt::run_async(&m_qtContext, [this, i, v = *val] {
176 const auto& outputs = m_node->windowOutputs();
177 if(i < (int)outputs.size())
178 {
179 if(auto& w = outputs[i].window)
180 w->resize(QSize{(int)v[0], (int)v[1]});
181 }
182 });
183 if(i < (int)m_perWindow.size())
184 update_viewport(m_perWindow[i]);
185 }
186 });
187 win_node->add_child(std::move(size_node));
188 }
189
190 // /i/position
191 {
192 auto pos_node
193 = std::make_unique<ossia::net::generic_node>("position", *this, *win_node);
194 pw.pos_param = pos_node->create_parameter(ossia::val_type::VEC2F);
195 pw.pos_param->push_value(
196 ossia::vec2f{
197 (float)mapping.windowPosition.x(), (float)mapping.windowPosition.y()});
198 pw.pos_param->add_callback([this, i](const ossia::value& v) {
199 if(auto val = v.target<ossia::vec2f>())
200 {
201 ossia::qt::run_async(&m_qtContext, [this, i, v = *val] {
202 const auto& outputs = m_node->windowOutputs();
203 if(i < (int)outputs.size())
204 {
205 if(auto& w = outputs[i].window)
206 w->setPosition(QPoint{(int)v[0], (int)v[1]});
207 }
208 });
209 }
210 });
211 win_node->add_child(std::move(pos_node));
212 }
213
214 // /i/fullscreen
215 {
216 auto fs_node
217 = std::make_unique<ossia::net::generic_node>("fullscreen", *this, *win_node);
218 auto fs_param = fs_node->create_parameter(ossia::val_type::BOOL);
219 fs_param->push_value(mapping.fullscreen);
220 fs_param->add_callback([this, i](const ossia::value& v) {
221 if(auto val = v.target<bool>())
222 {
223 ossia::qt::run_async(&m_qtContext, [this, i, v = *val] {
224 const auto& outputs = m_node->windowOutputs();
225 if(i < (int)outputs.size())
226 {
227 if(auto& w = outputs[i].window)
228 {
229 if(v)
230 w->showFullScreen();
231 else
232 w->showNormal();
233 }
234 }
235 });
236 }
237 });
238 win_node->add_child(std::move(fs_node));
239 }
240
241 // /i/screen (accepts an integer index or a screen name string)
242 {
243 auto screen_node
244 = std::make_unique<ossia::net::generic_node>("screen", *this, *win_node);
245 auto screen_param = screen_node->create_parameter(ossia::val_type::STRING);
246 screen_param->push_value(
247 mapping.screenIndex >= 0 ? std::to_string(mapping.screenIndex)
248 : std::string{});
249 screen_param->add_callback([this, i](const ossia::value& v) {
250 auto apply = [this, i](const std::string& scr) {
251 const auto& outputs = m_node->windowOutputs();
252 if(i >= (int)outputs.size())
253 return;
254 auto& w = outputs[i].window;
255 if(!w)
256 return;
257
258 const auto& cur_screens = qGuiApp->screens();
259
260 // Try parsing as integer index first
261 bool ok = false;
262 int idx = QString::fromStdString(scr).toInt(&ok);
263 if(ok)
264 {
265 if(ossia::valid_index(idx, cur_screens))
266 w->setScreen(cur_screens[idx]);
267 return;
268 }
269
270 // Otherwise match by screen name
271 for(auto s : cur_screens)
272 {
273 if(s->name().toStdString() == scr)
274 {
275 w->setScreen(s);
276 return;
277 }
278 }
279 };
280
281 if(auto val = v.target<std::string>())
282 {
283 ossia::qt::run_async(
284 &m_qtContext, [apply, scr = *val] { apply(scr); });
285 }
286 else if(auto val = v.target<int>())
287 {
288 ossia::qt::run_async(
289 &m_qtContext, [apply, scr = std::to_string(*val)] { apply(scr); });
290 }
291 });
292 win_node->add_child(std::move(screen_node));
293 }
294
295 // /i/source (UV mapping rectangle)
296 {
297 auto source_node
298 = std::make_unique<ossia::net::generic_node>("source", *this, *win_node);
299 {
300 auto pos_node = std::make_unique<ossia::net::generic_node>(
301 "position", *this, *source_node);
302 ossia::net::set_description(*pos_node, "UV position of source rect (0-1)");
303 pw.source_pos = pos_node->create_parameter(ossia::val_type::VEC2F);
304 pw.source_pos->set_domain(ossia::make_domain(0.f, 1.f));
305 pw.source_pos->push_value(
306 ossia::vec2f{
307 (float)mapping.sourceRect.x(), (float)mapping.sourceRect.y()});
308 pw.source_pos->add_callback([this, i](const ossia::value& v) {
309 if(auto val = v.target<ossia::vec2f>())
310 {
311 const auto& outputs = m_node->windowOutputs();
312 if(i < (int)outputs.size())
313 {
314 auto r = outputs[i].sourceRect;
315 r.moveTopLeft(QPointF{(*val)[0], (*val)[1]});
316 m_node->setSourceRect(i, r);
317 }
318 }
319 });
320 source_node->add_child(std::move(pos_node));
321 }
322 {
323 auto sz_node
324 = std::make_unique<ossia::net::generic_node>("size", *this, *source_node);
325 ossia::net::set_description(*sz_node, "UV size of source rect (0-1)");
326 pw.source_size = sz_node->create_parameter(ossia::val_type::VEC2F);
327 pw.source_size->set_domain(ossia::make_domain(0.f, 1.f));
328 pw.source_size->push_value(
329 ossia::vec2f{
330 (float)mapping.sourceRect.width(),
331 (float)mapping.sourceRect.height()});
332 pw.source_size->add_callback([this, i](const ossia::value& v) {
333 if(auto val = v.target<ossia::vec2f>())
334 {
335 const auto& outputs = m_node->windowOutputs();
336 if(i < (int)outputs.size())
337 {
338 auto r = outputs[i].sourceRect;
339 r.setSize(QSizeF{(*val)[0], (*val)[1]});
340 m_node->setSourceRect(i, r);
341 }
342 }
343 });
344 source_node->add_child(std::move(sz_node));
345 }
346 win_node->add_child(std::move(source_node));
347 }
348
349 // /i/blend (soft-edge blending per side)
350 {
351 auto blend_node
352 = std::make_unique<ossia::net::generic_node>("blend", *this, *win_node);
353
354 struct BlendSide
355 {
356 const char* name;
357 int side;
358 float initW;
359 float initG;
360 };
361 BlendSide sides[]
362 = {{"left", 0, mapping.blendLeft.width, mapping.blendLeft.gamma},
363 {"right", 1, mapping.blendRight.width, mapping.blendRight.gamma},
364 {"top", 2, mapping.blendTop.width, mapping.blendTop.gamma},
365 {"bottom", 3, mapping.blendBottom.width, mapping.blendBottom.gamma}};
366
367 for(auto& [sname, side, initW, initG] : sides)
368 {
369 auto side_node
370 = std::make_unique<ossia::net::generic_node>(sname, *this, *blend_node);
371 {
372 auto w_node
373 = std::make_unique<ossia::net::generic_node>("width", *this, *side_node);
374 ossia::net::set_description(*w_node, "Blend width in UV space (0-0.5)");
375 auto w_param = w_node->create_parameter(ossia::val_type::FLOAT);
376 w_param->set_domain(ossia::make_domain(0.f, 0.5f));
377 w_param->push_value(initW);
378 w_param->add_callback([this, i, side](const ossia::value& v) {
379 if(auto val = v.target<float>())
380 {
381 const auto& outputs = m_node->windowOutputs();
382 if(i < (int)outputs.size())
383 {
384 float gamma = 2.2f;
385 switch(side)
386 {
387 case 0:
388 gamma = outputs[i].blendLeft.gamma;
389 break;
390 case 1:
391 gamma = outputs[i].blendRight.gamma;
392 break;
393 case 2:
394 gamma = outputs[i].blendTop.gamma;
395 break;
396 case 3:
397 gamma = outputs[i].blendBottom.gamma;
398 break;
399 }
400 m_node->setEdgeBlend(i, side, *val, gamma);
401 }
402 }
403 });
404 side_node->add_child(std::move(w_node));
405 }
406 {
407 auto g_node
408 = std::make_unique<ossia::net::generic_node>("gamma", *this, *side_node);
409 ossia::net::set_description(*g_node, "Blend curve exponent (0.1-4.0)");
410 auto g_param = g_node->create_parameter(ossia::val_type::FLOAT);
411 g_param->set_domain(ossia::make_domain(0.1f, 4.f));
412 g_param->push_value(initG);
413 g_param->add_callback([this, i, side](const ossia::value& v) {
414 if(auto val = v.target<float>())
415 {
416 const auto& outputs = m_node->windowOutputs();
417 if(i < (int)outputs.size())
418 {
419 float width = 0.f;
420 switch(side)
421 {
422 case 0:
423 width = outputs[i].blendLeft.width;
424 break;
425 case 1:
426 width = outputs[i].blendRight.width;
427 break;
428 case 2:
429 width = outputs[i].blendTop.width;
430 break;
431 case 3:
432 width = outputs[i].blendBottom.width;
433 break;
434 }
435 m_node->setEdgeBlend(i, side, width, *val);
436 }
437 }
438 });
439 side_node->add_child(std::move(g_node));
440 }
441 blend_node->add_child(std::move(side_node));
442 }
443 win_node->add_child(std::move(blend_node));
444 }
445
446 // /i/key
447 {
448 auto key_node
449 = std::make_unique<ossia::net::generic_node>("key", *this, *win_node);
450
451 // /i/key/press
452 {
453 auto press_node
454 = std::make_unique<ossia::net::generic_node>("press", *this, *key_node);
455 {
456 auto code_node
457 = std::make_unique<ossia::net::generic_node>("code", *this, *press_node);
458 pw.key_press_code = code_node->create_parameter(ossia::val_type::INT);
459 press_node->add_child(std::move(code_node));
460 }
461 {
462 auto text_node
463 = std::make_unique<ossia::net::generic_node>("text", *this, *press_node);
464 pw.key_press_text = text_node->create_parameter(ossia::val_type::STRING);
465 press_node->add_child(std::move(text_node));
466 }
467 key_node->add_child(std::move(press_node));
468 }
469
470 // /i/key/release
471 {
472 auto release_node
473 = std::make_unique<ossia::net::generic_node>("release", *this, *key_node);
474 {
475 auto code_node = std::make_unique<ossia::net::generic_node>(
476 "code", *this, *release_node);
477 pw.key_release_code = code_node->create_parameter(ossia::val_type::INT);
478 release_node->add_child(std::move(code_node));
479 }
480 {
481 auto text_node = std::make_unique<ossia::net::generic_node>(
482 "text", *this, *release_node);
483 pw.key_release_text = text_node->create_parameter(ossia::val_type::STRING);
484 release_node->add_child(std::move(text_node));
485 }
486 key_node->add_child(std::move(release_node));
487 }
488
489 win_node->add_child(std::move(key_node));
490 }
491
492 m_root.add_child(std::move(win_node));
493 }
494 }
495
496 // Called after windows are created by createOutput() to connect Qt signals
497 void connectWindowSignals()
498 {
499 const auto& outputs = m_node->windowOutputs();
500 for(int i = 0; i < (int)outputs.size() && i < (int)m_perWindow.size(); ++i)
501 {
502 auto& pw = m_perWindow[i];
503 const auto& wo = outputs[i];
504 if(!wo.window)
505 continue;
506
507 auto* w = wo.window.get();
508
509 // Mouse cursor
510 pw.connections.push_back(
511 QObject::connect(
512 w, &score::gfx::Window::mouseMove, [&pw, w](QPointF screen, QPointF win) {
513 auto sz = w->size();
514 if(sz.width() > 0 && sz.height() > 0)
515 {
516 pw.scaled_cursor->push_value(
517 ossia::vec2f{float(win.x() / sz.width()), float(win.y() / sz.height())});
518 pw.abs_cursor->push_value(ossia::vec2f{float(win.x()), float(win.y())});
519 }
520 }));
521
522 // // Window position feedback
523 // pw.connections.push_back(
524 // QObject::connect(w, &QWindow::xChanged, [&pw, w](int x) {
525 // pw.pos_param->set_value(ossia::vec2f{float(x), float(w->y())});
526 // }));
527 // pw.connections.push_back(
528 // QObject::connect(w, &QWindow::yChanged, [&pw, w](int y) {
529 // pw.pos_param->set_value(ossia::vec2f{float(w->x()), float(y)});
530 // }));
531
532 // Keyboard
533 pw.connections.push_back(
534 QObject::connect(w, &score::gfx::Window::key, [&pw](int k, const QString& t) {
535 pw.key_press_code->push_value(k);
536 pw.key_press_text->push_value(t.toStdString());
537 }));
538 pw.connections.push_back(
539 QObject::connect(
540 w, &score::gfx::Window::keyRelease, [&pw](int k, const QString& t) {
541 pw.key_release_code->push_value(k);
542 pw.key_release_text->push_value(t.toStdString());
543 }));
544 }
545 }
546
547 ~multiwindow_device()
548 {
549 // Disconnect all window signals
550 for(auto& pw : m_perWindow)
551 for(auto& c : pw.connections)
552 QObject::disconnect(c);
553 m_perWindow.clear();
554
555 m_node->onWindowsCreated = [] { };
556 m_node->onFps = [](float) { };
557 m_protocol->stop();
558 m_root.clear_children();
559 m_protocol.reset();
560 }
561
562 const gfx_node_base& get_root_node() const override { return m_root; }
563 gfx_node_base& get_root_node() override { return m_root; }
564};
565
566}
Definition score-plugin-gfx/Gfx/Settings/Model.hpp:38
Definition GfxParameter.hpp:60
Definition GfxParameter.hpp:41
Definition MultiWindowDevice.hpp:32
Binds the rendering pipeline to ossia processes.
Definition CameraDevice.cpp:30
STL namespace.
T & settings() const
Access a specific Settings model instance.
Definition ApplicationContext.hpp:41
Definition MultiWindowNode.hpp:10
Definition OutputNode.hpp:61