inspector: trying new stuff

This commit is contained in:
Mitchell Hashimoto
2026-01-28 08:52:08 -08:00
parent 38aae2325d
commit 7cfac87fc4
4 changed files with 244 additions and 41 deletions

View File

@@ -14,6 +14,7 @@ const input = @import("../input.zig");
const renderer = @import("../renderer.zig");
const terminal = @import("../terminal/main.zig");
const inspector = @import("main.zig");
const widgets = @import("widgets.zig");
/// The window names. These are used with docking so we need to have access.
const window_cell = "Cell";
@@ -57,6 +58,9 @@ windows: struct {
terminal: inspector.terminal.Window = .{},
} = .{},
// ImGui state
gui: widgets.surface.Inspector = .empty,
/// Enum representing keyboard navigation actions
const KeyAction = enum {
down,
@@ -216,9 +220,7 @@ pub fn recordPtyRead(self: *Inspector, data: []const u8) !void {
/// Render the frame.
pub fn render(self: *Inspector) void {
const widgets = @import("widgets.zig");
var s: widgets.surface.Inspector = .{ .surface = self.surface };
s.draw();
self.gui.draw(self.surface);
if (true) return;
const dock_id = cimgui.c.ImGui_DockSpaceOverViewport();

View File

@@ -16,6 +16,120 @@ pub fn helpMarker(text: [:0]const u8) void {
cimgui.c.ImGui_TextUnformatted(text.ptr);
}
/// DetachableHeader allows rendering a collapsing header that can be
/// detached into its own window.
pub const DetachableHeader = struct {
/// Set whether the window is detached.
detached: bool = false,
/// If true, detaching will move the item into a docking position
/// to the right.
dock: bool = true,
// Internal state do not touch.
window_first: bool = true,
pub fn windowEnd(self: *DetachableHeader) void {
_ = self;
// If we started the window, we need to end it.
cimgui.c.ImGui_End();
}
/// Returns null if there is no window created (not detached).
/// Otherwise returns whether the window is open.
pub fn window(
self: *DetachableHeader,
label: [:0]const u8,
) ?bool {
// If we're not detached, we don't create a window.
if (!self.detached) {
self.window_first = true;
return null;
}
// If this is our first time showing the window then we need to
// setup docking. We only do this on the first time because we
// don't want to reset a user's docking behavior later.
if (self.window_first) dock: {
self.window_first = false;
if (!self.dock) break :dock;
const dock_id = cimgui.c.ImGui_GetWindowDockID();
if (dock_id == 0) break :dock;
var dock_id_right: cimgui.c.ImGuiID = 0;
var dock_id_left: cimgui.c.ImGuiID = 0;
_ = cimgui.ImGui_DockBuilderSplitNode(
dock_id,
cimgui.c.ImGuiDir_Right,
0.4,
&dock_id_right,
&dock_id_left,
);
cimgui.ImGui_DockBuilderDockWindow(label, dock_id_right);
cimgui.ImGui_DockBuilderFinish(dock_id);
}
return cimgui.c.ImGui_Begin(
label,
&self.detached,
cimgui.c.ImGuiWindowFlags_NoFocusOnAppearing,
);
}
pub fn header(
self: *DetachableHeader,
label: [:0]const u8,
) bool {
// If we're detached, create a separate window.
if (self.detached) return false;
// Make sure all headers have a unique ID in the stack. We only
// need to do this for the header side because creating a window
// automatically creates an ID.
cimgui.c.ImGui_PushID(label);
defer cimgui.c.ImGui_PopID();
// Create the collapsing header with the pop out button overlaid.
cimgui.c.ImGui_SetNextItemAllowOverlap();
const is_open = cimgui.c.ImGui_CollapsingHeader(
label,
cimgui.c.ImGuiTreeNodeFlags_None,
);
// Place pop-out button inside the header bar
const header_max = cimgui.c.ImGui_GetItemRectMax();
const header_min = cimgui.c.ImGui_GetItemRectMin();
const frame_height = cimgui.c.ImGui_GetFrameHeight();
const button_size = frame_height - 4;
const padding = 4;
cimgui.c.ImGui_SameLine();
cimgui.c.ImGui_SetCursorScreenPos(.{
.x = header_max.x - button_size - padding,
.y = header_min.y + 2,
});
{
cimgui.c.ImGui_PushStyleVarImVec2(
cimgui.c.ImGuiStyleVar_FramePadding,
.{ .x = 0, .y = 0 },
);
defer cimgui.c.ImGui_PopStyleVar();
if (cimgui.c.ImGui_ButtonEx(
">>##detach",
.{ .x = button_size, .y = button_size },
)) {
self.detached = true;
}
}
if (cimgui.c.ImGui_IsItemHovered(cimgui.c.ImGuiHoveredFlags_DelayShort)) {
cimgui.c.ImGui_SetTooltip("Detach into separate window");
}
return is_open;
}
};
pub const DetachableHeaderState = struct {
show_window: bool = false,

View File

@@ -12,10 +12,14 @@ const window_imgui_demo = "Dear ImGui Demo";
const window_terminal = "Terminal";
pub const Inspector = struct {
/// The surface being inspected.
surface: *const Surface,
/// Internal GUI state
terminal_info: widgets.terminal.Info,
pub fn draw(self: *Inspector) void {
pub const empty: Inspector = .{
.terminal_info = .empty,
};
pub fn draw(self: *Inspector, surface: *const Surface) 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");
@@ -30,10 +34,20 @@ pub const Inspector = struct {
// Draw everything that requires the terminal state mutex.
{
self.surface.renderer_state.mutex.lock();
defer self.surface.renderer_state.mutex.unlock();
const t = self.surface.renderer_state.terminal;
drawTerminalWindow(.{ .terminal = t });
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);
}
}
if (first_render) {
@@ -86,18 +100,3 @@ pub const Inspector = struct {
return setup;
}
};
fn drawTerminalWindow(state: struct {
terminal: *terminal.Terminal,
}) void {
defer cimgui.c.ImGui_End();
if (!cimgui.c.ImGui_Begin(
window_terminal,
null,
cimgui.c.ImGuiWindowFlags_NoFocusOnAppearing,
)) return;
widgets.terminal.drawInfo(.{
.terminal = state.terminal,
});
}

View File

@@ -3,27 +3,115 @@ const builtin = @import("builtin");
const assert = @import("../../quirks.zig").inlineAssert;
const Allocator = std.mem.Allocator;
const cimgui = @import("dcimgui");
const widgets = @import("../widgets.zig");
const terminal = @import("../../terminal/main.zig");
const Terminal = terminal.Terminal;
pub const Info = struct {
terminal: *Terminal,
};
misc_header: widgets.DetachableHeader,
pub fn drawInfo(data: Info) void {
if (cimgui.c.ImGui_CollapsingHeader(
"Help",
cimgui.c.ImGuiTreeNodeFlags_None,
)) {
cimgui.c.ImGui_TextWrapped(
"This window displays the internal state of the terminal. " ++
"The terminal state is global to this terminal. Some state " ++
"is specific to the active screen or other subsystems. Values " ++
"here reflect the running state and will update as the terminal " ++
"application modifies them via escape sequences or shell integration. " ++
"Some can be modified directly for debugging purposes.",
);
pub const empty: Info = .{
.misc_header = .{},
};
const misc_header_label = "Misc";
/// Draw the terminal info window.
pub fn draw(
self: *Info,
open: bool,
t: *Terminal,
) void {
// Draw our open state if we're open.
if (open) self.drawOpen(t);
// Draw our detached state that draws regardless of if
// we're open or not.
if (self.misc_header.window(misc_header_label)) |visible| {
defer self.misc_header.windowEnd();
if (visible) miscTable(t);
}
}
_ = data;
fn drawOpen(self: *Info, t: *Terminal) void {
if (cimgui.c.ImGui_CollapsingHeader(
"Help",
cimgui.c.ImGuiTreeNodeFlags_None,
)) {
cimgui.c.ImGui_TextWrapped(
"This window displays the internal state of the terminal. " ++
"The terminal state is global to this terminal. Some state " ++
"is specific to the active screen or other subsystems. Values " ++
"here reflect the running state and will update as the terminal " ++
"application modifies them via escape sequences or shell integration. " ++
"Some can be modified directly for debugging purposes.",
);
}
if (self.misc_header.header(misc_header_label)) miscTable(t);
}
};
pub fn miscTable(t: *Terminal) void {
_ = cimgui.c.ImGui_BeginTable(
"table_misc",
2,
cimgui.c.ImGuiTableFlags_None,
);
defer cimgui.c.ImGui_EndTable();
{
cimgui.c.ImGui_TableNextRow();
{
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Working Directory");
cimgui.c.ImGui_SameLine();
widgets.helpMarker("The current working directory reported by the shell.");
}
{
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
if (t.pwd.items.len > 0) {
cimgui.c.ImGui_Text(
"%.*s",
t.pwd.items.len,
t.pwd.items.ptr,
);
} else {
cimgui.c.ImGui_TextDisabled("(none)");
}
}
}
{
cimgui.c.ImGui_TableNextRow();
{
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Focused");
cimgui.c.ImGui_SameLine();
widgets.helpMarker("Whether the terminal itself is currently focused.");
}
{
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
var value: bool = t.flags.focused;
_ = cimgui.c.ImGui_Checkbox("##focused", &value);
}
}
{
cimgui.c.ImGui_TableNextRow();
{
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Previous Char");
cimgui.c.ImGui_SameLine();
widgets.helpMarker("The previously printed character, used only for the REP sequence.");
}
{
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
if (t.previous_char) |c| {
cimgui.c.ImGui_Text("U+%04X", @as(u32, c));
} else {
cimgui.c.ImGui_TextDisabled("(none)");
}
}
}
}