第一步/教程:如何在 Linux 上使用 Code::Blocks 启动 TGUI(在 SFML 上运行并利用 OpenGL)





5.00/5 (2投票s)
我的方法是运行“Texus 的图形用户界面”(TGUI)的第一个示例程序,这是一个跨平台的现代 C++ 即时 GUI,我使用“简单快速多媒体库”(SFML)作为后端。
结果应该如下图所示(加载一个小 PNG 文件后)
目录
简介
Texus 的图形用户界面 (TGUI) 将自己定义为:一个跨平台的现代 C++ GUI 库。TGUI 专注于 GUI 库的核心任务,并依赖于基于 OpenGL / GLES 的 简单快速多媒体库 (SFML),或基于 OpenGL / DirectX 的 简单直接媒体层 (SDL),或基于 OpenGL / GLES / Vulkan 的 图形库框架 (GLFW) 来渲染小部件并与平台 UI(窗口、指针、键盘、控制器等)进行交互。
因此,TGUI(因为它通过 OpenGL / GLES 渲染)可以归类为即时模式 GUI (IMGUI)。所以,如果你选择 TGUI,你应该了解 IMGUI 的优点和缺点。
下一部分是关于我个人背景的一些内容 - 你可以跳过。我无法解释为什么,但 GUI 一直是我特别感兴趣的。也许是因为我从自己最初对用 K&R C 编写的 Athena Widget Set (Xaw) 的扩展中了解了面向对象编程的真正含义(是的,C 中没有 ++ 的面向对象),后来又了解了与 C 中的面向对象相比,C++ 为开发人员节省了多少手动工作。或者因为我通过 Motif 了解了一个结构良好的 API 是什么,以及“更好是好的敌人”这个道理。或者因为我通过 MS Windows UI 了解到使用 UI 库不必花费数千美元(Motif 后来也因为 LessTif 的压力,通过 OpenMotif 了解了这一点)。或者是因为我后来与 Microsoft 从 Win32 API 走到 Microsoft Foundation Classes (MFC) 和 Windows Template Library (WTL) 到 Windows Forms 的艰难道路,后者最终为 Microsoft Windows UI 提供了一个功能齐全、结构良好且易于学习的 API。毫无疑问,Windows Presentation Foundation (WPF) 是一个很酷的方法 - 但后来被 WinUI 和最近的 MAUI 所取代(被取代,我的意思是 Microsoft 在 WPF 完全流行之前发布了新技术)。
现在我刚读到(2022 年 8 月),微软已经彻底重写了 Windows Forms 库,并且(在其所谓的消亡之后)现在再次为 .NET 5.0 和 .NET 6.0 提供。这促使我在长时间休息后寻找可跨平台用于 Windows 和 Linux 的 GUI。在我最近关于保持 现代 GUI 更新的研究中,我偶然发现了 Texus 的图形用户界面 (TGUI)。由于我除了目前对 C# 的喜爱之外,仍然对 C++ 有着旧爱,所以我并不害怕 TGUI 是用 C++ C14 编写的,并且很高兴看到它也有 C# 绑定。
背景
我选择 SFML 作为我第一个 TGUI 编程实验的后端。我意识到 SFML 目前正在努力支持 OpenGL 的后续技术(例如,Vulkan)。这个话题——受到 Windows(关键词 DirectX)和 OSX(关键词 Metal)上 OpenGL 支持质量下降的推动——确实在 SFML 社区中进行了讨论(此处、此处、此处 和 此处...)。
下一节讲述了为什么本文是关于 TGUI 和 SFML 的故事——你也可以跳过这一部分。由于我的职业兴趣是 Microsoft Windows,而我的个人兴趣是 Linux,所以如果我考虑 跨平台 GUI 工具包,这两个平台对我来说很重要。不幸的是,MAUI 不属于这一类别。此外,几乎所有其他已知的 Microsoft Windows GUI 工具包都无法跨平台用于 Linux。因此,我想从 Linux 方面着手处理这个主题。
我有一些使用 经典 Linux GUI(这里经典指的是在 CPU 中渲染)的经验,例如 X11(如 Athena Widget Set、Motif、GTK2),并且我自己也编写了一个(Roma Widget Set)。随着 OpenGL 和 DirectX 的普遍可用,对 GUI 外观的要求显著提高。我也发现目前 现代 GUI(这里现代指的是在 GPU 中渲染)更令人兴奋。我也迈出了 GPU 渲染的第一步——即 OpenGL(因为 DirectX 永远不会在 Linux 上得到全面支持,所以相关的跨平台图形/多媒体库仅限于 OpenGL、Vulkan、Cairo、SDL2 和 Skia)。
我认为 Vulkan 是最有趣的选择——但由于我经常处理旧硬件,我暂时还不敢使用 Vulkan。
我对 Cairo 和 Pango 非常了解,并非常欣赏它们的可靠性——所以 Cairo 现在并没有引起我特别的好奇心。
SDL2 是一个功能非常丰富的库,也是一个真正的重量级选手。由于我没有计划涉足照片级渲染或光线追踪,所以它对我来说有点太重了。
在互联网上,Skia 比 Cairo 更现代的观点似乎正在流行。我曾尝试过基于 SkiaSharp 的 AvaloniaUI,但 Skia 在 Linux 上的安装复杂性和支持的 IDE 列表让我失去了兴趣。
最后我选择了 Methuselah:OpenGL。但是,尽管我对 OpenGL 的可能性着迷(例如参见文章 使用 OpenGL/OpenTK 在 MONO/.NET 中进行严肃应用程序的入门),但使用 OpenGL 渲染文本却是一场噩梦。即使 GLUT、GLFW 和 OpenTK(例如参见文章 使用 OpenGL/OpenTK 在 MONO/.NET 中进行文本渲染的摘要)也改变不了多少。
TGUI 可以使用 简单快速多媒体库 (SFML)、简单直接媒体层 (SDL) 和 图形库框架 (GLFW) 作为其渲染后端。由于我之前不知道,所以我也对 SFML 非常好奇,它用 C++ 编写,并且也有 C# 绑定。SFML 看起来是非常干净的 C++,似乎值得测试。因此,关于是否存在比 Cairo、SDL2 或 Skia 更好的跨平台图形/多媒体库的问题,我只认为是次要的。
创建先决条件
本教程的最后,我希望在 Linux 上编译并运行一个简单的 TGUI 示例程序。
TGUI 直接支持的 IDE 之一是 Code::Blocks。此外,Code::Blocks 在 MS Windows 和 Linux 上都可用,功能非常丰富,并且经常在“最佳 C/C++ IDE”列表中名列前茅。即使 UI 看起来有点过时,但其界面概念和功能与旧版 Visual Studio 非常相似,您会很快上手。
这就是我决定使用 Code::Blocks 的原因。
我的最爱 - openSUSE 和 Debian
我喜欢开源软件方法,乐于通过我的行动推广它,并且整天都在专业地使用 Microsoft Windows——还有什么比在业余时间用 Linux 放松更自然的事呢?
下一节讲述了我为什么在此处关注 openSUSE 和 Debian 的故事 - 您也可以跳过。曾几何时,Debian 8(当时领先的 Linux 发行版之一,与 RedHat 并驾齐驱)推出并推广了一个 GTK 3 桌面,这让我感觉就像坐在一个超大智能手机前。只不过,我不是用胖手指,而是用高精度鼠标指针作为输入设备 - 它的精度没有被利用。那不适合我。
于是我转向了 SUSE,后来又转向了 openSUSE。我学会了欣赏备受诟病的配置工具 YaST 直观的操作,并长期在 XFCE 上使用 MonoDevelop。
但与此同时,Debian 发生了很大变化:XFCE 作为主要桌面非常容易设置,Code::Blocks 和 MonoDevelop 也很容易安装。因此,Debian 再次成为我的关注焦点。
在 openSUSE 上提供最小环境
我从 openSUSE Leap 15.4 “带 XFCE 的桌面”的全新默认安装开始。
Mesa 21.2.4(用于渲染 3D 图形并利用 OpenGL 命令语法和状态机的系统)已安装。
GNU C++ 11(或更高版本)
Code::Blocks 至少需要 GNU C++ 7,但安装 GNU C++ 11(或更高版本)也没有问题。我安装了以下软件包:
- cpp11 (GCC 预处理器)
- gcc11-c++ (GNU C++ 编译器)
- libX11-devel (核心 X11 协议库的开发文件)
- libXt-devel (X Toolkit Intrinsics 库的开发文件)
我安装了 libX11-devel 和 libXt-devel,以便使用 Code::Blocks 编写一个小型 X11 测试程序,并且它们在以后编译时也会用到。
Code::Blocks
通过“欢迎”对话框(及其“获取软件”按钮),可以调用openSUSE 软件存档。搜索“codeblocks”会显示 openSUSE Leap 15.4 的社区软件包。
不幸的是,一键安装 的所有选项都无法工作(可能是因为一些所需的软件包必须从不受支持的存储库[#]加载),所以我选择了 home:regataos 专家下载。
即使这个安装最终成功,也必须接受一些标准软件包(在我的情况下:10+ 个软件包)的交换。
但幸运的是,所有必需的包都可以安装。安装后,不受支持的存储库[#]可以再次删除……
- https://download.opensuse.org/repositories/SUSE:/SLE-15-SP1:/GA/pool/
- https://download.opensuse.org/repositories/SUSE:/SLE-15-SP2:/GA/pool/
- https://download.opensuse.org/repositories/SUSE:/SLE-15-SP3:/GA/pool/
- https://download.opensuse.org/repositories/SUSE:/SLE-15-SP4:/GA/pool/
与此同时,我的系统安装了更新,并且 GNU C++ 12 变得可用。所以我迅速安装了 C++ 12,然后设置 Code::Blocks。
安装 gcc-c++ 软件包也很重要(它会更新对 gcc12-c++ 的引用)。
现在 Code::Blocks 20.03 终于可以启动了——并且它会自动正确配置 C++。
SFML
YaST 提供 SFML 2.5.1 - 这是目前最新的稳定版本。我安装了这些软件包
libsfml2-2_5
libsfml2-devel
make
openSUSE 默认安装已包含 make 4.2.1-7.3.2 版本。
Cmake
YaST 提供 Cmake 3.20.4。我安装了这些软件包:
cmake 3.20.4
cmake-full 3.20.4
cmake-gui 3.20.4
cmale-man 3.20.4
我已下载当前开发分支 0.10 的源代码并将其解压到 ~/Projects/CodeBlocks/TGUI-0.10-Oct-2022。我发现它足够稳定,可以基于此版本进行测试。有一个很好的使用 cmake 编译 TGUI 的教程。
本教程基于 Windows 上的 Visual Studio 2019,也描述了 Windows 上的 Code::Blocks。以下是 openSUSE 上 Code::Blocks 的详细信息...
- 环境应如下配置:
- 第一次 [配置] 运行后(注意: Cmake 表区域中的红色行是新行,而不是错误行。Cmake 日志输出区域中的红色行不一定是错误,通常新的 [配置] 运行可以解决问题。),我将
CMAKE_BUILD_TYPE
更改为“Debug
”——但这纯粹是我的个人偏好。TGUI_BACKEND
的默认值SFML_GRAPHICS
是一个非常合适的选择,它具有以下优点:- 优点:它使用 OpenGL 作为渲染后端(就像
SFML_OPENGL3
)。 - 优点:它只需要 OpenGL 1.1 版本,而不是 OpenGL 3.3 版本(像
SFML_OPENGL3
)。 - 优点:它提供更多(便利)功能(与
SFML_OPENGL3
相比)。 - 优点:对于大量使用 OpenGL 的情况,也可以请求更高的 OpenGL 版本。
- 缺点:多窗口/多线程应用程序的实现略显复杂(与
SFML_OPENGL3
相比)。
- 优点:它使用 OpenGL 作为渲染后端(就像
- 第二次 [Configure] 运行后,我修复了这些错误
X11_xcb_icccm_INCLUDE_PATH-NOTFOUND
可以通过以下方式修复:/usr/include/xcb-
X11_xcb_icccm_LIB-NOTFOUND
可以通过以下方式修复:/usr/lib64 -
X11_xcb_util_INCLUDE_PATH-NOTFOUND
可以通过以下方式修复:/usr/include/xcb -
X11_xcb_util_LIB-NOTFOUND
可以通过以下方式修复:/usr/lib64
为什么(更新 1):根据配置的渲染后端(对于 SDL 或 SFML < 2.6,但对于 GLFW 或 SFML >= 2.6 则不需要),TGUI 需要一些 X11 调用才能在调整子窗口大小时获得正确的鼠标光标,并且 X11 由 find_package(X11)
调用识别。但是,如果当前渲染后端不需要此调用,则很难或不可能抑制此调用。因此,这些错误也可以忽略。
-
我还将
TGUI_CXX_STANDARD
从 17 切换到 14 -
第三次 [Configure] 运行没有任何错误,[Generate] 可以开始。
在 Code::Blocks 项目文件和 make 文件生成后,子文件夹 /build 包含所有中间结果以及 Code::Blocks 项目文件 TGUI.cbp 和 make 文件。库的创建以两种方式(通过 Code::Blocks GUI 和通过 make 命令行调用)都完美无缺。
库生成后,可以再次启动 cmake-gui
,更改配置(例如,“Release
”而不是“Debug
”),然后重复 [Configure]、[Generate] 和编译。结果可能如下所示
最后的准备步骤是将库复制到 /usr/lib,并将 include 文件夹 TGUI 复制到 /usr/include。
在 Debian 上提供最小环境
我本想从全新安装的 Debian 11 开始,但是...
第一个陷阱
从 openSUSE 转过来,我没有意识到在 Debian 上,使用默认网络安装 CD,我会遇到一个没有 WLAN 驱动程序的系统。经过一些互联网搜索,我才意识到我需要一个非官方非免费安装 CD 才能进行网络安装,以充分利用我的所有硬件(一个普通的英特尔 N1000 WLAN 适配器)。
解决这个问题后,安装程序识别了我的 WLAN 适配器并建立了与我的 WLAN 的连接。
从 openSUSE 转过来,我以为我可以在安装过程中分别设置系统语言(我想使用英语)和键盘布局(我需要使用德语)——但这在 Debian 中不起作用。之后添加第二种语言是命令行调用的一场噩梦(据我目前研究),至少我还没有成功。
因此,如果本说明中包含德语文本或屏幕截图,我深表歉意。也许我稍后会克服这场噩梦并替换它们。
libglapi-mesa 20.3.5(GL-API 的免费实现 - 运行时库)已安装。
GNU C++ 10(或更高版本)
Code::Blocks 至少需要 GNU C++ 7。系统已经安装了 GNU C 10。我保持不变并安装
- g++-10 (GNU C++ 编译器)
除此之外。
Code::Blocks
我们达到了 Debian 相对于 openSUSE 的第一个优势:Code::Blocks 20.03 可以通过 Synaptic 软件包管理器安装。我安装了以下软件包:
- codeblocks (集成开发环境 (IDE) Code::Blocks)
及其所有自动依赖项。
Code::Blocks 20.03 现在可以启动了——并且它会自动正确配置 C++。
SFML
Synaptic 提供 SFML 2.5.1 - 这是目前最新的稳定版本。我安装了以下软件包:
libsfml-dev 2.5.1
(简单快速多媒体库 - 开发文件)
以及其所有自动依赖项。
make
Debian 默认安装已包含 make 4.3-4.1 版本。
Cmake
Synaptic 提供 Cmake 3.20.4。我安装了这些软件包:
cmake 3.20.4
(跨平台开源构建系统)cmake-qt-gui 3.20.4
(基于 Qt 的 CMake 用户界面 (cmake-gui))
及其所有自动依赖项。
我已下载当前开发分支 0.10 的源代码并将其解压到 ~/Projects/CodeBlocks/TGUI-0.10-Oct-2022。我发现它足够稳定,可以基于此版本进行测试。有一个很好的使用 cmake 编译 TGUI 的教程。
本教程基于 Windows 上的 Visual Studio 2019,也描述了 Windows 上的 Code::Blocks。以下是 Debian 上 Code::Blocks 的详细信息...
- 环境应如下配置:
- 第一次 [配置] 运行后(注意: Cmake 表区域中的红色行是新行,而不是错误行。Cmake 日志输出区域中的红色行不一定是错误,通常新的 [配置] 运行可以解决问题。),我将
CMAKE_BUILD_TYPE
更改为“Debug
”——但这纯粹是我的个人偏好。TGUI_CXX_STANDARD
已经是 14。TGUI_BACKEND
的默认值SFML_GRAPHICS
是一个非常合适的选择,它具有以下特性:- 优点:它使用 OpenGL 作为渲染后端(就像
SFML_OPENGL3
)。 - 优点:它只需要 OpenGL 1.1 版本,而不是 OpenGL 3.3 版本(像
SFML_OPENGL3
)。 - 优点:它提供更多(便利)功能(与
SFML_OPENGL3
相比)。 - 优点:对于大量使用 OpenGL 的情况,也可以请求更高的 OpenGL 版本。
- 缺点:多窗口/多线程应用程序的实现略显复杂(与
SFML_OPENGL3
相比)。
- 优点:它使用 OpenGL 作为渲染后端(就像
- 第二次 [配置] 运行没有任何错误,[生成] 可以开始。
在 Code::Blocks 项目文件和 make 文件生成后,子文件夹 /build 包含所有中间结果以及 Code::Blocks 项目文件 TGUI.cbp 和 make 文件。库的创建以两种方式(通过 Code::Blocks GUI 和通过 make 命令行调用)都完美无缺。
库生成后,可以再次启动 cmake-gui,更改配置(例如,“Release
”而不是“Debug
”),然后重复 [Configure]、[Generate] 和编译。结果可能如下所示:
最后的准备步骤是将库复制到 /usr/lib,并将 include 文件夹 TGUI 复制到 /usr/include。
在 Manjaro 上提供最小环境(更新 1)
我从 TGUI 的开发者 texus 那里得到了这个建议——根据我的偏好,去看看 Manjaro(事实证明 Manjaro 是一个不错的选择)。我从 Manjaro 21.3.7 “带 XFCE 的桌面”的全新默认安装开始。
Mesa 21.2.1(OpenGL 规范的开源实现)已安装。
GNU C++ 11(或更高版本)
Code::Blocks 至少需要 GNU C++ 7,但安装 GNU C++ 11(或更高版本)也没有问题。Manjaro 默认安装已包含 GNU C++ 12 版本。
Code::Blocks
Manjaro 默认安装已包含 Code::Blocks 20.3 版本。
SFML
Manjaro 软件安装程序提供 SFML 2.5.1——这是目前最新的稳定版本。我安装了以下软件包:
sfml 2.5.1-3
(一个简单、快速、跨平台且面向对象的多媒体 API)
以及其所有自动依赖项。
make
Make 并非默认安装的一部分,但可以通过 Manjaro 软件安装程序进行安装。我安装了以下软件包:
make 4.3.3
(GNU make 工具,用于维护程序组)
以及其所有自动依赖项。
Cmake
Manjaro 软件安装程序提供 Cmake 3.24.2。我安装了以下软件包:
cmake 3.24.2
(跨平台构建系统)cmake-qt-gui 3.18.4
(基于 Qt 的 CMake 用户界面 (cmake-gui))
及其所有自动依赖项。
TGUI
我已下载当前开发分支 0.10 的源代码并将其解压到 ~/Projects/CodeBlocks/TGUI-0.10-Oct-2022。我发现它足够稳定,可以基于此版本进行测试。有一个很好的使用 cmake 编译 TGUI 的教程。
本教程基于 Windows 上的 Visual Studio 2019,也描述了 Windows 上的 Code::Blocks。以下是 Manjaro 上 Code::Blocks 的详细信息...
- 环境应如下配置:
- 第一次 [配置] 运行后(注意: Cmake 表区域中的红色行是新行,而不是错误行。Cmake 日志输出区域中的红色行不一定是错误,通常新的 [配置] 运行可以解决问题。),我将
CMAKE_BUILD_TYPE
更改为“Debug
”——但这纯粹是我的个人偏好。TGUI_CXX_STANDARD
已经是 17——无需更改。TGUI_BACKEND
的默认值SFML_GRAPHICS
是一个非常合适的选择,它具有以下特性:- 优点:它使用 OpenGL 作为渲染后端(就像
SFML_OPENGL3
)。 - 优点:它只需要 OpenGL 1.1 版本,而不是 OpenGL 3.3 版本(像
SFML_OPENGL3
)。 - 优点:它提供更多(便利)功能(与
SFML_OPENGL3
相比)。 - 优点:对于大量使用 OpenGL 的情况,也可以请求更高的 OpenGL 版本。
- 缺点:多窗口/多线程应用程序的实现略显复杂(与
SFML_OPENGL3
相比)。
- 优点:它使用 OpenGL 作为渲染后端(就像
- 第二次 [配置] 运行没有任何错误,[生成] 可以开始。
在 Code::Blocks 项目文件和 make 文件生成后,子文件夹 /build 包含所有中间结果以及 Code::Blocks 项目文件 TGUI.cbp 和 make 文件。库的创建以两种方式(通过 Code::Blocks GUI 和通过 make 命令行调用)都完美无缺。
库生成后,可以再次启动 cmake-gui
,更改配置(例如,“Release
”而不是“Debug
”),然后重复 [Configure]、[Generate] 和编译。结果可能如下所示
最后的准备步骤是将库复制到 /usr/lib,并将 include 文件夹 TGUI 复制到 /usr/include。
TGUI 示例程序
提供的源代码附带一个项目文件,其配置如下...
项目设置
下一节是关于从头创建项目设置——如果您想直接从示例程序开始,可以跳过这一部分。我已经创建了一个 ~/Projects 文件夹。在此基础上,可以启动 Code::Blocks GUI 并创建一个新的 SFML 项目。首先,必须选择项目类型。
向导会引导您完成项目创建过程...
即使我们安装了 SFML 2.5.1,它仍然是 SFML 2.x 家族的成员,并且正确的库已分配给项目配置。
项目创建完成后,可以优化构建选项。
向导将所需的 SFML 库 sfml-graphics
、sfml-window
和 sfml-system
注册到“Debug
”和“Release
”的 链接器设置 中——但是“Debug
”和“Release
”之间没有区别,SFML 库可以移动到通用 链接器设置 中。
除了 SFML 库之外,TGUI 库也必须注册到 链接器设置 中。如上文“在 openSUSE 上提供最小环境”TGUI 和“在 Debian 上提供最小环境”TGUI 章所述,我已经创建了 TGUI 库的调试版本 libtgui-d
(~29MiB)和发布版本 libtgui
(~5MiB),现在我可以将调试版本注册到“Debug
”链接器设置 中,并将发布版本注册到“Release
”链接器设置 中。
TGUI 要求至少使用 C++ 14 标准进行编译。由于 GCC >= 6 默认使用 C++14,GCC >= 11 默认使用 C++17,因此通常无需指定 C++ 版本,除非应用程序需要特定的 C++ 版本。当出现此类错误时...
<font font-size:-2="">/usr/include/TGUI/String.hpp:87:18: error: ‘is_same_v’ is not a member of ‘std’; did you mean ‘is_same’?</font>
...那么选定的 C++ 版本与 C++ 安装不匹配。通常已安装 C++ 版本 >= 11,但设置了 -std=c++14
标志。这可以在 *.cbp 文件中修复
<Compiler>
<Add option="-Wall" />
<Add option="-std=c17" />
<Add option="-std=c++17" />
<Add directory="/usr/include" />
</Compiler>
我还将我偏好的警告设置添加到通用 编译器设置 中。
最后,我检查项目属性中的构建目标。
对于“Debug”构建,应用程序 类型 应为 控制台应用程序。背景是,
std::cerr << "...\n";
可以写入控制台窗口,这可能有助于调试错误。
对于“Release”构建,应用程序 类型 应为 GUI 应用程序。
使用代码
特定 TGUI_BACKEND 决策的影响
如上文“在 openSUSE 上提供最小环境”TGUI 和“在 Debian 上提供最小环境”TGUI 章所述,我保留了 TGUI_BACKEND
的默认值 SFML_GRAPHICS
。
这将根据此选择在 TGUI/Config.hpp 头文件中设置常量,确保启用匹配的后端类并禁用不匹配的后端类。您可以这样理解:
控制此行为的 TGUI/Config.hpp 头文件中的常量如下所示:
// Enables code that relies on a specific backend
#define TGUI_HAS_WINDOW_BACKEND_SFML 1
#define TGUI_HAS_WINDOW_BACKEND_SDL 0
#define TGUI_HAS_WINDOW_BACKEND_GLFW 0
#define TGUI_HAS_RENDERER_BACKEND_SFML_GRAPHICS 1
#define TGUI_HAS_RENDERER_BACKEND_SDL_RENDERER 0
#define TGUI_HAS_RENDERER_BACKEND_OPENGL3 0
#define TGUI_HAS_RENDERER_BACKEND_GLES2 0
#define TGUI_HAS_FONT_BACKEND_SFML_GRAPHICS 1
#define TGUI_HAS_FONT_BACKEND_SDL_TTF 0
#define TGUI_HAS_FONT_BACKEND_FREETYPE 0
#define TGUI_HAS_BACKEND_SFML_GRAPHICS 1
#define TGUI_HAS_BACKEND_SFML_OPENGL3 0
#define TGUI_HAS_BACKEND_SDL_RENDERER 0
#define TGUI_HAS_BACKEND_SDL_OPENGL3 0
#define TGUI_HAS_BACKEND_SDL_GLES2 0
#define TGUI_HAS_BACKEND_SDL_TTF_OPENGL3 0
#define TGUI_HAS_BACKEND_SDL_TTF_GLES2 0
#define TGUI_HAS_BACKEND_GLFW_OPENGL3 0
#define TGUI_HAS_BACKEND_GLFW_GLES2
由于我保留了 TGUI_BACKEND
的默认值 SFML_GRAPHICS
,因此有四个常量设置为 1,其他所有常量设置为 0。
TGUI_HAS_WINDOW_BACKEND_SFML
TGUI_HAS_RENDERER_BACKEND_SFML_GRAPHICS
TGUI_HAS_FONT_BACKEND_SFML_GRAPHICS
TGUI_HAS_BACKEND_SFML_GRAPHICS
通常,无需包含 TGUI/Config.hpp 头文件,但也有例外。
扩展
由于这个小型 TGUI 示例程序旨在成为一个经典的 GUI 应用程序(而非游戏),因此准备一些可重用于类似情况的应用程序基础设施非常有用(可在 TGUI-0.10-Extensions 文件夹中找到)。
MainForm
MainForm
类为主应用程序窗口提供了一个最小的基础设施。它不依赖于所选的后端(SFML、SDL、GLFW)。
GuiEx
GuiEx
类提供了一个被覆盖的 mainLoop()
方法。它依赖于所选的后端(SFML、SDL、GLFW),目前仅为 SFML_GRAPHICS
实现。通过这个类,可以干预主循环,例如,集成一个“您真的要关闭应用程序吗?”对话框。但是,这个简单的 TGUI 示例程序并未利用它。
ImageBytes
ImageBytes
类提供图像资源。它不依赖于所选的后端(SFML、SDL、GLFW)。
RibbonButton
RibbonButton
类提供了一个方便的 BitmapButton
类包装器,显著降低了创建功能区按钮所需的工作量。它不依赖于所选的后端(SFML、SDL、GLFW)。
PortableFileDialogs
PortableFileDialogs
仅头文件库提供了一种跨平台的方式来调用系统原生对话框。PortableFileDialogsWrapper
可用于方便地将此功能嵌入到 TGUI 程序中。它不依赖于所选的后端(SFML、SDL、GLFW)。但是,这个简单的 TGUI 示例程序并未利用它。
TGUI 示例程序
启动代码
我们先看一下 main()
方法:
// Run main(int, char**) instead main() to be able to debug in Code::Blocks.
int main(int argc, char** argv)
{
// Section 1: Native window creation.
#ifdef TGUI_HAS_BACKEND_SFML_GRAPHICS
sf::RenderWindow window(sf::VideoMode(980, 600), "TGUI window",
sf::Style::Default);
#else
// The OpenGL renderer backend in TGUI requires at least OpenGL version 3.3.
// Request the OpenGL version 3.3, even if it isn't provided by the hardware.
// Sometimes you'll have luck. (Mesa 20.2.5 pretends 3.3 on my 3.0 hardware.)
sf::ContextSettings requestedettings;
requestedettings.attributeFlags = sf::ContextSettings::Attribute::Core;
requestedettings.majorVersion = 3;
requestedettings.minorVersion = 3;
sf::Window window(sf::VideoMode(980, 600), "TGUI window", sf::Style::Default,
requestedettings);
#endif
auto realizedSettings = window.getSettings();
std::cout << "SUCCESS creating main frame window with OpenGL "
<< realizedSettings.majorVersion << "."
<< realizedSettings.minorVersion << " context.\n";
// Section 2: TGUI initialization.
tgui::Gui gui(window);
const char* themeFilePath = "./themes/BabyBlue.txt";
std::ifstream themeFile(themeFilePath);
if (!themeFile.is_open())
std::cerr << "ERROR: Unable to load theme.\n";
else
tgui::Theme::setDefault(themeFilePath);
auto container = gui.getContainer();
// Section 2: Sample application.
Bin2HeaderMainForm mainForm(window, gui);
if (!mainForm.createFrameContent())
{
std::cerr << "ERROR: Unable to create window content.\n";
return EXIT_FAILURE;
}
std::cout << "SUCCESS creating window content.\n";
// Section 4: Generic application infrastructure.
gui.mainLoop(mainForm.getClearColor());
return EXIT_SUCCESS;
}
这里我只想强调一点,第 1 节已经为 TGUI_BACKEND
可能已针对 SFML_GRAPHICS
和 SFML_OPENGL3
编译的事实做好了准备。
GUI 创建
接下来,让我们看看使用 createFrameContent()
创建 GUI
////////////////////////////////////////////////////////////////////////////////////
/// @brief Create the widgets of the main frame
////////////////////////////////////////////////////////////////////////////////////
bool createFrameContent()
{
const float groupWidth = 100.0f;
// Section 1: Prepare TabContainer to be used as ribbon.
m_menuTabContainer = tgui::TabContainer::create();
m_menuTabContainer->setPosition(0.0f, 0.0f);
m_menuTabContainer->setSize("100%", "84");
m_menuTabContainer->setTabFixedSize(m_tabFixedSize);
m_menuTabContainer->setTabAlignment(tgui::TabContainer::TabAlign::Bottom);
auto tabPanel1 = m_menuTabContainer->addTab(U"Start", true);
auto tabPanel2 = m_menuTabContainer->addTab(U"Edit", false);
this->getGui().add(m_menuTabContainer, U"MenuTabContainer");
// Section 2: Fill first ribbon tab.
auto systemLogOutButton = tgui::RibbonButton::create(
tangoIconTheme::Icons28::Action_SystemLogOut.getBytes(),
tangoIconTheme::Icons28::Action_SystemLogOut.getByteCount());
systemLogOutButton->setPosition(m_ribbonWidgetSpacing.x,
m_ribbonWidgetSpacing.y);
systemLogOutButton->setSize(groupWidth - m_ribbonWidgetSpacing.x * 2, 28.0f);
systemLogOutButton->setText(U"LogOut");
tabPanel1->add(systemLogOutButton, U"RibbonFile_LogOutButton");
systemLogOutButton->onMouseRelease(
&Bin2HeaderMainForm::buttonFileLogOutCallBack, this);
// Section 2a: Separate ribbon groups.
auto separator1 = tgui::SeparatorLine::create();
separator1->setPosition(groupWidth, 0.0f);
separator1->setSize(2.0f, "100%");
tabPanel1->add(separator1, U"RibbonFile_Separator1");
auto separator1Renderer = separator1->getRenderer();
separator1Renderer->setColor(tgui::Color(192, 192, 192, 255));
auto fileOpenButton = tgui::RibbonButton::create(
tangoIconTheme::Icons28::Action_DocumentOpen.getBytes(),
tangoIconTheme::Icons28::Action_DocumentOpen.getByteCount());
fileOpenButton->setPosition(groupWidth + m_ribbonWidgetSpacing.x,
m_ribbonWidgetSpacing.y);
fileOpenButton->setSize(groupWidth - m_ribbonWidgetSpacing.x * 2, 28.0f);
fileOpenButton->setText(U"Open");
tabPanel1->add(fileOpenButton, U"RibbonFile_OpenButton");
fileOpenButton->onMouseRelease(
&Bin2HeaderMainForm::buttonFileOpenCallBack, this);
// Section 2b: Separate ribbon groups.
auto separator2 = tgui::SeparatorLine::create();
separator2->setPosition(2 * groupWidth, 0.0f);
separator2->setSize(2.0f, "100%");
tabPanel1->add(separator2, U"RibbonFile_Separator2");
auto separator2Renderer = separator2->getRenderer();
separator2Renderer->setColor(tgui::Color(192, 192, 192, 255));
auto infoBrowserButton = tgui::RibbonButton::create(
tangoIconTheme::Icons28::Apps_InfoBrowser.getBytes(),
tangoIconTheme::Icons28::Apps_InfoBrowser.getByteCount());
infoBrowserButton->setPosition(groupWidth * 2 + m_ribbonWidgetSpacing.x,
m_ribbonWidgetSpacing.y);
infoBrowserButton->setSize(groupWidth - + m_ribbonWidgetSpacing.x * 2, 28.0f);
infoBrowserButton->setText(U"Info");
tabPanel1->add(infoBrowserButton, U"RibbonFile_InfoButton");
infoBrowserButton->onMouseRelease(
&Bin2HeaderMainForm::buttonInfoCallBack, this);
// Section 3: Fill second ribbon tab.
auto editCopyButton = tgui::RibbonButton::create(
tangoIconTheme::Icons28::Action_EditCopy.getBytes(),
tangoIconTheme::Icons28::Action_EditCopy.getByteCount());
editCopyButton->setPosition(m_ribbonWidgetSpacing.x, m_ribbonWidgetSpacing.y);
editCopyButton->setSize(groupWidth, 28.0f);
editCopyButton->setText(U"Copy");
tabPanel2->add(editCopyButton, U"RibbonCopyButton");
auto editPasteButton = tgui::RibbonButton::create(
tangoIconTheme::Icons28::Action_EditPaste.getBytes(),
tangoIconTheme::Icons28::Action_EditPaste.getByteCount());
editPasteButton->setPosition(m_ribbonWidgetSpacing.x, 32.0f);
editPasteButton->setSize(groupWidth, 28.0f);
editPasteButton->setText(U"Paste");
tabPanel2->add(editPasteButton, U"RibbonPasteButton");
// Section 4: Fill work area.
m_textArea = tgui::TextArea::create();
m_textArea->setPosition("3%", "100");
tgui::Layout hightLayout(tgui::Layout::Operation::Minus,
std::make_unique<tgui::Layout>("100%"),
std::make_unique<tgui::Layout>("140"));
m_textArea->setSize("94%", hightLayout);
std::string fontPath = "/usr/share/fonts/truetype/Hack-Regular.ttf";
std::ifstream themeFile(fontPath);
if (!themeFile.is_open())
std::cerr << "ERROR: Unable to load mono-space font.\n";
else
{
auto textAreaRenderer = m_textArea->getRenderer();
#ifdef TGUI_HAS_BACKEND_SFML_GRAPHICS
std::shared_ptr<tgui::BackendFont> monospaceBackendFont(
new tgui::BackendFontSFML());
#else
std::shared_ptr<tgui::BackendFont> monospaceBackendFont(
new tgui::BackendFontFreetype());
#endif
monospaceBackendFont->loadFromFile(fontPath);
tgui::Font monospaceFont(monospaceBackendFont, U"Hack-Regular");
textAreaRenderer->setFont(monospaceFont);
}
m_textArea->setText(U"Hello reader,\n\nthis is a multiline text editor.");
this->getGui().add(m_textArea, "TextArea");
// Section 5: Create status bar.
this->setStatusText(L"Hello, I am a state text!");
return true;
}
回调
前三个功能区按钮已注册回调(非静态类方法)
systemLogOutButton->onMouseRelease(
&Bin2HeaderMainForm::buttonFileLogOutCallBack, this);
fileOpenButton->onMouseRelease(
&Bin2HeaderMainForm::buttonFileOpenCallBack, this);
infoBrowserButton->onMouseRelease(
&Bin2HeaderMainForm::buttonInfoCallBack, this);
让我们深入了解这些回调
buttonFileLogOutCallBack
////////////////////////////////////////////////////////////////////////////////////
/// @brief Close the application
////////////////////////////////////////////////////////////////////////////////////
void buttonFileLogOutCallBack()
{
// This is a very simple approach, that doesn't support the
// "Do you really want to close?" question.
getWindow().close();
}
以下是 SFML 文档中关于 void sf::Window::close() 的摘录
引用关闭窗口并销毁所有附加资源。
调用此函数后,sf::Window 实例仍然有效,您可以调用 create() 来重新创建窗口。所有其他函数,例如 pollEvent() 或 display() 仍然会起作用(即您不必每次都测试 isOpen()),并且对已关闭的窗口没有影响。
因此,调用 close()
来退出应用程序虽然不优雅,但也没问题。
buttonFileOpenCallBack
////////////////////////////////////////////////////////////////////////////////////
/// @brief Call the TGUI internal file dialog
///
/// See also: https://forum.tgui.eu/index.php?topic=789.msg3894#msg3894
////////////////////////////////////////////////////////////////////////////////////
void buttonFileOpenCallBack()
{
std::vector<tgui::String> imageFileExtensions;
if (m_fileOpenDialog == nullptr)
{
imageFileExtensions.push_back(U"*.png");
imageFileExtensions.push_back(U"*.jpg");
std::vector<std::pair<tgui::String, std::vector<tgui::String>>> filters;
filters.push_back(std::pair<tgui::String,
std::vector<tgui::String>>(U"Images (png, jpg)", imageFileExtensions));
filters.push_back(std::pair<tgui::String,
std::vector<tgui::String>>(U"All files",
std::vector<tgui::String>({U"*.*"})));
m_fileOpenDialog = tgui::FileDialog::create(U"Open file", U"Open");
m_fileOpenDialog->onClosing(
&Bin2HeaderMainForm::onClosingFileOpenCallBack, this);
m_fileOpenDialog->setFileTypeFilters(filters, 0);
}
m_fileOpenDialog->setPosition("5%", "5%");
m_fileOpenDialog->setSize("90%", "90%");
this->getGui().add(m_fileOpenDialog, U"FileOpenDialog");
// Closing the ChildWindow (FileDialog) just means to remove it from GUI,
// the class instance will not be destroyed.
return;
}
m_fileOpenDialog
是 Bin2HeaderMainForm
类的一个成员字段,类型为 tgui::FileDialog::Ptr
。
已注册另一个回调(非静态类方法)
m_fileOpenDialog->onClosing(
&Bin2HeaderMainForm::onClosingFileOpenCallBack, this);
此回调负责处理选定的文件。由于它对理解 TGU 没有附加价值,因此我将其省略。
buttonInfoCallBack
////////////////////////////////////////////////////////////////////////////////////
/// @brief Call the TGUI internal message box
////////////////////////////////////////////////////////////////////////////////////
void buttonInfoCallBack()
{
if (m_infoMessageBox == nullptr)
{
tgui::String message(
U"This is 'Bin2HeaderTGUI', a TGUI sample application version 0.1.\n\n");
message.append(U"It shows some basic TGUI capabilities like:\n");
message.append(U"- TabContainer and RibbonButton with icon,\n");
message.append(U"- internal FileDialog to open a file including filter,\n");
message.append(U"- internal MessageBox and\n");
message.append(U"- mono-space Font assignment to the TextArea.\n");
std::vector<tgui::String> buttons = {U"OK"};
m_infoMessageBox = tgui::MessageBox::create(
U"Bin2HeaderTGUI 0.1", message, buttons);
m_infoMessageBox->onButtonPress(
&Bin2HeaderMainForm::buttonInfoMessageBoxCallBack, this);
}
m_infoMessageBox->setPosition("25%", "25%");
m_infoMessageBox->setSize("50%", "50%");
this->getGui().add(m_infoMessageBox, U"InfoMessageBox");
// Closing the ChildWindow (MessageBox) just means to remove it from GUI,
// the class instance will not be destroyed.
return;
}
m_unfoMessageBox
是 Bin2HeaderMainForm
类的一个成员字段,类型为 tgui::MessageBox::Ptr
。
已注册另一个回调(非静态类方法)
m_infoMessageBox->onButtonPress(
&Bin2HeaderMainForm::buttonInfoMessageBoxCallBack, this);
此回调处理消息框的关闭。
////////////////////////////////////////////////////////////////////////////////////
/// @brief Close the TGUI internal message box
////////////////////////////////////////////////////////////////////////////////////
bool buttonInfoMessageBoxCallBack()
{
return this->getGui().remove(m_infoMessageBox);
}
目前就这些。
项目的下载链接位于章节开头。
玩得开心!
兴趣点
尽管 TGUI 遗漏了许多其他 GUI 库提供的内容——但与 SFML 结合使用,第一个简单的示例程序看起来非常简洁。
结合 TGUI 和 SFML,用 C++ 进行开发确实很有趣——大量使用了 STL 的当前特性。
我一定会继续关注并深入研究 TGUI 和 SFML。
历史
- 2022 年 10 月 15 日 初始版本
- 2022 年 10 月 29 日 修复 1:《项目设置》章节的一些小修复
- 2022 年 11 月 6 日 更新 1:增加了 Manjaro Linux 作为目标系统的描述,以及关于 openSUSE 上
X11_xcb_...
错误的解释