GithubHelp home page GithubHelp logo

Comments (27)

Rosemoe avatar Rosemoe commented on May 28, 2024 2

不过我个人不建议逐个字符绘制,这在显示字符较多的时候效率较低,尤其是在较旧的设备上。而且这样就和TextWarrior的绘制方式几乎无异。我十分关注编辑器在显示方面的性能,因此我才使用了按Span绘制的方案。
我目前使用的R7s,编辑器绘制我的CodeEditor.java的部分区域会超过16ms线,字体较小时部分区域甚至达16ms的2.5倍左右。这还是去除了绘制过程中额外用于计算最大滑动X位置的measureText()过程的数据(现在已经改在插入/删除时计算需要刷新的行)。之后measure这一步还将移动位置。

对比如图所示:
Screenshot_2020-08-18-12-42-12-848
可以发现,字体大小相同的情况下,在TextWarrior中拥有更少的高亮区域,但是时间仍然较高,以至掉帧。

from sora-editor.

Rosemoe avatar Rosemoe commented on May 28, 2024 2

对的。向系统频繁提交measure/draw是很费时的。这一般与代码行数无关(至少在我这里是),而与单行的长度有关。一般来说,横向滑动到的位置越大,字体越小(可见行越多),绘制越困难。
在绘制单行超长文本的时候,会更慢,因此我引入了二分来减少损耗,但是效果也不是很明显。
我曾经试过把100KB的文本挤在同一行,即使二分也无法拯救掉帧。而且在滑动到的位置较小的时候,由于前部有高亮,后面全部被识别为注释,使用二分反而比有序绘制慢了。但是从总体上来说这种情况必须使用二分。
我的二分不在所有条件下使用的原因是,在绘制小文本的时候二分反而会导致性能轻微下降。

from sora-editor.

Rosemoe avatar Rosemoe commented on May 28, 2024 2

现在已经加入了对空白符号的绘制支持(仅限tab和空格),支持设定单行文本区域的哪些部位需要绘制它们(Leading, Inner, Railing)。
此外,对于光标闪烁的支持(可选,可定闪烁间隔)也已添加。

from sora-editor.

liyujiang-gzu avatar liyujiang-gzu commented on May 28, 2024 1

对了,Gutter是什么位置😂

gutter就是行号栏,取名参照ace、codemirror的

from sora-editor.

Rosemoe avatar Rosemoe commented on May 28, 2024 1

应该就是measureText的问题了,上次我测试了复制出char[],基本没有消耗。而二分之后drawText的任务很小,所以应该是measure问题。
我测试发现发现缓存确实十分有效,尤其是在多次测量中能体现巨大的优势。
我刚刚开了#15,准备使用缓存。

from sora-editor.

Rosemoe avatar Rosemoe commented on May 28, 2024

这个主意确实很好,我会考虑如何在将来编辑器中融合这一特点和现今的方式。

from sora-editor.

liyujiang-gzu avatar liyujiang-gzu commented on May 28, 2024

主要是面向接口编程,抽出主题色彩接口,实现用户自定义配色,自定义配色方案是代码编辑器的标配了,通过该接口可以轻松导入主流代码编辑器或者IDE的主题

from sora-editor.

Rosemoe avatar Rosemoe commented on May 28, 2024

对了,Gutter是什么位置😂

from sora-editor.

Rosemoe avatar Rosemoe commented on May 28, 2024

再请教一下,Secondary的Foreground/Background分别是什么位置呢?
还有当前代码块的划线的颜色是取消了吗?还有选择小手柄的颜色在哪里

from sora-editor.

Rosemoe avatar Rosemoe commented on May 28, 2024

还有NonPrintable的显示很不错,我之后也将会加入它

from sora-editor.

liyujiang-gzu avatar liyujiang-gzu commented on May 28, 2024

还有NonPrintable的显示很不错,我之后也将会加入它

不可打印字符目前我只实现了制表符和空格,utf8的bom头、回车换行符还没完全弄好

from sora-editor.

liyujiang-gzu avatar liyujiang-gzu commented on May 28, 2024

Secondary的Foreground/Background,是次要的前景色和背景色,可用在工具栏等其他地方,和编辑器关系不大了

from sora-editor.

liyujiang-gzu avatar liyujiang-gzu commented on May 28, 2024

再请教一下,Secondary的Foreground/Background分别是什么位置呢?
还有当前代码块的划线的颜色是取消了吗?还有选择小手柄的颜色在哪里

当前代码块的那个矩形颜色,我看好多IDE都没用有,效果也不太好,有那个代码块的折叠线足够了。光标手柄颜色是有的,我用的是光标的颜色,没单独提供定义,最近我又改进了配色方案,更改了主题配色接口。

from sora-editor.

liyujiang-gzu avatar liyujiang-gzu commented on May 28, 2024

Screenshot_2020_0817_231831

/*
 *   Copyright 2020 Rosemoe
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */
package io.github.rosemoe.editor.interfaces;

import androidx.annotation.ColorInt;

/**
 * Theme scheme for all UI
 *
 * @author gzu-liyujiang ([email protected])
 * @since 2020/08/12 17:20
 */
public interface ThemeScheme {

    /**
     * 元数据的颜色,如注解
     */
    @ColorInt
    int getMetadataColor();

    /**
     * 函数名/方法名的颜色
     */
    @ColorInt
    int getFunctionColor();

    /**
     * 标识名的颜色,如变量声明标识、标签属性标识
     */
    @ColorInt
    int getIdentifierColor();

    /**
     * 变量名的颜色
     */
    @ColorInt
    int getVariableColor();

    /**
     * 字符/字符串的颜色
     */
    @ColorInt
    int getStringColor();

    /**
     * 数字的颜色,包括整数、小数、二进制数、十六进制数等
     */
    @ColorInt
    int getNumberColor();

    /**
     * 特殊操作符的颜色,如 @$;,
     */
    @ColorInt
    int getOperatorColor();

    /**
     * 块注释或文档注释的颜色
     */
    @ColorInt
    int getBlockCommentColor();

    /**
     * 单行注释的颜色
     */
    @ColorInt
    int getLineCommentColor();

    /**
     * 关键字/保留字的颜色
     */
    @ColorInt
    int getKeywordColor();

    /**
     * 主要的背景色
     */
    @ColorInt
    int getPrimaryBackgroundColor();

    /**
     * 主要的前景色
     */
    @ColorInt
    int getPrimaryForegroundColor();

    /**
     * 次要的背景色
     */
    @ColorInt
    int getSecondaryBackgroundColor();

    /**
     * 次要的前景色
     */
    @ColorInt
    int getSecondaryForegroundColor();

    /**
     * 行号栏的背景色
     */
    @ColorInt
    int getGutterBackgroundColor();

    /**
     * 行号栏的前景色
     */
    @ColorInt
    int getGutterForegroundColor();

    /**
     * 行号栏的分割线色
     */
    @ColorInt
    int getGutterDividerColor();

    /**
     * 滚动条块普通状态的颜色
     */
    @ColorInt
    int getScrollBarThumbNormalColor();

    /**
     * 滚动条块按下状态的颜色
     */
    @ColorInt
    int getScrollBarThumbPressedColor();

    /**
     * 选取文字的背景色
     */
    @ColorInt
    int getTextSelectedColor();

    /**
     * 搜索匹配文字的颜色
     */
    @ColorInt
    int getTextMatchedColor();

    /**
     * 光标所在行的颜色
     */
    @ColorInt
    int getCurrentLineColor();

    /**
     * 光标及其选择手柄的颜色
     */
    @ColorInt
    int getCaretColor();

    /**
     * 不可打印字符的颜色,如空格、制表符、换行符
     */
    @ColorInt
    int getNonPrintableCharColor();

    /**
     * 折叠线/代码块指示线的颜色
     */
    @ColorInt
    int getFoldLineColor();

    /**
     * 下滑线/波浪线的颜色
     */
    @ColorInt
    int getUnderlineColor();

    /**
     * 着重强调的颜色,如 TODO、FIXME
     */
    @ColorInt
    int getEmphasizeColor();

    /**
     * 是否暗色系/夜间模式
     */
    boolean isDark();

}

from sora-editor.

liyujiang-gzu avatar liyujiang-gzu commented on May 28, 2024
/*
 *   Copyright 2020 Rosemoe
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */
package io.github.rosemoe.editor.theme;

import io.github.rosemoe.editor.interfaces.ThemeScheme;

/**
 * `Eclipse` style, color picked from Eclipse IDE for Java Developers Version 2019-12 (4.14.0)
 *
 * @author gzu-liyujiang ([email protected])
 * @since 2020/08/15 01:49
 */
public class Eclipse implements ThemeScheme {

    @Override
    public int getMetadataColor() {
        return 0xff646464;
    }

    @Override
    public int getFunctionColor() {
        return 0xff000000;
    }

    @Override
    public int getIdentifierColor() {
        return 0xff000000;
    }

    @Override
    public int getVariableColor() {
        return 0xffb8633e;
    }

    @Override
    public int getStringColor() {
        return 0xff2a00ff;
    }

    @Override
    public int getNumberColor() {
        return 0xff000000;
    }

    @Override
    public int getOperatorColor() {
        return 0xff3a0000;
    }

    @Override
    public int getBlockCommentColor() {
        return 0xff3f5fbf;
    }

    @Override
    public int getLineCommentColor() {
        return 0xff3f7f5f;
    }

    @Override
    public int getKeywordColor() {
        return 0xff7f0074;
    }

    @Override
    public int getPrimaryBackgroundColor() {
        return 0xffffffff;
    }

    @Override
    public int getPrimaryForegroundColor() {
        return 0xff000000;
    }

    @Override
    public int getSecondaryBackgroundColor() {
        return 0xffe1e6f6;
    }

    @Override
    public int getSecondaryForegroundColor() {
        return 0xff282828;
    }

    @Override
    public int getGutterBackgroundColor() {
        return 0xffffffff;
    }

    @Override
    public int getGutterForegroundColor() {
        return 0xff787878;
    }

    @Override
    public int getGutterDividerColor() {
        return 0xfff8f8f8;
    }

    @Override
    public int getScrollBarThumbNormalColor() {
        return 0xffd8d8d8;
    }

    @Override
    public int getScrollBarThumbPressedColor() {
        return 0xffc800a4;
    }

    @Override
    public int getTextSelectedColor() {
        return 0xff3399ff;
    }

    @Override
    public int getTextMatchedColor() {
        return 0xffd4d4d4;
    }

    @Override
    public int getCurrentLineColor() {
        return 0xffe8f2fe;
    }

    @Override
    public int getCaretColor() {
        return 0xff03ebeb;
    }

    @Override
    public int getNonPrintableCharColor() {
        return 0xffe8e8e8;
    }

    @Override
    public int getFoldLineColor() {
        return 0xffd8d8d8;
    }

    @Override
    public int getUnderlineColor() {
        return 0xffff0000;
    }

    @Override
    public int getEmphasizeColor() {
        return 0xff7f9fc9;
    }

    @Override
    public boolean isDark() {
        return false;
    }

}

from sora-editor.

liyujiang-gzu avatar liyujiang-gzu commented on May 28, 2024

还有NonPrintable的显示很不错,我之后也将会加入它

不可打印字符实现(UTF-8的BOM头绘制位置还没算好):
image

    private static final char CHAR_BOM_HEADER = '\ufeff';
    private static final char CHAR_SPACE = 0x20;
    private static final char CHAR_TAB = '\t';
    private static final char CHAR_NEWLINE = '\n';
    private static final String GRAPH_BOM_HEADER = "¤";
    private static final String GRAPH_SPACE = "·";
    private static final String GRAPH_TAB = "»";
    private static final String GRAPH_HARD_WRAP = "↵";
    private static final String GRAPH_SOFT_WRAP_START = "↩";
    private static final String GRAPH_SOFT_WRAP_END = "↪";

    /**
     * 绘制高亮文本
     */
    private void drawTextWithSpans(...) {
        char[] chars = getCharsAtLine(line);
        LogUtils.print("Draw line " + line + ": region[" + regionStart + ", " + regionEnd +
                "] charLength=" + chars.length);
...
        if (nonPrintableCharVisible) {
            drawNonPrintableChar(canvas, width, baseline, GRAPH_HARD_WRAP);
        }
    }

    /**
     * 绘制已进行过跨度处理的字符
     */
    private void drawSpanChars(Canvas canvas, char[] src, int index, int count, float offX, float offY) {
        for (int i = index, n = index + count; i < n; i++) {
            switch (src[i]) {
                case 0xd83c:
                case 0xd83d:
                    emojiCharPrefix = src[i];
                    break;
                case CHAR_BOM_HEADER:
                    if (nonPrintableCharVisible) {
                        drawNonPrintableChar(canvas, offX, offY, GRAPH_BOM_HEADER);
                        offX += paint.measureText(GRAPH_BOM_HEADER);
                    }
                    break;
                case CHAR_TAB:
                    if (nonPrintableCharVisible) {
                        drawNonPrintableChar(canvas, offX, offY, GRAPH_TAB);
                        float tabWidth = paint.measureText(GRAPH_TAB);
                        tabWidth = Math.max(tabWidth, tabSize * spaceWidthPx);
                        offX += tabWidth;
                    } else {
                        canvas.drawText(new char[]{CHAR_SPACE}, 0, 1, offX, offY, paint);
                        offX += tabSize * spaceWidthPx;
                    }
                    break;
                case CHAR_SPACE:
                    if (nonPrintableCharVisible) {
                        drawNonPrintableChar(canvas, offX, offY, GRAPH_SPACE);
                    } else {
                        canvas.drawText(new char[]{CHAR_SPACE}, 0, 1, offX, offY, paint);
                    }
                    offX += spaceWidthPx;
                    break;
                default:
                    char[] ch;
                    if (emojiCharPrefix != 0) {
                        ch = new char[]{emojiCharPrefix, src[i]};
                        emojiCharPrefix = 0;
                    } else {
                        ch = new char[]{src[i]};
                    }
                    canvas.drawText(ch, 0, ch.length, offX, offY, paint);
                    offX += paint.measureText(ch, 0, ch.length);
                    break;
            }
        }
    }

    /**
     * 绘制不可打印字符
     */
    private void drawNonPrintableChar(Canvas canvas, float x, float y, String graph) {
        int originColor = paint.getColor();
        float originTextSize = paint.getTextSize();
        if (graph.equals(GRAPH_BOM_HEADER)) {
            paint.setColor(0xffff0000);
        } else {
            paint.setColor(themeScheme.getNonPrintableCharColor());
        }
        paint.setTextSize(originTextSize * 0.9f);
        canvas.drawText(graph, x, y, paint);
        paint.setColor(originColor);
        paint.setTextSize(originTextSize);
    }

from sora-editor.

Rosemoe avatar Rosemoe commented on May 28, 2024

好的,谢谢了。

from sora-editor.

liyujiang-gzu avatar liyujiang-gzu commented on May 28, 2024

@Rosemoe 老铁你在性能方面优化的还不错,我倒是忽略性能方面了,其实不可打印字符的实现方式我就是参考了TextWarrior,不逐个字符绘制的话,那么不可打印字符(尤其是制表符和空格)应该可以当做Span的一部分。

from sora-editor.

Rosemoe avatar Rosemoe commented on May 28, 2024

其实一般的编辑器都是绘制每行前部的不可见字符,和行末回车,中间的其实可以不必绘制😃

from sora-editor.

liyujiang-gzu avatar liyujiang-gzu commented on May 28, 2024

image

Android Studio这个强,支持设置首尾及内部空白符是否显示。
@Rosemoe
顺便说一下:有必要绘制光标闪烁:

/**
 * @author gzu-liyujiang ([email protected])
 * @since 2020/8/14 22:07
 */
@RestrictTo(RestrictTo.Scope.LIBRARY)
public final class CaretBlink implements Runnable {
    private final TextEditor editor;

    public CaretBlink(TextEditor editor) {
        this.editor = editor;
    }

    @Override
    public void run() {
        editor.reverseCaretVisibility();
        editor.postDelayed(this, 500);
    }

}
    /**
     * Initialize variants
     */
    private void init() {
...
        caretBlink = new CaretBlink(this);
        startCaretBlink();
...
     }

    public void startCaretBlink() {
        stopCaretBlink();
        postDelayed(caretBlink, 500);
    }

    public void stopCaretBlink() {
        caretVisible = true;
        removeCallbacks(caretBlink);
        invalidate();
    }

    void reverseCaretVisibility() {
        this.caretVisible = !caretVisible;
        invalidate();
    }

    /**
     * 绘制光标
     */
    private void drawCaret(Canvas canvas, int line, int column) {
        if (isRowVisible(line) && caretVisible) {
            int offsetX = getGutterOffsetX();
            char[] chars = getCharsAtLine(line);
            float width = measureText(chars, 0, column);
            rect.top = getRowTop(line) - getCurrentScrollY();
            rect.bottom = getRowBottom(line) - getCurrentScrollY();
            rect.left = offsetX + width;
            rect.right = offsetX + width + caretWidthPx;
            drawRect(canvas, themeScheme.getCaretColor(), rect);
        }
    }

from sora-editor.

Rosemoe avatar Rosemoe commented on May 28, 2024

确实如此。目前主要在做wordwrap,这个可能会在之后加。
光标移动后,可见性可能应该保留一段时间。
另外这个工作可以在onAttachedToWindow和onDetachedFromWindow对光标闪烁进行启动/停止,以便减少性能损耗。

from sora-editor.

liyujiang-gzu avatar liyujiang-gzu commented on May 28, 2024

确实如此。目前主要在做wordwrap,这个可能会在之后加。
光标移动后,可见性可能应该保留一段时间。
另外这个工作可以在onAttachedToWindow和onDetachedFromWindow对光标闪烁进行启动/停止,以便减少性能损耗。

感谢你的提点,还有正在滚动的时候也应该暂停光标闪烁。兄弟加油,感谢你的无私奉献!

from sora-editor.

liyujiang-gzu avatar liyujiang-gzu commented on May 28, 2024

逐字绘制的确损耗了太多性能啊!绘制9779行的codemirror.js的内容,最高花费35毫秒。按行绘制则最高只要18毫秒,损耗了一半的时间。

from sora-editor.

Rosemoe avatar Rosemoe commented on May 28, 2024

然而,我发现MT的编辑器的效率比我的更高,这说明有其他优化方案,我还会继续改进。

from sora-editor.

Rosemoe avatar Rosemoe commented on May 28, 2024

吐了,不小心close了

from sora-editor.

Rosemoe avatar Rosemoe commented on May 28, 2024

粘贴120KB的单行文本,我现在十分不明白为什么MT的测量可以秒出,而我的测量却用了大约10s,这实在让我迷惑。
而且MT绘制也不是很卡顿,大约50ms,而我的却会几乎无法绘制完成
而且似乎也没有使用二分,滑动位置为0的时候绘制时间与正常文本无异
简直就像常数很小的O(N)
我初步怀疑它具有字体缓存或其它优化。
可能是直接使用measureText太慢。系统可能会每次都重新测量字体,无论画笔属性是否改变。
(自言自语,可以忽略它)

from sora-editor.

liyujiang-gzu avatar liyujiang-gzu commented on May 28, 2024

粘贴120KB的单行文本,我现在十分不明白为什么MT的测量可以秒出,而我的测量却用了大约10s,这实在让我迷惑。
而且MT绘制也不是很卡顿,大约50ms,而我的却会几乎无法绘制完成
而且似乎也没有使用二分,滑动位置为0的时候绘制时间与正常文本无异
简直就像常数很小的O(N)
我初步怀疑它具有字体缓存或其它优化。
可能是直接使用measureText太慢。系统可能会每次都重新测量字体,无论画笔属性是否改变。
(自言自语,可以忽略它)

能检测出是哪一步耗时最多吗?找出来才好对症下药。若是measureText耗时最多,那可以在初始化画笔及字体改变的时候测量好一个字所占的宽度存起来,不用在onDraw里处处测量。

from sora-editor.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.