aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/src/inspector/widgets.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.zig
parent55c138263e190a1e79d4b997bfb9397306c0ec1f (diff)
feat: finish ghostty pane backend integration
Diffstat (limited to 'vendor/libghostty-vt/src/inspector/widgets.zig')
-rw-r--r--vendor/libghostty-vt/src/inspector/widgets.zig227
1 files changed, 227 insertions, 0 deletions
diff --git a/vendor/libghostty-vt/src/inspector/widgets.zig b/vendor/libghostty-vt/src/inspector/widgets.zig
new file mode 100644
index 00000000..dd8ebc00
--- /dev/null
+++ b/vendor/libghostty-vt/src/inspector/widgets.zig
@@ -0,0 +1,227 @@
+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");
+pub const terminal = @import("widgets/terminal.zig");
+pub const termio = @import("widgets/termio.zig");
+
+/// Draws a "(?)" disabled text marker that shows some help text
+/// on hover.
+pub fn helpMarker(text: [:0]const u8) void {
+ cimgui.c.ImGui_TextDisabled("(?)");
+ if (!cimgui.c.ImGui_BeginItemTooltip()) return;
+ defer cimgui.c.ImGui_EndTooltip();
+
+ cimgui.c.ImGui_PushTextWrapPos(cimgui.c.ImGui_GetFontSize() * 35.0);
+ defer cimgui.c.ImGui_PopTextWrapPos();
+
+ 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,
+
+ /// Internal state. Don't touch.
+ first_show: bool = false,
+};
+
+/// Render a collapsing header that can be detached into its own window.
+/// When detached, renders as a separate window with a close button.
+/// When attached, renders as a collapsing header with a pop-out button.
+pub fn detachableHeader(
+ label: [:0]const u8,
+ state: *DetachableHeaderState,
+ ctx: anytype,
+ comptime contentFn: fn (@TypeOf(ctx)) void,
+) void {
+ cimgui.c.ImGui_PushID(label);
+ defer cimgui.c.ImGui_PopID();
+
+ if (state.show_window) {
+ // On first show, dock this window to the right of the parent window's dock.
+ // We only do this once so the user can freely reposition the window afterward
+ // without it snapping back to the right on every frame.
+ if (!state.first_show) {
+ state.first_show = true;
+ const current_dock_id = cimgui.c.ImGui_GetWindowDockID();
+ if (current_dock_id != 0) {
+ var dock_id_right: cimgui.c.ImGuiID = 0;
+ var dock_id_left: cimgui.c.ImGuiID = 0;
+ _ = cimgui.ImGui_DockBuilderSplitNode(
+ current_dock_id,
+ cimgui.c.ImGuiDir_Right,
+ 0.3,
+ &dock_id_right,
+ &dock_id_left,
+ );
+ cimgui.ImGui_DockBuilderDockWindow(label, dock_id_right);
+ cimgui.ImGui_DockBuilderFinish(current_dock_id);
+ }
+ }
+
+ defer cimgui.c.ImGui_End();
+ if (cimgui.c.ImGui_Begin(
+ label,
+ &state.show_window,
+ cimgui.c.ImGuiWindowFlags_NoFocusOnAppearing,
+ )) contentFn(ctx);
+ return;
+ }
+
+ // Reset first_show when window is closed so next open docks again
+ state.first_show = false;
+
+ 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 },
+ );
+ if (cimgui.c.ImGui_ButtonEx(
+ ">>##detach",
+ .{ .x = button_size, .y = button_size },
+ )) {
+ state.show_window = true;
+ }
+ cimgui.c.ImGui_PopStyleVar();
+ if (cimgui.c.ImGui_IsItemHovered(cimgui.c.ImGuiHoveredFlags_DelayShort)) {
+ cimgui.c.ImGui_SetTooltip("Pop out into separate window");
+ }
+
+ if (is_open) contentFn(ctx);
+}