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 
15 #include <avnd/concepts/layout.hpp>
16 
17 namespace oscr
18 {
19 
20 template <typename Info>
22 {
23  using inputs_type = typename avnd::input_introspection<Info>::type;
24  using outputs_type = typename avnd::output_introspection<Info>::type;
25  inputs_type temp_inputs;
26  outputs_type temp_outputs;
27 
28  typename Info::ui* rootUi{};
29 
30  template <typename Item>
31  void setupControl(Process::ControlInlet* inl, Item& item)
32  {
33  if constexpr(requires { sizeof(Item::value); })
34  {
35  oscr::from_ossia_value(inl->value(), item.value);
36  if constexpr(requires { rootUi->on_control_update(); })
37  {
38  QObject::connect(
39  inl, &Process::ControlInlet::valueChanged, &context,
40  [rui = rootUi, layout = this->layout, &item](const ossia::value& v) {
41  oscr::from_ossia_value(v, item.value);
42 
43  rui->on_control_update();
44  layout->update();
45  });
46  }
47  else
48  {
49  QObject::connect(
50  inl, &Process::ControlInlet::valueChanged, &context,
51  [layout = this->layout, &item](const ossia::value& v) {
52  oscr::from_ossia_value(v, item.value);
53  layout->update();
54  });
55  }
56  }
57  }
58  template <typename Item>
59  QGraphicsItem* createControl(Item& item, auto... member)
60  {
61  if constexpr(requires { ((inputs_type{}).*....*member); })
62  {
63  int index = avnd::index_in_struct(temp_inputs, member...);
64  auto [port, qitem] = makeInlet(index);
65  setupControl(port, item);
66  return qitem;
67  }
68  else if constexpr(requires { ((outputs_type{}).*....*member); })
69  {
70  int index = avnd::index_in_struct(temp_outputs, member...);
71  auto [port, qitem] = makeOutlet(index);
72  return qitem;
73  }
74  else
75  {
76  static_assert(sizeof...(member) < 0, "not_a_member_of_inputs_or_outputs");
77  }
78 
79  return nullptr;
80  }
81 
82  template <typename Item, typename T>
83  QGraphicsItem* createWidget(Item& it, const T& member)
84  {
85  if constexpr(requires {
86  {
87  member
88  } -> std::convertible_to<std::string_view>;
89  })
90  {
91  return makeLabel(member);
92  }
93  else if constexpr(requires {
94  {
95  member.text
96  } -> std::convertible_to<std::string_view>;
97  })
98  {
99  return makeLabel(member.text);
100  }
101  else
102  {
103  return createControl(it, member);
104  }
105  }
106 
107  template <typename Item, typename... T>
108  requires(sizeof...(T) > 1)
109  QGraphicsItem* createWidget(Item& item, T... recursive_members)
110  {
111  return createControl(item, recursive_members...);
112  }
113 
114  template <typename Item>
115  QGraphicsItem* createCustom(Item& item)
116  {
117  static_assert(!requires { item.transaction; });
118  return new oscr::CustomItem<Item&>{item};
119  }
120 
121  template <typename Item>
122  QGraphicsItem* createCustomControl(Item& item, auto... member)
123  {
124  if constexpr(requires { ((inputs_type{}).*....*member); })
125  {
126  int index = avnd::index_in_struct(temp_inputs, member...);
127 
128  if(auto* port = qobject_cast<Process::ControlInlet*>(inlets[index]))
129  {
130  auto qitem = new oscr::CustomControl<Item&>{item, *port, this->doc};
131  setupControl(port, item);
132  return qitem;
133  }
134  return nullptr;
135  }
136  else if constexpr(requires { ((outputs_type{}).*....*member); })
137  {
138  int index = avnd::index_in_struct(temp_outputs, member...);
139 
140  if(auto* port = qobject_cast<Process::ControlOutlet*>(outlets[index]))
141  {
142  auto qitem = new oscr::CustomControl<Item&>{item};
143  setupControl(port, item);
144  return qitem;
145  }
146  return nullptr;
147  }
148  else
149  {
150  static_assert(sizeof...(member) < 0, "not_a_member_of_inputs_or_outputs");
151  }
152 
153  return nullptr;
154  }
155 
156  /*
157  template<int N>
158  constexpr void recurse(auto item)
159  {
160  using namespace boost::pfr;
161  using namespace boost::pfr::detail;
162  auto t = boost::pfr::detail::tie_as_tuple(item, size_t_<N>{});
163  [&]<std::size_t... I>(std::index_sequence<I...>)
164  { (this->walkLayout(sequence_tuple::get<I>(t)), ...); }
165  (make_index_sequence<N>{});
166  }
167  */
168 
169  template <typename Item>
170  void subLayout(Item& item, score::GraphicsLayout* new_l, auto... recursive_members)
171  {
172  auto old_l = layout;
173  setupLayout(item, *new_l);
174  setupItem(item, *new_l);
175  layout = new_l;
176  createdLayouts.push_back(new_l);
177 
178  {
179  using namespace boost::pfr;
180  using namespace boost::pfr::detail;
181  constexpr int N = boost::pfr::tuple_size_v<Item>;
182  auto t = boost::pfr::detail::tie_as_tuple(item, size_t_<N>{});
183  [&]<std::size_t... I>(std::index_sequence<I...>)
184  {
185  (this->walkLayout(sequence_tuple::get<I>(t), recursive_members...), ...);
186  }
187  (make_index_sequence<N>{});
188  }
189 
190  layout = old_l;
191  }
192 
193  template <typename Item>
194  void walkLayout(Item& item, auto... recursive_members)
195  {
196  if constexpr(avnd::spacing_layout<Item>)
197  {
198  auto widg = new score::EmptyRectItem{layout};
199  double w = 1., h = 1.;
200  if constexpr(requires { Item::width(); })
201  w = Item::width();
202  if constexpr(requires { Item::height(); })
203  h = Item::height();
204  widg->setRect({0, 0, w, h});
205  }
206  else if constexpr(avnd::container_layout<Item>)
207  {
208  subLayout(item, new score::GraphicsLayout{layout}, recursive_members...);
209  }
210  else if constexpr(avnd::hbox_layout<Item> || avnd::group_layout<Item>)
211  {
212  subLayout(item, new score::GraphicsHBoxLayout{layout}, recursive_members...);
213  }
214  else if constexpr(avnd::vbox_layout<Item>)
215  {
216  subLayout(item, new score::GraphicsVBoxLayout{layout}, recursive_members...);
217  }
218  else if constexpr(avnd::split_layout<Item>)
219  {
220  subLayout(item, new score::GraphicsSplitLayout{layout}, recursive_members...);
221  }
222  else if constexpr(avnd::grid_layout<Item>)
223  {
224  if constexpr(requires { Item::columns(); })
225  {
226  auto new_l = new score::GraphicsGridColumnsLayout{layout};
227  new_l->setColumns(Item::columns());
228  subLayout(item, new_l, recursive_members...);
229  }
230  else if constexpr(requires { Item::rows(); })
231  {
232  auto new_l = new score::GraphicsGridRowsLayout{layout};
233  new_l->setRows(Item::rows());
234  subLayout(item, new_l, recursive_members...);
235  }
236  }
237  else if constexpr(avnd::tab_layout<Item>)
238  {
239  auto new_l = new score::GraphicsTabLayout{layout};
240  avnd::for_each_field_ref(
241  item, [&]<typename F>(F field) { new_l->addTab(F::name()); });
242 
243  subLayout(item, new_l, recursive_members...);
244  }
245  else if constexpr(avnd::control_layout<Item>)
246  {
247  // Widget with some metadata.. FIXME
248  if(auto widg = createWidget(item, recursive_members..., item.model))
249  setupItem(item, *widg);
250  }
251  else if constexpr(avnd::custom_control_layout<Item>)
252  {
253  // Widget with some metadata.. FIXME
254  if(auto widg = createCustomControl(item, recursive_members..., item.model))
255  setupItem(item, *widg);
256  }
257  else if constexpr(avnd::custom_layout<Item>)
258  {
259  // Widget with some metadata.. FIXME
260  if(auto widg = createCustom(item))
261  setupItem(item, *widg);
262  }
263  else if constexpr(avnd::recursive_group_layout<Item>)
264  {
265  walkLayout(item.ui, recursive_members..., item.group);
266  }
267  else if constexpr(avnd::has_layout<Item>)
268  {
269  // Treat it like group
270  subLayout(item, new score::GraphicsLayout{layout}, recursive_members...);
271  }
272  else
273  {
274  // Normal widget, e.g. just a const char*
275  if(auto widg = createWidget(item, item))
276  setupItem(item, *widg);
277  }
278  }
279 };
280 
281 template <typename Info>
283 {
284 public:
285  virtual ~LayerFactory() { }
286 
287 private:
288  std::optional<double> recommendedHeight() const noexcept override
289  {
290  if constexpr(requires { (double)Info::layout::height(); })
291  {
292  return Info::layout::height();
293  }
294  return Process::LayerFactory::recommendedHeight();
295  }
296 
297  UuidKey<Process::ProcessModel> concreteKey() const noexcept override
298  {
300  }
301 
302  bool matches(const UuidKey<Process::ProcessModel>& p) const override
303  {
305  }
306 
307  Process::LayerView* makeLayerView(
308  const Process::ProcessModel& proc, const Process::Context& context,
309  QGraphicsItem* parent) const final override
310  {
311  return nullptr;
312  }
313 
314  Process::LayerPresenter* makeLayerPresenter(
316  const Process::Context& context, QObject* parent) const final override
317  {
318  return nullptr;
319  }
320 
321  auto makeItemImpl(ProcessModel<Info>& proc, QGraphicsItem* parent) const noexcept
322  {
323  // Initialize if needed
324  if constexpr(requires { sizeof(typename Info::ui::bus); })
325  {
326  struct Item : score::EmptyRectItem
327  {
328  using score::EmptyRectItem::EmptyRectItem;
329  typename Info::ui ui;
330  typename Info::ui::bus bus;
331  };
332  auto ptr = new Item{parent};
333 
334  if constexpr(avnd::has_gui_to_processor_bus<Info>)
335  {
336  // ui -> engine
337  ptr->bus.send_message = MessageBusSender{proc.from_ui};
338  }
339 
340  if constexpr(avnd::has_processor_to_gui_bus<Info>)
341  {
342  // engine -> ui
343  proc.to_ui = [ptr = QPointer{ptr}](QByteArray mess) {
344  // FIXME this is not enough as the message may be sent from another thread?
345  if(!ptr)
346  return;
347 
348  if constexpr(requires { ptr->bus.process_message(); })
349  {
350  ptr->bus.process_message();
351  }
352  else if constexpr(requires { ptr->bus.process_message(ptr->ui); })
353  {
354  ptr->bus.process_message(ptr->ui);
355  }
356  else if constexpr(requires { ptr->bus.process_message(ptr->ui, {}); })
357  {
358  std::decay_t<avnd::second_argument<&Info::ui::bus::process_message>> arg;
359  MessageBusReader b{mess};
360  b(arg);
361  ptr->bus.process_message(ptr->ui, std::move(arg));
362  }
363  else
364  {
365  ptr->bus.process_message(ptr->ui, {});
366  }
367  };
368  }
369 
370  if_possible(ptr->bus.init(ptr->ui));
371  return ptr;
372  }
373  else
374  {
375  struct Item : score::EmptyRectItem
376  {
377  using score::EmptyRectItem::EmptyRectItem;
378  typename Info::ui ui;
379  };
380  return new Item{parent};
381  }
382  }
383 
384  score::ResizeableItem* makeItem(
385  const Process::ProcessModel& proc, const Process::Context& ctx,
386  QGraphicsItem* parent) const final override
387  {
388  using namespace score;
389 
390  auto rootItem = makeItemImpl(
391  const_cast<ProcessModel<Info>&>(static_cast<const ProcessModel<Info>&>(proc)),
392  parent);
393 
395  *rootItem, ctx, ctx.app.interfaces<Process::PortFactoryList>(), proc.inlets(),
396  proc.outlets()};
397  b.rootUi = &rootItem->ui;
398 
399  b.walkLayout(rootItem->ui);
400 
401  b.finalizeLayout(rootItem);
402 
403  rootItem->fitChildrenRect();
404 
405  if_possible(b.rootUi->on_control_update());
406 
407  return rootItem;
408  }
409 };
410 
411 }
Definition: score-lib-process/Process/Dataflow/Port.hpp:202
Definition: score-lib-process/Process/ProcessFactory.hpp:58
Definition: LayerPresenter.hpp:34
Definition: LayerView.hpp:21
Definition: PortFactory.hpp:65
The Process class.
Definition: score-lib-process/Process/Process.hpp:61
Definition: Painter.hpp:345
Definition: Painter.hpp:189
Definition: score-plugin-avnd/Crousti/Layer.hpp:283
Definition: score-plugin-avnd/Crousti/ProcessModel.hpp:551
Definition: RectItem.hpp:64
Definition: GraphicsGridLayout.hpp:9
Definition: GraphicsGridLayout.hpp:23
Definition: GraphicsBoxLayout.hpp:9
Definition: GraphicsLayout.hpp:10
Definition: GraphicsSplitLayout.hpp:9
Definition: GraphicsTabLayout.hpp:11
Definition: GraphicsBoxLayout.hpp:18
Definition: RectItem.hpp:12
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: plugins/score-lib-process/Control/Layout.hpp:11
Definition: ObjectMatches.hpp:6
Definition: score-plugin-avnd/Crousti/Layer.hpp:22
Definition: MessageBus.hpp:120
Definition: MessageBus.hpp:37
const T & interfaces() const
Access to a specific interface list.
Definition: ApplicationContext.hpp:67