apprt/gtk-ng: resize overlay

pull/8012/head
Mitchell Hashimoto 2025-07-21 12:55:16 -07:00
parent e34248a2ad
commit c6abf65dd1
No known key found for this signature in database
GPG Key ID: 523D5DC389D273BC
3 changed files with 144 additions and 0 deletions

View File

@ -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, &.{

View File

@ -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;
}

View File

@ -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;
}