package widget import ( "fyne.io/fyne/v2" "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/internal/repository/mime" "fyne.io/fyne/v2/internal/widget" "fyne.io/fyne/v2/storage" "fyne.io/fyne/v2/theme" ) const ( ratioDown = 0.45 ratioTextSize = 0.22 ) // FileIcon is an adaption of widget.Icon for showing files and folders // // Since: 1.4 type FileIcon struct { BaseWidget // Deprecated: Selection is now handled externally. Selected bool URI fyne.URI resource fyne.Resource extension string } // NewFileIcon takes a filepath and creates an icon with an overlaid label using the detected mimetype and extension // // Since: 1.4 func NewFileIcon(uri fyne.URI) *FileIcon { i := &FileIcon{URI: uri} i.ExtendBaseWidget(i) return i } // SetURI changes the URI and makes the icon reflect a different file func (i *FileIcon) SetURI(uri fyne.URI) { i.URI = uri i.Refresh() } // must be called with i.propertyLock RLocked func (i *FileIcon) setURI(uri fyne.URI) { if uri == nil { i.resource = i.Theme().Icon(theme.IconNameFile) return } i.URI = uri i.resource = i.lookupIcon(i.URI) i.extension = trimmedExtension(uri) } // MinSize returns the size that this widget should not shrink below func (i *FileIcon) MinSize() fyne.Size { i.ExtendBaseWidget(i) return i.BaseWidget.MinSize() } // CreateRenderer is a private method to Fyne which links this widget to its renderer func (i *FileIcon) CreateRenderer() fyne.WidgetRenderer { th := i.Theme() v := fyne.CurrentApp().Settings().ThemeVariant() i.ExtendBaseWidget(i) i.setURI(i.URI) // TODO remove background when `SetSelected` is gone. background := canvas.NewRectangle(th.Color(theme.ColorNameSelection, v)) background.Hide() s := &fileIconRenderer{file: i, background: background} s.img = canvas.NewImageFromResource(s.file.resource) s.img.FillMode = canvas.ImageFillContain s.ext = canvas.NewText(s.file.extension, th.Color(theme.ColorNameBackground, v)) s.ext.Alignment = fyne.TextAlignCenter s.SetObjects([]fyne.CanvasObject{s.background, s.img, s.ext}) return s } // SetSelected makes the file look like it is selected. // // Deprecated: Selection is now handled externally. func (i *FileIcon) SetSelected(selected bool) { i.Selected = selected i.Refresh() } // must be called with i.propertyLock RLocked func (i *FileIcon) lookupIcon(uri fyne.URI) fyne.Resource { if icon, ok := uri.(fyne.URIWithIcon); ok { return icon.Icon() } if i.isDir(uri) { return theme.FolderIcon() } th := i.Theme() mainMimeType, _ := mime.Split(uri.MimeType()) switch mainMimeType { case "application": return th.Icon(theme.IconNameFileApplication) case "audio": return th.Icon(theme.IconNameFileAudio) case "image": return th.Icon(theme.IconNameFileImage) case "text": return th.Icon(theme.IconNameFileText) case "video": return th.Icon(theme.IconNameFileVideo) default: return th.Icon(theme.IconNameFile) } } func (i *FileIcon) isDir(uri fyne.URI) bool { if _, ok := uri.(fyne.ListableURI); ok { return true } listable, err := storage.ListerForURI(uri) if err != nil { return false } i.URI = listable // Avoid having to call storage.ListerForURI(uri) the next time. return true } type fileIconRenderer struct { widget.BaseRenderer file *FileIcon background *canvas.Rectangle ext *canvas.Text img *canvas.Image } func (s *fileIconRenderer) MinSize() fyne.Size { th := s.file.Theme() return fyne.NewSquareSize(th.Size(theme.SizeNameInlineIcon)) } func (s *fileIconRenderer) Layout(size fyne.Size) { isize := fyne.Min(size.Width, size.Height) xoff := float32(0) yoff := (size.Height - isize) / 2 if size.Width > size.Height { xoff = (size.Width - isize) / 2 } yoff += isize * ratioDown oldSize := s.ext.TextSize s.ext.TextSize = float32(int(isize * ratioTextSize)) s.ext.Resize(fyne.NewSize(isize, s.ext.MinSize().Height)) s.ext.Move(fyne.NewPos(xoff, yoff)) if oldSize != s.ext.TextSize { s.ext.Refresh() } s.Objects()[0].Resize(size) s.Objects()[1].Resize(size) } func (s *fileIconRenderer) Refresh() { th := s.file.Theme() v := fyne.CurrentApp().Settings().ThemeVariant() s.file.setURI(s.file.URI) if s.file.Selected { s.background.Show() s.ext.Color = th.Color(theme.ColorNameSelection, v) if _, ok := s.img.Resource.(*theme.InvertedThemedResource); !ok { s.img.Resource = theme.NewInvertedThemedResource(s.img.Resource) } } else { s.background.Hide() s.ext.Color = th.Color(theme.ColorNameBackground, v) if res, ok := s.img.Resource.(*theme.InvertedThemedResource); ok { s.img.Resource = res.Original() } } if s.img.Resource != s.file.resource { s.img.Resource = s.file.resource s.img.Refresh() } if s.ext.Text != s.file.extension { s.ext.Text = s.file.extension s.ext.Refresh() } canvas.Refresh(s.file.super()) } func trimmedExtension(uri fyne.URI) string { ext := uri.Extension() if len(ext) > 5 { ext = ext[:5] } return ext }