1. UI子系统
1.1 UI组件
UIElement是与用户交互的组件。它的成员children_指向一组子组件,parent_指向它的父组件,这样就可以将UIElement实例组成一棵树。
UI就是这棵树的容器,它的成员rootElement_是树的根。
UIElement的成员position_和size_分别指出它的位置和大小。
UIElment()有两组虚拟函数。
- 一是从UIElement收集用于OpenGL绘制行为的参数,就是UIElement()::GetBatches()。
- 二是设备输入的处理,包括UIElement::OnKey()、UIElement::OnDragBegine()、UIElement::OnDragEnd()等。
UIElement的派生类必须实现第一组,根据功能的需要实现第二组。比如Sprite只是显示图片,无须实现第二组,Button是响应用户按键,需要实现第二组。
在UI的构造函数中注册所有的UI组件,这样以后可以通过Context::CreateObject创建它们。
- 调用一组RegisterObject()注册从UIElement派生的所有UI组件。
- 调用SubscribeToEvent(),向Context订阅UI需要的各种事件。
1.2 创建UI组件
以Urho3D的例子HelloWorld为例。
在HelloWorld::Start()中,
- 创建显示在屏幕右下角的Urho3D的Logo图像,这是一个Sprite组件。将它加入UI Element树。调用ResourceCache::GetResorce<Texture2D>加载texture,作为Sprite的图像。
- 创建一行文字”Hello World from Urho3D!”,这是一个Text组件。调用ResourceCache::GetResource<Font>加载font,作为Text的字体。最后将它加入UI Element树。
1.3 从UI组件收集绘制数据
UIBatch保存绘制数据。它的成员vertexData_保存顶点数据。调用UIElement的虚拟函数GetBatches()得到它的数据,以便后面绘制。
UI的成员rootElement_是UIElement树的根。
UI::GetBatches()遍历所有的UIElement收集数据,包括顶点数据、texture等。收集的数据保存在UI的成员batches_中,这是一个UIBatch实例的数组。UIBatch的成员vertexData_实际上引用UI::vertexData_,所以顶点数据就累积在UI::vertexData_中。
这里Sprite::GetBatches()为例。
- 用来自Sprite的数据(texture)创建UIBatch实例。
- 调用UIBatch::AddQuad()向UI::vertexData_中添加新的顶点数据,AddQuad()的参数是UIElement的位置和大小。
在UI::RenderUpdate()中调用UI::GetBatches(),进而调用Sprite::GetBatches()。
1.4 绘制UI组件
前面说过,收集到的顶点数据保存在UI::vertexData_中。UI以这些数据作为参数,调用Graphics的函数进行绘制。
UI::Render()负责绘制。有两层UI::Render()。
顶层UI::Render()的工作是:
- 调用SetVertexData()。这里调用VertexBuffer::SetSize()和VertexBuffer::SetData(),用成员vertexData_设置成员vertexBuffer_。
- 使用成员vertexBuffer_,及包括texture的成员batches_,调用下一层UI::Render()。
下层UI::Render()负责绘制。
- 调用Graphics::SetVertexBuffer(),用传递进来的vertexBuffer_参数设置Graphics的成员vertexBuffers_。vertexBuffers_是个VertexBuffer数组,而传进来的vertexBuffer_只有一个vertexBuffer实例,所以vertexBuffers_设置成只有一个元素的数组。
接下来遍历batches,调用Graphics::Draw() 进行绘制。对每一个UIBatch实例做如下工作。
- 调用Graphics::GetShader()查询ShaderVariation。如果还没有对应的实例,则创建它。
- 调用Graphics::SetShaders()。 根据vertex shader和pixel shader的ShaderVariation的组合,查询ShaderProgram。如果对应的ShaderProgram还没有创建,则创建它。
- 调用Graphics::SetShaderParameter()设置绘制的参数,如模型变换矩阵和投影变换矩阵等。
- 调用Graphics::SetTexture()绑定texture。
- 调用Graphics::Draw()绘制。
Graphics::Draw()的工作如下:
-
调用PrepareDraw()。遍历vertexbuffers_中的所有vertexBuffer_(这里实际上只有1个元素),并遍历vertexBufer_中的所有VertexElement。VertexElement保存了每种类型数据的大小和在VertexBuffer中的偏移。根据它们把VertexBuffer中的数据分别绑定到相应的vertex attribute上去。
- 调用glEnabeVertexAttribArray()使能顶点数组模式,这里需要指定相应vertex attribute的位置。位置保存在ShaderProgram的成员vertexAttributes_中。
- 调用SetVBO(),后者调用glBindBuffer()绑定VertexBuffer。
- 调用glVertexAttribPointer()指定该类型的数据在VertexBuffer的位置。
-
最后调用glDrawArrays()绘制图元。
1.5 UI组件与输入设备交互
1.5.1 Input子系统与SDL库
Input子系统依赖第三方库SDL,从设备抓取输入事件,转换成Input自己定义的事件并送出。其他子系统,如UI,接收Input事件。
Urho3在循环中调用Engine::RunFrame(),后者调用Engine::Render()进行渲染。
在每次调用Render()之前调用Time::BeginFrame(),后者发送E_BEGIN_BEGINFRAME事件,Input在Input::HandleBeginFrame()中处理该事件,调用Input::Update()。
Input::Update()在循环中调用SDL_PollEvent()得到所有SDL事件,调用HandleSDLEvent()处理。根据不同的SDL事件,发送相应的Input自己定义的事件。
1.5.2 UI子系统处理Input事件
这里以E_MOUSEBUTTONDOWN事件的处理为例。
在UI::HandleMouseButtonDown()中,
- 调用GetCursorAndVisible()得到光标的当前位置,和可见性。
- 调用ProcessClickBegin()。
- 调用GetElementAt(),根据光标位置找到对应的UIElement实例。如果该位置上有对应的UIElement实例,
- 调用SetFocusElement()将它设置为焦点组件。
- 调用UIElement::BeginToFront()将它放到布局最前的位置。
- 调用UIElement::OnClickBegin(),这样UIElement的派生类可以定制自己的行为,比如CheckBox这时会改变自己的选中状态。
- 发送E_UIMOUSECLICK事件,参数是这个UIElement实例,和鼠标按键信息。这样Urho3D库的使用者,就能处理该事件。
相关链接
Urho3D 1.7.1 源代码分析 (一)
Urho3D 1.7.1 源代码分析 (二)
Urho3D 1.7.1 源代码分析 (三)
Urho3D 1.7.1 源代码分析 (四)
Urho3D 1.7.1 源代码分析 (五)
网友评论