progress_bar.go RawHistoryFolder
1// Package component provides UI components for the browser.
2package component
3
4import (
5 "sync"
6
7 "github.com/jwijenbergh/puregotk/v4/glib"
8 "github.com/jwijenbergh/puregotk/v4/gtk"
9
10 "github.com/bnema/dumber/internal/ui/layout"
11)
12
13const (
14 // Animation step size - how much to increment per frame
15 progressStep = 0.02
16 // Animation interval in milliseconds
17 progressIntervalMs = 16 // ~60fps
18)
19
20// ProgressBar displays a slim loading progress indicator at the bottom of a pane.
21// Uses native GtkProgressBar with "osd" styling for overlay appearance.
22// Implements smooth animation by incrementing towards the target value.
23type ProgressBar struct {
24 progressBar layout.ProgressBarWidget
25
26 visible bool
27 currentValue float64 // Current displayed value
28 targetValue float64 // Target value to animate towards
29 animationTimer uint // Timer source ID for animation
30
31 mu sync.Mutex
32}
33
34// NewProgressBar creates a new progress bar component using the widget factory.
35func NewProgressBar(factory layout.WidgetFactory) *ProgressBar {
36 progressBar := factory.NewProgressBar()
37
38 // Add "osd" class for on-screen-display overlay styling (like Epiphany)
39 progressBar.AddCssClass("osd")
40
41 // Position at bottom, full width
42 progressBar.SetValign(gtk.AlignEndValue)
43 progressBar.SetHalign(gtk.AlignFillValue)
44 progressBar.SetHexpand(true)
45
46 // Don't intercept pointer events - let clicks pass through to WebView
47 progressBar.SetCanTarget(false)
48 progressBar.SetCanFocus(false)
49
50 // Initialize fraction to 0 to force proper sizing of internal progress gizmo
51 // This prevents GTK warning about negative minimum width
52 progressBar.SetFraction(0)
53
54 // Hidden by default
55 progressBar.SetVisible(false)
56
57 return &ProgressBar{
58 progressBar: progressBar,
59 visible: false,
60 currentValue: 0,
61 targetValue: 0,
62 }
63}
64
65// SetProgress sets the target progress value and starts smooth animation.
66// progress should be between 0.0 and 1.0.
67func (pb *ProgressBar) SetProgress(progress float64) {
68 pb.mu.Lock()
69 defer pb.mu.Unlock()
70
71 // Clamp progress to valid range
72 if progress < 0 {
73 progress = 0
74 } else if progress > 1 {
75 progress = 1
76 }
77
78 pb.targetValue = progress
79
80 // For large jumps (>0.3) or completion, update immediately
81 diff := progress - pb.currentValue
82 if diff > 0.3 || progress >= 1.0 {
83 pb.currentValue = progress
84 pb.progressBar.SetFraction(progress)
85 return
86 }
87
88 // Start animation if not already running
89 if pb.animationTimer == 0 {
90 pb.startAnimation()
91 }
92}
93
94// startAnimation begins the smooth progress animation.
95// Must be called with lock held.
96func (pb *ProgressBar) startAnimation() {
97 cb := glib.SourceFunc(func(_ uintptr) bool {
98 pb.mu.Lock()
99 defer pb.mu.Unlock()
100
101 // Check if we've reached the target
102 if pb.currentValue >= pb.targetValue {
103 pb.animationTimer = 0
104 return false // Stop the timer
105 }
106
107 // Increment towards target
108 pb.currentValue += progressStep
109 if pb.currentValue > pb.targetValue {
110 pb.currentValue = pb.targetValue
111 }
112
113 pb.progressBar.SetFraction(pb.currentValue)
114
115 // Continue animation if not at target
116 if pb.currentValue >= pb.targetValue {
117 pb.animationTimer = 0 // Clear before GLib auto-removes
118 return false
119 }
120 return true
121 })
122
123 pb.animationTimer = glib.TimeoutAdd(progressIntervalMs, &cb, 0)
124}
125
126// Show makes the progress bar visible.
127func (pb *ProgressBar) Show() {
128 pb.mu.Lock()
129 defer pb.mu.Unlock()
130
131 if !pb.visible {
132 pb.visible = true
133 pb.progressBar.SetVisible(true)
134 }
135}
136
137// Hide makes the progress bar invisible and resets state.
138func (pb *ProgressBar) Hide() {
139 pb.mu.Lock()
140 defer pb.mu.Unlock()
141
142 if pb.visible {
143 pb.visible = false
144 pb.progressBar.SetVisible(false)
145
146 // Stop any running animation
147 if pb.animationTimer != 0 {
148 glib.SourceRemove(pb.animationTimer)
149 pb.animationTimer = 0
150 }
151
152 // Reset values
153 pb.currentValue = 0
154 pb.targetValue = 0
155 pb.progressBar.SetFraction(0)
156 }
157}
158
159// IsVisible returns whether the progress bar is currently visible.
160func (pb *ProgressBar) IsVisible() bool {
161 pb.mu.Lock()
162 defer pb.mu.Unlock()
163 return pb.visible
164}
165
166// Widget returns the underlying widget for embedding in overlays.
167func (pb *ProgressBar) Widget() layout.Widget {
168 return pb.progressBar
169}