Urho3D 1.7.1 源代码分析(四)

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加载texture,作为Sprite的图像。
  • 创建一行文字”Hello World from Urho3D!”,这是一个Text组件。调用ResourceCache::GetResource加载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 源代码分析 (五)

你可能感兴趣的:(Urho3D 1.7.1 源代码分析(四))