ghostty/src/apprt/gtk/notebook_adw.zig
2025-01-02 17:49:15 -06:00

164 lines
6.8 KiB
Zig

const std = @import("std");
const assert = std.debug.assert;
const c = @import("c.zig").c;
const Window = @import("Window.zig");
const Tab = @import("Tab.zig");
const Notebook = @import("notebook.zig").Notebook;
const createWindow = @import("notebook.zig").createWindow;
const adwaita = @import("adwaita.zig");
const log = std.log.scoped(.gtk);
const AdwTabView = if (adwaita.versionAtLeast(0, 0, 0)) c.AdwTabView else anyopaque;
const AdwTabPage = if (adwaita.versionAtLeast(0, 0, 0)) c.AdwTabPage else anyopaque;
pub const NotebookAdw = struct {
/// the tab view
tab_view: *AdwTabView,
pub fn init(notebook: *Notebook) void {
const window: *Window = @fieldParentPtr("notebook", notebook);
const app = window.app;
assert(adwaita.enabled(&app.config));
const tab_view: *c.AdwTabView = c.adw_tab_view_new().?;
c.gtk_widget_add_css_class(@ptrCast(@alignCast(tab_view)), "notebook");
if (comptime adwaita.versionAtLeast(1, 2, 0) and adwaita.versionAtLeast(1, 2, 0)) {
// Adwaita enables all of the shortcuts by default.
// We want to manage keybindings ourselves.
c.adw_tab_view_remove_shortcuts(tab_view, c.ADW_TAB_VIEW_SHORTCUT_ALL_SHORTCUTS);
}
notebook.* = .{
.adw = .{
.tab_view = tab_view,
},
};
_ = c.g_signal_connect_data(tab_view, "page-attached", c.G_CALLBACK(&adwPageAttached), window, null, c.G_CONNECT_DEFAULT);
_ = c.g_signal_connect_data(tab_view, "create-window", c.G_CALLBACK(&adwTabViewCreateWindow), window, null, c.G_CONNECT_DEFAULT);
_ = c.g_signal_connect_data(tab_view, "notify::selected-page", c.G_CALLBACK(&adwSelectPage), window, null, c.G_CONNECT_DEFAULT);
}
pub fn asWidget(self: *NotebookAdw) *c.GtkWidget {
return @ptrCast(@alignCast(self.tab_view));
}
pub fn nPages(self: *NotebookAdw) c_int {
if (comptime adwaita.versionAtLeast(0, 0, 0))
return c.adw_tab_view_get_n_pages(self.tab_view)
else
unreachable;
}
/// Returns the index of the currently selected page.
/// Returns null if the notebook has no pages.
pub fn currentPage(self: *NotebookAdw) ?c_int {
if (comptime !adwaita.versionAtLeast(0, 0, 0)) unreachable;
const page = c.adw_tab_view_get_selected_page(self.tab_view) orelse return null;
return c.adw_tab_view_get_page_position(self.tab_view, page);
}
/// Returns the currently selected tab or null if there are none.
pub fn currentTab(self: *NotebookAdw) ?*Tab {
if (comptime !adwaita.versionAtLeast(0, 0, 0)) unreachable;
const page = c.adw_tab_view_get_selected_page(self.tab_view) orelse return null;
const child = c.adw_tab_page_get_child(page);
return @ptrCast(@alignCast(
c.g_object_get_data(@ptrCast(child), Tab.GHOSTTY_TAB) orelse return null,
));
}
pub fn gotoNthTab(self: *NotebookAdw, position: c_int) void {
if (comptime !adwaita.versionAtLeast(0, 0, 0)) unreachable;
const page_to_select = c.adw_tab_view_get_nth_page(self.tab_view, position);
c.adw_tab_view_set_selected_page(self.tab_view, page_to_select);
}
pub fn getTabPosition(self: *NotebookAdw, tab: *Tab) ?c_int {
if (comptime !adwaita.versionAtLeast(0, 0, 0)) unreachable;
const page = c.adw_tab_view_get_page(self.tab_view, @ptrCast(tab.box)) orelse return null;
return c.adw_tab_view_get_page_position(self.tab_view, page);
}
pub fn reorderPage(self: *NotebookAdw, tab: *Tab, position: c_int) void {
if (comptime !adwaita.versionAtLeast(0, 0, 0)) unreachable;
const page = c.adw_tab_view_get_page(self.tab_view, @ptrCast(tab.box));
_ = c.adw_tab_view_reorder_page(self.tab_view, page, position);
}
pub fn setTabLabel(self: *NotebookAdw, tab: *Tab, title: [:0]const u8) void {
if (comptime !adwaita.versionAtLeast(0, 0, 0)) unreachable;
const page = c.adw_tab_view_get_page(self.tab_view, @ptrCast(tab.box));
c.adw_tab_page_set_title(page, title.ptr);
}
pub fn setTabTooltip(self: *NotebookAdw, tab: *Tab, tooltip: [:0]const u8) void {
if (comptime !adwaita.versionAtLeast(0, 0, 0)) unreachable;
const page = c.adw_tab_view_get_page(self.tab_view, @ptrCast(tab.box));
c.adw_tab_page_set_tooltip(page, tooltip.ptr);
}
pub fn addTab(self: *NotebookAdw, tab: *Tab, position: c_int, title: [:0]const u8) void {
if (comptime !adwaita.versionAtLeast(0, 0, 0)) unreachable;
const box_widget: *c.GtkWidget = @ptrCast(tab.box);
const page = c.adw_tab_view_insert(self.tab_view, box_widget, position);
c.adw_tab_page_set_title(page, title.ptr);
c.adw_tab_view_set_selected_page(self.tab_view, page);
}
pub fn closeTab(self: *NotebookAdw, tab: *Tab) void {
if (comptime !adwaita.versionAtLeast(0, 0, 0)) unreachable;
const page = c.adw_tab_view_get_page(self.tab_view, @ptrCast(tab.box)) orelse return;
c.adw_tab_view_close_page(self.tab_view, page);
// If we have no more tabs we close the window
if (self.nPages() == 0) {
// libadw versions <= 1.3.x leak the final page view
// which causes our surface to not properly cleanup. We
// unref to force the cleanup. This will trigger a critical
// warning from GTK, but I don't know any other workaround.
// Note: I'm not actually sure if 1.4.0 contains the fix,
// I just know that 1.3.x is broken and 1.5.1 is fixed.
// If we know that 1.4.0 is fixed, we can change this.
if (!adwaita.versionAtLeast(1, 4, 0)) {
c.g_object_unref(tab.box);
}
c.gtk_window_destroy(tab.window.window);
}
}
};
fn adwPageAttached(_: *AdwTabView, page: *c.AdwTabPage, _: c_int, ud: ?*anyopaque) callconv(.C) void {
const window: *Window = @ptrCast(@alignCast(ud.?));
const child = c.adw_tab_page_get_child(page);
const tab: *Tab = @ptrCast(@alignCast(c.g_object_get_data(@ptrCast(child), Tab.GHOSTTY_TAB) orelse return));
tab.window = window;
window.focusCurrentTab();
}
fn adwTabViewCreateWindow(
_: *AdwTabView,
ud: ?*anyopaque,
) callconv(.C) ?*AdwTabView {
const currentWindow: *Window = @ptrCast(@alignCast(ud.?));
const window = createWindow(currentWindow) catch |err| {
log.warn("error creating new window error={}", .{err});
return null;
};
return window.notebook.adw.tab_view;
}
fn adwSelectPage(_: *c.GObject, _: *c.GParamSpec, ud: ?*anyopaque) void {
const window: *Window = @ptrCast(@alignCast(ud.?));
const page = c.adw_tab_view_get_selected_page(window.notebook.adw.tab_view) orelse return;
const title = c.adw_tab_page_get_title(page);
c.gtk_window_set_title(window.window, title);
}