【编写自己的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;}
运行截图
- 【编写自己的GUI】(三)EditText的实现
- 【编写自己的GUI】(二)基本控件的实现
- 【编写自己的GUI】(一)起航
- 关于编写GUI程序我自己的一些理解
- 基于OgreBites::Widget 实现自己的GUI系统(1)
- 基于OgreBites::Widget 实现自己的GUI系统(2)
- 基于Ogre::Bites实现自己的GUI系统(3)
- 在BREW中打造自己的GUI(1)-图形化菜单的实现
- 在BREW中打造自己的GUI(2)-TabPane的实现
- 在BREW中打造自己的GUI(4)-IGStatic的实现
- 在BREW中打造自己的GUI(5)-滚动条的实现
- 在BREW中打造自己的GUI(6)-单选框与复选框的实现
- 在BREW中打造自己的GUI(7)-动态效果的实现
- 在BREW中打造自己的GUI(5)-滚动条的实现
- GUI(三)一个菜单的程序
- 跨平台 GUI 的三种实现策略.md
- 编写自己的代码生成工具三:代码生成组织者
- 篇三、编写自己的linux系统调用
- Hibernate与 MyBatis的比较
- html 的标签的使用
- 深入理解Android中的View
- 【BZOJ3407】[Usaco2009 Oct]Bessie's Weight Problem 贝茜的体重问题【01背包】
- hibernate分页查询和方言
- 【编写自己的GUI】(三)EditText的实现
- 新的起点
- C++游戏系列5:不止有一件武器
- Leetcode no.33
- JSP的CSS背景样式写法
- Android UI-实现底部切换标签(fragment)
- 华为2016年精英挑战赛总结
- lm3488升压芯片电路调试 boost
- 面试题之二维数组中的查找