积木图形
Blockly中的积木是如何画出来的呢?请看下图:
-c
- 积木没有输入和输出,只有卡合的凹凸部分
- 积木输出部分,凸出的部分,可以放入输入积木中
- 积木输入部分,等待其他输出积木卡合
- 简单积木输出部分
- 独立的外部积木卡合到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
网友评论