diff --git a/src/App.zig b/src/App.zig index c80665577..744615f6c 100644 --- a/src/App.zig +++ b/src/App.zig @@ -10,6 +10,7 @@ const Window = @import("Window.zig"); const tracy = @import("tracy"); const Config = @import("config.zig").Config; const BlockingQueue = @import("./blocking_queue.zig").BlockingQueue; +const renderer = @import("renderer.zig"); const log = std.log.scoped(.app); @@ -34,34 +35,33 @@ mailbox: *Mailbox, /// Initialize the main app instance. This creates the main window, sets /// up the renderer state, compiles the shaders, etc. This is the primary /// "startup" logic. -pub fn init(alloc: Allocator, config: *const Config) !App { +pub fn create(alloc: Allocator, config: *const Config) !*App { // The mailbox for messaging this thread var mailbox = try Mailbox.create(alloc); errdefer mailbox.destroy(alloc); - // Create the first window - var window = try Window.create(alloc, config); - errdefer window.destroy(); - - // Create our windows list and add our initial window. - var windows: WindowList = .{}; - errdefer windows.deinit(alloc); - try windows.append(alloc, window); - - return App{ + var app = try alloc.create(App); + errdefer alloc.destroy(app); + app.* = .{ .alloc = alloc, - .windows = windows, + .windows = .{}, .config = config, .mailbox = mailbox, }; + errdefer app.windows.deinit(alloc); + + // Create the first window + try app.newWindow(); + + return app; } -pub fn deinit(self: *App) void { +pub fn destroy(self: *App) void { // Clean up all our windows for (self.windows.items) |window| window.destroy(); self.windows.deinit(self.alloc); self.mailbox.destroy(self.alloc); - self.* = undefined; + self.alloc.destroy(self); } /// Wake up the app event loop. This should be called after any messages @@ -86,6 +86,11 @@ pub fn run(self: *App) !void { while (i < self.windows.items.len) { const window = self.windows.items[i]; if (window.shouldClose()) { + // If this was our final window, deinitialize the renderer + if (self.windows.items.len == 1) { + renderer.Renderer.lastWindowDeinit(); + } + window.destroy(); _ = self.windows.swapRemove(i); continue; @@ -107,11 +112,24 @@ fn drainMailbox(self: *App) !void { while (drain.next()) |message| { log.debug("mailbox message={}", .{message}); switch (message) { - .new_window => unreachable, + .new_window => try self.newWindow(), } } } +/// Create a new window +fn newWindow(self: *App) !void { + var window = try Window.create(self.alloc, self, self.config); + errdefer window.destroy(); + try self.windows.append(self.alloc, window); + errdefer _ = self.windows.pop(); + + // This was the first window, so we need to initialize our renderer. + if (self.windows.items.len == 1) { + try window.renderer.firstWindowInit(window.window); + } +} + /// The message types that can be sent to the app thread. pub const Message = union(enum) { /// Create a new terminal window. diff --git a/src/Window.zig b/src/Window.zig index 83237d391..89721364d 100644 --- a/src/Window.zig +++ b/src/Window.zig @@ -22,6 +22,7 @@ const terminal = @import("terminal/main.zig"); const Config = @import("config.zig").Config; const input = @import("input.zig"); const DevMode = @import("DevMode.zig"); +const App = @import("App.zig"); // Get native API access on certain platforms so we can do more customization. const glfwNative = glfw.Native(.{ @@ -36,6 +37,9 @@ const Renderer = renderer.Renderer; /// Allocator alloc: Allocator, +/// The app that this window is a part of. +app: *App, + /// The font structures font_lib: font.Library, font_group: *font.GroupCache, @@ -107,7 +111,7 @@ const Mouse = struct { /// Create a new window. This allocates and returns a pointer because we /// need a stable pointer for user data callbacks. Therefore, a stack-only /// initialization is not currently possible. -pub fn create(alloc: Allocator, config: *const Config) !*Window { +pub fn create(alloc: Allocator, app: *App, config: *const Config) !*Window { var self = try alloc.create(Window); errdefer alloc.destroy(self); @@ -313,6 +317,7 @@ pub fn create(alloc: Allocator, config: *const Config) !*Window { self.* = .{ .alloc = alloc, + .app = app, .font_lib = font_lib, .font_group = font_group, .window = window, @@ -386,7 +391,7 @@ pub fn create(alloc: Allocator, config: *const Config) !*Window { // Give the renderer one more opportunity to finalize any window // setup on the main thread prior to spinning up the rendering thread. - try renderer_impl.finalizeInit(window); + try renderer_impl.finalizeWindowInit(window); // Start our renderer thread self.renderer_thr = try std.Thread.spawn( @@ -752,6 +757,13 @@ fn keyCallback( DevMode.instance.visible = !DevMode.instance.visible; win.queueRender() catch unreachable; } else log.warn("dev mode was not compiled into this binary", .{}), + + .new_window => { + _ = win.app.mailbox.push(.{ + .new_window = {}, + }, .{ .forever = {} }); + win.app.wakeup(); + }, } // Bindings always result in us ignoring the char if printable diff --git a/src/config.zig b/src/config.zig index 9cd4ebdc3..4874f99ec 100644 --- a/src/config.zig +++ b/src/config.zig @@ -157,6 +157,12 @@ pub const Config = struct { .{ .toggle_dev_mode = {} }, ); + try result.keybind.set.put( + alloc, + .{ .key = .up, .mods = .{ .super = true } }, + .{ .new_window = {} }, + ); + return result; } diff --git a/src/input/Binding.zig b/src/input/Binding.zig index f6e602fc5..dd015b0c1 100644 --- a/src/input/Binding.zig +++ b/src/input/Binding.zig @@ -133,6 +133,9 @@ pub const Action = union(enum) { /// Dev mode toggle_dev_mode: void, + + /// Open a new terminal window. + new_window: void, }; /// Trigger is the associated key state that can trigger an action. diff --git a/src/main.zig b/src/main.zig index a985416cb..41d03e5a9 100644 --- a/src/main.zig +++ b/src/main.zig @@ -128,8 +128,8 @@ pub fn main() !void { defer glfw.terminate(); // Run our app - var app = try App.init(alloc, &config); - defer app.deinit(); + var app = try App.create(alloc, &config); + defer app.destroy(); try app.run(); } diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index 1b4a9112a..489826ebd 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -240,11 +240,6 @@ pub fn init(alloc: Allocator, font_group: *font.GroupCache) !Metal { } pub fn deinit(self: *Metal) void { - if (DevMode.enabled) { - imgui.ImplMetal.shutdown(); - imgui.ImplGlfw.shutdown(); - } - self.cells.deinit(self.alloc); self.font_shaper.deinit(); @@ -255,7 +250,7 @@ pub fn deinit(self: *Metal) void { /// This is called just prior to spinning up the renderer thread for /// final main thread setup requirements. -pub fn finalizeInit(self: *const Metal, window: glfw.Window) !void { +pub fn finalizeWindowInit(self: *const Metal, window: glfw.Window) !void { // Set our window backing layer to be our swapchain const nswindow = objc.Object.fromId(glfwNative.getCocoaWindow(window).?); const contentView = objc.Object.fromId(nswindow.getProperty(?*anyopaque, "contentView").?); @@ -268,7 +263,12 @@ pub fn finalizeInit(self: *const Metal, window: glfw.Window) !void { const layer = contentView.getProperty(objc.Object, "layer"); const scaleFactor = nswindow.getProperty(macos.graphics.c.CGFloat, "backingScaleFactor"); layer.setProperty("contentsScale", scaleFactor); +} +/// This is called only after the first window is opened. This may be +/// called multiple times if all windows are closed and a new one is +/// reopened. +pub fn firstWindowInit(self: *const Metal, window: glfw.Window) !void { if (DevMode.enabled) { // Initialize for our window assert(imgui.ImplGlfw.initForOther( @@ -279,6 +279,14 @@ pub fn finalizeInit(self: *const Metal, window: glfw.Window) !void { } } +/// This is called only when the last window is destroyed. +pub fn lastWindowDeinit() void { + if (DevMode.enabled) { + imgui.ImplMetal.shutdown(); + imgui.ImplGlfw.shutdown(); + } +} + /// Callback called by renderer.Thread when it begins. pub fn threadEnter(self: *const Metal, window: glfw.Window) !void { _ = self; diff --git a/src/renderer/OpenGL.zig b/src/renderer/OpenGL.zig index b5479ce69..efa1f3a1e 100644 --- a/src/renderer/OpenGL.zig +++ b/src/renderer/OpenGL.zig @@ -300,11 +300,6 @@ pub fn init(alloc: Allocator, font_group: *font.GroupCache) !OpenGL { } pub fn deinit(self: *OpenGL) void { - if (DevMode.enabled) { - imgui.ImplOpenGL3.shutdown(); - imgui.ImplGlfw.shutdown(); - } - self.font_shaper.deinit(); self.alloc.free(self.font_shaper.cell_buf); @@ -387,7 +382,17 @@ pub fn windowInit(window: glfw.Window) !void { /// This is called just prior to spinning up the renderer thread for /// final main thread setup requirements. -pub fn finalizeInit(self: *const OpenGL, window: glfw.Window) !void { +pub fn finalizeWindowInit(self: *const OpenGL, window: glfw.Window) !void { + _ = self; + _ = window; +} + +/// This is called only after the first window is opened. This may be +/// called multiple times if all windows are closed and a new one is +/// reopened. +pub fn firstWindowInit(self: *const OpenGL, window: glfw.Window) !void { + _ = self; + if (DevMode.enabled) { // Initialize for our window assert(imgui.ImplGlfw.initForOpenGL( @@ -396,9 +401,14 @@ pub fn finalizeInit(self: *const OpenGL, window: glfw.Window) !void { )); assert(imgui.ImplOpenGL3.init("#version 330 core")); } +} - // Call thread exit to clean up our context - self.threadExit(); +/// This is called only when the last window is destroyed. +pub fn lastWindowDeinit() void { + if (DevMode.enabled) { + imgui.ImplOpenGL3.shutdown(); + imgui.ImplGlfw.shutdown(); + } } /// Callback called by renderer.Thread when it begins.