Click here to Skip to main content
15,883,705 members
Articles / Multimedia / OpenGL

First Steps / Tutorial: How to Start with TGUI (Running on SFML and Utilizing OpenGL) using Code::Blocks on Linux

Rate me:
Please Sign up or sign in to vote.
5.00/5 (2 votes)
5 Nov 2022CPOL23 min read 10K   8  
My way to have a first sample program running with "Texus' Graphical User Interface" (TGUI) - a cross-platform modern C++ immediate GUI, on which I use "Simple and Fast Multimedia Library" (SFML) as backend.
This tip provides instructions for successfully installing "Code::Blocks", "Simple and Fast Multimedia Library" (SFML) and "Texus' Graphical User Interface" (TGUI) on Linux to create and run a first little TGUI sample program in C++. The sample program shows some things that are not currently covered by example in the TGUI tutorial: [a] Creating a ribbon-like UI. [b] Working with the internal file dialog. [c] Setting the widget font.

This is how the result should look (after loading a small PNG file):

Image 1

Table of Contents

Introduction

The Texus' Graphical User Interface (TGUI) characterizes itself as - a cross-platform modern C++ GUI library. TGUI focuses on the core tasks of a GUI library and relies on either Simple and Fast Multimedia Library (SFML) - based on OpenGL / GLES, or Simple DirectMedia Layer (SDL) - based on OpenGL / DirectX or Graphics Library Framework (GLFW) - based on OpenGL / GLES / Vulcan to render the widgets and to interact with the platform UI (window, pointer, keyboard, controllers, ...).

TGUI could therefore (because it's rendering via OpenGL / GLES) be classified as immediate mode GUI (IMGUI). So, if you are going for TGUI, you should be aware of the advantages and disadvantages of an IMGUI.

The next section is a little bit about my personal background - you can just skip this. I can't explain why, but GUIs have just always been of particular interest to me. Maybe it's because I learned what object-oriented programming really means from my first own extensions to the Athena Widget Set (Xaw) written in K&R C (yes, object-orientation in C without ++) and later how much manual work C++ takes away from the developer compared to object orientation in C. Or because I learned with Motif what a well structured API is and that the better is the enemy of the good. Or because I learned with MS Windows UI that it doesn't have to cost thousands of dollars to be allowed to use a UI library (which Motif later learned with OpenMotif by the pressure of LessTif as well). Or about the fact that I then walked the rocky road with Microsoft from the Win32 API through Microsoft Foundation Classes (MFC) and Windows Template Library (WTL) to Windows Forms, which finally provided a full-featured, well-structured and easy-to-learn API for the Microsoft Windows UI. Completely indisputably, Windows Presentation Foundation (WPF) was a cool approach - but was then cannibalized by WinUI and more recently MAUI (by cannibalized, I mean Microsoft released new technologies before WPF fully caught on).

Now I just read (August 2022) that Microsoft has completely reworked the Windows Forms library and (after its supposed death) now makes it available again for .NET 5.0 and .NET 6.0. This was the trigger for me to look for GUIs that can be used cross-platform with Windows and Linux after a longer break. During one of my recent researches to stay updated regarding modern GUIs, I stumbled across Texus' Graphical User Interface (TGUI). Since I still have an old love for C++ in addition to my current love for C#, I wasn't afraid that TGUI is written in C++ C14 and was pleased to see that there is a C# binding for it as well.

Background

I chose SFML as the backend for my first TGUI programming experiments. I am aware that SFML is currently struggling to support successor technologies of OpenGL (e.g., Vulkan). This topic is - driven by the decreasing quality of OpenGL support on Windows (keyword DirectX) and OSX (keyword Metal) - indeed discussed in the SFML community (here and here and here and here ...).

The next section tells the story of why this article is about TGUI and SFML - you can also just skip this. Since my professional interest is Microsoft Windows and my private interest is Linux, these two platforms are important to me if I think of cross-platform GUI toolkits. Unfortunately, MAUI falls out of this category. Also, almost all other known GUI toolkits for Microsoft Windows are not available cross-platform for Linux. Therefore, I would like to approach this topic from the Linux side.

I already have some experience with classic Linux GUIs (classic in this case means rendering in the CPU) for X11 (like Athena Widget Set, Motif, GTK2) and also wrote one myself (Roma Widget Set). With the general availability of OpenGL and DirectX, the demands on the appearance of GUIs have increased significantly. I too find modern GUIs (modern in this case means rendering in the GPU) more exciting at the moment. I also made first steps with GPU rendering - namely OpenGL (since DirectX will never be fully supported on Linux, the relevant cross-platform graphics/multimedia libraries are limited to OpenGL, Vulcan, Cairo, SDL2 and Skia).

I think Vulkan is the most interesting option - but since I regularly deal with older hardware, I don't really dare to use Vulkan yet.

Cairo and Pango I know well and appreciate their reliability very much - so Cairo does not awake any particular curiosity in me right now.

SDL2 is a very feature-rich library and a real heavyweight. Since I don't plan to get into photorealistic rendering or ray tracing, it's a bit too heavy for me.

On the Internet, the opinion seems to be gaining ground that Skia is more modern than Cairo. I once tried the SkiaSharp based AvaloniaUI, but the complexity of a Skia installation on Linux and the list of supported IDEs made me lose interest.

Finally I decided for the Methuselah: OpenGL. But as much as I am fascinated by the possibilities of OpenGL (see e.g. article Getting started with OpenGL/OpenTK in MONO/.NET for serious applications) - rendering text with OpenGL is a nightmare. Even GLUT, GLFW and OpenTK (see e.g. article Abstract of the text rendering with OpenGL/OpenTK in MONO/.NET) do not change much.

TGUI can use Simple and Fast Multimedia Library (SFML), Simple DirectMedia Layer (SDL) and Graphics Library Framework (GLFW) as it's rendering backend. And since I don't know it until now, I am also very curious about SFML, which is written in C++ and for which there is also a C# binding. SFML looks like very clean C++ and seems worth to be tested. Consequently, I found the question of whether there are better cross-platform graphics/multimedia libraries like Cairo, SDL2 or Skia only secondary.

Create the Prerequisites

At the end of this tutorial, I want to have a simple TGUI sample program compiled and running on Linux.

One of the IDEs directly supported by TGUI is Code::Blocks. In addition, Code::Blocks is available on MS Windows and Linux, is very feature-rich and is regularly among the top places in the "best of C/C++ IDEs" lists. Even if the UI looks a bit antiquated, the interface concept and functionality is very similar to an older version of Visual Studio and you will quickly find your way around.

That's why I decided to use Code::Blocks.

My Favorites - openSUSE and Debian

I like the open source software approach, like to promote it by my actions and work professionally with Microsoft Windows all day - what could be more natural than relax with Linux in my spare time?

The next section tells the story of why I'm focusing on openSUSE and Debian here - you can also just skip this. Once upon a time, Debian 8 (the leading Linux distribution at the time, along with RedHad) introduced and pushed a GTK 3 desktop that made me feel like I was sitting in front of an oversized smartphone. Only, instead of a fat finger, I had a high-precision mouse pointer available as a typing device - whose precision was not used. That was not for me.

And so I switched to SUSE and later openSUSE. I learned to appreciate the intuitive handling of the much-maligned configuration tool YaST and worked with MonoDevelop on XFCE for a long time.

But in the meantime, a lot has changed in Debian: XFCE is very easy to set up as primary desktop, Code::Blocks and MonoDevelop are easy to install. Consequently, Debian has moved into my focus again.

Providing the Minimum Environment on openSUSE

I start with a fresh default installation of openSUSE Leap 15.4 "Desktop with XFCE".

Mesa 21.2.4 (system for rendering 3-D graphics, that utilizes the OpenGL command syntax and state machine) is already installed.

GNU C++ 11 (or higher)

Code::Blocks requires at least GNU C++ 7, but there is nothing against a GNU C++ 11 (or higher) installation. I have installed the following packages:

  • cpp11 (The GCC Preprocessor)
  • gcc11-c++ (The GNU C++ Compiler)
  • libX11-devel (Development files for the Core X11 protocol library)
  • libXt-devel (Development files for the X Toolkit Intrinsics library)

I installed the libX11-devel and libXt-devel to write a small X11 test program with Code::Blocks and because they will be needed later for compiling anyway.

Code::Blocks

Via the "Welcome" dialog (and its button "Get Software"), the openSUSE Software Archive can be invoked. A search for "codeblocks" reveals the community packages for openSUSE Leap 15.4.

Image 2

Unfortunately, none of the 1 Click install offers work (probably because some of the required packages have to be loaded from unsupported repositories[#]), so I opted for home:regataos Expert Download.

Image 3

Even if this installation will succeed at the end, the exchange of some standard packages (in my case: 10+ packages) must be accepted.

Image 4

But luckily all required packages can be installed. After Installation, the unsupported repositories[#] can be removed again ...

  • 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/

In the meantime, my system has installed updates and GNU C++ 12 became available. So I quickly install C++ 12 before I set up Code::Blocks.

It is also important that the gcc-c++ package is installed (it updates the references to gcc12-c++).

Now Code::Blocks 20.03 can finally be started - and it automatically configures C++ correctly.

SFML

YaST offers SFML 2.5.1 - this is the latest stable version now. I install the packages:

  • libsfml2-2_5
  • libsfml2-devel

make

The openSUS default installation already comes with make version 4.2.1-7.3.2.

Cmake

YaST offers Cmake 3.20.4. I install the packages:

  • cmake 3.20.4
  • cmake-full 3.20.4
  • cmake-gui 3.20.4
  • cmale-man 3.20.4

TGUI

I've downloaded the source code of current development branch 0.10 and extracted it to ~/Projects/CodeBlocks/TGUI-0.10-Oct-2022. I found it stable enough to base my tests on this version. There is a good tutorial how to compile TGUI using cmake.

The tutorial is based on Visual Studio 2019 on Windows and also describes Code::Blocks on Windows. Here are the details for Code::Blocks on openSUSE...

  • The environment should be configured like this:

Image 5

  • After the first [Configure] run (note: Red rows in the Cmake table area are new rows, not error rows. Red lines in Cmake log output area are not necessarily errors, often a new [Configure] run can solve the problem.), I changed the CMAKE_BUILD_TYPE to "Debug" - but this is my personal preference. The TGUI_BACKEND default value SFML_GRAPHICS is a really suitable choice, that involves the following qualities:
    • Pro: It uses OpenGL as rendering backend (just like SFML_OPENGL3).
    • Pro: It requires just OpenGL version 1.1, not OpenGL version 3.3 (like SFML_OPENGL3).
    • Pro: It provides more (convenience) functionality (compared to SFML_OPENGL3).
    • Pro: For heavy OpenGL usage, a higher OpenGL version can be requested as well.
    • Con: Multi window / multi thread applications are slightly more complex to implement (compared to SFML_OPENGL3).

Image 6

  • After the second [Configure] run, I fixed these errors:
    • X11_xcb_icccm_INCLUDE_PATH-NOTFOUND can be fixed with: /usr/include/xcb
    • X11_xcb_icccm_LIB-NOTFOUND can be fixed with: /usr/lib64

    • X11_xcb_util_INCLUDE_PATH-NOTFOUND can be fixed with: /usr/include/xcb

    • X11_xcb_util_LIB-NOTFOUND can be fixed with: /usr/lib64

Why (Update 1): Depending on the configured rendering backend (for SDL or SFML < 2.6 but not for GLFW or SFML >= 2.6) TGUI requires some X11 calls to get proper mouse cursors when resizing a child window, and X11 is identified by the find_package(X11) call. However, it is difficult to impossible to suppress this call if it is not needed for the current rendering backend. So these errors could also be ignored.

  • I also switch the TGUI_CXX_STANDARD from 17 to 14

  • The third [Configure] run ends without any error and [Generate] can be started.

After the Code::Blocks project file and the make file generation, the sub-folder /build contains all intermediate results as well as the Code::Blocks project file TGUI.cbp and the make file. The creation of the library works flawlessly in both ways (via Code::Blocks GUI and via make command line call).

After the library generation, the cmake-gui can be started again, configuration can be changed (e.g. "Release" instead of "Debug") and [Configure], [Generate] and compilation can be repeated. The result can look like this:

Image 7

The final preparation step is to copy the libraries to /usr/lib and the include folder TGUI to /usr/include.

Providing the Minimum Environment on Debian

I wanted to start with a fresh installation of Debian 11, but ...

First Pitfall

Coming from openSUSE, I didn't realize that on Debian, I would end up in a system without WLAN drivers with the default network installation CD. It took some internet research before I realized that I needed an unofficial non-free installation CD for the network installation to fully use all my hardware (an ordinary Intel N1000 WLAN adapter).

After solving this, installation recognizes my WLAN adapter and establishes connection to my WLAN.

Second Pitfall

Coming from openSUSE, I assumed that I can set the system language (I'd like to use English) and the keyboard layout (I need to use German) separately during installation - but that doesn't work with Debian. Adding a second language afterwards is a nightmare of command line calls (as far as I have researched so far) - at least I couldn't manage it so far.

So I apologize if German texts or screenshots are included in this description. Maybe I'll fight my way through this nightmare later and replace them.

libglapi-mesa 20.3.5 (Free implementation of the GL-API - runtime library) is already installed.

GNU C++ 10 (or higher)

Code::Blocks requires at least GNU C++ 7. There is already a GNU C 10 installed. I leave it unchanged and install:

  • g++-10 (The GNU C++ compiler)

in addition to that.

Code::Blocks

We reach the first advantage of Debian over openSUSE: Code::Blocks 20.03 can be installed from the Synaptic package manager. I install the package:

  • codeblocks (Integrated Development Environment (IDE) Code::Blocks)

and all its auto-dependencies.

Code::Blocks 20.03 can be started now - and it automatically configures C++ correctly.

SFML

Synaptic offers SFML 2.5.1 - this is the latest stable version now. I install the package:

  • libsfml-dev 2.5.1 (Simple and Fast Multimedia Library - Development Files)

and all it's auto-dependencies.

make

Debian default installation already comes with make version 4.3-4.1.

Cmake

Synaptic offers Cmake 3.20.4. I install the packages

  • cmake 3.20.4 (Cross-platform, open source make system)
  • cmake-qt-gui 3.20.4 (Qt based user interface for CMake (cmake-gui))

and all their auto-dependencies.

TGUI

I've downloaded the source code of current development branch 0.10 and extracted it to ~/Projects/CodeBlocks/TGUI-0.10-Oct-2022. I found it stable enough to base my tests on this version. There is a good tutorial how to compile TGUI using cmake.

The tutorial is based on Visual Studio 2019 on Windows and also describes Code::Blocks on Windows. Here are the details for Code::Blocks on Debian...

  • The environment should be configured like this:

Image 8

  • After the first [Configure] run (note: Red rows in the Cmake table area are new rows, not error rows. Red lines in Cmake log output area are not necessarily errors, often a new [Configure] run can solve the problem.), I changed the CMAKE_BUILD_TYPE to "Debug" - but this is my personal preference. The TGUI_CXX_STANDARD is already 14. The TGUI_BACKEND default value SFML_GRAPHICS is a really suitable choice, that involves the following qualities:
    • Pro: It uses OpenGL as rendering backend (just like SFML_OPENGL3).
    • Pro: It requires just OpenGL version 1.1, not OpenGL version 3.3 (like SFML_OPENGL3).
    • Pro: It provides more (convenience) functionality (compared to SFML_OPENGL3).
    • Pro: For heavy OpenGL usage, a higher OpenGL version can be requested as well.
    • Con: Multi window / multi thread applications are slightly more complex to implement (compared to SFML_OPENGL3).

Image 9

  • The second [Configure] run ends without any error and [Generate] can be started.

After the Code::Blocks project file and the make file generation, the sub-folder /build contains all intermediate results as well as the Code::Blocks project file TGUI.cbp and the make file. The creation of the library works flawlessly in both ways (via Code::Blocks GUI and via make command line call).

After the library generation, the cmake-gui can be started again, configuration can be changed (e.g. "Release" instead of "Debug") and [Configure], [Generate] and compilation can be repeated. The result can look like this:

Image 10

The final preparation step is to copy the libraries to /usr/lib and the include folder TGUI to /usr/include.

Providing the Minimum Environment on Manjaro (Update 1)

I got this tip from texus - the developer of TGUI - with my preferences to take a look at Manjaro (and it will turn out Manjaro is a good choice).  I start with a fresh default installation of Manjaro 21.3.7 "Desktop with XFCE".

Mesa 21.2.1 (An open source implementation of the OpenGL specification) is already installed.

GNU C++ 11 (or higher)

Code::Blocks requires at least GNU C++ 7, but there is nothing against a GNU C++ 11 (or higher) installation. Manjaro default installation comes already with GNU C++ version 12.

Code::Blocks

Manjaro default installation already comes with Code::Blocks version 20.3.

SFML

The Manjaro software installer offers SFML 2.5.1 - this is the latest stable version now. I install the package:

  • sfml 2.5.1-3 (A simple, fast, cross-platform, and object-oriented multimedia API)

and all it's auto-dependencies.

make

Make isn't part of the default installation, but can be installed with the Manjaro software installer. I install the package

  • make 4.3.3 (GNU make utility to maintain groups of programs)

and all it's auto-dependencies.

Cmake

The Manjaro software installer offers Cmake 3.24.2. I install the packages

  • cmake 3.24.2 (Cross-platform build system)
  • cmake-qt-gui 3.18.4 (Qt based user interface for CMake (cmake-gui))

and all their auto-dependencies.

TGUI

I've downloaded the source code of current development branch 0.10 and extracted it to ~/Projects/CodeBlocks/TGUI-0.10-Oct-2022. I found it stable enough to base my tests on this version. There is a good tutorial how to compile TGUI using cmake.

The tutorial is based on Visual Studio 2019 on Windows and also describes Code::Blocks on Windows. Here are the details for Code::Blocks on Manjora...

  • The environment should be configured like this:

Image 11

  • After the first [Configure] run (note: Red rows in the Cmake table area are new rows, not error rows. Red lines in Cmake log output area are not necessarily errors, often a new [Configure] run can solve the problem.), I changed the CMAKE_BUILD_TYPE to "Debug" - but this is my personal preference. The TGUI_CXX_STANDARD is already 17 - no need to change it. The TGUI_BACKEND default value SFML_GRAPHICS is a really suitable choice, that involves the following qualities:
    • Pro: It uses OpenGL as rendering backend (just like SFML_OPENGL3).
    • Pro: It requires just OpenGL version 1.1, not OpenGL version 3.3 (like SFML_OPENGL3).
    • Pro: It provides more (convenience) functionality (compared to SFML_OPENGL3).
    • Pro: For heavy OpenGL usage, a higher OpenGL version can be requested as well.
    • Con: Multi window / multi thread applications are slightly more complex to implement (compared to SFML_OPENGL3).

Image 12

  • The second [Configure] run ends without any error and [Generate] can be started.

After the Code::Blocks project file and the make file generation, the sub-folder /build contains all intermediate results as well as the Code::Blocks project file TGUI.cbp and the make file. The creation of the library works flawlessly in both ways (via Code::Blocks GUI and via make command line call).

After the library generation, the cmake-gui can be started again, configuration can be changed (e.g. "Release" instead of "Debug") and [Configure], [Generate] and compilation can be repeated. The result can look like this:

Image 13

The final preparation step is to copy the libraries to /usr/lib and the include folder TGUI to /usr/include.

The TGUI Sample Program

The provided source code comes with a project file, that is configured like this...

Project Setup

The next section is about the creation of the project setup from scratch - you can skip this, if you want to start with the sample program directly. I've already created a ~/Projects folder. Based on this, the Code::Blocks GUI can be started and a new SFML project can be created. First of all, the project type must be selected.

Image 14

The wizard guides through the project creation process...

Image 15

Even if we have SFML 2.5.1 installed - it is still a member of the SFML 2.x family and the right libraries are assigned to the project configuration.

Image 16

Image 17

After the project has been created, the build options can be optimized.

Image 18

The wizard registers the required SFML libraries sfml-graphics, sfml-window and sfml-system to the "Debug" and "Release" Linker settings - but there is no difference between "Debug" and "Release" and the SFML libraries can be moved to the generic Linker settings.

Image 19

In addition to the SFML libraries, the TGUI library must be registered to the Linker settings. As described above in the "Providing the minimum environment on openSUSE" TGUI and "Providing the minimum environment on Debian" TGUI chapters, I have created the debug libtgui-d (~29MiB) and release libtgui (~5MiB) versions of the TGUI library and now I can register the debug version to the "Debug" Linker settings as well as the release version to the "Release" Linker settings.

Image 20

Image 21

TGUI requires to be compiled with the C++ standard 14 at least. Since GCC >= 6 uses C++14 by default and GCC >= 11 uses C++17 by default, typically there is no need to specify the C++ version unless a specific C++ version is needed for the application. When errors like this arise ...

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

... then the selected C++ version does not match the C++ installation. Typically C++ version >= 11 is then installed, but the flag -std=c++14 is set. This can be fixed in the *.cbp file:

XML
<Compiler>
    <Add option="-Wall" />
    <Add option="-std=c17" />
    <Add option="-std=c++17" />
    <Add directory="/usr/include" />
</Compiler>

I also add my preferred warning settings to the generic Compiler settings.

Image 22

Finally, I check the build targets within the project properties.

Image 23

The application Type should be Console application for "Debug" build. The background is, that

C++
std::cerr << "...\n";

can write to the console window and that might help to debug errors.

Image 24

The application Type should be GUI application for "Release" build.

Image 25

Using the Code

Effects of the Decision for a Specific TGUI_BACKEND

As described above in the "Providing the minimum environment on openSUSE" TGUI and "Providing the minimum environment on Debian" TGUI chapters, I have kept the TGUI_BACKEND default value SFML_GRAPHICS.

Image 26

This will set constants in the TGUI/Config.hpp header file according to this selection, which will ensure that the matching backend classes are enabled and the non-matching backend classes are disabled. You have to imagine it like this:

Image 27

The constants in the TGUI/Config.hpp header file, that control this behavior, look like this:

C++
// 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 

Since I have kept the TGUI_BACKEND default value SFML_GRAPHICS, there are four constants set to 1 and all other set to 0.

  • TGUI_HAS_WINDOW_BACKEND_SFML
  • TGUI_HAS_RENDERER_BACKEND_SFML_GRAPHICS
  • TGUI_HAS_FONT_BACKEND_SFML_GRAPHICS
  • TGUI_HAS_BACKEND_SFML_GRAPHICS

Typically, it is not necessary to include TGUI/Config.hpp header file - but there are exceptions.

Extensions

Since this small TGUI sample program is intended to be a classic GUI application (and not a game), it is useful to prepare some application infrastructure that can be reused for similar cases (to be found in the folder TGUI-0.10-Extensions).

MainForm

The MainForm class provides a minimal infrastructure for the main window of an application. It is not dependent on the selected backend (SFML, SDL, GLFW).

GuiEx

The GuiEx class provides an overridden mainLoop() method. It depends on the selected backend (SFML, SDL, GLFW) and is currently implemented for SFML_GRAPHICS only. With this class, there is the possibility to intervene in the main loop, and e.g., integrate a "Do you really want to close the application?" dialog. However, this simple TGUI sample program does not make use of it.

ImageBytes

The ImageBytes class provides the resource for images. It is not dependent on the selected backend (SFML, SDL, GLFW).

RibbonButton

The RibbonButton class provides a convenient wrapper around the BitmapButton class that significantly reduces the effort required to create ribbon buttons. It is not dependent on the selected backend (SFML, SDL, GLFW).

PortableFileDialogs

The PortableFileDialogs header-only library provides a cross-platform way to call system-native dialogs. PortableFileDialogsWrapper is available for convenient embedding of this functionality into a TGUI program. It is not dependent on the selected backend (SFML, SDL, GLFW). However, this simple TGUI sample program does not make use of it.

The TGUI Sample Program

Starter Code

Let's look at the main() method first:

C++
// 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;
}

The only thing I want to go into here is that Section 1 is already prepared for the fact that TGUI_BACKEND may have been compiled for SFML_GRAPHICS as well as for SFML_OPENGL3.

GUI Creation

Next, let's take a look at the creation of the GUI with createFrameContent():

C++
////////////////////////////////////////////////////////////////////////////////////
/// @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;
}

Callbacks

The first three ribbon buttons have callbacks (non-static class method) registered:

C++
systemLogOutButton->onMouseRelease(
    &Bin2HeaderMainForm::buttonFileLogOutCallBack, this);

fileOpenButton->onMouseRelease(
    &Bin2HeaderMainForm::buttonFileOpenCallBack, this);

infoBrowserButton->onMouseRelease(
    &Bin2HeaderMainForm::buttonInfoCallBack, this);

Let's have a deeper look at these callbacks:

buttonFileLogOutCallBack
C++
////////////////////////////////////////////////////////////////////////////////////
/// @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();
}

Here is an excerpt from the SFML documentation regarding void sf::Window::close():

Quote:

Close the window and destroy all the attached resources.

After calling this function, the sf::Window instance remains valid and you can call create() to recreate the window. All other functions such as pollEvent() or display() will still work (i.e. you don't have to test isOpen() every time), and will have no effect on closed windows.

So, calling close() to exit the application is not elegant, but okay.

buttonFileOpenCallBack
C++
////////////////////////////////////////////////////////////////////////////////////
/// @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;
}

The m_fileOpenDialog is a Bin2HeaderMainForm class member field of type tgui::FileDialog::Ptr.

There is another callback (non-static class method) registered:

C++
m_fileOpenDialog->onClosing(
    &Bin2HeaderMainForm::onClosingFileOpenCallBack, this);

This callback cares for the processing of the selected file. Since it adds no value to understand TGU, I'll leave it out.

buttonInfoCallBack
C++
////////////////////////////////////////////////////////////////////////////////////
/// @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;
}

The m_unfoMessageBox is a Bin2HeaderMainForm class member field of type tgui::MessageBox::Ptr.

There is another callback (non-static class method) registered:

C++
m_infoMessageBox->onButtonPress(
    &Bin2HeaderMainForm::buttonInfoMessageBoxCallBack, this);

This callback cares for the closing of the message box.

C++
////////////////////////////////////////////////////////////////////////////////////
/// @brief Close the TGUI internal message box
////////////////////////////////////////////////////////////////////////////////////
bool buttonInfoMessageBoxCallBack()
{
    return this->getGui().remove(m_infoMessageBox);
}

That's it so far.

The download of the project is located at the beginning of the chapter.

Have fun!

Points of Interest

Even if TGUI leaves out a lot of what other GUI libraries offer - in combination with SFML, a first simple example program looks very neat.

With the combination of TGUI and SFML, the development in C++ is really fun - current features of the STL are used abundantly.

I will definitely stay on the ball and dive deeper into TGUI and SFML.

History

  • 15th October, 2022 Initial version
  • 29th October, 2022 Fixes 1: Some minor fixes to the "Project Setup" chapter
  • 6th November, 2022 Update 1: Added description for Manjaro Linux as a target system and explanations regarding the X11_xcb_... errors on openSUSE

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Team Leader Celonis SA
Germany Germany
I am currently the CEO of Symbioworld GmbH and as such responsible for personnel management, information security, data protection and certifications. Furthermore, as a senior programmer, I am responsible for the automatic layout engine, the simulation (Activity Based Costing), the automatic creation of Word/RTF reports and the data transformation in complex migration projects.

The main focus of my work as a programmer is the development of Microsoft Azure Services using C# and Visual Studio.

Privately, I am interested in C++ and Linux in addition to C#. I like the approach of open source software and like to support OSS with own contributions.

Comments and Discussions

 
-- There are no messages in this forum --