link_status.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 // linkStatusShowDelayMs is the delay before showing the overlay to avoid flicker.
15 linkStatusShowDelayMs = 100
16 // linkStatusMaxChars is the maximum characters before truncation.
17 linkStatusMaxChars = 80
18)
19
20// LinkStatusOverlay displays the destination URL when hovering over links.
21// It appears in the bottom-left corner with a fade-in/fade-out transition.
22type LinkStatusOverlay struct {
23 factory layout.WidgetFactory
24 container layout.BoxWidget
25 label layout.LabelWidget
26
27 pendingURI string // URI waiting to be shown after delay
28 showTimer uint // GLib timer for delayed show
29 visible bool // Whether overlay is currently visible (has .visible class)
30 mu sync.Mutex
31}
32
33// NewLinkStatusOverlay creates a new link status overlay component.
34// The overlay is positioned at bottom-left with fade transitions via CSS.
35func NewLinkStatusOverlay(factory layout.WidgetFactory) *LinkStatusOverlay {
36 // Create container box for styling and positioning
37 container := factory.NewBox(layout.OrientationHorizontal, 0)
38 container.AddCssClass("link-status")
39
40 // Position at bottom-left
41 container.SetHalign(gtk.AlignStartValue)
42 container.SetValign(gtk.AlignEndValue)
43
44 // Don't expand to fill space
45 container.SetHexpand(false)
46 container.SetVexpand(false)
47
48 // Don't intercept pointer events - let clicks pass through
49 container.SetCanTarget(false)
50 container.SetCanFocus(false)
51
52 // Create label for URL text
53 label := factory.NewLabel("")
54 label.SetCanTarget(false)
55 label.SetCanFocus(false)
56 label.SetEllipsize(layout.EllipsizeMiddle) // Truncate in middle for URLs
57 label.SetMaxWidthChars(linkStatusMaxChars)
58 container.Append(label)
59
60 return &LinkStatusOverlay{
61 factory: factory,
62 container: container,
63 label: label,
64 visible: false,
65 }
66}
67
68// Show displays the link status overlay with the given URI.
69// If uri is empty, hides the overlay instead.
70// Uses a small delay to avoid flicker during rapid mouse movement.
71func (l *LinkStatusOverlay) Show(uri string) {
72 l.mu.Lock()
73 defer l.mu.Unlock()
74
75 // Cancel any pending show timer
76 if l.showTimer != 0 {
77 glib.SourceRemove(l.showTimer)
78 l.showTimer = 0
79 }
80
81 if uri == "" {
82 l.hide()
83 return
84 }
85
86 l.pendingURI = uri
87
88 // Start delay timer before showing
89 cb := glib.SourceFunc(func(_ uintptr) bool {
90 l.mu.Lock()
91 defer l.mu.Unlock()
92
93 if l.pendingURI != "" {
94 l.label.SetText(l.pendingURI)
95 if !l.visible {
96 l.visible = true
97 l.container.AddCssClass("visible")
98 }
99 }
100 l.showTimer = 0
101 return false // Don't repeat
102 })
103
104 l.showTimer = glib.TimeoutAdd(linkStatusShowDelayMs, &cb, 0)
105}
106
107// Hide manually hides the link status overlay.
108func (l *LinkStatusOverlay) Hide() {
109 l.mu.Lock()
110 defer l.mu.Unlock()
111 l.hide()
112}
113
114// hide performs the actual hide operation (must be called with lock held).
115func (l *LinkStatusOverlay) hide() {
116 // Cancel pending show timer
117 if l.showTimer != 0 {
118 glib.SourceRemove(l.showTimer)
119 l.showTimer = 0
120 }
121 l.pendingURI = ""
122
123 if l.visible {
124 l.visible = false
125 l.container.RemoveCssClass("visible")
126 }
127}
128
129// Widget returns the underlying widget for embedding in overlays.
130func (l *LinkStatusOverlay) Widget() layout.Widget {
131 return l.container
132}
133
134// IsVisible returns whether the overlay is currently visible.
135func (l *LinkStatusOverlay) IsVisible() bool {
136 l.mu.Lock()
137 defer l.mu.Unlock()
138 return l.visible
139}
140
141// Cleanup cancels any pending timers and clears state.
142// Must be called before removing the overlay from the UI.
143func (l *LinkStatusOverlay) Cleanup() {
144 l.mu.Lock()
145 defer l.mu.Unlock()
146
147 // Cancel pending show timer to prevent use-after-free
148 if l.showTimer != 0 {
149 glib.SourceRemove(l.showTimer)
150 l.showTimer = 0
151 }
152 l.pendingURI = ""
153 l.visible = false
154}