【编写自己的GUI】(三)EditText的实现

来源:互联网 发布:模拟退火算法例题 编辑:程序博客网 时间:2024/05/23 23:37

在这篇博客中,我们说说EditText(编辑框)的实现。

字体的绘制

我们这里会使用到SDL的插件SDL_image和SDL_ttf的功能。

首先将字符串转换成SDL_Surface,然后再转换为SDL_Texture。

SDL_Surface*  textSurface = TTF_RenderText_Solid(gFont, textureText.c_str(), textColor);mTexture = SDL_CreateTextureFromSurface(gRenderer, textSurface);

这里的gFont是一个TTF_Font类型的指针,我们在初始化的时候对其进行了加载。

gFont = TTF_OpenFont("Cousine-Regular.ttf", 18);

然后调用SDL_RenderCopyEx()函数将Texture绘制到屏幕窗口上。

SDL_RenderCopyEx(gRenderer, mTexture, clip, &renderQuad, angle, center, flip);

就这样,我们就可以把一个字符串绘制到窗口上了。

CuEditState

EditText有许多状态需要保存,因此我们需要在CuGuiState中保存一个CuEditState结构体变量。该结构体定义如下:

struct CuEditState{    int       EditingTextId;    int       FontWidth;    int       MaxPresentFontNum;    int       TextBufferSize;    int       PreTextLength;       double    BlinkingTime;    int       CursorPos;           int       CursorIndex;              CuEditState()    {        ...    }};

其中 EditingTextId指定我们当前正在编辑哪个编辑框, FontWidth是字体的宽度, TextBufferSize是我们保存字符串文本的缓存大小。接下来四个成员与光标和字符串的绘制有关,我们待会来解释它们。

光标的实现

光标是编辑框实现中最麻烦的问题,特别是光标的位置更新。
一开始,我是使用两个变量去确定光标的位置信息,一是PreTextLength表示输入字符串在编辑框左边(看不到的地方)的长度。另一个是CursorPos,记录光标在输入字符串中的位置(可以称为字符串光标)。后来,发现这样总会出现各种问题。仔细思考之后发现,单纯记录这两个变量不足以正确地表达光标和文本的显示状态。因此,我又加上了一个变量 CursorIndex,表示光标在编辑框中的位置(可以称为显示光标)。

在设计光标的位置更新的逻辑时,我主要参考了百度搜索框的输入逻辑。 在搜索框来来回回输入删除了几十遍之后,发现引起光标的位置更新(实际上还有PreTextLength的更新)的,有下面几种情况:

  • 点击编辑框时:此时我们需要计算点击的位置,如果这个位置超过了 显示字符串的长度,那么就把显示光标移动到显示字符串的末尾。否则就把显示光标移动到所点击的位置。而字符串光标的位置,就是编辑框左端的字符串长度加上显示光标的值,也就是CursorPos=PreTextLength+CursorIndex;
    此部分代码如下:
if (isHovered){    //Update cursor     int offset = (GuiState.MouseX - x)/editState.FontWidth;    if (offset > textLength-editState.PreTextLength)         editState.CursorIndex = textLength - editState.PreTextLength;    else        editState.CursorIndex = offset;    editState.CursorPos = editState.PreTextLength + editState.CursorIndex;    }   }
  • 删除字符时:字符串光标左移(CursorPos-1)。然后分三种情况:(1)字符已经塞满了输入框,这个情况下需要让显示光标后退,呈现出光标后的字符左移的效果;(2)字符没有塞满输入框,而编辑框左端有字符串没有被显示,那么就让PreTextLength-1,产生光标前字符串右移的效果;(3)在(1)(2)都不成立的情况下,也就是我们普通的删除的情况下,让显示光标左移就好了。
switch (KeyInput.SpecialKey){case CU_BACKSPACE:    if (editState.CursorPos > 0)      {        int pos = editState.CursorPos - 1;        while (text[pos]!='\0')        {            text[pos] = text[pos+1];            pos++;        }        editState.CursorPos--;        if (textLength - editState.PreTextLength > editState.MaxPresentFontNum)            editState.CursorIndex--;        else if (editState.PreTextLength > 0)            editState.PreTextLength--;        else            editState.CursorIndex--;        //Update textlength        textLength = caculStringLen(text);}   
  • 输入字符时:字符串光标右移(CursorPos+1)。然后判断显示光标是否已经到了输入框的末尾了。如果不是,那就直接右移。如果是的话,当我们在输入框末尾希望继续输入的话,我们希望显示的字符串整体向前弹一段距离,让字符串的最末端到达输入框的最末端。moveoffset就是我们向前弹的距离,用MaxPresentFontNum / 2限制它是为了防止moveoffset过大让显示光标左移太多甚至移出输入框的范围。
editState.CursorPos++;//Update textlengthtextLength = caculStringLen(text);if (editState.CursorIndex == editState.MaxPresentFontNum){    int moveOffset = textLength - editState.MaxPresentFontNum-editState.PreTextLength;    if (moveOffset > editState.MaxPresentFontNum / 2) moveOffset = editState.MaxPresentFontNum / 2;    editState.PreTextLength += moveOffset;    editState.CursorIndex = editState.CursorPos - editState.PreTextLength;}else    editState.CursorIndex++;
  • 左移光标时:当字符串光标大于0的时候,允许左移光标。首先让字符串光标左移(CursorPos-1),然后判断此时显示光标是否到达编辑框的最左端,如果是的话,就让PreTextLength-1,也就是字符串的显示范围右移。如果显示光标未到达编辑框的最左端,就让显示光标左移。
if (editState.CursorPos > 0){    editState.CursorPos--;    if (editState.CursorIndex == 0)        editState.PreTextLength--;    else  editState.CursorIndex--;}
  • 右移光标时:当字符串光标小于当前字符串的长度的时候,允许右移光标。首先让字符串光标右移,然后判断此时显示光标是否到达编辑框的最右端,如果是的话,就让PreTextLength++,也就是字符串的显示范围左移。如果显示光标未到达编辑框的最右端,就让显示光标右移。
if (editState.CursorPos < textLength){    editState.CursorPos++;    if (editState.CursorIndex == editState.MaxPresentFontNum)        editState.PreTextLength++;    else  editState.CursorIndex++;}   

光标的绘制

我们在editState(CuEditState变量)中增加一个BlinkingTime成员记录时间,每一帧我们让它增加1.0/60.0秒,当这个值大于0.5的时候绘制光标,当这个值大于1的时候又让它重置为0。通过这样的方式,就得到了一个以1秒为周期闪烁的光标了。其中AddLine()函数的功能与上一篇博客提到的AddRect()类似。

//Render Cursor//Blingking per secondeditState.BlinkingTime += 1.0 / 60.0;if (editState.BlinkingTime > 0.5){    int offset = editState.CursorIndex*editState.FontWidth;    AddLine(x + offset, y + 8, VERTICAL, 14, CURSORCOLOR);}if (editState.BlinkingTime > 1.0){    editState.BlinkingTime = 0;}

完整代码及运行截图

完整代码

int EditText(int id, int x, int y, int width, int height, char* text, int textSize){    CuEditState& editState = GuiState.EditState;    if (textSize > editState.TextBufferSize)    {        return -1;    }    editState.MaxPresentFontNum = width / editState.FontWidth;    int textLength = caculStringLen(text);    //Check whether the EditText is hovered     bool isHovered = IsInRect(x, y, width, height);    //Check whether the EditText should be active    if (isHovered)    {        GuiState.HoveredId = id;        if (GuiState.ActiveId == 0 && GuiState.MouseDown)        {            GuiState.ActiveId = id;            editState.EditingTextId = id;            //Update cursor             int offset = (GuiState.MouseX - x) /editState.FontWidth;            if (offset > textLength-editState.PreTextLength)                 editState.CursorIndex = textLength - editState.PreTextLength;            else                editState.CursorIndex = offset;            editState.CursorPos = editState.PreTextLength + editState.CursorIndex;        }    }    //When we are focusing on this EditText Widget    if (editState.EditingTextId == id)    {        //Process Keyboard Input        switch (KeyInput.SpecialKey)        {        case CU_BACKSPACE:            if (editState.CursorPos > 0)            {                    int pos = editState.CursorPos - 1;                    while (text[pos]!='\0')                    {                        text[pos] = text[pos+1];                        pos++;                    }                    editState.CursorPos--;                    if (textLength - editState.PreTextLength > editState.MaxPresentFontNum)                        editState.CursorIndex--;                    else if (editState.PreTextLength > 0)                        editState.PreTextLength--;                    else                        editState.CursorIndex--;                    //Update textlength                    textLength = caculStringLen(text);            }               break;        case CU_LEFT:            if (editState.CursorPos > 0)            {                editState.CursorPos--;                if (editState.CursorIndex == 0)                    editState.PreTextLength--;                else  editState.CursorIndex--;            }            break;        case CU_RIGHT:            if (editState.CursorPos < textLength)            {                editState.CursorPos++;                if (editState.CursorIndex == editState.MaxPresentFontNum)                    editState.PreTextLength++;                else  editState.CursorIndex++;            }               break;        default:            break;        }        if (KeyInput.InputCharacter)        {            int charNum = caculStringLen(KeyInput.InputCharacter);            int i = 0;            while (textLength <textSize - 1 && i < charNum)            {                int pos = textSize - 2;                while (pos > editState.CursorPos)                {                    text[pos] = text[pos - 1];                    pos--;                }                text[editState.CursorPos] = KeyInput.InputCharacter[i];                KeyInput.InputCharacter[i] = '\0';                i++;                editState.CursorPos++;                //Update textlength                textLength = caculStringLen(text);                if (editState.CursorIndex == editState.MaxPresentFontNum)                {                    int moveOffset = textLength - editState.MaxPresentFontNum-editState.PreTextLength;                    if (moveOffset > editState.MaxPresentFontNum / 2) moveOffset = editState.MaxPresentFontNum / 2;                    editState.PreTextLength += moveOffset;                    editState.CursorIndex = editState.CursorPos - editState.PreTextLength;                }                else                    editState.CursorIndex++;            }           }        //Render Cursor        //Blingking per second        editState.BlinkingTime += 1.0 / 60.0;        if (editState.BlinkingTime > 0.5)        {            int offset = editState.CursorIndex*editState.FontWidth;            AddLine(x + offset, y + 8, VERTICAL, 14, CURSORCOLOR);        }        if (editState.BlinkingTime > 1.0)        {            editState.BlinkingTime = 0;        }    }    //Render Rect    AddRect(x, y, width, height, TEXTFIED_COLOR);    //Render text    int renderTextNum = textLength - editState.PreTextLength > editState.MaxPresentFontNum ?        editState.MaxPresentFontNum : textLength - editState.PreTextLength;    AddFont(x, y + 5, text + editState.PreTextLength , renderTextNum);    if (GuiState.ActiveId != 0 && GuiState.ActiveId != id)    {        editState.EditingTextId = 0;    }    return 0;}

运行截图

这里写图片描述

0 0
原创粉丝点击