fileicon.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. package widget
  2. import (
  3. "fyne.io/fyne/v2"
  4. "fyne.io/fyne/v2/canvas"
  5. "fyne.io/fyne/v2/internal/repository/mime"
  6. "fyne.io/fyne/v2/internal/widget"
  7. "fyne.io/fyne/v2/storage"
  8. "fyne.io/fyne/v2/theme"
  9. )
  10. const (
  11. ratioDown = 0.45
  12. ratioTextSize = 0.22
  13. )
  14. // FileIcon is an adaption of widget.Icon for showing files and folders
  15. //
  16. // Since: 1.4
  17. type FileIcon struct {
  18. BaseWidget
  19. // Deprecated: Selection is now handled externally.
  20. Selected bool
  21. URI fyne.URI
  22. resource fyne.Resource
  23. extension string
  24. }
  25. // NewFileIcon takes a filepath and creates an icon with an overlaid label using the detected mimetype and extension
  26. //
  27. // Since: 1.4
  28. func NewFileIcon(uri fyne.URI) *FileIcon {
  29. i := &FileIcon{URI: uri}
  30. i.ExtendBaseWidget(i)
  31. return i
  32. }
  33. // SetURI changes the URI and makes the icon reflect a different file
  34. func (i *FileIcon) SetURI(uri fyne.URI) {
  35. i.URI = uri
  36. i.Refresh()
  37. }
  38. func (i *FileIcon) setURI(uri fyne.URI) {
  39. if uri == nil {
  40. i.resource = theme.FileIcon()
  41. return
  42. }
  43. i.URI = uri
  44. i.resource = i.lookupIcon(i.URI)
  45. i.extension = trimmedExtension(uri)
  46. }
  47. // MinSize returns the size that this widget should not shrink below
  48. func (i *FileIcon) MinSize() fyne.Size {
  49. i.ExtendBaseWidget(i)
  50. return i.BaseWidget.MinSize()
  51. }
  52. // CreateRenderer is a private method to Fyne which links this widget to its renderer
  53. func (i *FileIcon) CreateRenderer() fyne.WidgetRenderer {
  54. i.ExtendBaseWidget(i)
  55. i.propertyLock.Lock()
  56. i.setURI(i.URI)
  57. i.propertyLock.Unlock()
  58. i.propertyLock.RLock()
  59. defer i.propertyLock.RUnlock()
  60. // TODO remove background when `SetSelected` is gone.
  61. background := canvas.NewRectangle(theme.SelectionColor())
  62. background.Hide()
  63. s := &fileIconRenderer{file: i, background: background}
  64. s.img = canvas.NewImageFromResource(s.file.resource)
  65. s.img.FillMode = canvas.ImageFillContain
  66. s.ext = canvas.NewText(s.file.extension, theme.BackgroundColor())
  67. s.ext.Alignment = fyne.TextAlignCenter
  68. s.SetObjects([]fyne.CanvasObject{s.background, s.img, s.ext})
  69. return s
  70. }
  71. // SetSelected makes the file look like it is selected.
  72. //
  73. // Deprecated: Selection is now handled externally.
  74. func (i *FileIcon) SetSelected(selected bool) {
  75. i.Selected = selected
  76. i.Refresh()
  77. }
  78. func (i *FileIcon) lookupIcon(uri fyne.URI) fyne.Resource {
  79. if i.isDir(uri) {
  80. return theme.FolderIcon()
  81. }
  82. mainMimeType, _ := mime.Split(uri.MimeType())
  83. switch mainMimeType {
  84. case "application":
  85. return theme.FileApplicationIcon()
  86. case "audio":
  87. return theme.FileAudioIcon()
  88. case "image":
  89. return theme.FileImageIcon()
  90. case "text":
  91. return theme.FileTextIcon()
  92. case "video":
  93. return theme.FileVideoIcon()
  94. default:
  95. return theme.FileIcon()
  96. }
  97. }
  98. func (i *FileIcon) isDir(uri fyne.URI) bool {
  99. if _, ok := uri.(fyne.ListableURI); ok {
  100. return true
  101. }
  102. listable, err := storage.ListerForURI(uri)
  103. if err != nil {
  104. return false
  105. }
  106. i.URI = listable // Avoid having to call storage.ListerForURI(uri) the next time.
  107. return true
  108. }
  109. type fileIconRenderer struct {
  110. widget.BaseRenderer
  111. file *FileIcon
  112. background *canvas.Rectangle
  113. ext *canvas.Text
  114. img *canvas.Image
  115. }
  116. func (s *fileIconRenderer) MinSize() fyne.Size {
  117. return fyne.NewSquareSize(theme.IconInlineSize())
  118. }
  119. func (s *fileIconRenderer) Layout(size fyne.Size) {
  120. isize := fyne.Min(size.Width, size.Height)
  121. xoff := float32(0)
  122. yoff := (size.Height - isize) / 2
  123. if size.Width > size.Height {
  124. xoff = (size.Width - isize) / 2
  125. }
  126. yoff += isize * ratioDown
  127. oldSize := s.ext.TextSize
  128. s.ext.TextSize = float32(int(isize * ratioTextSize))
  129. s.ext.Resize(fyne.NewSize(isize, s.ext.MinSize().Height))
  130. s.ext.Move(fyne.NewPos(xoff, yoff))
  131. if oldSize != s.ext.TextSize {
  132. s.ext.Refresh()
  133. }
  134. s.Objects()[0].Resize(size)
  135. s.Objects()[1].Resize(size)
  136. }
  137. func (s *fileIconRenderer) Refresh() {
  138. s.file.propertyLock.Lock()
  139. s.file.setURI(s.file.URI)
  140. s.file.propertyLock.Unlock()
  141. s.file.propertyLock.RLock()
  142. s.img.Resource = s.file.resource
  143. s.ext.Text = s.file.extension
  144. s.file.propertyLock.RUnlock()
  145. if s.file.Selected {
  146. s.background.Show()
  147. s.ext.Color = theme.SelectionColor()
  148. if _, ok := s.img.Resource.(*theme.InvertedThemedResource); !ok {
  149. s.img.Resource = theme.NewInvertedThemedResource(s.img.Resource)
  150. }
  151. } else {
  152. s.background.Hide()
  153. s.ext.Color = theme.BackgroundColor()
  154. if res, ok := s.img.Resource.(*theme.InvertedThemedResource); ok {
  155. s.img.Resource = res.Original()
  156. }
  157. }
  158. canvas.Refresh(s.file.super())
  159. canvas.Refresh(s.ext)
  160. }
  161. func trimmedExtension(uri fyne.URI) string {
  162. ext := uri.Extension()
  163. if len(ext) > 5 {
  164. ext = ext[:5]
  165. }
  166. return ext
  167. }