diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-04-04 19:22:04 +0300 |
|---|---|---|
| committer | Can Celik <ogulcancelik@gmail.com> | 2026-04-05 01:23:06 +0300 |
| commit | 41dd733ca7123c9ac4bfc6c781697a660e95b052 (patch) | |
| tree | 3fc91815ca139ef2e0b281bba0abda4ef15175f4 /vendor/libghostty-vt/src/inspector/widgets/surface.zig | |
| parent | 55c138263e190a1e79d4b997bfb9397306c0ec1f (diff) | |
feat: finish ghostty pane backend integration
Diffstat (limited to 'vendor/libghostty-vt/src/inspector/widgets/surface.zig')
| -rw-r--r-- | vendor/libghostty-vt/src/inspector/widgets/surface.zig | 503 |
1 files changed, 503 insertions, 0 deletions
diff --git a/vendor/libghostty-vt/src/inspector/widgets/surface.zig b/vendor/libghostty-vt/src/inspector/widgets/surface.zig new file mode 100644 index 00000000..d73e784c --- /dev/null +++ b/vendor/libghostty-vt/src/inspector/widgets/surface.zig @@ -0,0 +1,503 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const assert = @import("../../quirks.zig").inlineAssert; +const Allocator = std.mem.Allocator; +const cimgui = @import("dcimgui"); +const inspector = @import("../main.zig"); +const widgets = @import("../widgets.zig"); +const input = @import("../../input.zig"); +const renderer = @import("../../renderer.zig"); +const terminal = @import("../../terminal/main.zig"); +const Surface = @import("../../Surface.zig"); + +/// This is discovered via the hardcoded string in the ImGui demo window. +const window_imgui_demo = "Dear ImGui Demo"; +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 + surface_info: Info, + key_stream: widgets.key.Stream, + terminal_info: widgets.terminal.Info, + vt_stream: widgets.termio.Stream, + renderer_info: widgets.renderer.Info, + show_demo_window: bool, + + pub fn init(alloc: Allocator) !Inspector { + return .{ + .surface_info = .empty, + .key_stream = try .init(alloc), + .terminal_info = .empty, + .vt_stream = try .init(alloc), + .renderer_info = .empty, + .show_demo_window = true, + }; + } + + 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( + self: *Inspector, + surface: *const Surface, + mouse: Mouse, + ) void { + // Create our dockspace first. If we had to setup our dockspace, + // then it is a first render. + const dockspace_id = cimgui.c.ImGui_GetID("Main Dockspace"); + const first_render = createDockSpace(dockspace_id); + + // Draw everything that requires the terminal state mutex. + { + surface.renderer_state.mutex.lock(); + defer surface.renderer_state.mutex.unlock(); + const t = surface.renderer_state.terminal; + + // Terminal info window + { + const open = cimgui.c.ImGui_Begin( + window_terminal, + null, + cimgui.c.ImGuiWindowFlags_NoFocusOnAppearing, + ); + defer cimgui.c.ImGui_End(); + self.terminal_info.draw(open, t); + } + + // Surface info window + { + const open = cimgui.c.ImGui_Begin( + window_surface, + null, + cimgui.c.ImGuiWindowFlags_NoFocusOnAppearing, + ); + defer cimgui.c.ImGui_End(); + self.surface_info.draw( + open, + surface, + mouse, + ); + } + + // Keyboard info window + { + const open = cimgui.c.ImGui_Begin( + window_keyboard, + null, + cimgui.c.ImGuiWindowFlags_NoFocusOnAppearing, + ); + defer cimgui.c.ImGui_End(); + self.key_stream.draw( + open, + surface.alloc, + ); + } + + // Terminal IO window + { + const open = cimgui.c.ImGui_Begin( + window_termio, + null, + cimgui.c.ImGuiWindowFlags_NoFocusOnAppearing, + ); + defer cimgui.c.ImGui_End(); + if (open) { + self.vt_stream.draw( + surface.alloc, + &t.colors.palette.current, + ); + } + } + + // 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, + ); + } + } + + // In debug we show the ImGui demo window so we can easily view + // available widgets and such. + if (comptime builtin.mode == .Debug) { + if (self.show_demo_window) { + cimgui.c.ImGui_ShowDemoWindow(&self.show_demo_window); + } + } + + if (first_render) { + // On first render, setup our initial focus state. We only + // do this on first render so that we can let the user change + // focus afterward without it snapping back. + cimgui.c.ImGui_SetWindowFocusStr(window_terminal); + } + } + + /// Create the global dock space for the inspector. A dock space + /// is a special area where windows can be docked into. The global + /// dock space fills the entire main viewport. + /// + /// Returns true if this was the first time the dock space was created. + fn createDockSpace(dockspace_id: cimgui.c.ImGuiID) bool { + const viewport: *cimgui.c.ImGuiViewport = cimgui.c.ImGui_GetMainViewport(); + + // Initial Docking setup + const setup = cimgui.ImGui_DockBuilderGetNode(dockspace_id) == null; + if (setup) { + // Register our dockspace node + assert(cimgui.ImGui_DockBuilderAddNodeEx( + dockspace_id, + cimgui.ImGuiDockNodeFlagsPrivate.DockSpace, + ) == dockspace_id); + + // Ensure it is the full size of the viewport + cimgui.ImGui_DockBuilderSetNodeSize( + dockspace_id, + viewport.Size, + ); + + // We only initialize one central docking point now but + // this is the point we'd pre-split and so on for the initial + // layout. + const dock_id_main: cimgui.c.ImGuiID = dockspace_id; + cimgui.ImGui_DockBuilderDockWindow(window_imgui_demo, dock_id_main); + cimgui.ImGui_DockBuilderDockWindow(window_terminal, dock_id_main); + 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_DockBuilderFinish(dockspace_id); + } + + // Put the dockspace over the viewport. + assert(cimgui.c.ImGui_DockSpaceOverViewportEx( + dockspace_id, + viewport, + cimgui.c.ImGuiDockNodeFlags_PassthruCentralNode, + null, + ) == dockspace_id); + return setup; + } +}; + +pub const Mouse = struct { + /// Last hovered x/y + last_xpos: f64 = 0, + last_ypos: f64 = 0, + + // Last hovered screen point + last_point: ?terminal.Pin = null, +}; + +/// Surface information inspector widget. +pub const Info = struct { + pub const empty: Info = .{}; + + /// Draw the surface info window. + pub fn draw( + self: *Info, + open: bool, + surface: *const Surface, + mouse: Mouse, + ) void { + _ = self; + if (!open) return; + + if (cimgui.c.ImGui_CollapsingHeader( + "Help", + cimgui.c.ImGuiTreeNodeFlags_None, + )) { + cimgui.c.ImGui_TextWrapped( + "This window displays information about the surface (window). " ++ + "A surface is the graphical area that displays the terminal " ++ + "content. It includes dimensions, font sizing, and mouse state " ++ + "information specific to this window instance.", + ); + } + + cimgui.c.ImGui_SeparatorText("Dimensions"); + dimensionsTable(surface); + + cimgui.c.ImGui_SeparatorText("Font"); + fontTable(surface); + + cimgui.c.ImGui_SeparatorText("Mouse"); + mouseTable(surface, mouse); + } +}; + +fn dimensionsTable(surface: *const Surface) void { + _ = cimgui.c.ImGui_BeginTable( + "table_size", + 2, + cimgui.c.ImGuiTableFlags_None, + ); + defer cimgui.c.ImGui_EndTable(); + + // Screen Size + { + cimgui.c.ImGui_TableNextRow(); + { + _ = cimgui.c.ImGui_TableSetColumnIndex(0); + cimgui.c.ImGui_Text("Screen Size"); + } + { + _ = cimgui.c.ImGui_TableSetColumnIndex(1); + cimgui.c.ImGui_Text( + "%dpx x %dpx", + surface.size.screen.width, + surface.size.screen.height, + ); + } + } + + // Grid Size + { + cimgui.c.ImGui_TableNextRow(); + { + _ = cimgui.c.ImGui_TableSetColumnIndex(0); + cimgui.c.ImGui_Text("Grid Size"); + } + { + _ = cimgui.c.ImGui_TableSetColumnIndex(1); + const grid_size = surface.size.grid(); + cimgui.c.ImGui_Text( + "%dc x %dr", + grid_size.columns, + grid_size.rows, + ); + } + } + + // Cell Size + { + cimgui.c.ImGui_TableNextRow(); + { + _ = cimgui.c.ImGui_TableSetColumnIndex(0); + cimgui.c.ImGui_Text("Cell Size"); + } + { + _ = cimgui.c.ImGui_TableSetColumnIndex(1); + cimgui.c.ImGui_Text( + "%dpx x %dpx", + surface.size.cell.width, + surface.size.cell.height, + ); + } + } + + // Padding + { + cimgui.c.ImGui_TableNextRow(); + { + _ = cimgui.c.ImGui_TableSetColumnIndex(0); + cimgui.c.ImGui_Text("Window Padding"); + } + { + _ = cimgui.c.ImGui_TableSetColumnIndex(1); + cimgui.c.ImGui_Text( + "T=%d B=%d L=%d R=%d px", + surface.size.padding.top, + surface.size.padding.bottom, + surface.size.padding.left, + surface.size.padding.right, + ); + } + } +} + +fn fontTable(surface: *const Surface) void { + _ = cimgui.c.ImGui_BeginTable( + "table_font", + 2, + cimgui.c.ImGuiTableFlags_None, + ); + defer cimgui.c.ImGui_EndTable(); + + { + cimgui.c.ImGui_TableNextRow(); + { + _ = cimgui.c.ImGui_TableSetColumnIndex(0); + cimgui.c.ImGui_Text("Size (Points)"); + } + { + _ = cimgui.c.ImGui_TableSetColumnIndex(1); + cimgui.c.ImGui_Text( + "%.2f pt", + surface.font_size.points, + ); + } + } + + { + cimgui.c.ImGui_TableNextRow(); + { + _ = cimgui.c.ImGui_TableSetColumnIndex(0); + cimgui.c.ImGui_Text("Size (Pixels)"); + } + { + _ = cimgui.c.ImGui_TableSetColumnIndex(1); + cimgui.c.ImGui_Text( + "%.2f px", + surface.font_size.pixels(), + ); + } + } +} + +fn mouseTable( + surface: *const Surface, + mouse: Mouse, +) void { + _ = cimgui.c.ImGui_BeginTable( + "table_mouse", + 2, + cimgui.c.ImGuiTableFlags_None, + ); + defer cimgui.c.ImGui_EndTable(); + + const surface_mouse = &surface.mouse; + const t = surface.renderer_state.terminal; + + { + const hover_point: terminal.point.Coordinate = pt: { + const p = mouse.last_point orelse break :pt .{}; + const pt = t.screens.active.pages.pointFromPin( + .active, + p, + ) orelse break :pt .{}; + break :pt pt.coord(); + }; + + cimgui.c.ImGui_TableNextRow(); + { + _ = cimgui.c.ImGui_TableSetColumnIndex(0); + cimgui.c.ImGui_Text("Hover Grid"); + } + { + _ = cimgui.c.ImGui_TableSetColumnIndex(1); + cimgui.c.ImGui_Text( + "row=%d, col=%d", + hover_point.y, + hover_point.x, + ); + } + } + + { + const coord: renderer.Coordinate.Terminal = (renderer.Coordinate{ + .surface = .{ + .x = mouse.last_xpos, + .y = mouse.last_ypos, + }, + }).convert(.terminal, surface.size).terminal; + + cimgui.c.ImGui_TableNextRow(); + { + _ = cimgui.c.ImGui_TableSetColumnIndex(0); + cimgui.c.ImGui_Text("Hover Point"); + } + { + _ = cimgui.c.ImGui_TableSetColumnIndex(1); + cimgui.c.ImGui_Text( + "(%dpx, %dpx)", + @as(i64, @intFromFloat(coord.x)), + @as(i64, @intFromFloat(coord.y)), + ); + } + } + + const any_click = for (surface_mouse.click_state) |state| { + if (state == .press) break true; + } else false; + + click: { + cimgui.c.ImGui_TableNextRow(); + { + _ = cimgui.c.ImGui_TableSetColumnIndex(0); + cimgui.c.ImGui_Text("Click State"); + } + { + _ = cimgui.c.ImGui_TableSetColumnIndex(1); + if (!any_click) { + cimgui.c.ImGui_Text("none"); + break :click; + } + + for (surface_mouse.click_state, 0..) |state, i| { + if (state != .press) continue; + const button: input.MouseButton = @enumFromInt(i); + cimgui.c.ImGui_SameLine(); + cimgui.c.ImGui_Text("%s", (switch (button) { + .unknown => "?", + .left => "L", + .middle => "M", + .right => "R", + .four => "{4}", + .five => "{5}", + .six => "{6}", + .seven => "{7}", + .eight => "{8}", + .nine => "{9}", + .ten => "{10}", + .eleven => "{11}", + }).ptr); + } + } + } + + { + const left_click_point: terminal.point.Coordinate = pt: { + const p = surface_mouse.left_click_pin orelse break :pt .{}; + const pt = t.screens.active.pages.pointFromPin( + .active, + p.*, + ) orelse break :pt .{}; + break :pt pt.coord(); + }; + + cimgui.c.ImGui_TableNextRow(); + { + _ = cimgui.c.ImGui_TableSetColumnIndex(0); + cimgui.c.ImGui_Text("Click Grid"); + } + { + _ = cimgui.c.ImGui_TableSetColumnIndex(1); + cimgui.c.ImGui_Text( + "row=%d, col=%d", + left_click_point.y, + left_click_point.x, + ); + } + } + + { + cimgui.c.ImGui_TableNextRow(); + { + _ = cimgui.c.ImGui_TableSetColumnIndex(0); + cimgui.c.ImGui_Text("Click Point"); + } + { + _ = cimgui.c.ImGui_TableSetColumnIndex(1); + cimgui.c.ImGui_Text( + "(%dpx, %dpx)", + @as(u32, @intFromFloat(surface_mouse.left_click_xpos)), + @as(u32, @intFromFloat(surface_mouse.left_click_ypos)), + ); + } + } +} |
