package widget import ( "time" "fyne.io/fyne/v2" "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/internal/widget" "fyne.io/fyne/v2/theme" ) const ( infiniteRefreshRate = 50 * time.Millisecond maxProgressBarInfiniteWidthRatio = 1.0 / 5 minProgressBarInfiniteWidthRatio = 1.0 / 20 progressBarInfiniteStepSizeRatio = 1.0 / 50 ) type infProgressRenderer struct { widget.BaseRenderer background, bar canvas.Rectangle animation fyne.Animation wasRunning bool progress *ProgressBarInfinite } // MinSize calculates the minimum size of a progress bar. func (p *infProgressRenderer) MinSize() fyne.Size { th := p.progress.Theme() innerPad2 := th.Size(theme.SizeNameInnerPadding) * 2 // this is to create the same size infinite progress bar as regular progress bar text := fyne.MeasureText("100%", th.Size(theme.SizeNameText), fyne.TextStyle{}) return fyne.NewSize(text.Width+innerPad2, text.Height+innerPad2) } func (p *infProgressRenderer) updateBar(done float32) { size := p.progress.Size() progressWidth := size.Width spanWidth := progressWidth + (progressWidth * (maxProgressBarInfiniteWidthRatio / 2)) maxBarWidth := progressWidth * maxProgressBarInfiniteWidthRatio barCenterX := spanWidth*done - (spanWidth-progressWidth)/2 barPos := fyne.NewPos(barCenterX-maxBarWidth/2, 0) barSize := fyne.NewSize(maxBarWidth, size.Height) if barPos.X < 0 { barSize.Width += barPos.X barPos.X = 0 } if barPos.X+barSize.Width > progressWidth { barSize.Width = progressWidth - barPos.X } p.bar.Resize(barSize) p.bar.Move(barPos) canvas.Refresh(&p.bar) } // Layout the components of the infinite progress bar func (p *infProgressRenderer) Layout(size fyne.Size) { p.background.Resize(size) } // Refresh updates the size and position of the horizontal scrolling infinite progress bar func (p *infProgressRenderer) Refresh() { running := p.progress.Running() if running { if !p.wasRunning { p.start() } return // we refresh from the goroutine } else if p.wasRunning { p.stop() return } th := p.progress.Theme() v := fyne.CurrentApp().Settings().ThemeVariant() cornerRadius := th.Size(theme.SizeNameInputRadius) primaryColor := th.Color(theme.ColorNamePrimary, v) p.background.FillColor = progressBlendColor(primaryColor) p.background.CornerRadius = cornerRadius p.bar.FillColor = primaryColor p.bar.CornerRadius = cornerRadius p.background.Refresh() p.bar.Refresh() canvas.Refresh(p.progress.super()) } // Start the infinite progress bar background thread to update it continuously func (p *infProgressRenderer) start() { p.animation.Duration = time.Second * 3 p.animation.Tick = p.updateBar p.animation.Curve = fyne.AnimationLinear p.animation.RepeatCount = fyne.AnimationRepeatForever p.animation.AutoReverse = true p.wasRunning = true p.animation.Start() } // Stop the background thread from updating the infinite progress bar func (p *infProgressRenderer) stop() { p.wasRunning = false p.animation.Stop() } func (p *infProgressRenderer) Destroy() { p.progress.running = false p.stop() } // ProgressBarInfinite widget creates a horizontal panel that indicates waiting indefinitely // An infinite progress bar loops 0% -> 100% repeatedly until Stop() is called type ProgressBarInfinite struct { BaseWidget running bool } // Show this widget, if it was previously hidden func (p *ProgressBarInfinite) Show() { p.running = true p.BaseWidget.Show() } // Hide this widget, if it was previously visible func (p *ProgressBarInfinite) Hide() { p.running = false p.BaseWidget.Hide() } // Start the infinite progress bar animation func (p *ProgressBarInfinite) Start() { if p.running { return } p.running = true p.BaseWidget.Refresh() } // Stop the infinite progress bar animation func (p *ProgressBarInfinite) Stop() { if !p.running { return } p.running = false p.BaseWidget.Refresh() } // Running returns the current state of the infinite progress animation func (p *ProgressBarInfinite) Running() bool { return p.running } // MinSize returns the size that this widget should not shrink below func (p *ProgressBarInfinite) MinSize() fyne.Size { p.ExtendBaseWidget(p) return p.BaseWidget.MinSize() } // CreateRenderer is a private method to Fyne which links this widget to its renderer func (p *ProgressBarInfinite) CreateRenderer() fyne.WidgetRenderer { p.ExtendBaseWidget(p) th := p.Theme() v := fyne.CurrentApp().Settings().ThemeVariant() primaryColor := th.Color(theme.ColorNamePrimary, v) cornerRadius := th.Size(theme.SizeNameInputRadius) render := &infProgressRenderer{ background: canvas.Rectangle{ FillColor: progressBlendColor(primaryColor), CornerRadius: cornerRadius, }, bar: canvas.Rectangle{ FillColor: primaryColor, CornerRadius: cornerRadius, }, progress: p, } render.SetObjects([]fyne.CanvasObject{&render.background, &render.bar}) p.running = true return render } // NewProgressBarInfinite creates a new progress bar widget that loops indefinitely from 0% -> 100% // SetValue() is not defined for infinite progress bar // To stop the looping progress and set the progress bar to 100%, call ProgressBarInfinite.Stop() func NewProgressBarInfinite() *ProgressBarInfinite { bar := &ProgressBarInfinite{} bar.ExtendBaseWidget(bar) return bar }