Introduction to the

In the Go language ecosystem, GUIs have always been the weak boards, let alone cross-platform GUIs. Fyne took a big step forward. Fyne is a cross-platform UI library written in Go that can be easily ported to mobile devices. Fyne is very simple to use, and it also provides Fyne commands to package static resources and applications. Let’s start with a brief introduction to the basic controls and layout, and then show you how to publish a Fyne application.

Quick to use

The code in this article uses Go Modules.

First initialize:

$ mkdir fyne && cd fyne
$ go mod init github.com/darjun/go-daily-lib/fyne

Because Fyne contains some C/C++ code, you need a GCC compiler. On Linux/Mac OSX, GCC is pretty much standard. On Windows, we have three ways to install the GCC toolchain:

  • MSYS2 + MingW-W64: https://www.msys2.org/;
  • TDM – GCC: https://jmeubank.github.io/tdm-gcc/download/;
  • Cygwin:https://www.cygwin.com/.

In this paper, TDM-GCC is chosen as the installation method. To https://jmeubank.github.io/tdm-gcc/download/ to download the installer and install. Under normal circumstances, the installer automatically sets the PATH PATH. Open the command line and type gcc-v. If the version information is printed normally, the installation is successful and the environment variables are set correctly.

Install fyne:

$ go get -u fyne.io/fyne

At this point, the preparation is complete and we start coding. By convention, we start with the Hello, World program:

package main import ( "fyne.io/fyne" "fyne.io/fyne/app" "fyne.io/fyne/widget" ) func main() { myApp := app.New() myWin := myApp.NewWindow("Hello") myWin.SetContent(widget.NewLabel("Hello Fyne!" )) myWin.Resize(fyne.NewSize(200, 200)) myWin.ShowAndRun() }

The running results are as follows:

Fyne is easy to use. Each Fyne program consists of two parts. One is the application object myApp, created through app.new (). The other is a window object that creates myApp.newWindow (“Hello”) from the application object myApp. The string passed in to the myApp.newWindow () method is the window title.

Fyne provides a number of commonly used components, created through widget.newXXX () (XXX is the component name). In the example above, we created a Label control and then set it to a window. Finally, call mywin.showAndRun () to start running the program. Actually mywin.showAndRun () is equivalent to

myWin.Show()
myApp.Run()

Mywin.show () displays the window and myapp.run () starts the event loop.

Note that the Fyne default window size is set according to the width and height of the content. Above we set the size manually by calling mywin.resize (). Otherwise the window can only drop the string Hello Fyne! .

fynePacket partition

Fyne divides functionality into multiple subpackages:

  • fyne.io/fyne: Provide allfyneCommon basic definitions of application code, including data types and interfaces;
  • fyne.io/fyne/app: Provides APIs for creating applications;
  • fyne.io/fyne/canvas: provideFyneThe drawing API used;
  • fyne.io/fyne/dialog: Provides dialog box components;
  • fyne.io/fyne/layout: Provide a variety of interface layout;
  • fyne.io/fyne/widget: provides a variety of components,fyneAll form controls and interaction elements are contained in this subpackage.

Canvas

In the Fyne application, all display elements are drawn on the Canvas. These elements are CanvasObjects. To set the Canvas content, call the Canvas.SetContent() method. Canvas is usually used with a Layout Container. The Canvas child package provides some basic canvas objects:

package main

import (
  "image/color"
  "math/rand"

  "fyne.io/fyne"
  "fyne.io/fyne/app"
  "fyne.io/fyne/canvas"
  "fyne.io/fyne/layout"
  "fyne.io/fyne/theme"
)

func main() {
  a := app.New()
  w := a.NewWindow("Canvas")

  rect := canvas.NewRectangle(color.White)

  text := canvas.NewText("Hello Text", color.White)
  text.Alignment = fyne.TextAlignTrailing
  text.TextStyle = fyne.TextStyle{Italic: true}

  line := canvas.NewLine(color.White)
  line.StrokeWidth = 5

  circle := canvas.NewCircle(color.White)
  circle.StrokeColor = color.Gray{0x99}
  circle.StrokeWidth = 5

  image := canvas.NewImageFromResource(theme.FyneLogo())
  image.FillMode = canvas.ImageFillOriginal

  raster := canvas.NewRasterWithPixels(
    func(_, _, w, h int) color.Color {
      return color.RGBA{uint8(rand.Intn(255)),
        uint8(rand.Intn(255)),
        uint8(rand.Intn(255)), 0xff}
    },
  )

  gradient := canvas.NewHorizontalGradient(color.White, color.Transparent)

  container := fyne.NewContainerWithLayout(
    layout.NewGridWrapLayout(fyne.NewSize(150, 150)),
    rect, text, line, circle, image, raster, gradient))
  w.SetContent(container)
  w.ShowAndRun()
}

The program runs as follows:

Canvas.Rectangle is the simplest Canvas object. Create it with Canvas.NewRectangle() and pass it a fill color.

Canvas.Text is a canvas object that displays Text, created with Canvas.NewText(), passing in a Text string and color. This object allows you to set alignment and font styles. The Alignment field of the Text object is set to the following values:

  • TextAlignLeading: Left align;
  • TextAlignCenter: middle alignment;
  • TextAlignTrailing: Right Align.

Font Styles By setting the value of the TextStyle field of the Text object, TextStyle is a structure:

type TextStyle struct {
  Bold      bool
  Italic    bool
  Monospace bool
}

Setting the corresponding field to true will display the corresponding style:

  • Bold: bold;
  • Italic: italic;
  • Monospace: System constant width font.

We can also use external fonts by setting the environment variable FYNE_FONT to a.ttf file.

Canvas.Line is a Line segment, created with Canvas.NewLine() and passed in a color. You can set the width of the line segment with line.strokeWidth. By default, the line segment goes from the upper left corner of the parent control or the canvas to the lower right corner. You can modify the location using line.move () and line.resize ().

Canvas.Circle is a Circle, created with Canvas.NewCircle() and passed in a color. Also use strokeColor and strokeWidth to set the color and width of the circular border.

Canvas. The Image is the Image that can be loaded in the application of resources to create (canvas. NewImageFromResource ()), the incoming resource objects. Or create a file path (Canvas.NewImageFromFile()) and pass in the file path. Or from the constructed Image.Image object (Canvas.NewImageFromImage()). You can set the filling mode of the image by FillMode:

  • ImageFillStretch: Stretch to fill the space;
  • ImageFillContain: Maintain the width to height ratio;
  • ImageFillOriginal: Keep the original size, do not zoom.

The following program demonstrates these three ways to create images:

package main

import (
  "image"
  "image/color"

  "fyne.io/fyne"
  "fyne.io/fyne/app"
  "fyne.io/fyne/canvas"
  "fyne.io/fyne/layout"
  "fyne.io/fyne/theme"
)

func main() {
  a := app.New()
  w := a.NewWindow("Hello")

  img1 := canvas.NewImageFromResource(theme.FyneLogo())
  img1.FillMode = canvas.ImageFillOriginal

  img2 := canvas.NewImageFromFile("./luffy.jpg")
  img2.FillMode = canvas.ImageFillOriginal

  image := image.NewAlpha(image.Rectangle{image.Point{0, 0}, image.Point{100, 100}})
  for i := 0; i < 100; i++ {
    for j := 0; j < 100; j++ {
      image.Set(i, j, color.Alpha{uint8(i % 256)})
    }
  }
  img3 := canvas.NewImageFromImage(image)
  img3.FillMode = canvas.ImageFillOriginal

  container := fyne.NewContainerWithLayout(
    layout.NewGridWrapLayout(fyne.NewSize(150, 150)),
    img1, img2, img3)
  w.SetContent(container)
  w.ShowAndRun()
}

Theme.fynelogo () is the Fyne icon resource, luffy.jpg is the file on disk, and finally create an image.image from which to generate Canvas.Image.

The last one is the gradient effect. There are two types, Canvas.LinearGradient and Canvas.RadialGradient, which are gradients that change from one color to another. Linear gradient is divided into two kinds of vertical and horizontal linear gradient linear gradient, respectively through the canvas. NewHorizontalGradient () and canvas. NewVerticalGradient () to create. The radialgradient is created using Canvas.NewRadialGradient(). We have seen the horizontal linear gradient in the above example, let’s take a look at the radial gradient:

func main() {
  a := app.New()
  w := a.NewWindow("Canvas")

  gradient := canvas.NewRadialGradient(color.White, color.Transparent)
  w.SetContent(gradient)
  w.Resize(fyne.NewSize(200, 200))
  w.ShowAndRun()
}

The running effect is as follows:

The radiation effect is gradual from the center to the periphery.

Widget

Forms controls are an integral part of a Fyne application. They adapt to the current topic and handle the interaction with the user.

Label

The Label is the simplest control for displaying strings. It is somewhat similar to Canvas.Text, except that Label can handle simple formatting, such as \n:

func main() {
  myApp := app.New()
  myWin := myApp.NewWindow("Label")

  l1 := widget.NewLabel("Name")
  l2 := widget.NewLabel("da\njun")

  container := fyne.NewContainerWithLayout(layout.NewVBoxLayout(), l1, l2)
  myWin.SetContent(container)
  myWin.Resize(fyne.NewSize(150, 150))
  myWin.ShowAndRun()
}

Anything after \n in the second widget.Label is rendered on the next line:

Button

The Button control lets the user click and gives the user feedback. A Button can contain text, an icon, or both. Call Widget.newButton () to create a default text button, pass in the text, and a callback function with no arguments. Buttons with ICONS need to call Widget.newButtonWithIcon (), pass in text and callback parameters, and also need an icon Resource of type Fyne.resource:

func main() {
  myApp := app.New()
  myWin := myApp.NewWindow("Button")

  btn1 := widget.NewButton("text button", func() {
    fmt.Println("text button clicked")
  })

  btn2 := widget.NewButtonWithIcon("icon", theme.HomeIcon(), func() {
    fmt.Println("icon button clicked")
  })

  container := fyne.NewContainerWithLayout(layout.NewVBoxLayout(), btn1, btn2)
  myWin.SetContent(container)
  myWin.Resize(fyne.NewSize(150, 50))
  myWin.ShowAndRun()
}

This creates a text button and an icon button. The Theme bundle contains some default icon resources. External ICONS can also be loaded. Run:

Click the button and the corresponding callback will be invoked. Try it!

Box

A Box control is simply a horizontal or vertical container. Internally, Box uses Box Layout for its child controls, as detailed in Layout below. You can create a box for either Widget.newhBox () or Widget.newvBox () by passing in the control object. Or you can add controls to the Box by calling the Append() or Prepend() of the already created Widget.Box object. The former is appended to the tail and the latter to the head.

func main() { myApp := app.New() myWin := myApp.NewWindow("Box") content := widget.NewVBox( widget.NewLabel("The top row  of VBox"), widget.NewHBox( widget.NewLabel("Label 1"), widget.NewLabel("Label 2"), ), ) content.Append(widget.NewButton("Append", func() { content.Append(widget.NewLabel("Appended")) })) content.Append(widget.NewButton("Prepend", func() { content.Prepend(widget.NewLabel("Prepended")) })) myWin.SetContent(content) myWin.Resize(fyne.NewSize(150, 150)) myWin.ShowAndRun() }

We can even nest the Widget.Box controls for a more flexible layout. The above code adds two buttons that, when clicked, add a Label to the tail and a Label to the head:

Entry

The Entry control is used to enter simple text to the user. To create an input box control, call Widget.newEntry (). We typically save a reference to an input field control so that we can access its Text field to get the content. Register the onChanged callback function. OnChanged is called whenever the content is modified. We can set the read-only property of the input field by calling setReadOnly (true). The setPlaceHolder () method sets the placeholder string and sets the field Multiline so that the input field accepts multiple lines of text. Alternatively, we can use newPasswordEntry () to create a password entry field where the text is not displayed in clear text.

func main() {
  myApp := app.New()
  myWin := myApp.NewWindow("Entry")

  nameEntry := widget.NewEntry()
  nameEntry.SetPlaceHolder("input name")
  nameEntry.OnChanged = func(content string) {
    fmt.Println("name:", nameEntry.Text, "entered")
  }

  passEntry := widget.NewPasswordEntry()
  passEntry.SetPlaceHolder("input password")

  nameBox := widget.NewHBox(widget.NewLabel("Name"), layout.NewSpacer(), nameEntry)
  passwordBox := widget.NewHBox(widget.NewLabel("Password"), layout.NewSpacer(), passEntry)

  loginBtn := widget.NewButton("Login", func() {
    fmt.Println("name:", nameEntry.Text, "password:", passEntry.Text, "login in")
  })

  multiEntry := widget.NewEntry()
  multiEntry.SetPlaceHolder("please enter\nyour description")
  multiEntry.MultiLine = true

  content := widget.NewVBox(nameBox, passwordBox, loginBtn, multiEntry)
  myWin.SetContent(content)
  myWin.ShowAndRun()
}

Here we have implemented a simple login interface:

Checkbox/Radio/Select

A CheckBox is a simple CheckBox that allows you to select individual hobbies, such as football, basketball, or both. Create the method Widget.newCheck (), passing in the option string (football, basketball) and the callback function. The callback takes an argument of type bool to indicate whether the option is selected.

Radio is a checkbox, only one can be selected from each group, e.g. gender, can only be male or female (?) . Create the method Widget.newRadio (), passing in the string slice and the callback function as parameters. The callback function takes a string argument representing the selected option. You can also use the Selected field to read the Selected option.

Select is a drop-down selection box that displays a drop-down menu when clicked. Click Select. When you have a lot of options, Select is a good choice. Create the method Widget.newSelect () with exactly the same parameters as newRadio ().

func main() { myApp := app.New() myWin := myApp.NewWindow("Choices") nameEntry := widget.NewEntry() nameEntry.SetPlaceHolder("input name") passEntry := widget.NewPasswordEntry() passEntry.SetPlaceHolder("input password")  repeatPassEntry := widget.NewPasswordEntry() repeatPassEntry.SetPlaceHolder("repeat password") nameBox := widget.NewHBox(widget.NewLabel("Name"), layout.NewSpacer(), nameEntry) passwordBox := widget.NewHBox(widget.NewLabel("Password"), layout.NewSpacer(), passEntry) repeatPasswordBox := widget.NewHBox(widget.NewLabel("Repeat Password"), layout.NewSpacer(), repeatPassEntry) sexRadio := widget.NewRadio([]string{"male", "female", "unknown"}, func(value string) { fmt.Println("sex:", value) }) sexBox := widget.NewHBox(widget.NewLabel("Sex"), sexRadio) football := widget.NewCheck("football", func(value bool) { fmt.Println("football:", value) }) basketball := widget.NewCheck("basketball", func(value bool) { fmt.Println("basketball:", value) }) pingpong := widget.NewCheck("pingpong", func(value bool) { fmt.Println("pingpong:", value) }) hobbyBox := widget.NewHBox(widget.NewLabel("Hobby"), football, basketball, pingpong) provinceSelect := widget.NewSelect([]string{"anhui", "zhejiang", "shanghai"}, func(value string) { fmt.Println("province:", value) }) provinceBox := widget.NewHBox(widget.NewLabel("Province"), layout.NewSpacer(), provinceSelect) registerBtn := widget.NewButton("Register", func() { fmt.Println("name:", nameEntry.Text, "password:", passEntry.Text, "register") }) content := widget.NewVBox(nameBox, passwordBox, repeatPasswordBox, sexBox, hobbyBox, provinceBox, registerBtn) myWin.SetContent(content) myWin.ShowAndRun() }

Here we have implemented a simple registration interface:

Form

Form controls are used to lay out a number of Label and input controls. If the OnSubmit or OnCancel functions are specified, the form control automatically adds the corresponding Button Button. We call Widget.newForm () and pass in a Widget.FormItem slice to create the Form control. Each item contains a string as the text of the Label, a control object. Once you’ve created the Form object, you can call its Append(Label, Widget) method to add a control.

func main() {
  myApp := app.New()
  myWindow := myApp.NewWindow("Form")

  nameEntry := widget.NewEntry()
  passEntry := widget.NewPasswordEntry()

  form := widget.NewForm(
    &widget.FormItem{"Name", nameEntry},
    &widget.FormItem{"Pass", passEntry},
  )
  form.OnSubmit = func() {
    fmt.Println("name:", nameEntry.Text, "pass:", passEntry.Text, "login in")
  }
  form.OnCancel = func() {
    fmt.Println("login canceled")
  }

  myWindow.SetContent(form)
  myWindow.Resize(fyne.NewSize(150, 150))
  myWindow.ShowAndRun()
}

Using forms can greatly simplify the construction of forms. We used forms to rewrite the above login screen:

Notice that the Submit and Cancel buttons are automatically generated!

ProgressBar

ProgressBar controls are used to indicate the progress of tasks, such as file downloads. Create method Widget.newProgressBar () with a default minimum of 0.0 and a maximum of 1.1, which can be set through the Min/Max field. Call the setValue () method to control progress. Another kind of progress bar is the circular animation, which indicates that there is a task in progress, but it does not indicate the specific completion of the status.

func main() { myApp := app.New() myWindow := myApp.NewWindow("ProgressBar") bar1 := widget.NewProgressBar() bar1.Min = 0  bar1.Max = 100 bar2 := widget.NewProgressBarInfinite() go func() { for i := 0; i <= 100; i ++ { time.Sleep(time.Millisecond * 500) bar1.SetValue(float64(i)) } }() content := widget.NewVBox(bar1, bar2) myWindow.SetContent(content) myWindow.Resize(fyne.NewSize(150, 150)) myWindow.ShowAndRun() }

Update progress in another goroutine. The effect is as follows:

TabContainer

TabContainers allow users to switch between different content panels. The label can be text or an icon. Create the method Widget.newTabContainer (), passing in the Widget.TabItem as an argument. Widget.TabItem can be created using Widget.NewTabItem(Label, Widget). Labels can also be set to the position:

  • TabLocationBottom: Display at the bottom;
  • TabLocationLeading: Display at the top left;
  • TabLocationTrailing: Shown at the top right.

See the sample:

func main() { myApp := app.New() myWindow := myApp.NewWindow("TabContainer") nameLabel := widget.NewLabel("Name: dajun") sexLabel := widget.NewLabel("Sex: male") ageLabel := widget.NewLabel("Age: 18") addressLabel := widget.NewLabel("Province: shanghai") addressLabel.Hide() profile := widget.NewVBox(nameLabel, sexLabel, ageLabel, addressLabel) musicRadio := widget.NewRadio([]string{"on", "off"}, func(string) {}) showAddressCheck := widget.NewCheck("show address?" , func(value bool) { if ! value { addressLabel.Hide() } else { addressLabel.Show() } }) memberTypeSelect := widget.NewSelect([]string{"junior", "senior", "admin"}, func(string) {}) setting := widget.NewForm( &widget.FormItem{"music", musicRadio}, &widget.FormItem{"check", showAddressCheck}, &widget.FormItem{"member type", memberTypeSelect}, ) tabs := widget.NewTabContainer( widget.NewTabItem("Profile", profile), widget.NewTabItem("Setting", setting), ) myWindow.SetContent(tabs) myWindow.Resize(fyne.NewSize(200, 200)) myWindow.ShowAndRun() }

The above code prepared a simple personal information panel and Settings panel, click Show Address? Whether to display swappable address information:



Toolbar

Toolbars are an essential part of many GUI applications. Toolbar will be commonly used in the form of ICONS very vividly displayed, easy to use. Create the method Widget.newToolBar (), passing in multiple Widget.ToolbarItems as arguments. The most commonly used ToolbarItems are the command (Action), Separator, and Spacer, Respectively with widgets. NewToolbarItemAction (resource, the callback)/widget NewToolbarSeparator ()/widget. NewToolbarSpacer () to create. The command needs to specify a callback, which is triggered when clicked.

func main() {
  myApp := app.New()
  myWindow := myApp.NewWindow("Toolbar")

  toolbar := widget.NewToolbar(
    widget.NewToolbarAction(theme.DocumentCreateIcon(), func() {
      fmt.Println("New document")
    }),
    widget.NewToolbarSeparator(),
    widget.NewToolbarAction(theme.ContentCutIcon(), func() {
      fmt.Println("Cut")
    }),
    widget.NewToolbarAction(theme.ContentCopyIcon(), func() {
      fmt.Println("Copy")
    }),
    widget.NewToolbarAction(theme.ContentPasteIcon(), func() {
      fmt.Println("Paste")
    }),
    widget.NewToolbarSpacer(),
    widget.NewToolbarAction(theme.HelpIcon(), func() {
      log.Println("Display help")
    }),
  )

  content := fyne.NewContainerWithLayout(
    layout.NewBorderLayout(toolbar, nil, nil, nil),
    toolbar, widget.NewLabel(`Lorem ipsum dolor, 
    sit amet consectetur adipisicing elit.
    Quidem consectetur ipsam nesciunt,
    quasi sint expedita minus aut,
    porro iusto magnam ducimus voluptates cum vitae.
    Vero adipisci earum iure consequatur quidem.`),
  )
  myWindow.SetContent(content)
  myWindow.ShowAndRun()
}

Toolbars generally use BorderLayout, and you can place the toolbar on top of any other control, which will be detailed later in the layout section. Run:

Extension control

The standard Fyne controls provide a minimal set of features and customization to suit most application scenarios. Sometimes, we need more advanced functionality. In addition to writing our own controls, we can extend existing controls. For example, we want the Icon control Widget. Icon to respond to the left, right, and double clicks of the mouse button. Start by writing a constructor that calls the extendBaseWidget () method to get the underlying control functionality:

type tappableIcon struct { widget.Icon } func newTappableIcon(res fyne.Resource) *tappableIcon { icon := &tappableIcon{}  icon.ExtendBaseWidget(icon) icon.SetResource(res) return icon }

Then implement the relevant interface:

/ / SRC/fyne. IO/fyne canvasobject. Go / / the left mouse button type Tappable interface {Tapped (* PointEvent)} / / the right mouse button or long press type SecondaryTappable interface {secondSecondary (* pointEvent)} // Double click Type DoubleTappable interface {secondaryTappable interface {secondaryTappable interface (* pointEvent)} DoubleTapped(*PointEvent) }

Interface implementation:

func (t *tappableIcon) Tapped(e *fyne.PointEvent) {
  log.Println("I have been left tapped at", e)
}

func (t *tappableIcon) TappedSecondary(e *fyne.PointEvent) {
  log.Println("I have been right tapped at", e)
}

func (t *tappableIcon) DoubleTapped(e *fyne.PointEvent) {
  log.Println("I have been double tapped at", e)
}

Last Use:

func main() {
  a := app.New()
  w := a.NewWindow("Tappable")
  w.SetContent(newTappableIcon(theme.FyneLogo()))
  w.Resize(fyne.NewSize(200, 200))
  w.ShowAndRun()
}

Run, click the icon console has the corresponding output:

2020/06/18 06:44:02 I have been left tapped at &{{110 97} {106 93}} 2020/06/18 06:44:03 I have been left tapped at &{{110 97} {106 93}} 2020/06/18 06:44:05 I have been right tapped at &{{88 102} {84 98}} 2020/06/18 06:44:06 I have been  right tapped at &{{88 102} {84 98}} 2020/06/18 06:44:06 I have been left tapped at &{{88 101} {84 97}} 2020/06/18 06:44:07 I have been double tapped at &{{88 101} {84 97}}

The output fyne. pointEvent has absolute position (for the upper-left corner of the window) and relative position (for the upper-left corner of the container).

Layout

A Layout is how controls are displayed and arranged on the interface. To make the interface look good, the layout must be mastered. Almost all GUI frameworks provide layouts or similar interfaces. In fact, we are already in the previous example fyne. NewContainerWithLayout () function is used in the layout.

BoxLayout

The BoxLayout is the most commonly used layout. It lists controls in a row or column. In Fyne, we can create a horizontal box layout with layout.newhBoxLayout () and a vertical box layout with layout.newVBoxLayout (). The controls in the horizontal layout are lined up on a single line, and the Width of each control is equal to the minimum Width of its content (minSize ().width). They all have the same Height, which is the maximum Height of all controls (minSize ().height).

The controls in a vertical layout are arranged in a column, and each control’s height is equal to the minimum height of its content, and they all have the same width, the maximum width of all controls.

Generally, use Layout.Newspacer () in the boxLayout auxiliary layout, which takes up the rest of the space. For horizontal box layouts, add a layout.Newspacer () before the first control, with all controls aligned to the right. Add a layout.Newspacer () after the last control, with all controls aligned to the left. Before and after, then the control is aligned in the center. If a layout.newspacer () is added in the middle, then the other controls are aligned on both sides.

func main() { myApp := app.New() myWindow := myApp.NewWindow("Box Layout") hcontainer1 := fyne.NewContainerWithLayout(layout.NewHBoxLayout(), canvas.NewText("left", color.White), canvas.NewText("right", Color. White)) / / left-aligned hcontainer2: = fyne. NewContainerWithLayout (layout) NewHBoxLayout (), layout, NewSpacer (), canvas.NewText("left", color.White), canvas.NewText("right", Color. White)) / / right aligned hcontainer3: = fyne. NewContainerWithLayout (layout) NewHBoxLayout (), canvas, NewText (" left ", color.White), canvas.NewText("right", color.White), Layout. NewSpacer ()) / / middle alignment hcontainer4: = fyne. NewContainerWithLayout (layout) NewHBoxLayout (), layout, NewSpacer (), canvas.NewText("left", color.White), canvas.NewText("right", color.White), On both sides of the layout. NewSpacer ()) / / alignment hcontainer5: = fyne. NewContainerWithLayout (layout) NewHBoxLayout (), canvas. NewText (" left ", color.White), layout.NewSpacer(), canvas.NewText("right", color.White)) myWindow.SetContent(fyne.NewContainerWithLayout(layout.NewVBoxLayout(), hcontainer1, hcontainer2, hcontainer3, hcontainer4, hcontainer5)) myWindow.Resize(fyne.NewSize(200, 200)) myWindow.ShowAndRun() }

Operation effect:

GridLayout

Each row of a GridLayout has a fixed number of columns. When the number of controls added exceeds this value, subsequent controls will appear on a new row. Create the method layout.newGridLayout (cols) and pass in the number of columns per row.

func main() {
  myApp := app.New()
  myWindow := myApp.NewWindow("Grid Layout")

  img1 := canvas.NewImageFromResource(theme.FyneLogo())
  img2 := canvas.NewImageFromResource(theme.FyneLogo())
  img3 := canvas.NewImageFromResource(theme.FyneLogo())
  myWindow.SetContent(fyne.NewContainerWithLayout(layout.NewGridLayout(2),
    img1, img2, img3))
  myWindow.Resize(fyne.NewSize(300, 300))
  myWindow.ShowAndRun()
}

Operation effect:

One advantage of this layout is that the controls automatically resize as we zoom in and out. Give it a try ~

GridWrapLayout

GridWraPlayOut is an extension to GridLayout. When GridWraPlayOut is created, it specifies an initial size that is applied to all child controls, and each child controls holds the same size. Initially, one control per line. If the interface size changes, the child controls are rearranged. For example, if the width is doubled, two controls can be arranged on one line. Kind of like a fluid layout:

func main() {
  myApp := app.New()
  myWindow := myApp.NewWindow("Grid Wrap Layout")

  img1 := canvas.NewImageFromResource(theme.FyneLogo())
  img2 := canvas.NewImageFromResource(theme.FyneLogo())
  img3 := canvas.NewImageFromResource(theme.FyneLogo())
  myWindow.SetContent(
    fyne.NewContainerWithLayout(
      layout.NewGridWrapLayout(fyne.NewSize(150, 150)),
      img1, img2, img3))
  myWindow.ShowAndRun()
}

The original:

Increase the width:

Increase the width again:

BorderLayout

BorderLayout is more commonly used to build user interfaces, and the Toolbar in the example above is usually used in conjunction with BorderLayout. Create Layout.NewBorderLayout(top, bottom, left, right) and pass in the top, bottom, left, and right control objects. Controls added to the container are shown in the corresponding position if they are these boundary objects, and everything else is shown in the center:

func main() {
  myApp := app.New()
  myWindow := myApp.NewWindow("Border Layout")

  left := canvas.NewText("left", color.White)
  right := canvas.NewText("right", color.White)
  top := canvas.NewText("top", color.White)
  bottom := canvas.NewText("bottom", color.White)
  content := widget.NewLabel(`Lorem ipsum dolor, 
  sit amet consectetur adipisicing elit.
  Quidem consectetur ipsam nesciunt,
  quasi sint expedita minus aut,
  porro iusto magnam ducimus voluptates cum vitae.
  Vero adipisci earum iure consequatur quidem.`)

  container := fyne.NewContainerWithLayout(
    layout.NewBorderLayout(top, bottom, left, right),
    top, bottom, left, right, content,
  )
  myWindow.SetContent(container)
  myWindow.ShowAndRun()
}

Effect:

FormLayout

The FormLayout is really just a 2-column GridLayout, but with some tweaks for the form.

func main() {
  myApp := app.New()
  myWindow := myApp.NewWindow("Border Layout")

  nameLabel := canvas.NewText("Name", color.Black)
  nameValue := canvas.NewText("dajun", color.White)
  ageLabel := canvas.NewText("Age", color.Black)
  ageValue := canvas.NewText("18", color.White)

  container := fyne.NewContainerWithLayout(
    layout.NewFormLayout(),
    nameLabel, nameValue, ageLabel, ageValue,
  )
  myWindow.SetContent(container)
  myWindow.Resize(fyne.NewSize(150, 150))
  myWindow.ShowAndRun()
}

Operation effect:

CenterLayout

CenterLayout displays all the controls in the container in a central location, in the order they are passed in. The last control passed in displays the topmost layer. All controls in CenterLayout will keep their minimum size (large enough to accommodate their contents).

func main() {
  myApp := app.New()
  myWindow := myApp.NewWindow("Center Layout")

  image := canvas.NewImageFromResource(theme.FyneLogo())
  image.FillMode = canvas.ImageFillOriginal
  text := canvas.NewText("Fyne Logo", color.Black)

  container := fyne.NewContainerWithLayout(
    layout.NewCenterLayout(),
    image, text,
  )
  myWindow.SetContent(container)
  myWindow.ShowAndRun()
}

Running results:

The string Fyne Logo is displayed at the top of the image. If we switch the order of text and image, the string will be obscured by the image. Try it

MaxLayout

The maxLayout is similar to the centerLayout, except that the maxLayout displays all the elements in the container to their maximum size (equal to the size of the container). As careful friends may have noticed, in the example of CenterLayout. We set the fill mode of the image to ImageillOriginal. If the fill mode is not set, the default MinSize of the image is (1, 1). You can verify this by fmt.println (image.minSize ()). So the image doesn’t appear in the screen.

In the MaxLayout container, we don’t need to do this:

func main() {
  myApp := app.New()
  myWindow := myApp.NewWindow("Max Layout")

  image := canvas.NewImageFromResource(theme.FyneLogo())
  text := canvas.NewText("Fyne Logo", color.Black)

  container := fyne.NewContainerWithLayout(
    layout.NewMaxLayout(),
    image, text,
  )
  myWindow.SetContent(container)
  myWindow.Resize(fyne.Size(200, 200))
  myWindow.ShowAndRun()
}

Running results:

Notice that Canvas.Text is left aligned. To center Alignment, set its Alignment property to Fyne.TactAlignment Center.

A custom Layout

The built-in layout is in the subpackage Layout. They all implement the Fyne.layout interface:

// src/fyne.io/fyne/layout.go
type Layout interface {
  Layout([]CanvasObject, Size)
  MinSize(objects []CanvasObject) Size
}

To implement a custom layout, you only need to implement this interface. Next we implement a step (diagonal) layout, like the diagonal of a matrix, from top left to bottom right. First, define a new type. Then implement two methods of the interface fyne.Layout:

type diagonal struct {
}

func (d *diagonal) MinSize(objects []fyne.CanvasObject) fyne.Size {
  w, h := 0, 0
  for _, o := range objects {
    childSize := o.MinSize()

    w += childSize.Width
    h += childSize.Height
  }

  return fyne.NewSize(w, h)
}

func (d *diagonal) Layout(objects []fyne.CanvasObject, containerSize fyne.Size) {
  pos := fyne.NewPos(0, 0)
  for _, o := range objects {
    size := o.MinSize()
    o.Resize(size)
    o.Move(pos)

    pos = pos.Add(fyne.NewPos(size.Width, size.Height))
  }
}

MinSize() returns the sum of the minSize of all child controls. Layout() arranges the controls from top left to bottom right. Then use:

func main() {
  myApp := app.New()
  myWindow := myApp.NewWindow("Diagonal Layout")

  img1 := canvas.NewImageFromResource(theme.FyneLogo())
  img1.FillMode = canvas.ImageFillOriginal
  img2 := canvas.NewImageFromResource(theme.FyneLogo())
  img2.FillMode = canvas.ImageFillOriginal
  img3 := canvas.NewImageFromResource(theme.FyneLogo())
  img3.FillMode = canvas.ImageFillOriginal

  container := fyne.NewContainerWithLayout(
    &diagonal{},
    img1, img2, img3,
  )
  myWindow.SetContent(container)
  myWindow.ShowAndRun()
}

Running results:

fyne demo

Fyne provides a Demo that demonstrates the use of most of the controls and layouts. To install it, use the following command:

$ go get fyne.io/fyne/cmd/fyne_demo
$ fyne_demo

Effect:

fyneThe command

The Fyne library provides the Fyne command for developers’ convenience. Fyne can be used to package static resources into executable programs, and can also package entire applications into publishable form. The fyne command is installed with the following command:

$ go get fyne.io/fyne/cmd/fyne

FYNE will be in the $GOPATH/bin directory after the installation is complete. Add $GOPATH/bin to the system $PATH to run FYNE directly.

Static resource

In fact, we have used the built-in Fyne static resources several times in the previous examples, most notably fyne.fynelogo (). Below we have two images: image1.png/image2.jpg. We use the fyne bundle command to package these two images into the code:

$ fyne bundle image1.png >> bundled.go
$ fyne bundle -append image2.jpg >> bundled.go

The second command specifies the -append option to add to an existing file. The resulting file looks like this:

// bundled.go package main import "fyne.io/fyne" var resourceImage1Png = &fyne.StaticResource{ StaticName: "image1.png", StaticContent: []byte{... }} var resourceImage2Jpg = &fyne.StaticResource{ StaticName: "image2.jpg", StaticContent: []byte{... }}

A byte is actually moving Image content into slices, we can invoke the canvas in the code. The NewImageFromResource (), the incoming resourceImage1Png or resourceImage2Jpg to create canvas. The Image object.

func main() {
  myApp := app.New()
  myWindow := myApp.NewWindow("Bundle Resource")

  img1 := canvas.NewImageFromResource(resourceImage1Png)
  img1.FillMode = canvas.ImageFillOriginal
  img2 := canvas.NewImageFromResource(resourceImage2Jpg)
  img2.FillMode = canvas.ImageFillOriginal
  img3 := canvas.NewImageFromResource(theme.FyneLogo())
  img3.FillMode = canvas.ImageFillOriginal

  container := fyne.NewContainerWithLayout(
    layout.NewGridLayout(1),
    img1, img2, img3,
  )
  myWindow.SetContent(container)
  myWindow.ShowAndRun()
}

Running results:

Note that since there are now two files, you cannot use Go Run Main. go instead of Go Run..

Theme.fynelogo () is actually pre-packaged into the code as well. The code file is Bundled-icons. Go:

// src/fyne.io/fyne/theme/icons.go
func FyneLogo() fyne.Resource {
  return fynelogo
}

// src/fyne.io/fyne/theme/bundled-icons.go
var fynelogo = &fyne.StaticResource{
  StaticName: "fyne.png",
  StaticContent: []byte{}}

Publishing the application

Publishing graphics applications to multiple operating systems is a complex task. Graphical interface applications usually have ICONS and some metadata. The fyne command provides support for publishing applications to multiple platforms. Using the FYNE PACKAGE command creates an application that can be installed/run on other computers. On Windows, the Fyne Package creates an.exe file. On MacOS, an.app file is created. On Linux, a.tar.xz file is generated and can be installed manually.

We’ll package the above application as an EXE file:

$ fyne package -os windows -icon icon.jpg

The above command generates two files, bundle.exe and fyne.syso, in the same directory. Copy these files to any directory or other Windows machine and you can run them by directly double-clicking on the bundle. There is no other dependency.

Fyne also supports cross-compilation, which allows you to compile Mac applications on Windows, but you need to install additional tools, so you can explore on your own.

conclusion

Fyne has a rich set of components and features, but we’ve only covered the basics, including clipboards, shortcuts, scrollbars, menus, and more. The fyne command is a convenient way to package static resources and applications. Fyne also has other advanced features for you to explore

If you find a fun and useful Go library, please submit an issue😄 on Go Daily’s GitHub

reference

  1. Fyne GitHub:https://github.com/fyne-io/fyne
  2. Fyne.com: https://fyne.io/
  3. Official fyne introductory tutorial: https://developer.fyne.io/tour/introduction/hello.html
  4. Go a library GitHub:https://github.com/darjun/go-daily-lib daily

I

My blog: https://darjun.github.io

Welcome to pay attention to my WeChat public number [GOUPUP], learn together, make progress together ~