Communicating with the GUI

Now that the basic layout is coded, we will add functionality to present some data from a mock email server. As with the Walk example, we will load the model definitions and a test email server from the github.com/PacktPublishing/Hands-On-GUI-Application-Development-in-Go/client package.

First of all, let's write the code to load content from our model into the user interface. We will create a SetEmail(EmailMessage) function that sets the content of an email into the user interface. To help with converting from client.Email and time.Time to string, we will use the helper ToEmailString() and DateString() functions. This function will be called during interface load and also whenever we change the selected email:

func (m *mainUI) setEmail(e *client.EmailMessage) {
m.subject.SetText(e.Subject)
m.to.SetText(e.ToEmailString())
m.from.SetText(e.FromEmailString())
m.date.SetText(e.DateString())
m.content.SetText(e.Content)
}

Next, we should update the email list. Instead of two dummy emails in the list, we create a new method that will iterate over all emails and add an item for each. To be able to set the email content when clicked, we have to move from ui.Label to ui.Button (no other andlabs UI standard controls have an OnClicked callback). As you can see, we set a new function for each button added, which sets the displayed email by calling the setEmail() function. The captured variable is required to avoid the for loop's re-definition of email in each iteration:

func (m *mainUI) listEmails(list []*client.EmailMessage) {
for _, email := range list {
item := ui.NewButton(email.Subject)
captured := email
item.OnClicked(func(*ui.Button) {
m.SetEmail(captured)
})
m.list.Append(item, false)
}
}

To invoke these new functions on load, we need to update the main() method. First, a new server is created with client.NewTestServer(), and then the functions we wrote are invoked with the appropriate information from the server:

func main() {
server := client.NewTestServer()
err := ui.Main(func() {
main := new(mainUI)
window := main.buildUI()

main.listEmails(server.ListMessages())
main.setEmail(server.CurrentMessage())
window.Show()
})
if err != nil {
panic(err)
}
}

The last step for the main view is to open the compose window when the user clicks on the New button. This is easily accomplished with another OnClicked handler, which builds and shows the secondary ui.Window:

compose := ui.NewButton("New")
compose.OnClicked(func(*ui.Button) {
compose := &composeUI{}
compose.buildUI().Show()
})

Before we can send an email, we need to construct one from the controls in the compose user interface. This new CreateMessage() function simply gathers the information entered by the user and encapsulates it in a new client.EmailMessage that's ready for sending:

func (c *composeUI) createMessage() *client.EmailMessage {
email := &client.EmailMessage{}

email.Subject = c.subject.Text()
email.To = client.Email(c.to.Text())
email.Content = c.content.Text()
email.Date = time.Now()

return email
}

Lastly, we want the Cancel and Send buttons to function as expected. Both should close the compose window, but the Send button should first attempt to send the email. We add simple OnClicked handlers for these buttons, attached to the buttons which are appended to the buttonBox already created in the UI code:

cancel := ui.NewButton("Cancel")
cancel.OnClicked(func(*ui.Button) {
window.Hide()
})
buttonBox.Append(cancel, false)
send := ui.NewButton("Send")
send.OnClicked(func(*ui.Button) {
email := c.createMessage()
c.server.Send(email)

window.Hide()
})
buttonBox.Append(send, false)

Once all of this code is put together, you can run it, and should see an application that looks something like these screenshots:

  • The GoMail interface with test data loaded running on Linux:
  • The same interface with a different theme (Minwaita):

  • The completed GoMail interface running on macOS:
  • Running on macOS dark mode:
  • The GoMail interface running on windows 10: