forked from fyne-io/fyne
/
progressbarinfinite.go
213 lines (176 loc) · 5.9 KB
/
progressbarinfinite.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
package widget
import (
"image/color"
"time"
"fyne.io/fyne"
"fyne.io/fyne/canvas"
"fyne.io/fyne/internal/cache"
"fyne.io/fyne/internal/widget"
"fyne.io/fyne/theme"
)
const (
infiniteRefreshRate = 50 * time.Millisecond
maxProgressBarInfiniteWidthRatio = 1.0 / 5
minProgressBarInfiniteWidthRatio = 1.0 / 20
progressBarInfiniteStepSizeRatio = 1.0 / 50
)
type infProgressRenderer struct {
widget.BaseRenderer
bar *canvas.Rectangle
ticker *time.Ticker
running bool
progress *ProgressBarInfinite
}
// MinSize calculates the minimum size of a progress bar.
func (p *infProgressRenderer) MinSize() fyne.Size {
// this is to create the same size infinite progress bar as regular progress bar
text := fyne.MeasureText("100%", theme.TextSize(), fyne.TextStyle{})
return fyne.NewSize(text.Width+theme.Padding()*4, text.Height+theme.Padding()*2)
}
func (p *infProgressRenderer) updateBar() {
progressSize := p.progress.Size()
barWidth := p.bar.Size().Width
barPos := p.bar.Position()
maxBarWidth := int(float64(progressSize.Width) * maxProgressBarInfiniteWidthRatio)
minBarWidth := int(float64(progressSize.Width) * minProgressBarInfiniteWidthRatio)
stepSize := int(float64(progressSize.Width) * progressBarInfiniteStepSizeRatio)
// check to make sure inner bar is sized correctly
// if bar is on the first half of the progress bar, grow it up to maxProgressBarInfiniteWidthPercent
// if on the second half of the progress bar width, shrink it down until it gets to minProgressBarInfiniteWidthPercent
if barWidth < maxBarWidth && barPos.X+barWidth < progressSize.Width/2 {
// slightly increase width
newBoxSize := fyne.Size{Width: barWidth + stepSize, Height: progressSize.Height}
p.bar.Resize(newBoxSize)
} else {
barPos.X += stepSize
if barWidth <= minBarWidth {
// loop around to start when bar goes to end
barPos.X = 0
stepSize = 0
newBoxSize := fyne.Size{Width: minBarWidth, Height: progressSize.Height}
p.bar.Resize(newBoxSize)
} else if barPos.X+barWidth > progressSize.Width {
// crop to the end of the bar
barWidth = progressSize.Width - barPos.X
newBoxSize := fyne.Size{Width: barWidth, Height: progressSize.Height}
p.bar.Resize(newBoxSize)
}
}
p.bar.Move(barPos)
}
// Layout the components of the infinite progress bar
func (p *infProgressRenderer) Layout(size fyne.Size) {
p.updateBar()
}
func (p *infProgressRenderer) BackgroundColor() color.Color {
return theme.ShadowColor()
}
// Refresh updates the size and position of the horizontal scrolling infinite progress bar
func (p *infProgressRenderer) Refresh() {
if p.isRunning() {
return // we refresh from the goroutine
}
p.doRefresh()
}
func (p *infProgressRenderer) doRefresh() {
p.bar.FillColor = theme.PrimaryColor()
p.updateBar()
canvas.Refresh(p.progress.super())
}
func (p *infProgressRenderer) isRunning() bool {
p.progress.propertyLock.RLock()
defer p.progress.propertyLock.RUnlock()
return p.running
}
// Start the infinite progress bar background thread to update it continuously
func (p *infProgressRenderer) start() {
if !p.isRunning() {
p.progress.propertyLock.Lock()
defer p.progress.propertyLock.Unlock()
p.ticker = time.NewTicker(infiniteRefreshRate)
p.running = true
go p.infiniteProgressLoop()
}
}
// Stop the infinite progress goroutine and sets value to the Max
func (p *infProgressRenderer) stop() {
p.progress.propertyLock.Lock()
defer p.progress.propertyLock.Unlock()
p.running = false
}
// infiniteProgressLoop should be called as a goroutine to update the inner infinite progress bar
// the function can be exited by calling Stop()
func (p *infProgressRenderer) infiniteProgressLoop() {
for p.isRunning() {
p.progress.propertyLock.RLock()
ticker := p.ticker.C
p.progress.propertyLock.RUnlock()
select {
case <-ticker:
p.doRefresh()
}
}
p.progress.propertyLock.RLock()
defer p.progress.propertyLock.RUnlock()
if p.ticker != nil {
p.ticker.Stop()
}
}
func (p *infProgressRenderer) Destroy() {
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
}
// Show this widget, if it was previously hidden
func (p *ProgressBarInfinite) Show() {
p.Start()
p.BaseWidget.Show()
}
// Hide this widget, if it was previously visible
func (p *ProgressBarInfinite) Hide() {
p.Stop()
p.BaseWidget.Hide()
}
// Start the infinite progress bar background thread to update it continuously
func (p *ProgressBarInfinite) Start() {
cache.Renderer(p).(*infProgressRenderer).start()
}
// Stop the infinite progress goroutine and sets value to the Max
func (p *ProgressBarInfinite) Stop() {
cache.Renderer(p).(*infProgressRenderer).stop()
}
// Running returns the current state of the infinite progress animation
func (p *ProgressBarInfinite) Running() bool {
if !cache.IsRendered(p) {
return false
}
return cache.Renderer(p).(*infProgressRenderer).isRunning()
}
// 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)
bar := canvas.NewRectangle(theme.PrimaryColor())
render := &infProgressRenderer{
BaseRenderer: widget.NewBaseRenderer([]fyne.CanvasObject{bar}),
bar: bar,
progress: p,
}
render.start()
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 {
p := &ProgressBarInfinite{}
Renderer(p).Layout(p.MinSize())
return p
}