loading_skeleton.go RawHistoryFolder
1package component
2
3import (
4 "sync/atomic"
5
6 "github.com/bnema/dumber/internal/ui/layout"
7 "github.com/jwijenbergh/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}