Trabalho há muitos anos desenvolvendo interface gráfica para a web, soluções desktop Java e Android. Atualmente, eu estou curioso sobre como seria criar essas soluções para Desktop sem utilizar alguma ferramenta multiplataforma.
Com o intuito de experimentar como seria desenvolver uma interface gráfica para PCs Desktop nativa em C/C++, decidi criar uma pequena aplicação usual "TODO list" (lista de afazeres) nesses moldes.
Quando eu me refiro a "Desktop nativo", estou falando de soluções que não envolvam containers web ou outras soluções multi-plataforma como o Java FX ou Swing. Apesar de soluções híbridas serem ideais para criar aplicações para várias plataformas com o mesmo código, elas possuem um GAP em questão de performance. Além disso, diferentes sistemas operacionais se comportam de maneiras distintas e um código genérico para todos eles pode apresentar comportamentos inesperados.
Os diferentes sistemas operacionais possuem diferentes APIs e frameworks para criar apps Desktop. Para a minha pequena aplicação, decidi que ela seria executada no Linux e codificada em C++ com o compilador GCC. Dessa forma, utilizo apenas software livre tanto no S.O. quanto nas ferramentas utilizadas. Caso o programa necessite ser executado em Windows, o WSL pode abrir o software. Já no MacOS, é muito fácil de emular outros sistemas.
Continuando com a utilização de software livre, a biblioteca GTK nos permite construir interfaces gráficas. Ferramenta esta utilizada em programas famosos como GIMP
(editor de imagens) e Transmission
(cliente BitTorrent).
O repositório com o código fonte do projeto encontra-se aqui: https://github.com/misabitencourt/gtk3-cpp-todolist
Criando um arquivo CMAKE para o projeto
Foi feito o uso do CMAKE para definir as configurações de build. Eu acho muito cômodo o fato de o CMAKE gerar o Makefile no Linux e também poder criar um projeto Visual Studio no Windows.
O CMAKE pode ser instalado via APT em distribuições Debian.
sudo apt install cmake
O arquivo CMakeLists
ficou da seguinte forma:
cmake_minimum_required(VERSION 3.0)
project(todolist_app)
set(CMAKE_CXX_STANDARD 17)
# Find GTK3 package
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK3 REQUIRED gtk+-3.0)
file(GLOB SOURCES
"src/sample.cpp"
)
add_compile_options(-fpermissive)
include_directories(./)
include_directories(${GTK3_INCLUDE_DIRS})
add_executable(todolist_app ${SOURCES})
target_link_libraries(todolist_app ${GTK3_LIBRARIES})
add_definitions(${GTK3_CFLAGS_OTHER})
Para gerar o Makefile
, basta rodar:
mkdir build
cd build
cmake ../
Antes de executar o script do Makefile
, é preciso instalar a lib-gtk:
sudo apt install libgtk-3-dev
Obviamente, o compilador GCC (e o build-essential) junto com o make também têm de estar instalados. Geralmente, esses pacotes já estão configurados em praticamente toda a máquina de desenvolvedores. Para compilar o projeto, basta usar o make:
cd build
make
./todolist_app
Main loop
O código abaixo é o arquivo que possui o void main
do projeto. Nele, o loop principal da aplicação é iniciado. Antes disso, foram definidas algumas structs
e módulos para salvar, atualizar, listar e deletar a lista de afazeres. Como o objetivo é apenas o foco na interface, esses métodos persistem em uma lista local na memória.
Main loop:
#include <gtk/gtk.h>
#include<list>
#include<string>
// Types
#include "types/models.h"
// Services
#include "services/todo-service.h"
// Views
#include "views/dialogs.h"
#include "views/main.h"
int main(int argc, char *argv[])
{
gtk_init(&argc, &argv);
// Show all widgets
MainWindow::openMainWindow();
gtk_main();
return 0;
}
Antes da criação da janela da aplicação, a API da GTK requer uma chamada de inicialização passando os parâmetros dos argumentos da chamada. O método gtk_main
inicia o event loop
da interface gráfica.
O módulo MainWindow
foi definido em views/main.h
:
#define APP_MAIN_WINDOW_WIDTH 400
#define APP_MAIN_WINDOW_HEIGHT 800
#define SUCCESS_COLOR "#99DD99"
namespace MainWindow
{
// Main widget pointers
GtkWidget *mainWindow = nullptr;
GtkWidget *mainWindowContainer = nullptr;
GtkWidget *(*mainWindowRefresh)();
GtkWidget *todoInputText;
int inEdition = 0;
// Button events
void mainWindowViewTodoListSaveClicked(GtkWidget *widget, gpointer data)
{
GtkEntry *entry = GTK_ENTRY(data);
const gchar *text = gtk_entry_get_text(entry);
Todo todoDto;
todoDto.description = (char *)text;
if (inEdition)
{
todoDto.id = inEdition;
TodoService::todolistUpdate(&todoDto);
}
else
{
TodoService::todolistSave(&todoDto);
}
inEdition = 0;
mainWindowRefresh();
gtk_widget_show_all(mainWindowContainer);
}
void mainWindowViewTodoListEditClicked(GtkWidget *widget, int id)
{
Todo *todo = TodoService::todolistLoad(id);
inEdition = id;
g_print("Editing %i %s\n", todo->id, todo->description);
gtk_entry_set_text(GTK_ENTRY(todoInputText), todo->description);
}
void mainWindowViewTodoListDeleteClicked(GtkWidget *widget, int id)
{
TodoService::todolistDelete(id);
mainWindowRefresh();
gtk_widget_show_all(mainWindowContainer);
}
// Render function
GtkWidget *createMainWindowView()
{
GtkWidget *list_box;
GtkWidget *row;
GtkWidget *hbox;
GtkWidget *label;
mainWindowRefresh = createMainWindowView;
// Creates a new window
if (mainWindow == nullptr)
{
mainWindow = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(mainWindow), "Todo List");
gtk_window_set_default_size(GTK_WINDOW(mainWindow), APP_MAIN_WINDOW_WIDTH, APP_MAIN_WINDOW_HEIGHT);
g_signal_connect(mainWindow, "destroy", G_CALLBACK(gtk_main_quit), NULL);
}
else
{
gtk_widget_destroy(mainWindowContainer);
}
GtkWidget *windowContainer;
windowContainer = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
mainWindowContainer = windowContainer;
gtk_container_add(GTK_CONTAINER(mainWindow), windowContainer);
GtkWidget *todoFormContainer;
todoFormContainer = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
gtk_container_add(GTK_CONTAINER(windowContainer), todoFormContainer);
GtkWidget *inputVbox;
inputVbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
gtk_widget_set_size_request(inputVbox, 250, -1);
gtk_container_add(GTK_CONTAINER(todoFormContainer), inputVbox);
todoInputText = gtk_entry_new();
gtk_box_pack_start(GTK_BOX(inputVbox), todoInputText, TRUE, TRUE, 0);
GtkWidget *saveBtn;
saveBtn = gtk_button_new_with_label("Save");
gtk_widget_set_size_request(saveBtn, 80, -1);
gtk_container_add(GTK_CONTAINER(todoFormContainer), saveBtn);
g_signal_connect(saveBtn, "clicked", G_CALLBACK(mainWindowViewTodoListSaveClicked), todoInputText);
GtkWidget *cancelBtn;
cancelBtn = gtk_button_new_with_label("Cancel");
gtk_widget_set_size_request(cancelBtn, 80, -1);
gtk_container_add(GTK_CONTAINER(todoFormContainer), cancelBtn);
// // Creates a new GtkListBox
list_box = gtk_list_box_new();
gtk_container_add(GTK_CONTAINER(windowContainer), list_box);
// // Creates and add rows to the GtkListBox
int i = 0;
for (std::list<Todo>::iterator it = todoListRepository.begin(); it != todoListRepository.end(); ++it)
{
row = gtk_list_box_row_new();
hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5);
// Column 1
gchar *label_text1 = g_strdup_printf(it->description, i);
label = gtk_label_new(label_text1);
gtk_widget_set_size_request(label, 250, -1);
gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
g_free(label_text1);
// Column 2
GtkWidget *editBtn = gtk_button_new_with_label("Edit");
gtk_widget_set_size_request(saveBtn, 80, -1);
gtk_box_pack_start(GTK_BOX(hbox), editBtn, TRUE, TRUE, 0);
g_signal_connect(editBtn, "clicked", G_CALLBACK(mainWindowViewTodoListEditClicked), it->id);
// Column 3
GtkWidget *removeBtn = gtk_button_new_with_label("Remove");
gtk_widget_set_size_request(removeBtn, 80, -1);
gtk_box_pack_start(GTK_BOX(hbox), removeBtn, TRUE, TRUE, 0);
int index = i;
g_signal_connect(removeBtn, "clicked", G_CALLBACK(mainWindowViewTodoListDeleteClicked), it->id);
gtk_container_add(GTK_CONTAINER(row), hbox);
gtk_container_add(GTK_CONTAINER(list_box), row);
i++;
}
return mainWindow;
}
// Open function
void openMainWindow()
{
gtk_widget_show_all(createMainWindowView());
}
}
Semelhante a abordagem utilizada nas tecnologias web e no desenvolvimento Android, no GTK, temos uma interface para criar uma janela gtk_window_new
e dentro dela, adicionamos vários elementos que podem conter vários outros elementos dentro, assim, formando uma árvore. Os elementos aqui são GtkWidget
. Na documentação da lib podemos encontrar os diversos tipos de Widgets que podemos utilizar.
Em meu experimento, não utilizei nenhuma ferramenta para o design da tela. Tudo foi feito via interface de programação. Todavia, o GTK permite a criação de arquivos XML que representam a tela. E o Glade é uma interface gráfica que permite a criação destes XML.
Top comments (0)