gtk-ng: port the terminal inspector
This is a (relatively) straightforward port of the terminal inspector from the old GTK application runtime. It's split into three widgets. At the lowest level is a widget designed for showing a generic Dear ImGui application. Above that is a widget that embeds the ImGui widget and plumbs it into the core Inspector. At the top is a custom Window widget that embeds the Inspector widget. And then there's all the plumbing necessary to hook everything into the rest of Ghostty. In theory this design _should_ allow showing the Inspector in a split or a tab in the future, not just in a separate window. It should also make it easier to display _other_ Dear ImGui applications if they are ever needed.pull/8212/head
parent
57f1033198
commit
bd7177a924
|
|
@ -99,7 +99,6 @@ pub fn performIpc(
|
|||
}
|
||||
|
||||
/// Redraw the inspector for the given surface.
|
||||
pub fn redrawInspector(self: *App, surface: *Surface) void {
|
||||
_ = self;
|
||||
_ = surface;
|
||||
pub fn redrawInspector(_: *App, surface: *Surface) void {
|
||||
surface.redrawInspector();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,3 +95,8 @@ pub fn setClipboardString(
|
|||
pub fn defaultTermioEnv(self: *Self) !std.process.EnvMap {
|
||||
return try self.surface.defaultTermioEnv();
|
||||
}
|
||||
|
||||
/// Redraw the inspector for our surface.
|
||||
pub fn redrawInspector(self: *Self) void {
|
||||
self.surface.redrawInspector();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,9 @@ pub const blueprints: []const Blueprint = &.{
|
|||
.{ .major = 1, .minor = 5, .name = "tab" },
|
||||
.{ .major = 1, .minor = 5, .name = "window" },
|
||||
.{ .major = 1, .minor = 5, .name = "command-palette" },
|
||||
.{ .major = 1, .minor = 5, .name = "imgui-widget" },
|
||||
.{ .major = 1, .minor = 5, .name = "inspector-widget" },
|
||||
.{ .major = 1, .minor = 5, .name = "inspector-window" },
|
||||
};
|
||||
|
||||
/// CSS files in css_path
|
||||
|
|
|
|||
|
|
@ -561,6 +561,8 @@ pub const Application = extern struct {
|
|||
|
||||
.initial_size => return Action.initialSize(target, value),
|
||||
|
||||
.inspector => return Action.controlInspector(target, value),
|
||||
|
||||
.mouse_over_link => Action.mouseOverLink(target, value),
|
||||
.mouse_shape => Action.mouseShape(target, value),
|
||||
.mouse_visibility => Action.mouseVisibility(target, value),
|
||||
|
|
@ -620,13 +622,6 @@ pub const Application = extern struct {
|
|||
.toggle_split_zoom => return Action.toggleSplitZoom(target),
|
||||
.show_on_screen_keyboard => return Action.showOnScreenKeyboard(target),
|
||||
|
||||
// Unimplemented but todo on gtk-ng branch
|
||||
.inspector,
|
||||
=> {
|
||||
log.warn("unimplemented action={}", .{action});
|
||||
return false;
|
||||
},
|
||||
|
||||
// Unimplemented
|
||||
.secure_input,
|
||||
.close_all_windows,
|
||||
|
|
@ -2235,6 +2230,15 @@ const Action = struct {
|
|||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn controlInspector(target: apprt.Target, value: apprt.Action.Value(.inspector)) bool {
|
||||
switch (target) {
|
||||
.app => return false,
|
||||
.surface => |surface| {
|
||||
return surface.rt_surface.gobj().controlInspector(value);
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// This sets various GTK-related environment variables as necessary
|
||||
|
|
|
|||
|
|
@ -0,0 +1,492 @@
|
|||
const std = @import("std");
|
||||
|
||||
const adw = @import("adw");
|
||||
const gdk = @import("gdk");
|
||||
const gobject = @import("gobject");
|
||||
const gtk = @import("gtk");
|
||||
|
||||
const cimgui = @import("cimgui");
|
||||
const gl = @import("opengl");
|
||||
|
||||
const input = @import("../../../input.zig");
|
||||
const gresource = @import("../build/gresource.zig");
|
||||
|
||||
const key = @import("../key.zig");
|
||||
const Common = @import("../class.zig").Common;
|
||||
|
||||
const log = std.log.scoped(.gtk_ghostty_imgui_widget);
|
||||
|
||||
pub const RenderCallback = *const fn (?*anyopaque) void;
|
||||
pub const RenderUserdata = *anyopaque;
|
||||
|
||||
/// A widget for embedding a Dear ImGui application.
|
||||
pub const ImguiWidget = extern struct {
|
||||
const Self = @This();
|
||||
parent_instance: Parent,
|
||||
pub const Parent = adw.Bin;
|
||||
pub const getGObjectType = gobject.ext.defineClass(Self, .{
|
||||
.name = "GhosttyImguiWidget",
|
||||
.instanceInit = &init,
|
||||
.classInit = &Class.init,
|
||||
.parent_class = &Class.parent,
|
||||
.private = .{ .Type = Private, .offset = &Private.offset },
|
||||
});
|
||||
|
||||
pub const properties = struct {};
|
||||
|
||||
pub const signals = struct {};
|
||||
|
||||
const Private = struct {
|
||||
/// GL area where we display the Dear ImGui application.
|
||||
gl_area: *gtk.GLArea,
|
||||
|
||||
/// GTK input method context
|
||||
im_context: *gtk.IMMulticontext,
|
||||
|
||||
/// Dear ImGui context
|
||||
ig_context: ?*cimgui.c.ImGuiContext = null,
|
||||
|
||||
/// True if the the Dear ImGui OpenGL backend was initialized.
|
||||
ig_gl_backend_initialized: bool = false,
|
||||
|
||||
/// Our previous instant used to calculate delta time for animations.
|
||||
instant: ?std.time.Instant = null,
|
||||
|
||||
/// This is called every frame to populate the Dear ImGui frame.
|
||||
render_callback: ?RenderCallback = null,
|
||||
render_userdata: ?RenderUserdata = null,
|
||||
|
||||
pub var offset: c_int = 0;
|
||||
};
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Virtual Methods
|
||||
|
||||
fn init(self: *Self, _: *Class) callconv(.c) void {
|
||||
gtk.Widget.initTemplate(self.as(gtk.Widget));
|
||||
|
||||
const priv = self.private();
|
||||
|
||||
priv.ig_context = ig_context: {
|
||||
const ig_context = cimgui.c.igCreateContext(null) orelse {
|
||||
log.warn("unable to initialize Dear ImGui context", .{});
|
||||
break :ig_context null;
|
||||
};
|
||||
errdefer cimgui.c.igDestroyContext(ig_context);
|
||||
cimgui.c.igSetCurrentContext(ig_context);
|
||||
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
|
||||
io.BackendPlatformName = "ghostty_gtk";
|
||||
|
||||
break :ig_context ig_context;
|
||||
};
|
||||
}
|
||||
|
||||
fn dispose(self: *Self) callconv(.c) void {
|
||||
gtk.Widget.disposeTemplate(
|
||||
self.as(gtk.Widget),
|
||||
getGObjectType(),
|
||||
);
|
||||
|
||||
gobject.Object.virtual_methods.dispose.call(
|
||||
Class.parent,
|
||||
self.as(Parent),
|
||||
);
|
||||
}
|
||||
|
||||
fn finalize(self: *Self) callconv(.c) void {
|
||||
const priv = self.private();
|
||||
|
||||
// If the the Dear ImGui OpenGL backend was never initialized then we
|
||||
// need to destroy the Dear ImGui context manually here. If it _was_
|
||||
// initialized cleanup will be handled when the GLArea is unrealized.
|
||||
if (!priv.ig_gl_backend_initialized) {
|
||||
if (priv.ig_context) |ig_context| {
|
||||
cimgui.c.igDestroyContext(ig_context);
|
||||
priv.ig_context = null;
|
||||
}
|
||||
}
|
||||
|
||||
gobject.Object.virtual_methods.finalize.call(
|
||||
Class.parent,
|
||||
self.as(Parent),
|
||||
);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Public methods
|
||||
|
||||
pub fn new() *Self {
|
||||
return gobject.ext.newInstance(Self, .{});
|
||||
}
|
||||
|
||||
/// Use to setup the Dear ImGui application.
|
||||
pub fn setup(self: *Self, callback: *const fn () void) void {
|
||||
self.setCurrentContext() catch return;
|
||||
callback();
|
||||
}
|
||||
|
||||
/// Set the callback used to render every frame.
|
||||
pub fn setRenderCallback(
|
||||
self: *Self,
|
||||
callback: ?RenderCallback,
|
||||
userdata: ?RenderUserdata,
|
||||
) void {
|
||||
const priv = self.private();
|
||||
priv.render_callback = callback;
|
||||
priv.render_userdata = userdata;
|
||||
}
|
||||
|
||||
/// This should be called anytime the underlying data for the UI changes
|
||||
/// so that the UI can be refreshed.
|
||||
pub fn queueRender(self: *ImguiWidget) void {
|
||||
const priv = self.private();
|
||||
priv.gl_area.queueRender();
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Private Methods
|
||||
|
||||
/// Set our imgui context to be current, or return an error.
|
||||
fn setCurrentContext(self: *Self) error{ContextNotInitialized}!void {
|
||||
const priv = self.private();
|
||||
const ig_context = priv.ig_context orelse {
|
||||
log.warn("Dear ImGui context not initialized", .{});
|
||||
return error.ContextNotInitialized;
|
||||
};
|
||||
cimgui.c.igSetCurrentContext(ig_context);
|
||||
}
|
||||
|
||||
/// Initialize the frame. Expects that the context is already current.
|
||||
fn newFrame(self: *Self) void {
|
||||
// If we can't determine the time since the last frame we default to
|
||||
// 1/60th of a second.
|
||||
const default_delta_time = 1 / 60;
|
||||
|
||||
const priv = self.private();
|
||||
|
||||
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
|
||||
|
||||
// Determine our delta time
|
||||
const now = std.time.Instant.now() catch unreachable;
|
||||
io.DeltaTime = if (priv.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 default_delta_time;
|
||||
|
||||
priv.instant = now;
|
||||
}
|
||||
|
||||
/// Handle key press/release events.
|
||||
fn keyEvent(
|
||||
self: *ImguiWidget,
|
||||
action: input.Action,
|
||||
ec_key: *gtk.EventControllerKey,
|
||||
keyval: c_uint,
|
||||
_: c_uint,
|
||||
gtk_mods: gdk.ModifierType,
|
||||
) bool {
|
||||
self.queueRender();
|
||||
|
||||
self.setCurrentContext() catch return false;
|
||||
|
||||
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
|
||||
|
||||
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 (inputkey.imguiKey()) |imgui_key| {
|
||||
cimgui.c.ImGuiIO_AddKeyEvent(io, imgui_key, action == .press);
|
||||
}
|
||||
}
|
||||
|
||||
// Try to process the event as text
|
||||
if (ec_key.as(gtk.EventController).getCurrentEvent()) |event| {
|
||||
const priv = self.private();
|
||||
_ = priv.im_context.as(gtk.IMContext).filterKeypress(event);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Translate a GTK mouse button to a Dear ImGui mouse button.
|
||||
fn translateMouseButton(button: c_uint) ?c_int {
|
||||
return switch (button) {
|
||||
1 => cimgui.c.ImGuiMouseButton_Left,
|
||||
2 => cimgui.c.ImGuiMouseButton_Middle,
|
||||
3 => cimgui.c.ImGuiMouseButton_Right,
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
||||
/// Get the scale factor that the display is operating at.
|
||||
fn getScaleFactor(self: *Self) f64 {
|
||||
const priv = self.private();
|
||||
return @floatFromInt(priv.gl_area.as(gtk.Widget).getScaleFactor());
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Properties
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Signal Handlers
|
||||
|
||||
fn glAreaRealize(_: *gtk.GLArea, self: *Self) callconv(.c) void {
|
||||
const priv = self.private();
|
||||
|
||||
priv.gl_area.makeCurrent();
|
||||
if (priv.gl_area.getError()) |err| {
|
||||
log.err("GLArea for Dear ImGui widget failed to realize: {s}", .{err.f_message orelse "(unknown)"});
|
||||
return;
|
||||
}
|
||||
|
||||
self.setCurrentContext() catch return;
|
||||
|
||||
// realize means that our OpenGL context is ready, so we can now
|
||||
// initialize the ImgUI OpenGL backend for our context.
|
||||
_ = cimgui.ImGui_ImplOpenGL3_Init(null);
|
||||
|
||||
priv.ig_gl_backend_initialized = true;
|
||||
}
|
||||
|
||||
/// Handle a request to unrealize the GLArea
|
||||
fn glAreaUnrealize(_: *gtk.GLArea, self: *ImguiWidget) callconv(.c) void {
|
||||
self.setCurrentContext() catch return;
|
||||
cimgui.ImGui_ImplOpenGL3_Shutdown();
|
||||
}
|
||||
|
||||
/// Handle a request to resize the GLArea
|
||||
fn glAreaResize(area: *gtk.GLArea, width: c_int, height: c_int, self: *Self) callconv(.c) void {
|
||||
self.setCurrentContext() catch return;
|
||||
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
|
||||
const scale_factor = area.as(gtk.Widget).getScaleFactor();
|
||||
|
||||
// Our display size is always unscaled. We'll do the scaling in the
|
||||
// style instead. This creates crisper looking fonts.
|
||||
io.DisplaySize = .{ .x = @floatFromInt(width), .y = @floatFromInt(height) };
|
||||
io.DisplayFramebufferScale = .{ .x = 1, .y = 1 };
|
||||
|
||||
// Setup a new style and scale it appropriately.
|
||||
const style = cimgui.c.ImGuiStyle_ImGuiStyle();
|
||||
defer cimgui.c.ImGuiStyle_destroy(style);
|
||||
cimgui.c.ImGuiStyle_ScaleAllSizes(style, @floatFromInt(scale_factor));
|
||||
const active_style = cimgui.c.igGetStyle();
|
||||
active_style.* = style.*;
|
||||
}
|
||||
|
||||
/// Handle a request to render the contents of our GLArea
|
||||
fn glAreaRender(_: *gtk.GLArea, _: *gdk.GLContext, self: *Self) callconv(.c) c_int {
|
||||
self.setCurrentContext() catch return @intFromBool(false);
|
||||
|
||||
// Setup our frame. We render twice because some ImGui behaviors
|
||||
// take multiple renders to process. I don't know how to make this
|
||||
// more efficient.
|
||||
for (0..2) |_| {
|
||||
cimgui.ImGui_ImplOpenGL3_NewFrame();
|
||||
self.newFrame();
|
||||
cimgui.c.igNewFrame();
|
||||
|
||||
// Use the callback to draw the UI.
|
||||
const priv = self.private();
|
||||
if (priv.render_callback) |cb| cb(priv.render_userdata);
|
||||
|
||||
// Render
|
||||
cimgui.c.igRender();
|
||||
}
|
||||
|
||||
// OpenGL final render
|
||||
gl.clearColor(0x28 / 0xFF, 0x2C / 0xFF, 0x34 / 0xFF, 1.0);
|
||||
gl.clear(gl.c.GL_COLOR_BUFFER_BIT);
|
||||
cimgui.ImGui_ImplOpenGL3_RenderDrawData(cimgui.c.igGetDrawData());
|
||||
|
||||
return @intFromBool(true);
|
||||
}
|
||||
|
||||
fn ecFocusEnter(_: *gtk.EventControllerFocus, self: *Self) callconv(.c) void {
|
||||
self.queueRender();
|
||||
self.setCurrentContext() catch return;
|
||||
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
|
||||
cimgui.c.ImGuiIO_AddFocusEvent(io, true);
|
||||
}
|
||||
|
||||
fn ecFocusLeave(_: *gtk.EventControllerFocus, self: *Self) callconv(.c) void {
|
||||
self.queueRender();
|
||||
self.setCurrentContext() catch return;
|
||||
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
|
||||
cimgui.c.ImGuiIO_AddFocusEvent(io, false);
|
||||
}
|
||||
|
||||
fn ecKeyPressed(
|
||||
ec_key: *gtk.EventControllerKey,
|
||||
keyval: c_uint,
|
||||
keycode: c_uint,
|
||||
gtk_mods: gdk.ModifierType,
|
||||
self: *ImguiWidget,
|
||||
) callconv(.c) c_int {
|
||||
return @intFromBool(self.keyEvent(
|
||||
.press,
|
||||
ec_key,
|
||||
keyval,
|
||||
keycode,
|
||||
gtk_mods,
|
||||
));
|
||||
}
|
||||
|
||||
fn ecKeyReleased(
|
||||
ec_key: *gtk.EventControllerKey,
|
||||
keyval: c_uint,
|
||||
keycode: c_uint,
|
||||
gtk_mods: gdk.ModifierType,
|
||||
self: *ImguiWidget,
|
||||
) callconv(.c) void {
|
||||
_ = self.keyEvent(
|
||||
.release,
|
||||
ec_key,
|
||||
keyval,
|
||||
keycode,
|
||||
gtk_mods,
|
||||
);
|
||||
}
|
||||
|
||||
fn ecMousePressed(
|
||||
gesture: *gtk.GestureClick,
|
||||
_: c_int,
|
||||
_: f64,
|
||||
_: f64,
|
||||
self: *ImguiWidget,
|
||||
) callconv(.c) void {
|
||||
self.queueRender();
|
||||
self.setCurrentContext() catch return;
|
||||
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
|
||||
const gdk_button = gesture.as(gtk.GestureSingle).getCurrentButton();
|
||||
if (translateMouseButton(gdk_button)) |button| {
|
||||
cimgui.c.ImGuiIO_AddMouseButtonEvent(io, button, true);
|
||||
}
|
||||
}
|
||||
|
||||
fn ecMouseReleased(
|
||||
gesture: *gtk.GestureClick,
|
||||
_: c_int,
|
||||
_: f64,
|
||||
_: f64,
|
||||
self: *ImguiWidget,
|
||||
) callconv(.c) void {
|
||||
self.queueRender();
|
||||
self.setCurrentContext() catch return;
|
||||
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
|
||||
const gdk_button = gesture.as(gtk.GestureSingle).getCurrentButton();
|
||||
if (translateMouseButton(gdk_button)) |button| {
|
||||
cimgui.c.ImGuiIO_AddMouseButtonEvent(io, button, false);
|
||||
}
|
||||
}
|
||||
|
||||
fn ecMouseMotion(
|
||||
_: *gtk.EventControllerMotion,
|
||||
x: f64,
|
||||
y: f64,
|
||||
self: *ImguiWidget,
|
||||
) callconv(.c) void {
|
||||
self.queueRender();
|
||||
self.setCurrentContext() catch return;
|
||||
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
|
||||
const scale_factor = self.getScaleFactor();
|
||||
cimgui.c.ImGuiIO_AddMousePosEvent(
|
||||
io,
|
||||
@floatCast(x * scale_factor),
|
||||
@floatCast(y * scale_factor),
|
||||
);
|
||||
}
|
||||
|
||||
fn ecMouseScroll(
|
||||
_: *gtk.EventControllerScroll,
|
||||
x: f64,
|
||||
y: f64,
|
||||
self: *ImguiWidget,
|
||||
) callconv(.c) c_int {
|
||||
self.queueRender();
|
||||
self.setCurrentContext() catch return @intFromBool(false);
|
||||
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
|
||||
cimgui.c.ImGuiIO_AddMouseWheelEvent(
|
||||
io,
|
||||
@floatCast(x),
|
||||
@floatCast(-y),
|
||||
);
|
||||
return @intFromBool(true);
|
||||
}
|
||||
|
||||
fn imCommit(
|
||||
_: *gtk.IMMulticontext,
|
||||
bytes: [*:0]u8,
|
||||
self: *ImguiWidget,
|
||||
) callconv(.c) void {
|
||||
self.queueRender();
|
||||
self.setCurrentContext() catch return;
|
||||
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
|
||||
cimgui.c.ImGuiIO_AddInputCharactersUTF8(io, bytes);
|
||||
}
|
||||
|
||||
const C = Common(Self, Private);
|
||||
pub const as = C.as;
|
||||
pub const ref = C.ref;
|
||||
pub const refSink = C.refSink;
|
||||
pub const unref = C.unref;
|
||||
const private = C.private;
|
||||
|
||||
pub const Class = extern struct {
|
||||
parent_class: Parent.Class,
|
||||
var parent: *Parent.Class = undefined;
|
||||
pub const Instance = Self;
|
||||
|
||||
fn init(class: *Class) callconv(.c) void {
|
||||
gtk.Widget.Class.setTemplateFromResource(
|
||||
class.as(gtk.Widget.Class),
|
||||
comptime gresource.blueprint(.{
|
||||
.major = 1,
|
||||
.minor = 5,
|
||||
.name = "imgui-widget",
|
||||
}),
|
||||
);
|
||||
|
||||
// Bindings
|
||||
class.bindTemplateChildPrivate("gl_area", .{});
|
||||
class.bindTemplateChildPrivate("im_context", .{});
|
||||
|
||||
// Template Callbacks
|
||||
class.bindTemplateCallback("realize", &glAreaRealize);
|
||||
class.bindTemplateCallback("unrealize", &glAreaUnrealize);
|
||||
class.bindTemplateCallback("resize", &glAreaResize);
|
||||
class.bindTemplateCallback("render", &glAreaRender);
|
||||
|
||||
class.bindTemplateCallback("focus_enter", &ecFocusEnter);
|
||||
class.bindTemplateCallback("focus_leave", &ecFocusLeave);
|
||||
|
||||
class.bindTemplateCallback("key_pressed", &ecKeyPressed);
|
||||
class.bindTemplateCallback("key_released", &ecKeyReleased);
|
||||
|
||||
class.bindTemplateCallback("mouse_pressed", &ecMousePressed);
|
||||
class.bindTemplateCallback("mouse_released", &ecMouseReleased);
|
||||
class.bindTemplateCallback("mouse_motion", &ecMouseMotion);
|
||||
|
||||
class.bindTemplateCallback("scroll", &ecMouseScroll);
|
||||
|
||||
class.bindTemplateCallback("im_commit", &imCommit);
|
||||
|
||||
// Properties
|
||||
|
||||
// Signals
|
||||
|
||||
// Virtual methods
|
||||
gobject.Object.virtual_methods.dispose.implement(class, &dispose);
|
||||
gobject.Object.virtual_methods.finalize.implement(class, &finalize);
|
||||
}
|
||||
|
||||
pub const as = C.Class.as;
|
||||
pub const bindTemplateChildPrivate = C.Class.bindTemplateChildPrivate;
|
||||
pub const bindTemplateCallback = C.Class.bindTemplateCallback;
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,210 @@
|
|||
const std = @import("std");
|
||||
|
||||
const adw = @import("adw");
|
||||
const gobject = @import("gobject");
|
||||
const gtk = @import("gtk");
|
||||
|
||||
const gresource = @import("../build/gresource.zig");
|
||||
const Inspector = @import("../../../inspector/Inspector.zig");
|
||||
|
||||
const Common = @import("../class.zig").Common;
|
||||
const WeakRef = @import("../weak_ref.zig").WeakRef;
|
||||
const Surface = @import("surface.zig").Surface;
|
||||
const ImguiWidget = @import("imgui_widget.zig").ImguiWidget;
|
||||
|
||||
const log = std.log.scoped(.gtk_ghostty_inspector_widget);
|
||||
|
||||
/// Widget for displaying the Ghostty inspector.
|
||||
pub const InspectorWidget = extern struct {
|
||||
const Self = @This();
|
||||
parent_instance: Parent,
|
||||
pub const Parent = adw.Bin;
|
||||
pub const getGObjectType = gobject.ext.defineClass(Self, .{
|
||||
.name = "GhosttyInspectorWidget",
|
||||
.instanceInit = &init,
|
||||
.classInit = &Class.init,
|
||||
.parent_class = &Class.parent,
|
||||
.private = .{ .Type = Private, .offset = &Private.offset },
|
||||
});
|
||||
|
||||
pub const properties = struct {
|
||||
pub const surface = struct {
|
||||
pub const name = "surface";
|
||||
const impl = gobject.ext.defineProperty(
|
||||
name,
|
||||
Self,
|
||||
?*Surface,
|
||||
.{
|
||||
.accessor = gobject.ext.typedAccessor(Self, ?*Surface, .{
|
||||
.getter = getSurface,
|
||||
.getter_transfer = .full,
|
||||
.setter = setSurface,
|
||||
.setter_transfer = .none,
|
||||
}),
|
||||
},
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
pub const signals = struct {};
|
||||
|
||||
const Private = struct {
|
||||
/// The surface that we are attached to
|
||||
surface: WeakRef(Surface) = .empty,
|
||||
|
||||
/// The embedded Dear ImGui widget.
|
||||
imgui_widget: *ImguiWidget,
|
||||
|
||||
pub var offset: c_int = 0;
|
||||
};
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Virtual Methods
|
||||
|
||||
fn init(self: *Self, _: *Class) callconv(.c) void {
|
||||
gtk.Widget.initTemplate(self.as(gtk.Widget));
|
||||
|
||||
const priv = self.private();
|
||||
priv.imgui_widget.setup(Inspector.setup);
|
||||
priv.imgui_widget.setRenderCallback(imguiRender, self);
|
||||
}
|
||||
|
||||
fn dispose(self: *Self) callconv(.c) void {
|
||||
const priv = self.private();
|
||||
|
||||
deactivate: {
|
||||
const surface = priv.surface.get() orelse break :deactivate;
|
||||
defer surface.unref();
|
||||
|
||||
const core_surface = surface.core() orelse break :deactivate;
|
||||
core_surface.deactivateInspector();
|
||||
}
|
||||
|
||||
gtk.Widget.disposeTemplate(
|
||||
self.as(gtk.Widget),
|
||||
getGObjectType(),
|
||||
);
|
||||
|
||||
gobject.Object.virtual_methods.dispose.call(
|
||||
Class.parent,
|
||||
self.as(Parent),
|
||||
);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Public methods
|
||||
|
||||
pub fn new(surface: *Surface) *Self {
|
||||
return gobject.ext.newInstance(Self, .{
|
||||
.surface = surface,
|
||||
});
|
||||
}
|
||||
|
||||
/// Queue a render of the Dear ImGui widget.
|
||||
pub fn queueRender(self: *Self) void {
|
||||
const priv = self.private();
|
||||
priv.imgui_widget.queueRender();
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Private Methods
|
||||
|
||||
/// This is the callback from the embedded Dear ImGui widget that is called
|
||||
/// to do the actual drawing.
|
||||
fn imguiRender(ud: ?*anyopaque) void {
|
||||
const self: *Self = @ptrCast(@alignCast(ud orelse return));
|
||||
const priv = self.private();
|
||||
const surface = priv.surface.get() orelse return;
|
||||
defer surface.unref();
|
||||
const core_surface = surface.core() orelse return;
|
||||
const inspector = core_surface.inspector orelse return;
|
||||
inspector.render();
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Properties
|
||||
|
||||
fn getSurface(self: *Self) ?*Surface {
|
||||
const priv = self.private();
|
||||
return priv.surface.get();
|
||||
}
|
||||
|
||||
fn setSurface(self: *Self, newvalue_: ?*Surface) void {
|
||||
const priv = self.private();
|
||||
|
||||
if (priv.surface.get()) |oldvalue| oldvalue: {
|
||||
defer oldvalue.unref();
|
||||
|
||||
// We don't need to do anything if we're just setting the same surface.
|
||||
if (newvalue_) |newvalue| if (newvalue == oldvalue) return;
|
||||
|
||||
// Deactivate the inspector on the old surface.
|
||||
const core_surface = oldvalue.core() orelse break :oldvalue;
|
||||
core_surface.deactivateInspector();
|
||||
}
|
||||
|
||||
const newvalue = newvalue_ orelse {
|
||||
priv.surface.set(null);
|
||||
return;
|
||||
};
|
||||
|
||||
const core_surface = newvalue.core() orelse {
|
||||
priv.surface.set(null);
|
||||
return;
|
||||
};
|
||||
|
||||
// Activate the inspector on the new surface.
|
||||
core_surface.activateInspector() catch |err| {
|
||||
log.err("failed to activate inspector err={}", .{err});
|
||||
};
|
||||
|
||||
priv.surface.set(newvalue);
|
||||
|
||||
self.queueRender();
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Signal Handlers
|
||||
|
||||
const C = Common(Self, Private);
|
||||
pub const as = C.as;
|
||||
pub const ref = C.ref;
|
||||
pub const refSink = C.refSink;
|
||||
pub const unref = C.unref;
|
||||
const private = C.private;
|
||||
|
||||
pub const Class = extern struct {
|
||||
parent_class: Parent.Class,
|
||||
var parent: *Parent.Class = undefined;
|
||||
pub const Instance = Self;
|
||||
|
||||
fn init(class: *Class) callconv(.c) void {
|
||||
gobject.ext.ensureType(ImguiWidget);
|
||||
gtk.Widget.Class.setTemplateFromResource(
|
||||
class.as(gtk.Widget.Class),
|
||||
comptime gresource.blueprint(.{
|
||||
.major = 1,
|
||||
.minor = 5,
|
||||
.name = "inspector-widget",
|
||||
}),
|
||||
);
|
||||
|
||||
// Bindings
|
||||
class.bindTemplateChildPrivate("imgui_widget", .{});
|
||||
|
||||
// Properties
|
||||
gobject.ext.registerProperties(class, &.{
|
||||
properties.surface.impl,
|
||||
});
|
||||
|
||||
// Signals
|
||||
|
||||
// Virtual methods
|
||||
gobject.Object.virtual_methods.dispose.implement(class, &dispose);
|
||||
}
|
||||
|
||||
pub const as = C.Class.as;
|
||||
pub const bindTemplateChildPrivate = C.Class.bindTemplateChildPrivate;
|
||||
pub const bindTemplateCallback = C.Class.bindTemplateCallback;
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,225 @@
|
|||
const std = @import("std");
|
||||
const build_config = @import("../../../build_config.zig");
|
||||
|
||||
const adw = @import("adw");
|
||||
const gdk = @import("gdk");
|
||||
const gobject = @import("gobject");
|
||||
const gtk = @import("gtk");
|
||||
|
||||
const gresource = @import("../build/gresource.zig");
|
||||
|
||||
const key = @import("../key.zig");
|
||||
const Common = @import("../class.zig").Common;
|
||||
const Application = @import("application.zig").Application;
|
||||
const Surface = @import("surface.zig").Surface;
|
||||
const DebugWarning = @import("debug_warning.zig").DebugWarning;
|
||||
const InspectorWidget = @import("inspector_widget.zig").InspectorWidget;
|
||||
const WeakRef = @import("../weak_ref.zig").WeakRef;
|
||||
|
||||
const log = std.log.scoped(.gtk_ghostty_inspector_window);
|
||||
|
||||
/// Window for displaying the Ghostty inspector.
|
||||
pub const InspectorWindow = extern struct {
|
||||
const Self = @This();
|
||||
parent_instance: Parent,
|
||||
pub const Parent = adw.ApplicationWindow;
|
||||
pub const getGObjectType = gobject.ext.defineClass(Self, .{
|
||||
.name = "GhosttyInspectorWindow",
|
||||
.instanceInit = &init,
|
||||
.classInit = &Class.init,
|
||||
.parent_class = &Class.parent,
|
||||
.private = .{ .Type = Private, .offset = &Private.offset },
|
||||
});
|
||||
|
||||
pub const properties = struct {
|
||||
pub const surface = struct {
|
||||
pub const name = "surface";
|
||||
const impl = gobject.ext.defineProperty(
|
||||
name,
|
||||
Self,
|
||||
?*Surface,
|
||||
.{
|
||||
.accessor = gobject.ext.typedAccessor(Self, ?*Surface, .{
|
||||
.getter = getSurface,
|
||||
.getter_transfer = .full,
|
||||
.setter = setSurface,
|
||||
.setter_transfer = .none,
|
||||
}),
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
pub const debug = struct {
|
||||
pub const name = "debug";
|
||||
const impl = gobject.ext.defineProperty(
|
||||
name,
|
||||
Self,
|
||||
bool,
|
||||
.{
|
||||
.default = build_config.is_debug,
|
||||
.accessor = gobject.ext.typedAccessor(Self, bool, .{
|
||||
.getter = struct {
|
||||
pub fn getter(_: *Self) bool {
|
||||
return build_config.is_debug;
|
||||
}
|
||||
}.getter,
|
||||
}),
|
||||
},
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
pub const signals = struct {};
|
||||
|
||||
const Private = struct {
|
||||
/// The surface that we are attached to
|
||||
surface: WeakRef(Surface) = .empty,
|
||||
|
||||
/// The embedded inspector widget.
|
||||
inspector_widget: *InspectorWidget,
|
||||
|
||||
pub var offset: c_int = 0;
|
||||
};
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Virtual Methods
|
||||
|
||||
fn init(self: *Self, _: *Class) callconv(.c) void {
|
||||
gtk.Widget.initTemplate(self.as(gtk.Widget));
|
||||
|
||||
// Add our dev CSS class if we're in debug mode.
|
||||
if (comptime build_config.is_debug) {
|
||||
self.as(gtk.Widget).addCssClass("devel");
|
||||
}
|
||||
|
||||
// Set our window icon. We can't set this in the blueprint file
|
||||
// because its dependent on the build config.
|
||||
self.as(gtk.Window).setIconName(build_config.bundle_id);
|
||||
}
|
||||
|
||||
fn dispose(self: *Self) callconv(.c) void {
|
||||
gtk.Widget.disposeTemplate(
|
||||
self.as(gtk.Widget),
|
||||
getGObjectType(),
|
||||
);
|
||||
|
||||
gobject.Object.virtual_methods.dispose.call(
|
||||
Class.parent,
|
||||
self.as(Parent),
|
||||
);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Public methods
|
||||
|
||||
pub fn new(surface: *Surface) *Self {
|
||||
const self = gobject.ext.newInstance(Self, .{
|
||||
.surface = surface,
|
||||
});
|
||||
|
||||
// Bump the ref so that we aren't immediately closed.
|
||||
return self.ref();
|
||||
}
|
||||
|
||||
/// Present the window.
|
||||
pub fn present(self: *Self) void {
|
||||
self.as(gtk.Window).present();
|
||||
}
|
||||
|
||||
/// Queue a render of the embedded widget.
|
||||
pub fn queueRender(self: *Self) void {
|
||||
const priv = self.private();
|
||||
priv.inspector_widget.queueRender();
|
||||
}
|
||||
|
||||
/// The surface we are connected to is going away, shut ourselves down.
|
||||
pub fn shutdown(self: *Self) void {
|
||||
const priv = self.private();
|
||||
priv.surface.set(null);
|
||||
self.as(gobject.Object).notifyByPspec(properties.surface.impl.param_spec);
|
||||
self.as(gtk.Window).close();
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Private Methods
|
||||
|
||||
fn isFullscreen(self: *Self) bool {
|
||||
return self.as(gtk.Window).isFullscreen() != 0;
|
||||
}
|
||||
|
||||
fn isMaximized(self: *Self) bool {
|
||||
return self.as(gtk.Window).isMaximized() != 0;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Properties
|
||||
|
||||
fn getSurface(self: *Self) ?*Surface {
|
||||
const priv = self.private();
|
||||
return priv.surface.get();
|
||||
}
|
||||
|
||||
fn setSurface(self: *Self, newvalue: ?*Surface) void {
|
||||
const priv = self.private();
|
||||
priv.surface.set(newvalue);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Signal Handlers
|
||||
|
||||
/// The user has clicked on the close button.
|
||||
fn closeRequest(_: *gtk.Window, self: *Self) callconv(.c) c_int {
|
||||
const priv = self.private();
|
||||
priv.surface.set(null);
|
||||
self.as(gobject.Object).notifyByPspec(properties.surface.impl.param_spec);
|
||||
self.as(gtk.Window).destroy();
|
||||
return @intFromBool(false);
|
||||
}
|
||||
|
||||
const C = Common(Self, Private);
|
||||
pub const as = C.as;
|
||||
pub const ref = C.ref;
|
||||
pub const refSink = C.refSink;
|
||||
pub const unref = C.unref;
|
||||
const private = C.private;
|
||||
|
||||
pub const Class = extern struct {
|
||||
parent_class: Parent.Class,
|
||||
var parent: *Parent.Class = undefined;
|
||||
pub const Instance = Self;
|
||||
|
||||
fn init(class: *Class) callconv(.c) void {
|
||||
gobject.ext.ensureType(DebugWarning);
|
||||
gobject.ext.ensureType(InspectorWidget);
|
||||
gtk.Widget.Class.setTemplateFromResource(
|
||||
class.as(gtk.Widget.Class),
|
||||
comptime gresource.blueprint(.{
|
||||
.major = 1,
|
||||
.minor = 5,
|
||||
.name = "inspector-window",
|
||||
}),
|
||||
);
|
||||
|
||||
// Template Bindings
|
||||
class.bindTemplateChildPrivate("inspector_widget", .{});
|
||||
|
||||
// Template Callbacks
|
||||
class.bindTemplateCallback("close_request", &closeRequest);
|
||||
|
||||
// Properties
|
||||
gobject.ext.registerProperties(class, &.{
|
||||
properties.surface.impl,
|
||||
properties.debug.impl,
|
||||
});
|
||||
|
||||
// Signals
|
||||
|
||||
// Virtual methods
|
||||
gobject.Object.virtual_methods.dispose.implement(class, &dispose);
|
||||
}
|
||||
|
||||
pub const as = C.Class.as;
|
||||
pub const bindTemplateChildPrivate = C.Class.bindTemplateChildPrivate;
|
||||
pub const bindTemplateCallback = C.Class.bindTemplateCallback;
|
||||
};
|
||||
};
|
||||
|
|
@ -29,6 +29,8 @@ const ChildExited = @import("surface_child_exited.zig").SurfaceChildExited;
|
|||
const ClipboardConfirmationDialog = @import("clipboard_confirmation_dialog.zig").ClipboardConfirmationDialog;
|
||||
const TitleDialog = @import("surface_title_dialog.zig").SurfaceTitleDialog;
|
||||
const Window = @import("window.zig").Window;
|
||||
const WeakRef = @import("../weak_ref.zig").WeakRef;
|
||||
const InspectorWindow = @import("inspector_window.zig").InspectorWindow;
|
||||
|
||||
const log = std.log.scoped(.gtk_ghostty_surface);
|
||||
|
||||
|
|
@ -470,6 +472,9 @@ pub const Surface = extern struct {
|
|||
// false by a parent widget.
|
||||
bell_ringing: bool = false,
|
||||
|
||||
/// A weak reference to an inspector window.
|
||||
inspector: WeakRef(InspectorWindow) = .empty,
|
||||
|
||||
// Template binds
|
||||
child_exited_overlay: *ChildExited,
|
||||
context_menu: *gtk.PopoverMenu,
|
||||
|
|
@ -573,6 +578,58 @@ pub const Surface = extern struct {
|
|||
return self.as(gtk.Widget).activateAction("win.toggle-command-palette", null) != 0;
|
||||
}
|
||||
|
||||
pub fn toggleInspector(self: *Self) bool {
|
||||
const priv = self.private();
|
||||
if (priv.inspector.get()) |inspector| {
|
||||
defer inspector.unref();
|
||||
inspector.shutdown();
|
||||
priv.inspector.set(null);
|
||||
return true;
|
||||
}
|
||||
const inspector = InspectorWindow.new(self);
|
||||
defer inspector.unref();
|
||||
priv.inspector.set(inspector);
|
||||
inspector.present();
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn showInspector(self: *Self) bool {
|
||||
const priv = self.private();
|
||||
const inspector = priv.inspector.get() orelse inspector: {
|
||||
const inspector = InspectorWindow.new(self);
|
||||
priv.inspector.set(inspector);
|
||||
break :inspector inspector;
|
||||
};
|
||||
defer inspector.unref();
|
||||
inspector.present();
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn hideInspector(self: *Self) bool {
|
||||
const priv = self.private();
|
||||
if (priv.inspector.get()) |inspector| {
|
||||
defer inspector.unref();
|
||||
inspector.shutdown();
|
||||
priv.inspector.set(null);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn controlInspector(self: *Self, value: apprt.Action.Value(.inspector)) bool {
|
||||
switch (value) {
|
||||
.toggle => return self.toggleInspector(),
|
||||
.show => return self.showInspector(),
|
||||
.hide => return self.hideInspector(),
|
||||
}
|
||||
}
|
||||
/// Redraw our inspector, if there is one associated with this surface.
|
||||
pub fn redrawInspector(self: *Self) void {
|
||||
const priv = self.private();
|
||||
const inspector = priv.inspector.get() orelse return;
|
||||
defer inspector.unref();
|
||||
inspector.queueRender();
|
||||
}
|
||||
|
||||
pub fn showOnScreenKeyboard(self: *Self, event: ?*gdk.Event) bool {
|
||||
const priv = self.private();
|
||||
return priv.im_context.as(gtk.IMContext).activateOsk(event) != 0;
|
||||
|
|
@ -1287,10 +1344,12 @@ pub const Surface = extern struct {
|
|||
|
||||
fn dispose(self: *Self) callconv(.c) void {
|
||||
const priv = self.private();
|
||||
|
||||
if (priv.config) |v| {
|
||||
v.unref();
|
||||
priv.config = null;
|
||||
}
|
||||
|
||||
if (priv.progress_bar_timer) |timer| {
|
||||
if (glib.Source.remove(timer) == 0) {
|
||||
log.warn("unable to remove progress bar timer", .{});
|
||||
|
|
@ -1298,6 +1357,11 @@ pub const Surface = extern struct {
|
|||
priv.progress_bar_timer = null;
|
||||
}
|
||||
|
||||
if (priv.inspector.get()) |inspector| {
|
||||
defer inspector.unref();
|
||||
inspector.shutdown();
|
||||
}
|
||||
|
||||
gtk.Widget.disposeTemplate(
|
||||
self.as(gtk.Widget),
|
||||
getGObjectType(),
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ const Surface = @import("surface.zig").Surface;
|
|||
const Tab = @import("tab.zig").Tab;
|
||||
const DebugWarning = @import("debug_warning.zig").DebugWarning;
|
||||
const CommandPalette = @import("command_palette.zig").CommandPalette;
|
||||
const InspectorWindow = @import("inspector_window.zig").InspectorWindow;
|
||||
const WeakRef = @import("../weak_ref.zig").WeakRef;
|
||||
|
||||
const log = std.log.scoped(.gtk_ghostty_window);
|
||||
|
|
@ -347,6 +348,7 @@ pub const Window = extern struct {
|
|||
.{ "clear", actionClear, null },
|
||||
// TODO: accept the surface that toggled the command palette
|
||||
.{ "toggle-command-palette", actionToggleCommandPalette, null },
|
||||
.{ "toggle-inspector", actionToggleInspector, null },
|
||||
};
|
||||
|
||||
const action_map = self.as(gio.ActionMap);
|
||||
|
|
@ -1820,6 +1822,23 @@ pub const Window = extern struct {
|
|||
self.toggleCommandPalette();
|
||||
}
|
||||
|
||||
/// Toggle the Ghostty inspector for the active surface.
|
||||
fn toggleInspector(self: *Self) void {
|
||||
const surface = self.getActiveSurface() orelse return;
|
||||
_ = surface.toggleInspector();
|
||||
}
|
||||
|
||||
/// React to a GTK action requesting that the Ghostty inspector be toggled.
|
||||
fn actionToggleInspector(
|
||||
_: *gio.SimpleAction,
|
||||
_: ?*glib.Variant,
|
||||
self: *Window,
|
||||
) callconv(.c) void {
|
||||
// TODO: accept the surface that toggled the command palette as a
|
||||
// parameter
|
||||
self.toggleInspector();
|
||||
}
|
||||
|
||||
const C = Common(Self, Private);
|
||||
pub const as = C.as;
|
||||
pub const ref = C.ref;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
using Gtk 4.0;
|
||||
using Adw 1;
|
||||
|
||||
template $GhosttyImguiWidget: Adw.Bin {
|
||||
styles [
|
||||
"imgui",
|
||||
]
|
||||
|
||||
Adw.Bin {
|
||||
Gtk.GLArea gl_area {
|
||||
auto-render: true;
|
||||
// needs to be focusable so that we can receive events
|
||||
focusable: true;
|
||||
focus-on-click: true;
|
||||
allowed-apis: gl;
|
||||
realize => $realize();
|
||||
unrealize => $unrealize();
|
||||
resize => $resize();
|
||||
render => $render();
|
||||
|
||||
EventControllerFocus {
|
||||
enter => $focus_enter();
|
||||
leave => $focus_leave();
|
||||
}
|
||||
|
||||
EventControllerKey {
|
||||
key-pressed => $key_pressed();
|
||||
key-released => $key_released();
|
||||
}
|
||||
|
||||
GestureClick {
|
||||
pressed => $mouse_pressed();
|
||||
released => $mouse_released();
|
||||
button: 0;
|
||||
}
|
||||
|
||||
EventControllerMotion {
|
||||
motion => $mouse_motion();
|
||||
}
|
||||
|
||||
EventControllerScroll {
|
||||
scroll => $scroll();
|
||||
flags: both_axes;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IMMulticontext im_context {
|
||||
commit => $im_commit();
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
using Gtk 4.0;
|
||||
using Adw 1;
|
||||
|
||||
template $GhosttyInspectorWidget: Adw.Bin {
|
||||
styles [
|
||||
"inspector",
|
||||
]
|
||||
|
||||
hexpand: true;
|
||||
vexpand: true;
|
||||
|
||||
Adw.Bin {
|
||||
$GhosttyImguiWidget imgui_widget {}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
using Gtk 4.0;
|
||||
using Adw 1;
|
||||
|
||||
template $GhosttyInspectorWindow: Adw.ApplicationWindow {
|
||||
title: _("Ghostty: Terminal Inspector");
|
||||
icon-name: "com.mitchellh.ghostty";
|
||||
default-width: 1000;
|
||||
default-height: 600;
|
||||
close-request => $close_request();
|
||||
|
||||
styles [
|
||||
"inspector",
|
||||
]
|
||||
|
||||
content: Adw.ToolbarView {
|
||||
[top]
|
||||
Adw.HeaderBar {
|
||||
title-widget: Adw.WindowTitle {
|
||||
title: bind template.title;
|
||||
};
|
||||
}
|
||||
|
||||
Gtk.Box {
|
||||
orientation: vertical;
|
||||
spacing: 0;
|
||||
hexpand: true;
|
||||
vexpand: true;
|
||||
|
||||
$GhosttyDebugWarning {
|
||||
visible: bind template.debug;
|
||||
}
|
||||
|
||||
$GhosttyInspectorWidget inspector_widget {
|
||||
surface: bind template.surface;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
Loading…
Reference in New Issue