gtk: update URLWidget to use zig-gobject

Also move URLWidget to a separate file to cut down on the size of
Surface.zig.
This commit is contained in:
Jeffrey C. Ollie
2025-03-01 10:02:16 -06:00
parent efc1b10bfd
commit 5d5a632a89
2 changed files with 121 additions and 95 deletions

View File

@ -4,6 +4,7 @@
const Surface = @This(); const Surface = @This();
const std = @import("std"); const std = @import("std");
const adw = @import("adw"); const adw = @import("adw");
const gtk = @import("gtk"); const gtk = @import("gtk");
const gio = @import("gio"); const gio = @import("gio");
@ -28,6 +29,7 @@ const Window = @import("Window.zig");
const Menu = @import("menu.zig").Menu; const Menu = @import("menu.zig").Menu;
const ClipboardConfirmationWindow = @import("ClipboardConfirmationWindow.zig"); const ClipboardConfirmationWindow = @import("ClipboardConfirmationWindow.zig");
const ResizeOverlay = @import("ResizeOverlay.zig"); const ResizeOverlay = @import("ResizeOverlay.zig");
const URLWidget = @import("URLWidget.zig");
const inspector = @import("inspector.zig"); const inspector = @import("inspector.zig");
const gtk_key = @import("key.zig"); const gtk_key = @import("key.zig");
const c = @import("c.zig").c; const c = @import("c.zig").c;
@ -219,99 +221,6 @@ pub const Container = union(enum) {
} }
}; };
/// Represents the URL hover widgets that show the hovered URL.
/// To explain a bit how this all works since its split across a few places:
/// We create a left/right pair of labels. The left label is shown by default,
/// and the right label is hidden. When the mouse enters the left label, we
/// show the right label. When the mouse leaves the left label, we hide the
/// right label.
///
/// The hover and styling is done with a combination of GTK event controllers
/// and CSS in style.css.
pub const URLWidget = struct {
left: *c.GtkWidget,
right: *c.GtkWidget,
pub fn init(surface: *const Surface, str: [:0]const u8) URLWidget {
// Create the left
const left = c.gtk_label_new(str.ptr);
c.gtk_label_set_ellipsize(@ptrCast(left), c.PANGO_ELLIPSIZE_MIDDLE);
c.gtk_widget_add_css_class(@ptrCast(left), "view");
c.gtk_widget_add_css_class(@ptrCast(left), "url-overlay");
c.gtk_widget_add_css_class(@ptrCast(left), "left");
c.gtk_widget_set_halign(left, c.GTK_ALIGN_START);
c.gtk_widget_set_valign(left, c.GTK_ALIGN_END);
// Create the right
const right = c.gtk_label_new(str.ptr);
c.gtk_label_set_ellipsize(@ptrCast(right), c.PANGO_ELLIPSIZE_MIDDLE);
c.gtk_widget_add_css_class(@ptrCast(right), "hidden");
c.gtk_widget_add_css_class(@ptrCast(right), "view");
c.gtk_widget_add_css_class(@ptrCast(right), "url-overlay");
c.gtk_widget_add_css_class(@ptrCast(right), "right");
c.gtk_widget_set_halign(right, c.GTK_ALIGN_END);
c.gtk_widget_set_valign(right, c.GTK_ALIGN_END);
// Setup our mouse hover event for the left
const ec_motion = c.gtk_event_controller_motion_new();
errdefer c.g_object_unref(ec_motion);
c.gtk_widget_add_controller(@ptrCast(left), ec_motion);
_ = c.g_signal_connect_data(
ec_motion,
"enter",
c.G_CALLBACK(&gtkLeftEnter),
right,
null,
c.G_CONNECT_DEFAULT,
);
_ = c.g_signal_connect_data(
ec_motion,
"leave",
c.G_CALLBACK(&gtkLeftLeave),
right,
null,
c.G_CONNECT_DEFAULT,
);
// Show it
c.gtk_overlay_add_overlay(surface.overlay, left);
c.gtk_overlay_add_overlay(surface.overlay, right);
return .{
.left = left,
.right = right,
};
}
pub fn deinit(self: *URLWidget, overlay: *c.GtkOverlay) void {
c.gtk_overlay_remove_overlay(overlay, @ptrCast(self.left));
c.gtk_overlay_remove_overlay(overlay, @ptrCast(self.right));
}
pub fn setText(self: *const URLWidget, str: [:0]const u8) void {
c.gtk_label_set_text(@ptrCast(self.left), str.ptr);
c.gtk_label_set_text(@ptrCast(self.right), str.ptr);
}
fn gtkLeftEnter(
_: *c.GtkEventControllerMotion,
_: c.gdouble,
_: c.gdouble,
ud: ?*anyopaque,
) callconv(.C) void {
const right: *c.GtkWidget = @ptrCast(@alignCast(ud orelse return));
c.gtk_widget_remove_css_class(@ptrCast(right), "hidden");
}
fn gtkLeftLeave(
_: *c.GtkEventControllerMotion,
ud: ?*anyopaque,
) callconv(.C) void {
const right: *c.GtkWidget = @ptrCast(@alignCast(ud orelse return));
c.gtk_widget_add_css_class(@ptrCast(right), "hidden");
}
};
/// Whether the surface has been realized or not yet. When a surface is /// Whether the surface has been realized or not yet. When a surface is
/// "realized" it means that the OpenGL context is ready and the core /// "realized" it means that the OpenGL context is ready and the core
/// surface has been initialized. /// surface has been initialized.
@ -1169,7 +1078,8 @@ pub fn setMouseVisibility(self: *Surface, visible: bool) void {
pub fn mouseOverLink(self: *Surface, uri_: ?[]const u8) void { pub fn mouseOverLink(self: *Surface, uri_: ?[]const u8) void {
const uri = uri_ orelse { const uri = uri_ orelse {
if (self.url_widget) |*widget| { if (self.url_widget) |*widget| {
widget.deinit(self.overlay); // FIXME: when Surface gets converted to zig-gobject
widget.deinit(@ptrCast(@alignCast(self.overlay)));
self.url_widget = null; self.url_widget = null;
} }
@ -1187,7 +1097,8 @@ pub fn mouseOverLink(self: *Surface, uri_: ?[]const u8) void {
return; return;
} }
self.url_widget = URLWidget.init(self, uriZ); // FIXME: when Surface gets converted to zig-gobject
self.url_widget = URLWidget.init(@ptrCast(@alignCast(self.overlay)), uriZ);
} }
pub fn supportsClipboard( pub fn supportsClipboard(

115
src/apprt/gtk/URLWidget.zig Normal file
View File

@ -0,0 +1,115 @@
//! Represents the URL hover widgets that show the hovered URL.
//!
//! To explain a bit how this all works since its split across a few places:
//! We create a left/right pair of labels. The left label is shown by default,
//! and the right label is hidden. When the mouse enters the left label, we
//! show the right label. When the mouse leaves the left label, we hide the
//! right label.
//!
//! The hover and styling is done with a combination of GTK event controllers
//! and CSS in style.css.
const URLWidget = @This();
const gtk = @import("gtk");
/// The label that appears on the bottom left.
left: *gtk.Label,
/// The label that appears on the bottom right.
right: *gtk.Label,
pub fn init(
/// The overlay that we will attach our labels to.
overlay: *gtk.Overlay,
/// The URL to display.
str: [:0]const u8,
) URLWidget {
// Create the left
const left = left: {
const left = gtk.Label.new(str.ptr);
left.setEllipsize(.middle);
const widget = left.as(gtk.Widget);
widget.addCssClass("view");
widget.addCssClass("url-overlay");
widget.addCssClass("left");
widget.setHalign(.start);
widget.setValign(.end);
break :left left;
};
// Create the right
const right = right: {
const right = gtk.Label.new(str.ptr);
right.setEllipsize(.middle);
const widget = right.as(gtk.Widget);
widget.addCssClass("hidden");
widget.addCssClass("view");
widget.addCssClass("url-overlay");
widget.addCssClass("right");
widget.setHalign(.end);
widget.setValign(.end);
break :right right;
};
// Setup our mouse hover event controller for the left label.
const ec_motion = gtk.EventControllerMotion.new();
errdefer ec_motion.unref();
left.as(gtk.Widget).addController(ec_motion.as(gtk.EventController));
_ = gtk.EventControllerMotion.signals.enter.connect(
ec_motion,
*gtk.Label,
gtkLeftEnter,
right,
.{},
);
_ = gtk.EventControllerMotion.signals.leave.connect(
ec_motion,
*gtk.Label,
gtkLeftLeave,
right,
.{},
);
// Show it
overlay.addOverlay(left.as(gtk.Widget));
overlay.addOverlay(right.as(gtk.Widget));
return .{
.left = left,
.right = right,
};
}
/// Remove our labels from the overlay.
pub fn deinit(self: *URLWidget, overlay: *gtk.Overlay) void {
overlay.removeOverlay(self.left.as(gtk.Widget));
overlay.removeOverlay(self.right.as(gtk.Widget));
}
/// Change the URL that is displayed.
pub fn setText(self: *const URLWidget, str: [:0]const u8) void {
self.left.setText(str.ptr);
self.right.setText(str.ptr);
}
/// Callback for when the mouse enters the left label. That means that we should
/// show the right label. CSS will handle hiding the left label.
fn gtkLeftEnter(
_: *gtk.EventControllerMotion,
_: f64,
_: f64,
right: *gtk.Label,
) callconv(.C) void {
right.as(gtk.Widget).removeCssClass("hidden");
}
/// Callback for when the mouse leaves the left label. That means that we should
/// hide the right label. CSS will handle showing the left label.
fn gtkLeftLeave(
_: *gtk.EventControllerMotion,
right: *gtk.Label,
) callconv(.C) void {
right.as(gtk.Widget).addCssClass("hidden");
}