rasteric / zedit-fyne

an experimental single-font text editing widget for Fyne with syntax coloring
MIT License
2 stars 0 forks source link

Regarding the issue of rendering very long texts in the Entry. #1

Open 0x414a opened 3 weeks ago

0x414a commented 3 weeks ago

Hello, I'm currently encountering a problem where the rendering of very long texts in the Entry is significantly lagging. I noticed that your project doesn't seem to have this issue. Could you please let me know how you solved it? Thank you.

rasteric commented 3 weeks ago

Zedit-fyne separates the rendering from the edit operations. It store rows in a[][]rune array, one[]rune array for each line. It also has a line offset that represents the current top line. It has a fixed line x column display size. Whenever it needs rendering in Refresh(), it currently uses a textgrid to display the runes for the lines from the line offset to line offset + lines. This operation is linear in lines x columns. That being said, textgrid's rendering is itself rather slow, so it could probably be sped up a lot by drawing the text directly in a Canvas. Word wrapping is also implemented per paragraph in zedit, meaning that only the paragraphs that are changed are reflown. It seems Fyne entries do none of this yet (but e.g. List does buffering), so they somehow deal with all lines of text on each refresh.

I also use an interval tree for marking text, including styles. If you look at the example, select text and press Cmd+1...Cmd+9, it marks the selection with a background color. When refreshing, the interval tree is used to look up only markers overlapping with the current display interval. This guarantees that any style refresh is linear in the number of markers that overlap with the display interval.

The problem is that zedit-fyne is completely untested and the edit operations are extremely complicated and likely buggy. The interval approach means that any editing operation may change all subsequent and overlapping intervals, and the semantics for these changes is dreadfully complicated. Take a look at func (z *Editor) maybeAdjustTagIntervalForDelete, for example. I hope to have gotten every case but have no systematic way of verifying that this is correct and no case was missed. The widget not ready for use, maybe never will.

But you could implement a similar widget easily by using the above ideas except for storing styles in a more traditional way with the runes, i.e., using [][]Cell where Cell is a struct containing any style information. That is less efficient but quite safe.

0x414a commented 3 weeks ago

Yes, Entry will re-render when in and out of focus causing heavy CPU usage and low rendering. This issue was reported to the fyne team but has not been resolved. Now I need to deal with a lot of text, but it is very slow, I am not a front-end, although I have studied for a while, it is still difficult to see that your project is not so slow when dealing with long text, but I do not have the ability to customize as you said, I wonder if you can help me in your project simply extract the function of the rendering part, I see a lot of code in your project, very headache

0x414a commented 3 weeks ago
image

Good project, worth me to learn, I try to understand the project code, try to see if I can extract what I need

rasteric commented 3 weeks ago

Refresh() is a hack to render not more often than z.Config.MinRefreshInterval but at least once. It could be a premature optimization.

The actual rendering is in (z *Editor) refreshProc(). It copies the runes into the TextGrid and resets its styles, puts the line numbers in a separate grid, and then applies the styles by looking up Tags by name, getting their intervals, and calling maybeStyleRange. This only applies the style if the Tag's interval is within the display window.

In the end, it just refreshes the grids, so they do the actual rendering. Hope that helps!

0x414a commented 3 weeks ago
image

Thanks for the idea, I haven't verified yet if I can handle very large text, but it feels good and I'm working on more features, thanks again

0x414a commented 3 weeks ago
image

Use a list to nest canvas elements, allowing the number of columns to change along with the parent widget. This setup has also resolved the issue with line numbers and cursor movement.