How to display text that can be copied on a fyne GUI?

Viewed 803

On my fyne GUI, I want to display text that can be copied by the user. So far, I'm using a (multi-line) widget.Entry, see example below. Although that works, it seems inappropriate since "entry" implies user-input - which is not the case here.

If I use a widget.Label or canvas.Text, the characters can't be copied. So what's the best approach here?


Example:

package main

import (
    "time"

    "fyne.io/fyne/v2"
    "fyne.io/fyne/v2/app"
    "fyne.io/fyne/v2/container"
    "fyne.io/fyne/v2/data/binding"
    "fyne.io/fyne/v2/widget"
)

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

    txtBound := binding.NewString()
    txtWid := widget.NewEntryWithData(txtBound)
    txtWid.MultiLine = true

    // we can disable the Entry field so the user can't modify the text:
    txtWid.Disabled()

    go func() {
        for {
            txtBound.Set(time.Now().Format("2006-01-02\n15:04:05\nMST -0700"))
            time.Sleep(time.Second)
        }
    }()

    content := container.NewBorder(nil, nil, nil, nil, txtWid)

    myWindow.SetContent(content)
    myWindow.Resize(fyne.NewSize(500, 300))

    myWindow.ShowAndRun()
}

The text in the Entry field can be selected and then copied using either mouse/right-click context menu or ctrl-c. On Windows 10, that would look like enter image description here

2 Answers

Labels are for display only, they afford no user interaction - this is something that web based apps have confused us about :). In Fyne apps we aim to have all user interaction hinted at, buttons and links are interactive, text is not. There are two possible approaches:

  1. put a “copy” button next to your label as has become common practice in many apps (for example password managers or YouTube)
  2. use a disabled Entry so the text can be interacted with but not edited.

To complement andy.xyz's answer, here's an example of how a button can be used to copy a certain content to the clipboard. Fyne windows have a Clipboard type that provides the necessary interface, e.g.

import (
    "time"

    "fyne.io/fyne/v2"
    "fyne.io/fyne/v2/app"
    "fyne.io/fyne/v2/container"
    "fyne.io/fyne/v2/data/binding"
    "fyne.io/fyne/v2/layout"
    "fyne.io/fyne/v2/theme"
    "fyne.io/fyne/v2/widget"
)

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

    txtBound := binding.NewString()
    txtWid := widget.NewLabelWithData(txtBound)

    bottomBox := container.NewHBox(
        layout.NewSpacer(),
        widget.NewButtonWithIcon("copy content", theme.ContentCopyIcon(), func() {
            if content, err := txtBound.Get(); err == nil {
                myWindow.Clipboard().SetContent(content)
            }
        }),
    )

    content := container.NewBorder(nil, bottomBox, nil, nil, txtWid)

    go func() { // make changing content...
        for {
            txtBound.Set(time.Now().Format("2006-01-02\n15:04:05\nMST -0700"))
            time.Sleep(time.Second)
        }
    }()

    myWindow.SetContent(content)
    myWindow.Resize(fyne.NewSize(500, 300))
    myWindow.ShowAndRun()
}

On Windows 10, that would look like https://pkg.go.dev/fyne.io/fyne#Clipboard

Related