Seluruh program yang kita tulis sepanjang course ini berbicara lewat terminal: std::cout mencetak, std::cin membaca, dan alurnya berjalan rapi dari awal main() sampai return. Aplikasi desktop bekerja dengan kontrak berbeda: pengguna bebas menekan tombol apa pun, kapan pun, dengan urutan yang tidak bisa kita tentukan — program bersiap menerima kejadian dan bereaksi. Section ini memetakan toolkit GUI C++; chapter ini menjadi petanya, ditutup program teruji yang membuktikan paradigma barunya tanpa pustaka GUI apa pun.

Toolkit Utama di Ekosistem C++ #

Empat keluarga toolkit menopang hampir semua aplikasi desktop C++. Masing-masing lahir dari kebutuhan berbeda, sehingga memilihnya bukan soal mana yang terbaik, melainkan mana yang cocok dengan jenis aplikasi kita. Tabel berikut merangkum petanya; sub-bagian setelahnya membedah tiap keluarga.

ToolkitModel antarmukaPenggunaan khasLisensi inti
Qtwidget (C++) dan QML (declarative)aplikasi desktop lintas platformLGPLv3, GPLv3, komersial
GTK (+ GTKmm)widgetaplikasi ekosistem GNOME di LinuxLGPL
SFMLwindow, grafis 2D, audiogame 2D dan aplikasi multimediazlib
SDLwindow, input, audiogame, emulator, porting lintas platformzlib
Dear ImGuiimmediate modedebug tooling dan editor internalMIT

Qt: Widget dan QML #

Qt adalah toolkit paling matang dan paling luas pemakaiannya di dunia C++. Antarmukanya punya dua wajah: Qt Widgets, pustaka class C++ klasik untuk aplikasi formulir, dan Qt Quick/QML, bahasa declaratif untuk UI beranimasi dengan logika tetap di C++. Lisensinya tiga pilihan — LGPLv3, GPLv3, komersial — sehingga aplikasi closed source umumnya memakai dynamic linking sesuai syarat LGPL atau membeli lisensi komersial.

GTK: Ranah GNOME #

GTK ditulis dalam C dan menjadi fondasi desktop GNOME, dengan binding C++ resmi bernama GTKmm. Desktop Linux terbagi ke dua kubu: KDE dibangun di atas Qt, GNOME di atas GTK. Bila target kita desktop Linux dengan tampilan native, GTK layak masuk daftar pendek.

SFML dan SDL: Bahan Baku Game, Bukan Widget #

Keduanya bukan toolkit widget — tidak ada tombol, tabel, atau dialog bawaan. SFML dan SDL menyediakan window, input, grafis 2D, dan audio: bahan baku game dan aplikasi multimedia. Alih-alih event loop widget, game memakai game loop yang menggambar puluhan hingga ratusan kali per detik — itulah sebabnya keluarga ini berdiri sendiri di lanskap GUI.

Dear ImGui: Immediate Mode untuk Tooling #

Dear ImGui mengambil pendekatan paling berbeda: tidak ada pohon widget yang tersimpan — UI digambar ulang setiap frame langsung dari kode C++. Pustaka ini lahir sebagai alat debug di industri game — panel statistik, editor level, inspector data — dan karena kecil (beberapa file berlisensi MIT), ImGui menjadi favorit tooling internal; chapter penutup section ini membahasnya tuntas.

Kriteria Memilih Toolkit #

Empat pertanyaan berikut menyaring opsi secara berurutan.

  • Platform target: desktop Linux saja membuka GTK dan Qt; lintas platform biasanya berarti Qt, kecuali aplikasi bergaya game.
  • Lisensi: produk closed source perlu memperhatikan syarat LGPL (dynamic linking) atau lisensi komersial; MIT dan zlib paling longgar.
  • Ukuran binary: Dear ImGui menambah ratusan kilobyte, Qt static linking puluhan megabyte — pertimbangan penting untuk tool ringan.
  • Jenis aplikasi: formulir dan panel data → widget toolkit; game dan multimedia → SFML/SDL; debug tooling → Dear ImGui.

Paradigma Event-Driven vs Program Konsol #

Program konsol yang selama ini kita tulis mengendalikan alurnya sendiri: main() berjalan dari atas ke bawah lalu kembali — program berakhir. Aplikasi GUI membalik arah kendali: setelah antarmuka disiapkan, main() memanggil fungsi event loop (pada Qt, QApplication::exec()) dan tidak kembali sampai pengguna menutup aplikasi. Selama itu, urutan eksekusi ditentukan kejadian — klik mouse, penekanan tombol, timer, pemberitahuan OS — yang masuk ke antrean, lalu diambil event loop untuk memanggil handler yang kita tulis. Kita tidak pernah menulis “tunggu pengguna menekan tombol simpan” — kita menulis handler untuk kejadian “tombol simpan ditekan” dan menyerahkan urutannya pada antrean.

Bentuk program Qt paling kecil memperlihatkan tiga kata kunci itu:

cpp Sketsa Qt — jendela pertama (ILUSTRATIF: Qt tidak terinstal di sandbox praktikum, bentuk API untuk proyek nyata)
#include <QApplication>
#include <QPushButton>

int main(int argc, char* argv[]) {
    QApplication app(argc, argv);      // wajib: pemilik event loop
    QPushButton tombol("Halo, GUI!");  // widget: tombol dengan label
    tombol.show();                     // minta widget ditampilkan
    return app.exec();                 // event loop berjalan sampai quit
}

Membangunnya di mesin nyata melibatkan langkah khas Qt yang dijalankan build system:

bash Sesi build Qt — ILUSTRATIF: perintah untuk proyek nyata, tidak dieksekusi di sandbox
qmake6 hello.pro            # menghasilkan Makefile dari file proyek Qt
make                        # kompilasi dan link, termasuk langkah khas Qt
./hello                     # jendela muncul, event loop mulai berjalan

Program Utama: Event Loop Mini di Terminal #

Sketsa Qt di atas berdiri di atas tiga pilar: pemilik antrean (QApplication), widget yang memancarkan kejadian, dan lingkaran exec(). Ketiganya bisa ditiru dengan pustaka standar — antrean berupa std::queue, handler berupa std::function, state berupa struct biasa yang dirender ulang setelah setiap handler. Program berikut meniru siklus klik → handler → render state secara deterministik.

cpp
#include <functional>    // std::function
#include <iostream>
#include <queue>         // std::queue
#include <string>
#include <utility>       // std::move
#include <vector>        // penampung dasar bawaan std::queue

// State aplikasi GUI mini: satu-satunya data yang "terlihat" pengguna.
// Handler kejadian membaca dan mengubah state ini, lalu renderer
// menggambar ulang tampilan dari state terkini.
struct StateAplikasi {
    int jumlahKlik = 0;
    bool sedangMemuat = false;
    std::string isiLabel = "Siap";
};

// Event loop mini: kejadian diambil satu per satu dari antrean,
// handler-nya dieksekusi, lalu tampilan dirender ulang.
class EventLoop {
public:
    using Handler = std::function<void(StateAplikasi&)>;

    // Memasukkan kejadian ke antrean. Dipakai main() maupun handler lain.
    void antre(std::string nama, Handler handler) {
        antrean_.push(Kejadian{std::move(nama), std::move(handler)});
    }

    // Analogi QApplication::exec(): habiskan antrean kejadian.
    void jalankan() {
        while (!antrean_.empty()) {
            Kejadian kejadian = std::move(antrean_.front());
            antrean_.pop();
            std::cout << "[kejadian] " << kejadian.nama << "\n";
            kejadian.handler(state_);
            render();
        }
        std::cout << "[kejadian] antrean kosong, exec() kembali\n";
    }

private:
    struct Kejadian {
        std::string nama;
        Handler handler;
    };

    // Analogi repaint: gambar ulang tampilan dari state saat ini.
    void render() const {
        std::cout << "  [render] label=\"" << state_.isiLabel
                  << "\" klik=" << state_.jumlahKlik
                  << " memuat=" << (state_.sedangMemuat ? "ya" : "tidak") << "\n";
    }

    std::queue<Kejadian> antrean_;
    StateAplikasi state_;
};

int main() {
    EventLoop loop;

    // Kejadian pertama: pengguna menekan tombol ambil data.
    loop.antre("klik tombolAmbilData", [&loop](StateAplikasi& state) {
        state.jumlahKlik += 1;
        state.sedangMemuat = true;
        state.isiLabel = "Memuat...";
        // Handler mengantre kejadian lanjutan, seperti operasi asinkron
        // yang "selesai" di kemudian hari.
        loop.antre("data selesai dimuat", [](StateAplikasi& stateLanjutan) {
            stateLanjutan.sedangMemuat = false;
            stateLanjutan.isiLabel = "128 baris dimuat";
        });
    });

    // Kejadian kedua: pengguna menekan tombol reset saat data masih dimuat.
    loop.antre("klik tombolReset", [](StateAplikasi& state) {
        state.jumlahKlik += 1;
        state.isiLabel = "Siap";
    });

    loop.jalankan();
    return 0;
}

Output yang Dihasilkan:

text
[kejadian] klik tombolAmbilData
  [render] label="Memuat..." klik=1 memuat=ya
[kejadian] klik tombolReset
  [render] label="Siap" klik=2 memuat=ya
[kejadian] data selesai dimuat
  [render] label="128 baris dimuat" klik=2 memuat=tidak
[kejadian] antrean kosong, exec() kembali

Mari kita bedah baris demi baris:

  1. Baris 8-14 (StateAplikasi): tiga data inilah seluruh “tampilan” program — jumlah klik, status memuat, dan teks label. Handler tidak menggambar apa pun; handler hanya mengubah struct ini.
  2. Baris 21 (using Handler): setiap kejadian membawa handler berupa std::function<void(StateAplikasi&)> — fungsi yang menerima state aplikasi dan boleh mengubahnya.
  3. Baris 24-26 (antre): memasukkan pasangan nama dan handler ke std::queue. Fungsi ini dipakai main() maupun handler lain, sehingga kejadian bisa memunculkan kejadian lain.
  4. Baris 29-38 (jalankan): analogi QApplication::exec(). Selama antrean tidak kosong, loop mengambil kejadian terdepan, mencetak jejaknya, mengeksekusi handler, lalu merender.
  5. Baris 47-51 (render): analogi repaint — tampilan dibaca ulang penuh dari state saat ini, bukan dari ingatan frame sebelumnya.
  6. Baris 61-71 (handler tombol ambil data): handler menaikkan jumlahKlik, mengubah label menjadi "Memuat...", lalu mengantre kejadian lanjutan "data selesai dimuat" — persis pola operasi asinkron yang selesai di kemudian hari.
  7. Baris 74-77 (handler tombol reset): kejadian ini diantrekan setelah kejadian pertama, tetapi sebelum kejadian lanjutan dari handler pertama.
  8. Baris 79-80 (eksekusi): jalankan() menghabiskan antrean, dan main() kembali — analogi pengguna menutup aplikasi.

Urutan output menegaskan sifat antrean yang FIFO: klik tombolReset dieksekusi sebelum data selesai dimuat, sehingga pada render keduanya label sudah kembali "Siap" sementara status masih memuat=ya. Aplikasi GUI sungguhan menerima kejadian tanpa urutan pasti setiap saat, dan event loop yang menjaga semuanya tetap tertib. Handler yang mengantre kejadian baru (baris 67) menunjukkan sisi lain paradigma ini: kode kita tidak pernah “menunggu” — cukup bereaksi dan mendelegasikan kelanjutan ke antrean.

Pilar event loop sudah kita buktikan dengan tangan kosong. Chapter berikutnya mengambil pilar kedua Qt — signals dan slots, jalur kejadian dari widget ke kode kita — dan membuktikan mekanismenya dengan program mini yang mandiri.

Referensi: doc.qt.io — Qt Documentation , gtk.org , dearimgui.com