From 199e1cae29367c331ec00837f1b53305154ecbe7 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 20 Oct 2023 08:44:23 -0700 Subject: [PATCH] apprt/gtk: surface has inspector state --- src/apprt/gtk/App.zig | 6 -- src/apprt/gtk/Surface.zig | 34 ++++++++++ src/apprt/gtk/inspector.zig | 129 +++++++++++++++++++++++++++++++----- 3 files changed, 146 insertions(+), 23 deletions(-) diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig index 7f89bdc83..e710bbc85 100644 --- a/src/apprt/gtk/App.zig +++ b/src/apprt/gtk/App.zig @@ -263,12 +263,6 @@ pub fn run(self: *App) !void { log.warn("error handling configuration changes err={}", .{err}); }; - // TODO: temporary, remove: show our inspector window - { - const win = try inspector.Window.create(self.core_app.alloc, self); - _ = win; - } - while (self.running) { _ = c.g_main_context_iteration(self.ctx, 1); diff --git a/src/apprt/gtk/Surface.zig b/src/apprt/gtk/Surface.zig index 2638eca9d..f964776d6 100644 --- a/src/apprt/gtk/Surface.zig +++ b/src/apprt/gtk/Surface.zig @@ -14,6 +14,7 @@ const CoreSurface = @import("../../Surface.zig"); const App = @import("App.zig"); const Window = @import("Window.zig"); +const inspector = @import("inspector.zig"); const c = @import("c.zig"); const log = std.log.scoped(.gtk); @@ -76,6 +77,9 @@ font_size: ?font.face.DesiredSize = null, size: apprt.SurfaceSize, cursor_pos: apprt.CursorPos, +/// Inspector state. +inspector: ?*inspector.Inspector = null, + /// Key input states. See gtkKeyPressed for detailed descriptions. in_keypress: bool = false, im_context: *c.GtkIMContext, @@ -218,6 +222,9 @@ pub fn deinit(self: *Surface) void { // We don't allocate anything if we aren't realized. if (!self.realized) return; + // Delete our inspector if we have one + self.controlInspector(.hide); + // Remove ourselves from the list of known surfaces in the app. self.app.core_app.deleteSurface(self); @@ -281,6 +288,33 @@ pub fn close(self: *Surface, processActive: bool) void { c.gtk_widget_show(alert); } +pub fn controlInspector(self: *Surface, mode: input.InspectorMode) void { + const show = switch (mode) { + .toggle => self.inspector == null, + .show => true, + .hide => false, + }; + + if (!show) { + if (self.inspector) |v| { + v.close(true); + self.inspector = null; + } + + return; + } + + // If we already have an inspector, we don't need to show anything. + if (self.inspector != null) return; + self.inspector = inspector.Inspector.create( + self, + .{ .window = {} }, + ) catch |err| { + log.err("failed to control inspector err={}", .{err}); + return; + }; +} + pub fn toggleFullscreen(self: *Surface, mac_non_native: configpkg.NonNativeFullscreen) void { self.window.toggleFullscreen(mac_non_native); } diff --git a/src/apprt/gtk/inspector.zig b/src/apprt/gtk/inspector.zig index b45ec8467..4d87a69de 100644 --- a/src/apprt/gtk/inspector.zig +++ b/src/apprt/gtk/inspector.zig @@ -1,7 +1,9 @@ const std = @import("std"); const Allocator = std.mem.Allocator; +const assert = std.debug.assert; const App = @import("App.zig"); +const Surface = @import("Surface.zig"); const TerminalWindow = @import("Window.zig"); const ImguiWidget = @import("ImguiWidget.zig"); const c = @import("c.zig"); @@ -9,37 +11,127 @@ const icon = @import("icon.zig"); const log = std.log.scoped(.inspector); -/// A window to hold a dedicated inspector instance. -pub const Window = struct { - app: *App, +/// Inspector is the primary stateful object that represents a terminal +/// inspector. An inspector is 1:1 with a Surface and is owned by a Surface. +/// Closing a surface must close its inspector. +pub const Inspector = struct { + /// The surface that owns this inspector. + surface: *Surface, + + /// The current state of where this inspector is rendered. The Inspector + /// is the state of the inspector but this is the state of the GUI. + location: LocationState, + + /// This is true if we want to destroy this inspector as soon as the + /// location is closed. For example: set this to true, request the + /// window be closed, let GTK do its cleanup, then note this to destroy + /// the inner state. + request_destroy: bool = false, + + /// Location where the inspector will be launched. + pub const Location = union(LocationKey) { + hidden: void, + window: void, + }; + + /// The internal state for each possible location. + const LocationState = union(LocationKey) { + hidden: void, + window: Window, + }; + + const LocationKey = enum { + /// No GUI, but load the inspector state. + hidden, + + /// A dedicated window for the inspector. + window, + }; + + /// Create an inspector for the given surface in the given location. + pub fn create(surface: *Surface, location: Location) !*Inspector { + const alloc = surface.app.core_app.alloc; + var ptr = try alloc.create(Inspector); + errdefer alloc.destroy(ptr); + try ptr.init(surface, location); + return ptr; + } + + /// Destroy all memory associated with this inspector. You generally + /// should NOT call this publicly and should call `close` instead to + /// use the GTK lifecycle. + pub fn destroy(self: *Inspector) void { + assert(self.location == .hidden); + const alloc = self.allocator(); + self.deinit(); + alloc.destroy(self); + } + + fn init(self: *Inspector, surface: *Surface, location: Location) !void { + self.* = .{ + .surface = surface, + .location = undefined, + }; + + switch (location) { + .hidden => self.location = .{ .hidden = {} }, + .window => try self.initWindow(), + } + } + + fn deinit(self: *Inspector) void { + _ = self; + } + + /// Request the inspector is closed. If request_destroy is true, also + /// destroy the inspector state when the GUI is closed. + pub fn close(self: *Inspector, request_destroy: bool) void { + self.request_destroy = request_destroy; + switch (self.location) { + .hidden => self.locationDidClose(), + .window => |v| v.close(), + } + } + + fn locationDidClose(self: *Inspector) void { + self.location = .{ .hidden = {} }; + if (self.request_destroy) self.destroy(); + } + + fn allocator(self: *const Inspector) Allocator { + return self.surface.app.core_app.alloc; + } + + fn initWindow(self: *Inspector) !void { + self.location = .{ .window = undefined }; + try self.location.window.init(self); + } +}; + +/// A dedicated window to hold an inspector instance. +const Window = struct { + inspector: *Inspector, window: *c.GtkWindow, icon: icon.Icon, imgui_widget: ImguiWidget, - pub fn create(alloc: Allocator, app: *App) !*Window { - var window = try alloc.create(Window); - errdefer alloc.destroy(window); - try window.init(app); - return window; - } - - pub fn init(self: *Window, app: *App) !void { + pub fn init(self: *Window, inspector: *Inspector) !void { // Initialize to undefined self.* = .{ - .app = app, + .inspector = inspector, .icon = undefined, .window = undefined, .imgui_widget = undefined, }; // Create the window - const window = c.gtk_application_window_new(app.app); + const window = c.gtk_application_window_new(inspector.surface.app.app); const gtk_window: *c.GtkWindow = @ptrCast(window); errdefer c.gtk_window_destroy(gtk_window); self.window = gtk_window; c.gtk_window_set_title(gtk_window, "Ghostty"); c.gtk_window_set_default_size(gtk_window, 1000, 600); - self.icon = try icon.appIcon(self.app, window); + self.icon = try icon.appIcon(self.inspector.surface.app, window); c.gtk_window_set_icon_name(gtk_window, self.icon.name); // Initialize our imgui widget @@ -55,7 +147,12 @@ pub const Window = struct { } pub fn deinit(self: *Window) void { - self.icon.deinit(self.app); + self.icon.deinit(self.inspector.surface.app); + self.inspector.locationDidClose(); + } + + pub fn close(self: *const Window) void { + c.gtk_window_destroy(self.window); } /// "destroy" signal for the window @@ -64,8 +161,6 @@ pub const Window = struct { log.debug("window destroy", .{}); const self: *Window = @ptrCast(@alignCast(ud.?)); - const alloc = self.app.core_app.alloc; self.deinit(); - alloc.destroy(self); } };