/// <summary> /// Return the generator code of each block type in an array in a given language. /// </summary> /// <param name="blockXmlMap"></param> Map of block type to XML. /// <param name="generatorLanguage"> E.g. "JavaScript", "Python", "PHP", "Lua", /// "Dart"</param> /// <returns>The concatenation of each block's generator code in the /// desired format.</returns> public string getGeneratorCode(Dictionary <string, Element> blockXmlMap, string generatorLanguage) { var multiblockCode = new JsArray <string>(); // Define the custom blocks in order to be able to create instances of // them in the exporter workspace. this.addBlockDefinitions(blockXmlMap); foreach (var blockType in blockXmlMap.Keys) { string blockGenCode; var xml = blockXmlMap[blockType]; if (xml != null) { // Render the preview block in the hidden workspace. var tempBlock = FactoryUtils.getDefinedBlock(blockType, this.hiddenWorkspace); // Get generator stub for the given block and add to generator code. blockGenCode = FactoryUtils.getGeneratorStub(tempBlock, generatorLanguage); } else { // Append warning comment and write to console. blockGenCode = "// No generator stub generated for " + blockType + ". Block was not found in Block Library Storage."; Console.WriteLine("No block generator stub generated for " + blockType + ". Block was not found in Block Library Storage."); } multiblockCode.Push(blockGenCode); } return(multiblockCode.Join("\n\n")); }
/// <summary> /// Return the given language code of each block type in an array. /// </summary> /// <param name="blockXmlMap"> Map of block type to XML.</param> /// <param name="definitionFormat"> "JSON" or "JavaScript"</param> /// <returns>The concatenation of each block's language code in the /// desired format.</returns> public string getBlockDefinitions(Dictionary <string, Element> blockXmlMap, string definitionFormat) { var blockCode = new JsArray <string>(); foreach (var blockType in blockXmlMap.Keys) { string code; var xml = blockXmlMap[blockType]; if (xml != null) { // Render and get block from hidden workspace. var rootBlock = this.getRootBlockFromXml_(xml); if (rootBlock != null) { // Generate the block's definition. code = FactoryUtils.getBlockDefinition(blockType, rootBlock, definitionFormat, this.hiddenWorkspace); // Add block's definition to the definitions to return. } else { // Append warning comment and write to console. code = "// No block definition generated for " + blockType + ". Could not find root block in XML stored for this block."; Console.WriteLine("No block definition generated for " + blockType + ". Could not find root block in XML stored for this block."); } } else { // Append warning comment and write to console. code = "// No block definition generated for " + blockType + ". Block was not found in Block Library Storage."; Console.WriteLine("No block definition generated for " + blockType + ". Block was not found in Block Library Storage."); } blockCode.Push(code); } // Surround json with [] and comma separate items. if (definitionFormat == "JSON") { return("[" + blockCode.Join(",\n") + "]"); } return(blockCode.Join("\n\n")); }
/// <summary> /// Get this warning's texts. /// </summary> /// <returns>All texts concatenated into one string.</returns> public override string getText() { var allWarnings = new JsArray <string>(); foreach (var id in this.text_.Keys) { allWarnings.Push(this.text_[id]); } return(allWarnings.Join("\n")); }
private string join_(int[] ints, string dem) { var sb = new JsArray <string>(); foreach (int i in ints) { sb.Push(i.ToString()); } return(sb.Join(dem)); }
/// <summary> /// Generate a unique ID. This should be globally unique. /// 87 characters ^ 20 length > 128 bits (better than a UUID). /// </summary> /// <returns>A globally unique ID string.</returns> public static string genUid() { var length = 20; var soupLength = Core.genUid_soup_.Length; var id = new JsArray <string>(); for (var i = 0; i < length; i++) { int r = (int)(Script.Random() * soupLength); id.Push(Core.genUid_soup_.CharAt(r)); } return(id.Join("")); }
/// <summary> /// Reassemble the array of words into text, with the specified line breaks. /// </summary> /// <param name="words">Array of each word.</param> /// <param name="wordBreaks">Array of line breaks.</param> /// <returns>Plain text.</returns> private static string wrapToText_(string[] words, bool?[] wordBreaks) { var text = new JsArray <string>(); for (var i = 0; i < words.Length; i++) { text.Push(words[i]); if (wordBreaks[i].HasValue) { text.Push(wordBreaks[i].Value ? "\n" : " "); } } return(text.Join("")); }
/// <summary> /// Tied to the "All Used" button in the Block Exporter's "Select" button. /// Selects all blocks stored in block library and used in workspace factory. /// </summary> public void selectUsedBlocks() { // Deselect all blocks. this.view.deselectAllBlocks(); // Get list of block types that are in block library and used in workspace // factory. var storedBlockTypes = this.blockLibStorage.getBlockTypes(); var sharedBlockTypes = new JsArray <string>(); // Keep list of custom block types used but not in library. var unstoredCustomBlockTypes = new JsArray <string>(); for (var i = 0; i < this.usedBlockTypes.Length; i++) { var blockType = this.usedBlockTypes[i]; if (storedBlockTypes.IndexOf(blockType) != -1) { sharedBlockTypes.Push(blockType); } else if (Array.IndexOf(StandardCategories.coreBlockTypes, blockType) == -1) { unstoredCustomBlockTypes.Push(blockType); } } // Select each shared block type. for (var i = 0; i < sharedBlockTypes.Length; i++) { var blockType = sharedBlockTypes[i]; this.view.select(blockType); } this.view.listSelectedBlocks(); if (unstoredCustomBlockTypes.Length > 0) { // Warn user to import block defifnitions and generator code for blocks // not in their Block Library nor Blockly's standard library. var blockTypesText = unstoredCustomBlockTypes.Join(", "); var customWarning = "Custom blocks used in workspace factory but not " + "stored in block library:\n " + blockTypesText + "\n\nDon\'t forget to include block definitions and generator code " + "for these blocks."; Window.Alert(customWarning); } }
/// <summary> /// Support function for button creation. /// </summary> /// <param name="parentNode">Container the button should be added to.</param> /// <param name="label">Button label.</param> /// <param name="opt_className">Class name for button, which will be used /// in addition to "goog-date-picker-btn".</param> private HTMLElement createButton_(HTMLTableDataCellElement parentNode, string label, string opt_className = null) { var classes = new JsArray <string>() { le.getCssName(this.getBaseCssClass(), "btn") }; if (opt_className != null) { classes.Push(opt_className); } var el = this.getDomHelper().createElement(goog.dom.TagName.BUTTON); el.ClassName = classes.Join(" "); el.AppendChild(this.getDomHelper().createTextNode(label)); parentNode.AppendChild(el); return(el); }
/// <summary> /// Redraw the graph with the current angle. /// </summary> public void updateGraph_() { if (this.gauge_ == null) { return; } var angleDegrees = Script.ParseFloat(this.getText()) + FieldAngle.OFFSET; var angleRadians = goog.math.toRadians(angleDegrees); var path = new JsArray <string> { "M " + FieldAngle.HALF + "," + FieldAngle.HALF }; double x2 = FieldAngle.HALF; double y2 = FieldAngle.HALF; if (!Double.IsNaN(angleRadians)) { var angle1 = goog.math.toRadians(FieldAngle.OFFSET); var x1 = System.Math.Cos(angle1) * FieldAngle.RADIUS; var y1 = System.Math.Sin(angle1) * -FieldAngle.RADIUS; if (FieldAngle.CLOCKWISE) { angleRadians = 2 * angle1 - angleRadians; } x2 += System.Math.Cos(angleRadians) * FieldAngle.RADIUS; y2 -= System.Math.Sin(angleRadians) * FieldAngle.RADIUS; // Don't ask how the flag calculations work. They just do. var largeFlag = System.Math.Abs(System.Math.Floor((angleRadians - angle1) / System.Math.PI) % 2); if (FieldAngle.CLOCKWISE) { largeFlag = 1 - largeFlag; } var sweepFlag = FieldAngle.CLOCKWISE ? 1 : 0; path.Push(" l " + x1 + "," + y1 + " A " + FieldAngle.RADIUS + "," + FieldAngle.RADIUS + " 0 " + largeFlag + " " + sweepFlag + " " + x2 + "," + y2 + " z"); } this.gauge_.SetAttribute("d", path.Join("")); this.line_.SetAttribute("x2", x2.ToString()); this.line_.SetAttribute("y2", y2.ToString()); }
/// <summary> /// Draw the path of the block. /// Move the fields to the correct locations. /// </summary> /// <param name="iconWidth">Offset of first row due to icons.</param> /// <param name="inputRows">2D array of objects, each /// containing position information.</param> private void renderDraw_(double iconWidth, InputRow inputRows) { this.startHat_ = false; // Reset the height to zero and let the rendering process add in // portions of the block height as it goes. (e.g. hats, inputs, etc.) this.height = 0; // Should the top and bottom left corners be rounded or square? if (this.outputConnection != null) { this.squareTopLeftCorner_ = true; this.squareBottomLeftCorner_ = true; } else { this.squareTopLeftCorner_ = false; this.squareBottomLeftCorner_ = false; // If this block is in the middle of a stack, square the corners. if (this.previousConnection != null) { var prevBlock = this.previousConnection.targetBlock(); if (prevBlock != null && prevBlock.getNextBlock() == this) { this.squareTopLeftCorner_ = true; } } else if (BlockSvg.START_HAT) { // No output or previous connection. this.squareTopLeftCorner_ = true; this.startHat_ = true; this.height += BlockSvg.START_HAT_HEIGHT; inputRows.rightEdge = System.Math.Max(inputRows.rightEdge, 100); } var nextBlock = this.getNextBlock(); if (nextBlock != null) { this.squareBottomLeftCorner_ = true; } } // Assemble the block's path. var steps = new JsArray <string>(); var inlineSteps = new JsArray <string>(); // The highlighting applies to edges facing the upper-left corner. // Since highlighting is a two-pixel wide border, it would normally overhang // the edge of the block by a pixel. So undersize all measurements by a pixel. var highlightSteps = new JsArray <string>(); var highlightInlineSteps = new JsArray <string>(); this.renderDrawTop_(steps, highlightSteps, inputRows.rightEdge); var cursorY = this.renderDrawRight_(steps, highlightSteps, inlineSteps, highlightInlineSteps, inputRows, iconWidth); this.renderDrawBottom_(steps, highlightSteps, cursorY); this.renderDrawLeft_(steps, highlightSteps); var pathString = steps.Join(" ") + "\n" + inlineSteps.Join(" "); this.svgPath_.SetAttribute("d", pathString); this.svgPathDark_.SetAttribute("d", pathString); pathString = highlightSteps.Join(" ") + "\n" + highlightInlineSteps.Join(" "); this.svgPathLight_.SetAttribute("d", pathString); if (this.RTL) { // Mirror the block's path. this.svgPath_.SetAttribute("transform", "scale(-1 1)"); this.svgPathLight_.SetAttribute("transform", "scale(-1 1)"); this.svgPathDark_.SetAttribute("transform", "translate(1,1) scale(-1 1)"); } }
/// <summary> /// Get the generator code for a given block. /// </summary> /// <param name="block"> Rendered block in preview workspace.</param> /// <param name="generatorLanguage">"JavaScript", "Python", "PHP", "Lua", /// "Dart".</param> /// <returns>Generator code for multiple blocks.</returns> public static string getGeneratorStub(Blockly.Block block, string generatorLanguage) { var makeVar = new Func <string, string, string>((root, name) => { name = name.ToLowerCase().Replace(new Regex(@"\W", RegexOptions.Multiline), "_"); return(" var " + root + "_" + name); }); // The makevar function lives in the original update generator. var language = generatorLanguage; var code = new JsArray <string>(); code.Push("Blockly." + language + "[\"" + block.type + "\"] = (block) => {"); // Generate getters for any fields or inputs. for (var i = 0; i < block.inputList.Length; i++) { string name; var input = block.inputList[i]; for (var j = 0; j < input.fieldRow.Length; j++) { var field = input.fieldRow[j]; name = field.name; if (String.IsNullOrEmpty(name)) { continue; } if (typeof(Blockly.FieldVariable).IsInstanceOfType(field)) { // Subclass of Blockly.FieldDropdown, must test first. code.Push(makeVar("variable", name) + " = Blockly." + language + ".variableDB_.getName(block.getFieldValue(\"" + name + "\"), Blockly.Variables.NAME_TYPE);"); } else if (typeof(Blockly.FieldAngle).IsInstanceOfType(field)) { // Subclass of Blockly.FieldTextInput, must test first. code.Push(makeVar("angle", name) + " = block.getFieldValue(\"" + name + "\");"); } else if (typeof(Blockly.FieldDate).IsInstanceOfType(field)) { // Blockly.FieldDate may not be compiled into Blockly. code.Push(makeVar("date", name) + " = block.getFieldValue(\"" + name + "\");"); } else if (typeof(Blockly.FieldColour).IsInstanceOfType(field)) { code.Push(makeVar("colour", name) + " = block.getFieldValue(\"" + name + "\");"); } else if (typeof(Blockly.FieldCheckbox).IsInstanceOfType(field)) { code.Push(makeVar("checkbox", name) + " = block.getFieldValue(\"" + name + "\") == \"TRUE\";"); } else if (typeof(Blockly.FieldDropdown).IsInstanceOfType(field)) { code.Push(makeVar("dropdown", name) + " = block.getFieldValue(\"" + name + "\");"); } else if (typeof(Blockly.FieldNumber).IsInstanceOfType(field)) { code.Push(makeVar("number", name) + " = block.getFieldValue(\"" + name + "\");"); } else if (typeof(Blockly.FieldTextInput).IsInstanceOfType(field)) { code.Push(makeVar("text", name) + " = block.getFieldValue(\"" + name + "\");"); } } name = input.name; if (!String.IsNullOrEmpty(name)) { if (input.type == Blockly.Core.INPUT_VALUE) { code.Push(makeVar("value", name) + " = Blockly." + language + ".valueToCode(block, \"" + name + "\", Blockly." + language + ".ORDER_ATOMIC);"); } else if (input.type == Blockly.Core.NEXT_STATEMENT) { code.Push(makeVar("statements", name) + " = Blockly." + language + ".statementToCode(block, \"" + name + "\");"); } } } // Most languages end lines with a semicolon. Python does not. var lineEnd = new Dictionary <string, string> { { "JavaScript", ";" }, { "Python", "" }, { "PHP", ";" }, { "Dart", ";" } }; code.Push(" // TODO: Assemble " + language + " into code variable."); if (block.outputConnection != null) { code.Push(" var code = \"...\";"); code.Push(" // TODO: Change ORDER_NONE to the correct strength."); code.Push(" return [code, Blockly." + language + ".ORDER_NONE];"); } else { code.Push(" var code = \"...\" + (lineEnd[language] || \"\") + \"\\n\";"); code.Push(" return code;"); } code.Push("};"); return(code.Join("\n")); }
/// <summary> /// Returns field strings and any config. /// </summary> /// <param name="block">Input block.</param> /// <returns>Field strings.</returns> private static JsArray <string> getFieldsJs_(Blockly.Block block) { var fields = new JsArray <string>(); while (block != null) { if (!block.disabled && !block.getInheritedDisabled()) { switch (block.type) { case "field_static": // Result: "hello" fields.Push(FactoryUtils.escapeString(block.getFieldValue("TEXT"))); break; case "field_input": // Result: new Blockly.FieldTextInput("Hello"), "GREET" fields.Push("new Blockly.FieldTextInput(" + FactoryUtils.escapeString(block.getFieldValue("TEXT")) + "), " + FactoryUtils.escapeString(block.getFieldValue("FIELDNAME"))); break; case "field_number": // Result: new Blockly.FieldNumber(10, 0, 100, 1), "NUMBER" var args = new JsArray <double> { Script.ParseFloat(block.getFieldValue("VALUE")), Script.ParseFloat(block.getFieldValue("MIN")), Script.ParseFloat(block.getFieldValue("MAX")), Script.ParseFloat(block.getFieldValue("PRECISION")) }; // Remove any trailing arguments that aren't needed. if (args[3] == 0) { args.Pop(); if (args[2] == Double.PositiveInfinity) { args.Pop(); if (args[1] == Double.NegativeInfinity) { args.Pop(); } } } fields.Push("new Blockly.FieldNumber(" + args.Join(", ") + "), " + FactoryUtils.escapeString(block.getFieldValue("FIELDNAME"))); break; case "field_angle": // Result: new Blockly.FieldAngle(90), "ANGLE" fields.Push("new Blockly.FieldAngle(" + Script.ParseFloat(block.getFieldValue("ANGLE")) + "), " + FactoryUtils.escapeString(block.getFieldValue("FIELDNAME"))); break; case "field_checkbox": // Result: new Blockly.FieldCheckbox("TRUE"), "CHECK" fields.Push("new Blockly.FieldCheckbox(" + FactoryUtils.escapeString(block.getFieldValue("CHECKED")) + "), " + FactoryUtils.escapeString(block.getFieldValue("FIELDNAME"))); break; case "field_colour": // Result: new Blockly.FieldColour("#ff0000"), "COLOUR" fields.Push("new Blockly.FieldColour(" + FactoryUtils.escapeString(block.getFieldValue("COLOUR")) + "), " + FactoryUtils.escapeString(block.getFieldValue("FIELDNAME"))); break; case "field_date": // Result: new Blockly.FieldDate("2015-02-04"), "DATE" fields.Push("new Blockly.FieldDate(" + FactoryUtils.escapeString(block.getFieldValue("DATE")) + "), " + FactoryUtils.escapeString(block.getFieldValue("FIELDNAME"))); break; case "field_variable": // Result: new Blockly.FieldVariable("item"), "VAR" var varname = FactoryUtils.escapeString(block.getFieldValue("TEXT") ?? null); fields.Push("new Blockly.FieldVariable(" + varname + "), " + FactoryUtils.escapeString(block.getFieldValue("FIELDNAME"))); break; case "field_dropdown": // Result: // new Blockly.FieldDropdown([["yes", "1"], ["no", "0"]]), "TOGGLE" var options = new JsArray <string>(); for (var i = 0; i < ((FieldDropdown)block).optionCount_; i++) { options[i] = "[" + FactoryUtils.escapeString(block.getFieldValue("USER" + i)) + ", " + FactoryUtils.escapeString(block.getFieldValue("CPU" + i)) + "]"; } if (options.Length > 0) { fields.Push("new Blockly.FieldDropdown([" + options.Join(", ") + "]), " + FactoryUtils.escapeString(block.getFieldValue("FIELDNAME"))); } break; case "field_image": // Result: new Blockly.FieldImage("http://...", 80, 60) var src = FactoryUtils.escapeString(block.getFieldValue("SRC")); var width = Convert.ToDouble(block.getFieldValue("WIDTH")); var height = Convert.ToDouble(block.getFieldValue("HEIGHT")); var alt = FactoryUtils.escapeString(block.getFieldValue("ALT")); fields.Push("new Blockly.FieldImage(" + src + ", " + width + ", " + height + ", " + alt + ")"); break; } } block = block.nextConnection == null ? null : block.nextConnection.targetBlock(); } return(fields); }
/// <summary> /// Update the language code as JavaScript. /// </summary> /// <param name="blockType"> Name of block.</param> /// <param name="rootBlock"> Factory_base block.</param> /// <param name="workspace"></param> Where the root block lives. /// <returns>Generated language code.</returns> private static string formatJavaScript_(string blockType, Blockly.Block rootBlock, Blockly.Workspace workspace) { var code = new JsArray <string>(); code.Push("Blockly.Core.Blocks[\"" + blockType + "\"] = {"); code.Push(" init: () => {"); // Generate inputs. var TYPES = new Dictionary <string, string>() { { "input_value", "appendValueInput" }, { "input_statement", "appendStatementInput" }, { "input_dummy", "appendDummyInput" } }; var contentsBlock = rootBlock.getInputTargetBlock("INPUTS"); while (contentsBlock != null) { if (!contentsBlock.disabled && !contentsBlock.getInheritedDisabled()) { var name = ""; // Dummy inputs don't have names. Other inputs do. if (contentsBlock.type != "input_dummy") { name = FactoryUtils.escapeString(contentsBlock.getFieldValue("INPUTNAME")); } code.Push(" this." + TYPES[contentsBlock.type] + "(" + name + ")"); var check = FactoryUtils.getOptTypesFrom(contentsBlock, "TYPE"); if (!String.IsNullOrEmpty(check)) { code.Push(" .setCheck(" + check + ")"); } var align = contentsBlock.getFieldValue("ALIGN"); if (align != "LEFT") { code.Push(" .setAlign(Blockly.ALIGN_" + align + ")"); } var fields = FactoryUtils.getFieldsJs_( contentsBlock.getInputTargetBlock("FIELDS")); for (var i = 0; i < fields.Length; i++) { code.Push(" .appendField(" + fields[i] + ")"); } // Add semicolon to last line to finish the statement. code[code.Length - 1] += ";"; } contentsBlock = contentsBlock.nextConnection == null ? null : contentsBlock.nextConnection.targetBlock(); } // Generate inline/external switch. if (rootBlock.getFieldValue("INLINE") == "EXT") { code.Push(" this.setInputsInline(false);"); } else if (rootBlock.getFieldValue("INLINE") == "INT") { code.Push(" this.setInputsInline(true);"); } // Generate output, or next/previous connections. switch (rootBlock.getFieldValue("CONNECTIONS")) { case "LEFT": code.Push(FactoryUtils.connectionLineJs_("setOutput", "OUTPUTTYPE", workspace)); break; case "BOTH": code.Push( FactoryUtils.connectionLineJs_("setPreviousStatement", "TOPTYPE", workspace)); code.Push( FactoryUtils.connectionLineJs_("setNextStatement", "BOTTOMTYPE", workspace)); break; case "TOP": code.Push( FactoryUtils.connectionLineJs_("setPreviousStatement", "TOPTYPE", workspace)); break; case "BOTTOM": code.Push( FactoryUtils.connectionLineJs_("setNextStatement", "BOTTOMTYPE", workspace)); break; } // Generate colour. var colourBlock = rootBlock.getInputTargetBlock("COLOUR"); if (colourBlock != null && !colourBlock.disabled) { var hue = Script.ParseFloat(colourBlock.getFieldValue("HUE")); if (!Double.IsNaN(hue)) { code.Push(" this.setColour(" + hue + ");"); } } code.Push(" this.setTooltip(\"\");"); code.Push(" this.setHelpUrl(\"http://www.example.com/\");"); code.Push(" }"); code.Push("};"); return(code.Join("\n")); }
/// <summary> /// Update the language code as JSON. /// </summary> /// <param name="blockType"> Name of block.</param> /// <param name="rootBlock">Factory_base block.</param> /// <returns>Generanted language code.</returns> private static string formatJson_(string blockType, Blockly.Block rootBlock) { var JS = new Dictionary <string, object>(); // Type is not used by Blockly, but may be used by a loader. JS["type"] = blockType; // Generate inputs. var message = new JsArray <string>(); var args = new JsArray <object>(); var contentsBlock = rootBlock.getInputTargetBlock("INPUTS"); Blockly.Block lastInput = null; while (contentsBlock != null) { if (!contentsBlock.disabled && !contentsBlock.getInheritedDisabled()) { var fields = FactoryUtils.getFieldsJson_( contentsBlock.getInputTargetBlock("FIELDS")); for (var i = 0; i < fields.Length; i++) { if (fields[i] is string str) { message.Push(str.Replace(new Regex("%", RegexOptions.Multiline), "%%")); } else { args.Push(fields[i]); message.Push("%" + args.Length); } } var input = new Dictionary <string, object>(); input.Add("type", contentsBlock.type); // Dummy inputs don't have names. Other inputs do. if (contentsBlock.type != "input_dummy") { input["name"] = contentsBlock.getFieldValue("INPUTNAME"); } var check = JSON.Parse( FactoryUtils.getOptTypesFrom(contentsBlock, "TYPE") ?? "null"); if (check != null) { input["check"] = check; } var align = contentsBlock.getFieldValue("ALIGN"); if (align != "LEFT") { input["align"] = align; } args.Push(input); message.Push("%" + args.Length); lastInput = contentsBlock; } contentsBlock = contentsBlock.nextConnection == null ? null : contentsBlock.nextConnection.targetBlock(); } // Remove last input if dummy and not empty. if (lastInput != null && lastInput.type == "input_dummy") { var fields = lastInput.getInputTargetBlock("FIELDS"); if (fields != null && FactoryUtils.getFieldsJson_(fields).Join("").Trim() != "") { var align = lastInput.getFieldValue("ALIGN"); if (align != "LEFT") { JS["lastDummyAlign0"] = align; } args.Pop(); message.Pop(); } } JS["message0"] = message.Join(" "); if (args.Length > 0) { JS["args0"] = args; } // Generate inline/external switch. if (rootBlock.getFieldValue("INLINE") == "EXT") { JS["inputsInline"] = false; } else if (rootBlock.getFieldValue("INLINE") == "INT") { JS["inputsInline"] = true; } // Generate output, or next/previous connections. switch (rootBlock.getFieldValue("CONNECTIONS")) { case "LEFT": JS["output"] = JSON.Parse( FactoryUtils.getOptTypesFrom(rootBlock, "OUTPUTTYPE") ?? "null"); break; case "BOTH": JS["previousStatement"] = JSON.Parse( FactoryUtils.getOptTypesFrom(rootBlock, "TOPTYPE") ?? "null"); JS["nextStatement"] = JSON.Parse( FactoryUtils.getOptTypesFrom(rootBlock, "BOTTOMTYPE") ?? "null"); break; case "TOP": JS["previousStatement"] = JSON.Parse( FactoryUtils.getOptTypesFrom(rootBlock, "TOPTYPE") ?? "null"); break; case "BOTTOM": JS["nextStatement"] = JSON.Parse( FactoryUtils.getOptTypesFrom(rootBlock, "BOTTOMTYPE") ?? "null"); break; } // Generate colour. var colourBlock = rootBlock.getInputTargetBlock("COLOUR"); if (colourBlock != null && !colourBlock.disabled) { var hue = Script.ParseFloat(colourBlock.getFieldValue("HUE")); JS["colour"] = hue; } JS["tooltip"] = ""; JS["helpUrl"] = "http://www.example.com/"; return(JSON.Stringify(JS, null, " ")); }
/// <summary> /// Draw the arrow between the bubble and the origin. /// </summary> private void renderArrow_() { var steps = new JsArray <string>(); // Find the relative coordinates of the center of the bubble. var relBubbleX = this.width_ / 2; var relBubbleY = this.height_ / 2; // Find the relative coordinates of the center of the anchor. var relAnchorX = -this.relativeLeft_; var relAnchorY = -this.relativeTop_; if (relBubbleX == relAnchorX && relBubbleY == relAnchorY) { // Null case. Bubble is directly on top of the anchor. // Short circuit this rather than wade through divide by zeros. steps.Push("M " + relBubbleX + "," + relBubbleY); } else { // Compute the angle of the arrow's line. var rise = relAnchorY - relBubbleY; var run = relAnchorX - relBubbleX; if (this.workspace_.RTL) { run *= -1; } var hypotenuse = System.Math.Sqrt(rise * rise + run * run); var angle = System.Math.Acos(run / hypotenuse); if (rise < 0) { angle = 2 * System.Math.PI - angle; } // Compute a line perpendicular to the arrow. var rightAngle = angle + System.Math.PI / 2; if (rightAngle > System.Math.PI * 2) { rightAngle -= System.Math.PI * 2; } var rightRise = System.Math.Sin(rightAngle); var rightRun = System.Math.Cos(rightAngle); // Calculate the thickness of the base of the arrow. var bubbleSize = this.getBubbleSize(); var thickness = (bubbleSize.width + bubbleSize.height) / Bubble.ARROW_THICKNESS; thickness = System.Math.Min(thickness, System.Math.Min(bubbleSize.width, bubbleSize.height)) / 4; // Back the tip of the arrow off of the anchor. var backoffRatio = 1 - Bubble.ANCHOR_RADIUS / hypotenuse; relAnchorX = relBubbleX + backoffRatio * run; relAnchorY = relBubbleY + backoffRatio * rise; // Coordinates for the base of the arrow. var baseX1 = relBubbleX + thickness * rightRun; var baseY1 = relBubbleY + thickness * rightRise; var baseX2 = relBubbleX - thickness * rightRun; var baseY2 = relBubbleY - thickness * rightRise; // Distortion to curve the arrow. var swirlAngle = angle + this.arrow_radians_; if (swirlAngle > System.Math.PI * 2) { swirlAngle -= System.Math.PI * 2; } var swirlRise = System.Math.Sin(swirlAngle) * hypotenuse / Bubble.ARROW_BEND; var swirlRun = System.Math.Cos(swirlAngle) * hypotenuse / Bubble.ARROW_BEND; steps.Push("M" + baseX1 + "," + baseY1); steps.Push("C" + (baseX1 + swirlRun) + "," + (baseY1 + swirlRise) + " " + relAnchorX + "," + relAnchorY + " " + relAnchorX + "," + relAnchorY); steps.Push("C" + relAnchorX + "," + relAnchorY + " " + (baseX2 + swirlRun) + "," + (baseY2 + swirlRise) + " " + baseX2 + "," + baseY2); } steps.Push("z"); this.bubbleArrow_.SetAttribute("d", steps.Join(" ")); }