Loading...
Searching...
No Matches
score-plugin-avnd/Crousti/Layer.hpp
1#pragma once
2#include <Process/LayerPresenter.hpp>
3#include <Process/LayerView.hpp>
4
5#include <Control/Layout.hpp>
6#include <Crousti/MessageBus.hpp>
7#include <Crousti/Painter.hpp>
8#include <Crousti/ProcessModel.hpp>
9
10#include <score/graphics/layouts/GraphicsBoxLayout.hpp>
11#include <score/graphics/layouts/GraphicsGridLayout.hpp>
12#include <score/graphics/layouts/GraphicsSplitLayout.hpp>
13#include <score/graphics/layouts/GraphicsTabLayout.hpp>
14#include <score/graphics/widgets/QGraphicsLineEdit.hpp>
15
16#include <avnd/concepts/layout.hpp>
17
18namespace oscr
19{
20template <typename Item>
22 = avnd::container_layout<Item> || avnd::hbox_layout<Item> || avnd::group_layout<Item>
23 || avnd::vbox_layout<Item> || avnd::split_layout<Item> || avnd::grid_layout<Item>
24 || avnd::tab_layout<Item>;
25
26template <typename Info>
28{
29};
30template <typename Info>
31 requires requires { sizeof(typename Info::ui::bus); }
32struct MessageBusUi<Info>
33{
34 typename Info::ui::bus bus;
35};
36
37template <typename Info, typename RootLayout>
39 : RootLayout
40 , MessageBusUi<Info>
41{
42 using RootLayout::RootLayout;
43 typename Info::ui ui;
44};
45
46template <typename Item>
47static auto createRecursiveLayout(QGraphicsItem* parent)
48{
49 if constexpr(avnd::container_layout<Item>)
50 {
51 return new score::GraphicsLayout{parent};
52 }
53 else if constexpr(avnd::hbox_layout<Item> || avnd::group_layout<Item>)
54 {
55 return new score::GraphicsHBoxLayout{parent};
56 }
57 else if constexpr(avnd::vbox_layout<Item>)
58 {
59 return new score::GraphicsVBoxLayout{parent};
60 }
61 else if constexpr(avnd::split_layout<Item>)
62 {
63 return new score::GraphicsSplitLayout{parent};
64 }
65 else if constexpr(avnd::grid_layout<Item>)
66 {
67 if constexpr(requires { Item::columns(); })
68 {
69 return new score::GraphicsGridColumnsLayout{parent};
70 }
71 else if constexpr(requires { Item::rows(); })
72 {
73 return new score::GraphicsGridRowsLayout{parent};
74 }
75 }
76 else if constexpr(avnd::tab_layout<Item>)
77 {
78 return new score::GraphicsTabLayout{parent};
79 }
80 else if constexpr(avnd::has_layout<Item>)
81 {
82 return new score::GraphicsLayout{parent};
83 }
84 else
85 {
86 // static_assert(Item::no_layout_provided);
87 return new score::GraphicsVBoxLayout{parent};
88 }
89}
90
91template <typename Item>
92static void setupRecursiveLayout(auto* new_l)
93{
94 if constexpr(avnd::grid_layout<Item>)
95 {
96 if constexpr(requires { Item::columns(); })
97 {
98 new_l->setColumns(Item::columns());
99 }
100 else if constexpr(requires { Item::rows(); })
101 {
102 new_l->setRows(Item::rows());
103 }
104 }
105 else if constexpr(avnd::tab_layout<Item>)
106 {
107 [=]<typename... Ts>(avnd::typelist<Ts...> args) {
108 (new_l->addTab(Ts::name()), ...);
109 }(avnd::as_typelist<Item>{});
110 }
111}
112
113template <typename T>
115template <typename T, typename V>
116struct pmf_member_type<V T::*>
117{
118 using type = V;
119};
120
121template <typename T>
122using pmf_member_type_t = typename pmf_member_type<T>::type;
123
124template <typename Info>
126{
127 using inputs_type = typename avnd::input_introspection<Info>::type;
128 using outputs_type = typename avnd::output_introspection<Info>::type;
129 inputs_type temp_inputs{};
130 outputs_type temp_outputs{};
131
132 typename Info::ui* rootUi{};
133
134 template <typename Item>
135 void setupControl(
136 QGraphicsItem* parent, Process::ControlOutlet* inl,
137 const Process::ControlLayout& lay, Item& item)
138 = delete; // TODO
139
140 template <typename Item>
141 void setupControl(
142 QGraphicsItem* parent, Process::ControlInlet* inl,
143 const Process::ControlLayout& lay, Item& item)
144 {
145 if constexpr(requires { sizeof(Item::value); })
146 {
147 using avnd_port_type = pmf_member_type_t<decltype(item.model)>;
148 avnd_port_type p;
149 oscr::from_ossia_value(p, inl->value(), item.value);
150 if constexpr(requires { rootUi->on_control_update(); })
151 {
152 QObject::connect(
153 inl, &Process::ControlInlet::valueChanged, &context,
154 [rui = rootUi, layout = this->layout, &item](const ossia::value& v) {
155 avnd_port_type p;
156 oscr::from_ossia_value(p, v, item.value);
157
158 rui->on_control_update();
159 layout->update();
160 });
161 }
162 else
163 {
164 QObject::connect(
165 inl, &Process::ControlInlet::valueChanged, &context,
166 [layout = this->layout, &item](const ossia::value& v) {
167 avnd_port_type p;
168 oscr::from_ossia_value(p, v, item.value);
169 layout->update();
170 });
171 }
172
173 if constexpr(requires { item.set = {}; })
174 {
175 item.set = [inl](const auto& val) { inl->setValue(oscr::to_ossia_value(val)); };
176 }
177 }
178
179 if constexpr(requires { Item::dynamic_size; })
180 {
181 if(auto obj = dynamic_cast<score::ResizeableItem*>(parent))
182 {
183 if(auto edit = qgraphicsitem_cast<score::QGraphicsLineEdit*>(lay.control))
184 QObject::connect(
185 edit, &score::QGraphicsLineEdit::sizeChanged, obj,
186 &score::ResizeableItem::childrenSizeChanged);
187 }
188 }
189 if constexpr(requires { Item::width; })
190 {
191 if(auto edit = qgraphicsitem_cast<score::QGraphicsLineEdit*>(lay.control))
192 edit->setTextWidth(Item::width());
193 }
194 }
195
196 template <typename Item>
197 void createControl(Item& item, auto... member)
198 {
199 if constexpr(requires { ((inputs_type{}).*....*member); })
200 {
201 int index = avnd::index_in_struct(temp_inputs, member...);
202 auto& proc = static_cast<const ProcessModel<Info>&>(this->proc);
203 auto ports = proc.avnd_input_idx_to_model_ports(index);
204 for(auto p : ports)
205 {
206 auto [port, qitem] = makeInlet(p);
207 {
208 SCORE_ASSERT(port);
209 SCORE_ASSERT(qitem.container);
210 setupControl(this->layout, port, qitem, item);
211 setupItem(item, *qitem.container);
212 }
213 }
214 }
215 else if constexpr(requires { ((outputs_type{}).*....*member); })
216 {
217 int index = avnd::index_in_struct(temp_outputs, member...);
218 auto& proc = static_cast<const ProcessModel<Info>&>(this->proc);
219 auto ports = proc.avnd_output_idx_to_model_ports(index);
220 for(auto p : ports)
221 {
222 auto [port, qitem] = makeOutlet(p);
223 {
224 SCORE_ASSERT(port);
225 SCORE_ASSERT(qitem.container);
226 setupControl(this->layout, port, qitem, item);
227 setupItem(item, *qitem.container);
228 }
229 }
230 }
231 else
232 {
233 static_assert(sizeof...(member) < 0, "not_a_member_of_inputs_or_outputs");
234 }
235 }
236
237 template <typename Item, typename T>
238 void createWidget(Item& it, const T& member)
239 {
240 if constexpr(requires {
241 { member } -> std::convertible_to<std::string_view>;
242 })
243 {
244 auto res = makeLabel(member);
245 setupItem(it, *res);
246 }
247 else if constexpr(requires {
248 { member.text } -> std::convertible_to<std::string_view>;
249 })
250 {
251 auto res = makeLabel(member.text);
252 setupItem(it, *res);
253 }
254 else
255 {
256 createControl(it, member);
257 }
258 }
259
260 template <typename Item, typename... T>
261 requires(sizeof...(T) > 1)
262 void createWidget(Item& item, T... recursive_members)
263 {
264 createControl(item, recursive_members...);
265 }
266
267 template <typename Item>
268 void createCustom(Item& item)
269 {
270 static_assert(!requires { item.transaction; });
271 auto res = new oscr::CustomItem<Item&>{item};
272 setupItem(item, *res);
273 }
274
275 template <typename Item>
276 void createCustomControl(Item& item, auto... member)
277 {
278 if constexpr(requires { ((inputs_type{}).*....*member); })
279 {
280 int index = avnd::index_in_struct(temp_inputs, member...);
281
282 auto& proc = static_cast<const ProcessModel<Info>&>(this->proc);
283 auto ports = proc.avnd_input_idx_to_model_ports(index);
284 for(auto p : ports)
285 {
286 if(auto* port = qobject_cast<Process::ControlInlet*>(p))
287 {
288 auto qitem = new oscr::CustomControl<Item&>{item, *port, this->doc};
289 Process::ControlLayout lay{.container = qitem};
290 if(auto* f = portFactory.get(port->concreteKey()))
291 {
292 lay.port_item = f->makePortItem(*port, this->doc, this->layout, &context);
293 }
294 setupControl(this->layout, port, lay, item);
295 setupItem(item, *qitem);
296 }
297 }
298 }
299 else if constexpr(requires { ((outputs_type{}).*....*member); })
300 {
301 int index = avnd::index_in_struct(temp_outputs, member...);
302
303 auto& proc = static_cast<const ProcessModel<Info>&>(this->proc);
304 auto ports = proc.avnd_output_idx_to_model_ports(index);
305 for(auto p : ports)
306 {
307 if(auto* port = qobject_cast<Process::ControlOutlet*>(p))
308 {
309 auto qitem = new oscr::CustomControl<Item&>{item, *port, this->doc};
310 Process::ControlLayout lay{.container = qitem};
311 if(auto* f = portFactory.get(port->concreteKey()))
312 {
313 lay.port_item = f->makePortItem(*port, this->doc, this->layout, &context);
314 }
315 setupControl(this->layout, port, lay, item);
316 setupItem(item, *qitem);
317 }
318 }
319 }
320 else
321 {
322 static_assert(sizeof...(member) < 0, "not_a_member_of_inputs_or_outputs");
323 }
324 }
325
326 template <typename Item>
327 void subLayout(Item& item, score::GraphicsLayout* new_l, auto... recursive_members)
328 {
329 auto old_l = layout;
330 setupLayout(item, *new_l);
331 setupItem(item, *new_l);
332 layout = new_l;
333 createdLayouts.push_back(new_l);
334
335 {
336 using namespace boost::pfr;
337 using namespace boost::pfr::detail;
338 static constexpr int N = boost::pfr::tuple_size_v<Item>;
339 auto t = boost::pfr::structure_tie(item);
340 [&]<std::size_t... I>(std::index_sequence<I...>) {
341 using namespace std;
342 using namespace boost::pfr;
343
344 (this->walkLayout(get<I>(t), recursive_members...), ...);
345 }(std::make_index_sequence<N>{});
346 }
347
348 layout = old_l;
349 }
350
351 template <typename Item>
352 auto initRecursiveLayout()
353 {
354 auto new_l = createRecursiveLayout<Item>(this->layout);
355 setupRecursiveLayout<Item>(new_l);
356 return new_l;
357 }
358
359 template <typename Item>
360 void walkLayout(Item& item, auto... recursive_members)
361 {
362 if constexpr(avnd::spacing_layout<Item>)
363 {
364 auto widg = new score::EmptyRectItem{layout};
365 double w = 1., h = 1.;
366 if constexpr(requires { Item::width(); })
367 w = Item::width();
368 if constexpr(requires { Item::height(); })
369 h = Item::height();
370 widg->setRect({0, 0, w, h});
371 }
372 else if constexpr(recursive_container_layout<Item>)
373 {
374 subLayout(item, initRecursiveLayout<Item>(), recursive_members...);
375 }
376 else if constexpr(avnd::control_layout<Item>)
377 {
378 // Widget with some metadata.. FIXME
379 // Auto-generated item for a control
380 createWidget(item, recursive_members..., item.model);
381 }
382 else if constexpr(avnd::custom_control_layout<Item>)
383 {
384 // Widget with some metadata.. FIXME
385 // Custom-drawn item for a control
386 createCustomControl(item, recursive_members..., item.model);
387 }
388 else if constexpr(avnd::custom_layout<Item>)
389 {
390 // Widget with some metadata.. FIXME
391 // This is just a cosmetic item without behaviour or control attached
392 createCustom(item);
393 }
394 else if constexpr(avnd::recursive_group_layout<Item>)
395 {
396 walkLayout(item.ui, recursive_members..., item.group);
397 }
398 else if constexpr(avnd::dynamic_controls<Item>)
399 {
400 walkLayout(item.ui, recursive_members..., item.group);
401 }
402 else if constexpr(avnd::has_layout<Item>)
403 {
404 // Treat it like group
405 subLayout(item, initRecursiveLayout<Item>(), recursive_members...);
406 }
407 else
408 {
409 // Normal widget, e.g. just a const char*
410 createWidget(item, item);
411 }
412 }
413};
414
415template <typename Info>
417{
418public:
419 virtual ~LayerFactory() { }
420
421private:
422 std::optional<double> recommendedHeight() const noexcept override
423 {
424 if constexpr(requires { (double)Info::layout::height(); })
425 {
426 return Info::layout::height();
427 }
428 return Process::LayerFactory::recommendedHeight();
429 }
430
431 UuidKey<Process::ProcessModel> concreteKey() const noexcept override
432 {
434 }
435
436 bool matches(const UuidKey<Process::ProcessModel>& p) const override
437 {
439 }
440
441 Process::LayerView* makeLayerView(
442 const Process::ProcessModel& proc, const Process::Context& context,
443 QGraphicsItem* parent) const final override
444 {
445 return nullptr;
446 }
447
448 Process::LayerPresenter* makeLayerPresenter(
450 const Process::Context& context, QObject* parent) const final override
451 {
452 return nullptr;
453 }
454
455 template <typename Item>
456 static void init_bus(ProcessModel<Info>& proc, Item& item)
457 {
458 auto ptr = &item;
459 if constexpr(avnd::has_gui_to_processor_bus<Info>)
460 {
461 // ui -> engine
462 ptr->bus.send_message = MessageBusSender{proc.from_ui};
463 }
464
465 if constexpr(avnd::has_processor_to_gui_bus<Info>)
466 {
467 // engine -> ui
468 proc.to_ui = [ptr = QPointer{ptr}](QByteArray mess) {
469 // FIXME this is not enough as the message may be sent from another thread?
470 if(!ptr)
471 return;
472
473 if constexpr(requires { ptr->bus.process_message(); })
474 {
475 ptr->bus.process_message();
476 }
477 else if constexpr(requires { ptr->bus.process_message(ptr->ui); })
478 {
479 ptr->bus.process_message(ptr->ui);
480 }
481 else if constexpr(requires { ptr->bus.process_message(ptr->ui, {}); })
482 {
483 std::decay_t<avnd::second_argument<&Info::ui::bus::process_message>> arg;
484 MessageBusReader b{mess};
485 b(arg);
486 ptr->bus.process_message(ptr->ui, std::move(arg));
487 }
488 else
489 {
490 ptr->bus.process_message(ptr->ui, {});
491 }
492 };
493 }
494
495 if_possible(ptr->bus.init(ptr->ui));
496 }
497
498 auto makeItemImpl(ProcessModel<Info>& proc, QGraphicsItem* parent) const noexcept
499 {
500 using ui_type = typename Info::ui;
501 using root_layout_type
502 = std::remove_cvref_t<decltype(*createRecursiveLayout<ui_type>(nullptr))>;
503
504 auto new_l = new RootItem<Info, root_layout_type>{parent};
505 setupRecursiveLayout<ui_type>(new_l);
506 if constexpr(requires { sizeof(typename Info::ui::bus); })
507 init_bus(proc, *new_l);
508
509 if constexpr(requires { new_l->ui.start(); })
510 {
511 QObject::connect(&proc, &Process::ProcessModel::startExecution, new_l, [new_l] {
512 new_l->ui.start();
513 });
514 }
515 if constexpr(requires { new_l->ui.stop(); })
516 {
517 QObject::connect(&proc, &Process::ProcessModel::stopExecution, new_l, [new_l] {
518 new_l->ui.stop();
519 });
520 }
521 if constexpr(requires { new_l->ui.reset(); })
522 {
523 QObject::connect(&proc, &Process::ProcessModel::resetExecution, new_l, [new_l] {
524 new_l->ui.reset();
525 });
526 }
527 return new_l;
528 }
529
530 score::ResizeableItem* makeItem(
531 const Process::ProcessModel& proc, const Process::Context& ctx,
532 QGraphicsItem* parent) const final override
533 {
534 using namespace score;
535 auto& process = static_cast<const ProcessModel<Info>&>(proc);
536
537 auto rootItem = makeItemImpl(const_cast<ProcessModel<Info>&>(process), parent);
538
539 auto recreate = [parent, &proc, &ctx, rootItem] {
540 LayoutBuilder<Info> b{
541 *rootItem, proc,
542 ctx, ctx.app.interfaces<Process::PortFactoryList>(),
543 proc.inlets(), proc.outlets(),
544 };
545 b.rootUi = &rootItem->ui;
546 b.layout = parent;
547
548 b.subLayout(rootItem->ui, rootItem);
549
550 b.finalizeLayout(rootItem);
551
552 rootItem->fitChildrenRect();
553
554 if_possible(b.rootUi->on_control_update());
555 };
556
557 QObject::connect(&proc, &Process::ProcessModel::inletsChanged, rootItem, [=]() {
558 auto cld = rootItem->childItems();
559 for(auto item : cld)
560 {
561 delete item;
562 }
563 recreate();
564 });
565 QObject::connect(&proc, &Process::ProcessModel::outletsChanged, rootItem, [=]() {
566 auto cld = rootItem->childItems();
567 for(auto item : cld)
568 {
569 delete item;
570 }
571 recreate();
572 });
573
574 recreate();
575 return rootItem;
576 }
577};
578
579}
Definition Port.hpp:205
Definition Port.hpp:426
Definition score-lib-process/Process/ProcessFactory.hpp:59
Definition LayerPresenter.hpp:34
Definition LayerView.hpp:21
Definition PortFactory.hpp:82
The Process class.
Definition score-lib-process/Process/Process.hpp:62
Definition UuidKey.hpp:345
Definition Painter.hpp:530
Definition Painter.hpp:362
Definition score-plugin-avnd/Crousti/Layer.hpp:417
Definition score-plugin-avnd/Crousti/ProcessModel.hpp:86
Definition RectItem.hpp:78
Definition GraphicsGridLayout.hpp:9
Definition GraphicsGridLayout.hpp:23
Definition GraphicsBoxLayout.hpp:9
Definition GraphicsLayout.hpp:8
Definition GraphicsSplitLayout.hpp:9
Definition GraphicsTabLayout.hpp:9
Definition GraphicsBoxLayout.hpp:18
FactoryType * get(const key_type &k) const noexcept
Get a particular factory from its ConcreteKey.
Definition InterfaceList.hpp:128
Definition RectItem.hpp:12
Definition score-plugin-avnd/Crousti/Layer.hpp:22
Definition Factories.hpp:19
Base toolkit upon which the software is built.
Definition Application.cpp:99
STL namespace.
Static metadata implementation.
Definition lib/score/tools/Metadata.hpp:36
Definition ProcessContext.hpp:12
Definition PortFactory.hpp:21
Definition plugins/score-lib-process/Control/Layout.hpp:16
Definition ObjectMatches.hpp:6
Definition score-plugin-avnd/Crousti/Layer.hpp:126
Definition MessageBus.hpp:171
Definition MessageBus.hpp:44
Definition score-plugin-avnd/Crousti/Layer.hpp:28
Definition score-plugin-avnd/Crousti/Layer.hpp:41
Definition score-plugin-avnd/Crousti/Layer.hpp:114