File and folder dialogs
The file dialogs hand your callback a reader, a writer or a folder, not a file path. That is what lets the same code work with a desktop file system, with the document pickers on Android and iOS, and in a browser. Read Files and URIs for how to use what you get back.
Opening a file
dialog.NewFileOpen
gives your callback a
bellui.URIReadCloser:
an io.ReadCloser that also reports the chosen file’s URI.
open := widget.NewButton("Open…", func() {
d := dialog.NewFileOpen(func(r bellui.URIReadCloser, err error) {
if err != nil {
dialog.ShowError(err, w)
return
}
if r == nil {
return // the user cancelled
}
defer r.Close()
data, err := io.ReadAll(r)
if err != nil {
dialog.ShowError(err, w)
return
}
text.SetText(string(data))
status.SetText("Opened " + r.URI().Name())
}, w)
d.SetFilter(storage.NewExtensionFileFilter([]string{".txt", ".md"}))
d.Show()
})Three rules for the callback:
- If the user cancels, the reader is
niland so is the error. Check for both. - If the reader is not
nil, you must close it. - The callback runs on the UI thread, so it can update widgets directly.
SetFilter limits which files can be chosen.
storage.NewExtensionFileFilter
matches extensions such as .txt (with the dot, ignoring case), and
storage.NewMimeTypeFileFilter
matches MIME types, including wildcards like image/*.
Saving a file
dialog.NewFileSave
works the same way but hands you a
bellui.URIWriteCloser.
If the user picks a file that already exists, they are asked to confirm before
it is overwritten. SetFileName suggests a name.
save := widget.NewButton("Save as…", func() {
d := dialog.NewFileSave(func(wr bellui.URIWriteCloser, err error) {
if err != nil {
dialog.ShowError(err, w)
return
}
if wr == nil {
return // the user cancelled
}
defer wr.Close()
if _, err := wr.Write([]byte(text.Text)); err != nil {
dialog.ShowError(err, w)
return
}
status.SetText("Saved " + wr.URI().Name())
}, w)
d.SetFileName("notes.txt")
d.Show()
})Choosing a folder
dialog.ShowFolderOpen
returns a
bellui.ListableURI,
whose List method returns the folder’s contents.
folder := widget.NewButton("Choose folder…", func() {
dialog.ShowFolderOpen(func(dir bellui.ListableURI, err error) {
if err != nil || dir == nil {
return
}
items, err := dir.List()
if err != nil {
dialog.ShowError(err, w)
return
}
status.SetText(dir.Name() + " holds " + strconv.Itoa(len(items)) + " items")
}, w)
})Choosing several files
dialog.ShowFileOpenMultiple
lets the user pick more than one file. The callback gets a slice of readers, or
nil if the user cancelled. Close every reader in the slice.
several := widget.NewButton("Open several…", func() {
dialog.ShowFileOpenMultiple(func(rs []bellui.URIReadCloser, err error) {
if err != nil || rs == nil {
return
}
for _, r := range rs {
r.Close() // close every reader you are given
}
status.SetText("Picked " + strconv.Itoa(len(rs)) + " files")
}, w)
})In Bellui’s own dialog, Ctrl-click (Cmd-click on macOS) adds or removes a file and Shift-click selects a range. On a touch screen, a long press on a file starts selection mode, where each tap adds or removes a file.
Native pickers or Bellui’s dialog
By default the file dialogs use the platform’s own picker where there is one:
| Platform | Picker |
|---|---|
| macOS | The system open and save panels |
| Windows | The system file dialogs |
| Linux and BSD | The desktop’s file chooser, through the XDG desktop portal |
| Android, iOS | The system document pickers |
| Web | The browser’s file picker for opening; saving downloads the file |
When no native picker is available, for example on a Linux desktop with no
portal running, Bellui draws its own dialog instead, so the feature always
works. To use Bellui’s dialog everywhere, call
dialog.UseNativeFileDialogs(false)
once at start-up.
Some details differ between the two:
SetShowHidden(true), which shows hidden files, only affects Bellui’s own dialog; native pickers have no such option.- A Flatpak build always uses the portal picker, whatever you set.
- In the browser, opening always uses the browser’s picker and saving is always a download, because a web page has no file system to browse. Folder picking and multi-file picking fall back to Bellui’s dialog there.
- In the browser the save callback receives a writer at once. The download is
offered when you close it, and a callback set with
SetOnClosedruns at that moment.
More options
The FileDialog
type also has:
SetLocation(bellui.ListableURI)to choose the starting folder.SetConfirmText,SetDismissTextandSetTitleTextto relabel it.SetView(dialog.ListView)orSetView(dialog.GridView)for Bellui’s dialog layout.SetWindowedandSetWindowHosted, as described in Messages and confirmation.
The complete example
// File open, file save and folder dialogs.
package main
import (
"io"
"strconv"
"github.com/belluikit/bellui"
"github.com/belluikit/bellui/app"
"github.com/belluikit/bellui/container"
"github.com/belluikit/bellui/dialog"
"github.com/belluikit/bellui/storage"
"github.com/belluikit/bellui/widget"
)
func main() {
a := app.New()
w := a.NewWindow("File dialogs")
text := widget.NewMultiLineEntry()
status := widget.NewLabel("")
open := widget.NewButton("Open…", func() {
d := dialog.NewFileOpen(func(r bellui.URIReadCloser, err error) {
if err != nil {
dialog.ShowError(err, w)
return
}
if r == nil {
return // the user cancelled
}
defer r.Close()
data, err := io.ReadAll(r)
if err != nil {
dialog.ShowError(err, w)
return
}
text.SetText(string(data))
status.SetText("Opened " + r.URI().Name())
}, w)
d.SetFilter(storage.NewExtensionFileFilter([]string{".txt", ".md"}))
d.Show()
})
save := widget.NewButton("Save as…", func() {
d := dialog.NewFileSave(func(wr bellui.URIWriteCloser, err error) {
if err != nil {
dialog.ShowError(err, w)
return
}
if wr == nil {
return // the user cancelled
}
defer wr.Close()
if _, err := wr.Write([]byte(text.Text)); err != nil {
dialog.ShowError(err, w)
return
}
status.SetText("Saved " + wr.URI().Name())
}, w)
d.SetFileName("notes.txt")
d.Show()
})
folder := widget.NewButton("Choose folder…", func() {
dialog.ShowFolderOpen(func(dir bellui.ListableURI, err error) {
if err != nil || dir == nil {
return
}
items, err := dir.List()
if err != nil {
dialog.ShowError(err, w)
return
}
status.SetText(dir.Name() + " holds " + strconv.Itoa(len(items)) + " items")
}, w)
})
several := widget.NewButton("Open several…", func() {
dialog.ShowFileOpenMultiple(func(rs []bellui.URIReadCloser, err error) {
if err != nil || rs == nil {
return
}
for _, r := range rs {
r.Close() // close every reader you are given
}
status.SetText("Picked " + strconv.Itoa(len(rs)) + " files")
}, w)
})
buttons := container.NewHBox(open, save, folder, several)
w.SetContent(container.NewBorder(buttons, status, nil, nil, text))
bellui.ShowAndRun(w)
}