v0.20.0 c741a39

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}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close