From 828ea2fe421cc5e6c7a4e828f78d51e0e2a90dc0 Mon Sep 17 00:00:00 2001 From: Billy Batista Date: Fri, 13 Sep 2024 18:58:18 -0400 Subject: [PATCH] added a search bar to the file dialog the search bar takes advantage of the existing filtering system in order to only show files or filders that match a specific prompt. for now, there is no way to recursively search through files, or use something like regex, though that could be added as options later. the main internal change needed is, in order to be backwards compatible, having a list of filters instead of just a single filter at a time. that way, library users can still have many filters while including the search bar. --- dialog/file.go | 61 ++++++++++++++++++++++++++++++++++++++++++----- dialog/folder.go | 13 ++++++++-- storage/filter.go | 9 +++++++ 3 files changed, 75 insertions(+), 8 deletions(-) diff --git a/dialog/file.go b/dialog/file.go index 738cfb7b78..9e6be0c3b0 100644 --- a/dialog/file.go +++ b/dialog/file.go @@ -89,7 +89,7 @@ type FileDialog struct { confirmText, dismissText string desiredSize fyne.Size - filter storage.FileFilter + filter []storage.FileFilter save bool // this will be applied to dialog.dir when it's loaded startingLocation fyne.ListableURI @@ -203,6 +203,23 @@ func (f *fileDialog) makeUI() fyne.CanvasObject { f.optionsMenu(fyne.CurrentApp().Driver().AbsolutePositionForObject(optionsButton), optionsButton.Size()) }) + searchBar := widget.NewEntry() + searchBar.OnChanged = func(filterText string) { + for _, filter := range f.file.filter { + if filter, ok := filter.(*storage.SearchFilter); ok { + filter.FilterText = filterText + } + } + f.refreshDir(f.dir) + } + searchBar.Hidden = true + // we should only draw the search bar if we've added a search filter + for _, filter := range f.file.filter { + if _, ok := filter.(*storage.SearchFilter); ok { + searchBar.Hidden = false + } + } + newFolderButton := widget.NewButtonWithIcon("", theme.FolderNewIcon(), func() { newFolderEntry := widget.NewEntry() ShowForm(lang.L("New Folder"), lang.L("Create Folder"), lang.L("Cancel"), []*widget.FormItem{ @@ -234,8 +251,8 @@ func (f *fileDialog) makeUI() fyne.CanvasObject { optionsButton, ) - header := container.NewBorder(nil, nil, nil, optionsbuttons, - optionsbuttons, widget.NewLabelWithStyle(title, fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), + header := container.NewBorder(nil, nil, container.NewHBox(widget.NewLabelWithStyle(title, fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), searchBar), optionsbuttons, + optionsbuttons, ) footer := container.NewBorder(nil, nil, nil, buttons, @@ -415,8 +432,18 @@ func (f *fileDialog) refreshDir(dir fyne.ListableURI) { continue } else if err == nil { // URI points to a directory icons = append(icons, listable) - } else if f.file.filter == nil || f.file.filter.Matches(file) { - icons = append(icons, file) + } else { + filterMatches := true + for _, filter := range f.file.filter { + if !filter.Matches(file) { + filterMatches = false + break + } + } + + if filterMatches { + icons = append(icons, file) + } } } @@ -801,12 +828,34 @@ func (f *FileDialog) SetFilter(filter storage.FileFilter) { fyne.LogError("Cannot set a filter for a folder dialog", nil) return } - f.filter = filter + f.filter = []storage.FileFilter{filter} if f.dialog != nil { f.dialog.refreshDir(f.dialog.dir) } } +// AddFilter adds to the list of existing filters for limiting files that can be chosen in the file dialog +func (f *FileDialog) AddFilter(filter storage.FileFilter) { + if f.isDirectory() { + fyne.LogError("Cannot set a filter for a folder dialog", nil) + return + } + if f.filter == nil { + f.filter = []storage.FileFilter{} + } + f.filter = append(f.filter, filter) + if f.dialog != nil { + f.dialog.refreshDir(f.dialog.dir) + } +} + +type ShowSearchBarArgs struct{} + +// ShowSearchBar adds a search bar to the file dialog, and allows filtering for specific text in the local directory +func (f *FileDialog) ShowSearchBar(ShowSearchBarArgs) { + f.AddFilter(&storage.SearchFilter{}) +} + // SetFileName sets the filename in a FileDialog in save mode. // This is normally called before the dialog is shown. func (f *FileDialog) SetFileName(fileName string) { diff --git a/dialog/folder.go b/dialog/folder.go index c2d8cb25ef..349a5ff64d 100644 --- a/dialog/folder.go +++ b/dialog/folder.go @@ -18,7 +18,10 @@ func NewFolderOpen(callback func(fyne.ListableURI, error), parent fyne.Window) * dialog := &FileDialog{} dialog.callback = callback dialog.parent = parent - dialog.filter = folderFilter + if dialog.filter == nil { + dialog.filter = []storage.FileFilter{} + } + dialog.filter = append(dialog.filter, folderFilter) return dialog } @@ -38,5 +41,11 @@ func ShowFolderOpen(callback func(fyne.ListableURI, error), parent fyne.Window) } func (f *FileDialog) isDirectory() bool { - return f.filter == folderFilter + for _, filter := range f.filter { + if filter == folderFilter { + return true + } + } + + return false } diff --git a/storage/filter.go b/storage/filter.go index 07bfd12fed..fee17339e4 100644 --- a/storage/filter.go +++ b/storage/filter.go @@ -24,6 +24,11 @@ type MimeTypeFileFilter struct { MimeTypes []string } +// SearchFilter represents a file or directory filter based on user inputted text +type SearchFilter struct { + FilterText string +} + // Matches returns true if a file URI has one of the filtered extensions. func (e *ExtensionFileFilter) Matches(uri fyne.URI) bool { extension := uri.Extension() @@ -63,3 +68,7 @@ func (mt *MimeTypeFileFilter) Matches(uri fyne.URI) bool { func NewMimeTypeFileFilter(mimeTypes []string) FileFilter { return &MimeTypeFileFilter{MimeTypes: mimeTypes} } + +func (s *SearchFilter) Matches(uri fyne.URI) bool { + return strings.Contains(uri.Name(), s.FilterText) +}