Skip to content

Commit

Permalink
gtk: menubar update
Browse files Browse the repository at this point in the history
1. "top menu" => "menubar"
2. Slimmed down context menu.
3. More robust compile time checks of GTK builder UI files.
  • Loading branch information
jcollie committed Jan 25, 2025
1 parent 8f7d0ee commit 05c2b88
Show file tree
Hide file tree
Showing 15 changed files with 165 additions and 90 deletions.
2 changes: 1 addition & 1 deletion include/ghostty.h
Original file line number Diff line number Diff line change
Expand Up @@ -571,7 +571,7 @@ typedef enum {
GHOSTTY_ACTION_TOGGLE_WINDOW_DECORATIONS,
GHOSTTY_ACTION_TOGGLE_QUICK_TERMINAL,
GHOSTTY_ACTION_TOGGLE_VISIBILITY,
GHOSTTY_ACTION_TOGGLE_TOP_MENU,
GHOSTTY_ACTION_TOGGLE_MENUBAR,
GHOSTTY_ACTION_MOVE_TAB,
GHOSTTY_ACTION_GOTO_TAB,
GHOSTTY_ACTION_GOTO_SPLIT,
Expand Down
4 changes: 2 additions & 2 deletions src/Surface.zig
Original file line number Diff line number Diff line change
Expand Up @@ -4213,9 +4213,9 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
.toggle,
),

.toggle_top_menu => try self.rt_app.performAction(
.toggle_menubar => try self.rt_app.performAction(
.{ .surface = self },
.toggle_top_menu,
.toggle_menubar,
{},
),

Expand Down
6 changes: 3 additions & 3 deletions src/apprt/action.zig
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,8 @@ pub const Action = union(Key) {
/// Toggle the visibility of all Ghostty terminal windows.
toggle_visibility,

/// Toggle whether the top menu is shown.
toggle_top_menu,
/// Toggle whether the window menubar is shown.
toggle_menubar,

/// Moves a tab by a relative offset.
///
Expand Down Expand Up @@ -243,7 +243,7 @@ pub const Action = union(Key) {
toggle_window_decorations,
toggle_quick_terminal,
toggle_visibility,
toggle_top_menu,
toggle_menubar,
move_tab,
goto_tab,
goto_split,
Expand Down
2 changes: 1 addition & 1 deletion src/apprt/glfw.zig
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ pub const App = struct {
.toggle_window_decorations,
.toggle_quick_terminal,
.toggle_visibility,
.toggle_top_menu,
.toggle_menubar,
.goto_tab,
.move_tab,
.inspector,
Expand Down
8 changes: 4 additions & 4 deletions src/apprt/gtk/App.zig
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,7 @@ pub fn performAction(
}),
.toggle_maximize => self.toggleMaximize(target),
.toggle_fullscreen => self.toggleFullscreen(target, value),
.toggle_top_menu => self.toggleTopMenu(target),
.toggle_menubar => self.toggleMenubar(target),

.new_tab => try self.newTab(target),
.close_tab => try self.closeTab(target),
Expand Down Expand Up @@ -789,18 +789,18 @@ fn toggleWindowDecorations(
}
}

fn toggleTopMenu(_: *App, target: apprt.Target) void {
fn toggleMenubar(_: *App, target: apprt.Target) void {
switch (target) {
.app => {},
.surface => |v| {
const window = v.rt_surface.container.window() orelse {
log.info(
"toggleTopMenu invalid for container={s}",
"toggleMenubar invalid for container={s}",
.{@tagName(v.rt_surface.container)},
);
return;
};
window.toggleTopMenu();
window.toggleMenubar();
},
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/apprt/gtk/Surface.zig
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,7 @@ im_len: u7 = 0,
cgroup_path: ?[]const u8 = null,

/// Our context menu.
context_menu: Menu(Surface, .context, .popover_menu),
context_menu: Menu(Surface, "context_menu", .popover_menu),

/// Configuration used for initializing the surface. We have to copy some
/// data since initialization is delayed with GTK (on realize).
Expand Down
36 changes: 18 additions & 18 deletions src/apprt/gtk/Window.zig
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,14 @@ tab_overview: ?*c.GtkWidget,
/// can be either c.GtkNotebook or c.AdwTabView.
notebook: Notebook,

/// The "top" menu that appears at the top of a window.
top_menu: Menu(Window, .top, .popover_menu_bar),
/// Menu that appears at the top of a window inbetween the titlebar and tabbar.
menubar: Menu(Window, "menubar", .popover_menu_bar),

/// Revealer for showing/hiding top menu.
top_menu_revealer: *c.GtkRevealer,
menubar_revealer: *c.GtkRevealer,

/// The "main" menu that is attached to a button in the headerbar.
titlebar_menu: Menu(Window, .titlebar, .popover_menu),
titlebar_menu: Menu(Window, "titlebar_menu", .popover_menu),

/// The libadwaita widget for receiving toast send requests. If libadwaita is
/// not used, this is null and unused.
Expand Down Expand Up @@ -104,8 +104,8 @@ pub fn init(self: *Window, app: *App) !void {
.tab_overview = null,
.toast_overlay = null,
.notebook = undefined,
.top_menu = undefined,
.top_menu_revealer = undefined,
.menubar = undefined,
.menubar_revealer = undefined,
.titlebar_menu = undefined,
.winproto = .none,
};
Expand Down Expand Up @@ -149,12 +149,12 @@ pub fn init(self: *Window, app: *App) !void {
const box = c.gtk_box_new(c.GTK_ORIENTATION_VERTICAL, 0);

// Set up the menus
self.top_menu.init();
self.menubar.init();
self.titlebar_menu.init();

self.top_menu_revealer = @ptrCast(@alignCast(c.gtk_revealer_new()));
c.gtk_revealer_set_child(self.top_menu_revealer, self.top_menu.asWidget());
c.gtk_revealer_set_transition_type(self.top_menu_revealer, c.GTK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN);
self.menubar_revealer = @ptrCast(@alignCast(c.gtk_revealer_new()));
c.gtk_revealer_set_child(self.menubar_revealer, self.menubar.asWidget());
c.gtk_revealer_set_transition_type(self.menubar_revealer, c.GTK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN);

// Setup our notebook
self.notebook.init();
Expand Down Expand Up @@ -200,7 +200,7 @@ pub fn init(self: *Window, app: *App) !void {
_ = c.g_signal_connect_data(
btn,
"notify::active",
c.G_CALLBACK(&gtkMenuActivate),
c.G_CALLBACK(&gtkTitlebarMenuActivate),
self,
null,
c.G_CONNECT_DEFAULT,
Expand Down Expand Up @@ -258,11 +258,11 @@ pub fn init(self: *Window, app: *App) !void {
.adw140 => {},
.adw, .adw130 => {
c.gtk_box_append(@ptrCast(box), self.headerbar.asWidget());
c.gtk_box_append(@ptrCast(box), @ptrCast(@alignCast(self.top_menu_revealer)));
c.gtk_box_append(@ptrCast(box), @ptrCast(@alignCast(self.menubar_revealer)));
},
.gtk => {
c.gtk_window_set_titlebar(gtk_window, self.headerbar.asWidget());
c.gtk_box_append(@ptrCast(box), @ptrCast(@alignCast(self.top_menu_revealer)));
c.gtk_box_append(@ptrCast(box), @ptrCast(@alignCast(self.menubar_revealer)));
},
}

Expand Down Expand Up @@ -344,7 +344,7 @@ pub fn init(self: *Window, app: *App) !void {

const top_box = c.gtk_box_new(c.GTK_ORIENTATION_VERTICAL, 0);
c.gtk_box_append(@ptrCast(top_box), self.headerbar.asWidget());
c.gtk_box_append(@ptrCast(top_box), @ptrCast(@alignCast(self.top_menu_revealer)));
c.gtk_box_append(@ptrCast(top_box), @ptrCast(@alignCast(self.menubar_revealer)));

c.adw_toolbar_view_add_top_bar(toolbar_view, top_box);

Expand Down Expand Up @@ -643,9 +643,9 @@ pub fn toggleWindowDecorations(self: *Window) void {
}

/// Toggle top menu.
pub fn toggleTopMenu(self: *Window) void {
const is_revealed = c.gtk_revealer_get_reveal_child(self.top_menu_revealer) != 0;
c.gtk_revealer_set_reveal_child(self.top_menu_revealer, @intFromBool(!is_revealed));
pub fn toggleMenubar(self: *Window) void {
const is_revealed = c.gtk_revealer_get_reveal_child(self.menubar_revealer) != 0;
c.gtk_revealer_set_reveal_child(self.menubar_revealer, @intFromBool(!is_revealed));
}

/// Grabs focus on the currently selected tab.
Expand Down Expand Up @@ -1142,7 +1142,7 @@ fn userdataSelf(ud: *anyopaque) *Window {
return @ptrCast(@alignCast(ud));
}

fn gtkMenuActivate(
fn gtkTitlebarMenuActivate(
btn: *c.GtkMenuButton,
_: *c.GParamSpec,
ud: ?*anyopaque,
Expand Down
24 changes: 24 additions & 0 deletions src/apprt/gtk/builder_check.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const std = @import("std");

pub const c = @cImport({
@cInclude("gtk/gtk.h");
});

pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const alloc = gpa.allocator();

const filename = filename: {
var it = try std.process.argsWithAllocator(alloc);
defer it.deinit();

_ = it.next() orelse return error.NoFilename;
break :filename try alloc.dupeZ(u8, it.next() orelse return error.NoFilename);
};
defer alloc.free(filename);

c.gtk_init();

const builder = c.gtk_builder_new_from_file(filename.ptr);
defer c.g_object_unref(builder);
}
23 changes: 22 additions & 1 deletion src/apprt/gtk/gresource.zig
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ const icons = [_]struct {
},
};

pub const ui_files = [_][]const u8{
"menu-surface-context_menu",
"menu-window-menubar",
"menu-window-titlebar_menu",
};

pub const gresource_xml = comptimeGenerateGResourceXML();

fn comptimeGenerateGResourceXML() []const u8 {
Expand Down Expand Up @@ -93,6 +99,17 @@ fn writeGResourceXML(writer: anytype) !void {
.{ icon.alias, icon.source },
);
}
try writer.writeAll(
\\ </gresource>
\\ <gresource prefix="/com/mitchellh/ghostty/ui">
\\
);
for (ui_files) |ui_file| {
try writer.print(
" <file alias=\"{0s}.ui\">src/apprt/gtk/ui/{0s}.ui</file>\n",
.{ui_file},
);
}
try writer.writeAll(
\\ </gresource>
\\</gresources>
Expand All @@ -101,7 +118,7 @@ fn writeGResourceXML(writer: anytype) !void {
}

pub const dependencies = deps: {
const total = css_files.len + icons.len;
const total = css_files.len + icons.len + ui_files.len;
var deps: [total][]const u8 = undefined;
var index: usize = 0;
for (css_files) |css_file| {
Expand All @@ -112,5 +129,9 @@ pub const dependencies = deps: {
deps[index] = std.fmt.comptimePrint("images/icons/icon_{s}.png", .{icon.source});
index += 1;
}
for (ui_files) |ui_file| {
deps[index] = std.fmt.comptimePrint("src/apprt/gtk/ui/{s}.ui", .{ui_file});
index += 1;
}
break :deps deps;
};
29 changes: 22 additions & 7 deletions src/apprt/gtk/menu.zig
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const log = std.log.scoped(.gtk_menu);

pub fn Menu(
comptime T: type,
comptime variant: enum { top, titlebar, context },
comptime variant: []const u8,
comptime style: enum { popover_menu, popover_menu_bar },
) type {
return struct {
Expand All @@ -29,12 +29,27 @@ pub fn Menu(
Surface => "surface",
else => unreachable,
};
const parent: *T = @alignCast(@fieldParentPtr(@tagName(variant) ++ "_menu", self));

// embed the menu data using Zig @embedFile rather than as a GTK resource so that we get
// compile-time errors if we try and embed a file that doesn't exist
const data = @embedFile("ui/menu-" ++ name ++ "-" ++ @tagName(variant) ++ ".ui");
const builder = c.gtk_builder_new_from_string(data.ptr, @intCast(data.len));
const parent: *T = @alignCast(@fieldParentPtr(variant, self));

const path = "ui/menu-" ++ name ++ "-" ++ variant ++ ".ui";

comptime {
// Use @embedFile to make sure that the file exists at compile
// time. Zig _should_ discard the data so that it doesn't end up
// in the final executable. At runtime we will load the data from
// a GResource.
_ = @embedFile(path);

// Check to make sure that our file is listed as a `ui_file` in
// `gresource.zig`. If it isn't Ghostty could crash at runtime
// when we try and load a nonexistent GResource.
const gresource = @import("gresource.zig");
for (gresource.ui_files) |ui_file| {
if (std.mem.eql(u8, ui_file, "menu-" ++ name ++ "-" ++ variant)) break;
} else @compileError("missing 'menu-" ++ name ++ "-" ++ variant ++ "' in gresource.zig");
}

const builder = c.gtk_builder_new_from_resource("/com/mitchellh/ghostty/" ++ path);
defer c.g_object_unref(@ptrCast(builder));

const menu_model: *c.GMenuModel = @ptrCast(@alignCast(c.gtk_builder_get_object(builder, "menu")));
Expand Down
Loading

0 comments on commit 05c2b88

Please sign in to comment.