* vendor/cimgui

* Add a "dev mode" window which for now is just imgui demo
This commit is contained in:
Mitchell Hashimoto
2022-10-16 16:20:08 -07:00
committed by GitHub
parent ddfb1dec4b
commit f29393bca6
18 changed files with 423 additions and 3 deletions

3
.gitmodules vendored
View File

@ -28,3 +28,6 @@
[submodule "vendor/zig-libxml2"]
path = vendor/zig-libxml2
url = https://github.com/mitchellh/zig-libxml2.git
[submodule "vendor/cimgui"]
path = vendor/cimgui
url = https://github.com/cimgui/cimgui.git

View File

@ -6,6 +6,7 @@ const glfw = @import("vendor/mach/libs/glfw/build.zig");
const fontconfig = @import("pkg/fontconfig/build.zig");
const freetype = @import("pkg/freetype/build.zig");
const harfbuzz = @import("pkg/harfbuzz/build.zig");
const imgui = @import("pkg/imgui/build.zig");
const libxml2 = @import("vendor/zig-libxml2/libxml2.zig");
const libuv = @import("pkg/libuv/build.zig");
const libpng = @import("pkg/libpng/build.zig");
@ -189,6 +190,7 @@ fn addDeps(
if (enable_fontconfig) step.addPackage(fontconfig.pkg);
step.addPackage(freetype.pkg);
step.addPackage(harfbuzz.pkg);
step.addPackage(imgui.pkg);
step.addPackage(glfw.pkg);
step.addPackage(libuv.pkg);
step.addPackage(utf8proc.pkg);
@ -214,10 +216,15 @@ fn addDeps(
_ = try utf8proc.link(b, step);
// Glfw
try glfw.link(b, step, .{
.metal = false,
.opengl = false, // Found at runtime
const glfw_opts: glfw.Options = .{ .metal = false, .opengl = false };
try glfw.link(b, step, glfw_opts);
// Imgui
const imgui_backends = [_][]const u8{ "glfw", "opengl3" };
const imgui_step = try imgui.link(b, step, .{
.backends = &imgui_backends,
});
try glfw.link(b, imgui_step, glfw_opts);
// Dynamic link
if (!static) {

93
pkg/imgui/build.zig Normal file
View File

@ -0,0 +1,93 @@
const std = @import("std");
/// Directories with our includes.
const root = thisDir() ++ "../../../vendor/cimgui/";
pub const include_paths = [_][]const u8{
root,
root ++ "imgui",
root ++ "imgui/backends",
};
pub const pkg = std.build.Pkg{
.name = "imgui",
.source = .{ .path = thisDir() ++ "/main.zig" },
};
fn thisDir() []const u8 {
return std.fs.path.dirname(@src().file) orelse ".";
}
pub const Options = struct {
backends: ?[]const []const u8 = null,
};
pub fn link(
b: *std.build.Builder,
step: *std.build.LibExeObjStep,
opt: Options,
) !*std.build.LibExeObjStep {
const lib = try buildImgui(b, step, opt);
step.linkLibrary(lib);
inline for (include_paths) |path| step.addIncludePath(path);
return lib;
}
pub fn buildImgui(
b: *std.build.Builder,
step: *std.build.LibExeObjStep,
opt: Options,
) !*std.build.LibExeObjStep {
const target = step.target;
const lib = b.addStaticLibrary("imgui", null);
lib.setTarget(step.target);
lib.setBuildMode(step.build_mode);
// Include
inline for (include_paths) |path| lib.addIncludePath(path);
// Link
lib.linkLibC();
// Compile
var flags = std.ArrayList([]const u8).init(b.allocator);
defer flags.deinit();
try flags.appendSlice(&.{
"-DIMGUI_DISABLE_OBSOLETE_FUNCTIONS=1",
//"-fno-sanitize=undefined",
});
switch (target.getOsTag()) {
.windows => try flags.appendSlice(&.{
"-DIMGUI_IMPL_API=extern\t\"C\"\t__declspec(dllexport)",
}),
else => try flags.appendSlice(&.{
"-DIMGUI_IMPL_API=extern\t\"C\"\t",
}),
}
// C files
lib.addCSourceFiles(srcs, flags.items);
if (opt.backends) |backends| {
for (backends) |backend| {
var buf: [4096]u8 = undefined;
const path = try std.fmt.bufPrint(
&buf,
"{s}imgui/backends/imgui_impl_{s}.cpp",
.{ root, backend },
);
lib.addCSourceFile(path, flags.items);
}
}
return lib;
}
const srcs = &.{
root ++ "cimgui.cpp",
root ++ "imgui/imgui.cpp",
root ++ "imgui/imgui_demo.cpp",
root ++ "imgui/imgui_draw.cpp",
root ++ "imgui/imgui_tables.cpp",
root ++ "imgui/imgui_widgets.cpp",
};

4
pkg/imgui/c.zig Normal file
View File

@ -0,0 +1,4 @@
pub usingnamespace @cImport({
@cDefine("CIMGUI_DEFINE_ENUMS_AND_STRUCTS", "");
@cInclude("cimgui.h");
});

28
pkg/imgui/context.zig Normal file
View File

@ -0,0 +1,28 @@
const std = @import("std");
const c = @import("c.zig");
const Allocator = std.mem.Allocator;
pub const Context = opaque {
pub fn create() Allocator.Error!*Context {
return @ptrCast(
?*Context,
c.igCreateContext(null),
) orelse Allocator.Error.OutOfMemory;
}
pub fn destroy(self: *Context) void {
c.igDestroyContext(self.cval());
}
pub inline fn cval(self: *Context) *c.ImGuiContext {
return @ptrCast(
*c.ImGuiContext,
@alignCast(@alignOf(c.ImGuiContext), self),
);
}
};
test {
var ctx = try Context.create();
defer ctx.destroy();
}

20
pkg/imgui/core.zig Normal file
View File

@ -0,0 +1,20 @@
const std = @import("std");
const c = @import("c.zig");
const imgui = @import("main.zig");
const Allocator = std.mem.Allocator;
pub fn newFrame() void {
c.igNewFrame();
}
pub fn endFrame() void {
c.igEndFrame();
}
pub fn render() void {
c.igRender();
}
pub fn showDemoWindow(open: ?*bool) void {
c.igShowDemoWindow(@ptrCast([*c]bool, if (open) |v| v else null));
}

20
pkg/imgui/draw_data.zig Normal file
View File

@ -0,0 +1,20 @@
const std = @import("std");
const c = @import("c.zig");
const imgui = @import("main.zig");
const Allocator = std.mem.Allocator;
pub const DrawData = opaque {
pub fn get() Allocator.Error!*DrawData {
return @ptrCast(
?*DrawData,
c.igGetDrawData(),
) orelse Allocator.Error.OutOfMemory;
}
pub inline fn cval(self: *DrawData) *c.ImGuiDrawData {
return @ptrCast(
*c.ImGuiDrawData,
@alignCast(@alignOf(c.ImGuiDrawData), self),
);
}
};

28
pkg/imgui/impl_glfw.zig Normal file
View File

@ -0,0 +1,28 @@
const std = @import("std");
const c = @import("c.zig");
const imgui = @import("main.zig");
const Allocator = std.mem.Allocator;
pub const ImplGlfw = struct {
pub const GLFWWindow = opaque {};
pub fn initForOpenGL(win: *GLFWWindow, install_callbacks: bool) bool {
// https://github.com/ocornut/imgui/issues/5785
defer _ = glfwGetError(null);
return ImGui_ImplGlfw_InitForOpenGL(win, install_callbacks);
}
pub fn shutdown() void {
return ImGui_ImplGlfw_Shutdown();
}
pub fn newFrame() void {
return ImGui_ImplGlfw_NewFrame();
}
extern "c" fn glfwGetError(?*const anyopaque) c_int;
extern "c" fn ImGui_ImplGlfw_InitForOpenGL(*GLFWWindow, bool) bool;
extern "c" fn ImGui_ImplGlfw_Shutdown() void;
extern "c" fn ImGui_ImplGlfw_NewFrame() void;
};

View File

@ -0,0 +1,30 @@
const std = @import("std");
const c = @import("c.zig");
const imgui = @import("main.zig");
const Allocator = std.mem.Allocator;
pub const ImplOpenGL3 = struct {
pub fn init(glsl_version: ?[:0]const u8) bool {
return ImGui_ImplOpenGL3_Init(
if (glsl_version) |s| s.ptr else null,
);
}
pub fn shutdown() void {
return ImGui_ImplOpenGL3_Shutdown();
}
pub fn newFrame() void {
return ImGui_ImplOpenGL3_NewFrame();
}
pub fn renderDrawData(data: *imgui.DrawData) void {
ImGui_ImplOpenGL3_RenderDrawData(data);
}
extern "c" fn glfwGetError(?*const anyopaque) c_int;
extern "c" fn ImGui_ImplOpenGL3_Init([*c]const u8) bool;
extern "c" fn ImGui_ImplOpenGL3_Shutdown() void;
extern "c" fn ImGui_ImplOpenGL3_NewFrame() void;
extern "c" fn ImGui_ImplOpenGL3_RenderDrawData(*imgui.DrawData) void;
};

26
pkg/imgui/io.zig Normal file
View File

@ -0,0 +1,26 @@
const std = @import("std");
const c = @import("c.zig");
const imgui = @import("main.zig");
const Allocator = std.mem.Allocator;
pub const IO = opaque {
pub fn get() Allocator.Error!*IO {
return @ptrCast(
?*IO,
c.igGetIO(),
) orelse Allocator.Error.OutOfMemory;
}
pub inline fn cval(self: *IO) *c.ImGuiIO {
return @ptrCast(
*c.ImGuiIO,
@alignCast(@alignOf(c.ImGuiIO), self),
);
}
};
test {
const ctx = try imgui.Context.create();
defer ctx.destroy();
_ = try IO.get();
}

13
pkg/imgui/main.zig Normal file
View File

@ -0,0 +1,13 @@
pub const c = @import("c.zig");
pub usingnamespace @import("context.zig");
pub usingnamespace @import("core.zig");
pub usingnamespace @import("draw_data.zig");
pub usingnamespace @import("io.zig");
pub usingnamespace @import("style.zig");
pub usingnamespace @import("impl_glfw.zig");
pub usingnamespace @import("impl_opengl3.zig");
test {
@import("std").testing.refAllDecls(@This());
}

35
pkg/imgui/style.zig Normal file
View File

@ -0,0 +1,35 @@
const std = @import("std");
const c = @import("c.zig");
const Allocator = std.mem.Allocator;
pub const Style = opaque {
pub fn get() Allocator.Error!*Style {
return @ptrCast(
?*Style,
c.igGetStyle(),
) orelse Allocator.Error.OutOfMemory;
}
pub fn colorsDark(self: *Style) void {
c.igStyleColorsDark(self.cval());
}
pub fn colorsLight(self: *Style) void {
c.igStyleColorsLight(self.cval());
}
pub fn colorsClassic(self: *Style) void {
c.igStyleColorsClassic(self.cval());
}
pub fn scaleAllSizes(self: *Style, factor: f32) void {
c.ImGuiStyle_ScaleAllSizes(self.cval(), factor);
}
pub inline fn cval(self: *Style) *c.ImGuiStyle {
return @ptrCast(
*c.ImGuiStyle,
@alignCast(@alignOf(c.ImGuiStyle), self),
);
}
};

38
src/DevMode.zig Normal file
View File

@ -0,0 +1,38 @@
//! This file implements the "dev mode" interface for the terminal. This
//! includes state managements and rendering.
const DevMode = @This();
const imgui = @import("imgui");
/// If this is false, the rest of the terminal will be compiled without
/// dev mode support at all.
pub const enabled = true;
/// The global DevMode instance that can be used app-wide. Assume all functions
/// are NOT thread-safe unless otherwise noted.
pub var instance: DevMode = .{};
/// Whether to show the dev mode UI currently.
visible: bool = false,
/// Update the state associated with the dev mode. This should generally
/// only be called paired with a render since it otherwise wastes CPU
/// cycles.
pub fn update(self: DevMode) void {
_ = self;
imgui.ImplOpenGL3.newFrame();
imgui.ImplGlfw.newFrame();
imgui.newFrame();
// Just demo for now
imgui.showDemoWindow(null);
}
/// Render the scene and return the draw data. The caller must be imgui-aware
/// in order to render the draw data. This lets this file be renderer/backend
/// agnostic.
pub fn render(self: DevMode) !*imgui.DrawData {
_ = self;
imgui.render();
return try imgui.DrawData.get();
}

View File

@ -12,6 +12,7 @@ const Allocator = std.mem.Allocator;
const Grid = @import("Grid.zig");
const glfw = @import("glfw");
const gl = @import("opengl.zig");
const imgui = @import("imgui");
const libuv = @import("libuv");
const Pty = @import("Pty.zig");
const font = @import("font/main.zig");
@ -22,6 +23,7 @@ const max_timer = @import("max_timer.zig");
const terminal = @import("terminal/main.zig");
const Config = @import("config.zig").Config;
const input = @import("input.zig");
const DevMode = @import("DevMode.zig");
const RenderTimer = max_timer.MaxTimer(renderTimerCallback);
@ -45,6 +47,9 @@ window: glfw.Window,
/// The glfw mouse cursor handle.
cursor: glfw.Cursor,
/// Imgui context
imgui_ctx: if (DevMode.enabled) *imgui.Context else void,
/// Whether the window is currently focused
focused: bool,
@ -475,7 +480,10 @@ pub fn create(alloc: Allocator, loop: libuv.Loop, config: *const Config) !*Windo
.bg_g = @intToFloat(f32, config.background.g) / 255.0,
.bg_b = @intToFloat(f32, config.background.b) / 255.0,
.bg_a = 1.0,
.imgui_ctx = if (!DevMode.enabled) void else try imgui.Context.create(),
};
errdefer if (DevMode.enabled) self.imgui_ctx.destroy();
// Setup our callbacks and user data
window.setUserPointer(self);
@ -499,10 +507,37 @@ pub fn create(alloc: Allocator, loop: libuv.Loop, config: *const Config) !*Windo
@intCast(i32, window_size.height),
);
// Load imgui. This must be done LAST because it has to be done after
// all our GLFW setup is complete.
if (DevMode.enabled) {
const io = try imgui.IO.get();
io.cval().IniFilename = "ghostty_dev_mode.ini";
// On Mac imgui handles scaling automatically just fine. On Linux
// and other platforms we need to apply a scaling factor.
if (builtin.os.tag != .macos) io.cval().FontGlobalScale = content_scale.x_scale;
const style = try imgui.Style.get();
style.colorsDark();
assert(imgui.ImplGlfw.initForOpenGL(
@ptrCast(*imgui.ImplGlfw.GLFWWindow, window.handle),
true,
));
assert(imgui.ImplOpenGL3.init("#version 330 core"));
}
return self;
}
pub fn destroy(self: *Window) void {
if (DevMode.enabled) {
// Uninitialize imgui
imgui.ImplOpenGL3.shutdown();
imgui.ImplGlfw.shutdown();
self.imgui_ctx.destroy();
}
// Deinitialize the pty. This closes the pty handles. This should
// cause a close in the our subprocess so just wait for that.
self.pty.deinit();
@ -747,6 +782,7 @@ fn keyCallback(
.F10 => .f10,
.F11 => .f11,
.F12 => .f12,
.grave_accent => .grave_accent,
else => .invalid,
},
};
@ -795,6 +831,11 @@ fn keyCallback(
log.err("error queueing write in keyCallback err={}", .{err});
}
},
.toggle_dev_mode => if (DevMode.enabled) {
DevMode.instance.visible = !DevMode.instance.visible;
win.render_timer.schedule() catch unreachable;
} else log.warn("dev mode was not compiled into this binary", .{}),
}
// Bindings always result in us ignoring the char if printable
@ -1109,6 +1150,13 @@ fn mouseButtonCallback(
const win = window.getUserPointer(Window) orelse return;
// If our dev mode window is visible then we always schedule a render on
// cursor move because the cursor might touch our windows.
if (DevMode.enabled and DevMode.instance.visible) {
win.render_timer.schedule() catch |err|
log.err("error scheduling render timer in cursorPosCallback err={}", .{err});
}
// Convert glfw button to input button
const button: input.MouseButton = switch (glfw_button) {
.left => .left,
@ -1186,6 +1234,13 @@ fn cursorPosCallback(
const win = window.getUserPointer(Window) orelse return;
// If our dev mode window is visible then we always schedule a render on
// cursor move because the cursor might touch our windows.
if (DevMode.enabled and DevMode.instance.visible) {
win.render_timer.schedule() catch |err|
log.err("error scheduling render timer in cursorPosCallback err={}", .{err});
}
// Do a mouse report
if (win.terminal.modes.mouse_event != .none) {
// We use the first mouse button we find pressed in order to report
@ -1525,6 +1580,12 @@ fn renderTimerCallback(t: *libuv.Timer) void {
return;
};
if (DevMode.enabled and DevMode.instance.visible) {
DevMode.instance.update();
const data = DevMode.instance.render() catch unreachable;
imgui.ImplOpenGL3.renderDrawData(data);
}
// Swap
win.window.swapBuffers() catch |err| {
log.err("error swapping buffers: {}", .{err});

View File

@ -120,6 +120,13 @@ pub const Config = struct {
try result.keybind.set.put(alloc, .{ .key = .f11 }, .{ .csi = "23~" });
try result.keybind.set.put(alloc, .{ .key = .f12 }, .{ .csi = "24~" });
// Dev Mode
try result.keybind.set.put(
alloc,
.{ .key = .grave_accent, .mods = .{ .shift = true, .super = true } },
.{ .toggle_dev_mode = 0 },
);
return result;
}

View File

@ -140,6 +140,9 @@ pub const Action = union(enum) {
/// Copy and paste.
copy_to_clipboard: Void,
paste_from_clipboard: Void,
/// Dev mode
toggle_dev_mode: Void,
};
/// Trigger is the associated key state that can trigger an action.

View File

@ -49,6 +49,9 @@ pub const Key = enum {
y,
z,
// other
grave_accent, // `
// control
up,
down,

1
vendor/cimgui vendored Submodule

@ -0,0 +1 @@
Subproject commit 9ce2c32dada1e1fcb90f2c9bab2568895db719f5