Gtkmm: Construindo uma transição de telas parecido com Android

Por Jean Hertel, 16/08/2020

gtkmm , gtk+ , gtkstack , android

Na última atualização do adriconf eu decidi que queria mudar o jeito que os usuários utilizam a aplicação e para isso decidi implementar várias mudanças.

Uma destas mudanças foi remover completamente todas as caixas de diálogos e substitui-las por algo mais simples. Eu gosto muito das transições de tela do Android pois elas são suaves e não dão a impressão de que algo ainda esteja executando em outra tela como costuma ocorrer com diálogos modais. Mas como implementar uma transição de telas usando apenas GTKmm?

O GTK+ (e o GTKmm por extensão) possuem um excelente componente chamado Stack. Com este componente podemos criar várias telas mostrando sempre uma por vez. Na documentação oficial os exemplos sempre utilizam um GTKStackSwitcher para dar as usuários a opção de mudar facilmente entre as telas mas em nosso caso fica mais fácil chamarmos a API set_visible_child.

Vamos ver um exemplo em ação:

<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk+" version="3.22"/>
<object class="GtkWindow" id="mainWindow">
    <property name="width_request">200</property>
    <property name="height_request">200</property>
    <property name="can_focus">False</property>
    <child>
    <object class="GtkStack" id="ourStack">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="transition_type">slide-right</property>
        <child>
        <object class="GtkBox" id="box1">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="orientation">vertical</property>
            <child>
            <object class="GtkButton" id="button1">
                <property name="label" translatable="yes">To Screen 2</property>
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="receives_default">True</property>
            </object>
            <packing>
                <property name="expand">False</property>
                <property name="fill">False</property>
                <property name="position">0</property>
            </packing>
            </child>
        </object>
        <packing>
            <property name="name">screen1</property>
        </packing>
        </child>
        <child>
        <object class="GtkBox" id="box2">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="orientation">vertical</property>
            <child>
            <object class="GtkButton" id="button2">
                <property name="label" translatable="yes">Back to screen 1</property>
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="receives_default">True</property>
            </object>
            <packing>
                <property name="expand">False</property>
                <property name="fill">True</property>
                <property name="position">0</property>
            </packing>
            </child>
        </object>
        <packing>
            <property name="name">screen2</property>
            <property name="position">1</property>
        </packing>
        </child>
    </object>
    </child>
    <child type="titlebar">
    <placeholder/>
    </child>
</object>
</interface>

Com estes componentes podemos agora criar algum código:

#include "gtkmm.h"

int main(int argc, char *argv[]) {
    auto app = Gtk::Application::create(argc, argv, "nice.test");

    // Load our glade file
    auto gladeBuilder = Gtk::Builder::create();
    gladeBuilder->add_from_resource("/test/test.glade");

    Gtk::Window *mainWindow;
    Gtk::Stack *stack;
    Gtk::Button *button1, *button2;

    // Get our widgets
    gladeBuilder->get_widget("mainWindow", mainWindow);
    gladeBuilder->get_widget("ourStack", stack);
    gladeBuilder->get_widget("button1", button1);
    gladeBuilder->get_widget("button2", button2);

    // Setups what happens with our application
    button1->signal_clicked().connect([stack]() {
        stack->set_visible_child("screen2");
    });

    button2->signal_clicked().connect([stack]() {
        stack->set_visible_child("screen1");
    });


    return app->run(*mainWindow);
}

Com este código teremos uma tela similar a esta abaixo: Transição de telas

Você deve ter notado que o efeito de transição está sempre indo para a direita. Podemos controlar isso com a propriedade transition_type do Gtk::Stack. Um outro ponto que devemos observar é em relação ao id que utilizamos na mudança de telas. Se você olhar o código poderá observar que os Gtk::Box possuem id de box1 e box2 porem quando vamos invocar o método set_visible_child utilizamos os nomes screen1 e screen2. O motivo disso é que o objeto Gtk::Stack pode apenas ter filhos de um determinado tipo e portanto sempre vai encapsular objetos do tipo errado. Resolvemos isso definindo a propriedade name nas propriedades de packing do objeto.