progressbar.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. package widget
  2. import (
  3. "image/color"
  4. "strconv"
  5. "fyne.io/fyne/v2"
  6. "fyne.io/fyne/v2/canvas"
  7. "fyne.io/fyne/v2/data/binding"
  8. "fyne.io/fyne/v2/internal/cache"
  9. col "fyne.io/fyne/v2/internal/color"
  10. "fyne.io/fyne/v2/internal/widget"
  11. "fyne.io/fyne/v2/theme"
  12. )
  13. type progressRenderer struct {
  14. widget.BaseRenderer
  15. background, bar *canvas.Rectangle
  16. label *canvas.Text
  17. progress *ProgressBar
  18. }
  19. // MinSize calculates the minimum size of a progress bar.
  20. // This is simply the "100%" label size plus padding.
  21. func (p *progressRenderer) MinSize() fyne.Size {
  22. var tsize fyne.Size
  23. if text := p.progress.TextFormatter; text != nil {
  24. tsize = fyne.MeasureText(text(), p.label.TextSize, p.label.TextStyle)
  25. } else {
  26. tsize = fyne.MeasureText("100%", p.label.TextSize, p.label.TextStyle)
  27. }
  28. padding := theme.InnerPadding() * 2
  29. return fyne.NewSize(tsize.Width+padding, tsize.Height+padding)
  30. }
  31. func (p *progressRenderer) updateBar() {
  32. if p.progress.Value < p.progress.Min {
  33. p.progress.Value = p.progress.Min
  34. }
  35. if p.progress.Value > p.progress.Max {
  36. p.progress.Value = p.progress.Max
  37. }
  38. delta := float32(p.progress.Max - p.progress.Min)
  39. ratio := float32(p.progress.Value-p.progress.Min) / delta
  40. if text := p.progress.TextFormatter; text != nil {
  41. p.label.Text = text()
  42. } else {
  43. p.label.Text = strconv.Itoa(int(ratio*100)) + "%"
  44. }
  45. size := p.progress.Size()
  46. p.bar.Resize(fyne.NewSize(size.Width*ratio, size.Height))
  47. }
  48. // Layout the components of the check widget
  49. func (p *progressRenderer) Layout(size fyne.Size) {
  50. p.background.Resize(size)
  51. p.label.Resize(size)
  52. p.updateBar()
  53. }
  54. // applyTheme updates the progress bar to match the current theme
  55. func (p *progressRenderer) applyTheme() {
  56. p.background.FillColor = progressBackgroundColor()
  57. p.background.CornerRadius = theme.InputRadiusSize()
  58. p.bar.FillColor = theme.PrimaryColor()
  59. p.bar.CornerRadius = theme.InputRadiusSize()
  60. p.label.Color = theme.BackgroundColor()
  61. p.label.TextSize = theme.TextSize()
  62. }
  63. func (p *progressRenderer) Refresh() {
  64. p.applyTheme()
  65. p.updateBar()
  66. p.background.Refresh()
  67. p.bar.Refresh()
  68. p.label.Refresh()
  69. canvas.Refresh(p.progress.super())
  70. }
  71. // ProgressBar widget creates a horizontal panel that indicates progress
  72. type ProgressBar struct {
  73. BaseWidget
  74. Min, Max, Value float64
  75. // TextFormatter can be used to have a custom format of progress text.
  76. // If set, it overrides the percentage readout and runs each time the value updates.
  77. //
  78. // Since: 1.4
  79. TextFormatter func() string `json:"-"`
  80. binder basicBinder
  81. }
  82. // Bind connects the specified data source to this ProgressBar.
  83. // The current value will be displayed and any changes in the data will cause the widget to update.
  84. //
  85. // Since: 2.0
  86. func (p *ProgressBar) Bind(data binding.Float) {
  87. p.binder.SetCallback(p.updateFromData)
  88. p.binder.Bind(data)
  89. }
  90. // SetValue changes the current value of this progress bar (from p.Min to p.Max).
  91. // The widget will be refreshed to indicate the change.
  92. func (p *ProgressBar) SetValue(v float64) {
  93. p.Value = v
  94. p.Refresh()
  95. }
  96. // MinSize returns the size that this widget should not shrink below
  97. func (p *ProgressBar) MinSize() fyne.Size {
  98. p.ExtendBaseWidget(p)
  99. return p.BaseWidget.MinSize()
  100. }
  101. // CreateRenderer is a private method to Fyne which links this widget to its renderer
  102. func (p *ProgressBar) CreateRenderer() fyne.WidgetRenderer {
  103. p.ExtendBaseWidget(p)
  104. if p.Min == 0 && p.Max == 0 {
  105. p.Max = 1.0
  106. }
  107. background := canvas.NewRectangle(progressBackgroundColor())
  108. background.CornerRadius = theme.InputRadiusSize()
  109. bar := canvas.NewRectangle(theme.PrimaryColor())
  110. bar.CornerRadius = theme.InputRadiusSize()
  111. label := canvas.NewText("0%", theme.BackgroundColor())
  112. label.Alignment = fyne.TextAlignCenter
  113. return &progressRenderer{widget.NewBaseRenderer([]fyne.CanvasObject{background, bar, label}), background, bar, label, p}
  114. }
  115. // Unbind disconnects any configured data source from this ProgressBar.
  116. // The current value will remain at the last value of the data source.
  117. //
  118. // Since: 2.0
  119. func (p *ProgressBar) Unbind() {
  120. p.binder.Unbind()
  121. }
  122. // NewProgressBar creates a new progress bar widget.
  123. // The default Min is 0 and Max is 1, Values set should be between those numbers.
  124. // The display will convert this to a percentage.
  125. func NewProgressBar() *ProgressBar {
  126. p := &ProgressBar{Min: 0, Max: 1}
  127. cache.Renderer(p).Layout(p.MinSize())
  128. return p
  129. }
  130. // NewProgressBarWithData returns a progress bar connected with the specified data source.
  131. //
  132. // Since: 2.0
  133. func NewProgressBarWithData(data binding.Float) *ProgressBar {
  134. p := NewProgressBar()
  135. p.Bind(data)
  136. return p
  137. }
  138. func progressBackgroundColor() color.Color {
  139. r, g, b, a := col.ToNRGBA(theme.PrimaryColor())
  140. faded := uint8(a) / 2
  141. return &color.NRGBA{R: uint8(r), G: uint8(g), B: uint8(b), A: faded}
  142. }
  143. func (p *ProgressBar) updateFromData(data binding.DataItem) {
  144. if data == nil {
  145. return
  146. }
  147. floatSource, ok := data.(binding.Float)
  148. if !ok {
  149. return
  150. }
  151. val, err := floatSource.Get()
  152. if err != nil {
  153. fyne.LogError("Error getting current data value", err)
  154. return
  155. }
  156. p.SetValue(val)
  157. }