v0.14.1 0da30ba

helpers.go RawHistoryFolder

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}

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