Loading...
Searching...
No Matches
BackgroundDevice.hpp
1#pragma once
2
3#include <Scenario/Document/ScenarioDocument/ScenarioDocumentView.hpp>
4
5#include <Gfx/GfxApplicationPlugin.hpp>
6#include <Gfx/GfxParameter.hpp>
7#include <Gfx/Graph/BackgroundNode.hpp>
8#include <Gfx/Window/PointerAddresses.hpp>
9
10#include <score/graphics/BackgroundRenderer.hpp>
11
12#include <ossia/network/base/device.hpp>
13#include <ossia/network/base/protocol.hpp>
14#include <ossia/network/generic/generic_node.hpp>
15
16#include <ossia-qt/invoke.hpp>
17
18#include <QEvent>
19#include <QWindow>
20
21namespace Gfx
22{
24{
25public:
28 {
29 this->shared_readback = std::make_shared<QRhiReadbackResult>();
30 node.shared_readback = this->shared_readback;
31 }
32
33 ~DeviceBackgroundRenderer() override { }
34
35 bool render(QPainter* painter, const QRectF& rect) override
36 {
37 auto& m_readback = *shared_readback;
38 const auto w = m_readback.pixelSize.width();
39 const auto h = m_readback.pixelSize.height();
40 painter->setRenderHint(
41 QPainter::RenderHint::SmoothPixmapTransform,
42 w > rect.width() && h > rect.height());
43 int sz = w * h * 4;
44 int bytes = m_readback.data.size();
45 if(bytes > 0 && bytes >= sz)
46 {
47 QImage img{
48 (const unsigned char*)m_readback.data.data(), w, h, w * 4,
49 QImage::Format_RGBA8888};
50 painter->drawImage(rect, img);
51 return true;
52 }
53 return false;
54 }
55
56private:
57 QPointer<Gfx::DocumentPlugin> plug;
58 std::shared_ptr<QRhiReadbackResult> shared_readback;
59};
60
61class background_device : public ossia::net::device_base
62{
64 gfx_node_base m_root;
65 QObject m_qtContext;
66 QPointer<Scenario::ScenarioDocumentView> m_view;
67 DeviceBackgroundRenderer* m_renderer{};
68
69 ossia::net::parameter_base* scaled_win{};
70 ossia::net::parameter_base* gl_win{};
71 ossia::net::parameter_base* abs_win{};
72 ossia::net::parameter_base* abs_tablet_win{};
73 ossia::net::parameter_base* size_param{};
74 ossia::net::parameter_base* rendersize_param{};
75 pointer_addresses m_pointers;
76 ossia::net::parameter_base* press_param{};
77 ossia::net::parameter_base* text_param{};
78
79 boost::container::static_vector<QMetaObject::Connection, 8> con;
80
81 // The device pixel ratio changes without a resize when score's window moves
82 // to a screen of another density: the view gets a DevicePixelRatioChange
83 // event, its window a screenChanged signal. Either renders again at the
84 // view's resolution; the other one then finds the size unchanged.
85 struct PixelRatioWatcher final : QObject
86 {
88 explicit PixelRatioWatcher(background_device& s)
89 : self{s}
90 {
91 }
92
93 bool eventFilter(QObject*, QEvent* e) override
94 {
95 switch(e->type())
96 {
97#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
98 case QEvent::DevicePixelRatioChange:
99 self.updatePixelSize();
100 break;
101#endif
102 // The view may now be in another window, or its window may have
103 // just been given a native handle.
104 case QEvent::Show:
105 case QEvent::ParentChange:
106 case QEvent::WinIdChange:
107 self.bindWindow();
108 break;
109 default:
110 break;
111 }
112 return false;
113 }
114 };
115 std::unique_ptr<PixelRatioWatcher> m_pixelRatioWatcher;
116 QPointer<QWindow> m_window;
117 QMetaObject::Connection m_screenChanged;
118
119 // The last "size", which the device pixel ratio multiplies
120 ossia::vec2f m_logicalSize{};
121
122 void update_viewport()
123 {
124 auto v = rendersize_param->value();
125 if(auto val = v.target<ossia::vec2f>())
126 {
127 auto dom = abs_win->get_domain();
128 if((*val)[0] >= 1.f && (*val)[1] >= 1.f)
129 {
130 ossia::set_max(dom, *val);
131 abs_win->set_domain(std::move(dom));
132 abs_tablet_win->set_domain(std::move(dom));
133 }
134 else
135 {
136 // Use the normal size
137 v = size_param->value();
138 if(auto val = v.target<ossia::vec2f>())
139 {
140 auto dom = abs_win->get_domain();
141 if((*val)[0] >= 1.f && (*val)[1] >= 1.f)
142 {
143 ossia::set_max(dom, *val);
144 abs_win->set_domain(std::move(dom));
145 abs_tablet_win->set_domain(std::move(dom));
146 }
147 }
148 }
149 }
150 else
151 {
152 v = size_param->value();
153 if(auto val = v.target<ossia::vec2f>())
154 {
155 auto dom = abs_win->get_domain();
156 if((*val)[0] >= 1.f && (*val)[1] >= 1.f)
157 {
158 ossia::set_max(dom, *val);
159 abs_win->set_domain(std::move(dom));
160 abs_tablet_win->set_domain(std::move(dom));
161 }
162 }
163 }
164 }
165
166 // "size" is the view's size in device-independent pixels, as the cursor
167 // positions are. The picture is rendered at the screen's resolution so that
168 // it covers the view pixel for pixel, as a window's swapchain does.
169 QSize pixelSize(ossia::vec2f logical) const
170 {
171 const qreal dpr = m_view ? m_view->view().devicePixelRatioF() : 1.;
172 return QSize{qRound(logical[0] * dpr), qRound(logical[1] * dpr)};
173 }
174
175 void updatePixelSize()
176 {
177 if(m_view)
178 m_screen->setSize(pixelSize(m_logicalSize));
179 }
180
181 void bindWindow()
182 {
183 QWindow* w{};
184 if(m_view)
185 if(auto* top = m_view->view().window())
186 w = top->windowHandle();
187 if(w == m_window)
188 return;
189
190 QObject::disconnect(m_screenChanged);
191 m_window = w;
192 if(w)
193 m_screenChanged = QObject::connect(
194 w, &QWindow::screenChanged, &m_qtContext, [this] { updatePixelSize(); });
195 updatePixelSize();
196 }
197
198public:
200 Scenario::ScenarioDocumentView& view, std::unique_ptr<gfx_protocol_base> proto,
201 std::string name)
202 : ossia::net::device_base{std::move(proto)}
203 , m_screen{new score::gfx::BackgroundNode}
204 , m_root{*this, *static_cast<gfx_protocol_base*>(m_protocol.get()), m_screen, name}
205 , m_view{&view}
206 {
207 this->m_capabilities.change_tree = true;
208 m_renderer = new DeviceBackgroundRenderer{*m_screen};
209 view.addBackgroundRenderer(m_renderer);
210
211 auto& v = view.view();
212
213 {
214 auto size_node = std::make_unique<ossia::net::generic_node>("size", *this, m_root);
215 size_param = size_node->create_parameter(ossia::val_type::VEC2F);
216 const auto sz = view.view().size();
217 const ossia::vec2f logical{(float)sz.width(), (float)sz.height()};
218 m_logicalSize = logical;
219 m_screen->setSize(pixelSize(logical));
220 size_param->push_value(logical);
221 size_param->add_callback([this](const ossia::value& v) {
222 if(auto val = v.target<ossia::vec2f>())
223 {
224 ossia::qt::run_async(&m_qtContext, [this, v = *val] {
225 m_logicalSize = v;
226 m_screen->setSize(pixelSize(v));
227 });
228
229 update_viewport();
230 }
231 });
232
233 con.push_back(
234 QObject::connect(
235 &v, &Scenario::ProcessGraphicsView::sizeChanged, &m_qtContext,
236 [this, v = QPointer{&view}, ptr = QPointer{m_screen}](QSize e) {
237 if(ptr && v)
238 {
239 size_param->push_value(ossia::vec2f{(float)e.width(), (float)e.height()});
240 }
241 }, Qt::DirectConnection));
242
243 m_root.add_child(std::move(size_node));
244
245 m_pixelRatioWatcher = std::make_unique<PixelRatioWatcher>(*this);
246 v.installEventFilter(m_pixelRatioWatcher.get());
247 bindWindow();
248 }
249 {
250 auto size_node
251 = std::make_unique<ossia::net::generic_node>("rendersize", *this, m_root);
252
253 rendersize_param = size_node->create_parameter(ossia::val_type::VEC2F);
254 rendersize_param->push_value(ossia::vec2f{0.f, 0.f});
255 rendersize_param->add_callback([this](const ossia::value& v) {
256 if(auto val = v.target<ossia::vec2f>())
257 {
258 ossia::qt::run_async(&m_qtContext, [screen = m_screen, v = *val] {
259 screen->setRenderSize({(int)v[0], (int)v[1]});
260 });
261
262 update_viewport();
263 }
264 });
265
266 m_root.add_child(std::move(size_node));
267 }
268
269 // Mouse input
270 {
271 auto node = std::make_unique<ossia::net::generic_node>("cursor", *this, m_root);
272 {
273 auto scale_node
274 = std::make_unique<ossia::net::generic_node>("scaled", *this, *node);
275 scaled_win = scale_node->create_parameter(ossia::val_type::VEC2F);
276 scaled_win->set_domain(ossia::make_domain(0.f, 1.f));
277 scaled_win->push_value(ossia::vec2f{0.f, 0.f});
278 node->add_child(std::move(scale_node));
279 }
280 {
281 auto scale_node = std::make_unique<ossia::net::generic_node>("gl", *this, *node);
282 gl_win = scale_node->create_parameter(ossia::val_type::VEC2F);
283 gl_win->set_domain(ossia::make_domain(0.f, 1.f));
284 gl_win->push_value(ossia::vec2f{0.f, 0.f});
285 node->add_child(std::move(scale_node));
286 }
287 {
288 auto abs_node
289 = std::make_unique<ossia::net::generic_node>("absolute", *this, *node);
290 abs_win = abs_node->create_parameter(ossia::val_type::VEC2F);
291 abs_win->set_domain(
292 ossia::make_domain(ossia::vec2f{0.f, 0.f}, ossia::vec2f{1280, 270.f}));
293 abs_win->push_value(ossia::vec2f{0.f, 0.f});
294 node->add_child(std::move(abs_node));
295 }
296 m_pointers.add_clip(*this, *node);
297
298 con.push_back(
299 QObject::connect(
300 &v, &Scenario::ProcessGraphicsView::hoverMove, &m_qtContext,
301 [this, v = QPointer{&view}, ptr = QPointer{m_screen}](QHoverEvent* e) {
302 if(ptr && v)
303 {
304 const auto win = e->position();
305 const auto sz = v->view().size();
306 scaled_win->push_value(
307 ossia::vec2f{float(win.x() / sz.width()), float(win.y() / sz.height())});
308 gl_win->push_value(
309 ossia::vec2f{
310 float(win.x() / sz.width()), 1.f - float(win.y() / sz.height())});
311 const auto clip = score::gfx::PointerState::toClip(win, QSizeF(sz));
312 m_pointers.set_clip(float(clip.x()), float(clip.y()));
313 // The view sends hovers only: slot 0 moves, never pressed.
314 m_pointers.move_slot(0, float(clip.x()), float(clip.y()));
315 abs_win->push_value(ossia::vec2f{float(win.x()), float(win.y())});
316 }
317 }, Qt::DirectConnection));
318
319 m_root.add_child(std::move(node));
320 }
321 m_pointers.add_touch(
322 *this, m_root, static_cast<gfx_protocol_base*>(m_protocol.get())->context);
323
324 // Tablet input
325 ossia::net::parameter_base* scaled_tablet_win{};
326 {
327 auto node = std::make_unique<ossia::net::generic_node>("tablet", *this, m_root);
328 ossia::net::parameter_base* tablet_pressure{};
329 ossia::net::parameter_base* tablet_z{};
330 ossia::net::parameter_base* tablet_tan{};
331 ossia::net::parameter_base* tablet_rot{};
332 ossia::net::parameter_base* tablet_tilt_x{};
333 ossia::net::parameter_base* tablet_tilt_y{};
334 {
335 auto scale_node
336 = std::make_unique<ossia::net::generic_node>("scaled", *this, *node);
337 scaled_tablet_win = scale_node->create_parameter(ossia::val_type::VEC2F);
338 scaled_tablet_win->set_domain(ossia::make_domain(0.f, 1.f));
339 scaled_tablet_win->push_value(ossia::vec2f{0.f, 0.f});
340 node->add_child(std::move(scale_node));
341 }
342 {
343 auto abs_node
344 = std::make_unique<ossia::net::generic_node>("absolute", *this, *node);
345 abs_tablet_win = abs_node->create_parameter(ossia::val_type::VEC2F);
346 abs_tablet_win->set_domain(
347 ossia::make_domain(ossia::vec2f{0.f, 0.f}, ossia::vec2f{1280, 270.f}));
348 abs_tablet_win->push_value(ossia::vec2f{0.f, 0.f});
349 node->add_child(std::move(abs_node));
350 }
351 {
352 auto scale_node = std::make_unique<ossia::net::generic_node>("z", *this, *node);
353 tablet_z = scale_node->create_parameter(ossia::val_type::INT);
354 node->add_child(std::move(scale_node));
355 }
356 {
357 auto scale_node
358 = std::make_unique<ossia::net::generic_node>("pressure", *this, *node);
359 tablet_pressure = scale_node->create_parameter(ossia::val_type::FLOAT);
360 //tablet_pressure->set_domain(ossia::make_domain(0.f, 1.f));
361 //tablet_pressure->push_value(0.f);
362 node->add_child(std::move(scale_node));
363 }
364 {
365 auto scale_node
366 = std::make_unique<ossia::net::generic_node>("tangential", *this, *node);
367 tablet_tan = scale_node->create_parameter(ossia::val_type::FLOAT);
368 tablet_tan->set_domain(ossia::make_domain(-1.f, 1.f));
369 //tablet_tan->push_value(0.f);
370 node->add_child(std::move(scale_node));
371 }
372 {
373 auto scale_node
374 = std::make_unique<ossia::net::generic_node>("rotation", *this, *node);
375 tablet_rot = scale_node->create_parameter(ossia::val_type::FLOAT);
376 tablet_rot->set_unit(ossia::degree_u{});
377 tablet_rot->set_domain(ossia::make_domain(-180.f, 180.f));
378 node->add_child(std::move(scale_node));
379 }
380 {
381 auto scale_node
382 = std::make_unique<ossia::net::generic_node>("tilt_x", *this, *node);
383 tablet_tilt_x = scale_node->create_parameter(ossia::val_type::FLOAT);
384 tablet_tilt_x->set_domain(ossia::make_domain(-60.f, 60.f));
385 tablet_tilt_x->set_unit(ossia::degree_u{});
386 node->add_child(std::move(scale_node));
387 }
388 {
389 auto scale_node
390 = std::make_unique<ossia::net::generic_node>("tilt_y", *this, *node);
391 tablet_tilt_y = scale_node->create_parameter(ossia::val_type::FLOAT);
392 tablet_tilt_y->set_domain(ossia::make_domain(-60.f, 60.f));
393 tablet_tilt_y->set_unit(ossia::degree_u{});
394 node->add_child(std::move(scale_node));
395 }
396
397 con.push_back(
398 QObject::connect(
399 &v, &Scenario::ProcessGraphicsView::tabletMove, m_screen,
400 [=, this, v = QPointer{&view},
401 ptr = QPointer{m_screen}](QTabletEvent* ev) {
402 if(ptr && v)
403 {
404 auto win = ev->position();
405 auto sz = v->view().size();
406 scaled_tablet_win->push_value(
407 ossia::vec2f{float(win.x() / sz.width()), float(win.y() / sz.height())});
408 abs_tablet_win->push_value(ossia::vec2f{float(win.x()), float(win.y())});
409 tablet_pressure->push_value(ev->pressure());
410 tablet_tan->push_value(ev->tangentialPressure());
411 tablet_rot->push_value(ev->rotation());
412 tablet_z->push_value(ev->z());
413 tablet_tilt_x->push_value(ev->xTilt());
414 tablet_tilt_y->push_value(ev->yTilt());
415 }
416 },
417 Qt::DirectConnection));
418 m_root.add_child(std::move(node));
419 }
420
421 // Keyboard input
422 {
423 auto node = std::make_unique<ossia::net::generic_node>("key", *this, m_root);
424 {
425 auto press_node
426 = std::make_unique<ossia::net::generic_node>("press", *this, *node);
427 {
428 auto code_node
429 = std::make_unique<ossia::net::generic_node>("code", *this, *press_node);
430 press_param = code_node->create_parameter(ossia::val_type::INT);
431 press_param->push_value(ossia::vec2f{0.f, 0.f});
432 press_node->add_child(std::move(code_node));
433 }
434 {
435 auto text_node
436 = std::make_unique<ossia::net::generic_node>("text", *this, *press_node);
437 text_param = text_node->create_parameter(ossia::val_type::STRING);
438 press_node->add_child(std::move(text_node));
439 }
440
441 con.push_back(
442 QObject::connect(
443 &v, &Scenario::ProcessGraphicsView::keyPress, m_screen,
444 [this, v = QPointer{&view}, ptr = QPointer{m_screen}](QKeyEvent* ev) {
445 if(ptr && v)
446 {
447 if(!ev->isAutoRepeat())
448 {
449 press_param->push_value(ev->key());
450 text_param->push_value(ev->text().toStdString());
451 }
452 }
453 }, Qt::DirectConnection));
454 node->add_child(std::move(press_node));
455 }
456 {
457 auto release_node
458 = std::make_unique<ossia::net::generic_node>("release", *this, *node);
459 ossia::net::parameter_base* press_param{};
460 ossia::net::parameter_base* text_param{};
461 {
462 auto code_node
463 = std::make_unique<ossia::net::generic_node>("code", *this, *release_node);
464 press_param = code_node->create_parameter(ossia::val_type::INT);
465 press_param->push_value(ossia::vec2f{0.f, 0.f});
466 release_node->add_child(std::move(code_node));
467 }
468 {
469 auto text_node
470 = std::make_unique<ossia::net::generic_node>("text", *this, *release_node);
471 text_param = text_node->create_parameter(ossia::val_type::STRING);
472 release_node->add_child(std::move(text_node));
473 }
474
475 con.push_back(
476 QObject::connect(
477 &v, &Scenario::ProcessGraphicsView::keyRelease, m_screen,
478 [=, v = QPointer{&view}, ptr = QPointer{m_screen}](QKeyEvent* ev) {
479 if(ptr && v)
480 {
481 if(!ev->isAutoRepeat())
482 {
483 press_param->push_value(ev->key());
484 text_param->push_value(ev->text().toStdString());
485 }
486 }
487 }, Qt::DirectConnection));
488 node->add_child(std::move(release_node));
489 }
490
491 m_root.add_child(std::move(node));
492 }
493 }
494
496 {
497 // The view's signals go first. Device::DeviceInterface::disconnect() has
498 // usually cleared the tree already, so every parameter pointer the slots
499 // hold is dangling, and taking the renderer off the view below swaps its
500 // viewport, which resizes it and emits sizeChanged synchronously.
501 for(auto& c : con)
502 QObject::disconnect(c);
503 con.clear();
504 QObject::disconnect(m_screenChanged);
505 if(m_view)
506 m_view->view().removeEventFilter(m_pixelRatioWatcher.get());
507 m_pixelRatioWatcher.reset();
508
509 if(m_view)
510 {
511 m_view->removeBackgroundRenderer(m_renderer);
512 }
513 delete m_renderer;
514 m_protocol->stop();
515
516 m_root.clear_children();
517
518 m_protocol.reset();
519 }
520
521 const gfx_node_base& get_root_node() const override { return m_root; }
522 gfx_node_base& get_root_node() override { return m_root; }
523
524 score::gfx::BackgroundNode* node() const noexcept { return m_screen; }
525};
526
527}
Definition BackgroundDevice.hpp:24
Definition BackgroundDevice.hpp:62
Definition GfxParameter.hpp:73
Definition GfxParameter.hpp:54
Definition ScenarioDocumentView.hpp:157
Definition BackgroundRenderer.hpp:13
Binds the rendering pipeline to ossia processes.
Definition AssetTable.cpp:7
Definition PointerAddresses.hpp:37
void move_slot(int slot, float x, float y)
Definition PointerAddresses.hpp:93
void add_touch(ossia::net::device_base &dev, ossia::net::node_base &root, GfxExecutionAction *context)
touch/points and touch/history, under root.
Definition PointerAddresses.hpp:56
void add_clip(ossia::net::device_base &dev, ossia::net::node_base &cursor)
cursor/clip, under an existing cursor node.
Definition PointerAddresses.hpp:45
Definition BackgroundNode.hpp:16
static QPointF toClip(QPointF win, QSizeF sz) noexcept
Window position -> clip space over a window of size sz.
Definition PointerState.hpp:32