Implementasi GUI Tcl/Tk tanpa CGo di Go (modernc.org/tk9.0): Pengemasan Widget dan Rendering Gambar

Python memiliki pustaka GUI bawaan seperti Tkinter. Baru-baru ini, untuk bahasa Go juga telah dikembangkan CGo-Free, Cross Platform Tk 라이브러리 yang memungkinkan penggunaan Tcl/Tk. Hari ini kita akan meninjau penggunaan dasarnya.
Membuat Hello, Tk
Pertama, mari mulai dengan contoh sederhana 'Hello, TK!'.
1package main
2
3import tk "modernc.org/tk9.0"
4
5func main() {
6 tk.Pack(
7 tk.TButton(
8 tk.Txt("Hello, TK!"),
9 tk.Command(func() {
10 tk.Destroy(tk.App)
11 })),
12 tk.Ipadx(10), tk.Ipady(5), tk.Padx(15), tk.Pady(10),
13 )
14 tk.App.Wait()
15}

Mari kita tinjau secara rinci kode contoh di atas dan hasil eksekusinya.
Bagi Anda yang pernah menggunakan Tk di Python, tentu Anda memahami struktur di mana widget dikemas (packed) di dalam jendela atau langsung di bawah jendela. Bergantung pada jenis widget, label dan elemen lain dapat termasuk di dalamnya.
Ipadx dan Ipady merupakan singkatan dari Internal padding, yang mengatur ruang kosong pada bagian dalam widget. Dalam contoh ini, ruang kosong pada tombol yang disesuaikan.
Pustaka ini memiliki struktur Window, dan variabel App mengelola jendela tingkat teratas. Variabel ini telah didefinisikan sebelumnya di dalam pustaka. Oleh karena itu, fungsi tk.App.Destroy() yang mengakhiri tk.App.Wait() berperan untuk menutup jendela tingkat teratas.
Sekarang mari kita tinjau beberapa contoh yang terdapat di folder _examples pada GitLab.
Memproses File SVG
Berikut adalah contoh yang menampilkan file SVG pada widget label.
1package main
2
3import . "modernc.org/tk9.0"
4
5// https://en.wikipedia.org/wiki/SVG
6const svg = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
7<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
8<svg width="391" height="391" viewBox="-70.5 -70.5 391 391" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
9<rect fill="#fff" stroke="#000" x="-70" y="-70" width="390" height="390"/>
10<g opacity="0.8">
11 <rect x="25" y="25" width="200" height="200" fill="lime" stroke-width="4" stroke="pink" />
12 <circle cx="125" cy="125" r="75" fill="orange" />
13 <polyline points="50,150 50,200 200,200 200,100" stroke="red" stroke-width="4" fill="none" />
14 <line x1="50" y1="50" x2="200" y2="200" stroke="blue" stroke-width="4" />
15</g>
16</svg>`
17
18func main() {
19 Pack(Label(Image(NewPhoto(Data(svg)))),
20 TExit(),
21 Padx("1m"), Pady("2m"), Ipadx("1m"), Ipady("1m"))
22 App.Center().Wait()
23}

Cara pustaka ini memproses SVG adalah sebagai berikut:
- Isi file SVG dibaca sebagai string (atau disertakan langsung seperti pada contoh di atas).
- Isi tersebut diteruskan ke fungsi Data untuk diubah menjadi string yang berisi opsi (opsi -data).
- Byte yang telah diubah diteruskan ke fungsi NewPhoto, yang mengembalikan pointer struktur Img yang merepresentasikan gambar Tcl/Tk.
- Saat melewati fungsi Image, pointer struktur Img diubah menjadi string dengan opsi -Image yang ditambahkan.
- Alasan struktur diubah menjadi string yang berisi nilai RAW adalah untuk pembuatan widget Label.
File ICO dan PNG juga diproses dengan cara yang serupa.
Memproses File PNG
1package main
2
3import _ "embed"
4import . "modernc.org/tk9.0"
5
6//go:embed gopher.png
7var gopher []byte
8
9func main() {
10 Pack(Label(Image(NewPhoto(Data(gopher)))),
11 TExit(),
12 Padx("1m"), Pady("2m"), Ipadx("1m"), Ipady("1m"))
13 App.Center().Wait()
14}

Proses pengolahan file PNG adalah sebagai berikut:
- Mengubah gopher.png yang di-embed menjadi tipe string yang berisi opsi.
- Mengubahnya menjadi tipe *Img melalui fungsi NewPhoto.
- Setelah melewati fungsi Image dan diubah menjadi string RAW, widget label dibuat.
File ICO diproses dengan cara yang sama, dan perbedaannya dengan format SVG hanya terletak pada cara pemrosesan di dalam fungsi Data.
Di sini kita akan meninjau hakikat "string yang berisi opsi":
1type rawOption string
String yang berisi opsi seperti yang disebutkan sebelumnya hanyalah string yang telah diformat.
1func (w *Window) optionString(_ *Window) string {
2 return w.String()
3}
Metode optionString adalah metode untuk pointer Window, dan mengembalikan sebuah string.
Terakhir, mari kita tinjau secara singkat struktur internal fungsi Data:
1func Data(val any) Opt {
2 switch x := val.(type) {
3 case []byte:
4 switch {
5 case bytes.HasPrefix(x, pngSig):
6 // ok
7 case bytes.HasPrefix(x, icoSig):
8 b := bytes.NewBuffer(x)
9 img, err := ico.Decode(bytes.NewReader(x))
10 if err != nil {
11 fail(err)
12 return rawOption("")
13 }
14
15 b.Reset()
16 if err := png.Encode(b, img); err != nil {
17 fail(err)
18 return rawOption("")
19 }
20
21 val = b.Bytes()
22 }
23 }
24 return rawOption(fmt.Sprintf(`-data %s`, optionString(val)))
25}
Dari kode tersebut terlihat bahwa untuk file ICO atau PNG diperlukan proses encoding/decoding. Untuk kasus lainnya, tanpa konversi khusus, hanya opsi -data yang ditambahkan pada string yang telah diubah menjadi tipe byte, untuk menandai bahwa itu adalah hasil dari fungsi Data.
Memuat Gambar dengan Widget Menu
Dengan menambahkan widget menu pada contoh pemuatan PNG dan ICO yang telah kita latih sebelumnya, kita dapat membuat aplikasi yang menampilkan gambar yang diperlukan.
Pertama, mari kita tinjau contoh widget menu sederhana.
1package main
2
3import (
4 "fmt"
5 . "modernc.org/tk9.0"
6 "runtime"
7)
8
9func main() {
10 menubar := Menu()
11
12 fileMenu := menubar.Menu()
13 fileMenu.AddCommand(Lbl("New"), Underline(0), Accelerator("Ctrl+N"))
14 fileMenu.AddCommand(Lbl("Open..."), Underline(0), Accelerator("Ctrl+O"), Command(func() { GetOpenFile() }))
15 Bind(App, "<Control-o>", Command(func() { fileMenu.Invoke(1) }))
16 fileMenu.AddCommand(Lbl("Save As..."), Underline(5))
17 fileMenu.AddSeparator()
18 fileMenu.AddCommand(Lbl("Exit"), Underline(1), Accelerator("Ctrl+Q"), ExitHandler())
19 Bind(App, "<Control-q>", Command(func() { fileMenu.Invoke(4) }))
20 menubar.AddCascade(Lbl("File"), Underline(0), Mnu(fileMenu))
21
22 App.WmTitle(fmt.Sprintf("%s on %s", App.WmTitle(""), runtime.GOOS))
23 App.Configure(Mnu(menubar), Width("8c"), Height("6c")).Wait()
24}

Dalam contoh ini, kita telah mencoba membuat menu bar dan menu, penekanan huruf, opsi Command, pengikatan pintasan keyboard, serta pengaturan ukuran awal jendela aplikasi.
Selanjutnya, mari kita ubah fungsi Command yang ditentukan pada GetOpenFile agar membuat program yang memuat dan menampilkan gambar.
Rencana penulisan program adalah sebagai berikut:
- Membatasi agar hanya file PNG dan ICO yang dapat dibuka
- Memproses file yang dipilih pada dialog file
- Mengimplementasikan widget untuk menampilkan gambar
Berikut adalah kode yang mencerminkan rencana tersebut:
1package main
2
3import (
4 "fmt"
5 "log"
6 "os"
7 "runtime"
8 "strings"
9
10 . "modernc.org/tk9.0"
11)
12
13func handleFileOpen() {
14 s := GetOpenFile()
15 for _, photo := range s {
16 formatCheck := strings.Split(photo, ".")
17 format := formatCheck[len(formatCheck)-1]
18
19 if (strings.Compare(format, "png") == 0) || (strings.Compare(format, "ico") == 0) {
20 picFile, err := os.Open(photo)
21 if err != nil {
22 log.Println("Error while opening photo, error is: ", err)
23 }
24
25 pictureRawData := make([]byte, 10000*10000)
26 picFile.Read(pictureRawData)
27
28 imageLabel := Label(Image(NewPhoto(Data(pictureRawData))))
29 Pack(imageLabel,
30 TExit(),
31 Padx("1m"), Pady("2m"), Ipadx("1m"), Ipady("1m"))
32 }
33 picFile.Close()
34 }
35}
36
37func main() {
38 menubar := Menu()
39
40 fileMenu := menubar.Menu()
41 fileMenu.AddCommand(Lbl("Open..."), Underline(0), Accelerator("Ctrl+O"), Command(handleFileOpen))
42 Bind(App, "<Control-o>", Command(func() { fileMenu.Invoke(0) }))
43 fileMenu.AddCommand(Lbl("Exit"), Underline(1), Accelerator("Ctrl+Q"), ExitHandler())
44 Bind(App, "<Control-q>", Command(func() { fileMenu.Invoke(1) }))
45 menubar.AddCascade(Lbl("File"), Underline(0), Mnu(fileMenu))
46
47 App.WmTitle(fmt.Sprintf("%s on %s", App.WmTitle(""), runtime.GOOS))
48 App.Configure(Mnu(menubar), Width("10c"), Height("10c")).Wait()
49}

Kode di atas bekerja dengan cara berikut:
- Memeriksa ekstensi file menggunakan fungsi perbandingan string dari paket strings
- Membuka, membaca, lalu menutup file menggunakan paket os
- Gambar yang dibaca ditampilkan dengan widget label
Ringkasan
Dalam tulisan ini, kita telah membahas hal-hal berikut dengan memanfaatkan pustaka Tcl/Tk pada bahasa Go:
- Struktur dasar aplikasi GUI
- Pemrosesan berbagai format gambar seperti SVG, PNG, dan ICO
- Pengemasan widget dan pengelolaan tata letak
- Struktur pemrosesan data gambar
- Pengikatan pintasan keyboard dan perintah widget
Dengan menggunakan Go bersama Tcl/Tk, Anda dapat membuat program GUI yang sederhana namun praktis. Berdasarkan hal tersebut, silakan mencoba mengembangkan aplikasi GUI yang lebih kompleks.