TDME2  1.9.200
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
TextEditorTabView.cpp
Go to the documentation of this file.
2 
3 #include <algorithm>
4 #include <cctype>
5 #include <memory>
6 #include <string>
7 #include <unordered_map>
8 
9 #include <tdme/tdme.h>
10 #include <tdme/engine/Color4.h>
13 #include <tdme/engine/Engine.h>
18 #include <tdme/gui/nodes/GUINode.h>
23 #include <tdme/gui/GUI.h>
24 #include <tdme/gui/GUIParser.h>
25 #include <tdme/math/Math.h>
26 #include <tdme/math/Vector2.h>
38 #include <tdme/utilities/Integer.h>
41 
42 using std::make_unique;
43 using std::sort;
44 using std::string;
45 using std::unique_ptr;
46 using std::unordered_map;
47 
49 
62 using tdme::gui::GUI;
64 using tdme::math::Math;
79 
80 TextEditorTabView::TextEditorTabView(EditorView* editorView, const string& tabId, GUIScreenNode* screenNode, const string& fileName)
81 {
82  this->editorView = editorView;
83  this->tabId = tabId;
84  this->screenNode = screenNode;
85  this->popUps = editorView->getPopUps();
86  this->textNode = required_dynamic_cast<GUIStyledTextNode*>(screenNode->getInnerNodeById("text"));
87  this->fileName = fileName;
88  auto fileNameLowerCase = StringTools::toLowerCase(fileName);
89  this->extension =
90  fileNameLowerCase == "makefile" || StringTools::endsWith(fileNameLowerCase, "/makefile")?"makefile":
91  StringTools::substring(fileNameLowerCase, fileNameLowerCase.rfind('.') + 1, fileNameLowerCase.size());
92  engine = unique_ptr<Engine>(Engine::createOffScreenInstance(512, 512, false, false, false));
93  engine->setSceneColor(Color4(39.0f / 255.0f, 39.0f / 255.0f, 39.0f / 255.0f, 1.0f));
94  engine->getGUI()->addScreen(screenNode->getId(), screenNode);
95  engine->getGUI()->addRenderScreen(screenNode->getId());
96 
97  //
98  visualCodingEnabled = extension == "tscript";
99  if (visualCodingEnabled == true) {
100  //
101  visualisationNode = required_dynamic_cast<GUIParentNode*>(screenNode->getNodeById("visualization_canvas"));
102  //
103  linesTexture = make_unique<DynamicColorTexture>(engine->getWidth(), engine->getHeight());
104  linesTexture->initialize();
105  required_dynamic_cast<GUIImageNode*>(screenNode->getNodeById("visualization_texture"))->setTexture(linesTexture.get());
106  // add node move listener
107  class NodeMoveListener: public GUIMoveListener {
108  public:
109  NodeMoveListener(TextEditorTabView* textEditorTabView): textEditorTabView(textEditorTabView) {
110  }
111  bool accept(GUINode* node) {
112  if (node->getId().find("_") != string::npos) {
113  textEditorTabView->createConnection(node->getId());
114  return false;
115  } else {
116  return textEditorTabView->isCreatingConnection() == false;
117  }
118  }
119  void onMove(GUINode* node) {
120  auto visualisationNode = textEditorTabView->visualisationNode;
121  const auto& nodeComputedConstraints = node->getComputedConstraints();
122  auto xMax = nodeComputedConstraints.left + nodeComputedConstraints.width;
123  auto yMax = nodeComputedConstraints.top + nodeComputedConstraints.height;
128  textEditorTabView->createConnectionsPasses = 3;
129  }
130  void onRelease(GUINode* node, int mouseX, int mouseY) {
131  // no op
132  }
133  private:
134  TextEditorTabView* textEditorTabView;
135  };
136  //
137  screenNode->addMoveListener((guiMoveListener = make_unique<NodeMoveListener>(this)).get());
138  // enable code mode
139  setCodeEditor();
140  }
141 
142  // initial text format
143  TextFormatter::getInstance()->format(extension, textNode);
144  // load code completion
145  codeCompletion = unique_ptr<const TextFormatter::CodeCompletion>(TextFormatter::getInstance()->loadCodeCompletion(extension));
146 
147  //
148  {
149  // add text node change listener
150  class TextChangeListener: public GUIStyledTextNodeController::ChangeListener {
151  public:
152  TextChangeListener(TextEditorTabView* textEditorTabView): textEditorTabView(textEditorTabView) {
153  }
154 
155  virtual ~TextChangeListener() {
156  }
157 
158  virtual void onRemoveText(int idx, int count) override {
159  if (textEditorTabView->countEnabled == true) {
160  TextFormatter::getInstance()->format(textEditorTabView->extension, textEditorTabView->textNode, 0, textEditorTabView->textNode->getText().size());
161  textEditorTabView->countEnabled = false;
162  } else {
163  TextFormatter::getInstance()->format(textEditorTabView->extension, textEditorTabView->textNode, idx, idx + count);
164  }
165  }
166  virtual void onInsertText(int idx, int count) override {
167  if (textEditorTabView->countEnabled == true) {
168  TextFormatter::getInstance()->format(textEditorTabView->extension, textEditorTabView->textNode, 0, textEditorTabView->textNode->getText().size());
169  textEditorTabView->countEnabled = false;
170  } else {
171  TextFormatter::getInstance()->format(textEditorTabView->extension, textEditorTabView->textNode, idx, idx + count);
172  }
173  }
174  private:
175  TextEditorTabView* textEditorTabView;
176  };
177  //
178  required_dynamic_cast<GUIStyledTextNodeController*>(textNode->getController())->addChangeListener((textNodeChangeListener = make_unique<TextChangeListener>(this)).get());
179  }
180 
181  //
182  {
183  // add code completion listener
184  class TextCodeCompletionListener: public GUIStyledTextNodeController::CodeCompletionListener {
185  public:
186  TextCodeCompletionListener(TextEditorTabView* textEditorTabView): textEditorTabView(textEditorTabView) {
187  }
188 
189  virtual ~TextCodeCompletionListener() {
190  }
191 
192  virtual void onCodeCompletion(int idx) override {
193  auto codeCompletion = textEditorTabView->codeCompletion.get();
194  if (codeCompletion == nullptr) return;
195  if (codeCompletion->delimiters.find(textEditorTabView->textNode->getText().getCharAt(idx)) != string::npos) {
196  if (idx > 0) idx--;
197  }
198  auto previousDelimiterPos = textEditorTabView->textNode->getPreviousDelimiter(idx, codeCompletion->delimiters);
199  string search = StringTools::substring(textEditorTabView->textNode->getText().getString(), previousDelimiterPos == 0?0:previousDelimiterPos + 1, idx);
200  vector<CodeCompletionSymbol> codeCompletionSymbolCandidates;
201  #define MAX_ENTRIES 40
202  for (const auto& symbol: codeCompletion->symbols) {
203  if (StringTools::startsWith(symbol.name, search) == true) {
204  if (symbol.overloadList.empty() == true) {
205  if (codeCompletionSymbolCandidates.size() == MAX_ENTRIES) {
206  codeCompletionSymbolCandidates.push_back(
207  {
209  .display = "...",
210  .name = {},
211  .parameters = {},
212  .returnValue = {}
213  }
214  );
215  break;
216  } else {
217  codeCompletionSymbolCandidates.push_back(
218  {
220  .display = symbol.name,
221  .name = symbol.name,
222  .parameters = {},
223  .returnValue = {}
224  }
225  );
226  }
227  } else {
228  for (const auto& overload: symbol.overloadList) {
229  if (codeCompletionSymbolCandidates.size() == MAX_ENTRIES) {
230  codeCompletionSymbolCandidates.push_back(
231  {
233  .display = "...",
234  .name = {},
235  .parameters = {},
236  .returnValue = {}
237  }
238  );
239  break;
240  } else {
241  string parameters;
242  for (const auto& parameter: overload.parameters) {
243  if (parameters.empty() == false) parameters+= ", ";
244  parameters+= parameter;
245  }
246  codeCompletionSymbolCandidates.push_back(
247  {
249  .display = symbol.name + "(" + parameters + "): " + overload.returnValue,
250  .name = symbol.name,
251  .parameters = overload.parameters,
252  .returnValue = overload.returnValue
253  }
254  );
255  }
256  }
257  if (codeCompletionSymbolCandidates.size() == MAX_ENTRIES + 1) break;
258  }
259  }
260  }
261  auto popUps = textEditorTabView->getPopUps();
262  // clear
264  //
265  sort(
266  codeCompletionSymbolCandidates.begin(),
267  codeCompletionSymbolCandidates.begin() + (Math::min(codeCompletionSymbolCandidates.size(), MAX_ENTRIES)),
268  [](const CodeCompletionSymbol& lhs, const CodeCompletionSymbol& rhs) {
269  return lhs.display < rhs.display;
270  }
271  );
272  //
273  {
274  auto i = 0;
275  for (const auto& codeCompletionSymbolCandidate: codeCompletionSymbolCandidates) {
276  // add light
277  class OnCodeCompletionAction: public virtual Action
278  {
279  public:
280  OnCodeCompletionAction(TextEditorTabView* textEditorTabView, int idx, const CodeCompletionSymbol& symbol): textEditorTabView(textEditorTabView), idx(idx), symbol(symbol) {}
281  void performAction() override {
282  if (symbol.name.empty() == true) return;
283  auto codeCompletion = textEditorTabView->codeCompletion.get();
284  if (codeCompletion == nullptr) return;
285  auto previousDelimiterPos = textEditorTabView->textNode->getPreviousDelimiter(idx, codeCompletion->delimiters);
286  auto nextDelimiterPos = textEditorTabView->textNode->getNextDelimiter(idx, codeCompletion->delimiters);
287  auto withoutWhiteSpaceDelimiters = codeCompletion->delimiters;
288  if (withoutWhiteSpaceDelimiters.find(' ') != string::npos) withoutWhiteSpaceDelimiters.erase(withoutWhiteSpaceDelimiters.find(' '), 1);
289  if (withoutWhiteSpaceDelimiters.find('\t') != string::npos) withoutWhiteSpaceDelimiters.erase(withoutWhiteSpaceDelimiters.find('\t'), 1);
290  if (withoutWhiteSpaceDelimiters.find('\n') != string::npos) withoutWhiteSpaceDelimiters.erase(withoutWhiteSpaceDelimiters.find('\n'), 1);
291  auto nextDelimiterPos2 = textEditorTabView->textNode->getNextDelimiter(idx, withoutWhiteSpaceDelimiters);
292  auto idxToDelimiterString = StringTools::trim(StringTools::substring(textEditorTabView->textNode->getText().getString(), idx + 1 < textEditorTabView->textNode->getTextLength()?idx + 1:idx, nextDelimiterPos2));
293  string parameterString;
294  if (symbol.type == CodeCompletionSymbol::TYPE_FUNCTION && textEditorTabView->textNode->getText().getCharAt(nextDelimiterPos2) != '(') {
295  for (const auto& parameter: symbol.parameters) {
296  auto parameterTokenized = StringTools::tokenize(parameter, " \t\n");
297  if (parameterString.empty() == false) parameterString+= ", ";
298  parameterString+= parameterTokenized[parameterTokenized.size() - 1];
299  }
300  parameterString = "(" + parameterString + ")"/* + codeCompletion->statementDelimiter*/;
301  }
302  textEditorTabView->textNode->removeText(previousDelimiterPos == 0?0:previousDelimiterPos + 1, nextDelimiterPos - (previousDelimiterPos == 0?0:previousDelimiterPos + 1));
303  textEditorTabView->textNode->insertText(previousDelimiterPos == 0?0:previousDelimiterPos + 1, symbol.name + parameterString);
304  TextFormatter::getInstance()->format(textEditorTabView->extension, textEditorTabView->textNode, previousDelimiterPos == 0?0:previousDelimiterPos + 1, (previousDelimiterPos == 0?0:previousDelimiterPos + 1) + symbol.name.size() + parameterString.size());
305  }
306  private:
307  TextEditorTabView* textEditorTabView;
308  int idx;
309  CodeCompletionSymbol symbol;
310  };
311  popUps->getContextMenuScreenController()->addMenuItem(codeCompletionSymbolCandidate.display, "contextmenu_codecompletion_" + to_string(i), new OnCodeCompletionAction(textEditorTabView, idx, codeCompletionSymbolCandidate));
312  //
313  i++;
314  }
315  }
316  if (codeCompletionSymbolCandidates.empty() == false) {
317  //
318  int left, top, width, height, offsetX, offsetY;
319  auto selectedTab = textEditorTabView->getEditorView()->getScreenController()->getSelectedTab();
320  if (selectedTab != nullptr) {
321  textEditorTabView->getEditorView()->getViewPort(selectedTab->getFrameBufferNode(), left, top, width, height, offsetX, offsetY);
323  left + textEditorTabView->textNode->getIndexPositionX() - textEditorTabView->textNode->computeParentChildrenRenderOffsetXTotal(),
324  top + textEditorTabView->textNode->getIndexPositionY() - textEditorTabView->textNode->computeParentChildrenRenderOffsetYTotal()
325  );
326  }
327  }
328  }
329  private:
330  TextEditorTabView* textEditorTabView;
331  };
332  //
333  required_dynamic_cast<GUIStyledTextNodeController*>(textNode->getController())->addCodeCompletionListener((textNodeCodeCompletionListener = make_unique<TextCodeCompletionListener>(this)).get());
334  }
335 }
336 
338 }
339 
340 void TextEditorTabView::saveFile(const string& pathName, const string& fileName) {
341  auto text = textNode->getText().getString();
342  FileSystem::getStandardFileSystem()->setContentFromString(
343  pathName,
344  fileName,
345  text
346  );
347 }
348 
350 {
351  if (isCreatingConnection() == true) {
352  auto visualizationNode = required_dynamic_cast<GUIParentNode*>(screenNode->getInnerNodeById("visualization"));
353  auto scrollX = visualizationNode->getChildrenRenderOffsetX();
354  auto scrollY = visualizationNode->getChildrenRenderOffsetY();
355  for (auto& event: engine->getGUI()->getMouseEvents()) {
356  if (event.getButton() != GUIMouseEvent::MOUSEEVENT_BUTTON_LEFT) continue;
357  if (event.getType() == GUIMouseEvent::MOUSEEVENT_RELEASED) {
358  //
359  finishCreateConnection(event.getX(), event.getY());
360  //
361  event.setProcessed(true);
362  //
363  break;
364  } else
365  if (event.getType() == GUIMouseEvent::MOUSEEVENT_DRAGGED) {
366  auto& connection = connections[createConnectionIdx];
367  if (connection.srcNodeId.empty() == true) {
368  connection.x1 = scrollX + event.getX();
369  connection.y1 = scrollY + event.getY();
370  } else
371  if (connection.dstNodeId.empty() == true) {
372  connection.x2 = scrollX + event.getX();
373  connection.y2 = scrollY + event.getY();
374  }
375  //
377  //
378  event.setProcessed(true);
379  }
380  }
381  }
382  //
383  engine->getGUI()->handleEvents();
384 }
385 
386 
387 
389 {
390  //
391  if (visualCodingEnabled == true) {
392  //
394 
395  //
396  auto visualizationNode = required_dynamic_cast<GUIParentNode*>(screenNode->getInnerNodeById("visualization"));
397 
398  auto scrolled = false;
399  auto scrollXNew = visualizationNode->getChildrenRenderOffsetX();
400  auto scrollYNew = visualizationNode->getChildrenRenderOffsetY();
401 
402  if (Float::equals(scrollXNew, scrollX) == false ||
403  Float::equals(scrollYNew, scrollY) == false) {
404  scrollX = scrollXNew;
405  scrollY = scrollYNew;
406  scrolled = true;
407  }
408 
409  // resize?
410  if (scrolled == true ||
411  linesTexture->getWidth() != engine->getWidth() ||
412  linesTexture->getHeight() != engine->getHeight()) {
413  linesTexture->reshape(engine->getWidth(), engine->getHeight());
414  auto visualizationTextureNode = dynamic_cast<GUIImageNode*>(screenNode->getNodeById("visualization_texture"));
415  if (visualizationTextureNode != nullptr) visualizationTextureNode->setTexture(linesTexture.get());
417  }
418  // we have a layouting issue here, we cant get dimensions of nodes right after adding them, so defer this for now
419  if (createConnectionsPasses > 0) {
420  auto visualizationScrollArea = required_dynamic_cast<GUIParentNode*>(screenNode->getNodeById("visualization"));
421  auto visualizationWidth = visualizationScrollArea->getComputedConstraints().width;
422  auto visualizationHeight = visualizationScrollArea->getComputedConstraints().height;
423  auto visualizationScrollX = static_cast<int>(scrollX);
424  auto visualizationScrollY = static_cast<int>(scrollY);
426  // create lines
427  ColorTextureCanvas canvas(linesTexture->getTexture());
428  canvas.clear(0, 0, 0, 0);
429  // grid dots
430  for (auto y = 0; y < linesTexture->getHeight(); y+= 10) {
431  for (auto x = 0; x < linesTexture->getWidth(); x+= 10) {
432  canvas.drawPixel(Math::absmod(x - visualizationScrollX, linesTexture->getWidth()), Math::absmod(y - visualizationScrollY, linesTexture->getHeight()), 125, 125, 125, 50);
433  }
434  }
435  // connections
436  for (const auto& connection: connections) {
437  auto x1 = connection.x1 - visualizationScrollX;
438  auto y1 = connection.y1 - visualizationScrollY;
439  auto x2 = connection.x2 - visualizationScrollX;
440  auto y2 = connection.y2 - visualizationScrollY;
441 
442  if ((x1 < 0 && x2 < 0) ||
443  (x1 > visualizationWidth && x2 > visualizationWidth) ||
444  (y1 < 0 && y2 < 0) ||
445  (y1 > visualizationHeight && y2 > visualizationHeight)) continue;
446 
447  auto straightLineLength = Math::min(static_cast<float>(Math::abs(y2 - y1)), 50.0f);
448  Vector2 srcVector1(x1, y1);
449  Vector2 srcVector2(x1 + (x2 < x1?-straightLineLength:straightLineLength), y1);
450  Vector2 dstVector1(x1 + (x2 < x1?-straightLineLength:straightLineLength), y2 - (y2 < y1?-straightLineLength:straightLineLength));
451  Vector2 dstVector2(x2, y2);
452  vector<Vector2> controlPoints;
453  controlPoints.push_back(srcVector1);
454  controlPoints.push_back(srcVector2);
455  controlPoints.push_back(dstVector1);
456  controlPoints.push_back(dstVector2);
457  canvas.drawBezier(controlPoints, connection.red, connection.green, connection.blue, connection.alpha);
458  }
459  linesTexture->update();
461  }
462  }
463 
464  //
465  engine->display();
466  engine->getGUI()->render();
467 }
468 
470 {
471  try {
472  textEditorTabController = make_unique<TextEditorTabController>(this);
476  } catch (Exception& exception) {
477  Console::println("TextEditorTabView::initialize(): An error occurred: " + string(exception.what()));
478  }
479  // TODO: load settings
480 }
481 
483 {
484  textEditorTabController->closeFindReplaceWindow();
485  engine->dispose();
486 }
487 
489 }
490 
491 void TextEditorTabView::onMethodSelection(const string& methodName) {
492  auto visualizationNode = required_dynamic_cast<GUIParentNode*>(screenNode->getInnerNodeById("visualization"));
493  auto scrollX = visualizationNode->getChildrenRenderOffsetX();
494  auto scrollY = visualizationNode->getChildrenRenderOffsetY();
495  createMiniScriptNode(methodName, scrollX + textEditorTabController->getAddNodeX(), scrollY + textEditorTabController->getAddNodeY());
496 }
497 
499  return engine.get();
500 }
501 
503  textEditorTabController->setOutlinerAddDropDownContent();
504  textEditorTabController->setOutlinerContent();
507 }
508 
511  textEditorTabController->closeFindReplaceWindow();
512 }
513 
515  textEditorTabController->setOutlinerContent();
517 }
518 
520  textEditorTabController->closeFindReplaceWindow();
521  auto editorNode = dynamic_cast<GUIElementNode*>(engine->getGUI()->getScreen(screenNode->getId())->getNodeById("editor"));
522  if (editorNode != nullptr) editorNode->getActiveConditions().set("visualization");
523  visualEditor = true;
524 }
525 
527  // dump nodes for now
528  {
529  auto i = 0;
530  for (const auto& [nodeId, node]: nodes) {
531  string nodeType;
532  switch (node.type) {
533  case Node::NODETYPE_NONE: nodeType = "None"; break;
534  case Node::NODETYPE_FLOW: nodeType = "Flow"; break;
535  case Node::NODETYPE_ARGUMENT: nodeType = "Argument"; break;
536  }
537  Console::println("Node[" + to_string(i++) + "]: " + node.id + ": " + node.value + "(" + nodeType + ")");
538  }
539  }
540  // dump connections for now
541  {
542  auto i = 0;
543  for (const auto& connection: connections) {
544  Console::println("Connection[" + to_string(i++) + "]");
545  string connectionType;
546  switch (connection.type) {
547  case Connection::CONNECTIONTYPE_NONE: connectionType = "None"; break;
548  case Connection::CONNECTIONTYPE_FLOW: connectionType = "Flow"; break;
549  case Connection::CONNECTIONTYPE_ARGUMENT: connectionType = "Argument"; break;
550  }
551  Console::println("\t" + connection.srcNodeId + " --> " + connection.dstNodeId + "(" + connectionType + ")");
552  }
553  }
554  //
555  string sourceCode;
556  auto startNode = getNodeById(getStartNodeId());
557  createSourceCodeFromNodes(sourceCode, startNode);
558  Console::println(
559  "Generated Source Code:\n\n" +
560  sourceCode
561  );
562  auto editorNode = dynamic_cast<GUIElementNode*>(engine->getGUI()->getScreen(screenNode->getId())->getNodeById("editor"));
563  if (editorNode != nullptr) editorNode->getActiveConditions().set("text");
564  visualEditor = false;
565 }
566 
567 void TextEditorTabView::createMiniScriptNode(const string& methodName, int x, int y) {
568  auto flattenedId = to_string(nodeIdx++);
569  auto method = textEditorTabController->getMiniScript()->getMethod(methodName);
570 
571  //
572  nodes[flattenedId] = {
573  .id = flattenedId,
574  .type = Node::NODETYPE_NONE,
575  .value = methodName,
576  .returnValueType = method->getReturnValueType(),
577  .left = x,
578  .top = y
579  };
580  //
581  auto nodeName = methodName;
582  auto nodeTypeColor = string("color.nodetype_method");
583  auto methodOperatorMapIt = methodOperatorMap.find(nodeName);
584  if (methodOperatorMapIt != methodOperatorMap.end()) {
585  nodeName = methodOperatorMapIt->second;
586  nodeTypeColor = "color.nodetype_math";
587  }
588 
589  if (method == nullptr) {
590  nodeTypeColor = "color.nodetype_function";
591  } else {
592  for (const auto& flowControlNode: flowControlNodes) {
593  if (nodeName == flowControlNode) {
594  nodeTypeColor = "color.nodetype_flowcontrol";
595  break;
596  }
597  }
598  for (const auto& mathNode: mathNodes) {
599  if (nodeName == mathNode || StringTools::startsWith(nodeName, mathNode + ".")) {
600  nodeTypeColor = "color.nodetype_math";
601  break;
602  }
603  }
604  }
605  //
606  {
607  string xml = "<template src='resources/engine/gui/template_visualcode_node.xml' id='" + flattenedId + "' left='" + to_string(x) + "' top='" + to_string(y) + "' node-name='" + GUIParser::escape(nodeName + "(" + flattenedId + ")") + "' node-type-color='{$" + GUIParser::escape(nodeTypeColor) + "}' />";
608  try {
609  GUIParser::parse(visualisationNode, xml);
610  } catch (Exception& exception) {
611  Console::println("TextEditorTabView::createMiniScriptNodes(): method/function: " + string(exception.what()));
612  }
613  }
614  //
615  auto nodeInputContainer = required_dynamic_cast<GUIParentNode*>(screenNode->getNodeById(flattenedId + "_input_container"));
616  auto nodeOutputContainer = required_dynamic_cast<GUIParentNode*>(screenNode->getNodeById(flattenedId + "_output_container"));
617  // pin input aka flow input
618  {
619  string xml;
620  //
621  xml+=
622  string() +
623  "<template " +
624  " id='" + flattenedId + "_fi' " +
625  " src='resources/engine/gui/template_visualcode_input.xml' " +
626  " pin_type_connected='resources/engine/images/visualcode_flow_connected.png' " +
627  " pin_type_unconnected='resources/engine/images/visualcode_flow_unconnected.png' " +
628  "/>";
629  //
630  try {
631  GUIParser::parse(nodeInputContainer, xml);
632  // update to be connected
633  required_dynamic_cast<GUIElementNode*>(screenNode->getNodeById(flattenedId + "_fi_pin_type_panel"))->getActiveConditions().add("connected");
634  } catch (Exception& exception) {
635  Console::println("TextEditorTabView::createMiniScriptNodes(): method/function: " + string(exception.what()));
636  }
637  }
638  // inputs aka arguments
639  {
640  //
641  auto argumentIdx = 0;
642  if (method != nullptr) {
643  const auto& argumentTypes = method->getArgumentTypes();
644  for (argumentIdx = 0; argumentIdx < argumentTypes.size(); argumentIdx++) {
645  //
646  auto isLiteral = true;
647  auto literal = string("");
648  auto argumentName = argumentTypes[argumentIdx].name;
649  if (argumentName.empty() == false) argumentName[0] = Character::toUpperCase(argumentName[0]);
650  //
651  string xml =
652  string() +
653  "<template " +
654  " id='" + flattenedId + "_a" + to_string(argumentIdx) + "' " +
655  " src='resources/engine/gui/template_visualcode_input.xml' " +
656  " pin_type_connected='resources/engine/images/visualcode_value_connected.png' " +
657  " pin_type_unconnected='resources/engine/images/visualcode_value_unconnected.png' " +
658  " pin_color='{$" + GUIParser::escape(getScriptVariableTypePinColor(argumentTypes[argumentIdx].type)) + "}' " +
659  " text='" + GUIParser::escape(argumentName) + "' ";
660  if (isLiteral == true) {
661  xml+= " input_text='" + GUIParser::escape(literal) + "' ";
662  }
663  xml+= "/>";
664  //
665  try {
666  GUIParser::parse(nodeInputContainer, xml);
667  //
668  if (isLiteral == true) {
669  // update to be a literal
670  required_dynamic_cast<GUIElementNode*>(screenNode->getNodeById(flattenedId + "_a" + to_string(argumentIdx) + "_input_type_panel"))->getActiveConditions().add("input");
671  } else {
672  // update to be connected
673  required_dynamic_cast<GUIElementNode*>(screenNode->getNodeById(flattenedId + "_a" + to_string(argumentIdx) + "_pin_type_panel"))->getActiveConditions().add("connected");
674  }
675 
676  } catch (Exception& exception) {
677  Console::println("TextEditorTabView::createMiniScriptNodes(): method/function: " + string(exception.what()));
678  }
679  }
680  }
681  }
682  // pin output aka flow output
683  {
684  string xml;
685  //
686  xml+=
687  string() +
688  "<template " +
689  " id='" + flattenedId + "_fo' " +
690  " src='resources/engine/gui/template_visualcode_output.xml' " +
691  " pin_type_connected='resources/engine/images/visualcode_flow_connected.png' " +
692  " pin_type_unconnected='resources/engine/images/visualcode_flow_unconnected.png' " +
693  "/>";
694  //
695  try {
696  GUIParser::parse(nodeOutputContainer, xml);
697  // update to be connected
698  required_dynamic_cast<GUIElementNode*>(screenNode->getNodeById(flattenedId + "_fo_pin_type_panel"))->getActiveConditions().add("connected");
699  } catch (Exception& exception) {
700  Console::println("TextEditorTabView::createMiniScriptNodes(): method/function: " + string(exception.what()));
701  }
702  }
703  // return value
704  if (method != nullptr && method->getReturnValueType() != MiniScript::ScriptVariableType::TYPE_NULL) {
705  string xml;
706  //
707  xml+=
708  string() +
709  "<template " +
710  " id='" + flattenedId + "_r' " +
711  " src='resources/engine/gui/template_visualcode_output.xml' " +
712  " pin_type_connected='resources/engine/images/visualcode_value_connected.png' " +
713  " pin_type_unconnected='resources/engine/images/visualcode_value_unconnected.png' " +
714  " pin_color='{$" + GUIParser::escape(getScriptVariableTypePinColor(method->getReturnValueType())) + "}' " +
715  " text='Return' " +
716  "/>";
717 
718  //
719  try {
720  GUIParser::parse(nodeOutputContainer, xml);
721  // update to be connected
722  required_dynamic_cast<GUIElementNode*>(screenNode->getNodeById(flattenedId + "_r_pin_type_panel"))->getActiveConditions().add("connected");
723  } catch (Exception& exception) {
724  Console::println("TextEditorTabView::createMiniScriptNodes(): method/function: " + string(exception.what()));
725  }
726  } else
727  // functions have a return value pin by default for now
728  // TODO: MiniScript user functions need also formal return values a) to find out if we have a return value at all and to know the type
729  if (method == nullptr) {
730  string xml;
731  //
732  xml+=
733  string() +
734  "<template " +
735  " id='" + flattenedId + "_r' " +
736  " src='resources/engine/gui/template_visualcode_output.xml' " +
737  " pin_type_connected='resources/engine/images/visualcode_value_connected.png' " +
738  " pin_type_unconnected='resources/engine/images/visualcode_value_unconnected.png' " +
739  " pin_color='{$color.pintype_undefined}' " +
740  " text='Return Value' " +
741  "/>";
742 
743  //
744  try {
745  GUIParser::parse(nodeOutputContainer, xml);
746  // update to be connected
747  required_dynamic_cast<GUIElementNode*>(screenNode->getNodeById(flattenedId + "_r_pin_type_panel"))->getActiveConditions().add("connected");
748  } catch (Exception& exception) {
749  Console::println("TextEditorTabView::createMiniScriptNodes(): method/function: " + string(exception.what()));
750  }
751  }
752 
753  // Bug: Work around! Sometimes layouting is not issued! Need to check!
755 }
756 
757 void TextEditorTabView::addMiniScriptNodeDeltaX(unordered_map<string, string>& idMapping, const string& id, const MiniScript::ScriptSyntaxTreeNode& syntaxTreeNode, int deltaX) {
758  auto node = required_dynamic_cast<GUIParentNode*>(screenNode->getNodeById(getMiniScriptNodeFlattenedId(idMapping, id)));
759  node->getRequestsConstraints().left+= deltaX;
760  for (auto argumentIdx = 0; argumentIdx < syntaxTreeNode.arguments.size(); argumentIdx++) {
761  //
762  auto isLiteral = syntaxTreeNode.arguments[argumentIdx].type == MiniScript::ScriptSyntaxTreeNode::SCRIPTSYNTAXTREENODE_LITERAL;
763  if (isLiteral == true) continue;
764 
765  addMiniScriptNodeDeltaX(idMapping, id + "." + to_string(argumentIdx), syntaxTreeNode.arguments[argumentIdx], deltaX);
766  }
767 }
768 
769 void TextEditorTabView::createMiniScriptScriptNode(unordered_map<string, string>& idMapping, const string& id, MiniScript::Script::ScriptType scriptType, const string& condition, const string& readableName, const MiniScript::ScriptSyntaxTreeNode* conditionSyntaxTreeNode, int x, int y, int& width, int& height) {
770  // create input nodes
771  auto yInitial = y;
772 
773  //
774  if (conditionSyntaxTreeNode->type != MiniScript::ScriptSyntaxTreeNode::SCRIPTSYNTAXTREENODE_NONE) {
775  y+= 200;
776  {
777  auto childWidth = 0;
778  auto childHeight = 0;
779  vector<string> leftNodeIds;
780  createMiniScriptNodes(idMapping, id + ".c", 0, 1, conditionSyntaxTreeNode, Node::NODETYPE_ARGUMENT, x, y, childWidth, childHeight, leftNodeIds, 1);
781  x+= childWidth;
782  width+= childWidth;
783  height+= childHeight;
784  }
785 
786  //
787  y = yInitial;
788  }
789 
790  //
791  auto flattenedId = getMiniScriptNodeFlattenedId(idMapping, id);
792 
793  //
794  {
795  //
796  nodes[flattenedId] = {
797  .id = flattenedId,
798  .type = Node::NODETYPE_ARGUMENT,
799  .value = conditionSyntaxTreeNode->value.getValueAsString(),
800  .returnValueType = MiniScript::ScriptVariableType::TYPE_NULL,
801  .left = x,
802  .top = y
803  };
804 
805  //
806  auto nodeName = readableName;
807  string nodeTypeColor = string("color.nodetype_condition");
808  //
809  {
810  string xml = "<template src='resources/engine/gui/template_visualcode_node.xml' id='" + flattenedId + "' left='" + to_string(x) + "' top='" + to_string(y) + "' node-name='" + GUIParser::escape(nodeName + "(" + flattenedId + ")") + "' node-type-color='{$" + GUIParser::escape(nodeTypeColor) + "}' />";
811  try {
812  GUIParser::parse(visualisationNode, xml);
813  } catch (Exception& exception) {
814  Console::println("TextEditorTabView::createMiniScriptScriptNode(): method/function: " + string(exception.what()));
815  }
816  }
817  //
818  auto nodeInputContainer = required_dynamic_cast<GUIParentNode*>(screenNode->getNodeById(id + "_input_container"));
819  // condition input
820  {
821  //
822  auto isLiteral = conditionSyntaxTreeNode->type == MiniScript::ScriptSyntaxTreeNode::SCRIPTSYNTAXTREENODE_NONE;
823  auto literal = isLiteral == true?condition:string();
824  //
825  string xml =
826  string() +
827  "<template " +
828  " id='" + flattenedId + "_c' " +
829  " src='resources/engine/gui/template_visualcode_input.xml' " +
830  " pin_type_connected='resources/engine/images/visualcode_value_connected.png' " +
831  " pin_type_unconnected='resources/engine/images/visualcode_value_unconnected.png' " +
832  " pin_color='{$" + GUIParser::escape(getScriptVariableTypePinColor(MiniScript::ScriptVariableType::TYPE_BOOLEAN)) + "}' " +
833  " text='" + GUIParser::escape("Cond") + "' ";
834  if (isLiteral == true) {
835  xml+= " input_text='" + GUIParser::escape(literal) + "' ";
836  }
837  xml+= "/>";
838  //
839  try {
840  GUIParser::parse(nodeInputContainer, xml);
841  //
842  if (isLiteral == true) {
843  // update to be a literal
844  required_dynamic_cast<GUIElementNode*>(screenNode->getNodeById(flattenedId + "_c_input_type_panel"))->getActiveConditions().add("input");
845  } else {
846  // update to be connected
847  required_dynamic_cast<GUIElementNode*>(screenNode->getNodeById(flattenedId + "_c_pin_type_panel"))->getActiveConditions().add("connected");
848  }
849  } catch (Exception& exception) {
850  Console::println("TextEditorTabView::createMiniScriptScriptNode(): method/function: " + string(exception.what()));
851  }
852  }
853  }
854 
855  // create connection to condition node return to node cond node
856  if (conditionSyntaxTreeNode->type != MiniScript::ScriptSyntaxTreeNode::SCRIPTSYNTAXTREENODE_NONE) {
857  //
858  string argumentInputNodeId = flattenedId + "_c";
859  string argumentOutputNodeId = getMiniScriptNodeFlattenedId(idMapping, id + ".c_r");
860  auto argumentInputNode = dynamic_cast<GUINode*>(screenNode->getNodeById(argumentInputNodeId));
861  auto argumentOutputNode = dynamic_cast<GUINode*>(screenNode->getNodeById(argumentOutputNodeId));
862  if (argumentInputNode == nullptr) {
863  Console::println("TextEditorTabView::createMiniScriptNodes(): missing argument input node: " + argumentInputNodeId);
864  } else
865  if (argumentOutputNode == nullptr) {
866  Console::println("TextEditorTabView::createMiniScriptNodes(): missing argument output node: " + argumentOutputNodeId);
867  } else {
868 
869  //
870  auto pinColor = string("color.pintype_boolean");
871  GUIColor color(GUIParser::getEngineThemeProperties()->get(pinColor, "#ffffff"));
872 
873  //
874  const auto& argumentInputNodeComputedConstraints = argumentInputNode->getComputedConstraints();
875  const auto& argumentOutputNodeComputedConstraints = argumentOutputNode->getComputedConstraints();
876  connections.push_back(
877  {
879  .srcNodeId = argumentInputNodeId,
880  .dstNodeId = argumentOutputNodeId,
881  .red = static_cast<uint8_t>(color.getRed() * 255.0f),
882  .green = static_cast<uint8_t>(color.getGreen() * 255.0f),
883  .blue = static_cast<uint8_t>(color.getBlue() * 255.0f),
884  .alpha = static_cast<uint8_t>(color.getAlpha() * 255.0f),
885  .x1 = argumentInputNodeComputedConstraints.left,
886  .y1 = argumentInputNodeComputedConstraints.top + argumentInputNodeComputedConstraints.height / 2,
887  .x2 = argumentOutputNodeComputedConstraints.left + argumentOutputNodeComputedConstraints.width,
888  .y2 = argumentOutputNodeComputedConstraints.top + argumentOutputNodeComputedConstraints.height / 2,
889  }
890  );
891  }
892  }
893 
894  //
895  width+= 400;
896  height = Math::max(height, 200);
897 }
898 
899 void TextEditorTabView::createMiniScriptNodes(unordered_map<string, string>& idMapping, const string& id, int syntaxTreeNodeIdx, int syntaxTreeNodeCount, const MiniScript::ScriptSyntaxTreeNode* syntaxTreeNode, Node::NodeType nodeType, int x, int y, int& width, int& height, vector<string>& createdNodeIds, int depth) {
900  // create input nodes
901  int childMaxWidth = 0;
902  auto yInitial = y;
903 
904  //
905  y+= 200;
906  vector<string> leftNodeIds;
907  for (auto argumentIdx = 0; argumentIdx < syntaxTreeNode->arguments.size(); argumentIdx++) {
908  //
909  auto isLiteral = syntaxTreeNode->arguments[argumentIdx].type == MiniScript::ScriptSyntaxTreeNode::SCRIPTSYNTAXTREENODE_LITERAL;
910  if (isLiteral == true) continue;
911  //
912  auto childWidth = 0;
913  auto childHeight = 0;
914  createMiniScriptNodes(idMapping, id + "." + to_string(argumentIdx), argumentIdx, syntaxTreeNode->arguments.size(), &syntaxTreeNode->arguments[argumentIdx], Node::NODETYPE_ARGUMENT, x, y, childWidth, childHeight, leftNodeIds, depth + 1);
915  if (childWidth > childMaxWidth) childMaxWidth = childWidth;
916  y+= childHeight;
917  height+= childHeight;
918  }
919 
920  //
921  y = yInitial;
922  x+= childMaxWidth;
923  width+= childMaxWidth;
924 
925  //
926  for (const auto& nodeId: leftNodeIds) createdNodeIds.push_back(nodeId);
927 
928  //
929  auto flattenedId = getMiniScriptNodeFlattenedId(idMapping, id);
930 
931  //
932  switch (syntaxTreeNode->type) {
933  case MiniScript::ScriptSyntaxTreeNode::SCRIPTSYNTAXTREENODE_EXECUTE_METHOD:
934  case MiniScript::ScriptSyntaxTreeNode::SCRIPTSYNTAXTREENODE_EXECUTE_FUNCTION:
935  {
936  //
937  createdNodeIds.push_back(flattenedId);
938  //
939  nodes[flattenedId] = {
940  .id = flattenedId,
941  .type = nodeType,
942  .value = syntaxTreeNode->value.getValueAsString(),
943  .returnValueType = syntaxTreeNode->method != nullptr?syntaxTreeNode->method->getReturnValueType():MiniScript::ScriptVariableType::TYPE_NULL,
944  .left = x,
945  .top = y
946  };
947  //
948  auto nodeName = syntaxTreeNode->value.getValueAsString();
949  auto nodeTypeColor = string("color.nodetype_method");
950  auto methodOperatorMapIt = methodOperatorMap.find(nodeName);
951  if (methodOperatorMapIt != methodOperatorMap.end()) {
952  nodeName = methodOperatorMapIt->second;
953  nodeTypeColor = "color.nodetype_math";
954  }
955 
956  if (syntaxTreeNode->method == nullptr) {
957  nodeTypeColor = "color.nodetype_function";
958  } else {
959  for (const auto& flowControlNode: flowControlNodes) {
960  if (nodeName == flowControlNode) {
961  nodeTypeColor = "color.nodetype_flowcontrol";
962  break;
963  }
964  }
965  for (const auto& mathNode: mathNodes) {
966  if (nodeName == mathNode || StringTools::startsWith(nodeName, mathNode + ".")) {
967  nodeTypeColor = "color.nodetype_math";
968  break;
969  }
970  }
971  }
972  //
973  {
974  string xml = "<template src='resources/engine/gui/template_visualcode_node.xml' id='" + flattenedId + "' left='" + to_string(x) + "' top='" + to_string(y) + "' node-name='" + GUIParser::escape(nodeName + "(" + flattenedId + ")") + "' node-type-color='{$" + GUIParser::escape(nodeTypeColor) + "}' />";
975  try {
976  GUIParser::parse(visualisationNode, xml);
977  } catch (Exception& exception) {
978  Console::println("TextEditorTabView::createMiniScriptNodes(): method/function: " + string(exception.what()));
979  }
980  }
981  //
982  auto nodeInputContainer = required_dynamic_cast<GUIParentNode*>(screenNode->getNodeById(flattenedId + "_input_container"));
983  auto nodeOutputContainer = required_dynamic_cast<GUIParentNode*>(screenNode->getNodeById(flattenedId + "_output_container"));
984  // pin input aka flow input
985  {
986  string xml;
987  //
988  xml+=
989  string() +
990  "<template " +
991  " id='" + flattenedId + "_fi' " +
992  " src='resources/engine/gui/template_visualcode_input.xml' " +
993  " pin_type_connected='resources/engine/images/visualcode_flow_connected.png' " +
994  " pin_type_unconnected='resources/engine/images/visualcode_flow_unconnected.png' " +
995  "/>";
996  //
997  try {
998  GUIParser::parse(nodeInputContainer, xml);
999  // update to be connected
1000  required_dynamic_cast<GUIElementNode*>(screenNode->getNodeById(flattenedId + "_fi_pin_type_panel"))->getActiveConditions().add("connected");
1001  } catch (Exception& exception) {
1002  Console::println("TextEditorTabView::createMiniScriptNodes(): method/function: " + string(exception.what()));
1003  }
1004  }
1005  // inputs aka arguments
1006  {
1007  //
1008  auto argumentIdx = 0;
1009  if (syntaxTreeNode->method != nullptr) {
1010  const auto& argumentTypes = syntaxTreeNode->method->getArgumentTypes();
1011  for (argumentIdx = 0; argumentIdx < argumentTypes.size(); argumentIdx++) {
1012  //
1013  auto isLiteral = argumentIdx < syntaxTreeNode->arguments.size()?syntaxTreeNode->arguments[argumentIdx].type == MiniScript::ScriptSyntaxTreeNode::SCRIPTSYNTAXTREENODE_LITERAL:false;
1014  auto literal = isLiteral == true?syntaxTreeNode->arguments[argumentIdx].value.getValueAsString():string();
1015  auto argumentName = argumentTypes[argumentIdx].name;
1016  if (argumentName.empty() == false) argumentName[0] = Character::toUpperCase(argumentName[0]);
1017  //
1018  string xml =
1019  string() +
1020  "<template " +
1021  " id='" + flattenedId + "_a" + to_string(argumentIdx) + "' " +
1022  " src='resources/engine/gui/template_visualcode_input.xml' " +
1023  " pin_type_connected='resources/engine/images/visualcode_value_connected.png' " +
1024  " pin_type_unconnected='resources/engine/images/visualcode_value_unconnected.png' " +
1025  " pin_color='{$" + GUIParser::escape(getScriptVariableTypePinColor(argumentTypes[argumentIdx].type)) + "}' " +
1026  " text='" + GUIParser::escape(argumentName) + "' ";
1027  if (isLiteral == true) {
1028  xml+= " input_text='" + GUIParser::escape(literal) + "' ";
1029  }
1030  xml+= "/>";
1031  //
1032  try {
1033  GUIParser::parse(nodeInputContainer, xml);
1034  //
1035  if (isLiteral == true) {
1036  // update to be a literal
1037  required_dynamic_cast<GUIElementNode*>(screenNode->getNodeById(flattenedId + "_a" + to_string(argumentIdx) + "_input_type_panel"))->getActiveConditions().add("input");
1038  } else {
1039  // update to be connected
1040  required_dynamic_cast<GUIElementNode*>(screenNode->getNodeById(flattenedId + "_a" + to_string(argumentIdx) + "_pin_type_panel"))->getActiveConditions().add("connected");
1041  }
1042 
1043  } catch (Exception& exception) {
1044  Console::println("TextEditorTabView::createMiniScriptNodes(): method/function: " + string(exception.what()));
1045  }
1046  }
1047  }
1048  for (; argumentIdx < syntaxTreeNode->arguments.size(); argumentIdx++) {
1049  //
1050  auto isLiteral = syntaxTreeNode->arguments[argumentIdx].type == MiniScript::ScriptSyntaxTreeNode::SCRIPTSYNTAXTREENODE_LITERAL;
1051  auto literal = isLiteral == true?syntaxTreeNode->arguments[argumentIdx].value.getValueAsString():string();
1052  //
1053  string xml =
1054  string() +
1055  "<template " +
1056  " id='" + flattenedId + "_a" + to_string(argumentIdx) + "' " +
1057  " src='resources/engine/gui/template_visualcode_input.xml' " +
1058  " pin_type_connected='resources/engine/images/visualcode_value_connected.png' " +
1059  " pin_type_unconnected='resources/engine/images/visualcode_value_unconnected.png' " +
1060  " pin_color='{$color.pintype_undefined}' " +
1061  " text='Arg " + to_string(argumentIdx) + "' ";
1062  if (isLiteral == true) {
1063  xml+= " input_text='" + GUIParser::escape(literal) + "' ";
1064  }
1065  xml+= "/>";
1066  //
1067  try {
1068  GUIParser::parse(nodeInputContainer, xml);
1069  //
1070  if (isLiteral == true) {
1071  // update to be a literal
1072  required_dynamic_cast<GUIElementNode*>(screenNode->getNodeById(flattenedId + "_a" + to_string(argumentIdx) + "_input_type_panel"))->getActiveConditions().add("input");
1073  } else {
1074  // update to be connected
1075  required_dynamic_cast<GUIElementNode*>(screenNode->getNodeById(flattenedId + "_a" + to_string(argumentIdx) + "_pin_type_panel"))->getActiveConditions().add("connected");
1076  }
1077  } catch (Exception& exception) {
1078  Console::println("TextEditorTabView::createMiniScriptNodes(): method/function: " + string(exception.what()));
1079  }
1080  }
1081  }
1082  // pin output aka flow output
1083  if (depth == 0) {
1084  string xml;
1085  //
1086  xml+=
1087  string() +
1088  "<template " +
1089  " id='" + flattenedId + "_fo' " +
1090  " src='resources/engine/gui/template_visualcode_output.xml' " +
1091  " pin_type_connected='resources/engine/images/visualcode_flow_connected.png' " +
1092  " pin_type_unconnected='resources/engine/images/visualcode_flow_unconnected.png' " +
1093  "/>";
1094  //
1095  try {
1096  GUIParser::parse(nodeOutputContainer, xml);
1097  // update to be connected
1098  required_dynamic_cast<GUIElementNode*>(screenNode->getNodeById(flattenedId + "_fo_pin_type_panel"))->getActiveConditions().add("connected");
1099  } catch (Exception& exception) {
1100  Console::println("TextEditorTabView::createMiniScriptNodes(): method/function: " + string(exception.what()));
1101  }
1102  }
1103  // return value
1104  if (syntaxTreeNode->method != nullptr && syntaxTreeNode->method->getReturnValueType() != MiniScript::ScriptVariableType::TYPE_NULL) {
1105  string xml;
1106  //
1107  xml+=
1108  string() +
1109  "<template " +
1110  " id='" + flattenedId + "_r' " +
1111  " src='resources/engine/gui/template_visualcode_output.xml' " +
1112  " pin_type_connected='resources/engine/images/visualcode_value_connected.png' " +
1113  " pin_type_unconnected='resources/engine/images/visualcode_value_unconnected.png' " +
1114  " pin_color='{$" + GUIParser::escape(getScriptVariableTypePinColor(syntaxTreeNode->method->getReturnValueType())) + "}' " +
1115  " text='Return' " +
1116  "/>";
1117 
1118  //
1119  try {
1120  GUIParser::parse(nodeOutputContainer, xml);
1121  // update to be connected
1122  required_dynamic_cast<GUIElementNode*>(screenNode->getNodeById(flattenedId + "_r_pin_type_panel"))->getActiveConditions().add("connected");
1123  } catch (Exception& exception) {
1124  Console::println("TextEditorTabView::createMiniScriptNodes(): method/function: " + string(exception.what()));
1125  }
1126  } else
1127  // functions have a return value pin by default for now
1128  // TODO: MiniScript user functions need also formal return values a) to find out if we have a return value at all and to know the type
1129  if (syntaxTreeNode->method == nullptr) {
1130  string xml;
1131  //
1132  xml+=
1133  string() +
1134  "<template " +
1135  " id='" + flattenedId + "_r' " +
1136  " src='resources/engine/gui/template_visualcode_output.xml' " +
1137  " pin_type_connected='resources/engine/images/visualcode_value_connected.png' " +
1138  " pin_type_unconnected='resources/engine/images/visualcode_value_unconnected.png' " +
1139  " pin_color='{$color.pintype_undefined}' " +
1140  " text='Return Value' " +
1141  "/>";
1142 
1143  //
1144  try {
1145  GUIParser::parse(nodeOutputContainer, xml);
1146  // update to be connected
1147  required_dynamic_cast<GUIElementNode*>(screenNode->getNodeById(flattenedId + "_r_pin_type_panel"))->getActiveConditions().add("connected");
1148  } catch (Exception& exception) {
1149  Console::println("TextEditorTabView::createMiniScriptNodes(): method/function: " + string(exception.what()));
1150  }
1151  }
1152  break;
1153  }
1154  case MiniScript::ScriptSyntaxTreeNode::SCRIPTSYNTAXTREENODE_LITERAL:
1155  break;
1156  }
1157 
1158  //
1159  width+= 400; //node->getContentWidth();
1160  height+= 200; //node->getContentHeight();
1161 
1162  // post layout of input nodes, move first level child argument nodes from from left to right according to the closest one
1163  auto rootDistanceMax = Integer::MAX_VALUE;
1164  auto nextLevelXBestFit = -1;
1165  for (auto argumentIdx = 0; argumentIdx < syntaxTreeNode->arguments.size(); argumentIdx++) {
1166  //
1167  auto isLiteral = syntaxTreeNode->arguments[argumentIdx].type == MiniScript::ScriptSyntaxTreeNode::SCRIPTSYNTAXTREENODE_LITERAL;
1168  if (isLiteral == true) continue;
1169  //
1170  auto nextLevelNode = required_dynamic_cast<GUINode*>(screenNode->getNodeById(getMiniScriptNodeFlattenedId(idMapping, id + "." + to_string(argumentIdx))));
1171  auto nodeXPosition = nextLevelNode->getRequestsConstraints().left;
1172  auto rootDistance = Math::abs(x - nodeXPosition);
1173  if (rootDistance < rootDistanceMax) {
1174  rootDistanceMax = rootDistance;
1175  nextLevelXBestFit = nodeXPosition;
1176  }
1177  }
1178  for (auto argumentIdx = 0; argumentIdx < syntaxTreeNode->arguments.size(); argumentIdx++) {
1179  //
1180  auto isLiteral = syntaxTreeNode->arguments[argumentIdx].type == MiniScript::ScriptSyntaxTreeNode::SCRIPTSYNTAXTREENODE_LITERAL;
1181  if (isLiteral == true) continue;
1182  //
1183  auto nextLevelNode = required_dynamic_cast<GUINode*>(screenNode->getNodeById(getMiniScriptNodeFlattenedId(idMapping, id + "." + to_string(argumentIdx))));
1184  auto nodeXPosition = nextLevelNode->getRequestsConstraints().left;
1185  auto deltaX = nextLevelXBestFit - nodeXPosition;
1186  if (deltaX == 0) continue;
1187  addMiniScriptNodeDeltaX(idMapping, id + "." + to_string(argumentIdx), syntaxTreeNode->arguments[argumentIdx], deltaX);
1188  }
1189 
1190  // create connections to input nodes
1191  for (auto argumentIdx = 0; argumentIdx < syntaxTreeNode->arguments.size(); argumentIdx++) {
1192  //
1193  auto isLiteral = syntaxTreeNode->arguments[argumentIdx].type == MiniScript::ScriptSyntaxTreeNode::SCRIPTSYNTAXTREENODE_LITERAL;
1194  if (isLiteral == true) continue;
1195  //
1196  string argumentInputNodeId = flattenedId + "_a" + to_string(argumentIdx);
1197  string argumentOutputNodeId = getMiniScriptNodeFlattenedId(idMapping, id + "." + to_string(argumentIdx)) + "_r";
1198  auto argumentInputNode = dynamic_cast<GUINode*>(screenNode->getNodeById(argumentInputNodeId));
1199  auto argumentOutputNode = dynamic_cast<GUINode*>(screenNode->getNodeById(argumentOutputNodeId));
1200  if (argumentInputNode == nullptr) {
1201  Console::println("TextEditorTabView::createMiniScriptNodes(): missing argument input node: " + argumentInputNodeId);
1202  continue;
1203  }
1204  if (argumentOutputNode == nullptr) {
1205  Console::println("TextEditorTabView::createMiniScriptNodes(): missing argument output node: " + argumentOutputNodeId);
1206  continue;
1207  }
1208 
1209  //
1210  auto pinColor = string("color.pintype_undefined");
1211  if (syntaxTreeNode->method != nullptr) {
1212  const auto& argumentTypes = syntaxTreeNode->method->getArgumentTypes();
1213  // first guess from argument type
1214  if (argumentIdx < argumentTypes.size()) {
1215  pinColor = getScriptVariableTypePinColor(argumentTypes[argumentIdx].type);
1216  }
1217  // no color?, try return value
1218  if (pinColor == "color.pintype_undefined") {
1219  auto node = getNodeById(flattenedId + "." + to_string(argumentIdx));
1220  if (node != nullptr) {
1221  if (node->returnValueType != MiniScript::ScriptVariableType::TYPE_NULL) {
1222  pinColor = getScriptVariableTypePinColor(node->returnValueType);
1223  }
1224  }
1225  }
1226  }
1227  GUIColor color(GUIParser::getEngineThemeProperties()->get(pinColor, "#ffffff"));
1228 
1229  //
1230  const auto& argumentInputNodeComputedConstraints = argumentInputNode->getComputedConstraints();
1231  const auto& argumentOutputNodeComputedConstraints = argumentOutputNode->getComputedConstraints();
1232  connections.push_back(
1233  {
1235  .srcNodeId = argumentInputNodeId,
1236  .dstNodeId = argumentOutputNodeId,
1237  .red = static_cast<uint8_t>(color.getRed() * 255.0f),
1238  .green = static_cast<uint8_t>(color.getGreen() * 255.0f),
1239  .blue = static_cast<uint8_t>(color.getBlue() * 255.0f),
1240  .alpha = static_cast<uint8_t>(color.getAlpha() * 255.0f),
1241  .x1 = argumentInputNodeComputedConstraints.left,
1242  .y1 = argumentInputNodeComputedConstraints.top + argumentInputNodeComputedConstraints.height / 2,
1243  .x2 = argumentOutputNodeComputedConstraints.left + argumentOutputNodeComputedConstraints.width,
1244  .y2 = argumentOutputNodeComputedConstraints.top + argumentOutputNodeComputedConstraints.height / 2,
1245  }
1246  );
1247  }
1248 }
1249 
1250 void TextEditorTabView::createMiniScriptBranchNodes(unordered_map<string, string>& idMapping, const string& id, int syntaxTreeNodeIdx, int syntaxTreeNodeCount, const MiniScript::ScriptSyntaxTreeNode* syntaxTreeNode, Node::NodeType nodeType, const vector<MiniScriptBranch>& branches, int x, int y, int& width, int& height, vector<string>& createdNodeIds, int depth) {
1251  //
1252  auto xInitial = x;
1253  auto yInitial = y;
1254 
1255  //
1256  y+= 200;
1257 
1258  //
1259  vector<string> leftNodeIds;
1260  auto childMaxWidth = 0;
1261  for (auto branchIdx = 0; branchIdx < branches.size(); branchIdx++) {
1262  // note: else has no condition
1263  if (branches[branchIdx].conditionSyntaxTree == nullptr) break;
1264  //
1265  if (branches[branchIdx].conditionSyntaxTree->type == MiniScript::ScriptSyntaxTreeNode::SCRIPTSYNTAXTREENODE_LITERAL) continue;
1266 
1267  //
1268  auto childWidth = 0;
1269  auto childHeight = 0;
1270  createMiniScriptNodes(idMapping, id + "." + to_string(branchIdx), branchIdx, branches.size(), branches[branchIdx].conditionSyntaxTree, Node::NODETYPE_ARGUMENT, x, y, childWidth, childHeight, leftNodeIds, depth + 1);
1271  if (childWidth > childMaxWidth) childMaxWidth = childWidth;
1272  y+= childHeight;
1273  height+= childHeight;
1274  }
1275  //
1276  x+= childMaxWidth;
1277  width+= childMaxWidth;
1278  y = yInitial;
1279 
1280  //
1281  for (const auto& nodeId: leftNodeIds) createdNodeIds.push_back(nodeId);
1282  //
1283  auto flattenedId = getMiniScriptNodeFlattenedId(idMapping, id);
1284 
1285  //
1286  {
1287  //
1288  createdNodeIds.push_back(flattenedId);
1289  //
1290  nodes[flattenedId] = {
1291  .id = flattenedId,
1292  .type = nodeType,
1293  .value = syntaxTreeNode->value.getValueAsString(),
1294  .returnValueType = MiniScript::ScriptVariableType::TYPE_NULL,
1295  .left = x,
1296  .top = y
1297  };
1298  //
1299  string nodeName = syntaxTreeNode->value.getValueAsString();
1300  string nodeTypeColor = string("color.nodetype_flowcontrol");
1301  //
1302  {
1303  string xml = "<template src='resources/engine/gui/template_visualcode_node.xml' id='" + flattenedId + "' left='" + to_string(x) + "' top='" + to_string(y) + "' node-name='" + GUIParser::escape(nodeName + "(" + flattenedId + ")") + "' node-type-color='{$" + GUIParser::escape(nodeTypeColor) + "}' />";
1304  try {
1305  GUIParser::parse(visualisationNode, xml);
1306  } catch (Exception& exception) {
1307  Console::println("TextEditorTabView::createMiniScriptIfBranchNodes(): method/function: " + string(exception.what()));
1308  }
1309  }
1310  //
1311  auto nodeInputContainer = required_dynamic_cast<GUIParentNode*>(screenNode->getNodeById(flattenedId + "_input_container"));
1312  auto nodeOutputContainer = required_dynamic_cast<GUIParentNode*>(screenNode->getNodeById(flattenedId + "_output_container"));
1313  // pin input aka flow input
1314  {
1315  string xml;
1316  //
1317  xml+=
1318  string() +
1319  "<template " +
1320  " id='" + flattenedId + "_fi' " +
1321  " src='resources/engine/gui/template_visualcode_input.xml' " +
1322  " pin_type_connected='resources/engine/images/visualcode_flow_connected.png' " +
1323  " pin_type_unconnected='resources/engine/images/visualcode_flow_unconnected.png' " +
1324  "/>";
1325  //
1326  try {
1327  GUIParser::parse(nodeInputContainer, xml);
1328  // update to be connected
1329  required_dynamic_cast<GUIElementNode*>(screenNode->getNodeById(flattenedId + "_fi_pin_type_panel"))->getActiveConditions().add("connected");
1330  } catch (Exception& exception) {
1331  Console::println("TextEditorTabView::createMiniScriptIfBranchNodes(): method/function: " + string(exception.what()));
1332  }
1333  }
1334  // pin output aka flow output
1335  {
1336  string xml;
1337  //
1338  xml+=
1339  string() +
1340  "<template " +
1341  " id='" + flattenedId + "_fo' " +
1342  " src='resources/engine/gui/template_visualcode_output.xml' " +
1343  " pin_type_connected='resources/engine/images/visualcode_flow_connected.png' " +
1344  " pin_type_unconnected='resources/engine/images/visualcode_flow_unconnected.png' " +
1345  "/>";
1346  //
1347  try {
1348  GUIParser::parse(nodeOutputContainer, xml);
1349  // update to be connected
1350  required_dynamic_cast<GUIElementNode*>(screenNode->getNodeById(flattenedId + "_fo_pin_type_panel"))->getActiveConditions().add("connected");
1351  } catch (Exception& exception) {
1352  Console::println("TextEditorTabView::createMiniScriptIfBranchNodes(): method/function: " + string(exception.what()));
1353  }
1354  }
1355  // inputs aka arguments
1356  for (auto branchIdx = 0; branchIdx < branches.size(); branchIdx++) {
1357  // condition inputs
1358  // note: else has no condition syntax tree
1359  if (branches[branchIdx].conditionSyntaxTree != nullptr) {
1360  //
1361  auto isLiteral = branches[branchIdx].conditionSyntaxTree->type == MiniScript::ScriptSyntaxTreeNode::SCRIPTSYNTAXTREENODE_LITERAL;
1362  auto literal = isLiteral == true?branches[branchIdx].conditionSyntaxTree->value.getValueAsString():string();
1363  //
1364  string xml =
1365  string() +
1366  "<template " +
1367  " id='" + flattenedId + "_c" + to_string(branchIdx) + "' " +
1368  " src='resources/engine/gui/template_visualcode_input.xml' " +
1369  " pin_type_connected='resources/engine/images/visualcode_value_connected.png' " +
1370  " pin_type_unconnected='resources/engine/images/visualcode_value_unconnected.png' " +
1371  " pin_color='{$" + GUIParser::escape(getScriptVariableTypePinColor(nodeName == "forTime"?MiniScript::ScriptVariableType::TYPE_INTEGER:MiniScript::ScriptVariableType::TYPE_BOOLEAN)) + "}' " +
1372  " text='" + GUIParser::escape(nodeName == "forTime"?"time":"Cond " + to_string(branchIdx)) + "' ";
1373  if (isLiteral == true) {
1374  xml+= " input_text='" + GUIParser::escape(literal) + "' ";
1375  }
1376  xml+= "/>";
1377  //
1378  try {
1379  GUIParser::parse(nodeInputContainer, xml);
1380  //
1381  if (isLiteral == true) {
1382  // update to be a literal
1383  required_dynamic_cast<GUIElementNode*>(screenNode->getNodeById(flattenedId + "_c" + to_string(branchIdx) + "_input_type_panel"))->getActiveConditions().add("input");
1384  } else {
1385  // update to be connected
1386  required_dynamic_cast<GUIElementNode*>(screenNode->getNodeById(flattenedId + "_c" + to_string(branchIdx) + "_pin_type_panel"))->getActiveConditions().add("connected");
1387  }
1388  } catch (Exception& exception) {
1389  Console::println("TextEditorTabView::createMiniScriptIfBranchNodes(): method/function: " + string(exception.what()));
1390  }
1391  }
1392  // flow outputs
1393  {
1394  string xml;
1395  //
1396  xml+=
1397  string() +
1398  "<template " +
1399  " id='" + flattenedId + "_b" + to_string(branchIdx) + "' " +
1400  " src='resources/engine/gui/template_visualcode_output.xml' " +
1401  " pin_type_connected='resources/engine/images/visualcode_flow_connected.png' " +
1402  " pin_type_unconnected='resources/engine/images/visualcode_flow_unconnected.png' " +
1403  " text='Flow " + to_string(branchIdx) + "' " +
1404  "/>";
1405  //
1406  try {
1407  GUIParser::parse(nodeOutputContainer, xml);
1408  // update to be connected
1409  required_dynamic_cast<GUIElementNode*>(screenNode->getNodeById(flattenedId + "_b" + to_string(branchIdx) + "_pin_type_panel"))->getActiveConditions().add("connected");
1410  } catch (Exception& exception) {
1411  Console::println("TextEditorTabView::createMiniScriptIfBranchNodes(): method/function: " + string(exception.what()));
1412  }
1413  }
1414  }
1415  }
1416 
1417  //
1418  width+= 400; //node->getContentWidth();
1419  height+= 200; //node->getContentHeight();
1420 
1421  // post layout of conditions, move first level child argument nodes from from left to right according to the closest one
1422  auto rootDistanceMax = Integer::MAX_VALUE;
1423  auto nextLevelXBestFit = -1;
1424  for (auto branchIdx = 0; branchIdx < branches.size(); branchIdx++) {
1425  // note: else has no condition
1426  if (branches[branchIdx].conditionSyntaxTree == nullptr) break;
1427  //
1428  if (branches[branchIdx].conditionSyntaxTree->type == MiniScript::ScriptSyntaxTreeNode::SCRIPTSYNTAXTREENODE_LITERAL) continue;
1429  //
1430  auto nextLevelNode = required_dynamic_cast<GUINode*>(screenNode->getNodeById(getMiniScriptNodeFlattenedId(idMapping, id + "." + to_string(branchIdx))));
1431  auto nodeXPosition = nextLevelNode->getRequestsConstraints().left;
1432  auto rootDistance = Math::abs(x - nodeXPosition);
1433  if (rootDistance < rootDistanceMax) {
1434  rootDistanceMax = rootDistance;
1435  nextLevelXBestFit = nodeXPosition;
1436  }
1437  }
1438  for (auto branchIdx = 0; branchIdx < branches.size(); branchIdx++) {
1439  // note: else has no condition
1440  if (branches[branchIdx].conditionSyntaxTree == nullptr) break;
1441  //
1442  if (branches[branchIdx].conditionSyntaxTree->type == MiniScript::ScriptSyntaxTreeNode::SCRIPTSYNTAXTREENODE_LITERAL) continue;
1443  //
1444  auto nextLevelNode = required_dynamic_cast<GUINode*>(screenNode->getNodeById(getMiniScriptNodeFlattenedId(idMapping, id + "." + to_string(branchIdx))));
1445  auto nodeXPosition = nextLevelNode->getRequestsConstraints().left;
1446  auto deltaX = nextLevelXBestFit - nodeXPosition;
1447  if (deltaX == 0) continue;
1448  addMiniScriptNodeDeltaX(idMapping, id + "." + to_string(branchIdx), *branches[branchIdx].conditionSyntaxTree, deltaX);
1449  }
1450 
1451  // create condition connections
1452  for (auto branchIdx = 0; branchIdx < branches.size(); branchIdx++) {
1453  //
1454  if (branches[branchIdx].conditionSyntaxTree == nullptr ||
1455  (branches[branchIdx].conditionSyntaxTree != nullptr && branches[branchIdx].conditionSyntaxTree->type == MiniScript::ScriptSyntaxTreeNode::SCRIPTSYNTAXTREENODE_LITERAL)) continue;
1456  //
1457  string conditionInputNodeId = flattenedId + "_c" + to_string(branchIdx);
1458  string conditionOutputNodeId = getMiniScriptNodeFlattenedId(idMapping, id + "." + to_string(branchIdx));
1459  auto conditionInputNode = dynamic_cast<GUINode*>(screenNode->getNodeById(conditionInputNodeId));
1460  auto conditionOutputNode = dynamic_cast<GUINode*>(screenNode->getNodeById(conditionOutputNodeId));
1461  if (conditionInputNode == nullptr) {
1462  Console::println("TextEditorTabView::createMiniScriptIfBranchNodes(): missing condition input node: " + conditionInputNodeId);
1463  continue;
1464  }
1465  if (conditionOutputNode == nullptr) {
1466  Console::println("TextEditorTabView::createMiniScriptIfBranchNodes(): missing condition output node: " + conditionOutputNodeId);
1467  continue;
1468  }
1469 
1470  //
1471  auto pinColor = string("color.pintype_boolean");
1472  GUIColor color(GUIParser::getEngineThemeProperties()->get(pinColor, "#ffffff"));
1473 
1474  //
1475  const auto& conditionInputNodeComputedConstraints = conditionInputNode->getComputedConstraints();
1476  const auto& conditionOutputNodeComputedConstraints = conditionOutputNode->getComputedConstraints();
1477  connections.push_back(
1478  {
1480  .srcNodeId = conditionInputNodeId,
1481  .dstNodeId = conditionOutputNodeId,
1482  .red = static_cast<uint8_t>(color.getRed() * 255.0f),
1483  .green = static_cast<uint8_t>(color.getGreen() * 255.0f),
1484  .blue = static_cast<uint8_t>(color.getBlue() * 255.0f),
1485  .alpha = static_cast<uint8_t>(color.getAlpha() * 255.0f),
1486  .x1 = conditionInputNodeComputedConstraints.left,
1487  .y1 = conditionInputNodeComputedConstraints.top + conditionInputNodeComputedConstraints.height / 2,
1488  .x2 = conditionOutputNodeComputedConstraints.left + conditionOutputNodeComputedConstraints.width,
1489  .y2 = conditionOutputNodeComputedConstraints.top + conditionOutputNodeComputedConstraints.height / 2,
1490  }
1491  );
1492  }
1493 
1494  // create branch flows
1495  vector<string> rightNodeIds;
1496  auto branchWidthMax = 0;
1497  y = yInitial;
1498  for (auto branchIdx = 0; branchIdx < branches.size(); branchIdx++) {
1499  // for each branch
1500  auto branchSyntaxTreeNodes = branches[branchIdx].syntaxTreeNodes;
1501  //
1502  GUINode* previousNodeFlowNode = dynamic_cast<GUINode*>(screenNode->getNodeById(flattenedId + "_b" + to_string(branchIdx)));
1503  //
1504  x = xInitial + width;
1505  auto branchHeightMax = 0;
1506  auto branchWidth = 0;
1507  auto branchNodesWidth = 0;
1508  auto branchNodesHeight = 0;
1509  for (auto i = 0; i < branchSyntaxTreeNodes.size(); i++) {
1510  auto branchSyntaxTreeNode = branchSyntaxTreeNodes[i];
1511 
1512  //
1513  auto branchNodeIdx = i;
1514  branchNodesWidth = 0;
1515  branchNodesHeight = 0;
1516  while (handleMiniScriptBranch(idMapping, id + ".b." + to_string(branchIdx) + ".", branchSyntaxTreeNodes, i, x, y, branchNodesWidth, branchNodesHeight, rightNodeIds) == true) {
1517  // advance x
1518  x+= branchNodesWidth + 100;
1519  // store max
1520  branchWidth += branchNodesWidth + 100;
1521  branchHeightMax = Math::max(branchHeightMax, branchNodesHeight);
1522  //
1523  auto nodeFlowInId = getMiniScriptNodeFlattenedId(idMapping, id + ".b." + to_string(branchIdx) + "." + to_string(branchNodeIdx)) + "_fi";
1524  auto nodeFlowOutId = getMiniScriptNodeFlattenedId(idMapping, id + ".b." + to_string(branchIdx) + "." + to_string(branchNodeIdx)) + "_fo";
1525  auto nodeFlowIn = dynamic_cast<GUINode*>(screenNode->getNodeById(nodeFlowInId));
1526  auto nodeFlowOut = dynamic_cast<GUINode*>(screenNode->getNodeById(nodeFlowOutId));
1527  if (previousNodeFlowNode != nullptr && nodeFlowIn != nullptr) {
1528  const auto& previousNodeComputedConstraints = previousNodeFlowNode->getComputedConstraints();
1529  const auto& nodeComputedConstraints = nodeFlowIn->getComputedConstraints();
1530  connections.push_back(
1531  {
1533  .srcNodeId = previousNodeFlowNode->getId(),
1534  .dstNodeId = nodeFlowInId,
1535  .red = 255,
1536  .green = 255,
1537  .blue = 255,
1538  .alpha = 255,
1539  .x1 = previousNodeComputedConstraints.left + previousNodeComputedConstraints.width,
1540  .y1 = previousNodeComputedConstraints.top + previousNodeComputedConstraints.height / 2,
1541  .x2 = nodeComputedConstraints.left,
1542  .y2 = nodeComputedConstraints.top + nodeComputedConstraints.height / 2,
1543  }
1544  );
1545  }
1546  //
1547  if (i >= branchSyntaxTreeNodes.size()) break;
1548  //
1549  previousNodeFlowNode = nodeFlowOut;
1550  //
1551  branchNodeIdx = i;
1552  branchNodesWidth = 0;
1553  branchNodesHeight = 0;
1554  }
1555 
1556  //
1557  if (i >= branchSyntaxTreeNodes.size()) break;
1558  branchSyntaxTreeNode = branchSyntaxTreeNodes[i];
1559 
1560  //
1561  branchNodesWidth = 0;
1562  branchNodesHeight = 0;
1563  createMiniScriptNodes(idMapping, id + ".b." + to_string(branchIdx) + "." + to_string(i), i + 1, branchSyntaxTreeNodes.size() + 1, branchSyntaxTreeNode, Node::NODETYPE_FLOW, x, y, branchNodesWidth, branchNodesHeight, rightNodeIds);
1564 
1565  // advance x
1566  x+= branchNodesWidth + 100;
1567  // store max
1568  branchWidth += branchNodesWidth + 100;
1569  branchHeightMax = Math::max(branchHeightMax, branchNodesHeight);
1570 
1571  // connections
1572  auto nodeFlowInId = getMiniScriptNodeFlattenedId(idMapping, id + ".b." + to_string(branchIdx) + "." + to_string(i)) + "_fi";
1573  auto nodeFlowOutId = getMiniScriptNodeFlattenedId(idMapping, id + ".b." + to_string(branchIdx) + "." + to_string(i)) + "_fo";
1574  auto nodeFlowIn = dynamic_cast<GUINode*>(screenNode->getNodeById(nodeFlowInId));
1575  auto nodeFlowOut = dynamic_cast<GUINode*>(screenNode->getNodeById(nodeFlowOutId));
1576  if (previousNodeFlowNode != nullptr && nodeFlowIn != nullptr) {
1577  const auto& previousNodeComputedConstraints = previousNodeFlowNode->getComputedConstraints();
1578  const auto& nodeComputedConstraints = nodeFlowIn->getComputedConstraints();
1579  connections.push_back(
1580  {
1582  .srcNodeId = previousNodeFlowNode->getId(),
1583  .dstNodeId = nodeFlowInId,
1584  .red = 255,
1585  .green = 255,
1586  .blue = 255,
1587  .alpha = 255,
1588  .x1 = previousNodeComputedConstraints.left + previousNodeComputedConstraints.width,
1589  .y1 = previousNodeComputedConstraints.top + previousNodeComputedConstraints.height / 2,
1590  .x2 = nodeComputedConstraints.left,
1591  .y2 = nodeComputedConstraints.top + nodeComputedConstraints.height / 2,
1592  }
1593  );
1594  }
1595  //
1596  previousNodeFlowNode = nodeFlowOut;
1597  }
1598  //
1599  if (branchSyntaxTreeNodes.empty() == false) branchWidth-= 100;
1600  branchWidthMax = Math::max(branchWidthMax, branchWidth);
1601  y+= branchHeightMax;
1602  }
1603  //
1604  width+= branchWidthMax;
1605  height = Math::max(height, y - yInitial);
1606  //
1607  for (const auto& nodeId: rightNodeIds) createdNodeIds.push_back(nodeId);
1608 
1609  // determine bounding box left and right
1610  auto leftTop = Integer::MAX_VALUE;
1611  auto leftBottom = Integer::MIN_VALUE;
1612  for (const auto& leftNodeId: leftNodeIds) {
1613  auto node = required_dynamic_cast<GUINode*>(screenNode->getNodeById(leftNodeId));
1614  auto nodeTop = node->getRequestsConstraints().top;
1615  auto nodeBottom = node->getRequestsConstraints().top + 200;
1616  leftTop = Math::min(leftTop, nodeTop);
1617  leftBottom = Math::max(leftBottom, nodeBottom);
1618  }
1619 
1620  //
1621  auto rightTop = Integer::MAX_VALUE;
1622  auto rightBottom = Integer::MIN_VALUE;
1623  for (const auto& rightNodeId: rightNodeIds) {
1624  auto node = required_dynamic_cast<GUINode*>(screenNode->getNodeById(rightNodeId));
1625  auto nodeTop = node->getRequestsConstraints().top;
1626  auto nodeBottom = node->getRequestsConstraints().top + 200;
1627  rightTop = Math::min(rightTop, nodeTop);
1628  rightBottom = Math::max(rightBottom, nodeBottom);
1629  }
1630 
1631  //
1632  auto leftHeight = leftBottom - leftTop;
1633  auto rightHeight = rightBottom - rightTop;
1634  if (leftNodeIds.empty() == false && rightNodeIds.empty() == false) {
1635  if (leftHeight == rightHeight) {
1636  // no op
1637  } else
1638  if (leftHeight > rightHeight) {
1639  auto deltaY = (leftHeight - rightHeight) / 2;
1640  for (const auto& rightNodeId: rightNodeIds) {
1641  auto node = required_dynamic_cast<GUINode*>(screenNode->getNodeById(rightNodeId));
1642  node->getRequestsConstraints().top+= deltaY;
1643  }
1644  {
1645  auto node = required_dynamic_cast<GUINode*>(screenNode->getNodeById(flattenedId));
1646  node->getRequestsConstraints().top = yInitial + (leftHeight - 200) / 2;
1647  }
1648  } else {
1649  auto deltaY = (rightHeight - leftHeight) / 2;
1650  for (const auto& leftNodeId: leftNodeIds) {
1651  auto node = required_dynamic_cast<GUINode*>(screenNode->getNodeById(leftNodeId));
1652  node->getRequestsConstraints().top+= deltaY;
1653  }
1654  {
1655  auto node = required_dynamic_cast<GUINode*>(screenNode->getNodeById(flattenedId));
1656  node->getRequestsConstraints().top = yInitial + (rightHeight - 200) / 2;
1657  }
1658  }
1659  }
1660 }
1661 
1662 
1663 bool TextEditorTabView::handleMiniScriptBranch(unordered_map<string, string>& idMapping, const string& idPrefix, const vector<MiniScript::ScriptSyntaxTreeNode*>& syntaxTree, int& i, int x, int y, int& width, int& height, vector<string>& createdNodeIds) {
1664  //
1665  auto syntaxTreeNode = syntaxTree[i];
1666  auto syntaxTreeNodeIdx = i;
1667  // handle if
1668  if (syntaxTreeNode->type == MiniScript::ScriptSyntaxTreeNode::SCRIPTSYNTAXTREENODE_EXECUTE_METHOD &&
1669  (syntaxTreeNode->value.getValueAsString() == "if" ||
1670  syntaxTreeNode->value.getValueAsString() == "forCondition" ||
1671  syntaxTreeNode->value.getValueAsString() == "forTime")) {
1672  // support if depth
1673  auto ifStatement = syntaxTreeNode->value.getValueAsString() == "if";
1674  auto stackDepth = 1;
1675  vector<MiniScriptBranch> branches;
1676  branches.push_back(
1677  {
1678  .name = syntaxTreeNode->value.getValueAsString(),
1679  .conditionSyntaxTree = syntaxTreeNode->arguments.empty() == false?&syntaxTreeNode->arguments[0]:nullptr,
1680  .syntaxTreeNodes = {}
1681  }
1682  );
1683  for (i++; i < syntaxTree.size(); i++) {
1684  auto branchSyntaxTreeNode = syntaxTree[i];
1685  if (branchSyntaxTreeNode->type == MiniScript::ScriptSyntaxTreeNode::SCRIPTSYNTAXTREENODE_EXECUTE_METHOD &&
1686  (branchSyntaxTreeNode->value.getValueAsString() == "if" ||
1687  branchSyntaxTreeNode->value.getValueAsString() == "forCondition" ||
1688  branchSyntaxTreeNode->value.getValueAsString() == "forTime")) {
1689  stackDepth++;
1690  branches[branches.size() - 1].syntaxTreeNodes.push_back(branchSyntaxTreeNode);
1691  } else
1692  if (ifStatement == true && stackDepth == 1 && branchSyntaxTreeNode->type == MiniScript::ScriptSyntaxTreeNode::SCRIPTSYNTAXTREENODE_EXECUTE_METHOD && branchSyntaxTreeNode->value.getValueAsString() == "elseif") {
1693  branches.push_back(
1694  {
1695  .name = branchSyntaxTreeNode->value.getValueAsString(),
1696  .conditionSyntaxTree = branchSyntaxTreeNode->arguments.empty() == false?&branchSyntaxTreeNode->arguments[0]:nullptr,
1697  .syntaxTreeNodes = {}
1698  }
1699  );
1700  } else
1701  if (ifStatement == true && stackDepth == 1 && branchSyntaxTreeNode->type == MiniScript::ScriptSyntaxTreeNode::SCRIPTSYNTAXTREENODE_EXECUTE_METHOD && branchSyntaxTreeNode->value.getValueAsString() == "else") {
1702  branches.push_back(
1703  {
1704  .name = branchSyntaxTreeNode->value.getValueAsString(),
1705  .conditionSyntaxTree = nullptr,
1706  .syntaxTreeNodes = {}
1707  }
1708  );
1709  } else
1710  if (branchSyntaxTreeNode->type == MiniScript::ScriptSyntaxTreeNode::SCRIPTSYNTAXTREENODE_EXECUTE_METHOD && branchSyntaxTreeNode->value.getValueAsString() == "end") {
1711  //
1712  stackDepth--;
1713  // done?
1714  if (stackDepth == 0) {
1715  //
1716  createMiniScriptBranchNodes(idMapping, idPrefix + to_string(syntaxTreeNodeIdx), syntaxTreeNodeIdx, syntaxTree.size(), syntaxTreeNode, Node::NODETYPE_FLOW, branches, x, y, width, height, createdNodeIds);
1717  //
1718  i++;
1719  //
1720  return true;
1721  } else {
1722  // a end that does not belong to our initial if, yarr
1723  branches[branches.size() - 1].syntaxTreeNodes.push_back(branchSyntaxTreeNode);
1724  }
1725  } else {
1726  branches[branches.size() - 1].syntaxTreeNodes.push_back(branchSyntaxTreeNode);
1727  }
1728  }
1729  }
1730  //
1731  return false;
1732 }
1733 
1734 void TextEditorTabView::updateMiniScriptSyntaxTree(int miniScriptScriptIdx) {
1736  this->connections.clear();
1737  this->nodes.clear();
1738  this->miniScriptScriptIdx = miniScriptScriptIdx;
1739  //
1740  unordered_map<string, string> idMapping;
1741  // structure
1743  auto syntaxTrees = textEditorTabController->getMiniScriptSyntaxTrees();
1744 
1745  //
1746  auto width = 0;
1747  auto height = 0;
1748  auto y = 200;
1749  auto yMax = y;
1750  auto xMax = 200;
1751  for (auto i = 0; i < syntaxTrees.size(); i++) {
1752  //
1753  auto x = 200;
1754  width = 0;
1755  height = 0;
1756  createMiniScriptScriptNode(idMapping, to_string(i), syntaxTrees[i].type, syntaxTrees[i].condition, syntaxTrees[i].name, &syntaxTrees[i].conditionSyntaxTree, x, y, width, height);
1757 
1758  //
1759  y+= height + 100;
1760  yMax = Math::max(y + height, yMax);
1761  xMax = Math::max(x + width, xMax);
1762  }
1763 
1764  // TODO: with absolute align of nodes content width/height is not yet computed in UI
1769  } else {
1770  // script
1771  // copy syntax tree
1772  auto syntaxTree = textEditorTabController->getMiniScriptSyntaxTrees()[miniScriptScriptIdx].syntaxTree;
1773  // construct syntax tree as vector of pointers to the nodes
1774  vector<MiniScript::ScriptSyntaxTreeNode*> syntaxTreeNodes;
1775  for (auto& syntaxTreeNode: syntaxTree) syntaxTreeNodes.push_back(&syntaxTreeNode);
1776  // remove end node if we have any
1777  if (syntaxTreeNodes.empty() == false &&
1778  syntaxTreeNodes[syntaxTreeNodes.size() - 1]->type == MiniScript::ScriptSyntaxTreeNode::SCRIPTSYNTAXTREENODE_EXECUTE_METHOD &&
1779  syntaxTreeNodes[syntaxTreeNodes.size() - 1]->value.getValueAsString() == "end") {
1780  syntaxTreeNodes.erase(syntaxTreeNodes.begin() + syntaxTreeNodes.size() - 1);
1781  }
1782  //
1783  auto width = 0;
1784  auto height = 0;
1785  auto x = 200;
1786  auto y = 200;
1787  auto yMax = y;
1788  vector<string> createdNodeIds;
1789  GUINode* previousNodeFlowNode = nullptr;
1790  for (auto i = 0; i < syntaxTreeNodes.size(); i++) {
1791  auto syntaxTreeNode = syntaxTreeNodes[i];
1792 
1793  //
1794  width = 0;
1795  height = 0;
1796  auto branchNodeIdx = i;
1797  while (handleMiniScriptBranch(idMapping, string(), syntaxTreeNodes, i, x, y, width, height, createdNodeIds) == true) {
1798  //
1799  x+= width + 100;
1800  yMax = Math::max(y + height, yMax);
1801  //
1802  auto nodeFlowInId = getMiniScriptNodeFlattenedId(idMapping, to_string(branchNodeIdx)) + "_fi";
1803  auto nodeFlowOutId = getMiniScriptNodeFlattenedId(idMapping, to_string(branchNodeIdx)) + "_fo";
1804  auto nodeFlowIn = dynamic_cast<GUINode*>(screenNode->getNodeById(nodeFlowInId));
1805  auto nodeFlowOut = dynamic_cast<GUINode*>(screenNode->getNodeById(nodeFlowOutId));
1806  if (previousNodeFlowNode != nullptr && nodeFlowIn != nullptr) {
1807  const auto& previousNodeComputedConstraints = previousNodeFlowNode->getComputedConstraints();
1808  const auto& nodeComputedConstraints = nodeFlowIn->getComputedConstraints();
1809  connections.push_back(
1810  {
1812  .srcNodeId = previousNodeFlowNode->getId(),
1813  .dstNodeId = nodeFlowInId,
1814  .red = 255,
1815  .green = 255,
1816  .blue = 255,
1817  .alpha = 255,
1818  .x1 = previousNodeComputedConstraints.left + previousNodeComputedConstraints.width,
1819  .y1 = previousNodeComputedConstraints.top + previousNodeComputedConstraints.height / 2,
1820  .x2 = nodeComputedConstraints.left,
1821  .y2 = nodeComputedConstraints.top + nodeComputedConstraints.height / 2,
1822  }
1823  );
1824  }
1825  //
1826  if (i >= syntaxTreeNodes.size()) break;
1827  //
1828  previousNodeFlowNode = nodeFlowOut;
1829  //
1830  branchNodeIdx = i;
1831  width = 0;
1832  height = 0;
1833  }
1834 
1835  //
1836  if (i >= syntaxTreeNodes.size()) break;
1837  syntaxTreeNode = syntaxTreeNodes[i];
1838 
1839  //
1840  width = 0;
1841  height = 0;
1842  createMiniScriptNodes(idMapping, to_string(i), i, syntaxTreeNodes.size(), syntaxTreeNode, Node::NODETYPE_FLOW, x, y, width, height, createdNodeIds);
1843 
1844  //
1845  x+= width + 100;
1846  yMax = Math::max(y + height, yMax);
1847 
1848  // connections
1849  auto nodeFlowInId = getMiniScriptNodeFlattenedId(idMapping, to_string(i)) + "_fi";
1850  auto nodeFlowOutId = getMiniScriptNodeFlattenedId(idMapping, to_string(i)) + "_fo";
1851  auto nodeFlowIn = dynamic_cast<GUINode*>(screenNode->getNodeById(nodeFlowInId));
1852  auto nodeFlowOut = dynamic_cast<GUINode*>(screenNode->getNodeById(nodeFlowOutId));
1853  if (previousNodeFlowNode != nullptr && nodeFlowIn != nullptr) {
1854  const auto& previousNodeComputedConstraints = previousNodeFlowNode->getComputedConstraints();
1855  const auto& nodeComputedConstraints = nodeFlowIn->getComputedConstraints();
1856  connections.push_back(
1857  {
1859  .srcNodeId = previousNodeFlowNode->getId(),
1860  .dstNodeId = nodeFlowInId,
1861  .red = 255,
1862  .green = 255,
1863  .blue = 255,
1864  .alpha = 255,
1865  .x1 = previousNodeComputedConstraints.left + previousNodeComputedConstraints.width,
1866  .y1 = previousNodeComputedConstraints.top + previousNodeComputedConstraints.height / 2,
1867  .x2 = nodeComputedConstraints.left,
1868  .y2 = nodeComputedConstraints.top + nodeComputedConstraints.height / 2,
1869  }
1870  );
1871  }
1872  //
1873  previousNodeFlowNode = nodeFlowOut;
1874  }
1875 
1876  // TODO: with absolute align of nodes content width/height is not yet computed in UI
1881  }
1882 
1883  // Bug: Work around! Sometimes layouting is not issued! Need to check!
1885 
1886  //
1887  nodeIdx = idMapping.size();
1888 
1889  //
1891 }
1892 
1894  for (auto& connection: connections) {
1895  auto srcNode = dynamic_cast<GUINode*>(screenNode->getNodeById(connection.srcNodeId));
1896  auto dstNode = dynamic_cast<GUINode*>(screenNode->getNodeById(connection.dstNodeId));
1897  if (srcNode == nullptr) {
1898  continue;
1899  } else
1900  if (dstNode == nullptr) {
1901  continue;
1902  }
1903  const auto& srcNodeComputedConstraints = srcNode->getComputedConstraints();
1904  const auto& dstNodeComputedConstraints = dstNode->getComputedConstraints();
1905  switch (connection.type) {
1907  connection.x1 = srcNodeComputedConstraints.left + srcNodeComputedConstraints.width;
1908  connection.y1 = srcNodeComputedConstraints.top + srcNodeComputedConstraints.height / 2;
1909  connection.x2 = dstNodeComputedConstraints.left;
1910  connection.y2 = dstNodeComputedConstraints.top + dstNodeComputedConstraints.height / 2;
1911  break;
1913  connection.x1 = srcNodeComputedConstraints.left;
1914  connection.y1 = srcNodeComputedConstraints.top + srcNodeComputedConstraints.height / 2;
1915  connection.x2 = dstNodeComputedConstraints.left + dstNodeComputedConstraints.width;
1916  connection.y2 = dstNodeComputedConstraints.top + dstNodeComputedConstraints.height / 2;
1917  break;
1918  default: break;
1919  }
1920  }
1921 }
1922 
1924  auto textNodeController = required_dynamic_cast<GUIStyledTextNodeController*>(textNode->getController());
1925  return textNodeController->getIndex();
1926 }
1927 
1928 bool TextEditorTabView::find(const string& findString, bool matchCase, bool wholeWord, bool selection, bool firstSearch, int& index) {
1929  cancelFind();
1930  return TextTools::find(textNode, findString, matchCase, wholeWord, selection, firstSearch, index);
1931 }
1932 
1933 int TextEditorTabView::count(const string& findString, bool matchCase, bool wholeWord, bool selection) {
1934  cancelFind();
1935  countEnabled = true;
1936  return TextTools::count(textNode, findString, matchCase, wholeWord, selection);
1937 }
1938 
1939 bool TextEditorTabView::replace(const string& findString, const string& replaceString, bool matchCase, bool wholeWord, bool selection, int& index) {
1940  cancelFind();
1941  auto success = TextTools::replace(textNode, findString, replaceString, matchCase, wholeWord, selection, index);
1942  TextFormatter::getInstance()->format(extension, textNode, 0, textNode->getText().size());
1943  return success;
1944 }
1945 
1946 bool TextEditorTabView::replaceAll(const string& findString, const string& replaceString, bool matchCase, bool wholeWord, bool selection) {
1947  auto success = TextTools::replaceAll(textNode, findString, replaceString, matchCase, wholeWord, selection);
1948  cancelFind();
1949  return success;
1950 }
1951 
1953  TextFormatter::getInstance()->format(extension, textNode, 0, textNode->getText().size());
1954  countEnabled = false;
1955 }
1956 
1958  if (visualEditor == false) {
1959  required_dynamic_cast<GUIStyledTextNodeController*>(textNode->getController())->redo();
1960  }
1961 }
1962 
1964  if (visualEditor == false) {
1965  required_dynamic_cast<GUIStyledTextNodeController*>(textNode->getController())->undo();
1966  }
1967 }
1968 
1970  if (visualEditor == false) {
1971  required_dynamic_cast<GUIStyledTextNodeController*>(textNode->getController())->selectAll();
1972  }
1973 }
1974 
1976  if (visualEditor == false) {
1977  required_dynamic_cast<GUIStyledTextNodeController*>(textNode->getController())->cut();
1978  }
1979 }
1980 
1982  if (visualEditor == false) {
1983  required_dynamic_cast<GUIStyledTextNodeController*>(textNode->getController())->copy();
1984  }
1985 }
1986 
1988  if (visualEditor == false) {
1989  required_dynamic_cast<GUIStyledTextNodeController*>(textNode->getController())->paste();
1990  }
1991 }
1992 
1994  if (visualEditor == false) {
1995  required_dynamic_cast<GUIStyledTextNodeController*>(textNode->getController())->delete_();
1996  }
1997 }
1998 
1999 void TextEditorTabView::createSourceCodeFromNodes(string& sourceCode, const Node* node, int depth) {
2000  while (node != nullptr) {
2001  createSourceCodeFromNode(sourceCode, node, depth);
2002  sourceCode+= "\n";
2003  auto nextNodeId = getNextNodeId(node->id);
2004  if (nextNodeId.empty() == true) break;
2005  node = getNodeById(nextNodeId);
2006  }
2007 }
2008 
2009 void TextEditorTabView::createSourceCodeFromNode(string& sourceCode, const Node* node, int depth) {
2010  //
2011  string spacePrefix;
2012  for (auto i = 0; i < depth; i++) spacePrefix+= "\t";
2013  if (node->value == "if") {
2014  //
2015  for (auto conditionIdx = 0; conditionIdx < 100; conditionIdx++) {
2016  auto branchNodeId = getConnectedBranchNodeId(node->id, conditionIdx);
2017  auto branchNode = getNodeById(branchNodeId);
2018  //
2019  auto conditionNodeId = getConnectedConditionNodeId(node->id, conditionIdx);
2020  auto conditionNode = getNodeById(conditionNodeId);
2021  if (conditionNode != nullptr) {
2022  sourceCode+= spacePrefix;
2023  if (conditionIdx == 0) {
2024  sourceCode+= "if ";
2025  } else {
2026  sourceCode+= "elseif ";
2027  }
2028  sourceCode+= "(";
2029  createSourceCodeFromNode(sourceCode, conditionNode, 0);
2030  sourceCode+= ")";
2031  sourceCode+= "\n";
2032  }
2033  if (branchNode != nullptr) {
2034  if (conditionNode == nullptr) {
2035  sourceCode+= spacePrefix;
2036  sourceCode+= "else";
2037  sourceCode+= "\n";
2038  }
2039  createSourceCodeFromNodes(sourceCode, branchNode, depth + 1);
2040  if (conditionNode == nullptr) {
2041  sourceCode+= spacePrefix;
2042  sourceCode+= "end";
2043  break;
2044  }
2045  }
2046  if (conditionNode == nullptr && branchNode == nullptr) {
2047  sourceCode+= spacePrefix;
2048  sourceCode+= "end";
2049  break;
2050  }
2051  }
2052  } else {
2053  sourceCode+= spacePrefix;
2054  sourceCode+= node->value + "(";
2055  // arguments
2056  string argumentsSourceCode;
2057  for (auto argumentIdx = 0; argumentIdx < 100; argumentIdx++) {
2058  auto argumentNodeId = getConnectedArgumentNodeId(node->id, argumentIdx);
2059  if (argumentNodeId.empty() == true) {
2060  // check for literal
2061  auto argumentNodeId = getArgumentNodeId(node->id, argumentIdx);
2062  auto literalInput = dynamic_cast<GUINode*>(screenNode->getNodeById(argumentNodeId + "_input"));
2063  if (literalInput != nullptr) {
2064  auto literalInputValue = literalInput->getController()->getValue().getString();
2065  if (argumentsSourceCode.empty() == false) argumentsSourceCode+= ", ";
2066  // implicitely literal
2067  MiniScript::ScriptVariable value;
2068  MiniScript::ScriptStatement scriptStatement(
2069  MiniScript::LINE_NONE,
2070  MiniScript::STATEMENTIDX_NONE,
2071  string(),
2072  string(),
2073  MiniScript::STATEMENTIDX_NONE
2074  );
2075  value.setImplicitTypedValueFromStringView(literalInputValue, textEditorTabController->getMiniScript(), scriptStatement);
2076  if (value.getType() == MiniScript::TYPE_STRING) {
2077  argumentsSourceCode+= "\"" + StringTools::replace(literalInputValue, "\"", "\\\"") + "\"";
2078  } else {
2079  argumentsSourceCode+= literalInputValue;
2080  }
2081  }
2082  } else {
2083  // check for connected argument
2084  auto argumentNode = getNodeById(argumentNodeId);
2085  if (argumentNode == nullptr) continue;
2086  //
2087  if (argumentsSourceCode.empty() == false) argumentsSourceCode+= ", ";
2088  createSourceCodeFromNode(argumentsSourceCode, argumentNode, 0);
2089  }
2090  }
2091  sourceCode+= argumentsSourceCode + ")";
2092  }
2093 }
2094 
2095 void TextEditorTabView::deleteConnection(const string& nodeId) {
2096  for (auto i = 0; i < connections.size(); i++) {
2097  const auto& connection = connections[i];
2098  auto srcNodeMatch = connection.srcNodeId == nodeId || StringTools::startsWith(connection.srcNodeId, nodeId + "_") == true;
2099  auto dstNodeMatch = connection.dstNodeId == nodeId || StringTools::startsWith(connection.dstNodeId, nodeId + "_") == true;
2100  if (srcNodeMatch == true || dstNodeMatch == true) {
2101  connections.erase(connections.begin() + i);
2102  i--;
2103  }
2104  }
2105 }
2106 
2107 void TextEditorTabView::deleteNode(const string& nodeId) {
2108  auto nodeIt = nodes.find(nodeId);
2109  if (nodeIt == nodes.end()) return;
2110  nodes.erase(nodeIt);
2111  deleteConnection(nodeId);
2112  screenNode->removeNodeById(nodeId, false);
2113 
2114  // Bug: Work around! Sometimes layouting is not issued! Need to check!
2116 
2117  //
2119 }
2120 
2121 void TextEditorTabView::createConnection(const string& guiNodeId) {
2122  Console::println("TextEditorTabView::createConnection(): " + guiNodeId);
2123  // return value as argument
2124  if (guiNodeId.find("_r_") != string::npos) {
2125  auto connectionNodeId = StringTools::substring(guiNodeId, 0, guiNodeId.find("_r_") + 2);
2126  auto nodeId = StringTools::substring(guiNodeId, 0, guiNodeId.find("_r_"));
2127  auto argumentOutputNode = dynamic_cast<GUINode*>(screenNode->getNodeById(connectionNodeId));
2128  if (argumentOutputNode != nullptr) {
2129  deleteConnection(connectionNodeId);
2130  const auto& argumentOutputNodeComputedConstraints = argumentOutputNode->getComputedConstraints();
2131  auto pinColor = string("color.pintype_undefined");
2132  GUIColor color(GUIParser::getEngineThemeProperties()->get(pinColor, "#ffffff"));
2133  connections.push_back(
2134  {
2136  .srcNodeId = string(),
2137  .dstNodeId = connectionNodeId,
2138  .red = static_cast<uint8_t>(color.getRed() * 255.0f),
2139  .green = static_cast<uint8_t>(color.getGreen() * 255.0f),
2140  .blue = static_cast<uint8_t>(color.getBlue() * 255.0f),
2141  .alpha = static_cast<uint8_t>(color.getAlpha() * 255.0f),
2142  .x1 = argumentOutputNodeComputedConstraints.left + argumentOutputNodeComputedConstraints.width,
2143  .y1 = argumentOutputNodeComputedConstraints.top + argumentOutputNodeComputedConstraints.height / 2,
2144  .x2 = argumentOutputNodeComputedConstraints.left + argumentOutputNodeComputedConstraints.width,
2145  .y2 = argumentOutputNodeComputedConstraints.top + argumentOutputNodeComputedConstraints.height / 2,
2146  }
2147  );
2148  //
2149  createConnectionIdx = connections.size() - 1;
2151  }
2152  } else
2153  // flow output
2154  if (guiNodeId.find("_fo_") != string::npos) {
2155  auto connectionNodeId = StringTools::substring(guiNodeId, 0, guiNodeId.find("_fo_") + 3);
2156  auto flowOutputFlowNode = dynamic_cast<GUINode*>(screenNode->getNodeById(connectionNodeId));
2157  if (flowOutputFlowNode != nullptr) {
2158  deleteConnection(connectionNodeId);
2159  const auto& flowOutputNodeComputedConstraints = flowOutputFlowNode->getComputedConstraints();
2160  auto pinColor = string("color.pintype_undefined");
2161  GUIColor color(GUIParser::getEngineThemeProperties()->get(pinColor, "#ffffff"));
2162  connections.push_back(
2163  {
2165  .srcNodeId = connectionNodeId,
2166  .dstNodeId = string(),
2167  .red = 255,
2168  .green = 255,
2169  .blue = 255,
2170  .alpha = 255,
2171  .x1 = flowOutputNodeComputedConstraints.left + flowOutputNodeComputedConstraints.width,
2172  .y1 = flowOutputNodeComputedConstraints.top + flowOutputNodeComputedConstraints.height / 2,
2173  .x2 = flowOutputNodeComputedConstraints.left + flowOutputNodeComputedConstraints.width,
2174  .y2 = flowOutputNodeComputedConstraints.top + flowOutputNodeComputedConstraints.height / 2,
2175  }
2176  );
2177  //
2178  createConnectionIdx = connections.size() - 1;
2180  }
2181  } else
2182  // flow input
2183  if (guiNodeId.find("_fi_") != string::npos) {
2184  auto connectionNodeId = StringTools::substring(guiNodeId, 0, guiNodeId.find("_fi_") + 3);
2185  auto flowInputNode = dynamic_cast<GUINode*>(screenNode->getNodeById(connectionNodeId));
2186  if (flowInputNode != nullptr) {
2187  deleteConnection(connectionNodeId);
2188  const auto& flowInputNodeComputedConstraints = flowInputNode->getComputedConstraints();
2189  auto pinColor = string("color.pintype_undefined");
2190  GUIColor color(GUIParser::getEngineThemeProperties()->get(pinColor, "#ffffff"));
2191  connections.push_back(
2192  {
2194  .srcNodeId = string(),
2195  .dstNodeId = connectionNodeId,
2196  .red = 255,
2197  .green = 255,
2198  .blue = 255,
2199  .alpha = 255,
2200  .x1 = flowInputNodeComputedConstraints.left,
2201  .y1 = flowInputNodeComputedConstraints.top + flowInputNodeComputedConstraints.height / 2,
2202  .x2 = flowInputNodeComputedConstraints.left,
2203  .y2 = flowInputNodeComputedConstraints.top + flowInputNodeComputedConstraints.height / 2,
2204  }
2205  );
2206  //
2207  createConnectionIdx = connections.size() - 1;
2209  }
2210  } else
2211  // argument
2212  if (guiNodeId.find("_a") != string::npos) {
2213  auto connectionNodeId = StringTools::substring(guiNodeId, 0, guiNodeId.find("_", guiNodeId.find("_a") + 2));
2214  auto argumentInputNode = dynamic_cast<GUINode*>(screenNode->getNodeById(connectionNodeId));
2215  if (argumentInputNode != nullptr) {
2216  deleteConnection(connectionNodeId);
2217  const auto& argumentInputNodeComputedConstraints = argumentInputNode->getComputedConstraints();
2218  auto pinColor = string("color.pintype_undefined");
2219  GUIColor color(GUIParser::getEngineThemeProperties()->get(pinColor, "#ffffff"));
2220  connections.push_back(
2221  {
2223  .srcNodeId = guiNodeId,
2224  .dstNodeId = string(),
2225  .red = static_cast<uint8_t>(color.getRed() * 255.0f),
2226  .green = static_cast<uint8_t>(color.getGreen() * 255.0f),
2227  .blue = static_cast<uint8_t>(color.getBlue() * 255.0f),
2228  .alpha = static_cast<uint8_t>(color.getAlpha() * 255.0f),
2229  .x1 = argumentInputNodeComputedConstraints.left,
2230  .y1 = argumentInputNodeComputedConstraints.top + argumentInputNodeComputedConstraints.height / 2,
2231  .x2 = argumentInputNodeComputedConstraints.left,
2232  .y2 = argumentInputNodeComputedConstraints.top + argumentInputNodeComputedConstraints.height / 2,
2233  }
2234  );
2235  //
2236  createConnectionIdx = connections.size() - 1;
2238  }
2239  }
2240 }
2241 
2242 void TextEditorTabView::finishCreateConnection(int mouseX, int mouseY) {
2243  if (isCreatingConnection() == false) return;
2244 
2245  //
2246  unordered_set<string> nodeIds;
2247  screenNode->determineNodesByCoordinate(Vector2(mouseX, mouseY), nodeIds);
2248  for (const auto& nodeId: nodeIds) {
2249  // return value as argument
2250  if (nodeId.find("_r_") != string::npos) {
2252  textEditorTabController->showInfoPopUp("Warning", "You can not connect a Argument Input with a " + getCreateConnectionModeName(createConnectionMode));
2253  } else {
2254  auto connectionNodeId = StringTools::substring(nodeId, 0, nodeId.find("_r_") + 2);
2255  auto argumentNode = dynamic_cast<GUINode*>(screenNode->getNodeById(connectionNodeId));
2256  if (argumentNode != nullptr) {
2257  // TODO: set node type
2258  auto connection = connections[createConnectionIdx];
2259  connections.erase(connections.begin() + createConnectionIdx);
2260  createConnectionIdx = -1;
2261  deleteConnection(connectionNodeId);
2262  const auto& argumentNodeComputedConstraints = argumentNode->getComputedConstraints();
2263  connection.dstNodeId = connectionNodeId;
2264  connection.x2 = argumentNodeComputedConstraints.left + argumentNodeComputedConstraints.width;
2265  connection.y2 = argumentNodeComputedConstraints.top + argumentNodeComputedConstraints.height / 2;
2266  connections.push_back(connection);
2267  //
2268  break;
2269  }
2270  }
2271  } else
2272  // flow output
2273  if (nodeId.find("_fo_") != string::npos) {
2274  auto connectionNodeId = StringTools::substring(nodeId, 0, nodeId.find("_fo_") + 3);
2276  textEditorTabController->showInfoPopUp("Warning", "You can not connect a Flow Input with a " + getCreateConnectionModeName(createConnectionMode));
2277  } else {
2278  auto flowNode = dynamic_cast<GUINode*>(screenNode->getNodeById(connectionNodeId));
2279  if (flowNode != nullptr) {
2280  // TODO: set node type
2281  auto connection = connections[createConnectionIdx];
2282  connections.erase(connections.begin() + createConnectionIdx);
2283  createConnectionIdx = -1;
2284  deleteConnection(connectionNodeId);
2285  const auto& flowNodeComputedConstraints = flowNode->getComputedConstraints();
2286  connection.srcNodeId = connectionNodeId;
2287  connection.x1 = flowNodeComputedConstraints.left;
2288  connection.y1 = flowNodeComputedConstraints.top + flowNodeComputedConstraints.height / 2;
2289  connections.push_back(connection);
2290  //
2291  break;
2292  }
2293  }
2294  } else
2295  // flow input
2296  if (nodeId.find("_fi_") != string::npos) {
2297  auto connectionNodeId = StringTools::substring(nodeId, 0, nodeId.find("_fi_") + 3);
2299  textEditorTabController->showInfoPopUp("Warning", "You can not connect a Flow Output with a " + getCreateConnectionModeName(createConnectionMode));
2300  } else {
2301  auto flowNode = dynamic_cast<GUINode*>(screenNode->getNodeById(connectionNodeId));
2302  if (flowNode != nullptr) {
2303  // TODO: set node type
2304  auto connection = connections[createConnectionIdx];
2305  connections.erase(connections.begin() + createConnectionIdx);
2306  createConnectionIdx = -1;
2307  deleteConnection(connectionNodeId);
2308  const auto& flowNodeComputedConstraints = flowNode->getComputedConstraints();
2309  connection.dstNodeId = connectionNodeId;
2310  connection.x2 = flowNodeComputedConstraints.left + flowNodeComputedConstraints.width;
2311  connection.y2 = flowNodeComputedConstraints.top + flowNodeComputedConstraints.height / 2;
2312  connections.push_back(connection);
2313  //
2314  break;
2315  }
2316  }
2317  } else
2318  // argument
2319  if (nodeId.find("_a") != string::npos) {
2320  auto connectionNodeId = StringTools::substring(nodeId, 0, nodeId.find("_", nodeId.find("_a") + 2));
2322  textEditorTabController->showInfoPopUp("Warning", "You can not connect a Argument Output with a " + getCreateConnectionModeName(createConnectionMode));
2323  } else {
2324  auto argumentNode = dynamic_cast<GUINode*>(screenNode->getNodeById(connectionNodeId));
2325  if (argumentNode != nullptr) {
2326  // TODO: set node type
2327  auto connection = connections[createConnectionIdx];
2328  connections.erase(connections.begin() + createConnectionIdx);
2329  createConnectionIdx = -1;
2330  deleteConnection(connectionNodeId);
2331  const auto& argumentNodeComputedConstraints = argumentNode->getComputedConstraints();
2332  connection.srcNodeId = connectionNodeId;
2333  connection.x1 = argumentNodeComputedConstraints.left;
2334  connection.y1 = argumentNodeComputedConstraints.top + argumentNodeComputedConstraints.height / 2;
2335  connections.push_back(connection);
2336  //
2337  break;
2338  }
2339  }
2340  }
2341  }
2342 
2343  //
2344  if (createConnectionIdx != -1) connections.erase(connections.begin() + createConnectionIdx);
2346  createConnectionIdx = -1;
2347 
2348  //
2350 }
2351 
2353  switch (textEditorTabController->getContextMenuType()) {
2354  case TextEditorTabController::CONTEXTMENUTYPE_NODE:
2355  {
2356  // clear
2358 
2359  // delete
2360  class OnNodeDeleteAction: public virtual Action
2361  {
2362  public:
2363  void performAction() override {
2364  textEditorTabView->deleteNode(nodeId);
2365  }
2366  OnNodeDeleteAction(TextEditorTabView* textEditorTabView, const string& nodeId): textEditorTabView(textEditorTabView), nodeId(nodeId) {
2367  }
2368  private:
2369  TextEditorTabView* textEditorTabView;
2370  string nodeId;
2371  };
2372  popUps->getContextMenuScreenController()->addMenuItem("Delete Node", "contextmenu_delete", new OnNodeDeleteAction(this, textEditorTabController->getContextMenuNodeId()));
2373 
2374  //
2376 
2377  //
2378  break;
2379  }
2380  case TextEditorTabController::CONTEXTMENUTYPE_CANVAS:
2381  {
2382  // clear
2386 
2387  //
2389 
2390  //
2391  break;
2392  }
2393  default:
2394  break;
2395  }
2396 
2397  //
2398  textEditorTabController->resetContextMenu();
2399 }
#define MAX_ENTRIES
Color 4 definition class.
Definition: Color4.h:18
float getRed() const
Definition: Color4.h:92
float getGreen() const
Definition: Color4.h:107
float getAlpha() const
Definition: Color4.h:137
float getBlue() const
Definition: Color4.h:122
void drawPixel(int x, int y, uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha)
Draw pixel.
void drawBezier(const vector< Vector2 > &controlPoints, uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha)
Draw bezier curve.
void clear(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha)
Clear texture with given clear color.
Engine main class.
Definition: Engine.h:131
GUI parser.
Definition: GUIParser.h:40
GUI module class.
Definition: GUI.h:64
GUINodeConditions & getActiveConditions()
void setTexture(Texture *texture)
Set texture.
void set(const string &condition)
Set condition.
virtual const MutableString & getValue()=0
GUI node base class.
Definition: GUINode.h:64
float computeParentChildrenRenderOffsetXTotal()
Definition: GUINode.cpp:952
GUINode_RequestedConstraints & getRequestsConstraints()
Definition: GUINode.h:405
GUINodeController * getController()
Definition: GUINode.h:661
float computeParentChildrenRenderOffsetYTotal()
Definition: GUINode.cpp:962
const string & getId()
Definition: GUINode.h:339
GUINode_ComputedConstraints & getComputedConstraints()
Definition: GUINode.h:412
GUI parent node base class thats supporting child nodes.
Definition: GUIParentNode.h:42
void clearSubNodes()
Clear sub nodes.
void determineNodesByCoordinate(const Vector2 &coordinate, unordered_set< string > &nodeIds) override
Determine nodes by coordinate.
GUI screen node that represents a screen that can be rendered via GUI system.
Definition: GUIScreenNode.h:72
void addContextMenuRequestListener(GUIContextMenuRequestListener *listener)
Add context menu request listener.
void addMoveListener(GUIMoveListener *listener)
Add move listener.
void addTooltipRequestListener(GUITooltipRequestListener *listener)
Add tooltip request listener.
GUINode * forceInvalidateLayout(GUINode *node)
Actually do the invalidate layout.
void removeNodeById(const string &nodeId, bool resetScrollOffsets)
Remove GUI node by id.
GUINode * getInnerNodeById(const string &nodeId)
Get inner GUI node by id.
GUINode * getNodeById(const string &nodeId)
Get GUI node by id.
int getNextDelimiter(int index, const string &delimiters)
Get next delimiter.
void insertText(int32_t idx, int c)
Insert character c at idx.
int getPreviousDelimiter(int index, const string &delimiters)
Get previous delimiter.
void removeText(int32_t idx, int32_t count)
Remove characters at idx with given length.
const MutableString & getText() const
Standard math functions.
Definition: Math.h:19
Vector2 class representing vector2 mathematical structure and operations with x, y components.
Definition: Vector2.h:20
File system singleton class.
Definition: FileSystem.h:17
void setMiniScriptMethodSelectionListener(MiniScriptMethodSelectionListener *miniScriptMethodSelectionListener)
Set MiniScript method selection listener.
void show(int mouseX, int mouseY)
Shows the pop up.
void setupVisualCodeAddNodeContextMenu()
Clear and setup visual code add node context menu.
void addMenuItem(const string &text, const string &id, Action *action=nullptr)
Add menu item.
void restoreOutlinerState(const TabView::OutlinerState &outlinerState)
Restore outliner state.
void storeOutlinerState(TabView::OutlinerState &outlinerState)
Store outliner state.
void setDetailsContent(const string &xml)
Set details content.
ContextMenuScreenController * getContextMenuScreenController()
Definition: PopUps.h:96
void saveFile(const string &pathName, const string &fileName)
Save file.
const string getConnectedBranchNodeId(const string &nodeId, int branchIdx)
Find branch node id.
const string getScriptVariableTypePinColor(EngineMiniScript::ScriptVariableType type)
Get script variable type pin color.
void finishCreateConnection(int mouseX, int mouseY)
Finish creating connection.
unique_ptr< GUIStyledTextNodeController::CodeCompletionListener > textNodeCodeCompletionListener
const string getNextNodeId(const string &nodeId)
Find next node.
void createSourceCodeFromNodes(string &sourceCode, const Node *node, int depth=0)
Create source code from nodes.
bool replace(const string &findString, const string &replaceString, bool matchCase, bool wholeWord, bool selection, int &index)
Replace string.
void initialize() override
Initiates the view.
static const string getCreateConnectionModeName(CreateConnectionMode createConnectionMode)
Node * getNodeById(const string &nodeId)
Get node by id.
void handleInputEvents() override
Handle input events that have not yet been processed.
void createSourceCodeFromNode(string &sourceCode, const Node *node, int depth=0)
Create source code from node.
void deleteConnection(const string &nodeId)
Delete connection.
bool handleMiniScriptBranch(unordered_map< string, string > &idMapping, const string &idPrefix, const vector< EngineMiniScript::ScriptSyntaxTreeNode * > &syntaxTree, int &i, int x, int y, int &width, int &height, vector< string > &createdNodeIds)
Handle EngineMiniScript branch.
unique_ptr< const TextFormatter::CodeCompletion > codeCompletion
bool isCreatingConnection()
Returns if creating a connection currently.
unique_ptr< TextEditorTabController > textEditorTabController
void createMiniScriptNode(const string &methodName, int x, int y)
Create EngineMiniScript node.
const string getMiniScriptNodeFlattenedId(unordered_map< string, string > &idMapping, const string &hierarchicalId)
Get EngineMiniScript node flattened id from hierarchical id.
unordered_map< string, string > methodOperatorMap
void addMiniScriptNodeDeltaX(unordered_map< string, string > &idMapping, const string &id, const EngineMiniScript::ScriptSyntaxTreeNode &syntaxTreeNode, int deltaX)
Adds a delta X value to UI node with given id and all nodes down the statement syntax tree.
void createMiniScriptBranchNodes(unordered_map< string, string > &idMapping, const string &id, int syntaxTreeNodeIdx, int syntaxTreeNodeCount, const EngineMiniScript::ScriptSyntaxTreeNode *syntaxTreeNode, Node::NodeType nodeType, const vector< MiniScriptBranch > &branches, int x, int y, int &width, int &height, vector< string > &createdNodeIds, int depth=0)
Create UI nodes for branch nodes like if, elseif, else, end; forTime, end; forCondition,...
void createConnection(const string &guiNodeId)
Create connection with given start or end node id.
const string getConnectedConditionNodeId(const string &nodeId, int conditionIdx)
Find condition node id.
void deleteNode(const string &nodeId)
Delete node.
int count(const string &findString, bool matchCase, bool wholeWord, bool selection)
Count string.
unique_ptr< GUIStyledTextNodeController::ChangeListener > textNodeChangeListener
bool find(const string &findString, bool matchCase, bool wholeWord, bool selection, bool firstSearch, int &index)
Find string.
void createMiniScriptConnections()
Create miniscript connections.
void updateMiniScriptSyntaxTree(int miniScriptScriptIdx)
Update miniscript syntax tree.
void updateRendering() override
Update rendering.
void createMiniScriptScriptNode(unordered_map< string, string > &idMapping, const string &id, EngineMiniScript::Script::ScriptType scriptType, const string &condition, const string &readableName, const EngineMiniScript::ScriptSyntaxTreeNode *conditionSyntaxTreeNode, int x, int y, int &width, int &height)
Create UI nodes for EngineMiniScript script node syntax tree, which matches a event or function in En...
const string getArgumentNodeId(const string &nodeId, int argumentIdx)
Create argument node id.
unique_ptr< DynamicColorTexture > linesTexture
void createMiniScriptNodes(unordered_map< string, string > &idMapping, const string &id, int syntaxTreeNodeIdx, int syntaxTreeNodeCount, const EngineMiniScript::ScriptSyntaxTreeNode *syntaxTreeNode, Node::NodeType nodeType, int x, int y, int &width, int &height, vector< string > &createdNodeIds, int depth=0)
Create UI nodes for given statement syntax tree, which matches a statement in miniscript.
bool replaceAll(const string &findString, const string &replaceString, bool matchCase, bool wholeWord, bool selection)
Replace all string.
const string getConnectedArgumentNodeId(const string &nodeId, int argumentIdx)
Find argument node id.
void getViewPort(GUINode *viewPortNode, int &left, int &top, int &width, int &height, int &offsetX, int &offsetY)
Determine viewport screen constraints.
Definition: EditorView.cpp:427
EditorScreenController * getScreenController()
Definition: EditorView.h:69
Character class.
Definition: Character.h:17
Integer class.
Definition: Integer.h:25
char getCharAt(int32_t idx) const
Get char at given binary index.
Definition: MutableString.h:78
const string & getString() const
Properties class, which helps out with storeing or loading key value pairs from/to property files.
Definition: Properties.h:23
String tools class.
Definition: StringTools.h:22
std::exception Exception
Exception base class.
Definition: Exception.h:18
GUI move listener interface.
Action Interface.
Definition: Action.h:11