392 std::vector<DeviceInfo>
const& infos,
393 std::vector<DeviceSpec>
const& specs,
394 std::vector<DataProcessingStates>
const& allStates,
395 std::vector<DataProcessorInfo>
const& metadata,
396 std::vector<DeviceControl>& controls,
397 std::vector<DeviceMetricsInfo>
const& metricsInfos)
399 ImGui::SetNextWindowPos(ImVec2(0, 0), 0);
400 if (
state.bottomPaneVisible) {
401 ImGui::SetNextWindowSize(ImVec2(ImGui::GetIO().DisplaySize.x, ImGui::GetIO().DisplaySize.y -
state.bottomPaneSize), 0);
403 ImGui::SetNextWindowSize(ImVec2(ImGui::GetIO().DisplaySize.x, ImGui::GetIO().DisplaySize.y), 0);
406 ImGui::Begin(
"Physical topology view",
nullptr, ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize);
408 static ImVector<Node>
nodes;
409 static ImVector<Group>
groups;
410 static ImVector<NodeLink> links;
411 static ImVector<NodePos> positions;
413 static bool inited =
false;
414 static ImVec2 scrolling = ImVec2(0.0f, 0.0f);
415 static bool show_grid =
true;
416 static bool show_legend =
true;
417 static int node_selected = -1;
419 auto prepareChannelView = [&specs, &metricsInfos, &metadata](ImVector<Node>& nodeList, ImVector<Group>& groupList) {
424 std::map<std::string, LinkInfo> linkToIndex;
425 for (
int si = 0; si < specs.size(); ++si) {
427 for (
auto&&
output : specs[si].outputChannels) {
428 linkToIndex.insert(std::make_pair(
output.name, LinkInfo{si, oi}));
433 std::string workflow =
"Ungrouped";
435 for (
size_t mi = 0; mi < metadata.size(); ++mi) {
436 auto const& metadatum = metadata[mi];
437 if (metadatum.executable == workflow) {
440 workflow = metadatum.executable;
441 char* groupBasename = strrchr(workflow.data(),
'/');
442 char const* groupName = groupBasename ? groupBasename + 1 : workflow.data();
443 bool hasDuplicate =
false;
444 for (
size_t gi = 0; gi < groupList.Size; ++gi) {
450 if (hasDuplicate ==
false) {
451 groupList.push_back(
Group(groupId++, groupName, mi));
455 for (
int si = 0; si < specs.size(); ++si) {
456 auto& spec = specs[si];
459 auto metadatum = std::find_if(metadata.begin(), metadata.end(),
462 for (
size_t gi = 0; gi < groupList.Size; ++gi) {
463 if (metadatum == metadata.end()) {
466 const char* groupName = strrchr(metadatum->executable.data(),
'/');
467 if (strncmp(groupList[gi].
name, groupName ? groupName + 1 : metadatum->executable.data(), 127) == 0) {
472 nodeList.push_back(
Node(si, groupId, spec.id.c_str(), 0.5f,
473 ImColor(255, 100, 100),
474 spec.inputChannels.size(),
475 spec.outputChannels.size()));
477 for (
auto& input : spec.inputChannels) {
478 auto const& outName = input.name;
479 auto const& out = linkToIndex.find(input.name);
480 if (out == linkToIndex.end()) {
481 LOG(error) <<
"Could not find suitable node for " << outName;
484 links.push_back(
NodeLink{out->second.specId, out->second.outputId, si, ii});
491 std::vector<TopoIndexInfo> sortedNodes = {{0, 0}};
499 for (
auto di = 0;
di < specs.size(); ++
di) {
500 auto fn = std::find_if(sortedNodes.begin(), sortedNodes.end(), [
di](
TopoIndexInfo const& info) {
501 return di == info.index;
503 if (fn == sortedNodes.end()) {
504 sortedNodes.push_back({(
int)
di, 0});
507 assert(specs.size() == sortedNodes.size());
509 std::sort(sortedNodes.begin(), sortedNodes.end());
511 std::vector<int> layerEntries(1024, 0);
512 std::vector<int> layerMax(1024, 0);
513 for (
auto&
node : sortedNodes) {
514 layerMax[
node.layer < 1023 ?
node.layer : 1023] += 1;
519 for (
int si = 0; si < specs.size(); ++si) {
520 auto&
node = sortedNodes[si];
521 assert(
node.index == si);
522 int xpos = 40 + 240 *
node.layer;
523 int ypos = 300 + (std::max(600, 60 * (
int)layerMax[
node.layer]) / (layerMax[
node.layer] + 1)) * (layerEntries[
node.layer] - layerMax[
node.layer] / 2);
524 positions.push_back(
NodePos{ImVec2(xpos, ypos)});
525 layerEntries[
node.layer] += 1;
536 ImGui::Checkbox(
"Show grid", &show_grid);
538 ImGui::Checkbox(
"Show legend", &show_legend);
540 ImGui::Checkbox(
"Light mode", &
state.topologyLightMode);
542 if (ImGui::Button(
"Center")) {
543 scrolling = ImVec2(0., 0.);
546 if (
state.leftPaneVisible ==
false && ImGui::Button(
"Show tree")) {
547 state.leftPaneVisible =
true;
549 if (
state.leftPaneVisible ==
true && ImGui::Button(
"Hide tree")) {
550 state.leftPaneVisible =
false;
553 if (
state.bottomPaneVisible ==
false && ImGui::Button(
"Show metrics")) {
554 state.bottomPaneVisible =
true;
556 if (
state.bottomPaneVisible ==
true && ImGui::Button(
"Hide metrics")) {
557 state.bottomPaneVisible =
false;
560 if (
state.rightPaneVisible ==
false && ImGui::Button(
"Show inspector")) {
561 state.rightPaneVisible =
true;
563 if (
state.rightPaneVisible ==
true && ImGui::Button(
"Hide inspector")) {
564 state.rightPaneVisible =
false;
568 auto toolbarSize = ImGui::GetItemRectSize();
570 bool open_context_menu =
false;
571 int node_hovered_in_list = -1;
572 int node_hovered_in_scene = -1;
573 if (
state.leftPaneVisible) {
574 ImGui::BeginChild(
"node_list", ImVec2(
state.leftPaneSize, 0));
575 ImGui::Text(
"Workflows %d",
groups.Size);
577 for (
int groupId = 0; groupId <
groups.Size; groupId++) {
579 if (ImGui::TreeNodeEx(
group->name, ImGuiTreeNodeFlags_DefaultOpen)) {
580 for (
int node_idx = 0; node_idx <
nodes.Size; node_idx++) {
582 if (
node->GroupID != groupId) {
586 ImGui::PushID(
node->ID);
587 if (ImGui::Selectable(
node->Name,
node->ID == node_selected)) {
588 if (ImGui::IsMouseDoubleClicked(0)) {
589 controls[node_selected].logVisible =
true;
591 node_selected =
node->ID;
593 if (ImGui::IsItemHovered()) {
594 node_hovered_in_list =
node->ID;
595 open_context_menu |= ImGui::IsMouseClicked(1);
609 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(1, 1));
610 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
611 auto canvasBg =
state.topologyLightMode ? (ImU32)ImColor(250, 250, 252, 255) : (ImU32)ImColor(44, 44, 46, 255);
612 auto canvasText = (ImU32)ImColor(235, 235, 245, 255);
613 ImGui::PushStyleColor(ImGuiCol_ChildBg, canvasBg);
614 ImGui::PushStyleColor(ImGuiCol_Text, canvasText);
615 ImVec2 graphSize = ImGui::GetWindowSize();
616 if (
state.leftPaneVisible) {
617 graphSize.x -=
state.leftPaneSize;
619 if (
state.rightPaneVisible) {
620 graphSize.x -=
state.rightPaneSize;
622 graphSize.y -= toolbarSize.y + 20;
623 ImGui::BeginChild(
"scrolling_region", graphSize,
true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollWithMouse);
624 ImGui::PushItemWidth(graphSize.x);
626 ImVec2
offset = ImGui::GetCursorScreenPos() - scrolling;
627 ImDrawList* draw_list = ImGui::GetWindowDrawList();
630 draw_list->ChannelsSplit((
nodes.Size + 2) * 2);
635 ImVec2 win_pos = ImGui::GetCursorScreenPos();
636 ImVec2 canvas_sz = ImGui::GetWindowSize();
639 const ImColor arrowBgColor =
state.topologyLightMode ? ImColor(140, 80, 0) : ARROW_BACKGROUND_COLOR;
640 const ImColor arrowHalfColor =
state.topologyLightMode ? ImColor(180, 110, 0) : ARROW_HALFGROUND_COLOR;
641 const ImColor arrowColor =
state.topologyLightMode ? ImColor(220, 140, 0) : ARROW_COLOR;
644 for (
int link_idx = 0; link_idx < links.Size; link_idx++) {
650 if ((
p1.x > win_pos.x + canvas_sz.x + 50) && (
p2.x > win_pos.x + canvas_sz.x + 50)) {
654 if ((
p1.y > win_pos.y + canvas_sz.y + 50) && (
p2.y > win_pos.y + canvas_sz.y + 50)) {
658 if ((
p1.y < win_pos.y) && (
p2.y < win_pos.y)) {
662 if ((
p1.x < win_pos.x) && (
p2.x < win_pos.x)) {
665 draw_list->ChannelsSetCurrent(0);
666 auto color = arrowBgColor;
667 auto thickness = ARROW_BACKGROUND_THICKNESS;
669 bool p1Inside =
false;
670 bool p2Inside =
false;
671 if ((
p1.x > win_pos.x) && (
p1.x < (win_pos.x + canvas_sz.x)) && (
p1.y < (win_pos.y + canvas_sz.y)) && (
p1.y > win_pos.y)) {
675 if ((
p2.x > win_pos.x) && (
p2.x < (win_pos.x + canvas_sz.x)) && (
p2.y < (win_pos.y + canvas_sz.y)) && (
p2.y > win_pos.y)) {
679 if (p1Inside && p2Inside) {
681 draw_list->ChannelsSetCurrent(2);
683 thickness = ARROW_THICKNESS;
684 }
else if (p1Inside || p2Inside) {
685 draw_list->ChannelsSetCurrent(1);
687 color = arrowHalfColor;
688 thickness = ARROW_HALFGROUND_THICKNESS;
692 if (
link.InputIdx == node_selected ||
link.OutputIdx == node_selected) {
693 auto foregroundLayer = (
nodes.Size + 1) * 2 + 1;
694 draw_list->ChannelsSetCurrent(foregroundLayer);
695 color = ARROW_SELECTED_COLOR;
696 thickness = thickness + 2;
699 draw_list->AddBezierCurve(
p1,
p1 + ImVec2(+50, 0),
p2 + ImVec2(-50, 0),
p2,
color, thickness);
702 auto fgDrawList = ImGui::GetForegroundDrawList();
704 for (
int node_idx = 0; node_idx <
nodes.Size; node_idx++) {
705 auto backgroundLayer = (node_idx + 1) * 2;
706 auto foregroundLayer = (node_idx + 1) * 2 + 1;
708 if (node_selected == node_idx) {
709 backgroundLayer = (
nodes.Size + 1) * 2;
710 foregroundLayer = (
nodes.Size + 1) * 2 + 1;
716 ImVec2 node_rect_min =
offset +
pos->pos;
719 if ((node_rect_min.x > ImGui::GetCursorScreenPos().x + ImGui::GetWindowSize().x + 50) ||
720 (node_rect_min.y > ImGui::GetCursorScreenPos().y + ImGui::GetWindowSize().y + 50)) {
724 ImGui::PushID(
node->ID);
727 draw_list->ChannelsSetCurrent(foregroundLayer);
728 bool old_any_active = ImGui::IsAnyItemActive();
731 ImGui::TextUnformatted(
node->Name);
732 switch (info.maxLogLevel) {
733 case LogLevel::Critical:
735 ImGui::TextColored(ERROR_MESSAGE_COLOR,
"%s", ICON_FA_EXCLAMATION_CIRCLE);
737 case LogLevel::Error:
739 ImGui::TextColored(ERROR_MESSAGE_COLOR,
"%s", ICON_FA_EXCLAMATION_CIRCLE);
741 case LogLevel::Alarm:
743 ImGui::TextColored(WARNING_MESSAGE_COLOR,
"%s", ICON_FA_EXCLAMATION_TRIANGLE);
745 case LogLevel::Warning:
747 ImGui::TextColored(WARNING_MESSAGE_COLOR,
"%s", ICON_FA_EXCLAMATION_TRIANGLE);
757 bool node_widgets_active = (!old_any_active && ImGui::IsAnyItemActive());
758 float attemptX = std::max(ImGui::GetItemRectSize().
x, 150.f);
759 float attemptY = std::min(ImGui::GetItemRectSize().
y, 128.f);
761 ImVec2 node_rect_max = node_rect_min +
node->Size;
762 ImVec2 node_rect_title = node_rect_min + ImVec2(
node->Size.x, 24);
774 draw_list->ChannelsSetCurrent(backgroundLayer);
775 ImGui::SetCursorScreenPos(node_rect_min);
776 ImGui::InvisibleButton(
"node",
node->Size);
777 if (ImGui::IsItemHovered()) {
778 node_hovered_in_scene =
node->ID;
779 open_context_menu |= ImGui::IsMouseClicked(1);
780 if (ImGui::IsMouseDoubleClicked(0)) {
781 controls[
node->ID].logVisible =
true;
784 bool node_moving_active = ImGui::IsItemActive();
785 if (node_widgets_active || node_moving_active) {
786 node_selected =
node->ID;
788 if (node_moving_active && ImGui::IsMouseDragging(0)) {
789 pos->pos =
pos->pos + ImGui::GetIO().MouseDelta;
791 if (ImGui::IsWindowHovered() && !node_moving_active && ImGui::IsMouseDragging(0)) {
792 scrolling = scrolling - ImVec2(ImGui::GetIO().MouseDelta.x / 4.f, ImGui::GetIO().MouseDelta.y / 4.f);
797 auto hovered = (node_hovered_in_list ==
node->ID || node_hovered_in_scene ==
node->ID || (node_hovered_in_list == -1 && node_selected ==
node->ID));
798 ImVec4 nodeBgColor = hovered ? nodeBg.hovered : nodeBg.normal;
799 ImVec4 nodeTitleColor = hovered ? nodeBg.title_hovered : nodeBg.title;
800 ImU32 node_bg_color = ImGui::ColorConvertFloat4ToU32(nodeBgColor);
801 ImU32 node_title_color = ImGui::ColorConvertFloat4ToU32(nodeTitleColor);
803 draw_list->AddRectFilled(node_rect_min + ImVec2(3.f, 3.f), node_rect_max + ImVec2(3.f, 3.f), ImColor(0, 0, 0, 70), 4.0f);
804 draw_list->AddRectFilled(node_rect_min, node_rect_max, node_bg_color, 4.0f);
805 draw_list->AddRectFilled(node_rect_min, node_rect_title, node_title_color, 4.0f);
806 draw_list->AddRect(node_rect_min, node_rect_max, NODE_BORDER_COLOR, NODE_BORDER_THICKNESS);
808 for (
int slot_idx = 0; slot_idx <
node->InputsCount; slot_idx++) {
809 draw_list->ChannelsSetCurrent(backgroundLayer);
814 auto pp3 = p3 +
offset + slotPos;
815 auto color = arrowColor;
816 if (node_idx == node_selected) {
817 color = ARROW_SELECTED_COLOR;
819 draw_list->AddTriangleFilled(pp1, pp2, pp3,
color);
823 draw_list->ChannelsSetCurrent(foregroundLayer);
836 MetricsPainter<int, uint64_t, MetricLabelsContext>::colorPalette(std::vector<ImU32>{{ImColor(0, 100, 0, 255), ImColor(0, 0, 100, 255), ImColor(100, 0, 0, 255)}}, 0, 3),
842 auto&
label =
node->oldestPossibleInput[item];
847 label.textSize = ImGui::CalcTextSize(
label.buffer).x;
851 draw_list->AddText(
nullptr, 12,
853 NODE_LABEL_TEXT_COLOR,
854 node->oldestPossibleInput[item].buffer);
857 for (
int slot_idx = 0; slot_idx <
node->OutputsCount; slot_idx++) {
871 MetricsPainter<int, uint64_t, MetricLabelsContext>::colorPalette(std::vector<ImU32>{{ImColor(0, 100, 0, 255), ImColor(0, 0, 100, 255), ImColor(100, 0, 0, 255)}}, 0, 3),
877 auto&
label =
node->oldestPossibleOutput[item];
882 label.textSize = ImGui::CalcTextSize(
label.buffer).x;
886 draw_list->AddRectFilled(
offset + slotPos + rectTL,
887 offset + slotPos + rectBR, NODE_LABEL_BACKGROUND_COLOR, 2., ImDrawFlags_RoundCornersAll);
888 draw_list->AddText(
nullptr, 12,
889 offset + slotPos + rectTL,
890 NODE_LABEL_TEXT_COLOR,
891 node->oldestPossibleOutput[item].buffer);
896 draw_list->ChannelsMerge();
900 if (!ImGui::IsAnyItemHovered() && ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow) && ImGui::IsMouseClicked(1)) {
901 node_selected = node_hovered_in_list = node_hovered_in_scene = -1;
902 open_context_menu =
true;
904 if (open_context_menu) {
905 ImGui::OpenPopup(
"context_menu");
906 if (node_hovered_in_list != -1) {
907 node_selected = node_hovered_in_list;
909 if (node_hovered_in_scene != -1) {
910 node_selected = node_hovered_in_scene;
918 ImGui::PopItemWidth();
920 ImGui::PopStyleColor(2);
921 ImGui::PopStyleVar(2);
924 if (
state.rightPaneVisible) {
927 ImGui::BeginChild(
"inspector");
928 ImGui::TextUnformatted(
"Device Inspector");
930 if (node_selected != -1) {
932 auto& spec = specs[node_selected];
933 auto&
states = allStates[node_selected];
934 auto& control = controls[node_selected];
935 auto& info = infos[node_selected];
936 auto&
metrics = metricsInfos[node_selected];
938 auto& metadatum = metadata[
group.metadataId];
940 if (
state.rightPaneVisible) {
944 ImGui::TextWrapped(
"Select a node in the topology to display information about it");