让OGRE支持中文中文输入

让OGRE支持中文3

                     ——中文输入

-1.前言

中文输入终于实现了,这并不是一个轻松的过程,足足用了几个月时间才搞定,真正负责中文输入处理的就是一个简单的类,不过为了实现这个类需要的接口对OGRE进行了大量的改动,在此期间读了相当多的OGRE源码,改写尽量保持OGRE自身的风格,零零碎碎改写的地方相当多,这篇文章难免有疏漏的地方,如果各位真的希望了解真正的改写,还是看看源码比较好,这篇文章能做一个源码导读。我相信大多数OGRE的使用者还是希望直接使用这个输入的,所以我给大家提供了一个例子和相应的代码,大家可以直接拿去用。

 

0.还是检讨

       之前已经写了两篇关于OGRE引擎支持中文的文章,第一篇是位图字体,第二篇是TFF字体,关于位图字体,在字体管理器的管理下,同一字体之产生了一个实例,所以不会产生我在第二篇文章说的用过多字体实例导致内存被占用。在第二篇TFF字体的程序中,有几个严重错误。第一个是释放文字,在写的时候我曾认为每个实例维护一个贴图,事实上却是相同字体实例共同使用一个贴图,而且在正常使用的过程中,这个字体类型的实例也只能有一个,但是调用这个字体类的实例进行渲染的TextAreaGuiElement却有很多,把记录字体是否使用的代码改成引用记数才能正常工作,之前的代码会导致错误的释放。第二个问题是向
贴图画字的时机,如果在申请贴图时候才画字,在某些情况下会无法显示,改成在判断是否使用该字也就是调用引用记数时进行画字便解决了这个问题。第三个问题是在之前代码的顺序下,首先申请绘字空间然后判断是否在字库中有这个字,这样导致申请空间却没有画字的情况出现。在修改了顺序之后解决了这个问题。大体思路没有改变,只是进行了一些修改和优化,有兴趣的朋友可以直接看源码中的OgreFont.h、OgreFont.cpp、OgreTextAreaGuiElement.h,、OgreTextAreaGuiElement.cpp,这四个文件,并和之前的文件对比一下就能发现区别了。

 

1.捕捉Windows消息

       在Windows环境下,如果要进行中文输入,捕捉Windows的祖字消息是必需的,但是OGRE为了其支持多平台的特性,从而拒绝将Windows消息交给用户处理,封装在相应的窗口渲染类中。


上图是处理窗口选的相关类的继承图,其中D3D7RenderWindow(DirectX7.0)、D3D9RenderWindow(DirectX9.0)、Win32Window(OpenGL)三个类是在Windows环境下处理窗口的相应类,Windows消息便封装在这三个类中,在我们所使用的抽象层RenderWindow类中已经无法得知具体的Windows消息,我们要做的就是在RenderWindow中建立一个可以监听Windows消息的接口,并且在D3D7RenderWindow(DirectX7.0)、D3D9RenderWindow(DirectX9.0)、Win32Window(OpenGL)三个类向上传递Windows消息,在Linux和Max环境中没有消息传递(这样的处理破坏了OGRE的多平台性,但是我所知道的中文输入处理只是针对Windows平台的,如果要保持多平台性应该做更多的处理,或者实现自己的输入法,不过这就是一个更大更大的工程了)。

参照OGRE的编码风格,我使用监听者模式,一个向要得知Windows消息的对象,需要具备(继承)Windows消息监听者的接口(RenderWindowListener),然后向RenderWindow实例注册自身,当有Windows消息出现时候RenderWindow实例得到windows消息,并向所有注册的实例发送消息。

我们首先实现两个基础结构和类。

 

 

 
     struct RenderWindowEvent //这个结构用来封装windows消息
     {
                 HWND hWnd;
                 UINT uMsg;
                 WPARAM wParam;
                 LPARAM lParam;
     };
 
     class _OgreExport RenderWindowListener //消息监听者的接口
     {
     public:
            virtual bool RWUpdate(const RenderWindowEvent& evt) { return true;}
    };
    
然后在RenderWindow增加

virtual void
 addRWListener (RenderWindowListener* listener)//注册监听者
 
virtual void
 removeAllRWListeners (void)//移除所有监听者
 
virtual void
 removeRWListener (RenderWindowListener* listener)//移除某一监听者
 
virtual bool
 RWUpdate (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

//系统得到消息时调用的函数,在这里向所有监听者发送消息
 

具体请参照本文提供的代码。

 

最后在Windows的回调函数中加入处理代码,这里拿DX9.0为例。在下面函数中

LRESULT D3D9RenderWindow::WndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
增加

                 LPCREATESTRUCT lpcs;
                 D3D9RenderWindow* win=NULL;
 
                 // look up window instance
                if( WM_CREATE != uMsg )
                         // Get window pointer
                        win = (D3D9RenderWindow*)GetWindowLong( hWnd, 0 );
         // 以下三行是我们增加的
                 if(win)
                         if(!win->RWUpdate(hWnd,uMsg, wParam, lParam ))
                                 return 0;
 

       这样就把Windows消息导出来了,不用高兴,现在只是开胃菜而已。

 

2.对中文输入的预处理。

       说到输入,就要有输入框,有光标。这些东西要到哪里搞呢。看看手上的资源TextBoxGuiElement类就是一个简单的输入框,不过也真够简单,只能支持英文不说,还没有光标,要做到一个类似我们在窗口中的输入框,还有很远的路要走。

       我们要实现自己的TextInputGuiElement类,让其可以实现中文输入。这个类是一个TextBoxGuiElement类的子类,继承了TextBoxGuiElement类的接口,但是我们要进行大规模的手术才可以为我所用。(相关源文件清参考OgreTextInputGuiElement.h、OgreTextInputGuiElement.cpp)

       首先是加入对全角字的预处理,因为如果进行中文输入的话,我们需要判定,现在所指的字节是半角字还是全角字,这样我们删除的时候才可以知道是删除一个字节还是两个字节。在这里我使用了一个

std::vector < bool > mCheck;
结构来辅助储存信息,

   bool b = true;
   for(i = text.begin();i != iend;++i) //检查整个字符串
   {
            mCheck.push_back(b);
            if((unsigned char)*i >= CHINESE_FIRST) //如果是全角字
         {
                   ++i;
              if(i == iend)
                     break;
                    mCheck.push_back(b); //下一位的bool和本位相等
           }
         b=!b;
}
上面text是String类型字符串,这种运行结果产生了一个判断全/半角的辅助结构,

比如“ab中国c人”这个字符串生成的辅助结构就是“true,false,true,true,false,false,ture,false,false”,当我们发现连续两个相等的布尔值时候,相应位置上的字符就是全角,否则就是半角。

       然后我们来加入光标。

       加入光标分两个步骤,第一是如何得到目前光标的位置,第二是如何显示光标。这些在WinAPI中轻而易举的事情要让我们从头做起了。

       得到光标位置,我们就需要在渲染时候分析全/半角字,并储存渲染的位置,然后我们要实现这样两个查询函数,第一个是通过第几个字得到这个字渲染的位置,另外一个函数是通过坐标(鼠标点选)得到这个字的位置和这个字是字符串中的几个字。这些艰巨的任务我们交给TextAreaGuiElement类来完成。

       我们首先加入这个数据

typedef std::vector<Rectangle> RectList;
RectList mRectList;
 
来保存相应文字的坐标,Rectangle结构是OGRE自身定义的有四个Real(float)数据定义的矩形。

       然后又回到修改

void TextAreaGuiElement::updateGeometry()
这个函数上了,我们主要是在渲染字的时候把其坐标储存到Rectagle数据中,具体的代码请参看OgreTextAreaGuiElement.cpp文件中

RectList::iterator iR,iendR;
相关的部分。

 

       然后是增加的两个函数。

       第一个是从字数中得到坐标。

//get the rect form the num
00136                 inline void getRect(unsigned int& num,Rectangle& rect)
00137                 {
00138                        
00139             if(mRectList.empty())//如果没有字
00140                         {
00141                                 num=0;
00142
00143                                 float left = _getDerivedLeft() * 2.0 - 1.0;//得到坐标
00144                                 float top = -( (_getDerivedTop() * 2.0 ) - 1.0 );
00145
00146                                 rect.left = left;
00147                                 rect.top = top;
00148                                 rect.bottom = top-mCharHeight * 2.0;
00149                                 rect.right = left;
00150                                
00151                                 return;
00152                         }
00153
00154                         RectList::iterator iR ,iendR;
00155                        
00156                         //如果取一个新字符位置
00157                         if(num >= mCaption.size())
00158                         {
00159                                 iendR = mRectList.end();
00160                                 --iendR;//取最后一个矩形
00161                                 num = mCaption.size();
00162
00163                                 rect.left = iendR->right;
00164                                 rect.top = iendR->top;
00165                                 rect.bottom = iendR->bottom;
00166                                 rect.right = rect.left;
00167
00168                                 return;
00169                     }
00170                        
00171                         iR = mRectList.begin();
00172                         iendR = mRectList.end();
00173                         //寻找字符位置
00174                        
00175                         for(unsigned int i = 0;i < num; ++i )
00176                         {
00177                                
00178                                 if(unsigned char(mCaption.at(i)) >= CHINESE_FIRST) //如果是汉字
00179                                         ++i;
00180                                 ++iR;
00181                         }
00182
00183                        
00184                         if(i != num)//当目前字符为汉字的第二个字节时候产生的情况;
00185                                 num = i;
00186
00187
00188                        
00189             //当目前字符为汉字的第二个字节时
00190                         //并且为最后一个字符候产生的情况;
00191                         if(iR >= iendR)
00192                         {
00193                                 --iendR;//取最后一个矩形
00194                                 num = mCaption.size();
00195
00196                                 rect.left = iendR->right;
00197                                 rect.top = iendR->top;
00198                                 rect.bottom = iendR->bottom;
00199                                 rect.right = rect.left;
00200
00201                                 return;
00202                         }
00203
00204                        
00205                        
00206
00207                         rect= *iR;
00208
00209             return;
00210                 }
 

       另外一个是通过坐标(鼠标点选)得到这个字的位置和这个字是字符串中的几个字。

                 inline unsigned int getNum(Real x, Real y ,Rectangle& rect)
00217                 {
00218                     x = x * 2.0 - 1.0;
00219                         y = - ( ( y * 2.0 ) - 1.0);
00220                         if(mRectList.empty())//如果没有字
00221                         {
00222
00223                                 float left = _getDerivedLeft() * 2.0 - 1.0;//得到坐标
00224                                 float top = -( (_getDerivedTop() * 2.0 ) - 1.0 );
00225
00226                                 rect.left = left;
00227                                 rect.top = top;
00228                                 rect.bottom = top-mCharHeight * 2.0;
00229                                 rect.right = left;
00230
00231                                 return 0;
00232                         }
00233
00234                         RectList::iterator iR ,iendR;
00235
00236                         iR = mRectList.begin();
00237                         iendR = mRectList.end();
00238            
00239                        
00240                         unsigned int i=0;
00241                        
00242                         for(;iR != iendR; ++iR,++i)
00243                         {
00244                                 if(i >= mCaption.size())//未知错误
00245                                         i = mCaption.size() - 1;
00246                        
00247                        
00248                                 if(x >= iR->left && x <= iR->right && y >= iR->bottom && y <= iR->top)
00249                                 {
00250                                
00251                                         rect=*iR;
00252                                         return i;
00253                                 }
00254                
00255                                 if(unsigned char(mCaption.at(i)) >= CHINESE_FIRST) //如果是汉字
00256                                         ++i;
00257
00258                         }
00259                         --iendR;//最后一个矩形
00260                         rect.left = iendR->right;
00261                         rect.top = iendR->top;
00262                         rect.bottom = iendR->bottom;
00263                         rect.right = rect.left;
00264
00265                         return mCaption.size();
00266
00267                 }
 

       这样我们便有了光标的位置,但是我们更需要光标。

 

       光标的显示。

       因为我们构造的类是TextBoxGuiElement 的子类,而TextBoxGuiElement 又是GuiContainer的子类,所以我们可以向我们构造的类中添加Element元素,具体概念请参考Ogre源码和相关教程,我们这里只是按照Ogre的风格向我们的类中添加一个2D元素。

void
 setCursorPanel (const String& templateName, int size)
 

 

String
 getCursorPanelName () const
 
void
 initCursorPanel ()
 
void
 updateCursor ()
 

 

GuiContainer*
 mCursorPanel
 

      

String
 mCursorPanelTemplateName
 

 

CmdCursorPanel
 msCmdCursorPanel
 

 

以上就是我们增加的函数和数据成员。CmdCursorPanel这个是我们为实现StringInterface实现的类,也便是为了实现脚本而作的,具体代码请参考OgreTextInputGuiElement.h和OgreTextInputGuiElement.cpp两个文件。

为了支持脚本系统,我们重载

void TextInputGuiElement::addBaseParameters(void)
并添加

dict->addParameter(ParameterDef("cursor_panel",
             "The template name of the panel is the cursor in text."
              , PT_STRING),
              &msCmdCursorPanel);
这样我们就可以在脚本中定义光标的样式了。

 

然后是控制光标和相关操作。

 

鼠标点选,当我们鼠标点到某个文字上面时候,光标应该以东道这个文字上,并且者时候这个输入框处于激活状态,者时候可以进行键盘操作。

在事件处理函数

  void TextInputGuiElement::processEvent(InputEvent* e)
中增加

case KeyEvent::KE_KEY_FOCUSOUT: // 失去焦点
         if (mCursorPanel)
                 mCursorPanel->hide();//隐藏光标
         break;
 case KeyEvent::KE_KEY_FOCUSIN: //得到焦点
         if (mCursorPanel)
         {
                 initCursorPanel();//初始化光标
                 mCursorPanel->show();//显示光标
         }
         break;
case MouseEvent::ME_MOUSE_PRESSED: //鼠标点击事件
         //通过鼠标位置得到 当前字数和当前光标位置
         mNum = mTextArea->getNum(static_cast<MouseEvent*> (e)->getX(),static_cast<MouseEvent*> (e)->getY(),mRect);
          setCaptionToTextArea();//更新
         break;
 
键盘操纵光标

我们来给我们的输入框增加一些键盘操作,比如删除,橡皮(向后删除),左移动,右移动,home,end这些功能。

00688         bool TextInputGuiElement::key(int key)
00689         {
00690                 switch(key)
00691                 {
00692                 case KC_LEFT: //左移动
00693                        
00694                     if(mNum)
00695                         if(mNum >= 2) //看看是否有空间移动全角
00696                         {       //判断是否全角
00697                                 if(mCheck.at(mNum-1)^mCheck.at(mNum-2))
00698                                         mNum -= 1;
00699                                 else
00700                             mNum -= 2; //全角移动两个字节
00701                         }
00702                         else
00703                                  mNum -= 1; //半角一个
00704                         setCaptionToTextArea();//更新
00705                         return true;
00706                        
00707                 case KC_RIGHT: //右移动
00708                                                
00709                         if(mNum < mCaption.size())
00710                                 if(mNum+2 <= mCaption.size())
00711                                 {
00712                                         if(mCheck.at(mNum)^mCheck.at(mNum+1))
00713                                          mNum += 1;
00714                                         else
00715                                  mNum += 2;
00716                                 }
00717                                 else
00718                                          mNum += 1;
00719                         setCaptionToTextArea();
00720                         return true;
00721                                
00722                 case KC_DELETE: //删除
00723                                                
00724                         if(mNum < mCaption.size())
00725                                 if(mNum+2 <= mCaption.size())
00726                                 {
00727                                         if(mCheck.at(mNum)^mCheck.at(mNum+1))
00728                                                 mCaption.erase(mNum,1);
00729                                         else
00730                                                 mCaption.erase(mNum,2);
00731                                 }
00732                                 else
00733                                         mCaption.erase(mNum,1);
00734                         setCaptionToTextArea();
00735                         return true;
00736
00737                 case KC_HOME: //移动到头
00738                         mNum = 0;
00739                         setCaptionToTextArea();
00740                         return true;
00741
00742                 case KC_END: //移动到尾
00743                         mNum = -1; //因为mNum是非负,这里付给他一个最大值
00744                         setCaptionToTextArea();
00745                         return true;
00746
00747         case KC_BACK : //橡皮,向后删除
00748                         if(mNum)
00749                         if(mNum >= 2)
00750                         {
00751                                 if(mCheck.at(mNum-1)^mCheck.at(mNum-2))
00752                             {
00753                                         mNum -= 1;
00754                                          mCaption.erase(mNum,1);
00755                                 }
00756                                 else
00757                                 {
00758                             mNum -= 2;
00759                                         mCaption.erase(mNum,2);
00760                                 }
00761                         }
00762                         else
00763                         {
00764                                 mNum -= 1;
00765                                 mCaption.erase(mNum,1);
00766                         }
00767                         setCaptionToTextArea();
00768                         return true;
00769
00770         case KC_RETURN :
00771                        
00772              if (mActionOnReturn)
00773              {
00774                   ActionEvent* ae = new ActionEvent(this, ActionEvent::AE_ACTION_PERFORMED, 0, 0, mName);
00775                           processActionEvent(ae);
00776                           delete ae;
00777              }
00778                         return true;
00779                                        
00780                 }
00781                 return false;
00782        
00783         }
 

 

这样我们也就可以调用键盘处理了,具体使用请参考相关文档和源文件OgreTextInputGuiElement.cpp。

 

3.中文输入

写了好久,现在才进入关键领域。

      

《一个中文输入法的类》,这是一篇在GameRes.com找到的教程,参照这个教程中的例子实现了Cime类,主要是把C风格字符串转换成String这样的工作之类。

具体实现请参照《一个中文输入法的类》这个教程和本文提供的源码中的Cime类,参看OgreTextInputGuiElement.h和OgreTextInputGuiElement.cpp。

 

然后我们让我们实现的TextInputGuiElement类分别继承RenderWindowListener和FrameListener。并注册监听windows消息和每一画面更新的消息。

 

下面是处理Windows消息的代码。

00503         bool TextInputGuiElement::RWUpdate(const RenderWindowEvent& evt)
00504         {
00505               
00506                 if(!mCursorPanel->isVisible())
00507                         {
00508                                 if(evt.uMsg==IMN_CHANGECANDIDATE||evt.uMsg==WM_IME_COMPOSITION)
00509                                         return false;
00510                                 return true;
00511                         }
00512                 switch( evt.uMsg )
00513                 {
00514                 case WM_CHAR:
00515                         if(evt.wParam==8)
00516                                 key(KC_BACK);
00517                         else
00518                         {
00519                                
00520
00521                                 if(mCTemp)
00522                                 {
00523                                         addChar( mCTemp , (char)evt.wParam);
00524                                         mCTemp=0;
00525                                 }
00526                                 else
00527                                 {
00528                                         if((unsigned char)evt.wParam>=(unsigned char)CHINESE_FIRST)
00529                                         {
00530                                                
00531                                                 mCTemp=(char)evt.wParam;
00532                                        
00533                                         }
00534                                         else
00535                                         {
00536                                                
00537                                                 addChar((char)evt.wParam);
00538                                         }
00539
00540                                 }
00541                         }
00542                         break;
00543                 case WM_KEYDOWN:
00544                         switch(evt.wParam)
00545                         {
00546                                        
00547                         case VK_LEFT:
00548                                 key(KC_LEFT);
00549                                 break;
00550                         case VK_RIGHT:
00551                                 key(KC_RIGHT);
00552                                 break;
00553                         case VK_HOME:
00554                                 key(KC_HOME);
00555                                 break;
00556                         case VK_END:
00557                                 key(KC_END);
00558                                 break;
00559                         case VK_DELETE:
00560                                 key(KC_DELETE);
00561                                 break;
00562                         }
00563                 case WM_IME_SETCONTEXT:
00564                         mIme.enableIme();
00565                         LogManager::getSingleton().logMessage("WM_IME_SETCONTEXT" );
00566                         return !mIme.onWM_IME_SETCONTEXT();
00567                
00568                         break;
00569                 case WM_INPUTLANGCHANGEREQUEST:
00570                         LogManager::getSingleton().logMessage("WM_INPUTLANGCHANGEREQUEST" );
00571                         return !mIme.onWM_INPUTLANGCHANGEREQUEST();
00572                         break;
00573                 case WM_INPUTLANGCHANGE:
00574                         LogManager::getSingleton().logMessage("WM_INPUTLANGCHANGE" );
00575                         return !mIme.onWM_INPUTLANGCHANGE( evt.hWnd );
00576                         break;
00577                 case WM_IME_STARTCOMPOSITION:
00578                         LogManager::getSingleton().logMessage("WM_IME_STARTCOMPOSITION" );
00579                         return !mIme.onWM_IME_STARTCOMPOSITION();
00580                         break;
00581                 case WM_IME_ENDCOMPOSITION:
00582                         LogManager::getSingleton().logMessage("WM_IME_ENDCOMPOSITION" );
00583                         return !mIme.onWM_IME_ENDCOMPOSITION();
00584                         break;
00585                 case WM_IME_NOTIFY:
00586                         if(mChosePanel)
00587                                 switch(evt.wParam)
00588                                 {
00589                                 case IMN_OPENCANDIDATE:
00590                                         mChosePanel->show();
00591                                         break;
00592                                 case IMN_CLOSECANDIDATE:
00593                                         mChosePanel->hide();
00594                                         break;
00595
00596                                 }
00597                                
00598                         LogManager::getSingleton().logMessage("WM_IME_NOTIFY" );
00599                         return !mIme.onWM_IME_NOTIFY( evt.hWnd, evt.wParam );
00600                         break;
00601                 case WM_IME_COMPOSITION:
00602                         LogManager::getSingleton().logMessage("WM_IME_COMPOSITION" );
00603                         return !mIme.onWM_IME_COMPOSITION( evt.hWnd, evt.lParam );
00604                         break;
00605                 }
00606
00607                 return true;
00608         }
上面主要是用来得到消息并调用Cime得到输入字串和相应组字信息的。

 

 

在每一画面渲染开始时候调用。

00651         bool TextInputGuiElement::frameStarted(const FrameEvent &evt)
00652         {
00653                 String a,b,c,listString;
00654                 char i=0;
00655                 std::vector<String> list;
00656                 c=mIme.getImeName();
00657                 mIme.getImeInput(&b,&a,NULL,&list);
00658                 c+=a+b;
00659                 std::vector<String>::iterator it;
00660                 for(it=list.begin();it!=list.end();++it)
00661                 {      
00662                         i++;
00663                         i=i%10;
00664                         listString+=StringConverter::toString(i)+"."+*it+"/n";
00665                        
00666                 }
00667
00668                 if(!listString.empty())
00669                 {
00670                         if(mChoseArea)
00671                                 mChoseArea->setCaption(listString);
00672                 }
00673                
00674                
00675                 if(mCaption!=c)
00676                 {
00677
00682                         setCaptionToTextArea();
00683                 }
00684                 return true;
00685         }
以上是,用来显示文字和组字信息的,mChoseArea用来显示选择的字的信息的文本区域,增加方式和我们增加光标是一样的,在我写的这个函数里面,省略了显示组字信息和数书法名称,如果您的程序需要这些信息进行显示的话,请进行相应的改动。

4.脚本

因为我们的输入框是一个hud元素,也便可以通过通用的脚本来定义输入框的样式,在这里给大家一个简单的例子。

// Ogre overlay scripts

#include <BasicOgreGuiTemplates.inc>

//光标

template container Panel(SS/Templates/CursorPanel)

{

    left 0

    top 0

    width 0.01

    height 0.01

    material Core/Edit

   

}

//文本区域

template element TextArea(SS/Templates/BasicSmallText)

{

    font_name StarWars

    char_height 0.04

    colour_top 1 1 0

    colour_bottom 1 0.2 0.2

    left 0.03

    top 0.02

    width 0.12

    height 0.04

}

 

//选字的背景

template container BorderPanel(SS/Templates/ChosePanel) : SS/Templates/BasicBorderPanel

        {

            left 0.1

            top 0.1

            width 0.6

            height 0.8

            material Core/StatsBlockCenter

           

 

 

   

 

 

        }

//选字区域

template element TextArea(SS/Templates/ChoseArea)

            {

                font_name StarWars

                char_height 0.04

                colour_top 1 1 0

                colour_bottom 1 0.2 0.2

                left 0.03

                top 0.02

                width 0.12

                height 0.04

               

            }

 

SS/Setup/HostScreen/Overlay

{

    zorder 490

    //这里的就是我们的输入框的定义

        container TextInput(SS/Setup/HostScreen/TextInput)

        {

        left 0.02

        top 0.5

        width 1.0

       height 0.05

        text_area SS/Templates/BasicSmallText test

        back_panel SS/Templates/BasicSmallBorderPanel

        cursor_panel SS/Templates/CursorPanel

                chose_panel  SS/Templates/ChosePanel

                chose_area SS/Templates/ChoseArea ss

        left 0.1

        caption 请在这里输入

        }

       

}

 

5.问题

       在DX9窗口模式下切换过输入法,在退出是后会有非法退出,这样的话只能在Dx9全屏和OpenGL全屏/窗口中实现中文输入了。还有就是在使用这个的时候记得要键盘即时输入才可以,当键盘位缓冲时候,无法得到窗口消息。

       原Ogre的GUI的例子已经被我篡改为中文输入的例子了。

 

6.结语

以上便是中文输入相关的主要部分,如果希望具体研究的请参看源码,虽然跌跌撞撞的OGRE中文支持三部曲基本上到此为止了,但是还是又改进的余地,比如unicode码的支持,还有本文是在ogre-win32-v0-14-0,最新版本应该是ogre-win32-v0-14-1,还有要更新的工作啊。

 

7.源码导航

被改写的文件

OgreFont.h

OgreFont.cpp

OgreRenderWindow.h

OgreRenderWindow.cpp

OgreTextAreaGuiElement.h

OgreTextAreaGuiElement.cpp

OgreD3D9RenderWindow.cpp

OgreD3D7RenderWindow..cpp

OgreWin32Window.cpp

OgreFontManager.cpp

新增加的文件

unicodemap.h

OgreTextInputGuiElement.h

OgreTextInputGuiElement.cpp

OgreRenderWindowListener.h

 

 

作者:免费打工仔

From:GameRes  http://www.GameRes.com

 

资源档案下载:http://resource.gameres.com/OgreSupportCHN3.rar

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值