From 394ab3017f14a80afe47051438f7f074e0f4ea4a Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 18 Sep 2023 12:54:19 -0700 Subject: [PATCH] apprt/gtk: initial app menu --- src/apprt/gtk/App.zig | 46 ++++++++++++++++++++++++++++++++++++++++ src/apprt/gtk/Window.zig | 10 +++++++++ 2 files changed, 56 insertions(+) diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig index 2505810ef..b5647427b 100644 --- a/src/apprt/gtk/App.zig +++ b/src/apprt/gtk/App.zig @@ -37,6 +37,9 @@ ctx: *c.GMainContext, /// The "none" cursor. We use one that is shared across the entire app. cursor_none: ?*c.GdkCursor, +/// The shared application menu. +menu: ?*c.GMenu = null, + /// The configuration errors window, if it is currently open. config_errors_window: ?*ConfigErrorsWindow = null, @@ -140,6 +143,7 @@ pub fn terminate(self: *App) void { c.g_object_unref(self.app); if (self.cursor_none) |cursor| c.g_object_unref(cursor); + if (self.menu) |menu| c.g_object_unref(menu); self.config.deinit(); @@ -195,6 +199,10 @@ pub fn wakeup(self: App) void { pub fn run(self: *App) !void { if (!self.running) return; + // If we're not remote, then we also setup our actions and menus. + self.initActions(); + self.initMenu(); + // On startup, we want to check for configuration errors right away // so we can show our error window. self.updateConfigErrors() catch |err| { @@ -331,3 +339,41 @@ fn gtkActivate(app: *c.GtkApplication, ud: ?*anyopaque) callconv(.C) void { .new_window = .{}, }, .{ .forever = {} }); } + +fn gtkActionQuit( + _: *c.GSimpleAction, + _: *c.GVariant, + ud: ?*anyopaque, +) callconv(.C) void { + const self: *App = @ptrCast(@alignCast(ud orelse return)); + self.core_app.setQuit() catch |err| { + log.warn("error setting quit err={}", .{err}); + return; + }; +} + +/// This is called to setup the action map that this application supports. +/// This should be called only once on startup. +fn initActions(self: *App) void { + const action_quit = c.g_simple_action_new("quit", null); + defer c.g_object_unref(action_quit); + c.g_action_map_add_action(@ptrCast(self.app), @ptrCast(action_quit)); + + _ = c.g_signal_connect_data(action_quit, "activate", c.G_CALLBACK(>kActionQuit), self, null, c.G_CONNECT_DEFAULT); +} + +/// This sets the self.menu property to the application menu that can be +/// shared by all application windows. +fn initMenu(self: *App) void { + const menu = c.g_menu_new(); + errdefer c.g_object_unref(menu); + c.g_menu_append(menu, "Quit", "app.quit"); + + // { + // const section = c.g_menu_new(); + // defer c.g_object_unref(section); + // c.g_menu_append_submenu(menu, "File", @ptrCast(@alignCast(section))); + // } + + self.menu = menu; +} diff --git a/src/apprt/gtk/Window.zig b/src/apprt/gtk/Window.zig index c336c2e59..86ef1cbd8 100644 --- a/src/apprt/gtk/Window.zig +++ b/src/apprt/gtk/Window.zig @@ -89,6 +89,16 @@ pub fn init(self: *Window, app: *App) !void { c.gtk_widget_set_opacity(@ptrCast(window), app.config.@"background-opacity"); } + // Use the new GTK4 header bar + const header = c.gtk_header_bar_new(); + c.gtk_window_set_titlebar(gtk_window, header); + { + const btn = c.gtk_menu_button_new(); + 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))); + c.gtk_header_bar_pack_end(@ptrCast(header), btn); + } + // Hide window decoration if configured. This has to happen before // `gtk_widget_show`. if (!app.config.@"window-decoration") {