CGo Olmadan Go ile Tcl/Tk (modernc.org/tk9.0) GUI Uygulaması: Widget Yerleştirme ve Görüntü Oluşturma

Python'da Tkinter gibi bir GUI kütüphanesi varsayılan olarak yerleşik gelir. Son zamanlarda Go dilinde de Tcl/Tk kullanılmasını sağlayan CGo-Free, Cross Platform Tk Kütüphanesi geliştirilmiştir. Bugün bu kütüphanenin temel kullanımını inceleyeceğiz.
Hello, Tk Oluşturma
Öncelikle basit bir 'Hello, TK!' örneğiyle başlayalım.
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}

Yukarıdaki örnek kodu ve çalışma sonucunu ayrıntılı olarak inceleyelim.
Python'da Tk kullanma deneyimi olan okuyucular, pencere içine widget'ların paketlendiği veya doğrudan pencere altına widget'ların paketlendiği yapıyı anlayacaktır. Widget türüne bağlı olarak etiketler gibi öğeler bunun içine dahil edilir.
Ipadx ve Ipady, Internal padding (iç dolgu) kelimelerinin kısaltmasıdır ve iç widget'ların boşluklarını ayarlar. Bu örnekte düğmenin boşlukları ayarlanmaktadır.
Bu kütüphanede bir Window yapısı bulunur ve App adlı değişken en üst düzey pencereyi yönetir. Bu değişken kütüphane içinde önceden tanımlanmıştır. Dolayısıyla tk.App.Wait()'i sonlandıran tk.App.Destroy() fonksiyonu en üst düzey pencereyi kapatma görevini üstlenir.
Şimdi GitLab'deki _examples klasöründe bulunan birkaç örneğe bakalım.
SVG Dosyalarını İşleme
Aşağıda, bir SVG dosyasının bir etiket (label) widget'ında gösterilmesine ilişkin bir örnek yer almaktadır.
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}

Bu kütüphanede SVG'nin işlenme yöntemi şu şekildedir:
- SVG dosyasının içeriği bir dize olarak okunur (veya yukarıdaki örnekte olduğu gibi doğrudan eklenir).
- Bu içerik Data fonksiyonuna iletilerek seçenekleri içeren bir dizeye dönüştürülür (-data seçeneği).
- Dönüştürülen bayt değerleri NewPhoto fonksiyonuna aktarılır ve Tcl/Tk görüntüsünü temsil eden Img yapısının işaretçisi döndürülür.
- Image fonksiyonundan geçerken Img yapısının işaretçisi, -Image seçeneği eklenmiş bir dizeye dönüştürülür.
- Yapının RAW değerini içeren bir dizeye dönüştürülmesinin nedeni, Label widget'ının oluşturulmasıdır.
ICO ve PNG dosyaları da benzer bir yöntemle işlenir.
PNG Dosyalarını İşleme
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}

PNG dosyasının işlenme süreci şu şekildedir:
- Gömülü gopher.png, seçenekleri içeren dize türüne dönüştürülür.
- NewPhoto fonksiyonu aracılığıyla *Img türüne dönüştürülür.
- Image fonksiyonundan geçirildikten sonra RAW dizesine dönüştürülür ve ardından etiket widget'ı olarak oluşturulur.
ICO dosyası da aynı yöntemle işlenir; SVG formatıyla farkı yalnızca Data fonksiyonunun iç işleme yöntemindedir.
Burada "seçenekleri içeren dize"nin ne olduğunu inceleyelim:
1type rawOption string
Yukarıda sözü edilen seçenekleri içeren dize, basitçe biçimlendirilmiş bir dizedir.
1func (w *Window) optionString(_ *Window) string {
2 return w.String()
3}
optionString metodu, Window işaretçisi üzerindeki bir metottur ve bir dize döndürür.
Son olarak Data fonksiyonunun iç yapısına kısaca bakalım:
1func Data(val any) Opt {
2 switch x := val.(type) {
3 case []byte:
4 switch {
5 case bytes.HasPrefix(x, pngSig):
6 // tamam
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}
Koda bakıldığında, ICO veya PNG dosyaları için kodlama/çözme sürecinin gerekli olduğu görülmektedir. Diğer durumlarda ise özel bir dönüşüm yapılmadan, bayt türüne dönüştürülmüş dizeye yalnızca -data seçeneği eklenerek bunun Data fonksiyonunun çıktısı olduğu belirtilir.
Menü Widget'ı ile Görüntü Yükleme
Daha önce denediğimiz PNG ve ICO yükleme örneğine bir menü widget'ı eklenirse, istenen görüntüleri gösteren bir uygulama oluşturulabilir.
Öncelikle basit bir menü widget'ı örneğini inceleyelim.
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}

Bu örnekte menü çubuğu ve menü oluşturma, harf vurgulama, Command seçeneği, kısayol tuşu bağlama ve uygulama penceresinin başlangıç boyutunun ayarlanması denenmiştir.
Şimdi GetOpenFile ile belirtilen Command fonksiyonunu değiştirerek, görüntüleri yükleyip gösteren bir program oluşturalım.
Program yazma planı şu şekildedir:
- Yalnızca PNG ve ICO dosyalarının açılmasına izin verilmesi
- Dosya iletişim kutusunda seçilen dosyanın işlenmesi
- Görüntünün gösterilmesi için widget'ın uygulanması
Bu planı yansıtan kod aşağıdadır:
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}

Yukarıdaki kod şu şekilde çalışır:
- strings paketinin dize karşılaştırma fonksiyonuyla dosya uzantısı kontrol edilir
- os paketi kullanılarak dosya açılır, okunur ve kapatılır
- Okunan görüntü etiket widget'ı ile gösterilir
Sonuç
Bu yazıda Go dilinin Tcl/Tk kütüphanesinden yararlanarak şu konuları ele aldık:
- GUI uygulamasının temel yapısı
- SVG, PNG, ICO gibi çeşitli görüntü formatlarının işlenmesi
- Widget paketleme ve yerleşim yönetimi
- Görüntü verisi işleme yapısı
- Kısayol tuşu bağlama ve widget komutları
Go dili ile Tcl/Tk birlikte kullanıldığında basit ama kullanışlı GUI programları geliştirmek mümkündür. Bunun üzerine kurarak daha karmaşık GUI uygulamaları geliştirmeyi denemenizi öneririm.