From b475cd28d52bb4ddd698e3f18f9feac6034ffdf6 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 29 Jul 2025 09:53:20 -0700 Subject: [PATCH] apprt/gtk-ng: new tab, slightly broken --- src/apprt/gtk-ng/class/application.zig | 28 +++++++++- src/apprt/gtk-ng/class/window.zig | 76 ++++++++++++++++++++++---- 2 files changed, 91 insertions(+), 13 deletions(-) diff --git a/src/apprt/gtk-ng/class/application.zig b/src/apprt/gtk-ng/class/application.zig index 636700284..b29f79ec0 100644 --- a/src/apprt/gtk-ng/class/application.zig +++ b/src/apprt/gtk-ng/class/application.zig @@ -500,6 +500,8 @@ pub const Application = extern struct { .mouse_shape => Action.mouseShape(target, value), .mouse_visibility => Action.mouseVisibility(target, value), + .new_tab => return Action.newTab(target), + .new_window => try Action.newWindow( self, switch (target) { @@ -532,7 +534,6 @@ pub const Application = extern struct { .toggle_fullscreen => Action.toggleFullscreen(target), // Unimplemented but todo on gtk-ng branch - .new_tab, .goto_tab, .move_tab, .new_split, @@ -1272,6 +1273,31 @@ const Action = struct { } } + pub fn newTab(target: apprt.Target) bool { + switch (target) { + .app => { + log.warn("new tab to app is unexpected", .{}); + return false; + }, + + .surface => |core| { + // Get the window ancestor of the surface. Surfaces shouldn't + // be aware they might be in windows but at the app level we + // can do this. + const surface = core.rt_surface.surface; + const window_widget = surface + .as(gtk.Widget) + .getAncestor(gobject.ext.typeFor(Window)) orelse { + log.warn("surface is not in a window, ignoring new_tab", .{}); + return false; + }; + const window = gobject.ext.cast(Window, window_widget).?; + window.newTab(core); + return true; + }, + } + } + pub fn newWindow( self: *Application, parent: ?*CoreSurface, diff --git a/src/apprt/gtk-ng/class/window.zig b/src/apprt/gtk-ng/class/window.zig index a17f41ff6..79d7d8f53 100644 --- a/src/apprt/gtk-ng/class/window.zig +++ b/src/apprt/gtk-ng/class/window.zig @@ -223,17 +223,7 @@ pub const Window = extern struct { const self = gobject.ext.newInstance(Self, .{ .application = app, }); - - // Create our initial tab. This will trigger the selected-page - // signal handler which will setup the remainder of the bindings - // for this to all work. - const priv = self.private(); - const tab = gobject.ext.newInstance(Tab, .{ - .config = priv.config, - }); - if (parent_) |p| tab.setParent(p); - _ = priv.tab_view.append(tab.as(gtk.Widget)); - + self.newTab(parent_); return self; } @@ -280,6 +270,8 @@ pub const Window = extern struct { const actions = .{ .{ "about", actionAbout, null }, .{ "close", actionClose, null }, + .{ "close-tab", actionCloseTab, null }, + .{ "new-tab", actionNewTab, null }, .{ "new-window", actionNewWindow, null }, .{ "copy", actionCopy, null }, .{ "paste", actionPaste, null }, @@ -305,6 +297,50 @@ pub const Window = extern struct { } } + /// Create a new tab with the given parent. The tab will be inserted + /// at the position dictated by the `window-new-tab-position` config. + /// The new tab will be selected. + pub fn newTab(self: *Self, parent_: ?*CoreSurface) void { + const priv = self.private(); + const tab_view = priv.tab_view; + + // Create our new tab object + const tab = gobject.ext.newInstance(Tab, .{ + .config = priv.config, + }); + if (parent_) |p| tab.setParent(p); + + // Get the position that we should insert the new tab at. + const config = if (priv.config) |v| v.get() else { + // If we don't have a config we just append it at the end. + // This should never happen. + _ = tab_view.append(tab.as(gtk.Widget)); + return; + }; + const position = switch (config.@"window-new-tab-position") { + .current => current: { + const selected = tab_view.getSelectedPage() orelse + break :current tab_view.getNPages(); + const current = tab_view.getPagePosition(selected); + break :current current + 1; + }, + + .end => tab_view.getNPages(), + }; + + // Add the page and select it + const page = tab_view.insert(tab.as(gtk.Widget), position); + tab_view.setSelectedPage(page); + + // Create some property bindings + _ = tab.as(gobject.Object).bindProperty( + "title", + page.as(gobject.Object), + "title", + .{ .sync_create = true }, + ); + } + /// Updates various appearance properties. This should always be safe /// to call multiple times. This should be called whenever a change /// happens that might affect how the window appears (config change, @@ -391,7 +427,7 @@ pub const Window = extern struct { } /// Returns true if this window needs confirmation before quitting. - pub fn getNeedsConfirmQuit(self: *Self) bool { + fn getNeedsConfirmQuit(self: *Self) bool { const priv = self.private(); const n = priv.tab_view.getNPages(); assert(n >= 0); @@ -953,6 +989,14 @@ pub const Window = extern struct { self.as(gtk.Window).close(); } + fn actionCloseTab( + _: *gio.SimpleAction, + _: ?*glib.Variant, + self: *Window, + ) callconv(.c) void { + self.performBindingAction(.close_tab); + } + fn actionNewWindow( _: *gio.SimpleAction, _: ?*glib.Variant, @@ -961,6 +1005,14 @@ pub const Window = extern struct { self.performBindingAction(.new_window); } + fn actionNewTab( + _: *gio.SimpleAction, + _: ?*glib.Variant, + self: *Window, + ) callconv(.c) void { + self.performBindingAction(.new_tab); + } + fn actionCopy( _: *gio.SimpleAction, _: ?*glib.Variant,