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.
| Toolkit | Model antarmuka | Penggunaan khas | Lisensi inti |
|---|---|---|---|
| Qt | widget (C++) dan QML (declarative) | aplikasi desktop lintas platform | LGPLv3, GPLv3, komersial |
| GTK (+ GTKmm) | widget | aplikasi ekosistem GNOME di Linux | LGPL |
| SFML | window, grafis 2D, audio | game 2D dan aplikasi multimedia | zlib |
| SDL | window, input, audio | game, emulator, porting lintas platform | zlib |
| Dear ImGui | immediate mode | debug tooling dan editor internal | MIT |
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:
#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:
qmake6 hello.pro # menghasilkan Makefile dari file proyek Qt
make # kompilasi dan link, termasuk langkah khas Qt
./hello # jendela muncul, event loop mulai berjalanProgram 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.
#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:
[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() kembaliMari kita bedah baris demi baris:
- 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. - Baris 21 (
using Handler): setiap kejadian membawa handler berupastd::function<void(StateAplikasi&)>— fungsi yang menerima state aplikasi dan boleh mengubahnya. - Baris 24-26 (
antre): memasukkan pasangan nama dan handler kestd::queue. Fungsi ini dipakaimain()maupun handler lain, sehingga kejadian bisa memunculkan kejadian lain. - Baris 29-38 (
jalankan): analogiQApplication::exec(). Selama antrean tidak kosong, loop mengambil kejadian terdepan, mencetak jejaknya, mengeksekusi handler, lalu merender. - Baris 47-51 (
render): analogi repaint — tampilan dibaca ulang penuh dari state saat ini, bukan dari ingatan frame sebelumnya. - 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. - Baris 74-77 (handler tombol reset): kejadian ini diantrekan setelah kejadian pertama, tetapi sebelum kejadian lanjutan dari handler pertama.
- Baris 79-80 (eksekusi):
jalankan()menghabiskan antrean, danmain()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