美文网首页
Blockly源码分析

Blockly源码分析

作者: 知晨创客坊 | 来源:发表于2020-04-10 13:13 被阅读0次

积木图形

Blockly中的积木是如何画出来的呢?请看下图:


-c
  1. 积木没有输入和输出,只有卡合的凹凸部分
  2. 积木输出部分,凸出的部分,可以放入输入积木中
  3. 积木输入部分,等待其他输出积木卡合
  4. 简单积木输出部分
  5. 独立的外部积木卡合到count积木中

svg path介绍

path元素是SVG基本形状中最强大的一个,它不仅能创建其他基本形状,还能创建更多其他形状。你可以用path元素绘制矩形、圆形、椭圆、折线形、多边形,以及一些其他的形状,例如贝塞尔曲线、2次曲线等曲线。path元素的形状是通过属性d来定义的,属性d的值是一个“命令+参数”的序列。

not积木svg

<g xmlns="http://www.w3.org/2000/svg" data-id="Q*?`b($N=1x.O/NlMwfi" class="blocklyDraggable" transform="translate(87.92968749999997,112.50781249999989)">
<path class="blocklyPathDark" transform="translate(1,1)" fill="#496684" d=" m 8,0  h 49.989990234375  v 5  H 57.989990234375  V 5  H 57.989990234375  c 0,10  -8,-8  -8,7.5  s 8,-2.5  8,7.5  v 0  H 57.989990234375  V 20  V 24  h -49.989990234375  H 8  V 20  c 0,-10  -8,8  -8,-7.5  s 8,2.5  8,-7.5 z "/>
<path class="blocklyPath" stroke="none" fill="#5b80a5" d=" m 8,0  h 49.989990234375  v 5  H 57.989990234375  V 5  H 57.989990234375  c 0,10  -8,-8  -8,7.5  s 8,-2.5  8,7.5  v 0  H 57.989990234375  V 20  V 24  h -49.989990234375  H 8  V 20  c 0,-10  -8,8  -8,-7.5  s 8,2.5  8,-7.5 z "/>
<path class="blocklyPathLight" stroke="#8ca6c0" d=" m 8,0  m 0.5,0.5  H 57.489990234375  H 57.489990234375  M 57.989990234375,5  m -5,14.3  l 3.68,-2.1  M 8.5,23.5  M 8.5,23.5  V 20  v -1.5  m -7.36,-0.5  q -1.52,-5.5  0,-11  m 7.36,1  V 0.5  "/>
    <g transform="translate(18,5)">
    <text class="blocklyText" y="13.09" dy="0" x="0">not</text></g>
</g>

not积木由三个path元素和一个文本元素组成
.blocklyPath 通过path,将积木画出来,并填充积木颜色

path如何生成的呢

从上文积木的特点可以看出,积木的卡合(上下文、输入输出)部分位置固定。

上文卡合处:位置在m 0,0 m 0,8 a 8 8 0 0,1 8,-8 h 7 l 6,4 3,0 6,-4顶端半圆'm 0,0 m 0,8 a 8 8 0 0,1 8,-8',左移'h 7',凹槽画线'l 6,4 3,0 6,-4'。

输出处:位置在m 8,0 H 8 V 20 c 0,-10 -8,8 -8,-7.5 s 8,2.5 8,-7.5 z绝对定位到'H 8 V 20',画凸起'c 0,-10 -8,8 -8,-7.5 s 8,2.5 8,-7.5 z'

可以看出积木的生成是有规律可言的,因此在生成svg path时,需要根据积木的特点,来组合生成path路径。

积木的解析

Toolbox工具箱

将xml刷新到菜单树上,菜单树的生成为div形式。


Toolbox -c
Blockly.Toolbox.prototype.renderTree = function(languageTree) {
  // 如果菜单存在清空菜单
  if (this.tree_) {
    this.tree_.dispose();  // Delete any existing content.
    this.lastCategory_ = null;
  }
  // 创建菜单树
  var tree = new Blockly.tree.TreeControl(this,
      /** @type {!Blockly.tree.BaseNode.Config} */ (this.config_));
  this.tree_ = tree;
  // 设置菜单对应的点击事件
  tree.setSelectedItem(null);
  tree.onBeforeSelected(this.handleBeforeTreeSelected_);
  tree.onAfterSelected(this.handleAfterTreeSelected_);
  var openNode = null;
  if (languageTree) {
    this.tree_.blocks = [];
    this.hasColours_ = false;
    // 根据languageTree 生成到菜单树上
    openNode = this.syncTrees_(
        languageTree, this.tree_, this.workspace_.options.pathToMedia);

    if (this.tree_.blocks.length) {
      throw Error('Toolbox cannot have both blocks and categories ' +
          'in the root level.');
    }
    // Fire a resize event since the toolbox may have changed width and height.
    this.workspace_.resizeContents();
  }
  // 将菜单树插入到div中进行渲染
  tree.render(this.HtmlDiv);
  // 选择菜单项
  if (openNode) {
    tree.setSelectedItem(openNode);
  }
  this.addColour_();
  this.position();

  // Trees have an implicit orientation of vertical, so we only need to set this
  // when the toolbox is in horizontal mode.
  if (this.horizontalLayout_) {
    Blockly.utils.aria.setState(
        /** @type {!Element} */ (this.tree_.getElement()),
        Blockly.utils.aria.State.ORIENTATION, 'horizontal');
  }
};

Xml解析器

当Toolbox工具箱渲染后,产生菜单树,Toolbox生成时,将xml生成菜单树,菜单树中的菜单扔保留的是xml数据。当点击分类时,右侧会弹出对应的积木,积木由分类对应的xml生成。

Blockly.Xml.domToBlock = function(xmlBlock, workspace) {
  // xmlBlock 为xml,数据存在菜单树中
  if (xmlBlock instanceof Blockly.Workspace) {
    var swap = xmlBlock;
    xmlBlock = /** @type {!Element} */ (workspace);
    workspace = swap;
    console.warn('Deprecated call to Blockly.Xml.domToBlock, ' +
                 'swap the arguments.');
  }
  // 因为要对积木进行创建和编辑所以先禁止事件,创建完成后,再将事件打开
  Blockly.Events.disable();
  // 获取所有变量,变量是全局的
  var variablesBeforeCreation = workspace.getAllVariables();
  try {
    // 获取顶部Block
    var topBlock = Blockly.Xml.domToBlockHeadless_(xmlBlock, workspace);
    // 顶部Block相关的积木
    var blocks = topBlock.getDescendants(false);
    if (workspace.rendered) {
    //初始化每一个积木的svg,并渲染
      for (var i = blocks.length - 1; i >= 0; i--) {
        // 初始化,将积木的输入,图标等信息初始化
        blocks[i].initSvg();
      }
      for (var i = blocks.length - 1; i >= 0; i--) {
        // 渲染积木
        blocks[i].render(false);
      }
      setTimeout(function() {
        if (!topBlock.disposed) {
          topBlock.setConnectionTracking(true);
        }
      }, 1);
      // 禁用积木
      topBlock.updateDisabled();
      workspace.resizeContents();
    } else {
      for (var i = blocks.length - 1; i >= 0; i--) {
        blocks[i].initModel();
      }
    }
  } finally {
    Blockly.Events.enable();
  }
  if (Blockly.Events.isEnabled()) {
    // 添加变量积木
    var newVariables = Blockly.Variables.getAddedVariables(workspace,
        variablesBeforeCreation);
    // Fire a VarCreate event for each (if any) new variable created.
    for (var i = 0; i < newVariables.length; i++) {
      var thisVariable = newVariables[i];
      // 触发变量创建事件
      Blockly.Events.fire(new Blockly.Events.VarCreate(thisVariable));
    }
    // Block events come after var events, in case they refer to newly created
    // 触发积木创建事件
    Blockly.Events.fire(new Blockly.Events.BlockCreate(topBlock));
  }
  return topBlock;
};

Block积木数据

积木对象由Block和BlockSvg组成,Block负责数据的存储,BlockSvg负责存储svg渲染数据。

Blockly.Block = function(workspace, prototypeName, opt_id) {
  if (Blockly.Generator &&
      typeof Blockly.Generator.prototype[prototypeName] != 'undefined') {
    // Occluding Generator class members is not allowed.
    throw Error('Block prototypeName "' + prototypeName +
        '" conflicts with Blockly.Generator members.');
  }
//添加顶部积木
  workspace.addTopBlock(this);
  //添加全局积木类型blocks中
  workspace.addTypedBlock(this);

  // Call an initialization function, if it exists.
  if (typeof this.init == 'function') {
    //根据积木的定义(json or js)初始化积木其实是调用jsonInit
    this.init();
  }
  // Record initial inline state.
  /** @type {boolean|undefined} */
  this.inputsInlineDefault = this.inputsInline;

  // Fire a create event.
  if (Blockly.Events.isEnabled()) {
    var existingGroup = Blockly.Events.getGroup();
    if (!existingGroup) {
      Blockly.Events.setGroup(true);
    }
    try {
    //触发积木创建
      Blockly.Events.fire(new Blockly.Events.BlockCreate(this));
    } finally {
      if (!existingGroup) {
        Blockly.Events.setGroup(false);
      }
    }

  }
  // Bind an onchange function, if it exists.
  if (typeof this.onchange == 'function') {
    this.setOnChange(this.onchange);
  }
};

初始化BlockSvg

Blockly.BlockSvg.prototype.initSvg = function() {
  if (!this.workspace.rendered) {
    throw TypeError('Workspace is headless.');
  }
  for (var i = 0, input; (input = this.inputList[i]); i++) {
  //输入参数初始化
    input.init();
  }
  //创建图标
  var icons = this.getIcons();
  for (var i = 0; i < icons.length; i++) {
    icons[i].createIcon();
  }
  this.applyColour();
  this.pathObject.updateMovable(this.isMovable());
  var svg = this.getSvgRoot();
  if (!this.workspace.options.readOnly && !this.eventsInit_ && svg) {
  //积木绑定鼠标按下事件
    Blockly.bindEventWithChecks_(
        svg, 'mousedown', this, this.onMouseDown_);
  }
  this.eventsInit_ = true;

  if (!svg.parentNode) {
  // 在workspace的svg中添加blocksvg
    this.workspace.getCanvas().appendChild(svg);
  }
};

积木事件Event

积木事件、变量事件、注释事件、ui事件

//事件触发,将事件加到队列中
Blockly.Events.fire = function(event) {
  if (!Blockly.Events.isEnabled()) {
    return;
  }
  if (!Blockly.Events.FIRE_QUEUE_.length) {
    // First event added; schedule a firing of the event queue.
    //超时立即触发
    setTimeout(Blockly.Events.fireNow_, 0);
  }
  //加入队列
  Blockly.Events.FIRE_QUEUE_.push(event);
};

Blockly.Events.fireNow_ = function() {
//对事件进行合并过滤,复制事件队列
  var queue = Blockly.Events.filter(Blockly.Events.FIRE_QUEUE_, true);
  Blockly.Events.FIRE_QUEUE_.length = 0;
  for (var i = 0, event; (event = queue[i]); i++) {
    if (!event.workspaceId) {
      continue;
    }
    var workspace = Blockly.Workspace.getById(event.workspaceId);
    if (workspace) {
    //开始执行事件
      workspace.fireChangeListener(event);
    }
  }
};

积木渲染Render

根据BlockSvg,生成对应svg,积木图像由Block中的数据生成path元素,渲染的方法有很多,过程比较复杂,这里不做具体说明。

Blockly.blockRendering.RenderInfo.prototype.measure = function() {
    //按照输入为积木创建每一行
  this.createRows_();
//为每一行创建空格
  this.addElemSpacing_();
  //为行添加间隔符
  this.addRowSpacing_();
  //计算积木右边缘的位置
  this.computeBounds_();
  // 行右对齐,并计算行高度
  this.alignRowElements_();
  this.finalize_();
};
Blockly.blockRendering.Drawer.prototype.draw = function() {
    //隐藏图标
  this.hideHiddenIcons_();
  //画外廓线
  this.drawOutline_();
  //画积木内部输入、图标等
  this.drawInternals_();

//设置path路径
  this.block_.pathObject.setPath(this.outlinePath_ + '\n' + this.inlinePath_);
  if (this.info_.RTL) {
    this.block_.pathObject.flipRTL();
  }
  if (Blockly.blockRendering.useDebugger) {
    this.block_.renderingDebugger.drawDebug(this.block_, this.info_);
  }
  this.recordSizeOnBlock_();
};

其他

Workspace和WorkspaceSvg

Blockly.Workspace = function(opt_options) {
  /** 生成工作空间id */
  this.id = Blockly.utils.genUid();
  //存储工作空间
  Blockly.Workspace.WorkspaceDB_[this.id] = this;
  /** 前端输入的选项*/
  this.options = opt_options ||
      new Blockly.Options(/** @type {!Blockly.BlocklyOptions} */ ({}));
  /** 有一些语言是右对齐的 */
  this.RTL = !!this.options.RTL;
  /** 是否是行布局 */
  this.horizontalLayout = !!this.options.horizontalLayout;
  /** 设置工具箱位置 */
  this.toolboxPosition = this.options.toolboxPosition;

  /** 工作空间顶部积木数组  */
  this.topBlocks_ = [];
  /** 工作空间注释数组 */
  this.topComments_ = [];
  /** 所有注释数组 */
  this.commentDB_ = Object.create(null);
  /** 监听工作空间的监视器 */
  this.listeners_ = [];
  /** 操作队列,以事件的形式保存*/
  this.undoStack_ = [];
  /** 撤销操作 */
  this.redoStack_ = [];
  /** 所有积木对象 */
  this.blockDB_ = Object.create(null);
  /** toolbox 积木类型对象 */
  this.typedBlocksDB_ = Object.create(null);

  /** 所有工作空间变量 */
  this.variableMap_ = new Blockly.VariableMap(this);
  this.potentialVariableMap_ = null;
};

Flyout

Flyout也是一个工作空间,Toolbox是工具箱,点击工具箱,弹出子菜单积木。积木显示在哪,怎么显示,由Flyout控制。Flyout分为行布局和列布局,如下图为列布局。


Flyout -c

Theme主题

Blockly.Theme = function(name, blockStyles, categoryStyles,
    opt_componentStyles) {
    //主题名称
  this.name = name;

  /** 积木样式 */
  this.blockStyles = blockStyles;

  /** 分类样式 */
  this.categoryStyles = categoryStyles;

  /** UI组件样式 */
  this.componentStyles_ = opt_componentStyles || Object.create(null);
};

总结

总体来说,Blockly中的对象关系还是比较清晰的,只是代码量较大,涉及到很多细节,所以很难看懂,我们抛开细节,主要对象之间的关系。

主要对象关系 -c

相关文章

网友评论

      本文标题:Blockly源码分析

      本文链接:https://www.haomeiwen.com/subject/wmlvphtx.html