積木圖形
Blockly中的積木是如何畫出來的呢?請看下圖:
- 積木沒有輸入和輸出帅腌,只有卡合的凹凸部分
- 積木輸出部分驰弄,凸出的部分,可以放入輸入積木中
- 積木輸入部分速客,等待其他輸出積木卡合
- 簡單積木輸出部分
- 獨立的外部積木卡合到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形式部服。
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分為行布局和列布局,如下圖為列布局菌羽。
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é)氓轰,主要對象之間的關系。