From c6abf65dd1443b038515ab51d00be52354f5abf8 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 21 Jul 2025 12:55:16 -0700 Subject: [PATCH 1/5] apprt/gtk-ng: resize overlay --- src/apprt/gtk-ng/class/surface.zig | 121 ++++++++++++++++++++++++++++ src/apprt/gtk-ng/css/style.css | 8 ++ src/apprt/gtk-ng/ui/1.2/surface.blp | 15 ++++ 3 files changed, 144 insertions(+) diff --git a/src/apprt/gtk-ng/class/surface.zig b/src/apprt/gtk-ng/class/surface.zig index 6231241bb..18d8bf524 100644 --- a/src/apprt/gtk-ng/class/surface.zig +++ b/src/apprt/gtk-ng/class/surface.zig @@ -202,6 +202,18 @@ pub const Surface = extern struct { url_left: *gtk.Label = undefined, url_right: *gtk.Label = undefined, + /// The resize label shown when resizing the surface. + size_label: *gtk.Label = undefined, + + /// The idle source that tracks our resize label. + size_idler: ?c_uint = null, + + /// If non-null this is a timer for dismissing the resize overlay. + size_timer: ?c_uint = null, + + /// Set to true after the first resize event. + size_first: bool = true, + /// The apprt Surface. rt_surface: ApprtSurface = undefined, @@ -511,6 +523,86 @@ pub const Surface = extern struct { }; } + fn resizeOverlaySchedule(self: *Self) void { + const priv = self.private(); + const config = priv.config orelse return; + switch (config.get().@"resize-overlay") { + .never => return, + .always => {}, + .@"after-first" => if (priv.size_first) { + priv.size_first = false; + return; + }, + } + + // We set this in case we reload our config later to after-first + // so we don't miss a tick. + priv.size_first = false; + + // When updating a widget, wait until GTK is "idle", i.e. not in the middle + // of doing any other updates. Since we are called in the middle of resizing + // GTK is doing a lot of work rearranging all of the widgets. Not doing this + // results in a lot of warnings from GTK and _horrible_ flickering of the + // resize overlay. + if (priv.size_idler != null) return; + priv.size_idler = glib.idleAdd(resizeOverlayIdle, self); + } + + fn resizeOverlayIdle(ud: ?*anyopaque) callconv(.c) c_int { + const self: *Self = @ptrCast(@alignCast(ud orelse return 0)); + const priv = self.private(); + + // No matter what our idler is complete with this callback + priv.size_idler = null; + + // We need config from here on out + const config = if (priv.config) |c| c.get() else return 0; + + var buf: [32]u8 = undefined; + const text = text: { + const surface = priv.core_surface orelse return 0; + const grid_size = surface.size.grid(); + break :text std.fmt.bufPrintZ( + &buf, + "{d} x {d}", + .{ + grid_size.columns, + grid_size.rows, + }, + ) catch |err| { + log.warn("unable to format text: {}", .{err}); + return 0; + }; + }; + + // The resize overlay widget already exists, just update it. + priv.size_label.setText(text.ptr); + priv.size_label.as(gtk.Widget).setVisible(1); + //setPosition(label, &self.config); + + if (priv.size_timer) |timer| { + if (glib.Source.remove(timer) == 0) { + log.warn("unable to remove size overlay timer", .{}); + } + } + + priv.size_timer = glib.timeoutAdd( + config.@"resize-overlay-duration".asMilliseconds(), + resizeOverlayTimerExpired, + self, + ); + + return 0; + } + + fn resizeOverlayTimerExpired(ud: ?*anyopaque) callconv(.c) c_int { + const self: *Self = @ptrCast(@alignCast(ud orelse return 0)); + const priv = self.private(); + priv.size_timer = null; + priv.size_label.as(gtk.Widget).setVisible(0); + return 0; + } + //--------------------------------------------------------------- // Libghostty Callbacks @@ -641,6 +733,9 @@ pub const Surface = extern struct { .width = 111, .height = 111, }; + priv.size_idler = null; + priv.size_timer = null; + priv.size_first = true; // If our configuration is null then we get the configuration // from the application. @@ -861,6 +956,14 @@ pub const Surface = extern struct { // Some other initialization steps self.initUrlOverlay(); + self.initResizeOverlay(); + } + + fn initResizeOverlay(self: *Self) void { + const priv = self.private(); + const overlay = priv.overlay; + const label = priv.size_label.as(gtk.Widget); + overlay.addOverlay(label); } fn initUrlOverlay(self: *Self) void { @@ -905,6 +1008,18 @@ pub const Surface = extern struct { v.unref(); priv.im_context = null; } + if (priv.size_idler) |v| { + if (glib.Source.remove(v) == 0) { + log.warn("unable to remove resize overlay idler", .{}); + } + priv.size_idler = null; + } + if (priv.size_timer) |v| { + if (glib.Source.remove(v) == 0) { + log.warn("unable to remove resize overlay timer", .{}); + } + priv.size_timer = null; + } gtk.Widget.disposeTemplate( self.as(gtk.Widget), @@ -1556,6 +1671,11 @@ pub const Surface = extern struct { surface.sizeCallback(priv.size) catch |err| { log.warn("error in size callback err={}", .{err}); }; + + // If we have resize overlays enabled, setup an idler + // to show that. We do this in an idle tick because doing it + // during the resize results in flickering. + self.resizeOverlaySchedule(); } } @@ -1669,6 +1789,7 @@ pub const Surface = extern struct { class.bindTemplateChildPrivate("gl_area", .{}); class.bindTemplateChildPrivate("url_left", .{}); class.bindTemplateChildPrivate("url_right", .{}); + class.bindTemplateChildPrivate("size_label", .{}); // Properties gobject.ext.registerProperties(class, &.{ diff --git a/src/apprt/gtk-ng/css/style.css b/src/apprt/gtk-ng/css/style.css index e26b4419b..953d0b836 100644 --- a/src/apprt/gtk-ng/css/style.css +++ b/src/apprt/gtk-ng/css/style.css @@ -22,3 +22,11 @@ label.url-overlay.left { label.url-overlay.right { border-radius: 6px 0px 0px 0px; } + +label.size-overlay { + padding: 4px 8px 4px 8px; + border-radius: 6px 6px 6px 6px; + outline-style: solid; + outline-width: 1px; + outline-color: #555555; +} diff --git a/src/apprt/gtk-ng/ui/1.2/surface.blp b/src/apprt/gtk-ng/ui/1.2/surface.blp index d08c21901..d30f2e7d7 100644 --- a/src/apprt/gtk-ng/ui/1.2/surface.blp +++ b/src/apprt/gtk-ng/ui/1.2/surface.blp @@ -42,3 +42,18 @@ Label url_right { valign: end; label: bind template.mouse-hover-url; } + +// The label that shows the resize information +Label size_label { + styles [ + "size-overlay", + ] + + visible: false; + focusable: false; + focus-on-click: false; + justify: center; + selectable: false; + halign: center; + valign: center; +} From 9caf5f5a86d2fa880df7d8a85036763a63770250 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 21 Jul 2025 13:49:28 -0700 Subject: [PATCH 2/5] apprt/gtk-ng: ResizeOverlay class --- src/apprt/gtk-ng/build/gresource.zig | 1 + src/apprt/gtk-ng/class/resize_overlay.zig | 302 +++++++++++++++++++++ src/apprt/gtk-ng/class/surface.zig | 213 +++++++-------- src/apprt/gtk-ng/css/style.css | 2 +- src/apprt/gtk-ng/ui/1.2/resize-overlay.blp | 22 ++ src/apprt/gtk-ng/ui/1.2/surface.blp | 11 +- 6 files changed, 428 insertions(+), 123 deletions(-) create mode 100644 src/apprt/gtk-ng/class/resize_overlay.zig create mode 100644 src/apprt/gtk-ng/ui/1.2/resize-overlay.blp diff --git a/src/apprt/gtk-ng/build/gresource.zig b/src/apprt/gtk-ng/build/gresource.zig index 4bd42a742..f953faaf6 100644 --- a/src/apprt/gtk-ng/build/gresource.zig +++ b/src/apprt/gtk-ng/build/gresource.zig @@ -35,6 +35,7 @@ pub const icon_sizes: []const comptime_int = &.{ 16, 32, 128, 256, 512, 1024 }; pub const blueprints: []const Blueprint = &.{ .{ .major = 1, .minor = 2, .name = "close-confirmation-dialog" }, .{ .major = 1, .minor = 2, .name = "config-errors-dialog" }, + .{ .major = 1, .minor = 2, .name = "resize-overlay" }, .{ .major = 1, .minor = 2, .name = "surface" }, .{ .major = 1, .minor = 5, .name = "window" }, }; diff --git a/src/apprt/gtk-ng/class/resize_overlay.zig b/src/apprt/gtk-ng/class/resize_overlay.zig new file mode 100644 index 000000000..aaec56b1f --- /dev/null +++ b/src/apprt/gtk-ng/class/resize_overlay.zig @@ -0,0 +1,302 @@ +const std = @import("std"); +const assert = std.debug.assert; +const adw = @import("adw"); +const glib = @import("glib"); +const gobject = @import("gobject"); +const gtk = @import("gtk"); + +const gresource = @import("../build/gresource.zig"); +const Common = @import("../class.zig").Common; + +const log = std.log.scoped(.gtk_ghostty_window); + +/// The overlay that shows the current size while a surface is resizing. +/// This can be used generically to show pretty much anything with a +/// disappearing overlay, but we have no other use at this point so it +/// is named specifically for what it does. +/// +/// General usage: +/// +/// 1. Add it to an overlay +/// 2. Set the label with `setLabel` +/// 3. Schedule to show it with `schedule` +/// +/// Set any properties to change the behavior. +pub const ResizeOverlay = extern struct { + const Self = @This(); + parent_instance: Parent, + pub const Parent = adw.Bin; + pub const getGObjectType = gobject.ext.defineClass(Self, .{ + .name = "GhosttyResizeOverlay", + .instanceInit = &init, + .classInit = &Class.init, + .parent_class = &Class.parent, + .private = .{ .Type = Private, .offset = &Private.offset }, + }); + + pub const properties = struct { + pub const duration = struct { + pub const name = "duration"; + const impl = gobject.ext.defineProperty( + name, + Self, + c_uint, + .{ + .nick = "Duration", + .blurb = "The duration this overlay appears in milliseconds.", + .default = 750, + .minimum = 250, + .maximum = std.math.maxInt(c_uint), + .accessor = gobject.ext.privateFieldAccessor( + Self, + Private, + &Private.offset, + "duration", + ), + }, + ); + }; + + pub const @"first-delay" = struct { + pub const name = "first-delay"; + const impl = gobject.ext.defineProperty( + name, + Self, + c_uint, + .{ + .nick = "First Delay", + .blurb = "The delay in milliseconds before any overlay is shown for the first time.", + .default = 250, + .minimum = 250, + .maximum = std.math.maxInt(c_uint), + .accessor = gobject.ext.privateFieldAccessor( + Self, + Private, + &Private.offset, + "first_delay", + ), + }, + ); + }; + + pub const @"overlay-halign" = struct { + pub const name = "overlay-halign"; + const impl = gobject.ext.defineProperty( + name, + Self, + gtk.Align, + .{ + .nick = "halign", + .blurb = "The alignment of the label.", + .default = .center, + .accessor = gobject.ext.privateFieldAccessor( + Self, + Private, + &Private.offset, + "halign", + ), + }, + ); + }; + + pub const @"overlay-valign" = struct { + pub const name = "overlay-valign"; + const impl = gobject.ext.defineProperty( + name, + Self, + gtk.Align, + .{ + .nick = "valign", + .blurb = "The alignment of the label.", + .default = .center, + .accessor = gobject.ext.privateFieldAccessor( + Self, + Private, + &Private.offset, + "valign", + ), + }, + ); + }; + }; + + const Private = struct { + /// The label with the text + label: *gtk.Label, + + /// The time that the overlay appears. + duration: c_uint, + + /// The first delay before any overlay is shown. Must be specified + /// during construction otherwise it has no effect. + first_delay: c_uint, + + /// The idle source that we use to update the label. + idler: ?c_uint = null, + + /// The timer for dismissing the overlay. + timer: ?c_uint = null, + + /// The first delay timer. + delay_timer: ?c_uint = null, + + /// The alignment of the label + halign: gtk.Align, + valign: gtk.Align, + + pub var offset: c_int = 0; + }; + + fn init(self: *Self, _: *Class) callconv(.C) void { + gtk.Widget.initTemplate(self.as(gtk.Widget)); + + const priv = self.private(); + if (priv.first_delay > 0) { + priv.delay_timer = glib.timeoutAdd( + priv.first_delay, + onDelayTimer, + self, + ); + } + } + + /// Set the label for the overlay. This will not show the + /// overlay if it is currently hidden; you must call schedule. + pub fn setLabel(self: *Self, label: [:0]const u8) void { + const priv = self.private(); + priv.label.setText(label.ptr); + } + + /// Schedule the overlay to be shown. To avoid flickering during + /// resizes we schedule the overlay to be shown on the next idle tick. + pub fn schedule(self: *Self) void { + const priv = self.private(); + + // If we have a delay timer then we're not showing anything + // yet so do nothing. + if (priv.delay_timer != null) return; + + // When updating a widget, wait until GTK is "idle", i.e. not in the middle + // of doing any other updates. Since we are called in the middle of resizing + // GTK is doing a lot of work rearranging all of the widgets. Not doing this + // results in a lot of warnings from GTK and _horrible_ flickering of the + // resize overlay. + if (priv.idler != null) return; + priv.idler = glib.idleAdd(onIdle, self); + } + + fn onIdle(ud: ?*anyopaque) callconv(.c) c_int { + const self: *Self = @ptrCast(@alignCast(ud orelse return 0)); + const priv = self.private(); + + // No matter what our idler is complete with this callback + priv.idler = null; + + // Show ourselves + self.as(gtk.Widget).setVisible(1); + + if (priv.timer) |timer| { + if (glib.Source.remove(timer) == 0) { + log.warn("unable to remove size overlay timer", .{}); + } + } + + priv.timer = glib.timeoutAdd( + priv.duration, + onTimer, + self, + ); + + return 0; + } + + fn onTimer(ud: ?*anyopaque) callconv(.c) c_int { + const self: *Self = @ptrCast(@alignCast(ud orelse return 0)); + const priv = self.private(); + priv.timer = null; + self.as(gtk.Widget).setVisible(0); + return 0; + } + + fn onDelayTimer(ud: ?*anyopaque) callconv(.c) c_int { + const self: *Self = @ptrCast(@alignCast(ud orelse return 0)); + const priv = self.private(); + priv.delay_timer = null; + return 0; + } + + //--------------------------------------------------------------- + // Virtual methods + + fn dispose(self: *Self) callconv(.C) void { + const priv = self.private(); + if (priv.idler) |v| { + if (glib.Source.remove(v) == 0) { + log.warn("unable to remove resize overlay idler", .{}); + } + priv.idler = null; + } + if (priv.timer) |v| { + if (glib.Source.remove(v) == 0) { + log.warn("unable to remove resize overlay timer", .{}); + } + priv.timer = null; + } + if (priv.delay_timer) |v| { + if (glib.Source.remove(v) == 0) { + log.warn("unable to remove resize overlay delay timer", .{}); + } + priv.delay_timer = null; + } + + gtk.Widget.disposeTemplate( + self.as(gtk.Widget), + getGObjectType(), + ); + + gobject.Object.virtual_methods.dispose.call( + Class.parent, + self.as(Parent), + ); + } + + const C = Common(Self, Private); + pub const as = C.as; + pub const ref = C.ref; + pub const unref = C.unref; + const private = C.private; + + pub const Class = extern struct { + parent_class: Parent.Class, + var parent: *Parent.Class = undefined; + pub const Instance = Self; + + fn init(class: *Class) callconv(.C) void { + gtk.Widget.Class.setTemplateFromResource( + class.as(gtk.Widget.Class), + comptime gresource.blueprint(.{ + .major = 1, + .minor = 2, + .name = "resize-overlay", + }), + ); + + // Bindings + class.bindTemplateChildPrivate("label", .{}); + + // Properties + gobject.ext.registerProperties(class, &.{ + properties.duration.impl, + properties.@"first-delay".impl, + properties.@"overlay-halign".impl, + properties.@"overlay-valign".impl, + }); + + // Virtual methods + gobject.Object.virtual_methods.dispose.implement(class, &dispose); + } + + pub const as = C.Class.as; + pub const bindTemplateChildPrivate = C.Class.bindTemplateChildPrivate; + }; +}; diff --git a/src/apprt/gtk-ng/class/surface.zig b/src/apprt/gtk-ng/class/surface.zig index 18d8bf524..9867369be 100644 --- a/src/apprt/gtk-ng/class/surface.zig +++ b/src/apprt/gtk-ng/class/surface.zig @@ -19,6 +19,7 @@ const ApprtSurface = @import("../Surface.zig"); const Common = @import("../class.zig").Common; const Application = @import("application.zig").Application; const Config = @import("config.zig").Config; +const ResizeOverlay = @import("resize_overlay.zig").ResizeOverlay; const log = std.log.scoped(.gtk_ghostty_surface); @@ -202,17 +203,8 @@ pub const Surface = extern struct { url_left: *gtk.Label = undefined, url_right: *gtk.Label = undefined, - /// The resize label shown when resizing the surface. - size_label: *gtk.Label = undefined, - - /// The idle source that tracks our resize label. - size_idler: ?c_uint = null, - - /// If non-null this is a timer for dismissing the resize overlay. - size_timer: ?c_uint = null, - - /// Set to true after the first resize event. - size_first: bool = true, + /// The resize overlay + resize_overlay: *ResizeOverlay = undefined, /// The apprt Surface. rt_surface: ApprtSurface = undefined, @@ -523,86 +515,6 @@ pub const Surface = extern struct { }; } - fn resizeOverlaySchedule(self: *Self) void { - const priv = self.private(); - const config = priv.config orelse return; - switch (config.get().@"resize-overlay") { - .never => return, - .always => {}, - .@"after-first" => if (priv.size_first) { - priv.size_first = false; - return; - }, - } - - // We set this in case we reload our config later to after-first - // so we don't miss a tick. - priv.size_first = false; - - // When updating a widget, wait until GTK is "idle", i.e. not in the middle - // of doing any other updates. Since we are called in the middle of resizing - // GTK is doing a lot of work rearranging all of the widgets. Not doing this - // results in a lot of warnings from GTK and _horrible_ flickering of the - // resize overlay. - if (priv.size_idler != null) return; - priv.size_idler = glib.idleAdd(resizeOverlayIdle, self); - } - - fn resizeOverlayIdle(ud: ?*anyopaque) callconv(.c) c_int { - const self: *Self = @ptrCast(@alignCast(ud orelse return 0)); - const priv = self.private(); - - // No matter what our idler is complete with this callback - priv.size_idler = null; - - // We need config from here on out - const config = if (priv.config) |c| c.get() else return 0; - - var buf: [32]u8 = undefined; - const text = text: { - const surface = priv.core_surface orelse return 0; - const grid_size = surface.size.grid(); - break :text std.fmt.bufPrintZ( - &buf, - "{d} x {d}", - .{ - grid_size.columns, - grid_size.rows, - }, - ) catch |err| { - log.warn("unable to format text: {}", .{err}); - return 0; - }; - }; - - // The resize overlay widget already exists, just update it. - priv.size_label.setText(text.ptr); - priv.size_label.as(gtk.Widget).setVisible(1); - //setPosition(label, &self.config); - - if (priv.size_timer) |timer| { - if (glib.Source.remove(timer) == 0) { - log.warn("unable to remove size overlay timer", .{}); - } - } - - priv.size_timer = glib.timeoutAdd( - config.@"resize-overlay-duration".asMilliseconds(), - resizeOverlayTimerExpired, - self, - ); - - return 0; - } - - fn resizeOverlayTimerExpired(ud: ?*anyopaque) callconv(.c) c_int { - const self: *Self = @ptrCast(@alignCast(ud orelse return 0)); - const priv = self.private(); - priv.size_timer = null; - priv.size_label.as(gtk.Widget).setVisible(0); - return 0; - } - //--------------------------------------------------------------- // Libghostty Callbacks @@ -733,9 +645,6 @@ pub const Surface = extern struct { .width = 111, .height = 111, }; - priv.size_idler = null; - priv.size_timer = null; - priv.size_first = true; // If our configuration is null then we get the configuration // from the application. @@ -932,6 +841,13 @@ pub const Surface = extern struct { ); // Some property signals + _ = gobject.Object.signals.notify.connect( + self, + ?*anyopaque, + &propConfig, + null, + .{ .detail = "config" }, + ); _ = gobject.Object.signals.notify.connect( self, ?*anyopaque, @@ -957,13 +873,15 @@ pub const Surface = extern struct { // Some other initialization steps self.initUrlOverlay(); self.initResizeOverlay(); + + // Initialize our config + self.propConfig(undefined, null); } fn initResizeOverlay(self: *Self) void { const priv = self.private(); const overlay = priv.overlay; - const label = priv.size_label.as(gtk.Widget); - overlay.addOverlay(label); + overlay.addOverlay(priv.resize_overlay.as(gtk.Widget)); } fn initUrlOverlay(self: *Self) void { @@ -1008,18 +926,6 @@ pub const Surface = extern struct { v.unref(); priv.im_context = null; } - if (priv.size_idler) |v| { - if (glib.Source.remove(v) == 0) { - log.warn("unable to remove resize overlay idler", .{}); - } - priv.size_idler = null; - } - if (priv.size_timer) |v| { - if (glib.Source.remove(v) == 0) { - log.warn("unable to remove resize overlay timer", .{}); - } - priv.size_timer = null; - } gtk.Widget.disposeTemplate( self.as(gtk.Widget), @@ -1075,6 +981,58 @@ pub const Surface = extern struct { return self.private().title; } + fn propConfig( + self: *Self, + _: *gobject.ParamSpec, + _: ?*anyopaque, + ) callconv(.c) void { + const priv = self.private(); + const config = if (priv.config) |c| c.get() else return; + + // resize-overlay-duration + { + const ms = config.@"resize-overlay-duration".asMilliseconds(); + var value = gobject.ext.Value.newFrom(ms); + defer value.unset(); + gobject.Object.setProperty( + priv.resize_overlay.as(gobject.Object), + "duration", + &value, + ); + } + + // resize-overlay-position + { + const hv: struct { + gtk.Align, // halign + gtk.Align, // valign + } = switch (config.@"resize-overlay-position") { + .center => .{ .center, .center }, + .@"top-left" => .{ .start, .start }, + .@"top-right" => .{ .end, .start }, + .@"top-center" => .{ .center, .start }, + .@"bottom-left" => .{ .start, .end }, + .@"bottom-right" => .{ .end, .end }, + .@"bottom-center" => .{ .center, .end }, + }; + + var halign = gobject.ext.Value.newFrom(hv[0]); + defer halign.unset(); + var valign = gobject.ext.Value.newFrom(hv[1]); + defer valign.unset(); + gobject.Object.setProperty( + priv.resize_overlay.as(gobject.Object), + "overlay-halign", + &halign, + ); + gobject.Object.setProperty( + priv.resize_overlay.as(gobject.Object), + "overlay-valign", + &valign, + ); + } + } + fn propMouseHoverUrl( self: *Self, _: *gobject.ParamSpec, @@ -1672,13 +1630,43 @@ pub const Surface = extern struct { log.warn("error in size callback err={}", .{err}); }; - // If we have resize overlays enabled, setup an idler - // to show that. We do this in an idle tick because doing it - // during the resize results in flickering. + // Setup our resize overlay if configured self.resizeOverlaySchedule(); } } + fn resizeOverlaySchedule(self: *Self) void { + const priv = self.private(); + const surface = priv.core_surface orelse return; + + // Only show the resize overlay if its enabled + const config = if (priv.config) |c| c.get() else return; + switch (config.@"resize-overlay") { + .always, .@"after-first" => {}, + .never => return, + } + + // If we have resize overlays enabled, setup an idler + // to show that. We do this in an idle tick because doing it + // during the resize results in flickering. + var buf: [32]u8 = undefined; + priv.resize_overlay.setLabel(text: { + const grid_size = surface.size.grid(); + break :text std.fmt.bufPrintZ( + &buf, + "{d} x {d}", + .{ + grid_size.columns, + grid_size.rows, + }, + ) catch |err| err: { + log.warn("unable to format text: {}", .{err}); + break :err ""; + }; + }); + priv.resize_overlay.schedule(); + } + const RealizeError = Allocator.Error || error{ GLAreaError, RendererError, @@ -1775,6 +1763,7 @@ pub const Surface = extern struct { pub const Instance = Self; fn init(class: *Class) callconv(.C) void { + gobject.ext.ensureType(ResizeOverlay); gtk.Widget.Class.setTemplateFromResource( class.as(gtk.Widget.Class), comptime gresource.blueprint(.{ @@ -1789,7 +1778,7 @@ pub const Surface = extern struct { class.bindTemplateChildPrivate("gl_area", .{}); class.bindTemplateChildPrivate("url_left", .{}); class.bindTemplateChildPrivate("url_right", .{}); - class.bindTemplateChildPrivate("size_label", .{}); + class.bindTemplateChildPrivate("resize_overlay", .{}); // Properties gobject.ext.registerProperties(class, &.{ diff --git a/src/apprt/gtk-ng/css/style.css b/src/apprt/gtk-ng/css/style.css index 953d0b836..99515ec4a 100644 --- a/src/apprt/gtk-ng/css/style.css +++ b/src/apprt/gtk-ng/css/style.css @@ -23,7 +23,7 @@ label.url-overlay.right { border-radius: 6px 0px 0px 0px; } -label.size-overlay { +.size-overlay label { padding: 4px 8px 4px 8px; border-radius: 6px 6px 6px 6px; outline-style: solid; diff --git a/src/apprt/gtk-ng/ui/1.2/resize-overlay.blp b/src/apprt/gtk-ng/ui/1.2/resize-overlay.blp new file mode 100644 index 000000000..a80b63fb3 --- /dev/null +++ b/src/apprt/gtk-ng/ui/1.2/resize-overlay.blp @@ -0,0 +1,22 @@ +using Gtk 4.0; +using Adw 1; +// We can't inherit directly from Label because its an opaque +// type in zig-gobject. +template $GhosttyResizeOverlay: Adw.Bin { + visible: false; + duration: 750; + first-delay: 250; + overlay-halign: center; + overlay-valign: center; + // See surface.blp for why we need to wrap this. + Adw.Bin { + Label label { + focusable: false; + focus-on-click: false; + justify: center; + selectable: false; + halign: bind template.overlay-halign; + valign: bind template.overlay-valign; + } + } +} diff --git a/src/apprt/gtk-ng/ui/1.2/surface.blp b/src/apprt/gtk-ng/ui/1.2/surface.blp index d30f2e7d7..dc3b051c7 100644 --- a/src/apprt/gtk-ng/ui/1.2/surface.blp +++ b/src/apprt/gtk-ng/ui/1.2/surface.blp @@ -43,17 +43,8 @@ Label url_right { label: bind template.mouse-hover-url; } -// The label that shows the resize information -Label size_label { +$GhosttyResizeOverlay resize_overlay { styles [ "size-overlay", ] - - visible: false; - focusable: false; - focus-on-click: false; - justify: center; - selectable: false; - halign: center; - valign: center; } From 66ce764d67db6a29e891d1ea0f85a8b66edb1964 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 21 Jul 2025 20:48:43 -0700 Subject: [PATCH 3/5] valgring supps for VMware VGA --- valgrind.supp | 67 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 62 insertions(+), 5 deletions(-) diff --git a/valgrind.supp b/valgrind.supp index 4c5d6b4a0..6b7320b75 100644 --- a/valgrind.supp +++ b/valgrind.supp @@ -34,6 +34,35 @@ ... } +{ + GSK GPU Rendering + Memcheck:Leak + match-leak-kinds: possible + ... + fun:gsk_gpu_render_pass_op_gl_command + fun:gsk_gl_frame_submit + fun:gsk_gpu_renderer_render + fun:gsk_renderer_render + fun:gtk_widget_render + fun:surface_render + fun:_gdk_marshal_BOOLEAN__BOXEDv + fun:_g_closure_invoke_va + fun:signal_emit_valist_unlocked + fun:g_signal_emit_valist + fun:g_signal_emit + fun:gdk_surface_paint_on_clock + fun:_g_closure_invoke_va + fun:signal_emit_valist_unlocked + fun:g_signal_emit_valist + fun:g_signal_emit + fun:gdk_frame_clock_paint_idle + ... + fun:g_timeout_dispatch + fun:g_main_context_dispatch_unlocked + fun:g_main_context_iterate_unlocked.isra.0 + fun:g_main_context_iteration + ... +} { GTK Shader Selector Memcheck:Leak @@ -57,7 +86,7 @@ fun:g_object_new_internal.part.0 fun:g_object_new_valist fun:g_object_new - fun:gtk_at_spi_create_context + ... fun:gtk_at_context_create fun:gtk_widget_init fun:g_type_create_instance @@ -79,7 +108,7 @@ fun:g_object_new_internal.part.0 fun:g_object_new_valist fun:g_object_new - fun:gtk_at_spi_create_context + ... fun:gtk_at_context_create fun:gtk_widget_init fun:g_type_create_instance @@ -101,6 +130,34 @@ ... } +{ + Fcitx + Memcheck:Leak + match-leak-kinds: definite + ... + fun:g_malloc0 + fun:parser_start_element + fun:emit_start_element + fun:g_markup_parse_context_parse + fun:g_dbus_node_info_new_for_xml + fun:_fcitx_g_client_* + ... +} + +{ + Fcitx + Memcheck:Leak + match-leak-kinds: possible + ... + fun:g_closure_invoke + fun:signal_emit_unlocked_R.isra.0 + fun:signal_emit_valist_unlocked + fun:g_signal_emit_valist + fun:g_signal_emit + fun:_fcitx_g_client_g_signal + ... +} + { GTK init Memcheck:Leak @@ -126,7 +183,7 @@ fun:fc_thread_func fun:g_thread_proxy fun:start_thread - fun:clone + ... } { @@ -211,17 +268,17 @@ { Mesa Memcheck:Leak - match-leak-kinds: possible ... fun:_mesa_* + ... } { Mesa Memcheck:Leak - match-leak-kinds: possible ... fun:mesa_* + ... } { From 6abb9ec427f76f3602813da9d4dc2730fbe55caa Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 21 Jul 2025 20:51:58 -0700 Subject: [PATCH 4/5] ignore valgrind supp file for typos --- typos.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/typos.toml b/typos.toml index 0d58ea654..5a23527d9 100644 --- a/typos.toml +++ b/typos.toml @@ -20,6 +20,8 @@ extend-exclude = [ "*.png", "*.ico", "*.icns", + # Valgrind nonsense + "valgrind.supp", # Other "*.pdf", "*.data", From 4aaacc04a571730f3653d2a85c149d3a9d61ffcb Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 21 Jul 2025 20:54:14 -0700 Subject: [PATCH 5/5] fix logger name --- src/apprt/gtk-ng/class/resize_overlay.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apprt/gtk-ng/class/resize_overlay.zig b/src/apprt/gtk-ng/class/resize_overlay.zig index aaec56b1f..321d3d565 100644 --- a/src/apprt/gtk-ng/class/resize_overlay.zig +++ b/src/apprt/gtk-ng/class/resize_overlay.zig @@ -8,7 +8,7 @@ const gtk = @import("gtk"); const gresource = @import("../build/gresource.zig"); const Common = @import("../class.zig").Common; -const log = std.log.scoped(.gtk_ghostty_window); +const log = std.log.scoped(.gtk_ghostty_resize_overlay); /// The overlay that shows the current size while a surface is resizing. /// This can be used generically to show pretty much anything with a