diff --git a/src/inspector/Inspector.zig b/src/inspector/Inspector.zig index 6035e8c2b..9c56a7920 100644 --- a/src/inspector/Inspector.zig +++ b/src/inspector/Inspector.zig @@ -65,6 +65,12 @@ pub fn deinit(self: *Inspector, alloc: Allocator) void { self.gui.deinit(alloc); } +/// Returns the renderer info panel. This is a convenience function +/// to access and find this state to read and modify. +pub fn rendererInfo(self: *Inspector) *widgets.renderer.Info { + return &self.gui.renderer_info; +} + /// Record a keyboard event. pub fn recordKeyEvent( self: *Inspector, diff --git a/src/inspector/widgets.zig b/src/inspector/widgets.zig index 6343b2d85..dd8ebc002 100644 --- a/src/inspector/widgets.zig +++ b/src/inspector/widgets.zig @@ -3,6 +3,7 @@ const cimgui = @import("dcimgui"); pub const page = @import("widgets/page.zig"); pub const pagelist = @import("widgets/pagelist.zig"); pub const key = @import("widgets/key.zig"); +pub const renderer = @import("widgets/renderer.zig"); pub const screen = @import("widgets/screen.zig"); pub const style = @import("widgets/style.zig"); pub const surface = @import("widgets/surface.zig"); diff --git a/src/inspector/widgets/renderer.zig b/src/inspector/widgets/renderer.zig new file mode 100644 index 000000000..3c6492dfe --- /dev/null +++ b/src/inspector/widgets/renderer.zig @@ -0,0 +1,71 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const cimgui = @import("dcimgui"); +const widgets = @import("../widgets.zig"); +const renderer = @import("../../renderer.zig"); + +const log = std.log.scoped(.inspector_renderer); + +/// Renderer information inspector widget. +pub const Info = struct { + features: std.AutoArrayHashMapUnmanaged( + std.meta.Tag(renderer.Overlay.Feature), + renderer.Overlay.Feature, + ), + + pub const empty: Info = .{ + .features = .empty, + }; + + pub fn deinit(self: *Info, alloc: Allocator) void { + self.features.deinit(alloc); + } + + /// Grab the features into a new allocated slice. This is used by + pub fn overlayFeatures( + self: *const Info, + alloc: Allocator, + ) Allocator.Error![]renderer.Overlay.Feature { + // The features from our internal state. + const features = self.features.values(); + + // For now we do a dumb copy since the features have no managed + // memory. + const result = try alloc.dupe( + renderer.Overlay.Feature, + features, + ); + errdefer alloc.free(result); + + return result; + } + + /// Draw the renderer info window. + pub fn draw( + self: *Info, + alloc: Allocator, + open: bool, + ) void { + if (!open) return; + + cimgui.c.ImGui_SeparatorText("Overlays"); + + // Hyperlinks + { + var hyperlinks: bool = self.features.contains(.highlight_hyperlinks); + _ = cimgui.c.ImGui_Checkbox("Overlay Hyperlinks", &hyperlinks); + cimgui.c.ImGui_SameLine(); + widgets.helpMarker("When enabled, highlights OSC8 hyperlinks."); + + if (!hyperlinks) { + _ = self.features.swapRemove(.highlight_hyperlinks); + } else { + self.features.put( + alloc, + .highlight_hyperlinks, + .highlight_hyperlinks, + ) catch log.warn("error enabling hyperlink overlay feature", .{}); + } + } + } +}; diff --git a/src/inspector/widgets/surface.zig b/src/inspector/widgets/surface.zig index 9f52501d4..3b69f214c 100644 --- a/src/inspector/widgets/surface.zig +++ b/src/inspector/widgets/surface.zig @@ -16,6 +16,7 @@ const window_keyboard = "Keyboard"; const window_terminal = "Terminal"; const window_surface = "Surface"; const window_termio = "Terminal IO"; +const window_renderer = "Renderer"; pub const Inspector = struct { /// Internal GUI state @@ -23,6 +24,7 @@ pub const Inspector = struct { key_stream: widgets.key.Stream, terminal_info: widgets.terminal.Info, vt_stream: widgets.termio.Stream, + renderer_info: widgets.renderer.Info, pub fn init(alloc: Allocator) !Inspector { return .{ @@ -30,12 +32,14 @@ pub const Inspector = struct { .key_stream = try .init(alloc), .terminal_info = .empty, .vt_stream = try .init(alloc), + .renderer_info = .empty, }; } pub fn deinit(self: *Inspector, alloc: Allocator) void { self.key_stream.deinit(alloc); self.vt_stream.deinit(alloc); + self.renderer_info.deinit(alloc); } pub fn draw( @@ -116,6 +120,20 @@ pub const Inspector = struct { ); } } + + // Renderer info window + { + const open = cimgui.c.ImGui_Begin( + window_renderer, + null, + cimgui.c.ImGuiWindowFlags_NoFocusOnAppearing, + ); + defer cimgui.c.ImGui_End(); + self.renderer_info.draw( + surface.alloc, + open, + ); + } } if (first_render) { @@ -157,6 +175,7 @@ pub const Inspector = struct { cimgui.ImGui_DockBuilderDockWindow(window_surface, dock_id_main); cimgui.ImGui_DockBuilderDockWindow(window_keyboard, dock_id_main); cimgui.ImGui_DockBuilderDockWindow(window_termio, dock_id_main); + cimgui.ImGui_DockBuilderDockWindow(window_renderer, dock_id_main); cimgui.ImGui_DockBuilderDockWindow(window_imgui_demo, dock_id_main); cimgui.ImGui_DockBuilderFinish(dockspace_id); } diff --git a/src/renderer.zig b/src/renderer.zig index 2d37ddd4c..9b5164e91 100644 --- a/src/renderer.zig +++ b/src/renderer.zig @@ -19,6 +19,7 @@ pub const Metal = @import("renderer/Metal.zig"); pub const OpenGL = @import("renderer/OpenGL.zig"); pub const WebGL = @import("renderer/WebGL.zig"); pub const Options = @import("renderer/Options.zig"); +pub const Overlay = @import("renderer/Overlay.zig"); pub const Thread = @import("renderer/Thread.zig"); pub const State = @import("renderer/State.zig"); pub const CursorStyle = cursor.Style; diff --git a/src/renderer/generic.zig b/src/renderer/generic.zig index 7f0e3e00c..3c77e4cdf 100644 --- a/src/renderer/generic.zig +++ b/src/renderer/generic.zig @@ -225,13 +225,6 @@ pub fn Renderer(comptime GraphicsAPI: type) type { /// Our overlay state, if any. overlay: ?Overlay = null, - // Right now, the debug overlay is turned on and configured by - // modifying these and recompiling. In the future, we will expose - // all of this at runtime via the inspector. - const overlay_features: []const Overlay.Feature = &.{ - //.highlight_hyperlinks, - }; - const HighlightTag = enum(u8) { search_match, search_match_selected, @@ -1152,6 +1145,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type { mouse: renderer.State.Mouse, preedit: ?renderer.State.Preedit, scrollbar: terminal.Scrollbar, + overlay_features: []const Overlay.Feature, }; // Update all our data as tightly as possible within the mutex. @@ -1231,11 +1225,20 @@ pub fn Renderer(comptime GraphicsAPI: type) type { }; }; + const overlay_features: []const Overlay.Feature = overlay: { + const insp = state.inspector orelse break :overlay &.{}; + const renderer_info = insp.rendererInfo(); + break :overlay renderer_info.overlayFeatures( + arena_alloc, + ) catch &.{}; + }; + break :critical .{ .links = links, .mouse = state.mouse, .preedit = preedit, .scrollbar = scrollbar, + .overlay_features = overlay_features, }; }; @@ -1306,7 +1309,9 @@ pub fn Renderer(comptime GraphicsAPI: type) type { // Rebuild the overlay image if we have one. We can do this // outside of any critical areas. - self.rebuildOverlay() catch |err| { + self.rebuildOverlay( + critical.overlay_features, + ) catch |err| { log.warn( "error rebuilding overlay surface err={}", .{err}, @@ -2241,7 +2246,10 @@ pub fn Renderer(comptime GraphicsAPI: type) type { /// Build the overlay as configured. Returns null if there is no /// overlay currently configured. - fn rebuildOverlay(self: *Self) Overlay.InitError!void { + fn rebuildOverlay( + self: *Self, + features: []const Overlay.Feature, + ) Overlay.InitError!void { // const start = std.time.Instant.now() catch unreachable; // const start_micro = std.time.microTimestamp(); // defer { @@ -2256,7 +2264,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type { // If we have no features enabled, don't build an overlay. // If we had a previous overlay, deallocate it. - if (overlay_features.len == 0) { + if (features.len == 0) { if (self.overlay) |*old| { old.deinit(alloc); self.overlay = null; @@ -2277,7 +2285,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type { overlay.applyFeatures( alloc, &self.terminal_state, - overlay_features, + features, ); }