diff --git a/src/apprt/gtk/Window.zig b/src/apprt/gtk/Window.zig index d0e678057..8f111cbc9 100644 --- a/src/apprt/gtk/Window.zig +++ b/src/apprt/gtk/Window.zig @@ -37,7 +37,7 @@ window: *c.GtkWindow, /// The header bar for the window. This is possibly null since it can be /// disabled using gtk-titlebar. This is either an AdwHeaderBar or /// GtkHeaderBar depending on if adw is enabled and linked. -header: ?HeaderBar, +headerbar: HeaderBar, /// The tab overview for the window. This is possibly null since there is no /// taboverview without a AdwApplicationWindow (libadwaita >= 1.4.0). @@ -78,7 +78,7 @@ pub fn init(self: *Window, app: *App) !void { self.* = .{ .app = app, .window = undefined, - .header = null, + .headerbar = undefined, .tab_overview = null, .notebook = undefined, .context_menu = undefined, @@ -151,64 +151,56 @@ pub fn init(self: *Window, app: *App) !void { break :overview tab_overview; } else null; - // gtk-titlebar can be used to disable the header bar (but keep - // the window manager's decorations). We create this no matter if we - // are decorated or not because we can have a keybind to toggle the - // decorations. - if (app.config.@"gtk-titlebar") { - const header = HeaderBar.init(self); + // gtk-titlebar can be used to disable the header bar (but keep the window + // manager's decorations). We create this no matter if we are decorated or + // not because we can have a keybind to toggle the decorations. + self.headerbar.init(); - // If we are not decorated then we hide the titlebar. - header.setVisible(app.config.@"window-decoration"); + { + const btn = c.gtk_menu_button_new(); + c.gtk_widget_set_tooltip_text(btn, "Main Menu"); + c.gtk_menu_button_set_icon_name(@ptrCast(btn), "open-menu-symbolic"); + c.gtk_menu_button_set_menu_model(@ptrCast(btn), @ptrCast(@alignCast(app.menu))); + self.headerbar.packEnd(btn); + } - { - const btn = c.gtk_menu_button_new(); - c.gtk_widget_set_tooltip_text(btn, "Main Menu"); - c.gtk_menu_button_set_icon_name(@ptrCast(btn), "open-menu-symbolic"); - c.gtk_menu_button_set_menu_model(@ptrCast(btn), @ptrCast(@alignCast(app.menu))); - header.packEnd(btn); - } + // If we're using an AdwWindow then we can support the tab overview. + if (self.tab_overview) |tab_overview| { + if (comptime !adwaita.versionAtLeast(1, 4, 0)) unreachable; + assert(self.app.config.@"gtk-adwaita" and adwaita.versionAtLeast(1, 4, 0)); + const btn = switch (app.config.@"gtk-tabs-location") { + .top, .bottom, .left, .right => btn: { + const btn = c.gtk_toggle_button_new(); + c.gtk_widget_set_tooltip_text(btn, "View Open Tabs"); + c.gtk_button_set_icon_name(@ptrCast(btn), "view-grid-symbolic"); + _ = c.g_object_bind_property( + btn, + "active", + tab_overview, + "open", + c.G_BINDING_BIDIRECTIONAL | c.G_BINDING_SYNC_CREATE, + ); - // If we're using an AdwWindow then we can support the tab overview. - if (self.tab_overview) |tab_overview| { - if (comptime !adwaita.versionAtLeast(1, 4, 0)) unreachable; - assert(self.app.config.@"gtk-adwaita" and adwaita.versionAtLeast(1, 4, 0)); - const btn = switch (app.config.@"gtk-tabs-location") { - .top, .bottom, .left, .right => btn: { - const btn = c.gtk_toggle_button_new(); - c.gtk_widget_set_tooltip_text(btn, "View Open Tabs"); - c.gtk_button_set_icon_name(@ptrCast(btn), "view-grid-symbolic"); - _ = c.g_object_bind_property( - btn, - "active", - tab_overview, - "open", - c.G_BINDING_BIDIRECTIONAL | c.G_BINDING_SYNC_CREATE, - ); + break :btn btn; + }, - break :btn btn; - }, + .hidden => btn: { + const btn = c.adw_tab_button_new(); + c.adw_tab_button_set_view(@ptrCast(btn), self.notebook.adw.tab_view); + c.gtk_actionable_set_action_name(@ptrCast(btn), "overview.open"); + break :btn btn; + }, + }; - .hidden => btn: { - const btn = c.adw_tab_button_new(); - c.adw_tab_button_set_view(@ptrCast(btn), self.notebook.adw.tab_view); - c.gtk_actionable_set_action_name(@ptrCast(btn), "overview.open"); - break :btn btn; - }, - }; + c.gtk_widget_set_focus_on_click(btn, c.FALSE); + self.headerbar.packEnd(btn); + } - c.gtk_widget_set_focus_on_click(btn, c.FALSE); - header.packEnd(btn); - } - - { - const btn = c.gtk_button_new_from_icon_name("tab-new-symbolic"); - c.gtk_widget_set_tooltip_text(btn, "New Tab"); - _ = c.g_signal_connect_data(btn, "clicked", c.G_CALLBACK(>kTabNewClick), self, null, c.G_CONNECT_DEFAULT); - header.packStart(btn); - } - - self.header = header; + { + const btn = c.gtk_button_new_from_icon_name("tab-new-symbolic"); + c.gtk_widget_set_tooltip_text(btn, "New Tab"); + _ = c.g_signal_connect_data(btn, "clicked", c.G_CALLBACK(>kTabNewClick), self, null, c.G_CONNECT_DEFAULT); + self.headerbar.packStart(btn); } _ = c.g_signal_connect_data(gtk_window, "notify::decorated", c.G_CALLBACK(>kWindowNotifyDecorated), self, null, c.G_CONNECT_DEFAULT); @@ -221,9 +213,7 @@ pub fn init(self: *Window, app: *App) !void { // If Adwaita is enabled and is older than 1.4.0 we don't have the tab overview and so we // need to stick the headerbar into the content box. if (!adwaita.versionAtLeast(1, 4, 0) and adwaita.enabled(&self.app.config)) { - if (self.header) |h| { - c.gtk_box_append(@ptrCast(box), h.asWidget()); - } + c.gtk_box_append(@ptrCast(box), self.headerbar.asWidget()); } // In debug we show a warning and apply the 'devel' class to the window. @@ -298,10 +288,7 @@ pub fn init(self: *Window, app: *App) !void { if ((comptime adwaita.versionAtLeast(1, 4, 0)) and adwaita.versionAtLeast(1, 4, 0) and adwaita.enabled(&self.app.config)) { const toolbar_view: *c.AdwToolbarView = @ptrCast(c.adw_toolbar_view_new()); - if (self.header) |header| { - const header_widget = header.asWidget(); - c.adw_toolbar_view_add_top_bar(toolbar_view, header_widget); - } + c.adw_toolbar_view_add_top_bar(toolbar_view, self.headerbar.asWidget()); if (self.app.config.@"gtk-tabs-location" != .hidden) { const tab_bar = c.adw_tab_bar_new(); @@ -374,10 +361,8 @@ pub fn init(self: *Window, app: *App) !void { box, ); } else { + c.gtk_window_set_titlebar(gtk_window, self.headerbar.asWidget()); c.gtk_window_set_child(gtk_window, box); - if (self.header) |h| { - c.gtk_window_set_titlebar(gtk_window, h.asWidget()); - } } } @@ -459,18 +444,12 @@ pub fn deinit(self: *Window) void { /// Set the title of the window. pub fn setTitle(self: *Window, title: [:0]const u8) void { - if ((comptime adwaita.versionAtLeast(1, 4, 0)) and adwaita.versionAtLeast(1, 4, 0) and adwaita.enabled(&self.app.config) and self.app.config.@"gtk-titlebar") { - if (self.header) |header| header.setTitle(title); - } else { - c.gtk_window_set_title(self.window, title); - } + self.headerbar.setTitle(title); } /// Set the subtitle of the window if it has one. pub fn setSubtitle(self: *Window, subtitle: [:0]const u8) void { - if ((comptime adwaita.versionAtLeast(1, 4, 0)) and adwaita.versionAtLeast(1, 4, 0) and adwaita.enabled(&self.app.config) and self.app.config.@"gtk-titlebar") { - if (self.header) |header| header.setSubtitle(subtitle); - } + self.headerbar.setSubtitle(subtitle); } /// Add a new tab to this window. @@ -563,9 +542,7 @@ pub fn toggleWindowDecorations(self: *Window) void { // decorated state. GTK tends to consider the titlebar part of the frame // and hides it with decorations, but libadwaita doesn't. This makes it // explicit. - if (self.header) |headerbar| { - headerbar.setVisible(new_decorated); - } + self.headerbar.setVisible(new_decorated); } /// Grabs focus on the currently selected tab. diff --git a/src/apprt/gtk/headerbar.zig b/src/apprt/gtk/headerbar.zig index 97c48a4c2..2b47ea4b7 100644 --- a/src/apprt/gtk/headerbar.zig +++ b/src/apprt/gtk/headerbar.zig @@ -4,93 +4,58 @@ const c = @import("c.zig").c; const Window = @import("Window.zig"); const adwaita = @import("adwaita.zig"); -const AdwHeaderBar = if (adwaita.versionAtLeast(0, 0, 0)) c.AdwHeaderBar else void; +const HeaderBarAdw = @import("headerbar_adw.zig"); +const HeaderBarGtk = @import("headerbar_gtk.zig"); pub const HeaderBar = union(enum) { - adw: *AdwHeaderBar, - gtk: *c.GtkHeaderBar, + adw: HeaderBarAdw, + gtk: HeaderBarGtk, - pub fn init(window: *Window) HeaderBar { - if ((comptime adwaita.versionAtLeast(1, 4, 0)) and - adwaita.enabled(&window.app.config)) - { - return initAdw(window); + pub fn init(self: *HeaderBar) void { + const window: *Window = @fieldParentPtr("headerbar", self); + if ((comptime adwaita.versionAtLeast(1, 4, 0)) and adwaita.enabled(&window.app.config)) { + HeaderBarAdw.init(self); + } else { + HeaderBarGtk.init(self); } - return initGtk(); - } - - fn initAdw(window: *Window) HeaderBar { - const headerbar = c.adw_header_bar_new(); - c.adw_header_bar_set_title_widget(@ptrCast(headerbar), @ptrCast(c.adw_window_title_new(c.gtk_window_get_title(window.window) orelse "Ghostty", null))); - return .{ .adw = @ptrCast(headerbar) }; - } - - fn initGtk() HeaderBar { - const headerbar = c.gtk_header_bar_new(); - return .{ .gtk = @ptrCast(headerbar) }; + if (!window.app.config.@"gtk-titlebar" or !window.app.config.@"window-decoration") + self.setVisible(false); } pub fn setVisible(self: HeaderBar, visible: bool) void { - c.gtk_widget_set_visible(self.asWidget(), @intFromBool(visible)); + switch (self) { + inline else => |v| v.setVisible(visible), + } } pub fn asWidget(self: HeaderBar) *c.GtkWidget { return switch (self) { - .adw => |headerbar| @ptrCast(@alignCast(headerbar)), - .gtk => |headerbar| @ptrCast(@alignCast(headerbar)), + inline else => |v| v.asWidget(), }; } pub fn packEnd(self: HeaderBar, widget: *c.GtkWidget) void { switch (self) { - .adw => |headerbar| if (comptime adwaita.versionAtLeast(0, 0, 0)) { - c.adw_header_bar_pack_end( - @ptrCast(@alignCast(headerbar)), - widget, - ); - }, - .gtk => |headerbar| c.gtk_header_bar_pack_end( - @ptrCast(@alignCast(headerbar)), - widget, - ), + inline else => |v| v.packEnd(widget), } } pub fn packStart(self: HeaderBar, widget: *c.GtkWidget) void { switch (self) { - .adw => |headerbar| if (comptime adwaita.versionAtLeast(0, 0, 0)) { - c.adw_header_bar_pack_start( - @ptrCast(@alignCast(headerbar)), - widget, - ); - }, - .gtk => |headerbar| c.gtk_header_bar_pack_start( - @ptrCast(@alignCast(headerbar)), - widget, - ), + inline else => |v| v.packStart(widget), } } pub fn setTitle(self: HeaderBar, title: [:0]const u8) void { switch (self) { - .adw => |headerbar| if (comptime adwaita.versionAtLeast(0, 0, 0)) { - const window_title: *c.AdwWindowTitle = @ptrCast(c.adw_header_bar_get_title_widget(@ptrCast(headerbar))); - c.adw_window_title_set_title(window_title, title); - }, - // The title is owned by the window when not using Adwaita - .gtk => unreachable, + inline else => |v| v.setTitle(title), } } pub fn setSubtitle(self: HeaderBar, subtitle: [:0]const u8) void { switch (self) { - .adw => |headerbar| if (comptime adwaita.versionAtLeast(0, 0, 0)) { - const window_title: *c.AdwWindowTitle = @ptrCast(c.adw_header_bar_get_title_widget(@ptrCast(headerbar))); - c.adw_window_title_set_subtitle(window_title, subtitle); - }, - // There is no subtitle unless Adwaita is used - .gtk => unreachable, + inline else => |v| v.setSubtitle(subtitle), } } }; diff --git a/src/apprt/gtk/headerbar_adw.zig b/src/apprt/gtk/headerbar_adw.zig new file mode 100644 index 000000000..c0d622207 --- /dev/null +++ b/src/apprt/gtk/headerbar_adw.zig @@ -0,0 +1,77 @@ +const HeaderBarAdw = @This(); + +const std = @import("std"); +const c = @import("c.zig").c; + +const Window = @import("Window.zig"); +const adwaita = @import("adwaita.zig"); + +const HeaderBar = @import("headerbar.zig").HeaderBar; + +const AdwHeaderBar = if (adwaita.versionAtLeast(0, 0, 0)) c.AdwHeaderBar else anyopaque; +const AdwWindowTitle = if (adwaita.versionAtLeast(0, 0, 0)) c.AdwWindowTitle else anyopaque; + +/// the window that this headerbar is attached to +window: *Window, +/// the Adwaita headerbar widget +headerbar: *AdwHeaderBar, +/// the Adwaita window title widget +title: *AdwWindowTitle, + +pub fn init(headerbar: *HeaderBar) void { + if (!adwaita.versionAtLeast(0, 0, 0)) return; + + const window: *Window = @fieldParentPtr("headerbar", headerbar); + headerbar.* = .{ + .adw = .{ + .window = window, + .headerbar = @ptrCast(@alignCast(c.adw_header_bar_new())), + .title = @ptrCast(@alignCast(c.adw_window_title_new( + c.gtk_window_get_title(window.window) orelse "Ghostty", + null, + ))), + }, + }; + c.adw_header_bar_set_title_widget( + headerbar.adw.headerbar, + @ptrCast(@alignCast(headerbar.adw.title)), + ); +} + +pub fn setVisible(self: HeaderBarAdw, visible: bool) void { + c.gtk_widget_set_visible(self.asWidget(), @intFromBool(visible)); +} + +pub fn asWidget(self: HeaderBarAdw) *c.GtkWidget { + return @ptrCast(@alignCast(self.headerbar)); +} + +pub fn packEnd(self: HeaderBarAdw, widget: *c.GtkWidget) void { + if (comptime adwaita.versionAtLeast(0, 0, 0)) { + c.adw_header_bar_pack_end( + @ptrCast(@alignCast(self.headerbar)), + widget, + ); + } +} + +pub fn packStart(self: HeaderBarAdw, widget: *c.GtkWidget) void { + if (comptime adwaita.versionAtLeast(0, 0, 0)) { + c.adw_header_bar_pack_start( + @ptrCast(@alignCast(self.headerbar)), + widget, + ); + } +} + +pub fn setTitle(self: HeaderBarAdw, title: [:0]const u8) void { + if (comptime adwaita.versionAtLeast(0, 0, 0)) { + c.adw_window_title_set_title(self.title, title); + } +} + +pub fn setSubtitle(self: HeaderBarAdw, subtitle: [:0]const u8) void { + if (comptime adwaita.versionAtLeast(0, 0, 0)) { + c.adw_window_title_set_subtitle(self.title, subtitle); + } +} diff --git a/src/apprt/gtk/headerbar_gtk.zig b/src/apprt/gtk/headerbar_gtk.zig new file mode 100644 index 000000000..63ba8b389 --- /dev/null +++ b/src/apprt/gtk/headerbar_gtk.zig @@ -0,0 +1,52 @@ +const HeaderBarGtk = @This(); + +const std = @import("std"); +const c = @import("c.zig").c; + +const Window = @import("Window.zig"); +const adwaita = @import("adwaita.zig"); + +const HeaderBar = @import("headerbar.zig").HeaderBar; + +/// the window that this headarbar is attached to +window: *Window, +/// the GTK headerbar widget +headerbar: *c.GtkHeaderBar, + +pub fn init(headerbar: *HeaderBar) void { + const window: *Window = @fieldParentPtr("headerbar", headerbar); + headerbar.* = .{ + .gtk = .{ + .window = window, + .headerbar = @ptrCast(c.gtk_header_bar_new()), + }, + }; +} + +pub fn setVisible(self: HeaderBarGtk, visible: bool) void { + c.gtk_widget_set_visible(self.asWidget(), @intFromBool(visible)); +} + +pub fn asWidget(self: HeaderBarGtk) *c.GtkWidget { + return @ptrCast(@alignCast(self.headerbar)); +} + +pub fn packEnd(self: HeaderBarGtk, widget: *c.GtkWidget) void { + c.gtk_header_bar_pack_end( + @ptrCast(@alignCast(self.headerbar)), + widget, + ); +} + +pub fn packStart(self: HeaderBarGtk, widget: *c.GtkWidget) void { + c.gtk_header_bar_pack_start( + @ptrCast(@alignCast(self.headerbar)), + widget, + ); +} + +pub fn setTitle(self: HeaderBarGtk, title: [:0]const u8) void { + c.gtk_window_set_title(self.window.window, title); +} + +pub fn setSubtitle(_: HeaderBarGtk, _: [:0]const u8) void {}