積木圖形
Blockly中的積木是如何畫出來的呢?請看下圖:
- 積木沒有輸入和輸出,只有卡合的凹凸部分
- 積木輸出部分,凸出的部分,可以放入輸入積木中
- 積木輸入部分,等待其他輸出積木卡合
- 簡單積木輸出部分
- 獨(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形式。
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分為行布局和列布局,如下圖為列布局。
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)系。