inspector: cell inspector

This commit is contained in:
Mitchell Hashimoto
2023-10-22 21:25:56 -07:00
parent bc398327f2
commit eaa7777fbb
2 changed files with 235 additions and 18 deletions

View File

@ -11,6 +11,7 @@ const input = @import("input.zig");
const terminal = @import("terminal/main.zig");
/// The window names. These are used with docking so we need to have access.
const window_cell = "Cell";
const window_modes = "Modes";
const window_screen = "Screen";
const window_size = "Surface Info";
@ -23,11 +24,6 @@ surface: *Surface,
/// is used to set up the initial window positions.
first_render: bool = true,
/// Window show states
show_modes_window: bool = true,
show_screen_window: bool = true,
show_size_window: bool = true,
/// Mouse state that we track in addition to normal mouse states that
/// Ghostty always knows about.
mouse: struct {
@ -39,6 +35,34 @@ mouse: struct {
last_point: terminal.point.ScreenPoint = .{},
} = .{},
/// A selected cell.
cell: CellInspect = .{ .idle = {} },
const CellInspect = union(enum) {
/// Idle, no cell inspection is requested
idle: void,
/// Requested, a cell is being picked.
requested: void,
/// The cell has been picked and set to this. This is a copy so that
/// if the cell contents change we still have the original cell.
selected: Selected,
const Selected = struct {
row: usize,
col: usize,
cell: terminal.Screen.Cell,
};
pub fn request(self: *CellInspect) void {
switch (self.*) {
.idle, .selected => self.* = .requested,
.requested => {},
}
}
};
/// Setup the ImGui state. This requires an ImGui context to be set.
pub fn setup() void {
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
@ -97,6 +121,7 @@ pub fn render(self: *Inspector) void {
defer self.surface.renderer_state.mutex.unlock();
self.renderScreenWindow();
self.renderModesWindow();
self.renderCellWindow();
self.renderSizeWindow();
}
@ -119,7 +144,7 @@ pub fn render(self: *Inspector) void {
fn setupLayout(self: *Inspector, dock_id_main: cimgui.c.ImGuiID) void {
_ = self;
// Our initial focus should always be the modes window
// Our initial focus
cimgui.c.igSetWindowFocus_Str(window_screen);
// Setup our initial layout.
@ -143,6 +168,7 @@ fn setupLayout(self: *Inspector, dock_id_main: cimgui.c.ImGuiID) void {
};
};
cimgui.c.igDockBuilderDockWindow(window_cell, dock_id.left);
cimgui.c.igDockBuilderDockWindow(window_modes, dock_id.left);
cimgui.c.igDockBuilderDockWindow(window_screen, dock_id.left);
cimgui.c.igDockBuilderDockWindow(window_imgui_demo, dock_id.left);
@ -151,13 +177,11 @@ fn setupLayout(self: *Inspector, dock_id_main: cimgui.c.ImGuiID) void {
}
fn renderScreenWindow(self: *Inspector) void {
if (!self.show_screen_window) return;
// Start our window. If we're collapsed we do nothing.
defer cimgui.c.igEnd();
if (!cimgui.c.igBegin(
window_screen,
&self.show_screen_window,
null,
cimgui.c.ImGuiWindowFlags_NoFocusOnAppearing,
)) return;
@ -367,13 +391,11 @@ fn renderScreenWindow(self: *Inspector) void {
/// The modes window shows the currently active terminal modes and allows
/// users to toggle them on and off.
fn renderModesWindow(self: *Inspector) void {
if (!self.show_modes_window) return;
// Start our window. If we're collapsed we do nothing.
defer cimgui.c.igEnd();
if (!cimgui.c.igBegin(
window_modes,
&self.show_modes_window,
null,
cimgui.c.ImGuiWindowFlags_NoFocusOnAppearing,
)) return;
@ -421,13 +443,11 @@ fn renderModesWindow(self: *Inspector) void {
}
fn renderSizeWindow(self: *Inspector) void {
if (!self.show_size_window) return;
// Start our window. If we're collapsed we do nothing.
defer cimgui.c.igEnd();
if (!cimgui.c.igBegin(
window_size,
&self.show_size_window,
null,
cimgui.c.ImGuiWindowFlags_NoFocusOnAppearing,
)) return;
@ -678,3 +698,173 @@ fn renderSizeWindow(self: *Inspector) void {
}
}
}
fn renderCellWindow(self: *Inspector) void {
// Start our window. If we're collapsed we do nothing.
defer cimgui.c.igEnd();
if (!cimgui.c.igBegin(
window_cell,
null,
cimgui.c.ImGuiWindowFlags_NoFocusOnAppearing,
)) return;
// Our popup for the picker
const popup_picker = "popup_modal_cell_picker";
if (cimgui.c.igButton("Picker", .{ .x = 0, .y = 0 })) {
// Request a cell
self.cell.request();
cimgui.c.igOpenPopup_Str(
popup_picker,
cimgui.c.ImGuiPopupFlags_None,
);
}
if (cimgui.c.igBeginPopupModal(
popup_picker,
null,
cimgui.c.ImGuiWindowFlags_AlwaysAutoResize,
)) popup: {
defer cimgui.c.igEndPopup();
// Once we select a cell, close this popup.
if (self.cell == .selected) {
cimgui.c.igCloseCurrentPopup();
break :popup;
}
cimgui.c.igText(
"Click on a cell in the terminal to inspect it.\n" ++
"The click will be intercepted by the picker, \n" ++
"so it won't be sent to the terminal.",
);
cimgui.c.igSeparator();
if (cimgui.c.igButton("Cancel", .{ .x = 0, .y = 0 })) {
cimgui.c.igCloseCurrentPopup();
}
} // cell pick popup
cimgui.c.igSeparator();
if (self.cell != .selected) {
cimgui.c.igText("No cell selected.");
return;
}
const selected = self.cell.selected;
{
// We have a selected cell, show information about it.
_ = cimgui.c.igBeginTable(
"table_cursor",
2,
cimgui.c.ImGuiTableFlags_None,
.{ .x = 0, .y = 0 },
0,
);
defer cimgui.c.igEndTable();
{
cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
{
_ = cimgui.c.igTableSetColumnIndex(0);
cimgui.c.igText("Grid Position");
}
{
_ = cimgui.c.igTableSetColumnIndex(1);
cimgui.c.igText("row=%d col=%d", selected.row, selected.col);
}
}
// NOTE: we don't currently write the character itself because
// we haven't hooked up imgui to our font system. That's hard! We
// can/should instead hook up our renderer to imgui and just render
// the single glyph in an image view so it looks _identical_ to the
// terminal.
codepoint: {
cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
{
_ = cimgui.c.igTableSetColumnIndex(0);
cimgui.c.igText("Codepoint");
}
{
_ = cimgui.c.igTableSetColumnIndex(1);
if (selected.cell.char == 0) {
cimgui.c.igTextDisabled("(empty)");
break :codepoint;
}
cimgui.c.igText("U+%X", selected.cell.char);
}
}
// If we have a color then we show the color
color: {
cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
_ = cimgui.c.igTableSetColumnIndex(0);
cimgui.c.igText("Foreground Color");
_ = cimgui.c.igTableSetColumnIndex(1);
if (!selected.cell.attrs.has_fg) {
cimgui.c.igText("default");
break :color;
}
var color: [3]f32 = .{
@as(f32, @floatFromInt(selected.cell.fg.r)) / 255,
@as(f32, @floatFromInt(selected.cell.fg.g)) / 255,
@as(f32, @floatFromInt(selected.cell.fg.b)) / 255,
};
_ = cimgui.c.igColorEdit3(
"color_fg",
&color,
cimgui.c.ImGuiColorEditFlags_NoPicker |
cimgui.c.ImGuiColorEditFlags_NoLabel,
);
}
color: {
cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
_ = cimgui.c.igTableSetColumnIndex(0);
cimgui.c.igText("Background Color");
_ = cimgui.c.igTableSetColumnIndex(1);
if (!selected.cell.attrs.has_bg) {
cimgui.c.igText("default");
break :color;
}
var color: [3]f32 = .{
@as(f32, @floatFromInt(selected.cell.bg.r)) / 255,
@as(f32, @floatFromInt(selected.cell.bg.g)) / 255,
@as(f32, @floatFromInt(selected.cell.bg.b)) / 255,
};
_ = cimgui.c.igColorEdit3(
"color_bg",
&color,
cimgui.c.ImGuiColorEditFlags_NoPicker |
cimgui.c.ImGuiColorEditFlags_NoLabel,
);
}
// Boolean styles
const styles = .{
"bold", "italic", "faint", "blink",
"inverse", "invisible", "protected", "strikethrough",
};
inline for (styles) |style| style: {
if (!@field(selected.cell.attrs, style)) break :style;
cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
{
_ = cimgui.c.igTableSetColumnIndex(0);
cimgui.c.igText(style.ptr);
}
{
_ = cimgui.c.igTableSetColumnIndex(1);
cimgui.c.igText("true");
}
}
} // table
cimgui.c.igTextDisabled("(Any styles not shown are not currently set)");
}

View File

@ -1611,13 +1611,40 @@ pub fn mouseButtonCallback(
const tracy = trace(@src());
defer tracy.end();
// If we have an inspector, we always queue a render
if (self.inspector) |insp| {
defer self.queueRender() catch {};
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
// If the inspector is requesting a cell, then we intercept
// left mouse clicks and send them to the inspector.
if (insp.cell == .requested and
button == .left and
action == .press)
{
const pos = try self.rt_surface.getCursorPos();
const point = self.posToViewport(pos.x, pos.y);
const cell = self.renderer_state.terminal.screen.getCell(
.viewport,
point.y,
point.x,
);
insp.cell = .{ .selected = .{
.row = point.y,
.col = point.x,
.cell = cell,
} };
return;
}
}
// Always record our latest mouse state
self.mouse.click_state[@intCast(@intFromEnum(button))] = action;
self.mouse.mods = @bitCast(mods);
// If we have an inspector, we always queue a render
if (self.inspector != null) try self.queueRender();
// Always show the mouse again if it is hidden
if (self.mouse.hidden) self.showMouse();