Konfi-Castle-Kasino/vendor/github.com/asticode/go-astilectron/README.md
2017-08-31 22:50:01 +02:00

647 lines
22 KiB
Markdown

[![GoReportCard](http://goreportcard.com/badge/github.com/asticode/go-astilectron)](http://goreportcard.com/report/github.com/asticode/go-astilectron)
[![GoDoc](https://godoc.org/github.com/asticode/go-astilectron?status.svg)](https://godoc.org/github.com/asticode/go-astilectron)
[![GoCoverage](https://cover.run/go/github.com/asticode/go-astilectron.svg)](https://cover.run/go/github.com/asticode/go-astilectron)
[![Travis](https://travis-ci.org/asticode/go-astilectron.svg?branch=master)](https://travis-ci.org/asticode/go-astilectron#)
Thanks to `go-astilectron` build cross platform GUI apps with GO and HTML/JS/CSS. It is the official GO bindings of [astilectron](https://github.com/asticode/astilectron) and is powered by [Electron](https://github.com/electron/electron).
# Real-life examples
Here's a list of awesome projects using `go-astilectron` (if you're using `go-astilectron` and want your project to be listed here please submit a PR):
- [go-astivid](https://github.com/asticode/go-astivid) Video tools written in GO
- [GroupMatcher](https://github.com/veecue/GroupMatcher) Program to allocate persons to groups while trying to fulfill all the given wishes as good as possible
# Quick start
WARNING: the code below doesn't handle errors for readibility purposes. However you SHOULD!
### Import `go-astilectron`
To import `go-astilectron` run:
$ go get -u github.com/asticode/go-astilectron
### Start `go-astilectron`
```go
// Initialize astilectron
var a, _ = astilectron.New(astilectron.Options{
AppName: "<your app name>",
AppIconDefaultPath: "<your .png icon>",
AppIconDarwinPath: "<your .icns icon>",
BaseDirectoryPath: "<where you want the provisioner to install the dependencies>",
})
defer a.Close()
// Start astilectron
a.Start()
```
For everything to work properly we need to fetch 2 dependencies : [astilectron](https://github.com/asticode/astilectron) and [Electron](https://github.com/electron/electron). `.Start()` takes care of it by downloading the sources and setting them up properly.
In case you want to embed the sources in the binary to keep a unique binary you can use the **NewDisembedderProvisioner** function to get the proper **Provisioner** and attach it to `go-astilectron` with `.SetProvisioner(p Provisioner)`. Check out the [example](https://github.com/asticode/go-astilectron/tree/master/examples/5.single_binary_distribution/main.go) to see how to use it with [go-bindata](https://github.com/jteeuwen/go-bindata).
Beware when trying to add your own app icon as you'll need 2 icons : one compatible with MacOSX (.icns) and one compatible with the rest (.png for instance).
If no BaseDirectoryPath is provided, it defaults to the executable's directory path.
The majority of methods are synchrone which means that when executing them `go-astilectron` will block until it receives a specific Electron event or until the overall context is cancelled. This is the case of `.Start()` which will block until it receives the `app.event.ready` `astilectron` event or until the overall context is cancelled.
### Create a window
```go
// Create a new window
var w, _ = a.NewWindow("http://127.0.0.1:4000", &astilectron.WindowOptions{
Center: astilectron.PtrBool(true),
Height: astilectron.PtrInt(600),
Width: astilectron.PtrInt(600),
})
w.Create()
```
When creating a window you need to indicate a URL as well as options such as position, size, etc.
This is pretty straightforward except the `astilectron.Ptr*` methods so let me explain: GO doesn't do optional fields when json encoding unless you use pointers whereas Electron does handle optional fields. Therefore I added helper methods to convert int, bool and string into pointers and used pointers in structs sent to Electron.
### Add listeners
```go
// Add a listener on Astilectron
a.On(astilectron.EventNameAppCrash, func(e astilectron.Event) (deleteListener bool) {
astilog.Error("App has crashed")
return
})
// Add a listener on the window
w.On(astilectron.EventNameWindowEventResize, func(e astilectron.Event) (deleteListener bool) {
astilog.Info("Window resized")
return
})
```
Nothing much to say here either except that you can add listeners to Astilectron as well.
### Play with the window
```go
// Play with the window
w.Resize(200, 200)
time.Sleep(time.Second)
w.Maximize()
```
Check out the [Window doc](https://godoc.org/github.com/asticode/go-astilectron#Window) for a list of all exported methods
### Send messages between GO and your webserver
In your webserver add the following javascript to any of the pages you want to interact with:
```html
<script>
// This will wait for the astilectron namespace to be ready
document.addEventListener('astilectron-ready', function() {
// This will listen to messages sent by GO
astilectron.listen(function(message) {
// This will send a message back to GO
astilectron.send("I'm good bro")
});
})
</script>
```
In your GO app add the following:
```go
// Listen to messages sent by webserver
w.On(astilectron.EventNameWindowEventMessage, func(e astilectron.Event) (deleteListener bool) {
var m string
e.Message.Unmarshal(&m)
astilog.Infof("Received message %s", m)
return
})
// Send message to webserver
w.Send("What's up?")
```
And that's it!
NOTE: needless to say that the message can be something other than a string. A custom struct for instance!
### Handle several screens/displays
```go
// If several displays, move the window to the second display
var displays = a.Displays()
if len(displays) > 1 {
time.Sleep(time.Second)
w.MoveInDisplay(displays[1], 50, 50)
}
```
### Menus
```go
// Init a new app menu
// You can do the same thing with a window
var m = a.NewMenu([]*astilectron.MenuItemOptions{
{
Label: astilectron.PtrStr("Separator"),
SubMenu: []*astilectron.MenuItemOptions{
{Label: astilectron.PtrStr("Normal 1")},
{
Label: astilectron.PtrStr("Normal 2"),
OnClick: func(e astilectron.Event) (deleteListener bool) {
astilog.Info("Normal 2 item has been clicked")
return
},
},
{Type: astilectron.MenuItemTypeSeparator},
{Label: astilectron.PtrStr("Normal 3")},
},
},
{
Label: astilectron.PtrStr("Checkbox"),
SubMenu: []*astilectron.MenuItemOptions{
{Checked: astilectron.PtrBool(true), Label: astilectron.PtrStr("Checkbox 1"), Type: astilectron.MenuItemTypeCheckbox},
{Label: astilectron.PtrStr("Checkbox 2"), Type: astilectron.MenuItemTypeCheckbox},
{Label: astilectron.PtrStr("Checkbox 3"), Type: astilectron.MenuItemTypeCheckbox},
},
},
{
Label: astilectron.PtrStr("Radio"),
SubMenu: []*astilectron.MenuItemOptions{
{Checked: astilectron.PtrBool(true), Label: astilectron.PtrStr("Radio 1"), Type: astilectron.MenuItemTypeRadio},
{Label: astilectron.PtrStr("Radio 2"), Type: astilectron.MenuItemTypeRadio},
{Label: astilectron.PtrStr("Radio 3"), Type: astilectron.MenuItemTypeRadio},
},
},
{
Label: astilectron.PtrStr("Roles"),
SubMenu: []*astilectron.MenuItemOptions{
{Label: astilectron.PtrStr("Minimize"), Role: astilectron.MenuItemRoleMinimize},
{Label: astilectron.PtrStr("Close"), Role: astilectron.MenuItemRoleClose},
},
},
})
// Retrieve a menu item
// This will retrieve the "Checkbox 1" item
mi, _ := m.Item(1, 0)
// Add listener manually
// An OnClick listener has already been added in the options directly for another menu item
mi.On(astilectron.EventNameMenuItemEventClicked, func(e astilectron.Event) bool {
astilog.Infof("Menu item has been clicked. 'Checked' status is now %t", *e.MenuItemOptions.Checked)
return false
})
// Create the menu
m.Create()
// Manipulate a menu item
mi.SetChecked(true)
// Init a new menu item
var ni = m.NewItem(&astilectron.MenuItemOptions{
Label: astilectron.PtrStr("Inserted"),
SubMenu: []*astilectron.MenuItemOptions{
{Label: astilectron.PtrStr("Inserted 1")},
{Label: astilectron.PtrStr("Inserted 2")},
},
})
// Insert the menu item at position "1"
m.Insert(1, ni)
// Fetch a sub menu
s, _ := m.SubMenu(0)
// Init a new menu item
ni = s.NewItem(&astilectron.MenuItemOptions{
Label: astilectron.PtrStr("Appended"),
SubMenu: []*astilectron.MenuItemOptions{
{Label: astilectron.PtrStr("Appended 1")},
{Label: astilectron.PtrStr("Appended 2")},
},
})
// Append menu item dynamically
s.Append(ni)
// Pop up sub menu as a context menu
s.Popup(&astilectron.MenuPopupOptions{PositionOptions: astilectron.PositionOptions{X: astilectron.PtrInt(50), Y: astilectron.PtrInt(50)}})
// Close popup
s.ClosePopup()
// Destroy the menu
m.Destroy()
```
A few things to know:
* when assigning a role to a menu item, `go-astilectron` won't be able to capture its click event
* on MacOS there's no such thing as a window menu, only app menus therefore my advice is to stick to one global app menu instead of creating separate window menus
### Tray
```go
// New tray
var t = a.NewTray(&astilectron.TrayOptions{
Image: astilectron.PtrStr("/path/to/image.png"),
Tooltip: astilectron.PtrStr("Tray's tooltip"),
})
// New tray menu
var m = t.NewMenu([]*astilectron.MenuItemOptions{
{
Label: astilectron.PtrStr("Root 1"),
SubMenu: []*astilectron.MenuItemOptions{
{Label: astilectron.PtrStr("Item 1")},
{Label: astilectron.PtrStr("Item 2")},
{Type: astilectron.MenuItemTypeSeparator},
{Label: astilectron.PtrStr("Item 3")},
},
},
{
Label: astilectron.PtrStr("Root 2"),
SubMenu: []*astilectron.MenuItemOptions{
{Label: astilectron.PtrStr("Item 1")},
{Label: astilectron.PtrStr("Item 2")},
},
},
})
// Create the menu
m.Create()
// Create tray
t.Create()
```
### Dialogs
In your webserver add one of the following javascript to achieve any kind of dialog.
#### Error box
```html
<script>
// This will wait for the astilectron namespace to be ready
document.addEventListener('astilectron-ready', function() {
// This will open the dialog
astilectron.showErrorBox("My Title", "My content")
})
</script>
```
#### Message box
```html
<script>
// This will wait for the astilectron namespace to be ready
document.addEventListener('astilectron-ready', function() {
// This will open the dialog
astilectron.showMessageBox({message: "My message", title: "My Title"})
})
</script>
```
#### Open dialog
```html
<script>
// This will wait for the astilectron namespace to be ready
document.addEventListener('astilectron-ready', function() {
// This will open the dialog
astilectron.showOpenDialog({properties: ['openFile', 'multiSelections'], title: "My Title"}, function(paths) {
console.log("chosen paths are ", paths)
})
})
</script>
```
#### Save dialog
```html
<script>
// This will wait for the astilectron namespace to be ready
document.addEventListener('astilectron-ready', function() {
// This will open the dialog
astilectron.showSaveDialog({title: "My title"}, function(filename) {
console.log("chosen filename is ", filename)
})
})
</script>
```
### Final code
```go
// Set up the logger
var l <your logger type>
astilog.SetLogger(l)
// Start an http server
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello world</title>
</head>
<body>
<span id="message">Hello world</span>
<script>
// This will wait for the astilectron namespace to be ready
document.addEventListener('astilectron-ready', function() {
// This will listen to messages sent by GO
astilectron.listen(function(message) {
document.getElementById('message').innerHTML = message
// This will send a message back to GO
astilectron.send("I'm good bro")
});
})
</script>
</body>
</html>`))
})
go http.ListenAndServe("127.0.0.1:4000", nil)
// Initialize astilectron
var a, _ = astilectron.New(astilectron.Options{
AppName: "<your app name>",
AppIconDefaultPath: "<your .png icon>",
AppIconDarwinPath: "<your .icns icon>",
BaseDirectoryPath: "<where you want the provisioner to install the dependencies>",
})
defer a.Close()
// Handle quit
a.HandleSignals()
a.On(astilectron.EventNameAppCrash, func(e astilectron.Event) (deleteListener bool) {
astilog.Error("App has crashed")
return
})
// Start astilectron: this will download and set up the dependencies, and start the Electron app
a.Start()
// Init a new app menu
// You can do the same thing with a window
var m = a.NewMenu([]*astilectron.MenuItemOptions{
{
Label: astilectron.PtrStr("Separator"),
SubMenu: []*astilectron.MenuItemOptions{
{Label: astilectron.PtrStr("Normal 1")},
{
Label: astilectron.PtrStr("Normal 2"),
OnClick: func(e astilectron.Event) (deleteListener bool) {
astilog.Info("Normal 2 item has been clicked")
return
},
},
{Type: astilectron.MenuItemTypeSeparator},
{Label: astilectron.PtrStr("Normal 3")},
},
},
{
Label: astilectron.PtrStr("Checkbox"),
SubMenu: []*astilectron.MenuItemOptions{
{Checked: astilectron.PtrBool(true), Label: astilectron.PtrStr("Checkbox 1"), Type: astilectron.MenuItemTypeCheckbox},
{Label: astilectron.PtrStr("Checkbox 2"), Type: astilectron.MenuItemTypeCheckbox},
{Label: astilectron.PtrStr("Checkbox 3"), Type: astilectron.MenuItemTypeCheckbox},
},
},
{
Label: astilectron.PtrStr("Radio"),
SubMenu: []*astilectron.MenuItemOptions{
{Checked: astilectron.PtrBool(true), Label: astilectron.PtrStr("Radio 1"), Type: astilectron.MenuItemTypeRadio},
{Label: astilectron.PtrStr("Radio 2"), Type: astilectron.MenuItemTypeRadio},
{Label: astilectron.PtrStr("Radio 3"), Type: astilectron.MenuItemTypeRadio},
},
},
{
Label: astilectron.PtrStr("Roles"),
SubMenu: []*astilectron.MenuItemOptions{
{Label: astilectron.PtrStr("Minimize"), Role: astilectron.MenuItemRoleMinimize},
{Label: astilectron.PtrStr("Close"), Role: astilectron.MenuItemRoleClose},
},
},
})
// Retrieve a menu item
// This will retrieve the "Checkbox 1" item
mi, _ := m.Item(1, 0)
// Add listener manually
// An OnClick listener has already been added in the options directly for another menu item
mi.On(astilectron.EventNameMenuItemEventClicked, func(e astilectron.Event) bool {
astilog.Infof("Menu item has been clicked. 'Checked' status is now %t", *e.MenuItemOptions.Checked)
return false
})
// Create the menu
m.Create()
// Create a new window with a listener on resize
var w, _ = a.NewWindow("http://127.0.0.1:4000", &astilectron.WindowOptions{
Center: astilectron.PtrBool(true),
Height: astilectron.PtrInt(600),
Icon: astilectron.PtrStr(<your icon path>),
Width: astilectron.PtrInt(600),
})
w.On(astilectron.EventNameWindowEventResize, func(e astilectron.Event) (deleteListener bool) {
astilog.Info("Window resized")
return
})
w.On(astilectron.EventNameWindowEventMessage, func(e astilectron.Event) (deleteListener bool) {
var m string
e.Message.Unmarshal(&m)
astilog.Infof("Received message %s", m)
return
})
w.Create()
// Play with the window
w.Resize(200, 200)
time.Sleep(time.Second)
w.Maximize()
// If several displays, move the window to the second display
var displays = a.Displays()
if len(displays) > 1 {
time.Sleep(time.Second)
w.MoveInDisplay(displays[1], 50, 50)
}
// Send a message to the server
time.Sleep(time.Second)
w.Send("What's up?")
// Manipulate a menu item
time.Sleep(time.Second)
mi.SetChecked(true)
// Init a new menu item
var ni = m.NewItem(&astilectron.MenuItemOptions{
Label: astilectron.PtrStr("Inserted"),
SubMenu: []*astilectron.MenuItemOptions{
{Label: astilectron.PtrStr("Inserted 1")},
{Label: astilectron.PtrStr("Inserted 2")},
},
})
// Insert the menu item at position "1"
time.Sleep(time.Second)
m.Insert(1, ni)
// Fetch a sub menu
s, _ := m.SubMenu(0)
// Init a new menu item
ni = s.NewItem(&astilectron.MenuItemOptions{
Label: astilectron.PtrStr("Appended"),
SubMenu: []*astilectron.MenuItemOptions{
{Label: astilectron.PtrStr("Appended 1")},
{Label: astilectron.PtrStr("Appended 2")},
},
})
// Append menu item dynamically
time.Sleep(time.Second)
s.Append(ni)
// Pop up sub menu as a context menu
time.Sleep(time.Second)
s.Popup(&astilectron.MenuPopupOptions{PositionOptions: astilectron.PositionOptions{X: astilectron.PtrInt(50), Y: astilectron.PtrInt(50)}})
// Close popup
time.Sleep(time.Second)
s.ClosePopup()
// Destroy the menu
time.Sleep(time.Second)
m.Destroy()
// Blocking pattern
a.Wait()
```
# Bootstrap
For convenience purposes I've added a **bootstrap** to help first timers and avoid code duplications.
NOTE: you DON'T have to use the bootstrap, it's entirely up to you whether to use it or not.
The bootstrap allows you to quickly create a one-window application.
## Using static files and remote messaging (the best way)
In order to use the **bootstrap** with static files and remote messaging you must:
- follow the following project organization:
|--+ resources
|
|--+ app (contains your static files such as .html, .css, .js, .png, etc.)
|--+ main.go
- use the `MessageHandler` **bootstrap** option in order to handle remote messaging
- use `remote messaging` in your static files
## Using a web server
In order to use the **bootstrap** with a web server you must:
- follow the following project organization:
|--+ resources
|
|--+ static (contains your static files such as .css, .js, .png, etc.)
|
|--+ templates (contains your templates .html files)
|--+ main.go
- use the `AdaptRouter` and `TemplateData` **bootstrap** options in order to handle the server routes
## Common
- if you're using the `RestoreAssets` **bootstrap** option, add the following comment on top of your `main()` method:
//go:generate go-bindata -pkg $GOPACKAGE -o resources.go resources/...
and run the following command before building your binary:
$ go generate main.go
- use the `bootstrap.Run()` method
Check out the [example](https://github.com/asticode/go-astilectron/tree/master/examples/9.bootstrap) for a detailed working example (see the **Examples** section below for the specific commands to run).
# I want to see it in actions!
To make things clearer I've tried to split features in different [examples](https://github.com/asticode/go-astilectron/tree/master/examples).
To run any of the examples, run the following commands:
$ go run examples/<name of the example>/main.go -v
Here's a list of the examples:
- [1.basic_window](https://github.com/asticode/go-astilectron/tree/master/examples/1.basic_window/main.go) creates a basic window that displays a static .html file
- [2.basic_window_events](https://github.com/asticode/go-astilectron/tree/master/examples/2.basic_window_events/main.go) plays with basic window methods and shows you how to set up your own listeners
- [3.webserver_app](https://github.com/asticode/go-astilectron/tree/master/examples/3.webserver_app/main.go) sets up a basic webserver app
- [4.remote_messaging](https://github.com/asticode/go-astilectron/tree/master/examples/4.remote_messaging/main.go) sends a message to the webserver and listens for any response
- [5.single_binary_distribution](https://github.com/asticode/go-astilectron/tree/master/examples/5.single_binary_distribution/main.go) shows how to use `go-astilectron` in a unique binary. For this example you have to run one of the previous examples (so that the .zip files exist) and run the following commands:
```
$ go generate examples/5.single_binary_distribution/main.go
$ go run examples/5.single_binary_distribution/main.go examples/5.single_binary_distribution/vendor.go -v
```
- [6.screens_and_displays](https://github.com/asticode/go-astilectron/tree/master/examples/6.screens_and_displays/main.go) plays around with screens and displays
- [7.menus](https://github.com/asticode/go-astilectron/tree/master/examples/7.menus/main.go) creates and manipulates menus
- [8.bootstrap](https://github.com/asticode/go-astilectron/tree/master/examples/8.bootstrap) shows how to use the **bootstrap**. For this example you have to run the following commands:
```
$ go generate examples/8.bootstrap/main.go
$ go run examples/8.bootstrap/main.go examples/8.bootstrap/resources.go -v
```
- [9.tray](https://github.com/asticode/go-astilectron/tree/master/examples/9.tray/main.go) creates a tray
# Features and roadmap
- [x] custom branding (custom app name, app icon, etc.)
- [x] window basic methods (create, show, close, resize, minimize, maximize, ...)
- [x] window basic events (close, blur, focus, unresponsive, crashed, ...)
- [x] remote messaging (messages between GO and the JS in the webserver)
- [x] single binary distribution
- [x] multi screens/displays
- [x] menu methods and events (create, insert, append, popup, clicked, ...)
- [x] bootstrap
- [x] dialogs (open or save file, alerts, ...)
- [x] tray
- [ ] loader
- [ ] bundle helper
- [ ] accelerators (shortcuts)
- [ ] file methods (drag & drop, ...)
- [ ] clipboard methods
- [ ] power monitor events (suspend, resume, ...)
- [ ] notifications (macosx)
- [ ] desktop capturer (audio and video)
- [ ] session methods
- [ ] session events
- [ ] window advanced options (add missing ones)
- [ ] window advanced methods (add missing ones)
- [ ] window advanced events (add missing ones)
- [ ] child windows
# Cheers to
[go-thrust](https://github.com/miketheprogrammer/go-thrust) which is awesome but unfortunately not maintained anymore. It inspired this project.