gtk: update ResizeOverlay for zig-gobject (#5941)

Also switch to a "DerivedConfig" model so that we aren't referring to a
global copy of the config.
This commit is contained in:
Tristan Partin
2025-02-22 20:07:20 -06:00
committed by GitHub
2 changed files with 96 additions and 80 deletions

View File

@ -1,24 +1,45 @@
const ResizeOverlay = @This(); const ResizeOverlay = @This();
const std = @import("std"); const std = @import("std");
const c = @import("c.zig").c;
const glib = @import("glib");
const gtk = @import("gtk");
const configpkg = @import("../../config.zig"); const configpkg = @import("../../config.zig");
const Surface = @import("Surface.zig"); const Surface = @import("Surface.zig");
const log = std.log.scoped(.gtk); const log = std.log.scoped(.gtk);
/// Back reference to the surface we belong to /// local copy of configuration data
surface: ?*Surface = null, const DerivedConfig = struct {
resize_overlay: configpkg.Config.ResizeOverlay,
resize_overlay_position: configpkg.Config.ResizeOverlayPosition,
resize_overlay_duration: configpkg.Config.Duration,
pub fn init(config: *const configpkg.Config) DerivedConfig {
return .{
.resize_overlay = config.@"resize-overlay",
.resize_overlay_position = config.@"resize-overlay-position",
.resize_overlay_duration = config.@"resize-overlay-duration",
};
}
};
/// the surface that we are attached to
surface: *Surface,
/// a copy of the configuration that we need to operate
config: DerivedConfig,
/// If non-null this is the widget on the overlay that shows the size of the /// If non-null this is the widget on the overlay that shows the size of the
/// surface when it is resized. /// surface when it is resized.
widget: ?*c.GtkWidget = null, label: ?*gtk.Label = null,
/// If non-null this is a timer for dismissing the resize overlay. /// If non-null this is a timer for dismissing the resize overlay.
timer: ?c.guint = null, timer: ?c_uint = null,
/// If non-null this is a timer for dismissing the resize overlay. /// If non-null this is a timer for dismissing the resize overlay.
idler: ?c.guint = null, idler: ?c_uint = null,
/// If true, the next resize event will be the first one. /// If true, the next resize event will be the first one.
first: bool = true, first: bool = true,
@ -26,24 +47,29 @@ first: bool = true,
/// Initialize the ResizeOverlay. This doesn't do anything more than save a /// Initialize the ResizeOverlay. This doesn't do anything more than save a
/// pointer to the surface that we are a part of as all of the widget creation /// pointer to the surface that we are a part of as all of the widget creation
/// is done later. /// is done later.
pub fn init(surface: *Surface) ResizeOverlay { pub fn init(self: *ResizeOverlay, surface: *Surface, config: *const configpkg.Config) void {
return .{ self.* = .{
.surface = surface, .surface = surface,
.config = DerivedConfig.init(config),
}; };
} }
pub fn updateConfig(self: *ResizeOverlay, config: *const configpkg.Config) void {
self.config = DerivedConfig.init(config);
}
/// De-initialize the ResizeOverlay. This removes any pending idlers/timers that /// De-initialize the ResizeOverlay. This removes any pending idlers/timers that
/// may not have fired yet. /// may not have fired yet.
pub fn deinit(self: *ResizeOverlay) void { pub fn deinit(self: *ResizeOverlay) void {
if (self.idler) |idler| { if (self.idler) |idler| {
if (c.g_source_remove(idler) == c.FALSE) { if (glib.Source.remove(idler) == 0) {
log.warn("unable to remove resize overlay idler", .{}); log.warn("unable to remove resize overlay idler", .{});
} }
self.idler = null; self.idler = null;
} }
if (self.timer) |timer| { if (self.timer) |timer| {
if (c.g_source_remove(timer) == c.FALSE) { if (glib.Source.remove(timer) == 0) {
log.warn("unable to remove resize overlay timer", .{}); log.warn("unable to remove resize overlay timer", .{});
} }
self.timer = null; self.timer = null;
@ -56,12 +82,7 @@ pub fn deinit(self: *ResizeOverlay) void {
/// ///
/// If we're not configured to show the overlay, do nothing. /// If we're not configured to show the overlay, do nothing.
pub fn maybeShow(self: *ResizeOverlay) void { pub fn maybeShow(self: *ResizeOverlay) void {
const surface = self.surface orelse { switch (self.config.resize_overlay) {
log.err("resize overlay configured without a surface", .{});
return;
};
switch (surface.app.config.@"resize-overlay") {
.never => return, .never => return,
.always => {}, .always => {},
.@"after-first" => if (self.first) { .@"after-first" => if (self.first) {
@ -78,23 +99,18 @@ pub fn maybeShow(self: *ResizeOverlay) void {
// results in a lot of warnings from GTK and _horrible_ flickering of the // results in a lot of warnings from GTK and _horrible_ flickering of the
// resize overlay. // resize overlay.
if (self.idler != null) return; if (self.idler != null) return;
self.idler = c.g_idle_add(gtkUpdate, @ptrCast(self)); self.idler = glib.idleAdd(gtkUpdate, self);
} }
/// Actually update the overlay widget. This should only be called from a GTK /// Actually update the overlay widget. This should only be called from a GTK
/// idle handler. /// idle handler.
fn gtkUpdate(ud: ?*anyopaque) callconv(.C) c.gboolean { fn gtkUpdate(ud: ?*anyopaque) callconv(.C) c_int {
const self: *ResizeOverlay = @ptrCast(@alignCast(ud)); const self: *ResizeOverlay = @ptrCast(@alignCast(ud orelse return 0));
// No matter what our idler is complete with this callback // No matter what our idler is complete with this callback
self.idler = null; self.idler = null;
const surface = self.surface orelse { const grid_size = self.surface.core_surface.size.grid();
log.err("resize overlay configured without a surface", .{});
return c.FALSE;
};
const grid_size = surface.core_surface.size.grid();
var buf: [32]u8 = undefined; var buf: [32]u8 = undefined;
const text = std.fmt.bufPrintZ( const text = std.fmt.bufPrintZ(
&buf, &buf,
@ -105,88 +121,86 @@ fn gtkUpdate(ud: ?*anyopaque) callconv(.C) c.gboolean {
}, },
) catch |err| { ) catch |err| {
log.err("unable to format text: {}", .{err}); log.err("unable to format text: {}", .{err});
return c.FALSE; return 0;
}; };
if (self.widget) |widget| { if (self.label) |label| {
// The resize overlay widget already exists, just update it. // The resize overlay widget already exists, just update it.
c.gtk_label_set_text(@ptrCast(widget), text.ptr); label.setText(text.ptr);
setPosition(widget, &surface.app.config); setPosition(label, &self.config);
show(widget); show(label);
} else { } else {
// Create the resize overlay widget. // Create the resize overlay widget.
const widget = c.gtk_label_new(text.ptr); const label = gtk.Label.new(text.ptr);
label.setJustify(gtk.Justification.center);
label.setSelectable(0);
setPosition(label, &self.config);
c.gtk_widget_add_css_class(widget, "view"); const widget = label.as(gtk.Widget);
c.gtk_widget_add_css_class(widget, "size-overlay"); widget.addCssClass("view");
c.gtk_widget_set_focusable(widget, c.FALSE); widget.addCssClass("size-overlay");
c.gtk_widget_set_can_target(widget, c.FALSE); widget.setFocusable(0);
c.gtk_label_set_justify(@ptrCast(widget), c.GTK_JUSTIFY_CENTER); widget.setCanTarget(0);
c.gtk_label_set_selectable(@ptrCast(widget), c.FALSE);
setPosition(widget, &surface.app.config);
c.gtk_overlay_add_overlay(surface.overlay, widget); const overlay: *gtk.Overlay = @ptrCast(@alignCast(self.surface.overlay));
overlay.addOverlay(widget);
self.widget = widget; self.label = label;
} }
if (self.timer) |timer| { if (self.timer) |timer| {
if (c.g_source_remove(timer) == c.FALSE) { if (glib.Source.remove(timer) == 0) {
log.warn("unable to remove size overlay timer", .{}); log.warn("unable to remove size overlay timer", .{});
} }
} }
self.timer = c.g_timeout_add(
surface.app.config.@"resize-overlay-duration".asMilliseconds(), self.timer = glib.timeoutAdd(
self.surface.app.config.@"resize-overlay-duration".asMilliseconds(),
gtkTimerExpired, gtkTimerExpired,
@ptrCast(self), self,
); );
return c.FALSE; return 0;
} }
// This should only be called from a GTK idle handler or timer. // This should only be called from a GTK idle handler or timer.
fn show(widget: *c.GtkWidget) void { fn show(label: *gtk.Label) void {
// The CSS class is used only by libadwaita. const widget = label.as(gtk.Widget);
c.gtk_widget_remove_css_class(@ptrCast(widget), "hidden"); widget.removeCssClass("hidden");
// Set the visibility for non-libadwaita usage.
c.gtk_widget_set_visible(@ptrCast(widget), 1);
} }
// This should only be called from a GTK idle handler or timer. // This should only be called from a GTK idle handler or timer.
fn hide(widget: *c.GtkWidget) void { fn hide(label: *gtk.Label) void {
// The CSS class is used only by libadwaita. const widget = label.as(gtk.Widget);
c.gtk_widget_add_css_class(widget, "hidden"); widget.addCssClass("hidden");
// Set the visibility for non-libadwaita usage.
c.gtk_widget_set_visible(widget, c.FALSE);
} }
/// Update the position of the resize overlay widget. It might seem excessive to /// Update the position of the resize overlay widget. It might seem excessive to
/// do this often, but it should make hot config reloading of the position work. /// do this often, but it should make hot config reloading of the position work.
/// This should only be called from a GTK idle handler. /// This should only be called from a GTK idle handler.
fn setPosition(widget: *c.GtkWidget, config: *configpkg.Config) void { fn setPosition(label: *gtk.Label, config: *DerivedConfig) void {
c.gtk_widget_set_halign( const widget = label.as(gtk.Widget);
@ptrCast(widget), widget.setHalign(
switch (config.@"resize-overlay-position") { switch (config.resize_overlay_position) {
.center, .@"top-center", .@"bottom-center" => c.GTK_ALIGN_CENTER, .center, .@"top-center", .@"bottom-center" => gtk.Align.center,
.@"top-left", .@"bottom-left" => c.GTK_ALIGN_START, .@"top-left", .@"bottom-left" => gtk.Align.start,
.@"top-right", .@"bottom-right" => c.GTK_ALIGN_END, .@"top-right", .@"bottom-right" => gtk.Align.end,
}, },
); );
c.gtk_widget_set_valign( widget.setValign(
@ptrCast(widget), switch (config.resize_overlay_position) {
switch (config.@"resize-overlay-position") { .center => gtk.Align.center,
.center => c.GTK_ALIGN_CENTER, .@"top-left", .@"top-center", .@"top-right" => gtk.Align.start,
.@"top-left", .@"top-center", .@"top-right" => c.GTK_ALIGN_START, .@"bottom-left", .@"bottom-center", .@"bottom-right" => gtk.Align.end,
.@"bottom-left", .@"bottom-center", .@"bottom-right" => c.GTK_ALIGN_END,
}, },
); );
} }
/// If this fires, it means that the delay period has expired and the resize /// If this fires, it means that the delay period has expired and the resize
/// overlay widget should be hidden. /// overlay widget should be hidden.
fn gtkTimerExpired(ud: ?*anyopaque) callconv(.C) c.gboolean { fn gtkTimerExpired(ud: ?*anyopaque) callconv(.C) c_int {
const self: *ResizeOverlay = @ptrCast(@alignCast(ud)); const self: *ResizeOverlay = @ptrCast(@alignCast(ud orelse return 0));
self.timer = null; self.timer = null;
if (self.widget) |widget| hide(widget); if (self.label) |label| hide(label);
return c.FALSE; return 0;
} }

View File

@ -273,8 +273,8 @@ pub const URLWidget = struct {
); );
// Show it // Show it
c.gtk_overlay_add_overlay(@ptrCast(surface.overlay), left); c.gtk_overlay_add_overlay(surface.overlay, left);
c.gtk_overlay_add_overlay(@ptrCast(surface.overlay), right); c.gtk_overlay_add_overlay(surface.overlay, right);
return .{ return .{
.left = left, .left = left,
@ -283,8 +283,8 @@ pub const URLWidget = struct {
} }
pub fn deinit(self: *URLWidget, overlay: *c.GtkOverlay) void { pub fn deinit(self: *URLWidget, overlay: *c.GtkOverlay) void {
c.gtk_overlay_remove_overlay(@ptrCast(overlay), @ptrCast(self.left)); c.gtk_overlay_remove_overlay(overlay, @ptrCast(self.left));
c.gtk_overlay_remove_overlay(@ptrCast(overlay), @ptrCast(self.right)); c.gtk_overlay_remove_overlay(overlay, @ptrCast(self.right));
} }
pub fn setText(self: *const URLWidget, str: [:0]const u8) void { pub fn setText(self: *const URLWidget, str: [:0]const u8) void {
@ -336,7 +336,7 @@ gl_area: *c.GtkGLArea,
url_widget: ?URLWidget = null, url_widget: ?URLWidget = null,
/// The overlay that shows resizing information. /// The overlay that shows resizing information.
resize_overlay: ResizeOverlay = .{}, resize_overlay: ResizeOverlay = undefined,
/// Whether or not the current surface is zoomed in (see `toggle_split_zoom`). /// Whether or not the current surface is zoomed in (see `toggle_split_zoom`).
zoomed_in: bool = false, zoomed_in: bool = false,
@ -583,7 +583,7 @@ pub fn init(self: *Surface, app: *App, opts: Options) !void {
.container = .{ .none = {} }, .container = .{ .none = {} },
.overlay = @ptrCast(overlay), .overlay = @ptrCast(overlay),
.gl_area = @ptrCast(gl_area), .gl_area = @ptrCast(gl_area),
.resize_overlay = ResizeOverlay.init(self), .resize_overlay = undefined,
.title_text = null, .title_text = null,
.core_surface = undefined, .core_surface = undefined,
.font_size = font_size, .font_size = font_size,
@ -600,6 +600,9 @@ pub fn init(self: *Surface, app: *App, opts: Options) !void {
self.context_menu.init(self); self.context_menu.init(self);
self.context_menu.setParent(@ptrCast(@alignCast(overlay))); self.context_menu.setParent(@ptrCast(@alignCast(overlay)));
// initialize the resize overlay
self.resize_overlay.init(self, &app.config);
// Set our default mouse shape // Set our default mouse shape
try self.setMouseShape(.text); try self.setMouseShape(.text);
@ -706,8 +709,7 @@ pub fn deinit(self: *Surface) void {
/// Update our local copy of any configuration that we use. /// Update our local copy of any configuration that we use.
pub fn updateConfig(self: *Surface, config: *const configpkg.Config) !void { pub fn updateConfig(self: *Surface, config: *const configpkg.Config) !void {
_ = self; self.resize_overlay.updateConfig(config);
_ = config;
} }
// unref removes the long-held reference to the gl_area and kicks off the // unref removes the long-held reference to the gl_area and kicks off the