v0.28.0 3cb3770

loading_skeleton.go RawHistoryFolder

1package component
2
3import (
4	"sync/atomic"
5
6	"github.com/bnema/dumber/internal/ui/layout"
7	"github.com/bnema/puregotk/v4/gtk"
8)
9
10// LoadingSkeleton displays a themed loading placeholder.
11// It is intended to be embedded as an overlay while primary content is loading.
12// Shows a faded app logo, a discrete spinner, and the version below.
13type LoadingSkeleton struct {
14	container layout.BoxWidget
15	content   layout.BoxWidget
16	spinner   layout.SpinnerWidget
17	logo      layout.ImageWidget
18	version   layout.LabelWidget
19}
20
21const (
22	loadingSkeletonSpacing         = 6
23	loadingSkeletonLogoSize        = 256
24	loadingSkeletonSpinnerSize     = 32
25	loadingSkeletonVersionMaxChars = 16
26)
27
28// skeletonVersion is set via SetSkeletonVersion during startup.
29var skeletonVersion atomic.Value
30
31// SetSkeletonVersion sets the version string shown on loading skeletons.
32func SetSkeletonVersion(v string) {
33	skeletonVersion.Store(v)
34}
35
36func NewLoadingSkeleton(factory layout.WidgetFactory) *LoadingSkeleton {
37	container := factory.NewBox(layout.OrientationVertical, 0)
38	container.SetHexpand(true)
39	container.SetVexpand(true)
40	container.SetHalign(gtk.AlignFillValue)
41	container.SetValign(gtk.AlignFillValue)
42	container.SetCanFocus(false)
43	container.SetCanTarget(false)
44	container.AddCssClass("loading-skeleton")
45
46	content := factory.NewBox(layout.OrientationVertical, loadingSkeletonSpacing)
47	content.SetHalign(gtk.AlignCenterValue)
48	content.SetValign(gtk.AlignCenterValue)
49	content.SetCanFocus(false)
50	content.SetCanTarget(false)
51	content.AddCssClass("loading-skeleton-content")
52
53	// Faded app logo
54	logo := factory.NewImage()
55	logo.SetHalign(gtk.AlignCenterValue)
56	logo.SetValign(gtk.AlignCenterValue)
57	logo.SetCanFocus(false)
58	logo.SetCanTarget(false)
59	logo.SetSizeRequest(loadingSkeletonLogoSize, loadingSkeletonLogoSize)
60	logo.SetPixelSize(loadingSkeletonLogoSize)
61	logo.AddCssClass("loading-skeleton-logo")
62
63	// Set logo texture if available
64	if logoTexture := GetLogoTexture(); logoTexture != nil {
65		logo.SetFromPaintable(logoTexture)
66	}
67
68	// Discrete spinner between logo and version
69	spinner := factory.NewSpinner()
70	spinner.SetHalign(gtk.AlignCenterValue)
71	spinner.SetValign(gtk.AlignCenterValue)
72	spinner.SetCanFocus(false)
73	spinner.SetCanTarget(false)
74	spinner.SetSizeRequest(loadingSkeletonSpinnerSize, loadingSkeletonSpinnerSize)
75	spinner.AddCssClass("loading-skeleton-spinner")
76
77	// Version label below spinner
78	versionText, _ := skeletonVersion.Load().(string)
79	if versionText == "" {
80		versionText = "dev"
81	}
82	versionLabel := factory.NewLabel(versionText)
83	versionLabel.SetHalign(gtk.AlignCenterValue)
84	versionLabel.SetValign(gtk.AlignCenterValue)
85	versionLabel.SetCanFocus(false)
86	versionLabel.SetCanTarget(false)
87	versionLabel.SetMaxWidthChars(loadingSkeletonVersionMaxChars)
88	versionLabel.SetEllipsize(layout.EllipsizeEnd)
89	versionLabel.AddCssClass("loading-skeleton-version")
90
91	// Layout: logo, spinner, version (vertically centered)
92	content.Append(logo)
93	content.Append(spinner)
94	content.Append(versionLabel)
95	container.Append(content)
96
97	ls := &LoadingSkeleton{
98		container: container,
99		content:   content,
100		spinner:   spinner,
101		logo:      logo,
102		version:   versionLabel,
103	}
104	ls.SetVisible(true)
105	return ls
106}
107
108func (ls *LoadingSkeleton) Widget() layout.Widget {
109	if ls == nil {
110		return nil
111	}
112	return ls.container
113}
114
115func (ls *LoadingSkeleton) SetVisible(visible bool) {
116	if ls == nil || ls.container == nil {
117		return
118	}
119	ls.container.SetVisible(visible)
120	if ls.spinner != nil {
121		if visible {
122			ls.spinner.Start()
123		} else {
124			ls.spinner.Stop()
125		}
126	}
127}

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