progressbarinfinite.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. package widget
  2. import (
  3. "time"
  4. "fyne.io/fyne/v2"
  5. "fyne.io/fyne/v2/canvas"
  6. "fyne.io/fyne/v2/internal/cache"
  7. "fyne.io/fyne/v2/internal/widget"
  8. "fyne.io/fyne/v2/theme"
  9. )
  10. const (
  11. infiniteRefreshRate = 50 * time.Millisecond
  12. maxProgressBarInfiniteWidthRatio = 1.0 / 5
  13. minProgressBarInfiniteWidthRatio = 1.0 / 20
  14. progressBarInfiniteStepSizeRatio = 1.0 / 50
  15. )
  16. type infProgressRenderer struct {
  17. widget.BaseRenderer
  18. background, bar *canvas.Rectangle
  19. animation *fyne.Animation
  20. running bool
  21. progress *ProgressBarInfinite
  22. }
  23. // MinSize calculates the minimum size of a progress bar.
  24. func (p *infProgressRenderer) MinSize() fyne.Size {
  25. // this is to create the same size infinite progress bar as regular progress bar
  26. text := fyne.MeasureText("100%", theme.TextSize(), fyne.TextStyle{})
  27. return fyne.NewSize(text.Width+theme.InnerPadding()*2, text.Height+theme.InnerPadding()*2)
  28. }
  29. func (p *infProgressRenderer) updateBar(done float32) {
  30. progressWidth := p.progress.size.Width
  31. spanWidth := progressWidth + (progressWidth * (maxProgressBarInfiniteWidthRatio / 2))
  32. maxBarWidth := progressWidth * maxProgressBarInfiniteWidthRatio
  33. barCenterX := spanWidth*done - (spanWidth-progressWidth)/2
  34. barPos := fyne.NewPos(barCenterX-maxBarWidth/2, 0)
  35. barSize := fyne.NewSize(maxBarWidth, p.progress.size.Height)
  36. if barPos.X < 0 {
  37. barSize.Width += barPos.X
  38. barPos.X = 0
  39. }
  40. if barPos.X+barSize.Width > progressWidth {
  41. barSize.Width = progressWidth - barPos.X
  42. }
  43. p.bar.Resize(barSize)
  44. p.bar.Move(barPos)
  45. canvas.Refresh(p.bar)
  46. }
  47. // Layout the components of the infinite progress bar
  48. func (p *infProgressRenderer) Layout(size fyne.Size) {
  49. p.background.Resize(size)
  50. }
  51. // Refresh updates the size and position of the horizontal scrolling infinite progress bar
  52. func (p *infProgressRenderer) Refresh() {
  53. if p.isRunning() {
  54. return // we refresh from the goroutine
  55. }
  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.background.Refresh()
  61. p.bar.Refresh()
  62. canvas.Refresh(p.progress.super())
  63. }
  64. func (p *infProgressRenderer) isRunning() bool {
  65. p.progress.propertyLock.RLock()
  66. defer p.progress.propertyLock.RUnlock()
  67. return p.running
  68. }
  69. // Start the infinite progress bar background thread to update it continuously
  70. func (p *infProgressRenderer) start() {
  71. if p.isRunning() {
  72. return
  73. }
  74. p.progress.propertyLock.Lock()
  75. defer p.progress.propertyLock.Unlock()
  76. p.animation = fyne.NewAnimation(time.Second*3, p.updateBar)
  77. p.animation.Curve = fyne.AnimationLinear
  78. p.animation.RepeatCount = fyne.AnimationRepeatForever
  79. p.running = true
  80. p.animation.Start()
  81. }
  82. // Stop the background thread from updating the infinite progress bar
  83. func (p *infProgressRenderer) stop() {
  84. p.progress.propertyLock.Lock()
  85. defer p.progress.propertyLock.Unlock()
  86. p.running = false
  87. p.animation.Stop()
  88. }
  89. func (p *infProgressRenderer) Destroy() {
  90. p.stop()
  91. }
  92. // ProgressBarInfinite widget creates a horizontal panel that indicates waiting indefinitely
  93. // An infinite progress bar loops 0% -> 100% repeatedly until Stop() is called
  94. type ProgressBarInfinite struct {
  95. BaseWidget
  96. }
  97. // Show this widget, if it was previously hidden
  98. func (p *ProgressBarInfinite) Show() {
  99. p.Start()
  100. p.BaseWidget.Show()
  101. }
  102. // Hide this widget, if it was previously visible
  103. func (p *ProgressBarInfinite) Hide() {
  104. p.Stop()
  105. p.BaseWidget.Hide()
  106. }
  107. // Start the infinite progress bar animation
  108. func (p *ProgressBarInfinite) Start() {
  109. cache.Renderer(p).(*infProgressRenderer).start()
  110. }
  111. // Stop the infinite progress bar animation
  112. func (p *ProgressBarInfinite) Stop() {
  113. cache.Renderer(p).(*infProgressRenderer).stop()
  114. }
  115. // Running returns the current state of the infinite progress animation
  116. func (p *ProgressBarInfinite) Running() bool {
  117. if !cache.IsRendered(p) {
  118. return false
  119. }
  120. return cache.Renderer(p).(*infProgressRenderer).isRunning()
  121. }
  122. // MinSize returns the size that this widget should not shrink below
  123. func (p *ProgressBarInfinite) MinSize() fyne.Size {
  124. p.ExtendBaseWidget(p)
  125. return p.BaseWidget.MinSize()
  126. }
  127. // CreateRenderer is a private method to Fyne which links this widget to its renderer
  128. func (p *ProgressBarInfinite) CreateRenderer() fyne.WidgetRenderer {
  129. p.ExtendBaseWidget(p)
  130. background := canvas.NewRectangle(progressBackgroundColor())
  131. background.CornerRadius = theme.InputRadiusSize()
  132. bar := canvas.NewRectangle(theme.PrimaryColor())
  133. bar.CornerRadius = theme.InputRadiusSize()
  134. render := &infProgressRenderer{
  135. BaseRenderer: widget.NewBaseRenderer([]fyne.CanvasObject{background, bar}),
  136. background: background,
  137. bar: bar,
  138. progress: p,
  139. }
  140. render.start()
  141. return render
  142. }
  143. // NewProgressBarInfinite creates a new progress bar widget that loops indefinitely from 0% -> 100%
  144. // SetValue() is not defined for infinite progress bar
  145. // To stop the looping progress and set the progress bar to 100%, call ProgressBarInfinite.Stop()
  146. func NewProgressBarInfinite() *ProgressBarInfinite {
  147. p := &ProgressBarInfinite{}
  148. cache.Renderer(p).Layout(p.MinSize())
  149. return p
  150. }