Blockly源碼分析

積木圖形

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


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

svg path介紹

path元素是SVG基本形狀中最強大的一個戚篙,它不僅能創(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如何生成的呢

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

上文卡合處:位置在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時,需要根據積木的特點朋截,來組合生成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;
  // 設置菜單對應的點擊事件
  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數(shù)據。當點擊分類時剧蹂,右側會彈出對應的積木声功,積木由分類對應的xml生成。

Blockly.Xml.domToBlock = function(xmlBlock, workspace) {
  // xmlBlock 為xml宠叼,數(shù)據存在菜單樹中
  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.');
  }
  // 因為要對積木進行創(chuàng)建和編輯所以先禁止事件先巴,創(chuàng)建完成后,再將事件打開
  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];
      // 觸發(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ù)據

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

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 {
    //觸發(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++) {
  //輸入參數(shù)初始化
    input.init();
  }
  //創(chuàng)建圖標
  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事件

//事件觸發(fā)丈咐,將事件加到隊列中
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);
  }
  //加入隊列
  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) {
    //開始執(zhí)行事件
      workspace.fireChangeListener(event);
    }
  }
};

積木渲染Render

根據BlockSvg,生成對應svg棵逊,積木圖像由Block中的數(shù)據生成path元素伤疙,渲染的方法有很多,過程比較復雜辆影,這里不做具體說明徒像。

Blockly.blockRendering.RenderInfo.prototype.measure = function() {
    //按照輸入為積木創(chuàng)建每一行
  this.createRows_();
//為每一行創(chuàng)建空格
  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;

  /** 工作空間頂部積木數(shù)組  */
  this.topBlocks_ = [];
  /** 工作空間注釋數(shù)組 */
  this.topComments_ = [];
  /** 所有注釋數(shù)組 */
  this.commentDB_ = Object.create(null);
  /** 監(jiān)聽工作空間的監(jiān)視器 */
  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中的對象關系還是比較清晰的,只是代碼量較大算凿,涉及到很多細節(jié)份蝴,所以很難看懂,我們拋開細節(jié)氓轰,主要對象之間的關系。

主要對象關系 -c
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末浸卦,一起剝皮案震驚了整個濱河市署鸡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌限嫌,老刑警劉巖靴庆,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異怒医,居然都是意外死亡炉抒,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進店門稚叹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來焰薄,“玉大人,你說我怎么就攤上這事扒袖∪” “怎么了?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵季率,是天一觀的道長野瘦。 經常有香客問我,道長飒泻,這世上最難降的妖魔是什么鞭光? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮泞遗,結果婚禮上惰许,老公的妹妹穿的比我還像新娘。我一直安慰自己刹孔,他們只是感情好啡省,可當我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布娜睛。 她就那樣靜靜地躺著,像睡著了一般卦睹。 火紅的嫁衣襯著肌膚如雪畦戒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天结序,我揣著相機與錄音障斋,去河邊找鬼。 笑死徐鹤,一個胖子當著我的面吹牛垃环,可吹牛的內容都是我干的。 我是一名探鬼主播返敬,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼遂庄,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了劲赠?” 一聲冷哼從身側響起涛目,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎凛澎,沒想到半個月后霹肝,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡塑煎,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年沫换,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片最铁。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡讯赏,死狀恐怖,靈堂內的尸體忽然破棺而出炭晒,到底是詐尸還是另有隱情待逞,我是刑警寧澤,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布网严,位于F島的核電站识樱,受9級特大地震影響,放射性物質發(fā)生泄漏震束。R本人自食惡果不足惜怜庸,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望垢村。 院中可真熱鬧割疾,春花似錦、人聲如沸嘉栓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至麻昼,卻和暖如春奠支,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背抚芦。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工倍谜, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人叉抡。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓尔崔,卻偏偏與公主長得像,于是被迫代替她去往敵國和親褥民。 傳聞我的和親對象是個殘疾皇子季春,可洞房花燭夜當晚...
    茶點故事閱讀 45,077評論 2 355

推薦閱讀更多精彩內容

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