v0.21.0 73bf644

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}

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