aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/src/inspector/widgets/terminal.zig
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-04-04 19:22:04 +0300
committerCan Celik <ogulcancelik@gmail.com>2026-04-05 01:23:06 +0300
commit41dd733ca7123c9ac4bfc6c781697a660e95b052 (patch)
tree3fc91815ca139ef2e0b281bba0abda4ef15175f4 /vendor/libghostty-vt/src/inspector/widgets/terminal.zig
parent55c138263e190a1e79d4b997bfb9397306c0ec1f (diff)
feat: finish ghostty pane backend integration
Diffstat (limited to 'vendor/libghostty-vt/src/inspector/widgets/terminal.zig')
-rw-r--r--vendor/libghostty-vt/src/inspector/widgets/terminal.zig726
1 files changed, 726 insertions, 0 deletions
diff --git a/vendor/libghostty-vt/src/inspector/widgets/terminal.zig b/vendor/libghostty-vt/src/inspector/widgets/terminal.zig
new file mode 100644
index 00000000..3dbea388
--- /dev/null
+++ b/vendor/libghostty-vt/src/inspector/widgets/terminal.zig
@@ -0,0 +1,726 @@
+const std = @import("std");
+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 modes = terminal.modes;
+const Terminal = terminal.Terminal;
+
+/// Terminal information inspector widget.
+pub const Info = struct {
+ /// True if we're showing the 256-color palette window.
+ show_palette: bool,
+
+ /// The various detachable headers.
+ misc_header: widgets.DetachableHeader,
+ layout_header: widgets.DetachableHeader,
+ mouse_header: widgets.DetachableHeader,
+ color_header: widgets.DetachableHeader,
+ modes_header: widgets.DetachableHeader,
+
+ /// Screen detail windows for each screen key.
+ screens: ScreenMap,
+
+ pub const empty: Info = .{
+ .show_palette = false,
+ .misc_header = .{},
+ .layout_header = .{},
+ .mouse_header = .{},
+ .color_header = .{},
+ .modes_header = .{},
+ .screens = .{},
+ };
+
+ /// 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("Terminal Misc")) |visible| {
+ defer self.misc_header.windowEnd();
+ if (visible) miscTable(t);
+ }
+ if (self.layout_header.window("Terminal Layout")) |visible| {
+ defer self.layout_header.windowEnd();
+ if (visible) layoutTable(t);
+ }
+ if (self.mouse_header.window("Terminal Mouse")) |visible| {
+ defer self.mouse_header.windowEnd();
+ if (visible) mouseTable(t);
+ }
+ if (self.color_header.window("Terminal Color")) |visible| {
+ defer self.color_header.windowEnd();
+ if (visible) colorTable(t, &self.show_palette);
+ }
+ if (self.modes_header.window("Terminal Modes")) |visible| {
+ defer self.modes_header.windowEnd();
+ if (visible) modesTable(t);
+ }
+
+ // Palette pop-out window
+ if (self.show_palette) {
+ defer cimgui.c.ImGui_End();
+ if (cimgui.c.ImGui_Begin(
+ "256-Color Palette",
+ &self.show_palette,
+ cimgui.c.ImGuiWindowFlags_NoFocusOnAppearing,
+ )) {
+ palette("palette", &t.colors.palette.current);
+ }
+ }
+
+ // Screen pop-out windows
+ var it = self.screens.iterator();
+ while (it.next()) |entry| {
+ const screen = t.screens.get(entry.key) orelse {
+ // Could happen if we opened up a window for a screen
+ // and that screen was subsequently deinitialized. In
+ // this case, hide the window.
+ self.screens.remove(entry.key);
+ continue;
+ };
+
+ var title_buf: [128]u8 = undefined;
+ const title = std.fmt.bufPrintZ(
+ &title_buf,
+ "Screen: {t}",
+ .{entry.key},
+ ) catch "Screen";
+
+ // Setup our next window so it has some size to it.
+ const viewport = cimgui.c.ImGui_GetMainViewport();
+ cimgui.c.ImGui_SetNextWindowSize(
+ .{
+ .x = @min(400, viewport.*.Size.x),
+ .y = @min(300, viewport.*.Size.y),
+ },
+ cimgui.c.ImGuiCond_FirstUseEver,
+ );
+
+ var screen_open: bool = true;
+ defer cimgui.c.ImGui_End();
+ const screen_draw = cimgui.c.ImGui_Begin(
+ title,
+ &screen_open,
+ cimgui.c.ImGuiWindowFlags_NoFocusOnAppearing,
+ );
+ entry.value.draw(screen_draw, .{
+ .screen = screen,
+ .key = entry.key,
+ .active_key = t.screens.active_key,
+ .modify_other_keys_2 = t.flags.modify_other_keys_2,
+ .color_palette = &t.colors.palette,
+ });
+
+ // If the window was closed, remove it from our map so future
+ // renders don't draw it.
+ if (!screen_open) self.screens.remove(entry.key);
+ }
+ }
+
+ fn drawOpen(self: *Info, t: *Terminal) void {
+ // Show our screens up top.
+ screensTable(t, &self.screens);
+
+ if (self.misc_header.header("Misc")) miscTable(t);
+ if (self.layout_header.header("Layout")) layoutTable(t);
+ if (self.mouse_header.header("Mouse")) mouseTable(t);
+ if (self.color_header.header("Color")) colorTable(t, &self.show_palette);
+ if (self.modes_header.header("Modes")) modesTable(t);
+ }
+};
+
+pub const ScreenMap = std.EnumMap(
+ terminal.ScreenSet.Key,
+ widgets.screen.Info,
+);
+
+/// Render the table of possible screens with various actions.
+fn screensTable(
+ t: *Terminal,
+ map: *ScreenMap,
+) void {
+ if (!cimgui.c.ImGui_BeginTable(
+ "screens",
+ 3,
+ cimgui.c.ImGuiTableFlags_Borders |
+ cimgui.c.ImGuiTableFlags_RowBg |
+ cimgui.c.ImGuiTableFlags_SizingFixedFit,
+ )) return;
+ defer cimgui.c.ImGui_EndTable();
+
+ cimgui.c.ImGui_TableSetupColumn("Screen", cimgui.c.ImGuiTableColumnFlags_WidthFixed);
+ cimgui.c.ImGui_TableSetupColumn("Status", cimgui.c.ImGuiTableColumnFlags_WidthFixed);
+ cimgui.c.ImGui_TableSetupColumn("", cimgui.c.ImGuiTableColumnFlags_WidthFixed);
+
+ // Custom header row to include help marker before "Screen"
+ {
+ cimgui.c.ImGui_TableNextRowEx(cimgui.c.ImGuiTableRowFlags_Headers, 0.0);
+ {
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_PushStyleVarImVec2(cimgui.c.ImGuiStyleVar_FramePadding, .{ .x = 0, .y = 0 });
+ widgets.helpMarker(
+ "A terminal can have multiple screens, only one of which is active at " ++
+ "a time. Each screen has its own grid, contents, and other state. " ++
+ "This section allows you to inspect the different screens managed by " ++
+ "the terminal.",
+ );
+ cimgui.c.ImGui_PopStyleVar();
+ cimgui.c.ImGui_SameLineEx(0.0, cimgui.c.ImGui_GetStyle().*.ItemInnerSpacing.x);
+ cimgui.c.ImGui_TableHeader("Screen");
+ }
+ {
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_TableHeader("Status");
+ }
+ {
+ _ = cimgui.c.ImGui_TableSetColumnIndex(2);
+ cimgui.c.ImGui_TableHeader("");
+ }
+ }
+
+ for (std.meta.tags(terminal.ScreenSet.Key)) |key| {
+ const is_initialized = t.screens.get(key) != null;
+ const is_active = t.screens.active_key == key;
+
+ cimgui.c.ImGui_TableNextRow();
+ {
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("%s", @tagName(key).ptr);
+ }
+ {
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ if (is_active) {
+ cimgui.c.ImGui_TextColored(
+ .{ .x = 0.4, .y = 1.0, .z = 0.4, .w = 1.0 },
+ "active",
+ );
+ } else if (is_initialized) {
+ cimgui.c.ImGui_TextColored(
+ .{ .x = 0.6, .y = 0.6, .z = 0.6, .w = 1.0 },
+ "initialized",
+ );
+ } else {
+ cimgui.c.ImGui_TextColored(
+ .{ .x = 0.4, .y = 0.4, .z = 0.4, .w = 1.0 },
+ "(not initialized)",
+ );
+ }
+ }
+ {
+ _ = cimgui.c.ImGui_TableSetColumnIndex(2);
+ cimgui.c.ImGui_PushIDInt(@intFromEnum(key));
+ defer cimgui.c.ImGui_PopID();
+ cimgui.c.ImGui_BeginDisabled(!is_initialized);
+ defer cimgui.c.ImGui_EndDisabled();
+ if (cimgui.c.ImGui_Button("View")) {
+ if (!map.contains(key)) {
+ map.put(key, .empty);
+ }
+ }
+ }
+ }
+}
+
+/// Table of miscellaneous terminal information.
+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)");
+ }
+ }
+ }
+}
+
+/// Table of terminal layout information.
+fn layoutTable(t: *Terminal) void {
+ _ = cimgui.c.ImGui_BeginTable(
+ "table_layout",
+ 2,
+ cimgui.c.ImGuiTableFlags_None,
+ );
+ defer cimgui.c.ImGui_EndTable();
+
+ {
+ cimgui.c.ImGui_TableNextRow();
+ {
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Grid");
+ cimgui.c.ImGui_SameLine();
+ widgets.helpMarker("The size of the terminal grid in columns and rows.");
+ }
+ {
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text(
+ "%dc x %dr",
+ t.cols,
+ t.rows,
+ );
+ }
+ }
+
+ {
+ cimgui.c.ImGui_TableNextRow();
+ {
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Pixels");
+ cimgui.c.ImGui_SameLine();
+ widgets.helpMarker("The size of the terminal grid in pixels.");
+ }
+ {
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text(
+ "%dw x %dh",
+ t.width_px,
+ t.height_px,
+ );
+ }
+ }
+
+ {
+ cimgui.c.ImGui_TableNextRow();
+ {
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Scroll Region");
+ cimgui.c.ImGui_SameLine();
+ widgets.helpMarker("The scrolling region boundaries (top, bottom, left, right).");
+ }
+ {
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_PushItemWidth(cimgui.c.ImGui_CalcTextSize("00000").x);
+ defer cimgui.c.ImGui_PopItemWidth();
+
+ var override = t.scrolling_region;
+ var changed = false;
+
+ cimgui.c.ImGui_AlignTextToFramePadding();
+ cimgui.c.ImGui_Text("T:");
+ cimgui.c.ImGui_SameLine();
+ if (cimgui.c.ImGui_InputScalar(
+ "##scroll_top",
+ cimgui.c.ImGuiDataType_U16,
+ &override.top,
+ )) {
+ override.top = @min(override.top, t.rows -| 1);
+ changed = true;
+ }
+
+ cimgui.c.ImGui_SameLine();
+ cimgui.c.ImGui_Text("B:");
+ cimgui.c.ImGui_SameLine();
+ if (cimgui.c.ImGui_InputScalar(
+ "##scroll_bottom",
+ cimgui.c.ImGuiDataType_U16,
+ &override.bottom,
+ )) {
+ override.bottom = @min(override.bottom, t.rows -| 1);
+ changed = true;
+ }
+
+ cimgui.c.ImGui_SameLine();
+ cimgui.c.ImGui_Text("L:");
+ cimgui.c.ImGui_SameLine();
+ if (cimgui.c.ImGui_InputScalar(
+ "##scroll_left",
+ cimgui.c.ImGuiDataType_U16,
+ &override.left,
+ )) {
+ override.left = @min(override.left, t.cols -| 1);
+ changed = true;
+ }
+
+ cimgui.c.ImGui_SameLine();
+ cimgui.c.ImGui_Text("R:");
+ cimgui.c.ImGui_SameLine();
+ if (cimgui.c.ImGui_InputScalar(
+ "##scroll_right",
+ cimgui.c.ImGuiDataType_U16,
+ &override.right,
+ )) {
+ override.right = @min(override.right, t.cols -| 1);
+ changed = true;
+ }
+
+ if (changed and
+ override.top < override.bottom and
+ override.left < override.right)
+ {
+ t.scrolling_region = override;
+ }
+ }
+ }
+}
+
+/// Table of mouse-related terminal information.
+fn mouseTable(t: *Terminal) void {
+ _ = cimgui.c.ImGui_BeginTable(
+ "table_mouse",
+ 2,
+ cimgui.c.ImGuiTableFlags_None,
+ );
+ defer cimgui.c.ImGui_EndTable();
+
+ {
+ cimgui.c.ImGui_TableNextRow();
+ {
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Event Mode");
+ cimgui.c.ImGui_SameLine();
+ widgets.helpMarker("The mouse event reporting mode set by the application.");
+ }
+ {
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text("%s", @tagName(t.flags.mouse_event).ptr);
+ }
+ }
+
+ {
+ cimgui.c.ImGui_TableNextRow();
+ {
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Format");
+ cimgui.c.ImGui_SameLine();
+ widgets.helpMarker("The mouse event encoding format.");
+ }
+ {
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text("%s", @tagName(t.flags.mouse_format).ptr);
+ }
+ }
+
+ {
+ cimgui.c.ImGui_TableNextRow();
+ {
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Shape");
+ cimgui.c.ImGui_SameLine();
+ widgets.helpMarker("The current mouse cursor shape set by the application.");
+ }
+ {
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text("%s", @tagName(t.mouse_shape).ptr);
+ }
+ }
+
+ {
+ cimgui.c.ImGui_TableNextRow();
+ {
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Shift Capture");
+ cimgui.c.ImGui_SameLine();
+ widgets.helpMarker("XTSHIFTESCAPE state for capturing shift in mouse protocol.");
+ }
+ {
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ if (t.flags.mouse_shift_capture == .null) {
+ cimgui.c.ImGui_TextDisabled("(unset)");
+ } else {
+ cimgui.c.ImGui_Text("%s", @tagName(t.flags.mouse_shift_capture).ptr);
+ }
+ }
+ }
+}
+
+/// Table of color-related terminal information.
+fn colorTable(
+ t: *Terminal,
+ show_palette: *bool,
+) void {
+ cimgui.c.ImGui_TextWrapped(
+ "Color state for the terminal. Note these colors only apply " ++
+ "to the palette and unstyled colors. Many modern terminal " ++
+ "applications use direct RGB colors which are not reflected here.",
+ );
+ cimgui.c.ImGui_Separator();
+
+ _ = cimgui.c.ImGui_BeginTable(
+ "table_color",
+ 2,
+ cimgui.c.ImGuiTableFlags_None,
+ );
+ defer cimgui.c.ImGui_EndTable();
+
+ {
+ cimgui.c.ImGui_TableNextRow();
+ {
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Background");
+ cimgui.c.ImGui_SameLine();
+ widgets.helpMarker("Unstyled cell background color.");
+ }
+ {
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ _ = dynamicRGB(
+ "bg_color",
+ &t.colors.background,
+ );
+ }
+ }
+
+ {
+ cimgui.c.ImGui_TableNextRow();
+ {
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Foreground");
+ cimgui.c.ImGui_SameLine();
+ widgets.helpMarker("Unstyled cell foreground color.");
+ }
+ {
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ _ = dynamicRGB(
+ "fg_color",
+ &t.colors.foreground,
+ );
+ }
+ }
+
+ {
+ cimgui.c.ImGui_TableNextRow();
+ {
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Cursor");
+ cimgui.c.ImGui_SameLine();
+ widgets.helpMarker("Cursor coloring set by escape sequences.");
+ }
+ {
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ _ = dynamicRGB(
+ "cursor_color",
+ &t.colors.cursor,
+ );
+ }
+ }
+
+ {
+ cimgui.c.ImGui_TableNextRow();
+ {
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Palette");
+ cimgui.c.ImGui_SameLine();
+ widgets.helpMarker("The 256-color palette.");
+ }
+ {
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ if (cimgui.c.ImGui_Button("View")) {
+ show_palette.* = true;
+ }
+ }
+ }
+}
+
+/// Table of terminal modes.
+fn modesTable(t: *Terminal) void {
+ _ = cimgui.c.ImGui_BeginTable(
+ "table_modes",
+ 3,
+ cimgui.c.ImGuiTableFlags_SizingFixedFit |
+ cimgui.c.ImGuiTableFlags_RowBg,
+ );
+ defer cimgui.c.ImGui_EndTable();
+
+ {
+ cimgui.c.ImGui_TableSetupColumn("", cimgui.c.ImGuiTableColumnFlags_NoResize);
+ cimgui.c.ImGui_TableSetupColumn("Number", cimgui.c.ImGuiTableColumnFlags_PreferSortAscending);
+ cimgui.c.ImGui_TableSetupColumn("Name", cimgui.c.ImGuiTableColumnFlags_WidthStretch);
+ cimgui.c.ImGui_TableHeadersRow();
+ }
+
+ inline for (@typeInfo(terminal.Mode).@"enum".fields) |field| {
+ @setEvalBranchQuota(6000);
+ const tag: modes.ModeTag = @bitCast(@as(modes.ModeTag.Backing, field.value));
+
+ cimgui.c.ImGui_TableNextRow();
+ cimgui.c.ImGui_PushIDInt(@intCast(field.value));
+ defer cimgui.c.ImGui_PopID();
+ {
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ var value: bool = t.modes.get(@field(terminal.Mode, field.name));
+ _ = cimgui.c.ImGui_Checkbox("##checkbox", &value);
+ }
+ {
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text(
+ "%s%d",
+ if (tag.ansi) "" else "?",
+ @as(u32, @intCast(tag.value)),
+ );
+ }
+ {
+ _ = cimgui.c.ImGui_TableSetColumnIndex(2);
+ const name = std.fmt.comptimePrint("{s}", .{field.name});
+ cimgui.c.ImGui_Text("%s", name.ptr);
+ }
+ }
+}
+
+/// Render a DynamicRGB color.
+fn dynamicRGB(
+ label: [:0]const u8,
+ rgb: *terminal.color.DynamicRGB,
+) bool {
+ _ = cimgui.c.ImGui_BeginTable(
+ label,
+ if (rgb.override != null) 2 else 1,
+ cimgui.c.ImGuiTableFlags_SizingFixedFit,
+ );
+ defer cimgui.c.ImGui_EndTable();
+
+ if (rgb.override != null) cimgui.c.ImGui_TableSetupColumn(
+ "##label",
+ cimgui.c.ImGuiTableColumnFlags_WidthFixed,
+ );
+ cimgui.c.ImGui_TableSetupColumn(
+ "##value",
+ cimgui.c.ImGuiTableColumnFlags_WidthStretch,
+ );
+
+ if (rgb.override) |c| {
+ cimgui.c.ImGui_TableNextRow();
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("override:");
+ cimgui.c.ImGui_SameLine();
+ widgets.helpMarker("Overridden color set by escape sequences.");
+
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ var col = [3]f32{
+ @as(f32, @floatFromInt(c.r)) / 255.0,
+ @as(f32, @floatFromInt(c.g)) / 255.0,
+ @as(f32, @floatFromInt(c.b)) / 255.0,
+ };
+ _ = cimgui.c.ImGui_ColorEdit3(
+ "##override",
+ &col,
+ cimgui.c.ImGuiColorEditFlags_None,
+ );
+ }
+
+ cimgui.c.ImGui_TableNextRow();
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ if (rgb.default) |c| {
+ if (rgb.override != null) {
+ cimgui.c.ImGui_Text("default:");
+ cimgui.c.ImGui_SameLine();
+ widgets.helpMarker("Default color from configuration.");
+
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ }
+
+ var col = [3]f32{
+ @as(f32, @floatFromInt(c.r)) / 255.0,
+ @as(f32, @floatFromInt(c.g)) / 255.0,
+ @as(f32, @floatFromInt(c.b)) / 255.0,
+ };
+ _ = cimgui.c.ImGui_ColorEdit3(
+ "##default",
+ &col,
+ cimgui.c.ImGuiColorEditFlags_None,
+ );
+ } else {
+ cimgui.c.ImGui_TextDisabled("(unset)");
+ }
+
+ return false;
+}
+
+/// Render a color palette as a 16x16 grid of color buttons.
+fn palette(
+ label: [:0]const u8,
+ pal: *const terminal.color.Palette,
+) void {
+ cimgui.c.ImGui_PushID(label);
+ defer cimgui.c.ImGui_PopID();
+
+ for (0..16) |row| {
+ for (0..16) |col| {
+ const idx = row * 16 + col;
+ const rgb = pal[idx];
+ var col_arr = [3]f32{
+ @as(f32, @floatFromInt(rgb.r)) / 255.0,
+ @as(f32, @floatFromInt(rgb.g)) / 255.0,
+ @as(f32, @floatFromInt(rgb.b)) / 255.0,
+ };
+
+ if (col > 0) cimgui.c.ImGui_SameLine();
+
+ cimgui.c.ImGui_PushIDInt(@intCast(idx));
+ _ = cimgui.c.ImGui_ColorEdit3(
+ "##color",
+ &col_arr,
+ cimgui.c.ImGuiColorEditFlags_NoInputs,
+ );
+ if (cimgui.c.ImGui_IsItemHovered(cimgui.c.ImGuiHoveredFlags_DelayShort)) {
+ cimgui.c.ImGui_SetTooltip(
+ "%d: #%02X%02X%02X",
+ idx,
+ rgb.r,
+ rgb.g,
+ rgb.b,
+ );
+ }
+ cimgui.c.ImGui_PopID();
+ }
+ }
+}