mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00
move resize overlay code to a new file to keep the file size down
This commit is contained in:
162
src/apprt/gtk/ResizeOverlay.zig
Normal file
162
src/apprt/gtk/ResizeOverlay.zig
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const c = @import("c.zig");
|
||||||
|
const configpkg = @import("../../config.zig");
|
||||||
|
const Surface = @import("Surface.zig");
|
||||||
|
|
||||||
|
const log = std.log.scoped(.gtk);
|
||||||
|
|
||||||
|
/// Back reference to the surface we belong to
|
||||||
|
surface: ?*Surface = null,
|
||||||
|
|
||||||
|
/// If non-null this is the widget on the overlay that shows the size of the
|
||||||
|
/// surface when it is resized.
|
||||||
|
widget: ?*c.GtkWidget = null,
|
||||||
|
|
||||||
|
/// If non-null this is a timer for dismissing the resize overlay.
|
||||||
|
timer: ?c.guint = null,
|
||||||
|
|
||||||
|
/// If non-null this is a timer for dismissing the resize overlay.
|
||||||
|
idler: ?c.guint = null,
|
||||||
|
|
||||||
|
/// If true, the next resize event will be the first one.
|
||||||
|
first: bool = true,
|
||||||
|
|
||||||
|
/// If we're configured to do so, create a label widget for displaying the size
|
||||||
|
/// of the surface during a resize event.
|
||||||
|
pub fn init(surface: *Surface, config: *configpkg.Config, overlay: *c.GtkOverlay) @This() {
|
||||||
|
// At this point the surface object has been _created_ but not
|
||||||
|
// _initialized_ so we can't use any information from it.
|
||||||
|
|
||||||
|
if (config.@"resize-overlay" == .never) return .{};
|
||||||
|
|
||||||
|
const widget = c.gtk_label_new("");
|
||||||
|
|
||||||
|
c.gtk_widget_add_css_class(widget, "view");
|
||||||
|
c.gtk_widget_add_css_class(widget, "size-overlay");
|
||||||
|
c.gtk_widget_add_css_class(widget, "hidden");
|
||||||
|
c.gtk_widget_set_visible(widget, c.FALSE);
|
||||||
|
c.gtk_widget_set_focusable(widget, c.FALSE);
|
||||||
|
c.gtk_widget_set_can_target(widget, c.FALSE);
|
||||||
|
c.gtk_label_set_justify(@ptrCast(widget), c.GTK_JUSTIFY_CENTER);
|
||||||
|
c.gtk_label_set_selectable(@ptrCast(widget), c.FALSE);
|
||||||
|
|
||||||
|
setOverlayWidgetPosition(widget, config);
|
||||||
|
|
||||||
|
c.gtk_overlay_add_overlay(overlay, widget);
|
||||||
|
|
||||||
|
return .{ .surface = surface, .widget = widget };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *@This()) void {
|
||||||
|
if (self.idler) |idler| {
|
||||||
|
if (c.g_source_remove(idler) == c.FALSE) {
|
||||||
|
log.warn("unable to remove resize overlay idler", .{});
|
||||||
|
}
|
||||||
|
self.idler = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.timer) |timer| {
|
||||||
|
if (c.g_source_remove(timer) == c.FALSE) {
|
||||||
|
log.warn("unable to remove resize overlay timer", .{});
|
||||||
|
}
|
||||||
|
self.timer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If we're configured to do so, update the text in the resize overlay widget
|
||||||
|
/// and make it visible. Schedule a timer to hide the widget after the delay
|
||||||
|
/// expires.
|
||||||
|
pub fn maybeShowResizeOverlay(self: *@This()) void {
|
||||||
|
if (self.widget == null) return;
|
||||||
|
const surface = self.surface orelse return;
|
||||||
|
|
||||||
|
if (surface.app.config.@"resize-overlay" == .never) return;
|
||||||
|
|
||||||
|
if (surface.app.config.@"resize-overlay" == .@"after-first" and self.first) {
|
||||||
|
self.first = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.first = false;
|
||||||
|
|
||||||
|
// When updating a widget, do so from GTK's thread, but not if there's
|
||||||
|
// already an update queued up.
|
||||||
|
if (self.idler != null) return;
|
||||||
|
self.idler = c.g_idle_add(gtkUpdateOverlayWidget, @ptrCast(self));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Actually update the overlay widget. This should only be called as an idle
|
||||||
|
/// handler.
|
||||||
|
fn gtkUpdateOverlayWidget(ud: ?*anyopaque) callconv(.C) c.gboolean {
|
||||||
|
const self: *@This() = @ptrCast(@alignCast(ud));
|
||||||
|
|
||||||
|
const widget = self.widget orelse return c.FALSE;
|
||||||
|
const surface = self.surface orelse return c.FALSE;
|
||||||
|
|
||||||
|
var buf: [32]u8 = undefined;
|
||||||
|
const text = std.fmt.bufPrintZ(
|
||||||
|
&buf,
|
||||||
|
"{d}c ⨯ {d}r",
|
||||||
|
.{
|
||||||
|
surface.core_surface.grid_size.columns,
|
||||||
|
surface.core_surface.grid_size.rows,
|
||||||
|
},
|
||||||
|
) catch |err| {
|
||||||
|
log.err("unable to format text: {}", .{err});
|
||||||
|
return c.FALSE;
|
||||||
|
};
|
||||||
|
|
||||||
|
c.gtk_label_set_text(@ptrCast(widget), text.ptr);
|
||||||
|
c.gtk_widget_remove_css_class(@ptrCast(widget), "hidden");
|
||||||
|
c.gtk_widget_set_visible(@ptrCast(widget), 1);
|
||||||
|
|
||||||
|
setOverlayWidgetPosition(widget, &surface.app.config);
|
||||||
|
|
||||||
|
if (self.timer) |timer| {
|
||||||
|
if (c.g_source_remove(timer) == c.FALSE) {
|
||||||
|
log.warn("unable to remove size overlay timer", .{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.timer = c.g_timeout_add(
|
||||||
|
surface.app.config.@"resize-overlay-duration".asMilliseconds(),
|
||||||
|
gtkResizeOverlayTimerExpired,
|
||||||
|
@ptrCast(self),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.idler = null;
|
||||||
|
|
||||||
|
return c.FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
fn setOverlayWidgetPosition(widget: *c.GtkWidget, config: *configpkg.Config) void {
|
||||||
|
c.gtk_widget_set_halign(
|
||||||
|
@ptrCast(widget),
|
||||||
|
switch (config.@"resize-overlay-position") {
|
||||||
|
.center, .@"top-center", .@"bottom-center" => c.GTK_ALIGN_CENTER,
|
||||||
|
.@"top-left", .@"bottom-left" => c.GTK_ALIGN_START,
|
||||||
|
.@"top-right", .@"bottom-right" => c.GTK_ALIGN_END,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
c.gtk_widget_set_valign(
|
||||||
|
@ptrCast(widget),
|
||||||
|
switch (config.@"resize-overlay-position") {
|
||||||
|
.center => c.GTK_ALIGN_CENTER,
|
||||||
|
.@"top-left", .@"top-center", .@"top-right" => c.GTK_ALIGN_START,
|
||||||
|
.@"bottom-left", .@"bottom-center", .@"bottom-right" => c.GTK_ALIGN_END,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If this fires, it means that the delay period has expired and the resize
|
||||||
|
/// overlay widget should be hidden.
|
||||||
|
fn gtkResizeOverlayTimerExpired(ud: ?*anyopaque) callconv(.C) c.gboolean {
|
||||||
|
const self: *@This() = @ptrCast(@alignCast(ud));
|
||||||
|
if (self.widget) |widget| {
|
||||||
|
c.gtk_widget_add_css_class(@ptrCast(widget), "hidden");
|
||||||
|
c.gtk_widget_set_visible(@ptrCast(widget), c.FALSE);
|
||||||
|
}
|
||||||
|
self.timer = null;
|
||||||
|
return c.FALSE;
|
||||||
|
}
|
@ -18,6 +18,7 @@ const Split = @import("Split.zig");
|
|||||||
const Tab = @import("Tab.zig");
|
const Tab = @import("Tab.zig");
|
||||||
const Window = @import("Window.zig");
|
const Window = @import("Window.zig");
|
||||||
const ClipboardConfirmationWindow = @import("ClipboardConfirmationWindow.zig");
|
const ClipboardConfirmationWindow = @import("ClipboardConfirmationWindow.zig");
|
||||||
|
const ResizeOverlay = @import("ResizeOverlay.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");
|
const c = @import("c.zig");
|
||||||
@ -1982,161 +1983,3 @@ fn translateMods(state: c.GdkModifierType) input.Mods {
|
|||||||
if (state & c.GDK_LOCK_MASK != 0) mods.caps_lock = true;
|
if (state & c.GDK_LOCK_MASK != 0) mods.caps_lock = true;
|
||||||
return mods;
|
return mods;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ResizeOverlay = struct {
|
|
||||||
/// Back reference to the surface we belong to
|
|
||||||
surface: ?*Surface = null,
|
|
||||||
|
|
||||||
/// If non-null this is the widget on the overlay that shows the size of the
|
|
||||||
/// surface when it is resized.
|
|
||||||
widget: ?*c.GtkWidget = null,
|
|
||||||
|
|
||||||
/// If non-null this is a timer for dismissing the resize overlay.
|
|
||||||
timer: ?c.guint = null,
|
|
||||||
|
|
||||||
/// If non-null this is a timer for dismissing the resize overlay.
|
|
||||||
idler: ?c.guint = null,
|
|
||||||
|
|
||||||
/// If true, the next resize event will be the first one.
|
|
||||||
first: bool = true,
|
|
||||||
|
|
||||||
/// If we're configured to do so, create a label widget for displaying the size
|
|
||||||
/// of the surface during a resize event.
|
|
||||||
pub fn init(surface: *Surface, config: *configpkg.Config, overlay: *c.GtkOverlay) ResizeOverlay {
|
|
||||||
// At this point the surface object has been _created_ but not
|
|
||||||
// _initialized_ so we can't use any information from it.
|
|
||||||
|
|
||||||
if (config.@"resize-overlay" == .never) return .{};
|
|
||||||
|
|
||||||
const widget = c.gtk_label_new("");
|
|
||||||
|
|
||||||
c.gtk_widget_add_css_class(widget, "view");
|
|
||||||
c.gtk_widget_add_css_class(widget, "size-overlay");
|
|
||||||
c.gtk_widget_add_css_class(widget, "hidden");
|
|
||||||
c.gtk_widget_set_visible(widget, c.FALSE);
|
|
||||||
c.gtk_widget_set_focusable(widget, c.FALSE);
|
|
||||||
c.gtk_widget_set_can_target(widget, c.FALSE);
|
|
||||||
c.gtk_label_set_justify(@ptrCast(widget), c.GTK_JUSTIFY_CENTER);
|
|
||||||
c.gtk_label_set_selectable(@ptrCast(widget), c.FALSE);
|
|
||||||
|
|
||||||
setOverlayWidgetPosition(widget, config);
|
|
||||||
|
|
||||||
c.gtk_overlay_add_overlay(overlay, widget);
|
|
||||||
|
|
||||||
return .{ .surface = surface, .widget = widget };
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *@This()) void {
|
|
||||||
if (self.idler) |idler| {
|
|
||||||
if (c.g_source_remove(idler) == c.FALSE) {
|
|
||||||
log.warn("unable to remove resize overlay idler", .{});
|
|
||||||
}
|
|
||||||
self.idler = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self.timer) |timer| {
|
|
||||||
if (c.g_source_remove(timer) == c.FALSE) {
|
|
||||||
log.warn("unable to remove resize overlay timer", .{});
|
|
||||||
}
|
|
||||||
self.timer = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If we're configured to do so, update the text in the resize overlay widget
|
|
||||||
/// and make it visible. Schedule a timer to hide the widget after the delay
|
|
||||||
/// expires.
|
|
||||||
pub fn maybeShowResizeOverlay(self: *@This()) void {
|
|
||||||
if (self.widget == null) return;
|
|
||||||
const surface = self.surface orelse return;
|
|
||||||
|
|
||||||
if (surface.app.config.@"resize-overlay" == .never) return;
|
|
||||||
|
|
||||||
if (surface.app.config.@"resize-overlay" == .@"after-first" and self.first) {
|
|
||||||
self.first = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.first = false;
|
|
||||||
|
|
||||||
// When updating a widget, do so from GTK's thread, but not if there's
|
|
||||||
// already an update queued up.
|
|
||||||
if (self.idler != null) return;
|
|
||||||
self.idler = c.g_idle_add(gtkUpdateOverlayWidget, @ptrCast(self));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Actually update the overlay widget. This should only be called as an idle
|
|
||||||
/// handler.
|
|
||||||
fn gtkUpdateOverlayWidget(ud: ?*anyopaque) callconv(.C) c.gboolean {
|
|
||||||
const self: *@This() = @ptrCast(@alignCast(ud));
|
|
||||||
|
|
||||||
const widget = self.widget orelse return c.FALSE;
|
|
||||||
const surface = self.surface orelse return c.FALSE;
|
|
||||||
|
|
||||||
var buf: [32]u8 = undefined;
|
|
||||||
const text = std.fmt.bufPrintZ(
|
|
||||||
&buf,
|
|
||||||
"{d}c ⨯ {d}r",
|
|
||||||
.{
|
|
||||||
surface.core_surface.grid_size.columns,
|
|
||||||
surface.core_surface.grid_size.rows,
|
|
||||||
},
|
|
||||||
) catch |err| {
|
|
||||||
log.err("unable to format text: {}", .{err});
|
|
||||||
return c.FALSE;
|
|
||||||
};
|
|
||||||
|
|
||||||
c.gtk_label_set_text(@ptrCast(widget), text.ptr);
|
|
||||||
c.gtk_widget_remove_css_class(@ptrCast(widget), "hidden");
|
|
||||||
c.gtk_widget_set_visible(@ptrCast(widget), 1);
|
|
||||||
|
|
||||||
setOverlayWidgetPosition(widget, &surface.app.config);
|
|
||||||
|
|
||||||
if (self.timer) |timer| {
|
|
||||||
if (c.g_source_remove(timer) == c.FALSE) {
|
|
||||||
log.warn("unable to remove size overlay timer", .{});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.timer = c.g_timeout_add(
|
|
||||||
surface.app.config.@"resize-overlay-duration".asMilliseconds(),
|
|
||||||
gtkResizeOverlayTimerExpired,
|
|
||||||
@ptrCast(self),
|
|
||||||
);
|
|
||||||
|
|
||||||
self.idler = null;
|
|
||||||
|
|
||||||
return c.FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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.
|
|
||||||
fn setOverlayWidgetPosition(widget: *c.GtkWidget, config: *configpkg.Config) void {
|
|
||||||
c.gtk_widget_set_halign(
|
|
||||||
@ptrCast(widget),
|
|
||||||
switch (config.@"resize-overlay-position") {
|
|
||||||
.center, .@"top-center", .@"bottom-center" => c.GTK_ALIGN_CENTER,
|
|
||||||
.@"top-left", .@"bottom-left" => c.GTK_ALIGN_START,
|
|
||||||
.@"top-right", .@"bottom-right" => c.GTK_ALIGN_END,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
c.gtk_widget_set_valign(
|
|
||||||
@ptrCast(widget),
|
|
||||||
switch (config.@"resize-overlay-position") {
|
|
||||||
.center => c.GTK_ALIGN_CENTER,
|
|
||||||
.@"top-left", .@"top-center", .@"top-right" => c.GTK_ALIGN_START,
|
|
||||||
.@"bottom-left", .@"bottom-center", .@"bottom-right" => c.GTK_ALIGN_END,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If this fires, it means that the delay period has expired and the resize
|
|
||||||
/// overlay widget should be hidden.
|
|
||||||
fn gtkResizeOverlayTimerExpired(ud: ?*anyopaque) callconv(.C) c.gboolean {
|
|
||||||
const self: *@This() = @ptrCast(@alignCast(ud));
|
|
||||||
if (self.widget) |widget| {
|
|
||||||
c.gtk_widget_add_css_class(@ptrCast(widget), "hidden");
|
|
||||||
c.gtk_widget_set_visible(@ptrCast(widget), c.FALSE);
|
|
||||||
}
|
|
||||||
self.timer = null;
|
|
||||||
return c.FALSE;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
Reference in New Issue
Block a user