给Notepad2加一个光标动画

发现一个问题, 经常碰到一种情况, 就是屏幕上找不到光标, 这是时候通常的做法是按住Shift键, 然后按一下方向键, 这样就会选择文本并且高亮, 这样就是看到光标在哪里了. 因为光标只有一个像素宽, 一般都是黑色, 周围围绕这黑色文本的话, 很容易迷失光标的所在. 这种情况通常发生在在其他窗口中做了一些操作之后再切换回编辑器窗口的时候, 此时可能已经忘记了上一次是在哪里编辑的. 如果切换回来的时候在光标所在的地方播放一段动画则一眼就可以看到了.

先看看最终效果:

为了完成这个功能, 改了Scintilla的很多部分, 为了解决闪烁的问题在原来的bufferedDraw的基础上增加了一个doubleBuffering. 不这样做动画会有严重的闪烁问题.

添加了一个定时器, 用来定期Invalidate动画区域, 每次获得焦点的时候就开始定时器, 如果用户有任何移动光标的动作就停止动画并切换回默认的光标闪烁方式.


void Editor::ShowAnimationCaret(){
    if(hasFocus) {
        ifcaret.active == true) {
            caret.active false;
            caret.on false;
            SetTicking(false);
        }
        SetAnimationTicking(true);
        animation true;
    else {
        animation false;
    }
}

下面的函数可以停止或者开始光标动画的定时器

void ScintillaWin::SetAnimationTicking(bool on ) {
    if(animation != on ) {
        animation on;
        if(animation) {
            animationTimer = ::SetTimer(MainHWND(), animationTimerID41NULL) ? 
                            reinterpret_cast<TickerID>(animationTimerID) : 
                            0;

        else {
            ::KillTimer(MainHWND(), reinterpret_cast<uptr_t>(animationTimer));
            animationTimer 0;
        }
    }
}

动画的实际操作在Animation中完成, 每隔几帧就释放出一个粒子, 这个粒子会按照一个随机的抛物方程运动, 同时颜色逐渐靠近背景色, 最终完全透明, 消失, 消失的粒子可以设置状态为dead, 在需要的时候可以重新初始化并作为一个新的粒子.

#define FRAMEMAX 30 
class ParticleBox {
public:
    int width;
    int height;
    
    // 不透明像素列表, 坐标相对于盒子的左上角
    // 绘制这个盒子的时候, 这些像素会和背景像素混合,其余的透明
    vector<PointpixelList;
    COLORREF color;
    
    ParticleBox() {
        color RGB(255,0,0);
        width height 10;
        for (int width i++) {
            if != width/2) {            
                pixelList.push_back(Point(i,height/2));
                pixelList.push_back(Point(i,height/1));
                
            }
        }
        
        for (int height i++) {
            if != height/2) {
                pixelList.push_back(Point(width/2,i));
                pixelList.push_back(Point(width/1,i));
            }
        }
        
        pixelList.push_back(Point(width/2,height/2));

    }
    void SetColor(COLORREF c) {
        color c;
    }
    
    
};

class Particle {
public:
    // 死掉的粒子可以重置后再利用
    bool died;
    int frameCount;
    Point position;
    ParticleBox pb;
    Point anchor;

    double a,b,c;// 抛物方程参数
    int anchorx;
    bool direction;

    int randomFactor;
    
    //一个粒子代表一个图案
    //一个活动的粒子有他的位置,和颜色颜色与背景色基于frame进行混合, 
    
    
    void Die () {
        died true;
        frameCount 0;
        
        // 重置一切必要的状态
    }
    Particle() {
        died true;
        frameCount 0;
        anchorx 0;
        direction true;
        randomFactor =  0;
    }

    
    // 粒子在一个盒子中, 盒子有他的宽高
    void Init(Point particleSourceint wingSpanint caretHeight bool direction) {
        randomFactor++;
        if(randomFactor == 10randomFactor 0;
        this->direction direction;
        died false;
        frameCount 0;

        srand(time(NULL) + randomFactor 800);
        // 在left 到 caret之间0 到 光标height之间产生一个坐标
        // 这个坐标是抛物线的顶点
        if(anchorx == wingSpananchorx 0;
        anchorx+= 8;

        if(directionanchor.rand() % wingSpan wingSpan;
        else anchor.rand() % wingSpan;
        anchor.rand() % caretHeight;

        // x == b 的时候取最大值, 取得极值, 极值就是c
        = (doubleanchor.x;
        = -b;// x == b的时候取得最大值
        = (doubleanchor.y;
        = -c;// 总是极大值,而且是负值

        // 代入particleSource从而得到a的值
        // -caretHeight = a ( wingSpan + 1 + b) ^ 2 + c
        = ((double)(-caretHeight) + (double)(-c)) / (( (double)wingSpan b) * ( (double)wingSpan b) );

        position.wingSpan 1;
        position.* (position.b) * (position.b) + ;
        position.= -position.y;
        
        int rand() % 255 1;
        int rand() % 255 1;
        int rand() % 255 1;
        pb.SetColor(RGB(255,0,0));
        
    }
    
    void Step() {
        // 左下

        if(directionposition.+= 1;
        else position.-= 1;
        position.* (position.b) * (position.b) + ;
        position.= -position.y;
        frameCount++;
        
        
    }
    
    // 在animationSurface绘图,然后复制到surface上
    void Render(Surface surfaceSurface animationSurface) {
        // we need a surface
        // 遍历每一个像素
        vector<Point>::iterator iter;
        for(iter pb.pixelList.begin() ; iter != pb.pixelList.end() ; iter++) {
            // 要根据*iter.x .y的值计算出, surface上对应点的坐标

            int targetx position.+ (*iter).x;
            int targety position.+ (*iter).y;
            COLORREF dest animationSurface->GetPixel(targetxtargety);
            COLORREF src pb.color;
            
            int deltR GetRValue(src)-GetRValue(dest);
            int deltG GetGValue(src)-GetGValue(dest);
            int deltB GetBValue(src)-GetBValue(dest);
            // (frameCount - FRAMEMAX)/50 应该从1到0
            // (((100.0/FRAMEMAX) * frameCount) * 0.01)
            COLORREF result RGB(GetRValue(dest) + (int)((double)deltR*(((100.0/FRAMEMAX) * (FRAMEMAX frameCount)) * 0.01)),
                                  GetGValue(dest) + (int)((double)deltG*(((100.0/FRAMEMAX) * (FRAMEMAX frameCount)) * 0.01)),
                                  GetBValue(dest) + (int)((double)deltB*(((100.0/FRAMEMAX) * (FRAMEMAX frameCount)) * 0.01))
                                  );
            animationSurface->SetPixel(targetxtargetyresult);
        }
    }    
};

// 当发生器需要一个粒子的时候, 就在这个列表中找一个没有使用的粒子
// 粒子可以循环使用, 当一个粒子生命周期到了之后, 进行重置, 然后可以再利用
class ParticleStock {
public:
    std::vector<Particle * > pList;
    
    ParticleStock () {
        pList.reserve(20);
        for(int 20 i++) {
            pList.push_back(new Particle());
        }
    }
    
    void MoveSurface surface Surface animationSurface ) {
        vector<Particle * >::iterator iter;
        // 某一个时刻有15个粒子存活
        for iter pList.begin(); iter != pList.end() ; ++iter ) {
            if( !(*iter)->died ) {
                (*iter)->Step();
                (*iter)->Render(surfaceanimationSurface);
                if( (*iter)->frameCount == FRAMEMAX ) {
                    (*iter)->Die();
                }
                
            }
            
        }    
    }
    
    Particle getDeadParticle() {
        vector<Particle * >::iterator iter;
        
        for iter pList.begin(); iter != pList.end() ; ++iter ) {
            
            if ( (*iter)->died ) {
                return (Particle *)(*iter);
            }
        }
        // nod dead particle?
        Particle *  new Particle();
        pList.push_back(p);
        return p;
    }
    
    // Stock应该只负责存取, 修改元素的内容不应在在这里发生
    void setAsDead(unsigned int index) {
        assert(index >= && index pList.size());
        Particle pList[index];
        p->Die();
        
    }
    
    ~ParticleStock () {
        vector<Particle * >::iterator iter;
        for iter pList.begin(); iter != pList.end() ; ++iter ) {
            if(*iter != NULL)
                delete (Particle *)(*iter);
        }        
    }
};

static RECT RectFromPRectangle(PRectangle prc) {
    RECT rc = {prc.leftprc.topprc.rightprc.bottom};
    return rc;
}

class Animation {
public:
    Point particleSource;
    ParticleStock ps;
    
    // 动画区域的宽度等于 wingSpan * 2 + caretWidth
    int wingSpan
    
    // 矩形的高度
    int height;
    
    // 绘制动画的区域, 和光标同一个参考系
    PRectangle drawRect;
    
    int frame;
    int caretHeight;
    bool direction;

    Surface animationSurface;

    bool initialized;
    Animation() {}
    ~Animation(){
        delete animationSurface;
    }
    // 
    Animation (int wingSpanint height) {
        this->wingSpan wingSpan;
        this->height height;
        initialized false;
        animationSurface Surface::Allocate();
        direction false;
        
    }
    
    
    void Init(PRectangle rcCaretint lineHeightSurface surfaceWindow) {
        if(initialized == truereturn;
        caretHeight rcCaret.bottom rcCaret.top;
        //要根据这个矩形构造出一个新的矩形
        //在这个矩形中, 我们会绘制动画, 矩形以光标所在的列为中轴线, 翼展可以在内部参数中定义
        drawRect.left rcCaret.left wingSpan;
        drawRect.right rcCaret.right wingSpan;
        drawRect.top rcCaret.top;
        drawRect.bottom drawRect.top height lineHeight;

        animationSurface->InitPixMap(drawRect.right drawRect.leftdrawRect.bottom drawRect.topsurfaceWindow0);

        
        // 确定粒子发生点
        particleSource.rcCaret.left;
        particleSource.rcCaret.top;
        
        frame 0;


        // 初始化particle列表
        initialized true;
    }
    
    void Step(Surface surface) {

        Point from(drawRect.left,drawRect.top);
        animationSurface->Copy(PRectangle(0,0,drawRect.right drawRect.left,drawRect.bottom drawRect.top),
                                from, *surface);

        //HBRUSH hbr = CreateSolidBrush(RGB(255,0,255));
        //FrameRect(surface->GetHdc(),&RectFromPRectangle(drawRect),hbr);
        //DeleteObject(hbr);
    
        updateParticle(surface);
    
        if(frame == 10) {
            frame 0;
        else {
            frame++;
        }
    }
    
    void updateParticle(Surface surface) {
        
        if(frame == ) {
            
            Particle ps.getDeadParticle();
            p->Init(particleSourcewingSpancaretHeightdirection);
            direction = !direction;// 每次切换方向
        }
        
        ps.Move(surface animationSurface );

        // 将animationSurface拷贝回去到surface中
        surface->Copy(drawRect,
                    Point(0,0),*animationSurface);
    }
};

这里定义一个粒子的方法是在一个矩形中定义一个像素列表, 这些像素就构成了一个粒子, 这里面是用ParticleBox来表示的, 现在只用了一个十字形状的粒子, 我们完全可以自定义粒子的形状, 只要定义不同的像素列表即可.

粒子的x坐标每次都会移动一个像素, 而y坐标则通过抛物线函数计算出来, 随着粒子的移动粒子的颜色会逐渐透明化, 直至完全透明.