美文网首页
C++教程4:NoesisGUI集成教程

C++教程4:NoesisGUI集成教程

作者: YottaYuan | 来源:发表于2020-03-14 05:27 被阅读0次

NoesisGUI集成教程

教程数据

本教程重点介绍将NoesisGUI集成到您自己的应用程序中并渲染接口所必须遵循的步骤。尽管我们提供了一个在所有支持的平台上使用NoesisGUI的开源框架Application Framework ,但本教程将向您展示实现该框架的必要步骤。

注意
SDK中包含IntegrationIntegrationGLUT的完整注释示例。在阅读本指南时,强烈建议您遵循这些样本。

前提

本教程假定您熟悉以下内容:

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的特定详细信息,我们为应用程序框架中的每个平台提供了实现:Win32DisplayAppKitDisplayUIKitDisplayXDisplay等。

鼠标

以下功能可用于指示何时移动鼠标,何时单击鼠标以及何时旋转水平和垂直滚轮。

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);

触摸屏

用于跟踪手指的TouchDownTouchMoveTouchUp可用。可以分别跟踪多个手指,每个手指具有不同的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();

相关文章

网友评论

      本文标题:C++教程4:NoesisGUI集成教程

      本文链接:https://www.haomeiwen.com/subject/jwvlshtx.html