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 // Set minimum size to prevent GTK warning about negative minimum width (-2)
47 // The internal "progress" gizmo needs valid dimensions before realization
48 // Using 0 for width (not -1) ensures GTK doesn't calculate negative sizes
49 progressBar.SetSizeRequest(0, 4)
50
51 // Don't intercept pointer events - let clicks pass through to WebView
52 progressBar.SetCanTarget(false)
53 progressBar.SetCanFocus(false)
54
55 // Initialize fraction to 0
56 progressBar.SetFraction(0)
57
58 // Hidden by default
59 progressBar.SetVisible(false)
60
61 return &ProgressBar{
62 progressBar: progressBar,
63 visible: false,
64 currentValue: 0,
65 targetValue: 0,
66 }
67}
68
69// SetProgress sets the target progress value and starts smooth animation.
70// progress should be between 0.0 and 1.0.
71func (pb *ProgressBar) SetProgress(progress float64) {
72 pb.mu.Lock()
73 defer pb.mu.Unlock()
74
75 // Clamp progress to valid range
76 if progress < 0 {
77 progress = 0
78 } else if progress > 1 {
79 progress = 1
80 }
81
82 pb.targetValue = progress
83
84 // For large jumps (>0.3) or completion, update immediately
85 diff := progress - pb.currentValue
86 if diff > 0.3 || progress >= 1.0 {
87 pb.currentValue = progress
88 pb.progressBar.SetFraction(progress)
89 return
90 }
91
92 // Start animation if not already running
93 if pb.animationTimer == 0 {
94 pb.startAnimation()
95 }
96}
97
98// startAnimation begins the smooth progress animation.
99// Must be called with lock held.
100func (pb *ProgressBar) startAnimation() {
101 cb := glib.SourceFunc(func(_ uintptr) bool {
102 pb.mu.Lock()
103 defer pb.mu.Unlock()
104
105 // Check if we've reached the target
106 if pb.currentValue >= pb.targetValue {
107 pb.animationTimer = 0
108 return false // Stop the timer
109 }
110
111 // Increment towards target
112 pb.currentValue += progressStep
113 if pb.currentValue > pb.targetValue {
114 pb.currentValue = pb.targetValue
115 }
116
117 pb.progressBar.SetFraction(pb.currentValue)
118
119 // Continue animation if not at target
120 if pb.currentValue >= pb.targetValue {
121 pb.animationTimer = 0 // Clear before GLib auto-removes
122 return false
123 }
124 return true
125 })
126
127 pb.animationTimer = glib.TimeoutAdd(progressIntervalMs, &cb, 0)
128}
129
130// Show makes the progress bar visible.
131func (pb *ProgressBar) Show() {
132 pb.mu.Lock()
133 defer pb.mu.Unlock()
134
135 if !pb.visible {
136 pb.visible = true
137 pb.progressBar.SetVisible(true)
138 }
139}
140
141// Hide makes the progress bar invisible and resets state.
142func (pb *ProgressBar) Hide() {
143 pb.mu.Lock()
144 defer pb.mu.Unlock()
145
146 if pb.visible {
147 pb.visible = false
148 pb.progressBar.SetVisible(false)
149
150 // Stop any running animation
151 if pb.animationTimer != 0 {
152 glib.SourceRemove(pb.animationTimer)
153 pb.animationTimer = 0
154 }
155
156 // Reset values
157 pb.currentValue = 0
158 pb.targetValue = 0
159 pb.progressBar.SetFraction(0)
160 }
161}
162
163// IsVisible returns whether the progress bar is currently visible.
164func (pb *ProgressBar) IsVisible() bool {
165 pb.mu.Lock()
166 defer pb.mu.Unlock()
167 return pb.visible
168}
169
170// Widget returns the underlying widget for embedding in overlays.
171func (pb *ProgressBar) Widget() layout.Widget {
172 return pb.progressBar
173}