NoesisGUI集成教程
本教程重点介绍将NoesisGUI集成到您自己的应用程序中并渲染接口所必须遵循的步骤。尽管我们提供了一个在所有支持的平台上使用NoesisGUI的开源框架Application Framework ,但本教程将向您展示实现该框架的必要步骤。
注意
SDK中包含Integration和IntegrationGLUT的完整注释示例。在阅读本指南时,强烈建议您遵循这些样本。
前提
本教程假定您熟悉以下内容:
SDK目录
SDK使用下面的根文件夹结构:
- /Bin:这是可以在其中找到Noesis动态库的目录。您的可执行文件必须能够到达此路径。最简单的方法是在构建后将其复制到可执行位置。
- /Include:公共头文件的目录。您必须将此路径添加到项目的“ 附加包含目录-Additional Include Directories”中
- /Lib:与之链接的对象库存储在此目录中。与include目录类似,您必须将此路径添加到附加链接库-Additional Library Directory中。除了将此目录添加到项目之外,还必须链接相应的Noesis库。
- /Build:此目录中包含用于构建所有样本的项目。
- /Data:用于测试应用程序和XamlPlayer的位置。
初始化(Initialization)
在能够呈现任何XAML之前,必须通过调用Noesis::GUI::Init() 并可选地传递错误处理程序,日志处理程序和内存分配器来初始化Noesis。
void LogHandler(const char* filename, uint32_t line, uint32_t level, const char* channel,
const char* message)
{
if (strcmp(channel, "") == 0)
{
// [TRACE] [DEBUG] [INFO] [WARNING] [ERROR]
const char* prefixes[] = { "T", "D", "I", "W", "E" };
const char* prefix = level < NS_COUNTOF(prefixes) ? prefixes[level] : " ";
fprintf(stderr, "[NOESIS/%s] %s\n", prefix, message);
}
}
void main()
{
Noesis::GUI::Init(nullptr, LogHandler, nullptr);
// ...
}
注意
默认错误处理程序仅重定向到日志处理程序。因此,仅设置日志处理程序可能就足够了。如果要控制Noesis分配,则必须通过一个内存分配器。为了简单起见,我们这里没做。
资源提供者(Resource Providers)
初始化后,资源提供(resource provider)程序必须建立以加载应用程序所需的各种资源。例如,可以通过以下方式从当前目录加载资源:
Noesis::GUI::SetXamlProvider(MakePtr<LocalXamlProvider>("."));
Noesis::GUI::SetTextureProvider(MakePtr<LocalTextureProvider>("."));
Noesis::GUI::SetFontProvider(MakePtr<LocalFontProvider>("."));
注意
每次需要资源(xaml,纹理,字体)时,都会调用相应的提供程序以获取内容的流。您必须为每个所需的资源安装加载程序。应用程序框架中有一些实现(例如LocalXamlProvider可从磁盘加载)
视图创建(View creation)
需要一个视图来呈现用户界面并与其进行交互。视图持有一棵元素树。构建接口树的最简单方法是从XAML文件中加载它们。可以使用辅助函数LoadXaml来完成。加载XAML后,您必须使用它创建一个视图并指定其尺寸。每次窗口或曲面尺寸更改时,都必须在视图中指明。
Ptr<FrameworkElement> xaml = Noesis::GUI::LoadXaml<FrameworkElement>("Reflections.xaml");
Ptr<IView> view = Noesis::GUI::CreateView(xaml);
view->SetSize(1024, 768);
创建视图后,必须使用渲染设备初始化其渲染器。尽管我们在Application Framework中提供了几种参考实现,但您应该提供自己的实现。
Ptr<RenderDevice> device = *new GLRenderDevice();
view->GetRenderer()->Init(device);
注意
如果您使用单独的线程进行渲染。最后一步必须在该线程中执行
注册类(Registering classes)
如果要使用新类扩展 Noesis,则必须在Noesis初始化后注册它们。
NsRegisterComponent<Scoreboard::MainWindow>();
NsRegisterComponent<Scoreboard::App>();
NsRegisterComponent<Scoreboard::ThousandConverter>();
NsRegisterComponent<EnumConverter<Scoreboard::Team>>();
NsRegisterComponent<EnumConverter<Scoreboard::Class>>();
挂钩事件(Attaching to events)
要与用户界面进行交互,您需要挂钩事件。如事件教程中所述,有许多方法可以实现此目的。一种简单的方法是将控制事件与本地委托连接起来。只需按名称找到每个所需的控件并连接到委托即可。
Slider* slider = view->GetContent()->FindName<Slider>("Luminance");
slider->ValueChanged() += &LuminanceChanged;
输入管理(Input Management)
每帧一次,您必须从键盘,鼠标,触摸和游戏手柄收集输入事件,并将其发送到每个视图。有关如何将事件从每个窗口子系统转换为Noesis的特定详细信息,我们为应用程序框架中的每个平台提供了实现:Win32Display,AppKitDisplay,UIKitDisplay,XDisplay等。
鼠标
以下功能可用于指示何时移动鼠标,何时单击鼠标以及何时旋转水平和垂直滚轮。
void MouseButtonDown(int x, int y, MouseButton button);
void MouseButtonUp(int x, int y, MouseButton button);
void MouseDoubleClick(int x, int y, MouseButton button);
void MouseMove(int x, int y);
void MouseWheel(int x, int y, int wheelRotation);
void MouseHWheel(int x, int y, int wheelRotation);
键盘
按下键时,必须使用KeyDown()和KeyUp()。要发送已处理的UTF-32字符,请提供Char。
void KeyDown(Key key);
void KeyUp(Key key);
void Char(uint32_t ch);
触摸屏
用于跟踪手指的TouchDown,TouchMove和TouchUp可用。可以分别跟踪多个手指,每个手指具有不同的ID,以支持多点触摸交互。
void TouchDown(int x, int y, uint64_t id);
void TouchMove(int x, int y, uint64_t id);
void TouchUp(int x, int y, uint64_t id);
游戏手柄
在用于发送键盘事件的键枚举中,专门添加了一些虚拟代码来支持硬件游戏手柄按钮:
<colgroup style="box-sizing: inherit;"><col width="34%" style="box-sizing: inherit;"><col width="33%" style="box-sizing: inherit;"><col width="33%" style="box-sizing: inherit;"></colgroup>
Noesis密钥 | Xbox映射 | 等效键 |
---|---|---|
Key_GamepadLeft | D-pad向左 | Key_Left |
Key_GamepadUp | D-垫起来 | Key_Up |
Key_GamepadRight | D-pad右 | Key_Right |
Key_GamepadDown | D-pad向下 | Key_Down |
Key_GamepadAccept | 一个按钮 | Key_Space |
Key_GamepadCancel | B按钮 | 键转义 |
Key_GamepadMenu | 菜单按钮 | |
Key_GamepadView | 查看按钮 | |
Key_GamepadPageUp | 左扳机 | Key_PageUp |
Key_GamepadPageDown | 正确触发 | Key_PageDown |
Key_GamepadPageLeft | 左保险杠 | Key_PageLeft |
Key_GamepadPageRight | 右保险杠 | Key_PageRight |
Key_GamepadContext1 | X键 | |
Key_GamepadContext2 | Y按钮 | |
Key_GamepadContext3 | 左摇杆 | |
Key_GamepadContext4 | 右棒 |
注意
这是WPF的扩展。如果您想保持与Blend项目的兼容性,我们提供了GamepadTrigger类来执行响应于游戏手柄事件的动作。
除此之外,还有两个功能可以向视图发送滚动反馈。您通常将此映射到正确的模拟摇杆。
void Scroll(float value);
void HScroll(float value);
下图是有关如何将Xbox控制器映射到Noesis事件的示例。
更新(Update)
每帧一次,需要使用全局时间来更新视图。此时,将在内部计算布局和动画之类的东西,并准备显示当前状态。
double time = GetGlobalTime();
view->Update(time);
注意
这里的一个常见错误是传递增量时间(delta time)而不是全局时间(global time)
渲染(Render)
更新后,就可以渲染视图了。在将命令发送到GPU之前,您必须做的第一件事就是更新渲染器,以从上次执行的更新中收集命令。
view->GetRenderer()->UpdateRenderTree();
注意
UpdateRenderTree返回自上一次渲染起是否有更改。如果您拥有最后一帧的有效副本,则此布尔值可用于跳过渲染。
更新渲染器后,必须生成屏幕外纹理。此步骤填充当前帧所需的所有内部纹理。从性能的角度来看,在绑定主渲染目标之前应用此步骤至关重要。
view->GetRenderer()->RenderOffscreen();
之后,必须绑定主要渲染目标和视口。请注意,上述渲染屏幕外纹理的步骤会修改GPU状态,因此您需要将其还原。如果您使用HUD(平视显示器)界面,这也是渲染3D场景的合适时机。
注意
由于Render()函数会修改GPU状态,因此您必须将其正确还原为应用程序的正常状态。由于性能原因,它不会自动完成。最直接的解决方案是在调用RenderOffscreen()之前保存设备状态,然后再恢复它。如果您的应用程序实现了一种使所需状态无效的方法,则可以实现更高的性能。这样可以更快,因为可以避免从驱动程序获取当前状态。
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glViewport(0, 0, glutGet(GLUT_WINDOW_WIDTH), glutGet(GLUT_WINDOW_HEIGHT));
glClearColor(0.0f, 0.0f, 0.25f, 0.0f);
glClearStencil(0);
glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
最后,将接口渲染到当前渲染目标中。
view->GetRenderer()->Render();
注意
在NoesisGUI中,使用StencilBuffer来实现用于隐藏部分UI元素的屏蔽。确保使用至少8位绑定模板缓冲区以正确显示蒙版。并且还要确保在执行屏幕UI渲染之前将其清除为零。
结束(Finalization)
在退出应用程序之前,必须关闭每个视图渲染器。如果使用任何线程,则必须从渲染线程完成。此外,您拥有的每个Ptr必须为Reset()。清洁所有物体后,必须通过调用Shutdown()函数正确关闭Noesis 。这将释放所有内部分配的资源。
view->GetRenderer()->Shutdown();
view.Reset();
Noesis::GUI::Shutdown();
网友评论