简单的 OpenGL GUI 框架用户交互建议?

我正在从头开始设计一个简单的 GUI 框架作为一个项目,使用 OpenGL 而没有其他任何外部设备,并且需要一些关于如何实现用户交互的建议.

基本上,我有一个基类 GUIItem,所有元素都从该基类继承.这为每个项目提供了一些基本变量,例如位置、包含子元素的向量以及一些用于鼠标移动和单击的基本函数.

所有元素都如上设置,以及它们的相关成员变量.

我正在努力解决的是如何正确实现用户交互.在我的窗口管理器中,我将创建一个项目的新实例,例如 GUIButton 并将其命名为 button1.窗口管理器将在单击发生时遍历其元素列表和它们可能具有的任何子元素,根据其坐标、高度和宽度计算对象周围的矩形区域,然后运行与表示项目,比如改变 textlabel1 的值.

首先,有没有更好的方法来做这个计算?它适用于矩形元素,但球形物体和其他物体会有更大的错误区域,可以点击.理想情况下,我会检查像素,但我不知道如何实现.我听说过但从未使用过 GLUT(我的项目只允许使用它来处理鼠标/键盘交互).在这种情况下,GLUT 是否提供任何帮助?

我的主要问题是处理实际发生点击"事件时会发生的情况.例如,目前 GUIButton 内置了一个点击时"功能,据我所知,我必须做一些事情,比如让它成为一个虚拟功能,这意味着每个新的我创建的按钮必须有自己的类才能覆盖单击时"功能,并且按钮的每个实例都是一个唯一类的实例,该类只是从 GUIButton 继承而来.这对我来说似乎很乱,因为我不知道将所有这些类存储在哪里,而且似乎有很多额外的代码.我会创建一个 button1.cpp 和 button1.h 文件吗?

如果我是 C++、OpenGL 的新手,我真的很欢迎任何关于这方面的建议,这是我第一次接触 GUI 编程,当现有的 GUI 框架是通常的选择时,没有太多可做的事情.

解决方案

如果你想要简单而快速的东西,那么你可以:

  1. 创建包含 ID/index/pointer 而不是颜色的阴影屏幕缓冲区

  2. 预渲染此缓冲区

    只需将每个可视组件渲染到它,而不是着色/纹理只需填写渲染组件的 ID/index/pointer.不要忘记先用一些 NULL 清除它......在这之后你应该有你的组件的掩码.您只需执行一次...

  3. 鼠标事件

    您只需将鼠标坐标转换为阴影屏幕空间并选择值.如果它是 NULL 那么你点击了空白区域或任何东西.如果它包含 ID,则更新或调用组件 ID 的回调.如果您有所有组件的列表,则 ID 可以是列表索引,否则使用其实际指针或以样式 (component_type, component_index) 进行编码.如您所见,无论您有多少组件,这都是非常快速的 O(1) 项目选择...阴影屏幕可以具有与实际屏幕不同的分辨率(以节省内存).

无论组件的形状如何,它都具有像素完美的鼠标选择精度,无需嵌套组件搜索循环.

[备注]

当我这样做时,这里有一些提示:

创建一个window class,其中包含单屏组件的配置.程序通常有更多的屏幕,其中包含不同的组件集,并且因为您切换页面/屏幕很糟糕而一遍又一遍地动态地执行屏幕.

使用单独的组件列表,每个组件类型一个列表.

为您的窗口创建 IDE 编辑器,请参阅

该窗口直接从 IDE 保存为 C++ 代码,我可以将其复制到我的应用程序中.这是上面没有旋钮的例子(忘记保存了):

//---------------------------------------------------------------------------//OpenGL VCL 窗口请求:winwin.grid.allocate(0);win.grid.num=0;win.scale.allocate(0);win.scale.num=0;win.button.allocate(0);win.button.num=0;win.knob.allocate(0);win.knob.num=0;win.scrollbar.allocate(3);win.scrollbar.num=3;win.scrollbar[0].x0=200.0;win.scrollbar[0].y0=19.0;win.scrollbar[0].xs=256.0;win.scrollbar[0].ys=16.0;win.scrollbar[0].fxs=8.0;win.scrollbar[0].fys=19.0;win.scrollbar[0].name=_vcl_scrollbar0";win.scrollbar[0].hint=";win.scrollbar[0].min=0.000;win.scrollbar[0].max=1.000;win.scrollbar[0].pos=0.000;win.scrollbar[0].dpos=0.100;win.scrollbar[0].horizo??ntal=1;win.scrollbar[0].style=0;win.scrollbar[0].resize();win.scrollbar[1].x0=200.0;win.scrollbar[1].y0=45.0;win.scrollbar[1].xs=256.0;win.scrollbar[1].ys=16.0;win.scrollbar[1].fxs=8.0;win.scrollbar[1].fys=19.0;win.scrollbar[1].name="_vcl_scrollbar1";win.scrollbar[1].hint=";win.scrollbar[1].min=0.000;win.scrollbar[1].max=1.000;win.scrollbar[1].pos=0.000;win.scrollbar[1].dpos=0.100;win.scrollbar[1].horizo??ntal=1;win.scrollbar[1].style=0;win.scrollbar[1].resize();win.scrollbar[2].x0=200.0;win.scrollbar[2].y0=70.0;win.scrollbar[2].xs=256.0;win.scrollbar[2].ys=16.0;win.scrollbar[2].fxs=8.0;win.scrollbar[2].fys=19.0;win.scrollbar[2].name=_vcl_scrollbar2";win.scrollbar[2].hint=";win.scrollbar[2].min=0.000;win.scrollbar[2].max=1.000;win.scrollbar[2].pos=0.000;win.scrollbar[2].dpos=0.100;win.scrollbar[2].horizo??ntal=1;win.scrollbar[2].style=0;win.scrollbar[2].resize();win.interpbox.allocate(0);win.interpbox.num=0;win.dblist.allocate(0);win.dblist.num=0;//OpenGL VCL 窗口结束:win//--------------------------------------------------------------------------

在此处查看图像在示波器上绘制实时数据以获取一些想法(我得到了这个适用于 GDI 和 OpenGL)

最好使用像素单位而不是 OpenGL <-1,+1> 屏幕单位,以获得更好的视觉质量和编辑舒适度.

I'm designing a simple GUI framework from scratch as a project, using OpenGL and nothing else external and need some advice on how I might implement user interaction.

Basically, I've a base class GUIItem from which all elements inherit. This gives each item some basic variables such as position, a vector to contain child elements as well as some basic functions for mouse movement and clicking.

All elements are setup as above, with their relevant member variables.

What I'm struggling with is how to implement user interaction properly. In my window manager I would create a new instance of an item, say GUIButton and call it button1. The window manager would, upon a click occurring, iterate through its list of elements and any child elements they may have, calculating a rectangular area around the object based on its coordinates, height and width, then running any "on click" function associated with said item, like change the value of textlabel1.

Firstly, is there a better way to do this calculation? It would work for rectangular elements but spherical objects and others would have a much larger erroneous area which could be clicked. Ideally I would check pixels but I've no real idea how that would be achieved. I've heard about but never used GLUT (my project only allows use of this for handling mouse/keyboard interaction though). Does GLUT provide anything to assist in this case?

My main issue is with handling what would occur when an "On click" event actually occurred. At the moment GUIButton for example, has an "On click" function built in, so as far as I can see, I'd have to do something like make it a virtual function, meaning that each new button I created would have to have its own class just to overwrite the "on click" function and each instance of a button would be an instance of a unique class that simply inherited off of GUIButton. This seems messy to me, as I've no idea where I would store all those classes, and it seems a lot of extra code. Would I be creating a button1.cpp and button1.h file?

Any advice on this really would be welcome as I'm new to C++, OpenGL and it's the first time I've been exposed to GUI programming and there's not a lot to go on when an existing GUI framework is the usual choice.

解决方案

if you want something stupidly simple and fast then you could:

  1. create shadow screen buffer containing ID/index/pointer instead of color

  2. pre-render this buffer

    Just render each of your visual component to it but instead coloring/texturing just fill in the ID/index/pointer of rendered component. Do not forget to clear this with some NULL first ... After this you should have mask of your components. You need to do this just once ...

  3. On mouse events

    you simply convert mouse coordinates to the shadow screen space and pick the value. If it is NULL then you clicked or whatever on empty area. If it contains ID instead update or call the callbacks for component ID. if you have a list of all components then ID can be the list index, otherwise use its actual pointer or encode in style (component_type, component_index). As you can see this is pretty fast O(1) item selection no matter how many components you have ... The shadow screen can have different resolution then your actual screen (to preserve memory).

This have pixel perfect mouse selection accuracy no matter the shape of your components without the need for nested component search loops.

[Notes]

As I did this stuff here are some hints:

create a window class containing configuration of your components for single screen. Programs have usually more screens with different set of components and doing dynamically the screens over and over again just because you switch page/screen sucks.

use separate list of components one list per component type.

create IDE editor for your windows see drag & drop example in C++ it might get handy for this. Add get,set functions controlled by string/enum or flag to easy obtain/change properties to make Object Inspector possible. Also this is how mine IDE looks like:

The window is saved from IDE directly as C++ code I can just copy to my App. This is the above example without the knob (forgot to save it):

//---------------------------------------------------------------------------
// OpenGL VCL window beg: win
    win.grid.allocate(0);
    win.grid.num=0;
    win.scale.allocate(0);
    win.scale.num=0;
    win.button.allocate(0);
    win.button.num=0;
    win.knob.allocate(0);
    win.knob.num=0;
    win.scrollbar.allocate(3);
    win.scrollbar.num=3;
    win.scrollbar[0].x0=200.0;
    win.scrollbar[0].y0=19.0;
    win.scrollbar[0].xs=256.0;
    win.scrollbar[0].ys=16.0;
    win.scrollbar[0].fxs=8.0;
    win.scrollbar[0].fys=19.0;
    win.scrollbar[0].name="_vcl_scrollbar0";
    win.scrollbar[0].hint="";
    win.scrollbar[0].min=0.000;
    win.scrollbar[0].max=1.000;
    win.scrollbar[0].pos=0.000;
    win.scrollbar[0].dpos=0.100;
    win.scrollbar[0].horizontal=1;
    win.scrollbar[0].style=0;
    win.scrollbar[0].resize();
    win.scrollbar[1].x0=200.0;
    win.scrollbar[1].y0=45.0;
    win.scrollbar[1].xs=256.0;
    win.scrollbar[1].ys=16.0;
    win.scrollbar[1].fxs=8.0;
    win.scrollbar[1].fys=19.0;
    win.scrollbar[1].name="_vcl_scrollbar1";
    win.scrollbar[1].hint="";
    win.scrollbar[1].min=0.000;
    win.scrollbar[1].max=1.000;
    win.scrollbar[1].pos=0.000;
    win.scrollbar[1].dpos=0.100;
    win.scrollbar[1].horizontal=1;
    win.scrollbar[1].style=0;
    win.scrollbar[1].resize();
    win.scrollbar[2].x0=200.0;
    win.scrollbar[2].y0=70.0;
    win.scrollbar[2].xs=256.0;
    win.scrollbar[2].ys=16.0;
    win.scrollbar[2].fxs=8.0;
    win.scrollbar[2].fys=19.0;
    win.scrollbar[2].name="_vcl_scrollbar2";
    win.scrollbar[2].hint="";
    win.scrollbar[2].min=0.000;
    win.scrollbar[2].max=1.000;
    win.scrollbar[2].pos=0.000;
    win.scrollbar[2].dpos=0.100;
    win.scrollbar[2].horizontal=1;
    win.scrollbar[2].style=0;
    win.scrollbar[2].resize();
    win.interpbox.allocate(0);
    win.interpbox.num=0;
    win.dblist.allocate(0);
    win.dblist.num=0;
// OpenGL VCL window end: win
//---------------------------------------------------------------------------

Look at images here plotting real time Data on Oscillocope for some ideas (I got this working for both GDI and OpenGL)

It is better to use pixel units instead of OpenGL <-1,+1> screen units for better visual quality and editing comfort.

相关文章