mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00
apprt/gtk: complete imgui backend
This commit is contained in:
@ -5,12 +5,15 @@ const assert = std.debug.assert;
|
||||
|
||||
const cimgui = @import("cimgui");
|
||||
const c = @import("c.zig");
|
||||
const key = @import("key.zig");
|
||||
const gl = @import("../../renderer/opengl/main.zig");
|
||||
const input = @import("../../input.zig");
|
||||
|
||||
const log = std.log.scoped(.gtk_imgui_widget);
|
||||
|
||||
/// Our OpenGL widget
|
||||
gl_area: *c.GtkGLArea,
|
||||
im_context: *c.GtkIMContext,
|
||||
|
||||
ig_ctx: *cimgui.c.ImGuiContext,
|
||||
|
||||
@ -27,7 +30,49 @@ pub fn init(self: *ImguiWidget) !void {
|
||||
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
|
||||
io.BackendPlatformName = "ghostty_gtk";
|
||||
|
||||
// Our OpenGL area for drawing
|
||||
const gl_area = c.gtk_gl_area_new();
|
||||
c.gtk_gl_area_set_auto_render(@ptrCast(gl_area), 1);
|
||||
|
||||
// The GL area has to be focusable so that it can receive events
|
||||
c.gtk_widget_set_focusable(@ptrCast(gl_area), 1);
|
||||
c.gtk_widget_set_focus_on_click(@ptrCast(gl_area), 1);
|
||||
|
||||
// Clicks
|
||||
const gesture_click = c.gtk_gesture_click_new();
|
||||
errdefer c.g_object_unref(gesture_click);
|
||||
c.gtk_gesture_single_set_button(@ptrCast(gesture_click), 0);
|
||||
c.gtk_widget_add_controller(@ptrCast(gl_area), @ptrCast(gesture_click));
|
||||
|
||||
// Mouse movement
|
||||
const ec_motion = c.gtk_event_controller_motion_new();
|
||||
errdefer c.g_object_unref(ec_motion);
|
||||
c.gtk_widget_add_controller(@ptrCast(gl_area), ec_motion);
|
||||
|
||||
// Scroll events
|
||||
const ec_scroll = c.gtk_event_controller_scroll_new(
|
||||
c.GTK_EVENT_CONTROLLER_SCROLL_BOTH_AXES |
|
||||
c.GTK_EVENT_CONTROLLER_SCROLL_DISCRETE,
|
||||
);
|
||||
errdefer c.g_object_unref(ec_scroll);
|
||||
c.gtk_widget_add_controller(@ptrCast(gl_area), ec_scroll);
|
||||
|
||||
// Focus controller will tell us about focus enter/exit events
|
||||
const ec_focus = c.gtk_event_controller_focus_new();
|
||||
errdefer c.g_object_unref(ec_focus);
|
||||
c.gtk_widget_add_controller(@ptrCast(gl_area), ec_focus);
|
||||
|
||||
// Key event controller will tell us about raw keypress events.
|
||||
const ec_key = c.gtk_event_controller_key_new();
|
||||
errdefer c.g_object_unref(ec_key);
|
||||
c.gtk_widget_add_controller(@ptrCast(gl_area), ec_key);
|
||||
errdefer c.gtk_widget_remove_controller(@ptrCast(gl_area), ec_key);
|
||||
|
||||
// The input method context that we use to translate key events into
|
||||
// characters. This doesn't have an event key controller attached because
|
||||
// we call it manually from our own key controller.
|
||||
const im_context = c.gtk_im_multicontext_new();
|
||||
errdefer c.g_object_unref(im_context);
|
||||
|
||||
// Signals
|
||||
_ = c.g_signal_connect_data(gl_area, "destroy", c.G_CALLBACK(>kDestroy), self, null, c.G_CONNECT_DEFAULT);
|
||||
@ -36,8 +81,19 @@ pub fn init(self: *ImguiWidget) !void {
|
||||
_ = 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);
|
||||
|
||||
_ = c.g_signal_connect_data(ec_focus, "enter", c.G_CALLBACK(>kFocusEnter), self, null, c.G_CONNECT_DEFAULT);
|
||||
_ = c.g_signal_connect_data(ec_focus, "leave", c.G_CALLBACK(>kFocusLeave), self, null, c.G_CONNECT_DEFAULT);
|
||||
_ = c.g_signal_connect_data(ec_key, "key-pressed", c.G_CALLBACK(>kKeyPressed), self, null, c.G_CONNECT_DEFAULT);
|
||||
_ = c.g_signal_connect_data(ec_key, "key-released", c.G_CALLBACK(>kKeyReleased), self, null, c.G_CONNECT_DEFAULT);
|
||||
_ = c.g_signal_connect_data(ec_motion, "motion", c.G_CALLBACK(>kMouseMotion), self, null, c.G_CONNECT_DEFAULT);
|
||||
_ = c.g_signal_connect_data(ec_scroll, "scroll", c.G_CALLBACK(>kMouseScroll), self, null, c.G_CONNECT_DEFAULT);
|
||||
_ = c.g_signal_connect_data(gesture_click, "pressed", c.G_CALLBACK(>kMouseDown), self, null, c.G_CONNECT_DEFAULT);
|
||||
_ = c.g_signal_connect_data(gesture_click, "released", c.G_CALLBACK(>kMouseUp), self, null, c.G_CONNECT_DEFAULT);
|
||||
_ = c.g_signal_connect_data(im_context, "commit", c.G_CALLBACK(>kInputCommit), self, null, c.G_CONNECT_DEFAULT);
|
||||
|
||||
self.* = .{
|
||||
.gl_area = @ptrCast(gl_area),
|
||||
.im_context = @ptrCast(im_context),
|
||||
.ig_ctx = ig_ctx,
|
||||
};
|
||||
}
|
||||
@ -63,6 +119,19 @@ fn newFrame(self: *ImguiWidget) !void {
|
||||
self.instant = now;
|
||||
}
|
||||
|
||||
fn queueRender(self: *ImguiWidget) void {
|
||||
c.gtk_gl_area_queue_render(self.gl_area);
|
||||
}
|
||||
|
||||
fn translateMouseButton(button: c.guint) ?c_int {
|
||||
return switch (button) {
|
||||
1 => cimgui.c.ImGuiMouseButton_Left,
|
||||
2 => cimgui.c.ImGuiMouseButton_Middle,
|
||||
3 => cimgui.c.ImGuiMouseButton_Right,
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
||||
fn gtkDestroy(v: *c.GtkWidget, ud: ?*anyopaque) callconv(.C) void {
|
||||
_ = v;
|
||||
log.debug("imgui widget destroy", .{});
|
||||
@ -146,3 +215,289 @@ fn gtkRender(area: *c.GtkGLArea, ctx: *c.GdkGLContext, ud: ?*anyopaque) callconv
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
fn gtkMouseMotion(
|
||||
_: *c.GtkEventControllerMotion,
|
||||
x: c.gdouble,
|
||||
y: c.gdouble,
|
||||
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();
|
||||
cimgui.c.ImGuiIO_AddMousePosEvent(io, @floatCast(x), @floatCast(y));
|
||||
self.queueRender();
|
||||
}
|
||||
|
||||
fn gtkMouseDown(
|
||||
gesture: *c.GtkGestureClick,
|
||||
_: c.gint,
|
||||
_: c.gdouble,
|
||||
_: c.gdouble,
|
||||
ud: ?*anyopaque,
|
||||
) callconv(.C) void {
|
||||
const self: *ImguiWidget = @ptrCast(@alignCast(ud.?));
|
||||
self.queueRender();
|
||||
|
||||
cimgui.c.igSetCurrentContext(self.ig_ctx);
|
||||
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
|
||||
const gdk_button = c.gtk_gesture_single_get_current_button(@ptrCast(gesture));
|
||||
if (translateMouseButton(gdk_button)) |button| {
|
||||
cimgui.c.ImGuiIO_AddMouseButtonEvent(io, button, true);
|
||||
}
|
||||
}
|
||||
|
||||
fn gtkMouseUp(
|
||||
gesture: *c.GtkGestureClick,
|
||||
_: c.gint,
|
||||
_: c.gdouble,
|
||||
_: c.gdouble,
|
||||
ud: ?*anyopaque,
|
||||
) callconv(.C) void {
|
||||
const self: *ImguiWidget = @ptrCast(@alignCast(ud.?));
|
||||
self.queueRender();
|
||||
|
||||
cimgui.c.igSetCurrentContext(self.ig_ctx);
|
||||
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
|
||||
const gdk_button = c.gtk_gesture_single_get_current_button(@ptrCast(gesture));
|
||||
if (translateMouseButton(gdk_button)) |button| {
|
||||
cimgui.c.ImGuiIO_AddMouseButtonEvent(io, button, false);
|
||||
}
|
||||
}
|
||||
|
||||
fn gtkMouseScroll(
|
||||
_: *c.GtkEventControllerScroll,
|
||||
x: c.gdouble,
|
||||
y: c.gdouble,
|
||||
ud: ?*anyopaque,
|
||||
) callconv(.C) void {
|
||||
const self: *ImguiWidget = @ptrCast(@alignCast(ud.?));
|
||||
self.queueRender();
|
||||
|
||||
cimgui.c.igSetCurrentContext(self.ig_ctx);
|
||||
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
|
||||
cimgui.c.ImGuiIO_AddMouseWheelEvent(
|
||||
io,
|
||||
@floatCast(x),
|
||||
@floatCast(-y),
|
||||
);
|
||||
}
|
||||
|
||||
fn gtkFocusEnter(_: *c.GtkEventControllerFocus, ud: ?*anyopaque) callconv(.C) void {
|
||||
const self: *ImguiWidget = @ptrCast(@alignCast(ud.?));
|
||||
self.queueRender();
|
||||
|
||||
cimgui.c.igSetCurrentContext(self.ig_ctx);
|
||||
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
|
||||
cimgui.c.ImGuiIO_AddFocusEvent(io, true);
|
||||
}
|
||||
|
||||
fn gtkFocusLeave(_: *c.GtkEventControllerFocus, ud: ?*anyopaque) callconv(.C) void {
|
||||
const self: *ImguiWidget = @ptrCast(@alignCast(ud.?));
|
||||
self.queueRender();
|
||||
|
||||
cimgui.c.igSetCurrentContext(self.ig_ctx);
|
||||
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
|
||||
cimgui.c.ImGuiIO_AddFocusEvent(io, false);
|
||||
}
|
||||
|
||||
fn gtkInputCommit(
|
||||
_: *c.GtkIMContext,
|
||||
bytes: [*:0]u8,
|
||||
ud: ?*anyopaque,
|
||||
) callconv(.C) void {
|
||||
const self: *ImguiWidget = @ptrCast(@alignCast(ud.?));
|
||||
self.queueRender();
|
||||
|
||||
cimgui.c.igSetCurrentContext(self.ig_ctx);
|
||||
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
|
||||
cimgui.c.ImGuiIO_AddInputCharactersUTF8(io, bytes);
|
||||
}
|
||||
|
||||
fn gtkKeyPressed(
|
||||
ec_key: *c.GtkEventControllerKey,
|
||||
keyval: c.guint,
|
||||
keycode: c.guint,
|
||||
gtk_mods: c.GdkModifierType,
|
||||
ud: ?*anyopaque,
|
||||
) callconv(.C) c.gboolean {
|
||||
return if (keyEvent(.press, ec_key, keyval, keycode, gtk_mods, ud)) 1 else 0;
|
||||
}
|
||||
|
||||
fn gtkKeyReleased(
|
||||
ec_key: *c.GtkEventControllerKey,
|
||||
keyval: c.guint,
|
||||
keycode: c.guint,
|
||||
state: c.GdkModifierType,
|
||||
ud: ?*anyopaque,
|
||||
) callconv(.C) c.gboolean {
|
||||
return if (keyEvent(.release, ec_key, keyval, keycode, state, ud)) 1 else 0;
|
||||
}
|
||||
|
||||
fn keyEvent(
|
||||
action: input.Action,
|
||||
ec_key: *c.GtkEventControllerKey,
|
||||
keyval: c.guint,
|
||||
keycode: c.guint,
|
||||
gtk_mods: c.GdkModifierType,
|
||||
ud: ?*anyopaque,
|
||||
) bool {
|
||||
_ = keycode;
|
||||
|
||||
const self: *ImguiWidget = @ptrCast(@alignCast(ud.?));
|
||||
self.queueRender();
|
||||
|
||||
cimgui.c.igSetCurrentContext(self.ig_ctx);
|
||||
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
|
||||
|
||||
// Translate the GTK mods and update the modifiers on every keypress
|
||||
const mods = key.translateMods(gtk_mods);
|
||||
cimgui.c.ImGuiIO_AddKeyEvent(io, cimgui.c.ImGuiKey_LeftShift, mods.shift);
|
||||
cimgui.c.ImGuiIO_AddKeyEvent(io, cimgui.c.ImGuiKey_LeftCtrl, mods.ctrl);
|
||||
cimgui.c.ImGuiIO_AddKeyEvent(io, cimgui.c.ImGuiKey_LeftAlt, mods.alt);
|
||||
cimgui.c.ImGuiIO_AddKeyEvent(io, cimgui.c.ImGuiKey_LeftSuper, mods.super);
|
||||
|
||||
// If our keyval has a key, then we send that key event
|
||||
if (key.keyFromKeyval(keyval)) |inputkey| {
|
||||
if (translateKey(inputkey)) |imgui_key| {
|
||||
cimgui.c.ImGuiIO_AddKeyEvent(io, imgui_key, action == .press);
|
||||
}
|
||||
}
|
||||
|
||||
// Try to process the event as text
|
||||
const event = c.gtk_event_controller_get_current_event(@ptrCast(ec_key));
|
||||
_ = c.gtk_im_context_filter_keypress(self.im_context, event);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
fn translateKey(v: input.Key) ?c_uint {
|
||||
return switch (v) {
|
||||
.a => cimgui.c.ImGuiKey_A,
|
||||
.b => cimgui.c.ImGuiKey_B,
|
||||
.c => cimgui.c.ImGuiKey_C,
|
||||
.d => cimgui.c.ImGuiKey_D,
|
||||
.e => cimgui.c.ImGuiKey_E,
|
||||
.f => cimgui.c.ImGuiKey_F,
|
||||
.g => cimgui.c.ImGuiKey_G,
|
||||
.h => cimgui.c.ImGuiKey_H,
|
||||
.i => cimgui.c.ImGuiKey_I,
|
||||
.j => cimgui.c.ImGuiKey_J,
|
||||
.k => cimgui.c.ImGuiKey_K,
|
||||
.l => cimgui.c.ImGuiKey_L,
|
||||
.m => cimgui.c.ImGuiKey_M,
|
||||
.n => cimgui.c.ImGuiKey_N,
|
||||
.o => cimgui.c.ImGuiKey_O,
|
||||
.p => cimgui.c.ImGuiKey_P,
|
||||
.q => cimgui.c.ImGuiKey_Q,
|
||||
.r => cimgui.c.ImGuiKey_R,
|
||||
.s => cimgui.c.ImGuiKey_S,
|
||||
.t => cimgui.c.ImGuiKey_T,
|
||||
.u => cimgui.c.ImGuiKey_U,
|
||||
.v => cimgui.c.ImGuiKey_V,
|
||||
.w => cimgui.c.ImGuiKey_W,
|
||||
.x => cimgui.c.ImGuiKey_X,
|
||||
.y => cimgui.c.ImGuiKey_Y,
|
||||
.z => cimgui.c.ImGuiKey_Z,
|
||||
|
||||
.zero => cimgui.c.ImGuiKey_0,
|
||||
.one => cimgui.c.ImGuiKey_1,
|
||||
.two => cimgui.c.ImGuiKey_2,
|
||||
.three => cimgui.c.ImGuiKey_3,
|
||||
.four => cimgui.c.ImGuiKey_4,
|
||||
.five => cimgui.c.ImGuiKey_5,
|
||||
.six => cimgui.c.ImGuiKey_6,
|
||||
.seven => cimgui.c.ImGuiKey_7,
|
||||
.eight => cimgui.c.ImGuiKey_8,
|
||||
.nine => cimgui.c.ImGuiKey_9,
|
||||
|
||||
.semicolon => cimgui.c.ImGuiKey_Semicolon,
|
||||
.space => cimgui.c.ImGuiKey_Space,
|
||||
.apostrophe => cimgui.c.ImGuiKey_Apostrophe,
|
||||
.comma => cimgui.c.ImGuiKey_Comma,
|
||||
.grave_accent => cimgui.c.ImGuiKey_GraveAccent,
|
||||
.period => cimgui.c.ImGuiKey_Period,
|
||||
.slash => cimgui.c.ImGuiKey_Slash,
|
||||
.minus => cimgui.c.ImGuiKey_Minus,
|
||||
.equal => cimgui.c.ImGuiKey_Equal,
|
||||
.left_bracket => cimgui.c.ImGuiKey_LeftBracket,
|
||||
.right_bracket => cimgui.c.ImGuiKey_RightBracket,
|
||||
.backslash => cimgui.c.ImGuiKey_Backslash,
|
||||
|
||||
.up => cimgui.c.ImGuiKey_UpArrow,
|
||||
.down => cimgui.c.ImGuiKey_DownArrow,
|
||||
.left => cimgui.c.ImGuiKey_LeftArrow,
|
||||
.right => cimgui.c.ImGuiKey_RightArrow,
|
||||
.home => cimgui.c.ImGuiKey_Home,
|
||||
.end => cimgui.c.ImGuiKey_End,
|
||||
.insert => cimgui.c.ImGuiKey_Insert,
|
||||
.delete => cimgui.c.ImGuiKey_Delete,
|
||||
.caps_lock => cimgui.c.ImGuiKey_CapsLock,
|
||||
.scroll_lock => cimgui.c.ImGuiKey_ScrollLock,
|
||||
.num_lock => cimgui.c.ImGuiKey_NumLock,
|
||||
.page_up => cimgui.c.ImGuiKey_PageUp,
|
||||
.page_down => cimgui.c.ImGuiKey_PageDown,
|
||||
.escape => cimgui.c.ImGuiKey_Escape,
|
||||
.enter => cimgui.c.ImGuiKey_Enter,
|
||||
.tab => cimgui.c.ImGuiKey_Tab,
|
||||
.backspace => cimgui.c.ImGuiKey_Backspace,
|
||||
.print_screen => cimgui.c.ImGuiKey_PrintScreen,
|
||||
.pause => cimgui.c.ImGuiKey_Pause,
|
||||
|
||||
.f1 => cimgui.c.ImGuiKey_F1,
|
||||
.f2 => cimgui.c.ImGuiKey_F2,
|
||||
.f3 => cimgui.c.ImGuiKey_F3,
|
||||
.f4 => cimgui.c.ImGuiKey_F4,
|
||||
.f5 => cimgui.c.ImGuiKey_F5,
|
||||
.f6 => cimgui.c.ImGuiKey_F6,
|
||||
.f7 => cimgui.c.ImGuiKey_F7,
|
||||
.f8 => cimgui.c.ImGuiKey_F8,
|
||||
.f9 => cimgui.c.ImGuiKey_F9,
|
||||
.f10 => cimgui.c.ImGuiKey_F10,
|
||||
.f11 => cimgui.c.ImGuiKey_F11,
|
||||
.f12 => cimgui.c.ImGuiKey_F12,
|
||||
|
||||
.kp_0 => cimgui.c.ImGuiKey_Keypad0,
|
||||
.kp_1 => cimgui.c.ImGuiKey_Keypad1,
|
||||
.kp_2 => cimgui.c.ImGuiKey_Keypad2,
|
||||
.kp_3 => cimgui.c.ImGuiKey_Keypad3,
|
||||
.kp_4 => cimgui.c.ImGuiKey_Keypad4,
|
||||
.kp_5 => cimgui.c.ImGuiKey_Keypad5,
|
||||
.kp_6 => cimgui.c.ImGuiKey_Keypad6,
|
||||
.kp_7 => cimgui.c.ImGuiKey_Keypad7,
|
||||
.kp_8 => cimgui.c.ImGuiKey_Keypad8,
|
||||
.kp_9 => cimgui.c.ImGuiKey_Keypad9,
|
||||
.kp_decimal => cimgui.c.ImGuiKey_KeypadDecimal,
|
||||
.kp_divide => cimgui.c.ImGuiKey_KeypadDivide,
|
||||
.kp_multiply => cimgui.c.ImGuiKey_KeypadMultiply,
|
||||
.kp_subtract => cimgui.c.ImGuiKey_KeypadSubtract,
|
||||
.kp_add => cimgui.c.ImGuiKey_KeypadAdd,
|
||||
.kp_enter => cimgui.c.ImGuiKey_KeypadEnter,
|
||||
.kp_equal => cimgui.c.ImGuiKey_KeypadEqual,
|
||||
|
||||
.left_shift => cimgui.c.ImGuiKey_LeftShift,
|
||||
.left_control => cimgui.c.ImGuiKey_LeftCtrl,
|
||||
.left_alt => cimgui.c.ImGuiKey_LeftAlt,
|
||||
.left_super => cimgui.c.ImGuiKey_LeftSuper,
|
||||
.right_shift => cimgui.c.ImGuiKey_RightShift,
|
||||
.right_control => cimgui.c.ImGuiKey_RightCtrl,
|
||||
.right_alt => cimgui.c.ImGuiKey_RightAlt,
|
||||
.right_super => cimgui.c.ImGuiKey_RightSuper,
|
||||
|
||||
.invalid,
|
||||
.f13,
|
||||
.f14,
|
||||
.f15,
|
||||
.f16,
|
||||
.f17,
|
||||
.f18,
|
||||
.f19,
|
||||
.f20,
|
||||
.f21,
|
||||
.f22,
|
||||
.f23,
|
||||
.f24,
|
||||
.f25,
|
||||
=> null,
|
||||
};
|
||||
}
|
||||
|
Reference in New Issue
Block a user