If you want to develop an editor, but you have no previous experience and no idea where to start, this series is for you. This article focuses on graphics editors. If you want to learn more about rich text editors, this series will not cover that, but there is a lot of common logic and modules.
This article was first published on my personal blog: Graphic Editor Development (1) – Module design and understanding
At present, I am developing an SVG editor of my own and still have a lot of work to do. You can refer to the source code of my project to get an idea of the architecture of the editor.
Resolution of the module
Editor development is a complex project, it is very important to follow the principle of single responsibility to decouple modules to reduce the complexity of development, even if only one person is developing.
The basic modules involved are:
- Editor: Editor class to which all other modules are mounted.
- History (CommandManager) : Records operations and provides undo redo capabilities. It is usually implemented using command mode, so I named it CommandManager (Command Management class) in my own project.
- ToolManager: Manages the switching and use of various tools and sets the current tool. For example, under the selection tool, you can select the graph in the canvas. The drag and drop tool moves the canvas.
- Graphic library: Abstract encapsulation of the underlying methods of graphic elements.
- Shortcut: Sets the event response method corresponding to Shortcut keys
- Viewport: If the editor needs to support scaling, get scaling, and canvas offset information, consider this class for management.
- Selected element manager (ActivedElsManager) : records one or more elements to operate on.
- Setting: Parameter Setting of the editor, such as default fill color, stroke color.
- Auxiliary line (HudManger) : Some auxiliary line drawing, such as selected graph highlight graph outline, this outline is actually the top of the auxiliary line layer of the graph.
- UI layer: The previous modules are part of the editor kernel, while the UI layer is outside the editor kernel. Its purpose is to call the API provided by the editor kernel while performing some operations. I use React for the UI layer of my SVG editor, and other frameworks work just as well.
Editor (Editor)
The first place you need to mount all your modules is the editor module (class). It is also the class used to expose methods for external use. In addition, we can communicate between modules by injecting instances of Editor into these module dependencies. Here is an example of emptying selected shapes of records and removing their highlights when activeDelsManager.clear () is called:
class Editor(a){
// ...
constructor() {
this.activedElsManager = new ActivedElsManager(this)
this.hudManager = new HudManager(this)}}export class HudManager {
constructor(private editor: Editor) {
// ...
}
clear() {
// ...}}class ActivedElsManager {
constructor(private editor: Editor) {
// ...
}
clear() {
this.els = []
this.editor.hudManager.outlineBoxHud.clear() // ActivedElsManager communicates with HudManager: the former calls the latter's clear method}}Copy the code
You may find that the above approach to implementing inter-class dependencies violates Demeter’s law (LOD, also known as the minimum knowledge principle) because ActivedElsManager only relies on HudManager, not Editor. The idea of Demeter’s rule is that modules should have dependencies only on modules that are closely related to them, not on “unfamiliar” classes, so that when some classes are modified, there is less contagion.
The reason Demeter’s rule is not followed here is because the editor I developed is not in a stable state. Modules can be added or modified at any time, and modules may find themselves dependent on more modules as they develop. We had to constantly change the constructor and pass in a large number of dependency classes for dependency injection, which was not convenient, so we ended up injecting an Editor class that acts as a bridge between any modules.
History Module (CommandManager)
History module, a module that records all user actions and provides undo and redo functions for those actions. This module uses a command mode implementation.
The core idea of the command pattern is to represent actual actions by means of objects.
For the editor, we need to implement the command classes for different operations (such as moving elements, deleting elements), implementing their first execution (exec, optional instantiation time), undo, and redo.
When “Execute first execution” is performed, the necessary information for the operation elements is saved. For example, to change the color of an element, we need to save the original color value of the element being changed, the new color value, and the element, which is called an object, and the actual action is encapsulated in a common template method.
If you are interested in implementing undo redo, take a look at the article I wrote a long time ago: Web-based SVG Editor (1) – Undo Redo.
Tool module (ToolManager)
A typical editor has many tools. Select different tools to enter different modes and bind the corresponding operation logic. For example, VIM editor has normal mode (Normal), Insert mode (Visual), observe mode (visual). The reason why there are multiple modes is that the user’s operation behavior is limited: button press release and combination, mouse press release and movement, graphic button click, etc., but there are many functions. A common strategy for this is to split functionality into multiple tools, with different tools leading to different modes, the same user actions leading to different effects.
The basic tools of a graphic editor are:
- Drag and drop the canvas: Because it is a high-frequency operation, shortcuts are usually provided, such as
Ctrl+Space
Or hold down the right mouse button; - Selection tool: Used to select one or more elements, which are recorded and can then be used for subsequent actions such as setting colors, deleting, moving to the top, etc
- Zoom tool: Zooms out or magnifies the contents of the canvas to handle details and give an overview
- Drawing tools: draw rectangles, circles, paths, etc.
Graphics Library
Developing a graphic editor is all about manipulating elements to achieve the desired visual effect. Editors can be SVG based, Canvas based, or directly based on the HTML DOM, which we’re most familiar with. For some elements, if we directly call the underlying method of the operation, it will be tedious to write. We will abstract and encapsulate the underlying methods of elements to reduce duplication and improve readability. You can use some popular third-party libraries (such as SVGJS and Paperjs) or write a set by yourself. The advantage is that it fits your own project, and it is convenient to modify the underlying implementation algorithm to improve performance and facilitate expansion.
Abstract the bottom method is very important, it will be difficult to use the tedious bottom logic for encapsulation, so that users have no perception of the bottom, provide coarser granularity method to users. If you want to improve performance in the future, you don’t need to change the business code, you just need to change the implementation underneath the abstraction layer methods. Even if you want to change your graphical scheme and do a refactoring, such as using Canvas instead of SVG, you can rewrite the underlying implementation under the abstraction layer without having to change the code that calls the abstraction layer methods all over the project.
Shortcut keys (Shortcut)
This module is responsible for binding some shortcut key operations. Common ones are:
- Undo (Ctrl + Z), Redo (Ctrl + Y)
- Delete an element (Delete or Backspace)
- Save (Ctrl + S)
- Single-key tool switching: For example, press B to switch the toolbar to the brush tool.
Viewport (Viewport)
Viewport refers to the area that the user can see, usually a part of the canvas. I recommend putting zooming and moving the canvas into the viewport module for easy management. In addition, it provides the ability to listen to zoom and decouple it from the UI layer.
Selected Element Management (ActivedElsManager)
This module stores selected elements and provides methods to highlight selected elements and determine whether they contain an element.
Set (Setting)
Manages editor parameters such as fill color, stroke color, stroke width, and so on. Changes to these values are generally listened for, and the corresponding listener function is notified of any changes.
Auxiliary line (HudManger)
Helper line management module for highlighting selected elements. Because elements can be blocked by elements higher up, it is not appropriate to change the stroke color to highlight the selected element itself. Such highlighting will still be blocked by elements higher up, so we need to place guides at the highest level. There are also many types of auxiliary lines:
- The outline of the selected element
- A box outline formed by multiple elements
- Draw bezier curve control line, control point, path line
- .
For better management, we have removed the auxiliary line module, which is used to manage the drawing of various auxiliary lines.
The UI layer,UI layer)
The UI layer sits outside the editor kernel and is responsible for interacting with the editor kernel. The UI layer is responsible for providing the buttons necessary to click to invoke methods in the browser kernel. In addition, the UI layer gets editor parameters by binding listeners. The UI layer is free to choose its implementation, and my SVG editor builds the UI layer based on React.
At the end
Of course, this is not all modules; as your functionality becomes more complex, more and more modules will be added to the Editor class. When you want to introduce Ruler function, you will add Ruler module. When you want to add Layer functionality, like in Photoshop, you add the Layer module; When you want to add recorded actions and reassign them to other elements, you need to add the Action module. If you don’t do a good enough underlying architecture design at first, you will struggle to introduce them, but if you read my article and consciously modularize your editor, you will soon be able to introduce new modules and make them blend well with other modules. My implementation may not be a best practice, but it will help you better manage your organization’s code, and that will serve the purpose of this article.
This article is the first in a series that will follow.