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