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 
18 namespace oscr
19 {
20 template <typename Item>
21 concept recursive_container_layout
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 
26 template <typename Info>
28 {
29 };
30 template <typename Info>
31  requires requires { sizeof(typename Info::ui::bus); }
32 struct MessageBusUi<Info>
33 {
34  typename Info::ui::bus bus;
35 };
36 
37 template <typename Info, typename RootLayout>
38 struct RootItem
39  : RootLayout
40  , MessageBusUi<Info>
41 {
42  using RootLayout::RootLayout;
43  typename Info::ui ui;
44 };
45 
46 template <typename Item>
47 static 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 
91 template <typename Item>
92 static 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 
113 template <typename T>
115 template <typename T, typename V>
116 struct pmf_member_type<V T::*>
117 {
118  using type = V;
119 };
120 
121 template <typename T>
122 using pmf_member_type_t = typename pmf_member_type<T>::type;
123 
124 template <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 
174  if constexpr(requires { Item::dynamic_size; })
175  {
176  if(auto obj = dynamic_cast<score::ResizeableItem*>(parent))
177  {
178  if(auto edit = qgraphicsitem_cast<score::QGraphicsLineEdit*>(lay.control))
179  QObject::connect(
180  edit, &score::QGraphicsLineEdit::sizeChanged, obj,
181  &score::ResizeableItem::childrenSizeChanged);
182  }
183  }
184  }
185 
186  template <typename Item>
187  void createControl(Item& item, auto... member)
188  {
189  if constexpr(requires { ((inputs_type{}).*....*member); })
190  {
191  int index = avnd::index_in_struct(temp_inputs, member...);
192  auto& proc = static_cast<const ProcessModel<Info>&>(this->proc);
193  auto ports = proc.avnd_input_idx_to_model_ports(index);
194  for(auto p : ports)
195  {
196  auto [port, qitem] = makeInlet(p);
197  {
198  SCORE_ASSERT(port);
199  SCORE_ASSERT(qitem.container);
200  setupControl(this->layout, port, qitem, item);
201  setupItem(item, *qitem.container);
202  }
203  }
204  }
205  else if constexpr(requires { ((outputs_type{}).*....*member); })
206  {
207  int index = avnd::index_in_struct(temp_outputs, member...);
208  auto& proc = static_cast<const ProcessModel<Info>&>(this->proc);
209  auto ports = proc.avnd_output_idx_to_model_ports(index);
210  for(auto p : ports)
211  {
212  auto [port, qitem] = makeOutlet(p);
213  {
214  SCORE_ASSERT(port);
215  SCORE_ASSERT(qitem.container);
216  setupControl(this->layout, port, qitem, item);
217  setupItem(item, *qitem.container);
218  }
219  }
220  }
221  else
222  {
223  static_assert(sizeof...(member) < 0, "not_a_member_of_inputs_or_outputs");
224  }
225  }
226 
227  template <typename Item, typename T>
228  void createWidget(Item& it, const T& member)
229  {
230  if constexpr(requires {
231  { member } -> std::convertible_to<std::string_view>;
232  })
233  {
234  auto res = makeLabel(member);
235  setupItem(it, *res);
236  }
237  else if constexpr(requires {
238  { member.text } -> std::convertible_to<std::string_view>;
239  })
240  {
241  auto res = makeLabel(member.text);
242  setupItem(it, *res);
243  }
244  else
245  {
246  createControl(it, member);
247  }
248  }
249 
250  template <typename Item, typename... T>
251  requires(sizeof...(T) > 1)
252  void createWidget(Item& item, T... recursive_members)
253  {
254  createControl(item, recursive_members...);
255  }
256 
257  template <typename Item>
258  void createCustom(Item& item)
259  {
260  static_assert(!requires { item.transaction; });
261  auto res = new oscr::CustomItem<Item&>{item};
262  setupItem(item, *res);
263  }
264 
265  template <typename Item>
266  void createCustomControl(Item& item, auto... member)
267  {
268  if constexpr(requires { ((inputs_type{}).*....*member); })
269  {
270  int index = avnd::index_in_struct(temp_inputs, member...);
271 
272  auto& proc = static_cast<const ProcessModel<Info>&>(this->proc);
273  auto ports = proc.avnd_input_idx_to_model_ports(index);
274  for(auto p : ports)
275  {
276  if(auto* port = qobject_cast<Process::ControlInlet*>(p))
277  {
278  auto qitem = new oscr::CustomControl<Item&>{item, *port, this->doc};
279  Process::ControlLayout lay{.container = qitem};
280  setupControl(this->layout, port, lay, item);
281  setupItem(item, *qitem);
282  }
283  }
284  }
285  else if constexpr(requires { ((outputs_type{}).*....*member); })
286  {
287  int index = avnd::index_in_struct(temp_outputs, member...);
288 
289  auto& proc = static_cast<const ProcessModel<Info>&>(this->proc);
290  auto ports = proc.avnd_output_idx_to_model_ports(index);
291  for(auto p : ports)
292  {
293  if(auto* port = qobject_cast<Process::ControlOutlet*>(p))
294  {
295  auto qitem = new oscr::CustomControl<Item&>{item, *port, this->doc};
296  Process::ControlLayout lay{.container = qitem};
297  setupControl(this->layout, port, lay, item);
298  setupItem(item, *qitem);
299  }
300  }
301  }
302  else
303  {
304  static_assert(sizeof...(member) < 0, "not_a_member_of_inputs_or_outputs");
305  }
306  }
307 
308  template <typename Item>
309  void subLayout(Item& item, score::GraphicsLayout* new_l, auto... recursive_members)
310  {
311  auto old_l = layout;
312  setupLayout(item, *new_l);
313  setupItem(item, *new_l);
314  layout = new_l;
315  createdLayouts.push_back(new_l);
316 
317  {
318  using namespace boost::pfr;
319  using namespace boost::pfr::detail;
320  static constexpr int N = boost::pfr::tuple_size_v<Item>;
321  auto t = boost::pfr::detail::tie_as_tuple(item, size_t_<N>{});
322  [&]<std::size_t... I>(std::index_sequence<I...>) {
323  (this->walkLayout(sequence_tuple::get<I>(t), recursive_members...), ...);
324  }(std::make_index_sequence<N>{});
325  }
326 
327  layout = old_l;
328  }
329 
330  template <typename Item>
331  auto initRecursiveLayout()
332  {
333  auto new_l = createRecursiveLayout<Item>(this->layout);
334  setupRecursiveLayout<Item>(new_l);
335  return new_l;
336  }
337 
338  template <typename Item>
339  void walkLayout(Item& item, auto... recursive_members)
340  {
341  if constexpr(avnd::spacing_layout<Item>)
342  {
343  auto widg = new score::EmptyRectItem{layout};
344  double w = 1., h = 1.;
345  if constexpr(requires { Item::width(); })
346  w = Item::width();
347  if constexpr(requires { Item::height(); })
348  h = Item::height();
349  widg->setRect({0, 0, w, h});
350  }
351  else if constexpr(recursive_container_layout<Item>)
352  {
353  subLayout(item, initRecursiveLayout<Item>(), recursive_members...);
354  }
355  else if constexpr(avnd::control_layout<Item>)
356  {
357  // Widget with some metadata.. FIXME
358  // Auto-generated item for a control
359  createWidget(item, recursive_members..., item.model);
360  }
361  else if constexpr(avnd::custom_control_layout<Item>)
362  {
363  // Widget with some metadata.. FIXME
364  // Custom-drawn item for a control
365  createCustomControl(item, recursive_members..., item.model);
366  }
367  else if constexpr(avnd::custom_layout<Item>)
368  {
369  // Widget with some metadata.. FIXME
370  // This is just a cosmetic item without behaviour or control attached
371  createCustom(item);
372  }
373  else if constexpr(avnd::recursive_group_layout<Item>)
374  {
375  walkLayout(item.ui, recursive_members..., item.group);
376  }
377  else if constexpr(avnd::dynamic_controls<Item>)
378  {
379  walkLayout(item.ui, recursive_members..., item.group);
380  }
381  else if constexpr(avnd::has_layout<Item>)
382  {
383  // Treat it like group
384  subLayout(item, initRecursiveLayout<Item>(), recursive_members...);
385  }
386  else
387  {
388  // Normal widget, e.g. just a const char*
389  createWidget(item, item);
390  }
391  }
392 };
393 
394 template <typename Info>
396 {
397 public:
398  virtual ~LayerFactory() { }
399 
400 private:
401  std::optional<double> recommendedHeight() const noexcept override
402  {
403  if constexpr(requires { (double)Info::layout::height(); })
404  {
405  return Info::layout::height();
406  }
407  return Process::LayerFactory::recommendedHeight();
408  }
409 
410  UuidKey<Process::ProcessModel> concreteKey() const noexcept override
411  {
413  }
414 
415  bool matches(const UuidKey<Process::ProcessModel>& p) const override
416  {
418  }
419 
420  Process::LayerView* makeLayerView(
421  const Process::ProcessModel& proc, const Process::Context& context,
422  QGraphicsItem* parent) const final override
423  {
424  return nullptr;
425  }
426 
427  Process::LayerPresenter* makeLayerPresenter(
429  const Process::Context& context, QObject* parent) const final override
430  {
431  return nullptr;
432  }
433 
434  template <typename Item>
435  static void init_bus(ProcessModel<Info>& proc, Item& item)
436  {
437  auto ptr = &item;
438  if constexpr(avnd::has_gui_to_processor_bus<Info>)
439  {
440  // ui -> engine
441  ptr->bus.send_message = MessageBusSender{proc.from_ui};
442  }
443 
444  if constexpr(avnd::has_processor_to_gui_bus<Info>)
445  {
446  // engine -> ui
447  proc.to_ui = [ptr = QPointer{ptr}](QByteArray mess) {
448  // FIXME this is not enough as the message may be sent from another thread?
449  if(!ptr)
450  return;
451 
452  if constexpr(requires { ptr->bus.process_message(); })
453  {
454  ptr->bus.process_message();
455  }
456  else if constexpr(requires { ptr->bus.process_message(ptr->ui); })
457  {
458  ptr->bus.process_message(ptr->ui);
459  }
460  else if constexpr(requires { ptr->bus.process_message(ptr->ui, {}); })
461  {
462  std::decay_t<avnd::second_argument<&Info::ui::bus::process_message>> arg;
463  MessageBusReader b{mess};
464  b(arg);
465  ptr->bus.process_message(ptr->ui, std::move(arg));
466  }
467  else
468  {
469  ptr->bus.process_message(ptr->ui, {});
470  }
471  };
472  }
473 
474  if_possible(ptr->bus.init(ptr->ui));
475  }
476 
477  auto makeItemImpl(ProcessModel<Info>& proc, QGraphicsItem* parent) const noexcept
478  {
479  using ui_type = typename Info::ui;
480  using root_layout_type
481  = std::remove_cvref_t<decltype(*createRecursiveLayout<ui_type>(nullptr))>;
482 
483  auto new_l = new RootItem<Info, root_layout_type>{parent};
484  setupRecursiveLayout<ui_type>(new_l);
485  if constexpr(requires { sizeof(typename Info::ui::bus); })
486  init_bus(proc, *new_l);
487  return new_l;
488  }
489 
490  score::ResizeableItem* makeItem(
491  const Process::ProcessModel& proc, const Process::Context& ctx,
492  QGraphicsItem* parent) const final override
493  {
494  using namespace score;
495  auto& process = static_cast<const ProcessModel<Info>&>(proc);
496 
497  auto rootItem = makeItemImpl(const_cast<ProcessModel<Info>&>(process), parent);
498 
499  auto recreate = [parent, &proc, &ctx, rootItem] {
501  *rootItem, proc,
502  ctx, ctx.app.interfaces<Process::PortFactoryList>(),
503  proc.inlets(), proc.outlets(),
504  };
505  b.rootUi = &rootItem->ui;
506  b.layout = parent;
507 
508  b.subLayout(rootItem->ui, rootItem);
509 
510  b.finalizeLayout(rootItem);
511 
512  rootItem->fitChildrenRect();
513 
514  if_possible(b.rootUi->on_control_update());
515  };
516 
517  QObject::connect(&proc, &Process::ProcessModel::inletsChanged, rootItem, [=]() {
518  auto cld = rootItem->childItems();
519  for(auto item : cld)
520  {
521  delete item;
522  }
523  recreate();
524  });
525  QObject::connect(&proc, &Process::ProcessModel::outletsChanged, rootItem, [=]() {
526  auto cld = rootItem->childItems();
527  for(auto item : cld)
528  {
529  delete item;
530  }
531  recreate();
532  });
533 
534  recreate();
535  return rootItem;
536  }
537 };
538 
539 }
Definition: Port.hpp:203
Definition: Port.hpp:425
Definition: score-lib-process/Process/ProcessFactory.hpp:58
Definition: LayerPresenter.hpp:34
Definition: LayerView.hpp:21
Definition: PortFactory.hpp:74
The Process class.
Definition: score-lib-process/Process/Process.hpp:61
Definition: Painter.hpp:350
Definition: Painter.hpp:194
Definition: score-plugin-avnd/Crousti/Layer.hpp:396
Definition: score-plugin-avnd/Crousti/ProcessModel.hpp:77
Definition: RectItem.hpp:64
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
Definition: RectItem.hpp:12
Definition: Factories.hpp:19
Base toolkit upon which the software is built.
Definition: Application.cpp:90
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:133
Definition: MessageBus.hpp:37
Definition: score-plugin-avnd/Crousti/Layer.hpp:28
Definition: score-plugin-avnd/Crousti/Layer.hpp:41
Definition: score-plugin-avnd/Crousti/Layer.hpp:114