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// WidgetSetFocusChild sets the focus child of a widget
175func WidgetSetFocusChild(w gtk.Widgetter, child gtk.Widgetter) {
176 if widget := getWidget(w); widget != nil {
177 widget.SetFocusChild(child)
178 }
179}
180
181// WidgetAddFocusController adds a focus event controller to a widget
182func WidgetAddFocusController(w gtk.Widgetter, onEnter, onLeave func()) *gtk.EventControllerFocus {
183 if widget := getWidget(w); widget != nil {
184 controller := gtk.NewEventControllerFocus()
185 if onEnter != nil {
186 controller.ConnectEnter(onEnter)
187 }
188 if onLeave != nil {
189 controller.ConnectLeave(onLeave)
190 }
191 widget.AddController(controller)
192 return controller
193 }
194 return nil
195}
196
197// WidgetRemoveFocusController removes a focus event controller from a widget
198func WidgetRemoveFocusController(w gtk.Widgetter, controller *gtk.EventControllerFocus) {
199 if widget := getWidget(w); widget != nil && controller != nil {
200 widget.RemoveController(controller)
201 }
202}
203
204// WidgetAddHoverHandler registers a hover callback for the given widget and
205// returns a controller that can be used to detach it later.
206func WidgetAddHoverHandler(w gtk.Widgetter, fn func()) *gtk.EventControllerMotion {
207 if widget := getWidget(w); widget != nil && fn != nil {
208 motion := gtk.NewEventControllerMotion()
209 motion.SetPropagationPhase(gtk.PhaseCapture)
210
211 // Connect to enter signal - triggered when mouse enters the widget
212 motion.ConnectEnter(func(x, y float64) {
213 fn()
214 })
215
216 widget.AddController(motion)
217 return motion
218 }
219 return nil
220}
221
222// WidgetRemoveHoverHandler removes a previously registered hover controller
223func WidgetRemoveHoverHandler(w gtk.Widgetter, controller *gtk.EventControllerMotion) {
224 if widget := getWidget(w); widget != nil && controller != nil {
225 widget.RemoveController(controller)
226 }
227}
228
229// WidgetAddController adds an event controller to a widget
230func WidgetAddController(w gtk.Widgetter, controller gtk.EventControllerer) {
231 if widget := getWidget(w); widget != nil && controller != nil {
232 widget.AddController(controller)
233 }
234}
235
236// WidgetAllocation returns the widget's allocation (alias for WidgetGetAllocation)
237func WidgetAllocation(w gtk.Widgetter) (x, y, width, height int) {
238 return WidgetGetAllocation(w)
239}
240
241// PrefersDarkTheme checks if the system prefers dark theme
242func PrefersDarkTheme() bool {
243 // First try to read GNOME's color-scheme setting (GTK4+)
244 // Check if the schema exists before attempting to create GSettings object
245 // to avoid panics on non-GNOME systems (e.g., Sway, Hyprland, etc.)
246 var colorScheme string
247 schemaSource := gio.SettingsSchemaSourceGetDefault()
248 if schemaSource != nil {
249 schema := schemaSource.Lookup("org.gnome.desktop.interface", true)
250 if schema != nil {
251 if gnomeSettings := gio.NewSettings("org.gnome.desktop.interface"); gnomeSettings != nil {
252 colorScheme = gnomeSettings.String("color-scheme")
253 }
254 }
255 }
256
257 switch colorScheme {
258 case "prefer-dark":
259 return true
260 case "prefer-light":
261 return false
262 }
263
264 // Fallback to GTK's application-level preference
265 settings := gtk.SettingsGetDefault()
266 if settings != nil {
267 obj := settings.Object
268 if obj != nil {
269 prop := obj.ObjectProperty("gtk-application-prefer-dark-theme")
270 if v, ok := prop.(bool); ok && v {
271 return true
272 }
273 }
274 }
275 return false
276}
277
278// AddCSSProvider adds a CSS provider for styling
279func AddCSSProvider(css string) error {
280 provider := gtk.NewCSSProvider()
281 provider.LoadFromString(css)
282
283 gtk.StyleContextAddProviderForDisplay(
284 gdk.DisplayGetDefault(),
285 provider,
286 gtk.STYLE_PROVIDER_PRIORITY_APPLICATION,
287 )
288
289 return nil
290}