Building a Flutter High-Performance Rich Text Editor - Rendering

Rendering implementation of Textfield

First let's see how Flutter's TextField is rendered:

As shown in the figure above, Textfield inherits from StatefulWidget and builds a nested Widget tree, including several key Widgets:
TextSelectionGestureDetector handles the logic related to gesture interaction, such as clicking to move the cursor, long-pressing to select text to display Toolbar, etc.;
Another important Widget - EditableText; when EditableText is built, through the buildTextSpan method, according to the normal text and composing part of TextEditingValue, create a Textspan object to _Editable; finally RenderEditable draws the text to the canvas through TextPainter;

Mural's rendering implementation

As shown in the figure above, the design of Mural in the rendering layer is basically the same as the previous part of the native TextField. The difference starts from MuralEditable, which corresponds to the EditableText of TextField;
As mentioned above at the protocol layer, Slate is consistent with Dom in terms of protocol design. When it reaches the Flutter rendering layer, the Dom tree will be converted into a Widget tree, and finally rendered on the screen;
MuralEditable is no longer simply creating a TextSpan, but according to the Dom tree structure, each Element is mapped into a Widget; the Widget corresponding to each Element, the created RenderObject implements the abstract class: RenderEditorInlineBox;

Next, let's take a look at how the Widget corresponding to Element handles its child nodes:
Let's take the simplest EditableTextLine as an example, which consists of two parts: Leading and Body. Leading is responsible for rendering paragraph decoration-related content, such as the serial number of ordered paragraphs, decorative vertical bars in front of quoted paragraphs, etc.; Body is responsible for rendering specific rich text content , implements the abstract class: RenderEditorTextBox, and finally converts all leaf nodes into InlineSpan, and draws text to the screen through TextPainer;

The buildChildren method of EditorUtils is implemented as follows:

Cursor & Selection Rendering

Cursor and selection are another difficulty that needs to be dealt with in the rendering layer of the rich text editor;
Compared with native TextField, Mural is more complex in processing cursors and selections; all input text in TextField is drawn in a TextPainter. As we said earlier, each Element of Mural is an independent paragraph, corresponding to a RenderObject; in Mural, We need to calculate the cursor position of user gestures to operate different paragraphs and the selection calculation between paragraphs;
To implement Mural's cursor and selection rendering, the following problems need to be solved:

1. Click on multiple Elements to get TextPosition;
2. TextPosition to MuralPoint;
3. Cursor position calculation;

Click on multiple Elements to get TextPosition

As shown in the figure above, when the user clicks on the green light spot, first we can confirm the RenderObject rendered by which Element is clicked according to the click event;
First, we convert the globalPosition of the gesture callback to the localPosition relative to Mural through the globalToLocal method; then traverse the child of the MuralRenderEditable to find the child containing the localPosition;
As described above, the RenderObject rendered by Element implements the RenderEditorInlineBox abstract class, and the TextPosition relative to the current TextPainter can be obtained through the getPositionForOffset method;
TextPosition to MuralPoint
Next, we need to solve the second problem, how to convert TextPosition into the description of the cursor and selection position in the protocol;
Take the above picture as an example. After clicking, the Offset of TextPosition is 12, and how does the Slate protocol describe such a cursor position? As shown in the figure above, it becomes a Point whose Path is [0,2] and offset is 2.
Cursor position calculation
The next step is to calculate the cursor position. Through the getOffsetForCaret method of TextPainter, the cursor position of the selected Element corresponding to the RenderObject is obtained, and then converted into an Offset relative to the global Mural;

Related Articles

Explore More Special Offers

  1. Short Message Service(SMS) & Mail Service

    50,000 email package starts as low as USD 1.99, 120 short messages start at only USD 1.00