mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 07:46:12 +03:00
GTK: refactor headerbar into separate Adwaita & GTK structs
This commit is contained in:
@ -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).
|
||||
@ -77,7 +77,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,
|
||||
@ -150,64 +150,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);
|
||||
@ -220,9 +212,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.
|
||||
@ -297,10 +287,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();
|
||||
@ -373,10 +360,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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -452,18 +437,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.
|
||||
@ -556,9 +535,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.
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
77
src/apprt/gtk/headerbar_adw.zig
Normal file
77
src/apprt/gtk/headerbar_adw.zig
Normal file
@ -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);
|
||||
}
|
||||
}
|
52
src/apprt/gtk/headerbar_gtk.zig
Normal file
52
src/apprt/gtk/headerbar_gtk.zig
Normal file
@ -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 {}
|
Reference in New Issue
Block a user