298 using inputs_type =
typename avnd::input_introspection<Info>::type;
299 using outputs_type =
typename avnd::output_introspection<Info>::type;
300 inputs_type temp_inputs{};
301 outputs_type temp_outputs{};
303 typename Info::ui* rootUi{};
311 std::map<std::string, std::shared_ptr<score::LayoutSelection>, std::less<>>
selections;
312 std::map<score::GraphicsTableLayout*, std::shared_ptr<score::LayoutSelection>>
315 std::shared_ptr<score::LayoutSelection> selectionNamed(std::string_view name)
320 auto sel = std::make_shared<score::LayoutSelection>();
322 const QByteArray key =
"layoutSelection:" + QByteArray{name.data(), qsizetype(name.size())};
323 sel->current = context.property(key.constData()).toInt();
324 sel->listeners.push_back(
325 [ctx = &context, key](
int i) { ctx->setProperty(key.constData(), i); });
326 it =
selections.emplace(std::string{name}, std::move(sel)).first;
334 template <
typename Port_T,
typename Item>
338 if constexpr(
requires {
sizeof(Item::value); })
340 using avnd_port_type = pmf_member_type_t<
decltype(item.model)>;
345 if constexpr(
requires { rootUi->on_control_update(); })
348 port, &Port_T::valueChanged, lifetime,
349 [rui = rootUi, layout = this->layout, &item,
351 const ossia::value& v) {
354 rui->on_control_update();
361 port, &Port_T::valueChanged, lifetime,
362 [layout = this->layout, &item,
364 const ossia::value& v) {
370 if constexpr(
requires { item.set = {}; })
379 if constexpr(
requires { Item::dynamic_size; })
383 if(
auto edit = qgraphicsitem_cast<score::QGraphicsLineEdit*>(lay.control))
385 edit, &score::QGraphicsLineEdit::sizeChanged, obj,
386 &score::ResizeableItem::childrenSizeChanged);
389 if constexpr(
requires { Item::width; })
391 if(
auto edit = qgraphicsitem_cast<score::QGraphicsLineEdit*>(lay.control))
392 edit->setTextWidth(Item::width());
398 template <
typename Item>
401 using Condition =
decltype(Item::condition());
402 const int index = avnd::index_in_struct(
403 temp_inputs, recursive_members..., Condition::condition_control);
405 for(
auto* base : model.avnd_input_idx_to_model_ports(index))
407 auto* port = qobject_cast<Process::ControlInlet*>(base);
411 auto matches = [port](
const ossia::value& v) {
413 if(
auto* e = qobject_cast<Process::Enum*>(port))
414 idx = e->indexOfValue(v);
415 else if(
auto* c = qobject_cast<Process::ComboBox*>(port))
416 idx = c->indexOfValue(v);
417 const bool is_choice = qobject_cast<Process::Enum*>(port)
418 || qobject_cast<Process::ComboBox*>(port);
419 for(
auto expected : Condition::condition_values())
421 if(is_choice ? idx == entryIndex(expected)
422 : ossia::convert<double>(v) ==
static_cast<double>(expected))
427 auto apply = [&lay,
matches](
const ossia::value& v) {
429 if constexpr(Condition::condition_hides)
436 lay.setOpacity(on ? 1. : 0.35);
439 apply(port->value());
441 QObject::connect(port, &Process::ControlInlet::valueChanged, &lay, apply);
446 template <
typename Item>
449 auto p = controlPresentation<Item>();
451 p.labelVisible =
false;
455 template <
typename Item>
456 void createControl(Item& item,
auto... member)
458 if constexpr(
requires { ((inputs_type{}).*....*member); })
460 int index = avnd::index_in_struct(temp_inputs, member...);
461 auto& proc =
static_cast<const ProcessModel<Info>&
>(this->proc);
462 auto ports = proc.avnd_input_idx_to_model_ports(index);
465 auto [port, qitem] = makeInlet(p, presentationFor<Item>());
468 SCORE_ASSERT(qitem.container);
470 setupItem(item, *qitem.container);
474 else if constexpr(
requires { ((outputs_type{}).*....*member); })
476 int index = avnd::index_in_struct(temp_outputs, member...);
477 auto& proc =
static_cast<const ProcessModel<Info>&
>(this->proc);
478 auto ports = proc.avnd_output_idx_to_model_ports(index);
481 auto [port, qitem] = makeOutlet(p, presentationFor<Item>());
484 SCORE_ASSERT(qitem.container);
486 setupItem(item, *qitem.container);
492 static_assert(
sizeof...(member) < 0,
"not_a_member_of_inputs_or_outputs");
496 template <
typename Item,
typename T>
497 void createWidget(Item& it,
const T& member)
499 if constexpr(
requires {
500 { member } -> std::convertible_to<std::string_view>;
503 auto res = makeLabel(member);
506 else if constexpr(
requires {
507 { member.text } -> std::convertible_to<std::string_view>;
510 auto res = makeLabel(member.text);
515 createControl(it, member);
519 template <
typename Item,
typename... T>
520 requires(
sizeof...(T) > 1)
521 void createWidget(Item& item, T... recursive_members)
523 createControl(item, recursive_members...);
530 for(
auto* item = this->layout; item && !cell; item = item->parentItem())
543 QStringList extensions;
544 static const QRegularExpression pattern{QStringLiteral(
"\\*\\.([A-Za-z0-9]+)")};
545 for(
auto it = pattern.globalMatch(port.filters()); it.hasNext();)
546 extensions.push_back(it.next().captured(1).toLower());
548 auto file_of = [extensions](
const QMimeData& data) -> QString {
549 for(
const QUrl& url : data.urls())
551 if(!url.isLocalFile())
553 const QString path = url.toLocalFile();
554 if(extensions.isEmpty()
555 || extensions.contains(QFileInfo{path}.suffix().toLower()))
560 cell->setDropHandler(
561 [file_of](
const QMimeData& data) {
return !file_of(data).isEmpty(); },
562 [file_of, &port, &doc = this->doc](
const QMimeData& data) {
563 const QString path = file_of(data);
574 const QFontMetricsF& metrics, qreal max_width = 72.)
576 if(
auto* c = qobject_cast<const Process::ComboBox*>(&port))
578 const int i = c->indexOfValue(v);
579 if(i >= 0 && i < std::ssize(c->alternatives))
580 return c->alternatives[i].first;
582 else if(
auto* e = qobject_cast<const Process::Enum*>(&port))
584 const int i = e->indexOfValue(v);
585 if(i >= 0 && i < std::ssize(e->values))
588 if(
auto str = v.target<std::string>())
590 QString text = QString::fromStdString(*str);
592 if(text.contains(QLatin1Char(
'/')) || text.contains(QLatin1Char(
'\\')))
593 text = QFileInfo{text}.completeBaseName();
594 else if(text.isEmpty())
595 text = QStringLiteral(
"\u2014");
596 return metrics.elidedText(text, Qt::ElideRight, max_width);
598 return QString::number(ossia::convert<double>(v),
'g', 4);
601 template <
typename Item>
602 void createDisplay(Item& item,
auto... member)
604 if constexpr(
requires { ((inputs_type{}).*....*member); })
606 const int index = avnd::index_in_struct(temp_inputs, member...);
607 auto& proc =
static_cast<const ProcessModel<Info>&
>(this->proc);
608 for(
auto* p : proc.avnd_input_idx_to_model_ports(index))
610 auto* port = qobject_cast<Process::ControlInlet*>(p);
613 if(
auto* file = qobject_cast<Process::FileChooserBase*>(port))
616 requires { Item::style; } && Item::style == halp::display_style::bar)
619 auto show = [bar, port](
const ossia::value& v) {
623 QObject::connect(port, &Process::ControlInlet::valueChanged, bar, show);
624 setupItem(item, *bar);
627 requires { Item::style; } && Item::style == halp::display_style::title)
632 auto show = [cell, port, metrics = QFontMetricsF{score::Skin::instance().Medium8Pt}](
633 const ossia::value& v) {
634 const auto str = v.target<std::string>();
636 str && !str->empty() ?
displayText(*port, v, metrics, 1e6) : QString{});
639 QObject::connect(port, &Process::ControlInlet::valueChanged, cell, show);
646 auto show = [text, port, metrics = QFontMetricsF{score::Skin::instance().Medium8Pt}](
647 const ossia::value& v) {
651 QObject::connect(port, &Process::ControlInlet::valueChanged, text, show);
652 setupItem(item, *text);
659 void addControlInlet(std::vector<Process::ControlInlet*>& ports,
auto... member)
661 if constexpr(
requires { ((inputs_type{}).*....*member); })
663 const int index = avnd::index_in_struct(temp_inputs, member...);
664 auto& proc =
static_cast<const ProcessModel<Info>&
>(this->proc);
665 for(
auto* p : proc.avnd_input_idx_to_model_ports(index))
667 if(
auto* port = qobject_cast<Process::ControlInlet*>(p))
669 ports.push_back(port);
676 static_assert(
sizeof...(member) < 0,
"not_a_member_of_inputs");
680 template <
typename Item>
681 void createCustomMultiControl(Item& item,
auto... recursive_members)
683 std::vector<Process::ControlInlet*> ports;
685 [&](
auto... models) { (addControlInlet(ports, recursive_members..., models), ...); },
688 setupItem(item, *qitem);
691 template <
typename Item>
692 void createCustom(Item& item)
694 static_assert(!
requires { item.transaction; });
696 setupItem(item, *res);
699 template <
typename Item>
700 void createCustomControl(Item& item,
auto... member)
702 if constexpr(
requires { ((inputs_type{}).*....*member); })
704 int index = avnd::index_in_struct(temp_inputs, member...);
706 auto& proc =
static_cast<const ProcessModel<Info>&
>(this->proc);
707 auto ports = proc.avnd_input_idx_to_model_ports(index);
710 if(
auto* port = qobject_cast<Process::ControlInlet*>(p))
714 if(
auto* f = portFactory.
get(port->concreteKey()))
716 lay.port_item = f->makePortItem(*port, this->doc, this->layout, &context);
719 setupItem(item, *qitem);
723 else if constexpr(
requires { ((outputs_type{}).*....*member); })
725 int index = avnd::index_in_struct(temp_outputs, member...);
727 auto& proc =
static_cast<const ProcessModel<Info>&
>(this->proc);
728 auto ports = proc.avnd_output_idx_to_model_ports(index);
731 if(
auto* port = qobject_cast<Process::ControlOutlet*>(p))
735 if(
auto* f = portFactory.
get(port->concreteKey()))
737 lay.port_item = f->makePortItem(*port, this->doc, this->layout, &context);
740 setupItem(item, *qitem);
746 static_assert(
sizeof...(member) < 0,
"not_a_member_of_inputs_or_outputs");
750 template <
typename Item>
754 setupLayout(item, *new_l);
755 setupItem(item, *new_l);
757 createdLayouts.push_back(new_l);
762 if constexpr(
requires { Item::name(); })
764 makeLabel(Item::name())->setParentItem(new_l);
765 table->setRowTitles(
true);
769 auto it = tableSelections.find(table);
770 if(it != tableSelections.end())
774 for(
auto* child : table->childItems())
775 if(dynamic_cast<
score::GraphicsSelectableRow*>(child))
777 auto sel = it->second;
778 row->onActivated = [sel, index] { sel->select(index); };
779 sel->listen([row, index](
int i) { row->setSelected(i == index); });
783 if constexpr(avnd::table_layout<Item> &&
requires { Item::columns(); })
786 if constexpr(
requires { Item::condition(); })
787 setupCondition<Item>(*new_l, recursive_members...);
789 if constexpr(avnd::table_layout<Item>)
791 forEachMember(item, [](
auto& row) {
793 avnd::hbox_layout<std::remove_cvref_t<
decltype(row)>>,
794 "The members of a halp table are its rows: structs with "
795 "halp_meta(layout, halp::layouts::hbox)");
800 if constexpr(avnd::table_layout<Item> &&
requires { Item::selection(); })
804 tableSelections[table] = selectionNamed(Item::selection());
807 if constexpr(avnd::tab_layout<Item> &&
requires { Item::model; })
811 = avnd::index_in_struct(temp_inputs, recursive_members..., Item::model);
815 std::vector<int> page_of;
818 forEachMember(item, [&](
auto& p) {
819 using page_type = std::remove_cvref_t<
decltype(p)>;
820 if constexpr(
requires { page_type::when(); })
822 for(
auto v : page_type::when())
824 const int i = entryIndex(v);
827 if(i >= std::ssize(page_of))
828 page_of.resize(i + 1, -1);
835 auto to_page = [page_of](
int i) {
836 return (i >= 0 && i < std::ssize(page_of) && page_of[i] >= 0) ? page_of[i] : i;
838 auto to_value_index = [page_of](
int page) {
839 for(
int i = 0; i < std::ssize(page_of); i++)
840 if(page_of[i] == page)
845 auto& model =
static_cast<const ProcessModel<Info>&
>(this->proc);
846 for(
auto* base : model.avnd_input_idx_to_model_ports(index))
850 auto select = [tabs, port, to_page](
const ossia::value& value) {
851 if(
const int i = port->indexOfValue(value); i >= 0)
852 tabs->setCurrentIndex(to_page(i));
854 select(port->value());
856 port, &Process::ControlInlet::valueChanged,
858 if constexpr(!avnd::tag_hide_tabs<Item>)
859 tabs->onCurrentIndexChanged
860 = [port, to_value_index, &doc = this->doc](
int i) {
861 const ossia::value value = port->valueAtIndex(to_value_index(i));
862 if(value.valid() && port->value() != value)
870 auto select = [tabs, port, to_page](
const ossia::value& value) {
871 if(
const int i = port->indexOfValue(value); i >= 0)
872 tabs->setCurrentIndex(to_page(i));
874 select(port->value());
876 port, &Process::ControlInlet::valueChanged,
878 if constexpr(!avnd::tag_hide_tabs<Item>)
879 tabs->onCurrentIndexChanged
880 = [port, to_value_index, &doc = this->doc](
int i) {
881 const ossia::value value = port->valueAtIndex(to_value_index(i));
882 if(value.valid() && port->value() != value)
891 if constexpr(avnd::strip_detail_layout<Item>)
894 createdLayouts.push_back(&strip.strip());
896 auto page = [&](
auto& p) {
898 using page_type = std::remove_cvref_t<
decltype(p)>;
899 if constexpr(
requires { page_type::name(); })
901 const std::string_view name = page_type::name();
902 cell->setTitle(QString::fromUtf8(name.data(), qsizetype(name.size())));
905 createdLayouts.push_back(cell);
907 if constexpr(
requires { p.summary; })
910 walkMembers(p.summary, recursive_members..., p.group);
913 this->walkLayout(p, recursive_members...);
915 forEachMember(item, page);
918 if constexpr(
requires { Item::selection(); })
920 auto sel = selectionNamed(Item::selection());
921 sel->listen([&strip](
int i) { strip.setCurrentIndex(i); });
922 strip.onCurrentIndexChanged = [sel](
int i) { sel->select(i); };
923 if constexpr(avnd::tag_hide_tabs<Item>)
924 strip.setStripVisible(
false);
929 walkMembers(item, recursive_members...);
932 if constexpr(avnd::table_layout<Item> &&
requires { Item::columns(); })
937 template <
typename Item,
typename F>
938 static void forEachMember(Item& item, F&& f)
940#if AVND_USE_BOOST_PFR
941 static constexpr int N = avnd::pfr::tuple_size_v<Item>;
942 auto t = avnd::pfr::detail::tie_as_tuple(item);
943 [&]<std::size_t... I>(std::index_sequence<I...>) {
945 using namespace avnd::pfr;
947 }(std::make_index_sequence<N>{});
949 auto&& [... members] = item;
954 template <
typename Item>
955 void walkMembers(Item& item,
auto... recursive_members)
957 forEachMember(item, [&](
auto& m) { this->walkLayout(m, recursive_members...); });
960 template <
typename Item>
961 auto initRecursiveLayout()
963 if constexpr(avnd::hbox_layout<Item>)
967 table && tableSelections.contains(table))
970 setupRecursiveLayout<Item>(row);
974 auto new_l = createRecursiveLayout<Item>(this->layout);
975 setupRecursiveLayout<Item>(new_l);
979 template <
typename Item>
980 void walkLayout(Item& item,
auto... recursive_members)
982 if constexpr(avnd::spacing_layout<Item>)
986 double w = 1., h = 1.;
987 if constexpr(
requires { Item::width(); })
989 else if constexpr(
requires { item.width; })
991 if constexpr(
requires { Item::height(); })
993 else if constexpr(
requires { item.height; })
995 widg->setRect({0, 0, w, h});
997 else if constexpr(recursive_container_layout<Item>)
999 subLayout(item, initRecursiveLayout<Item>(), recursive_members...);
1001 else if constexpr(avnd::control_layout<Item> &&
requires { Item::display_only; })
1004 createDisplay(item, recursive_members..., item.model);
1006 else if constexpr(avnd::control_layout<Item>)
1010 createWidget(item, recursive_members..., item.model);
1012 else if constexpr(avnd::custom_multi_control_layout<Item>)
1015 createCustomMultiControl(item, recursive_members...);
1017 else if constexpr(avnd::custom_control_layout<Item>)
1021 createCustomControl(item, recursive_members..., item.model);
1023 else if constexpr(avnd::custom_layout<Item>)
1029 else if constexpr(avnd::recursive_group_layout<Item>)
1031 walkLayout(item.ui, recursive_members..., item.group);
1033 else if constexpr(avnd::dynamic_controls<Item>)
1035 walkLayout(item.ui, recursive_members..., item.group);
1037 else if constexpr(avnd::has_layout<Item>)
1040 subLayout(item, initRecursiveLayout<Item>(), recursive_members...);
1045 createWidget(item, item);
1057 std::optional<double> recommendedHeight()
const noexcept override
1059 if constexpr(
requires { (double)Info::layout::height(); })
1061 return Info::layout::height();
1063 return Process::LayerFactory::recommendedHeight();
1078 QGraphicsItem* parent)
const final override
1090 template <
typename Item>
1094 if constexpr(avnd::has_gui_to_processor_bus<Info>)
1100 if constexpr(avnd::has_processor_to_gui_bus<Info>)
1103 proc.to_ui = [ptr = QPointer{ptr}](QByteArray mess) {
1108 if constexpr(
requires { ptr->bus.process_message(); })
1110 ptr->bus.process_message();
1112 else if constexpr(
requires { ptr->bus.process_message(ptr->ui); })
1114 ptr->bus.process_message(ptr->ui);
1116 else if constexpr(
requires { ptr->bus.process_message(ptr->ui, {}); })
1118 std::decay_t<avnd::second_argument<&Info::ui::bus::process_message>> arg;
1121 ptr->bus.process_message(ptr->ui, std::move(arg));
1125 ptr->bus.process_message(ptr->ui, {});
1130 if_possible(ptr->bus.init(ptr->ui));
1135 using ui_type =
typename Info::ui;
1136 using root_layout_type
1137 = std::remove_cvref_t<decltype(*createRecursiveLayout<ui_type>(
nullptr))>;
1140 setupRecursiveLayout<ui_type>(new_l);
1141 if constexpr(
requires {
sizeof(
typename Info::ui::bus); })
1142 init_bus(proc, *new_l);
1144 if constexpr(
requires { new_l->ui.start(); })
1146 QObject::connect(&proc, &Process::ProcessModel::startExecution, new_l, [new_l] {
1150 if constexpr(
requires { new_l->ui.stop(); })
1152 QObject::connect(&proc, &Process::ProcessModel::stopExecution, new_l, [new_l] {
1156 if constexpr(
requires { new_l->ui.reset(); })
1158 QObject::connect(&proc, &Process::ProcessModel::resetExecution, new_l, [new_l] {
1167 QGraphicsItem* parent)
const final override
1169 using namespace score;
1170 auto& process =
static_cast<const ProcessModel<Info>&
>(proc);
1172 auto rootItem = makeItemImpl(
const_cast<ProcessModel<Info>&
>(process), parent);
1174 auto recreate = [parent, &proc, &ctx, rootItem] {
1175 delete rootItem->uiConnections;
1176 rootItem->uiConnections =
new QObject{rootItem};
1177 LayoutBuilder<Info> b{
1180 proc.inlets(), proc.outlets(),
1182 b.rootUi = &rootItem->ui;
1183 b.connections = rootItem->uiConnections;
1186 b.marginOnNestedLayouts =
false;
1188 b.subLayout(rootItem->ui, rootItem);
1190 b.finalizeLayout(rootItem);
1192 rootItem->fitChildrenRect();
1194 if_possible(b.rootUi->on_control_update());
1199 auto rebuild = [rootItem, recreate] {
1200 delete rootItem->uiConnections;
1201 rootItem->uiConnections =
nullptr;
1202 auto cld = rootItem->childItems();
1203 for(
auto item : cld)
1209 QObject::connect(&proc, &Process::ProcessModel::inletsChanged, rootItem, rebuild);
1210 QObject::connect(&proc, &Process::ProcessModel::outletsChanged, rootItem, rebuild);