Blockly源碼分析

積木圖形

Blockly中的積木是如何畫出來的呢?請看下圖:


-c
  1. 積木沒有輸入和輸出,只有卡合的凹凸部分
  2. 積木輸出部分,凸出的部分,可以放入輸入積木中
  3. 積木輸入部分,等待其他輸出積木卡合
  4. 簡單積木輸出部分
  5. 獨(dú)立的外部積木卡合到count積木中

svg path介紹

path元素是SVG基本形狀中最強(qiáng)大的一個,它不僅能創(chuàng)建其他基本形狀,還能創(chuàng)建更多其他形狀。你可以用path元素繪制矩形、圓形、橢圓、折線形、多邊形,以及一些其他的形狀,例如貝塞爾曲線、2次曲線等曲線。path元素的形狀是通過屬性d來定義的,屬性d的值是一個“命令+參數(shù)”的序列。

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如何生成的呢

從上文積木的特點(diǎn)可以看出,積木的卡合(上下文、輸入輸出)部分位置固定。

上文卡合處:位置在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'

可以看出積木的生成是有規(guī)律可言的,因此在生成svg path時,需要根據(jù)積木的特點(diǎn),來組合生成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;
  }
  // 創(chuàng)建菜單樹
  var tree = new Blockly.tree.TreeControl(this,
      /** @type {!Blockly.tree.BaseNode.Config} */ (this.config_));
  this.tree_ = tree;
  // 設(shè)置菜單對應(yīng)的點(diǎn)擊事件
  tree.setSelectedItem(null);
  tree.onBeforeSelected(this.handleBeforeTreeSelected_);
  tree.onAfterSelected(this.handleAfterTreeSelected_);
  var openNode = null;
  if (languageTree) {
    this.tree_.blocks = [];
    this.hasColours_ = false;
    // 根據(jù)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中進(jìn)行渲染
  tree.render(this.HtmlDiv);
  // 選擇菜單項(xiàng)
  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解析器

當(dāng)Toolbox工具箱渲染后,產(chǎn)生菜單樹,Toolbox生成時,將xml生成菜單樹,菜單樹中的菜單扔保留的是xml數(shù)據(jù)。當(dāng)點(diǎn)擊分類時,右側(cè)會彈出對應(yīng)的積木,積木由分類對應(yīng)的xml生成。

Blockly.Xml.domToBlock = function(xmlBlock, workspace) {
  // xmlBlock 為xml,數(shù)據(jù)存在菜單樹中
  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.');
  }
  // 因?yàn)橐獙Ψe木進(jìn)行創(chuàng)建和編輯所以先禁止事件,創(chuàng)建完成后,再將事件打開
  Blockly.Events.disable();
  // 獲取所有變量,變量是全局的
  var variablesBeforeCreation = workspace.getAllVariables();
  try {
    // 獲取頂部Block
    var topBlock = Blockly.Xml.domToBlockHeadless_(xmlBlock, workspace);
    // 頂部Block相關(guān)的積木
    var blocks = topBlock.getDescendants(false);
    if (workspace.rendered) {
    //初始化每一個積木的svg,并渲染
      for (var i = blocks.length - 1; i >= 0; i--) {
        // 初始化,將積木的輸入,圖標(biāo)等信息初始化
        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];
      // 觸發(fā)變量創(chuàng)建事件
      Blockly.Events.fire(new Blockly.Events.VarCreate(thisVariable));
    }
    // Block events come after var events, in case they refer to newly created
    // 觸發(fā)積木創(chuàng)建事件
    Blockly.Events.fire(new Blockly.Events.BlockCreate(topBlock));
  }
  return topBlock;
};

Block積木數(shù)據(jù)

積木對象由Block和BlockSvg組成,Block負(fù)責(zé)數(shù)據(jù)的存儲,BlockSvg負(fù)責(zé)存儲svg渲染數(shù)據(jù)。

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') {
    //根據(jù)積木的定義(json or js)初始化積木其實(shí)是調(diào)用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 {
    //觸發(fā)積木創(chuàng)建
      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++) {
  //輸入?yún)?shù)初始化
    input.init();
  }
  //創(chuàng)建圖標(biāo)
  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) {
  //積木綁定鼠標(biāo)按下事件
    Blockly.bindEventWithChecks_(
        svg, 'mousedown', this, this.onMouseDown_);
  }
  this.eventsInit_ = true;

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

積木事件Event

積木事件、變量事件、注釋事件、ui事件

//事件觸發(fā),將事件加到隊(duì)列中
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.
    //超時立即觸發(fā)
    setTimeout(Blockly.Events.fireNow_, 0);
  }
  //加入隊(duì)列
  Blockly.Events.FIRE_QUEUE_.push(event);
};

Blockly.Events.fireNow_ = function() {
//對事件進(jìn)行合并過濾,復(fù)制事件隊(duì)列
  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) {
    //開始執(zhí)行事件
      workspace.fireChangeListener(event);
    }
  }
};

積木渲染Render

根據(jù)BlockSvg,生成對應(yīng)svg,積木圖像由Block中的數(shù)據(jù)生成path元素,渲染的方法有很多,過程比較復(fù)雜,這里不做具體說明。

Blockly.blockRendering.RenderInfo.prototype.measure = function() {
    //按照輸入為積木創(chuàng)建每一行
  this.createRows_();
//為每一行創(chuàng)建空格
  this.addElemSpacing_();
  //為行添加間隔符
  this.addRowSpacing_();
  //計(jì)算積木右邊緣的位置
  this.computeBounds_();
  // 行右對齊,并計(jì)算行高度
  this.alignRowElements_();
  this.finalize_();
};
Blockly.blockRendering.Drawer.prototype.draw = function() {
    //隱藏圖標(biāo)
  this.hideHiddenIcons_();
  //畫外廓線
  this.drawOutline_();
  //畫積木內(nèi)部輸入、圖標(biāo)等
  this.drawInternals_();

//設(shè)置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;
  /** 前端輸入的選項(xiàng)*/
  this.options = opt_options ||
      new Blockly.Options(/** @type {!Blockly.BlocklyOptions} */ ({}));
  /** 有一些語言是右對齊的 */
  this.RTL = !!this.options.RTL;
  /** 是否是行布局 */
  this.horizontalLayout = !!this.options.horizontalLayout;
  /** 設(shè)置工具箱位置 */
  this.toolboxPosition = this.options.toolboxPosition;

  /** 工作空間頂部積木數(shù)組  */
  this.topBlocks_ = [];
  /** 工作空間注釋數(shù)組 */
  this.topComments_ = [];
  /** 所有注釋數(shù)組 */
  this.commentDB_ = Object.create(null);
  /** 監(jiān)聽工作空間的監(jiān)視器 */
  this.listeners_ = [];
  /** 操作隊(duì)列,以事件的形式保存*/
  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是工具箱,點(diǎn)擊工具箱,彈出子菜單積木。積木顯示在哪,怎么顯示,由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);
};

總結(jié)

總體來說,Blockly中的對象關(guān)系還是比較清晰的,只是代碼量較大,涉及到很多細(xì)節(jié),所以很難看懂,我們拋開細(xì)節(jié),主要對象之間的關(guān)系。

主要對象關(guān)系 -c
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 前天,我們要在雙西小學(xué)上一節(jié)體育公開課。由于路程不算近,所以大家一起乘車前去。 坐在大巴車上,我的思緒一下子回到了...
    Harriet516閱讀 237評論 0 0
  • 顧客:這個款有大號嗎? 店員:美女你穿起多大的? 顧客:我得穿最大號吧。 店員:這個你穿應(yīng)該不行,這個款碼數(shù)偏小 ...
    淄川DDM趙鑫閱讀 578評論 2 0
  • 文 | 龔榮庭 一個普通的農(nóng)村婦女,30歲不到就胖起來,40多歲就患上高血壓、糖尿病,50多歲時心腦血管的幾種并發(fā)...
    榮庭de日拱一卒閱讀 410評論 3 3