前にGo+QtでGUIアプリの検討をしましたが、学習コストと出来るアプリの形式(ファイル数や容量)を考えて普段使いには微妙だなと思いました。
Golangの知識のみで作れるようなものを探してみます。
使用ライブラリ
GolangでGUIを扱うライブラリを探してみる。
- ★7.2k github.com/andlabs/ui
- ★6.9k github.com/fyne-io/fyne
- ★3.9k github.com/lxn/walk
andlabs/uiの人気が高いですが1年以上前に更新が止まっているためfyneを使います。
インストールと動作確認
インストールとテストビルド。Windowsなので環境変数はちょっと変えてます。
1 2 3 4 |
go get fyne.io/fyne cd %GOPATH%/src/fyne.io/fyne/cmd/fyne_demo/ go build fyne_demo |
公式ページのウィジェット一覧やアプリ一覧を見て、気になるところをリポジトリソースやGoDocで確認するのがいいかと思います。
- 公式のウィジェット一覧
- 公式のサンプルアプリ一覧とそのリポジトリ
- 動作を一通り網羅しているデモアプリのソース
- FynoのGodoc
個人的にはデモを見てソース探るのがわかりやすかった。
コーディング
適当なタイマーアプリを作ってみます。
音を鳴らすのにはfaiface/beepを使わせてもらいました。
サンプル切り貼りしてそれっぽくしているだけなので意味のない部分もあります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
package main import ( "fmt" "os" "strconv" "time" "github.com/faiface/beep" "github.com/faiface/beep/mp3" "github.com/faiface/beep/speaker" "fyne.io/fyne" "fyne.io/fyne/app" "fyne.io/fyne/layout" "fyne.io/fyne/theme" "fyne.io/fyne/widget" ) var mp3name = "coin01.mp3" func doBeep(path string) error { if path == "" { path = mp3name } f, _ := os.Open(path) s, format, err := mp3.Decode(f) if err != nil { return err } done := make(chan struct{}) err = speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/10)) if err != nil { return err } speaker.Play(beep.Seq(s, beep.Callback(func() { close(done) }))) <-done return nil } //Dummy func makeMenu() *fyne.MainMenu { var menuMain = fyne.NewMenu("File", fyne.NewMenuItem("New", func() { fmt.Println("Menu New") })) var menuSub1 = fyne.NewMenu("Edit", fyne.NewMenuItem("Cut", func() { fmt.Println("Menu Cut") }), fyne.NewMenuItem("Copy", func() { fmt.Println("Menu Copy") }), fyne.NewMenuItem("Paste", func() { fmt.Println("Menu Paste") }), ) var menu = fyne.NewMainMenu(menuMain, menuSub1) return menu } //Timer func timer(sec int64, lbl *widget.Label, prg *widget.ProgressBar, stopCh chan struct{}) { prg.SetValue(0) var s int64 st := time.NewTicker(time.Duration(1) * time.Second) defer st.Stop() for { select { case <-stopCh: return case <-st.C: s++ lbl.SetText(fmt.Sprintf("%d : %02d : %02d", s/3600, (s%3600)/60, s%60)) prg.SetValue(float64(s) / float64(sec)) if s > sec { doBeep("") } } } } //Screen func makeWelcome() *widget.Box { var stopCh chan struct{} //Labels and Progress lbl1 := widget.NewLabelWithStyle("0 : 00 : 00", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) lbl2 := widget.NewLabelWithStyle("0 : 03 : 00", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) prg := widget.NewProgressBar() //Set Timer Area var sec int64 = 180 e1, e2, e3 := widget.NewEntry(), widget.NewEntry(), widget.NewEntry() e1.SetText("0") e2.SetText("03") e3.SetText("00") eb := widget.NewButton("Set", func() { t1, _ := strconv.ParseInt(e1.Text, 10, 64) t2, _ := strconv.ParseInt(e2.Text, 10, 64) t3, _ := strconv.ParseInt(e3.Text, 10, 64) sec = t1*3600 + t2*60 + t3 lbl2.SetText(fmt.Sprintf("%d : %02d : %02d", t1, t2, t3)) }) timerSetArea := fyne.NewContainerWithLayout(layout.NewCenterLayout(), widget.NewHBox(e1, e2, e3, eb)) //Buttons btnStart := widget.NewButton("Start", func() { if stopCh != nil { close(stopCh) } stopCh = make(chan struct{}) go timer(sec, lbl1, prg, stopCh) }) btnStop := widget.NewButton("Stop", func() { if stopCh != nil { close(stopCh) } stopCh = make(chan struct{}) }) btn := fyne.NewContainerWithLayout(layout.NewGridLayout(2), btnStart, btnStop) return widget.NewVBox(timerSetArea, btn, lbl1, lbl2, prg) } func main() { app := app.New() w := app.NewWindow("Timer") //w.SetTitle("TitleSet") w.SetMainMenu(makeMenu()) tabs := widget.NewTabContainer( widget.NewTabItemWithIcon("Welcome", theme.HomeIcon(), makeWelcome()), widget.NewTabItemWithIcon("Setting", theme.SettingsIcon(), widget.NewVBox())) //Dummy //tabs.SetTabLocation(widget.TabLocationLeading) //Vertical tab menu w.SetContent(tabs) w.ShowAndRun() } |
ビルド
適当に書いたらWindows向けにビルドしてみます。
そのままだとターミナルとGUIが表示されるためオプションでターミナル非表示に。
1 |
go build -ldflags="-H windowsgui" -o out.exe main.go |
リンカフラグに関しては以下参照。
シングルバイナリで15MB程度です。
所感
ライブラリの出来が良くてかなり簡単にGo製GUIアプリが作れました。
直感的に分かりやすい構造ですが情報が少ないので微調整で難航しそう。
今回の方法のメリットとしてはGolangでGUIが作れるという点くらいかな。
そもそもGolangがGUI向けではないのもあり使いたくなる機会は少ない気がする。