diff --git a/src/App.zig b/src/App.zig index 12a4389c7..b9b9501eb 100644 --- a/src/App.zig +++ b/src/App.zig @@ -205,6 +205,7 @@ fn drainMailbox(self: *App, rt_app: *apprt.App) !void { .quit => try self.setQuit(), .surface_message => |msg| try self.surfaceMessage(msg.surface, msg.message), .redraw_surface => |surface| try self.redrawSurface(rt_app, surface), + .redraw_inspector => |surface| try self.redrawInspector(rt_app, surface), } } } @@ -232,6 +233,11 @@ fn redrawSurface(self: *App, rt_app: *apprt.App, surface: *apprt.Surface) !void rt_app.redrawSurface(surface); } +fn redrawInspector(self: *App, rt_app: *apprt.App, surface: *apprt.Surface) !void { + if (!self.hasSurface(&surface.core_surface)) return; + rt_app.redrawInspector(surface); +} + /// Create a new window pub fn newWindow(self: *App, rt_app: *apprt.App, msg: Message.NewWindow) !void { if (!@hasDecl(apprt.App, "newWindow")) { @@ -304,6 +310,10 @@ pub const Message = union(enum) { /// message if it needs to. redraw_surface: *apprt.Surface, + /// Redraw the inspector. This is called whenever some non-OS event + /// causes the inspector to need to be redrawn. + redraw_inspector: *apprt.Surface, + const NewWindow = struct { /// The parent surface parent: ?*Surface = null, diff --git a/src/Surface.zig b/src/Surface.zig index 5cb08de89..b59a90daf 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -584,10 +584,16 @@ pub fn activateInspector(self: *Surface) !void { self.inspector = ptr; // Put the inspector onto the render state - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); - assert(self.renderer_state.inspector == null); - self.renderer_state.inspector = self.inspector; + { + self.renderer_state.mutex.lock(); + defer self.renderer_state.mutex.unlock(); + assert(self.renderer_state.inspector == null); + self.renderer_state.inspector = self.inspector; + } + + // Notify our components we have an inspector active + _ = self.renderer_thread.mailbox.push(.{ .inspector = true }, .{ .forever = {} }); + _ = self.io_thread.mailbox.push(.{ .inspector = true }, .{ .forever = {} }); } /// Deactivate the inspector and stop collecting any information. @@ -602,6 +608,10 @@ pub fn deactivateInspector(self: *Surface) void { self.renderer_state.inspector = null; } + // Notify our components we have deactivated inspector + _ = self.renderer_thread.mailbox.push(.{ .inspector = false }, .{ .forever = {} }); + _ = self.io_thread.mailbox.push(.{ .inspector = false }, .{ .forever = {} }); + // Deinit the inspector inspector.deinit(); self.alloc.destroy(inspector); diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index 6c929b9f5..173fc4378 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -181,6 +181,11 @@ pub const App = struct { // No-op, we use a threaded interface so we're constantly drawing. } + pub fn redrawInspector(self: *App, surface: *Surface) void { + _ = self; + surface.queueInspectorRender(); + } + pub fn newWindow(self: *App, parent: ?*CoreSurface) !void { _ = self; diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig index ff669628b..74d3c6b76 100644 --- a/src/apprt/gtk/App.zig +++ b/src/apprt/gtk/App.zig @@ -279,6 +279,12 @@ pub fn redrawSurface(self: *App, surface: *Surface) void { surface.redraw(); } +/// Redraw the inspector for the given surface. +pub fn redrawInspector(self: *App, surface: *Surface) void { + _ = self; + surface.queueInspectorRender(); +} + /// Called by CoreApp to create a new window with a new surface. pub fn newWindow(self: *App, parent_: ?*CoreSurface) !void { const alloc = self.core_app.alloc; diff --git a/src/apprt/gtk/Surface.zig b/src/apprt/gtk/Surface.zig index 688a48c29..c6403a0d5 100644 --- a/src/apprt/gtk/Surface.zig +++ b/src/apprt/gtk/Surface.zig @@ -239,10 +239,14 @@ pub fn deinit(self: *Surface) void { } fn render(self: *Surface) !void { - if (self.inspector) |v| v.queueRender(); try self.core_surface.renderer.draw(); } +/// Queue the inspector to render if we have one. +pub fn queueInspectorRender(self: *Surface) void { + if (self.inspector) |v| v.queueRender(); +} + /// Invalidate the surface so that it forces a redraw on the next tick. pub fn redraw(self: *Surface) void { c.gtk_gl_area_queue_render(self.gl_area); diff --git a/src/renderer/Thread.zig b/src/renderer/Thread.zig index df3001b4e..f1c2280ca 100644 --- a/src/renderer/Thread.zig +++ b/src/renderer/Thread.zig @@ -48,11 +48,6 @@ cursor_h: xev.Timer, cursor_c: xev.Completion = .{}, cursor_c_cancel: xev.Completion = .{}, -/// This is true when a blinking cursor should be visible and false -/// when it should not be visible. This is toggled on a timer by the -/// thread automatically. -cursor_blink_visible: bool = false, - /// The surface we're rendering to. surface: *apprt.Surface, @@ -69,6 +64,16 @@ mailbox: *Mailbox, /// Mailbox to send messages to the app thread app_mailbox: App.Mailbox, +flags: packed struct { + /// This is true when a blinking cursor should be visible and false + /// when it should not be visible. This is toggled on a timer by the + /// thread automatically. + cursor_blink_visible: bool = false, + + /// This is true when the inspector is active. + has_inspector: bool = false, +} = .{}, + /// Initialize the thread. This does not START the thread. This only sets /// up all the internal state necessary prior to starting the thread. It /// is up to the caller to start the thread with the threadMain entrypoint. @@ -225,7 +230,7 @@ fn drainMailbox(self: *Thread) !void { // If we're focused, we immediately show the cursor again // and then restart the timer. if (self.cursor_c.state() != .active) { - self.cursor_blink_visible = true; + self.flags.cursor_blink_visible = true; self.cursor_h.run( &self.loop, &self.cursor_c, @@ -239,7 +244,7 @@ fn drainMailbox(self: *Thread) !void { }, .reset_cursor_blink => { - self.cursor_blink_visible = true; + self.flags.cursor_blink_visible = true; if (self.cursor_c.state() == .active) { self.cursor_h.reset( &self.loop, @@ -265,6 +270,8 @@ fn drainMailbox(self: *Thread) !void { defer config.alloc.destroy(config.ptr); try self.renderer.changeConfig(config.ptr); }, + + .inspector => |v| self.flags.has_inspector = v, } } } @@ -322,10 +329,15 @@ fn renderCallback( return .disarm; }; + // If we have an inspector, let the app know we want to rerender that. + if (t.flags.has_inspector) { + _ = t.app_mailbox.push(.{ .redraw_inspector = t.surface }, .{ .instant = {} }); + } + t.renderer.render( t.surface, t.state, - t.cursor_blink_visible, + t.flags.cursor_blink_visible, ) catch |err| log.warn("error rendering err={}", .{err}); @@ -365,7 +377,7 @@ fn cursorTimerCallback( return .disarm; }; - t.cursor_blink_visible = !t.cursor_blink_visible; + t.flags.cursor_blink_visible = !t.flags.cursor_blink_visible; t.wakeup.notify() catch {}; t.cursor_h.run(&t.loop, &t.cursor_c, CURSOR_BLINK_INTERVAL, Thread, t, cursorTimerCallback); diff --git a/src/renderer/message.zig b/src/renderer/message.zig index 19cde4385..d3fdc21de 100644 --- a/src/renderer/message.zig +++ b/src/renderer/message.zig @@ -34,4 +34,7 @@ pub const Message = union(enum) { alloc: Allocator, ptr: *renderer.Renderer.DerivedConfig, }, + + /// Activate or deactivate the inspector. + inspector: bool, }; diff --git a/src/termio/Thread.zig b/src/termio/Thread.zig index 459cec97c..93faa38d5 100644 --- a/src/termio/Thread.zig +++ b/src/termio/Thread.zig @@ -62,14 +62,19 @@ sync_reset_cancel_c: xev.Completion = .{}, /// The underlying IO implementation. impl: *termio.Impl, -/// True if linefeed mode is enabled. This is duplicated here so that the -/// write thread doesn't need to grab a lock to check this on every write. -linefeed_mode: bool = false, - /// The mailbox that can be used to send this thread messages. Note /// this is a blocking queue so if it is full you will get errors (or block). mailbox: *Mailbox, +flags: packed struct { + /// True if linefeed mode is enabled. This is duplicated here so that the + /// write thread doesn't need to grab a lock to check this on every write. + linefeed_mode: bool = false, + + /// This is true when the inspector is active. + has_inspector: bool = false, +} = .{}, + /// Initialize the thread. This does not START the thread. This only sets /// up all the internal state necessary prior to starting the thread. It /// is up to the caller to start the thread with the threadMain entrypoint. @@ -174,17 +179,18 @@ fn drainMailbox(self: *Thread) !void { defer config.alloc.destroy(config.ptr); try self.impl.changeConfig(config.ptr); }, + .inspector => |v| self.flags.has_inspector = v, .resize => |v| self.handleResize(v), .clear_screen => |v| try self.impl.clearScreen(v.history), .scroll_viewport => |v| try self.impl.scrollViewport(v), .jump_to_prompt => |v| try self.impl.jumpToPrompt(v), .start_synchronized_output => self.startSynchronizedOutput(), - .linefeed_mode => |v| self.linefeed_mode = v, - .write_small => |v| try self.impl.queueWrite(v.data[0..v.len], self.linefeed_mode), - .write_stable => |v| try self.impl.queueWrite(v, self.linefeed_mode), + .linefeed_mode => |v| self.flags.linefeed_mode = v, + .write_small => |v| try self.impl.queueWrite(v.data[0..v.len], self.flags.linefeed_mode), + .write_stable => |v| try self.impl.queueWrite(v, self.flags.linefeed_mode), .write_alloc => |v| { defer v.alloc.free(v.data); - try self.impl.queueWrite(v.data, self.linefeed_mode); + try self.impl.queueWrite(v.data, self.flags.linefeed_mode); }, } } diff --git a/src/termio/message.zig b/src/termio/message.zig index c7fe19976..bffe0592a 100644 --- a/src/termio/message.zig +++ b/src/termio/message.zig @@ -36,6 +36,9 @@ pub const Message = union(enum) { ptr: *termio.Impl.DerivedConfig, }, + /// Activate or deactivate the inspector. + inspector: bool, + /// Resize the window. resize: Resize,