1package webkit
2
3import (
4 "github.com/diamondburned/gotk4/pkg/gdk/v4"
5 "github.com/diamondburned/gotk4/pkg/gio/v2"
6 "github.com/diamondburned/gotk4/pkg/glib/v2"
7 "github.com/diamondburned/gotk4/pkg/gtk/v4"
8)
9
10// IsNativeAvailable returns true if the native GTK/WebKit libraries are available
11// In gotk4, this is always true if the import succeeded
12func IsNativeAvailable() bool {
13 return true
14}
15
16// IdleAdd adds a function to be called in an idle callback
17// Returns an idle source ID that can be used with IdleRemove
18func IdleAdd(fn func() bool) uint {
19 return uint(glib.IdleAdd(fn))
20}
21
22// IdleRemove removes an idle callback by its source ID
23func IdleRemove(sourceID uint) {
24 glib.SourceRemove(glib.SourceHandle(sourceID))
25}
26
27// WidgetIsValid checks if a widget pointer is valid (non-nil)
28func WidgetIsValid(w gtk.Widgetter) bool {
29 return w != nil
30}
31
32// WidgetSetMargin sets all margins for a widget
33func WidgetSetMargin(w gtk.Widgetter, margin int) {
34 if widget := getWidget(w); widget != nil {
35 widget.SetMarginStart(margin)
36 widget.SetMarginEnd(margin)
37 widget.SetMarginTop(margin)
38 widget.SetMarginBottom(margin)
39 }
40}
41
42// WidgetSetMarginTop sets the top margin for a widget
43func WidgetSetMarginTop(w gtk.Widgetter, margin int) {
44 if widget := getWidget(w); widget != nil {
45 widget.SetMarginTop(margin)
46 }
47}
48
49// WidgetSetMarginBottom sets the bottom margin for a widget
50func WidgetSetMarginBottom(w gtk.Widgetter, margin int) {
51 if widget := getWidget(w); widget != nil {
52 widget.SetMarginBottom(margin)
53 }
54}
55
56// WidgetSetMarginStart sets the start margin for a widget
57func WidgetSetMarginStart(w gtk.Widgetter, margin int) {
58 if widget := getWidget(w); widget != nil {
59 widget.SetMarginStart(margin)
60 }
61}
62
63// WidgetSetMarginEnd sets the end margin for a widget
64func WidgetSetMarginEnd(w gtk.Widgetter, margin int) {
65 if widget := getWidget(w); widget != nil {
66 widget.SetMarginEnd(margin)
67 }
68}
69
70// WidgetAddCSSClass adds a CSS class to a widget
71func WidgetAddCSSClass(w gtk.Widgetter, className string) {
72 if widget := getWidget(w); widget != nil {
73 widget.AddCSSClass(className)
74 }
75}
76
77// WidgetRemoveCSSClass removes a CSS class from a widget
78func WidgetRemoveCSSClass(w gtk.Widgetter, className string) {
79 if widget := getWidget(w); widget != nil {
80 widget.RemoveCSSClass(className)
81 }
82}
83
84// WidgetHasCSSClass checks if a widget has a CSS class
85func WidgetHasCSSClass(w gtk.Widgetter, className string) bool {
86 if widget := getWidget(w); widget != nil {
87 return widget.HasCSSClass(className)
88 }
89 return false
90}
91
92// WidgetSetHExpand sets whether the widget should expand horizontally
93func WidgetSetHExpand(w gtk.Widgetter, expand bool) {
94 if widget := getWidget(w); widget != nil {
95 widget.SetHExpand(expand)
96 }
97}
98
99// WidgetSetVExpand sets whether the widget should expand vertically
100func WidgetSetVExpand(w gtk.Widgetter, expand bool) {
101 if widget := getWidget(w); widget != nil {
102 widget.SetVExpand(expand)
103 }
104}
105
106// WidgetRealizeInContainer realizes a widget within a container
107func WidgetRealizeInContainer(w gtk.Widgetter, container gtk.Widgetter) {
108 if widget := getWidget(w); widget != nil {
109 widget.Realize()
110 }
111}
112
113// WidgetRealize realizes a widget
114func WidgetRealize(w gtk.Widgetter) {
115 if widget := getWidget(w); widget != nil {
116 widget.Realize()
117 }
118}
119
120// WidgetGetParent returns the parent widget
121func WidgetGetParent(w gtk.Widgetter) gtk.Widgetter {
122 if widget := getWidget(w); widget != nil {
123 return widget.Parent()
124 }
125 return nil
126}
127
128// WidgetUnparent removes a widget from its parent
129func WidgetUnparent(w gtk.Widgetter) {
130 if widget := getWidget(w); widget != nil {
131 widget.Unparent()
132 }
133}
134
135// WidgetQueueAllocate queues an allocate operation
136func WidgetQueueAllocate(w gtk.Widgetter) {
137 if widget := getWidget(w); widget != nil {
138 widget.QueueAllocate()
139 }
140}
141
142// WidgetQueueDraw queues a redraw operation
143func WidgetQueueDraw(w gtk.Widgetter) {
144 if widget := getWidget(w); widget != nil {
145 widget.QueueDraw()
146 }
147}
148
149// WidgetGetAllocation returns the widget's allocated area
150func WidgetGetAllocation(w gtk.Widgetter) (x, y, width, height int) {
151 if widget := getWidget(w); widget != nil {
152 alloc := widget.Allocation()
153 return alloc.X(), alloc.Y(), alloc.Width(), alloc.Height()
154 }
155 return 0, 0, 0, 0
156}
157
158// WidgetGetBounds returns the widget's bounds relative to its parent
159func WidgetGetBounds(w gtk.Widgetter) (x, y, width, height float64) {
160 if widget := getWidget(w); widget != nil {
161 parent := widget.Parent()
162 if parent != nil {
163 rect, ok := widget.ComputeBounds(parent)
164 if ok && rect != nil {
165 // Get bounds directly from graphene.Rect
166 return float64(rect.X()), float64(rect.Y()),
167 float64(rect.Width()), float64(rect.Height())
168 }
169 }
170 }
171 return 0, 0, 0, 0
172}
173
174// WidgetGetWindowBounds returns the widget's bounds relative to the toplevel window
175// This provides window-absolute coordinates needed for navigation geometry calculations
176func WidgetGetWindowBounds(w gtk.Widgetter) (x, y, width, height int) {
177 if widget := getWidget(w); widget != nil {
178 // Get the toplevel window (GtkWindow)
179 root := widget.Root()
180 if root != nil {
181 rect, ok := widget.ComputeBounds(root)
182 if ok && rect != nil {
183 return int(rect.X()), int(rect.Y()),
184 int(rect.Width()), int(rect.Height())
185 }
186 }
187 }
188 return 0, 0, 0, 0
189}
190
191// WidgetSetFocusChild sets the focus child of a widget
192func WidgetSetFocusChild(w gtk.Widgetter, child gtk.Widgetter) {
193 if widget := getWidget(w); widget != nil {
194 widget.SetFocusChild(child)
195 }
196}
197
198// WidgetAddFocusController adds a focus event controller to a widget
199func WidgetAddFocusController(w gtk.Widgetter, onEnter, onLeave func()) *gtk.EventControllerFocus {
200 if widget := getWidget(w); widget != nil {
201 controller := gtk.NewEventControllerFocus()
202 if onEnter != nil {
203 controller.ConnectEnter(onEnter)
204 }
205 if onLeave != nil {
206 controller.ConnectLeave(onLeave)
207 }
208 widget.AddController(controller)
209 return controller
210 }
211 return nil
212}
213
214// WidgetRemoveFocusController removes a focus event controller from a widget
215func WidgetRemoveFocusController(w gtk.Widgetter, controller *gtk.EventControllerFocus) {
216 if widget := getWidget(w); widget != nil && controller != nil {
217 widget.RemoveController(controller)
218 }
219}
220
221// WidgetAddHoverHandler registers a hover callback for the given widget and
222// returns a controller that can be used to detach it later.
223func WidgetAddHoverHandler(w gtk.Widgetter, fn func()) *gtk.EventControllerMotion {
224 if widget := getWidget(w); widget != nil && fn != nil {
225 motion := gtk.NewEventControllerMotion()
226 motion.SetPropagationPhase(gtk.PhaseCapture)
227
228 // Connect to enter signal - triggered when mouse enters the widget
229 motion.ConnectEnter(func(x, y float64) {
230 fn()
231 })
232
233 widget.AddController(motion)
234 return motion
235 }
236 return nil
237}
238
239// WidgetRemoveHoverHandler removes a previously registered hover controller
240func WidgetRemoveHoverHandler(w gtk.Widgetter, controller *gtk.EventControllerMotion) {
241 if widget := getWidget(w); widget != nil && controller != nil {
242 widget.RemoveController(controller)
243 }
244}
245
246// WidgetAddController adds an event controller to a widget
247func WidgetAddController(w gtk.Widgetter, controller gtk.EventControllerer) {
248 if widget := getWidget(w); widget != nil && controller != nil {
249 widget.AddController(controller)
250 }
251}
252
253// WidgetAttachClickHandler attaches a left-click handler to a widget
254func WidgetAttachClickHandler(w gtk.Widgetter, callback func()) *gtk.GestureClick {
255 widget := getWidget(w)
256 if widget == nil {
257 return nil
258 }
259
260 gestureClick := gtk.NewGestureClick()
261 gestureClick.SetButton(1) // Left click only
262
263 gestureClick.ConnectPressed(func(nPress int, x, y float64) {
264 callback()
265 })
266
267 widget.AddController(gestureClick)
268 return gestureClick
269}
270
271// WidgetAllocation returns the widget's allocation (alias for WidgetGetAllocation)
272func WidgetAllocation(w gtk.Widgetter) (x, y, width, height int) {
273 return WidgetGetAllocation(w)
274}
275
276// PrefersDarkTheme checks if the system prefers dark theme
277func PrefersDarkTheme() bool {
278 // First try to read GNOME's color-scheme setting (GTK4+)
279 // Check if the schema exists before attempting to create GSettings object
280 // to avoid panics on non-GNOME systems (e.g., Sway, Hyprland, etc.)
281 var colorScheme string
282 schemaSource := gio.SettingsSchemaSourceGetDefault()
283 if schemaSource != nil {
284 schema := schemaSource.Lookup("org.gnome.desktop.interface", true)
285 if schema != nil {
286 if gnomeSettings := gio.NewSettings("org.gnome.desktop.interface"); gnomeSettings != nil {
287 colorScheme = gnomeSettings.String("color-scheme")
288 }
289 }
290 }
291
292 switch colorScheme {
293 case "prefer-dark":
294 return true
295 case "prefer-light":
296 return false
297 }
298
299 // Fallback to GTK's application-level preference
300 settings := gtk.SettingsGetDefault()
301 if settings != nil {
302 obj := settings.Object
303 if obj != nil {
304 prop := obj.ObjectProperty("gtk-application-prefer-dark-theme")
305 if v, ok := prop.(bool); ok && v {
306 return true
307 }
308 }
309 }
310 return false
311}
312
313// AddCSSProvider adds a CSS provider for styling
314func AddCSSProvider(css string) error {
315 provider := gtk.NewCSSProvider()
316 provider.LoadFromString(css)
317
318 gtk.StyleContextAddProviderForDisplay(
319 gdk.DisplayGetDefault(),
320 provider,
321 gtk.STYLE_PROVIDER_PRIORITY_APPLICATION,
322 )
323
324 return nil
325}