mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 07:46:12 +03:00
apprt/gtk: can render imgui
This commit is contained in:
@ -42,6 +42,11 @@ pub fn build(b: *std.Build) !void {
|
||||
lib.addCSourceFile(.{ .file = imgui.path("imgui_widgets.cpp"), .flags = flags.items });
|
||||
lib.addCSourceFile(.{ .file = imgui.path("imgui_tables.cpp"), .flags = flags.items });
|
||||
|
||||
lib.addCSourceFile(.{
|
||||
.file = imgui.path("backends/imgui_impl_opengl3.cpp"),
|
||||
.flags = flags.items,
|
||||
});
|
||||
|
||||
lib.installHeadersDirectoryOptions(.{
|
||||
.source_dir = .{ .path = "vendor" },
|
||||
.install_dir = .header,
|
||||
|
@ -1,3 +1,13 @@
|
||||
pub usingnamespace @cImport({
|
||||
const c = @cImport({
|
||||
@cDefine("CIMGUI_DEFINE_ENUMS_AND_STRUCTS", "1");
|
||||
@cInclude("cimgui.h");
|
||||
});
|
||||
|
||||
// Export all of the C API
|
||||
pub usingnamespace c;
|
||||
|
||||
// OpenGL
|
||||
pub extern fn ImGui_ImplOpenGL3_Init(?[*:0]const u8) callconv(.C) void;
|
||||
pub extern fn ImGui_ImplOpenGL3_Shutdown() callconv(.C) void;
|
||||
pub extern fn ImGui_ImplOpenGL3_NewFrame() callconv(.C) void;
|
||||
pub extern fn ImGui_ImplOpenGL3_RenderDrawData(*c.ImDrawData) callconv(.C) void;
|
||||
|
148
src/apprt/gtk/ImguiWidget.zig
Normal file
148
src/apprt/gtk/ImguiWidget.zig
Normal file
@ -0,0 +1,148 @@
|
||||
const ImguiWidget = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
|
||||
const cimgui = @import("cimgui");
|
||||
const c = @import("c.zig");
|
||||
const gl = @import("../../renderer/opengl/main.zig");
|
||||
|
||||
const log = std.log.scoped(.gtk_imgui_widget);
|
||||
|
||||
/// Our OpenGL widget
|
||||
gl_area: *c.GtkGLArea,
|
||||
|
||||
ig_ctx: *cimgui.c.ImGuiContext,
|
||||
|
||||
/// Our previous instant used to calculate delta time.
|
||||
instant: ?std.time.Instant = null,
|
||||
|
||||
/// Initialize the widget. This must have a stable pointer for events.
|
||||
pub fn init(self: *ImguiWidget) !void {
|
||||
// Each widget gets its own imgui context so we can have multiple
|
||||
// imgui views in the same application.
|
||||
const ig_ctx = cimgui.c.igCreateContext(null);
|
||||
errdefer cimgui.c.igDestroyContext(ig_ctx);
|
||||
cimgui.c.igSetCurrentContext(ig_ctx);
|
||||
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
|
||||
io.BackendPlatformName = "ghostty_gtk";
|
||||
|
||||
const gl_area = c.gtk_gl_area_new();
|
||||
|
||||
// Signals
|
||||
_ = c.g_signal_connect_data(gl_area, "destroy", c.G_CALLBACK(>kDestroy), self, null, c.G_CONNECT_DEFAULT);
|
||||
_ = c.g_signal_connect_data(gl_area, "realize", c.G_CALLBACK(>kRealize), self, null, c.G_CONNECT_DEFAULT);
|
||||
_ = c.g_signal_connect_data(gl_area, "unrealize", c.G_CALLBACK(>kUnrealize), self, null, c.G_CONNECT_DEFAULT);
|
||||
_ = c.g_signal_connect_data(gl_area, "render", c.G_CALLBACK(>kRender), self, null, c.G_CONNECT_DEFAULT);
|
||||
_ = c.g_signal_connect_data(gl_area, "resize", c.G_CALLBACK(>kResize), self, null, c.G_CONNECT_DEFAULT);
|
||||
|
||||
self.* = .{
|
||||
.gl_area = @ptrCast(gl_area),
|
||||
.ig_ctx = ig_ctx,
|
||||
};
|
||||
}
|
||||
|
||||
/// Deinitialize the widget. This should ONLY be called if the widget gl_area
|
||||
/// was never added to a parent. Otherwise, cleanup automatically happens
|
||||
/// when the widget is destroyed and this should NOT be called.
|
||||
pub fn deinit(self: *ImguiWidget) void {
|
||||
cimgui.c.igDestroyContext(self.ig_ctx);
|
||||
}
|
||||
|
||||
/// Initialize the frame. Expects that the context is already current.
|
||||
fn newFrame(self: *ImguiWidget) !void {
|
||||
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
|
||||
|
||||
// Determine our delta time
|
||||
const now = try std.time.Instant.now();
|
||||
io.DeltaTime = if (self.instant) |prev| delta: {
|
||||
const since_ns = now.since(prev);
|
||||
const since_s: f32 = @floatFromInt(since_ns / std.time.ns_per_s);
|
||||
break :delta @max(0.00001, since_s);
|
||||
} else (1 / 60);
|
||||
self.instant = now;
|
||||
}
|
||||
|
||||
fn gtkDestroy(v: *c.GtkWidget, ud: ?*anyopaque) callconv(.C) void {
|
||||
_ = v;
|
||||
log.debug("imgui widget destroy", .{});
|
||||
|
||||
const self: *ImguiWidget = @ptrCast(@alignCast(ud.?));
|
||||
self.deinit();
|
||||
}
|
||||
|
||||
fn gtkRealize(area: *c.GtkGLArea, ud: ?*anyopaque) callconv(.C) void {
|
||||
log.debug("gl surface realized", .{});
|
||||
|
||||
// We need to make the context current so we can call GL functions.
|
||||
c.gtk_gl_area_make_current(area);
|
||||
if (c.gtk_gl_area_get_error(area)) |err| {
|
||||
log.err("surface failed to realize: {s}", .{err.*.message});
|
||||
return;
|
||||
}
|
||||
|
||||
// realize means that our OpenGL context is ready, so we can now
|
||||
// initialize the ImgUI OpenGL backend for our context.
|
||||
const self: *ImguiWidget = @ptrCast(@alignCast(ud.?));
|
||||
cimgui.c.igSetCurrentContext(self.ig_ctx);
|
||||
cimgui.c.ImGui_ImplOpenGL3_Init(null);
|
||||
}
|
||||
|
||||
fn gtkUnrealize(area: *c.GtkGLArea, ud: ?*anyopaque) callconv(.C) void {
|
||||
_ = area;
|
||||
log.debug("gl surface unrealized", .{});
|
||||
|
||||
const self: *ImguiWidget = @ptrCast(@alignCast(ud.?));
|
||||
cimgui.c.igSetCurrentContext(self.ig_ctx);
|
||||
cimgui.c.ImGui_ImplOpenGL3_Shutdown();
|
||||
}
|
||||
|
||||
fn gtkResize(area: *c.GtkGLArea, width: c.gint, height: c.gint, ud: ?*anyopaque) callconv(.C) void {
|
||||
const self: *ImguiWidget = @ptrCast(@alignCast(ud.?));
|
||||
cimgui.c.igSetCurrentContext(self.ig_ctx);
|
||||
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
|
||||
const scale_factor = c.gtk_widget_get_scale_factor(@ptrCast(area));
|
||||
log.debug("gl resize width={} height={} scale={}", .{
|
||||
width,
|
||||
height,
|
||||
scale_factor,
|
||||
});
|
||||
|
||||
io.DisplaySize = .{
|
||||
.x = @floatFromInt(@divFloor(width, scale_factor)),
|
||||
.y = @floatFromInt(@divFloor(height, scale_factor)),
|
||||
};
|
||||
io.DisplayFramebufferScale = .{
|
||||
.x = @floatFromInt(scale_factor),
|
||||
.y = @floatFromInt(scale_factor),
|
||||
};
|
||||
}
|
||||
|
||||
fn gtkRender(area: *c.GtkGLArea, ctx: *c.GdkGLContext, ud: ?*anyopaque) callconv(.C) c.gboolean {
|
||||
_ = area;
|
||||
_ = ctx;
|
||||
const self: *ImguiWidget = @ptrCast(@alignCast(ud.?));
|
||||
|
||||
// Setup our frame
|
||||
cimgui.c.igSetCurrentContext(self.ig_ctx);
|
||||
cimgui.c.ImGui_ImplOpenGL3_NewFrame();
|
||||
self.newFrame() catch |err| {
|
||||
log.err("failed to setup frame: {}", .{err});
|
||||
return 0;
|
||||
};
|
||||
cimgui.c.igNewFrame();
|
||||
|
||||
// Build our UI
|
||||
var show: bool = true;
|
||||
cimgui.c.igShowDemoWindow(&show);
|
||||
|
||||
// Render
|
||||
cimgui.c.igRender();
|
||||
|
||||
// OpenGL final render
|
||||
gl.clearColor(0.45, 0.55, 0.60, 1.00);
|
||||
gl.clear(gl.c.GL_COLOR_BUFFER_BIT);
|
||||
cimgui.c.ImGui_ImplOpenGL3_RenderDrawData(cimgui.c.igGetDrawData());
|
||||
|
||||
return 1;
|
||||
}
|
@ -3,6 +3,7 @@ const Allocator = std.mem.Allocator;
|
||||
|
||||
const App = @import("App.zig");
|
||||
const TerminalWindow = @import("Window.zig");
|
||||
const ImguiWidget = @import("ImguiWidget.zig");
|
||||
const c = @import("c.zig");
|
||||
const icon = @import("icon.zig");
|
||||
|
||||
@ -10,14 +11,10 @@ const log = std.log.scoped(.inspector);
|
||||
|
||||
/// A window to hold a dedicated inspector instance.
|
||||
pub const Window = struct {
|
||||
/// Our app
|
||||
app: *App,
|
||||
|
||||
/// Our window
|
||||
window: *c.GtkWindow,
|
||||
|
||||
/// The window icon
|
||||
icon: icon.Icon,
|
||||
imgui_widget: ImguiWidget,
|
||||
|
||||
pub fn create(alloc: Allocator, app: *App) !*Window {
|
||||
var window = try alloc.create(Window);
|
||||
@ -32,6 +29,7 @@ pub const Window = struct {
|
||||
.app = app,
|
||||
.icon = undefined,
|
||||
.window = undefined,
|
||||
.imgui_widget = undefined,
|
||||
};
|
||||
|
||||
// Create the window
|
||||
@ -44,10 +42,15 @@ pub const Window = struct {
|
||||
self.icon = try icon.appIcon(self.app, window);
|
||||
c.gtk_window_set_icon_name(gtk_window, self.icon.name);
|
||||
|
||||
// Initialize our imgui widget
|
||||
try self.imgui_widget.init();
|
||||
errdefer self.imgui_widget.deinit();
|
||||
|
||||
// Signals
|
||||
_ = c.g_signal_connect_data(window, "destroy", c.G_CALLBACK(>kDestroy), self, null, c.G_CONNECT_DEFAULT);
|
||||
|
||||
// Show the window
|
||||
c.gtk_window_set_child(gtk_window, @ptrCast(self.imgui_widget.gl_area));
|
||||
c.gtk_widget_show(window);
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user