开始之前
在上文中,我们提到了几样基本工具之间的配合。
然而,基本工具也需要向引擎注册才能使用。在此基础上,我们需要给引擎写一些模块用来适配。
设计思路
最基础的工具类,告诉引擎这是一个工具:
// ================================================================================
// * Tool <SDUDOC Engine>
// --------------------------------------------------------------------------------
// Designer: Lagomoro <Yongrui Wang>
// From: SDU <Shandong University>
// License: MIT license
// --------------------------------------------------------------------------------
// Latest update:
// 2020/03/10 - Version 1.0.0
// - Engine core
// ================================================================================
// ================================================================================
// * Tool
// --------------------------------------------------------------------------------
function Tool(){
this.initialize.apply(this, arguments);
}
// --------------------------------------------------------------------------------
// * Enum
// --------------------------------------------------------------------------------
Tool.Type = {
DOCUMENT: 0, HISTORY: 1, PLUGIN: 2, PAGE: 3
};
// --------------------------------------------------------------------------------
// * Property
// --------------------------------------------------------------------------------
Tool.prototype._id = "";
Tool.prototype._tooltip = "";
Tool.prototype._icon = "";
Tool.prototype._type = 0;
Tool.prototype._description = "";
Tool.prototype._callback = function(){};
// --------------------------------------------------------------------------------
// * Initialize
// --------------------------------------------------------------------------------
Tool.prototype.initialize = function(id, tooltip, icon, type, description, callback){
this._id = id;
this._tooltip = tooltip;
this._icon = icon;
this._type = type;
this._description = description;
this._callback = callback;
};
// --------------------------------------------------------------------------------
// * Getter & Setter
// --------------------------------------------------------------------------------
Object.defineProperty(Tool.prototype, 'id', {
get: function() {
return this._id;
},
configurable: true
});
Object.defineProperty(Tool.prototype, 'tooltip', {
get: function() {
return this._tooltip;
},
configurable: true
});
Object.defineProperty(Tool.prototype, 'icon', {
get: function() {
return this._icon;
},
configurable: true
});
Object.defineProperty(Tool.prototype, 'type', {
get: function() {
return this._type;
},
configurable: true
});
Object.defineProperty(Tool.prototype, 'description', {
get: function() {
return this._description;
},
configurable: true
});
Object.defineProperty(Tool.prototype, 'callback', {
get: function() {
return this._callback;
},
configurable: true
});
// ================================================================================
然后我们需要一个Manager来管理工具类:
// ================================================================================
// * ToolManager <SDUDOC Engine>
// --------------------------------------------------------------------------------
// Designer: Lagomoro <Yongrui Wang>
// From: SDU <Shandong University>
// License: MIT license
// --------------------------------------------------------------------------------
// Latest update:
// 2020/03/14 - Version 1.0.0
// - Engine core
// ================================================================================
// ================================================================================
// * ToolManager
// --------------------------------------------------------------------------------
function ToolManager() {
throw new Error('This is a static class');
}
// --------------------------------------------------------------------------------
// * Property
// --------------------------------------------------------------------------------
ToolManager._tools = [];
ToolManager._handlers = {};
ToolManager._current_plugin = null;
// --------------------------------------------------------------------------------
// * Initialize
// --------------------------------------------------------------------------------
ToolManager.initialize = function() {
this.clear();
this._setupEventHandlers();
};
ToolManager.clear = function() {
this._current_plugin = this.getInitialPlugin().id;
};
ToolManager._setupEventHandlers = function(){
MouseInput.addHandler(new Handler("ToolManager.leftClick", "left_click", false, this, (event) => {
this._processHandler.call(this, event, "left_click")}));
MouseInput.addHandler(new Handler("ToolManager.middleClick", "middle_click", false, this, (event) => {
this._processHandler.call(this, event, "middle_click")}));
MouseInput.addHandler(new Handler("ToolManager.rightClick", "right_click", false, this, (event) => {
this._processHandler.call(this, event, "right_click")}));
MouseInput.addHandler(new Handler("ToolManager.leftDoubleClick", "left_double_click", false, this, (event) => {
this._processHandler.call(this, event, "left_double_click")}));
MouseInput.addHandler(new Handler("ToolManager.middleDoubleClick", "middle_double_click", false, this, (event) => {
this._processHandler.call(this, event, "middle_double_click")}));
MouseInput.addHandler(new Handler("ToolManager.rightDoubleClick", "right_double_click", false, this, (event) => {
this._processHandler.call(this, event, "right_double_click")}));
MouseInput.addHandler(new Handler("ToolManager.leftDown", "left_down", false, this, (event) => {
this._processHandler.call(this, event, "left_down")}));
MouseInput.addHandler(new Handler("ToolManager.middleDown", "middle_down", false, this, (event) => {
this._processHandler.call(this, event, "middle_down")}));
MouseInput.addHandler(new Handler("ToolManager.rightDown", "right_down", false, this, (event) => {
this._processHandler.call(this, event, "right_down")}));
MouseInput.addHandler(new Handler("ToolManager.leftUp", "left_up", false, this, (event) => {
this._processHandler.call(this, event, "left_up")}));
MouseInput.addHandler(new Handler("ToolManager.middleUp", "middle_up", false, this, (event) => {
this._processHandler.call(this, event, "middle_up")}));
MouseInput.addHandler(new Handler("ToolManager.rightUp", "right_up", false, this, (event) => {
this._processHandler.call(this, event, "right_up")}));
MouseInput.addHandler(new Handler("ToolManager.mouseMove", "mousemove", false, this, (event) => {
this._processHandler.call(this, event, "mousemove")}));
MouseInput.addHandler(new Handler("ToolManager.mouseOver", "mouseover", false, this, (event) => {
this._processHandler.call(this, event, "mouseover")}));
MouseInput.addHandler(new Handler("ToolManager.mouseOut", "mouseout", false, this, (event) => {
this._processHandler.call(this, event, "mouseout")}));
MouseInput.addHandler(new Handler("ToolManager.wheel", "wheel", false, this, (event) => {
this._processHandler.call(this, event, "wheel")}));
Input.addHandler(new Handler("ToolManager.keyClick", "key_click", 'all', this, (event) => {
this._processHandler.call(this, event, "key_click")}));
Input.addHandler(new Handler("ToolManager.keyHold", "key_hold", 'all', this, (event) => {
this._processHandler.call(this, event, "key_hold")}));
Input.addHandler(new Handler("ToolManager.keyLongHold", "key_long_hold", 'all', this, (event) => {
this._processHandler.call(this, event, "key_long_hold")}));
Input.addHandler(new Handler("ToolManager.keyDown", "key_down", 'all', this, (event) => {
this._processHandler.call(this, event, "key_down")}));
Input.addHandler(new Handler("ToolManager.keyUp", "key_up", 'all', this, (event) => {
this._processHandler.call(this, event, "key_up")}));
};
// --------------------------------------------------------------------------------
// * Functions
// --------------------------------------------------------------------------------
ToolManager._processHandler = function(event, type){
if(!this._current_plugin) return;
switch (type){
case 'left_click':
case 'middle_click':
case 'right_click':
case 'left_double_click':
// case 'middle_double_click':
// case 'right_double_click':
case 'left_down':
case 'middle_down':
case 'right_down':
case 'left_up':
case 'middle_up':
case 'right_up':
case 'mousemove':
case 'mouseover':
case 'mouseout':
case 'wheel':this.callMouseHandler(event, type);break;
case 'key_click':
case 'key_hold':
case 'key_long_hold':
case 'key_down':
case 'key_up':this.callKeyHandler(event, type);break;
}
}
ToolManager.callMouseHandler = function(event, type){
for(let i in this._handlers){
if(this._handlers[i].type === type &&
(this._handlers[i].id.startsWith('_') || this._handlers[i].id.startsWith(this.getCurrentPlugin().id))){
this._handlers[i].callback.call(this._handlers[i].owner, event);
}
}
}
ToolManager.callKeyHandler = function(event, type){
for(let i in this._handlers){
if(this._handlers[i].type === type && this._handlers[i].key_code === Input.getKeyCode(event.keyCode)
&& (this._handlers[i].id.startsWith('_') || this._handlers[i].id.startsWith(this.getCurrentPlugin().id))){
this._handlers[i].callback.call(this._handlers[i].owner, event);
}
}
}
// --------------------------------------------------------------------------------
ToolManager.addTool = function(tool){
this._tools.push(tool);
}
ToolManager.addHandler = function(handler){
this._handlers[handler.id] = handler;
};
ToolManager.removeHandler = function(id){
this._handlers.remove(id);
};
// --------------------------------------------------------------------------------
ToolManager.getToolList = function(type){
let data = [];
for(let i in this._tools){
if(this._tools[i].type === type){
let callback_function = this._tools[i].callback;
data.push({
id: this._tools[i].id,
tooltip: this._tools[i].tooltip,
icon: this._tools[i].icon,
description: this._tools[i].description,
callback: function(id){
Engine.clearFactory()
callback_function(id)
}
})
}
}
return data;
}
ToolManager.getInitialPlugin = function(){
let list = this.getToolList(Tool.Type.PLUGIN);
if(list.length === 0) return {id: null};
return list[0];
}
ToolManager.getCurrentPlugin = function(){
let list = this.getToolList(Tool.Type.PLUGIN);
for(let i in list){
if(list[i].id === this._current_plugin){
return list[i];
}
}
}
ToolManager.setCurrentPlugin = function(id){
let list = this.getToolList(Tool.Type.PLUGIN);
for(let i in list){
if(list[i].id === id){
this._current_plugin = list[i].id;
Engine.owner.current_plugin = i;
}
}
Graphics.refresh();
}
// ================================================================================
Manager将会注册所有工具用到的事件,并向工具进行分发。
这是由于不同工具他们监听的事件不一样,在切换工具时有不同的表现。例如:点工具在按下鼠标时生成点,移动工具进行移动。
如果我们不写一个Manager去管理,而是用if去判断,那就耦合度太高了,达不到插件化的效果。
由此,点工具只需要如此即可注册:
// ================================================================================
// * Register Plugin Tool
// ================================================================================
ToolManager.addTool(new Tool("dot", "点工具", "mdi-circle-medium", Tool.Type.PLUGIN, "", function(id){
ToolManager.setCurrentPlugin(id);
}));
// --------------------------------------------------------------------------------
ToolManager.addHandler(new Handler("dot.onLeftClick", "left_click", false, DotFactory, function(event){
if(DocumentManager.getCurrentPage() <= 0) return;
let collide_list = CollideManager.getCollideList(Dot2D.TAG, 1);
if(collide_list.length > 0) return;
collide_list = CollideManager.getCollideList(Line2D.TAG, 2);
if(collide_list.length === 2){
DocumentManager.addElement(Dot2D.TAG, DotFactory.makeObject(DocumentManager.getCurrentPageId(),
Dot2D.Type.INTERSECTION, collide_list[0], collide_list[1]));
}else if(collide_list.length === 1){
let dependent = LineFactory.getDependent(collide_list[0], new Point(event.layerX, event.layerY));
DocumentManager.addElement(Dot2D.TAG, DotFactory.makeObject(DocumentManager.getCurrentPageId(),
Dot2D.Type.DEPENDENT, collide_list[0], dependent));
}else{
let point = Graphics.getGridPoint(new Point(event.layerX, event.layerY));
DocumentManager.addElement(Dot2D.TAG, DotFactory.makeObject(DocumentManager.getCurrentPageId(),
Dot2D.Type.FREE, point.x, point.y));
}
}));
ToolManager.addHandler(new Handler("dot.onRightClick", "right_click", false, DotFactory, function(event){
if(DocumentManager.getCurrentPage() <= 0) return;
let collide_list = CollideManager.getCollideList(Dot2D.TAG, 1);
if(collide_list.length === 0) return;
DocumentManager.deleteElement(Dot2D.TAG, collide_list[0]);
}));
ToolManager.addHandler(new Handler("dot.onMouseMove", "mousemove", false, DotFactory, function(event){
Graphics.refresh();
}));
ToolManager.addHandler(new Handler("dot.onMouseOut", "mouseout", false, DotFactory, function(event){
Graphics.refresh();
}));
接下来是渲染器,不同工具的渲染顺序、渲染内容都存在一定偏差。
// ================================================================================
// * Renderer <SDUDOC Engine>
// --------------------------------------------------------------------------------
// Designer: Lagomoro <Yongrui Wang>
// From: SDU <Shandong University>
// License: MIT license
// --------------------------------------------------------------------------------
// Latest update:
// 2020/03/17 - Version 1.0.0
// - Engine core
// ================================================================================
// ================================================================================
// * Renderer
// --------------------------------------------------------------------------------
function Renderer(){
this.initialize.apply(this, arguments);
}
// --------------------------------------------------------------------------------
// * Property
// --------------------------------------------------------------------------------
Renderer.prototype._id = "";
Renderer.prototype._z = 0;
Renderer.prototype._owner = null;
Renderer.prototype._render = function(){};
// --------------------------------------------------------------------------------
// * Initialize
// --------------------------------------------------------------------------------
Renderer.prototype.initialize = function(id, z, owner, render){
this._id = id;
this._z = z;
this._owner = owner;
this._render = render;
};
// --------------------------------------------------------------------------------
// * Getter & Setter
// --------------------------------------------------------------------------------
Object.defineProperty(Renderer.prototype, 'id', {
get: function() {
return this._id;
},
configurable: true
});
Object.defineProperty(Renderer.prototype, 'z', {
get: function() {
return this._z;
},
configurable: true
});
Object.defineProperty(Renderer.prototype, 'owner', {
get: function() {
return this._owner;
},
configurable: true
});
Object.defineProperty(Renderer.prototype, 'render', {
get: function() {
return this._render;
},
configurable: true
});
// ================================================================================
在Randerer指定z值来规定渲染顺序。
同样需要Manager去管理。
// ================================================================================
// * RenderManager <SDUDOC Engine>
// --------------------------------------------------------------------------------
// Designer: Lagomoro <Yongrui Wang>
// From: SDU <Shandong University>
// License: MIT license
// --------------------------------------------------------------------------------
// Latest update:
// 2020/03/15 - Version 1.0.0
// - Engine core
// ================================================================================
// ================================================================================
// * RenderManager
// --------------------------------------------------------------------------------
function RenderManager() {
throw new Error('This is a static class');
}
// --------------------------------------------------------------------------------
// * Property
// --------------------------------------------------------------------------------
RenderManager._renderers = {};
RenderManager._z_list = [];
// --------------------------------------------------------------------------------
// * Initialize
// --------------------------------------------------------------------------------
RenderManager.initialize = function() {
this.clear();
this._setupZList();
};
RenderManager.clear = function() {
this._z_list = [];
};
RenderManager._setupZList = function() {
let temp = [];
for(let i in this._renderers){
temp.push({id: this._renderers[i].id, z: this._renderers[i].z});
}
let min_id = 0;
while(temp.length > 0){
min_id = 0;
for(let i = 0;i < temp.length; i++){
if(temp[i].z < temp[min_id].z){
min_id = i;
}
}
this._z_list.push(temp.splice(min_id, 1)[0].id);
}
};
// --------------------------------------------------------------------------------
// * Functions
// --------------------------------------------------------------------------------
RenderManager.addRenderer = function(renderer){
this._renderers[renderer.id] = renderer;
};
RenderManager.removeRenderer = function(id){
this._renderers.remove(id);
};
RenderManager.callRenderer = function(ctx){
for(let i = 0; i < this._z_list.length; i++){
if(this._renderers[this._z_list[i]].id.startsWith("!" + ToolManager.getCurrentPlugin().id)) {
continue;
}else if(this._renderers[this._z_list[i]].id.startsWith("!")){
this._renderers[this._z_list[i]].render.call(this._renderers[this._z_list[i]].owner, ctx);
continue;
}
if(this._renderers[this._z_list[i]].id.startsWith("_") ||
this._renderers[this._z_list[i]].id.startsWith(ToolManager.getCurrentPlugin().id)){
this._renderers[this._z_list[i]].render.call(this._renderers[this._z_list[i]].owner, ctx);
continue;
}
}
}
// ================================================================================
最后注册,就大功告成。
至此一个完整插件就ok啦!
// --------------------------------------------------------------------------------
RenderManager.addRenderer(new Renderer("_dot.normal", 10, DotFactory, function(ctx){
if(DocumentManager.getCurrentPage() <= 0) return;
let collide_list = CollideManager.getCollideList(Dot2D.TAG, 1);
let dots = SDUDocument.getCurrentPageElements(Dot2D.TAG);
for(let i in dots){
if(collide_list.indexOf(i) === -1){
dots[i].render(ctx);
}
}
}));
RenderManager.addRenderer(new Renderer("!dot.collide", 11, DotFactory, function(ctx){
if(DocumentManager.getCurrentPage() <= 0) return;
let collide_list = CollideManager.getCollideList(Dot2D.TAG, 1);
if(collide_list.length > 0){
SDUDocument.getCurrentPageElement(Dot2D.TAG, collide_list[0]).render(ctx);
}
}));
RenderManager.addRenderer(new Renderer("dot.collide", 11, DotFactory, function(ctx){
if(DocumentManager.getCurrentPage() <= 0) return;
let collide_list = CollideManager.getCollideList(Dot2D.TAG, 1);
if(collide_list.length > 0){
SDUDocument.getCurrentPageElement(Dot2D.TAG, collide_list[0]).renderCollide(ctx);
}
}));
RenderManager.addRenderer(new Renderer("dot.line.collide", 9, DotFactory, function(ctx){
if(DocumentManager.getCurrentPage() <= 0) return;
let collide_list = CollideManager.getCollideList(Line2D.TAG, 2);
if(collide_list.length > 0){
for(let i = 0; i < collide_list.length; i++){
SDUDocument.getCurrentPageElement(Line2D.TAG, collide_list[i]).renderCollide(ctx);
}
}
}));
// --------------------------------------------------------------------------------
RenderManager.addRenderer(new Renderer("dot.mouse", 100, DotFactory, function(ctx){
if(DocumentManager.getCurrentPage() <= 0) return;
let collide_list = CollideManager.getCollideList(Dot2D.TAG, 1);
if(collide_list.length > 0) return;
collide_list = CollideManager.getCollideList(Line2D.TAG, 2);
if(collide_list.length === 2){
let point = LineFactory.getIntersection(collide_list[0], collide_list[1]);
point.fillSelf(ctx, 3, 'rgba(255, 255, 255, 0.5)');
point.strokeSelf(ctx, 5, 2, 'rgba(0, 0, 255, 0.5)');
}else if(collide_list.length === 1){
let mouse_point = MouseInput.getMousePoint();
if(mouse_point !== null){
let point = LineFactory.getProjection(collide_list[0], mouse_point);
point.fillSelf(ctx, 3, 'rgba(255, 255, 255, 0.5)');
point.strokeSelf(ctx, 5, 2, 'rgba(0, 0, 255, 0.5)');
}
}else{
let mouse_point = MouseInput.getMousePoint();
if(mouse_point !== null){
mouse_point.fillSelf(ctx, 3, 'rgba(255, 255, 255, 0.5)');
mouse_point.strokeSelf(ctx, 5, 2, 'rgba(0, 0, 255, 0.5)');
}
}
}));
// ================================================================================