Merge remote-tracking branch 'upstream/main' into coretext-position-y

This commit is contained in:
Jacob Sandlund
2026-01-05 09:58:10 -05:00
40 changed files with 1063 additions and 13363 deletions

1
.gitattributes vendored
View File

@@ -4,7 +4,6 @@ build.zig.zon.json linguist-generated=true
vendor/** linguist-vendored
website/** linguist-documentation
pkg/breakpad/vendor/** linguist-vendored
pkg/cimgui/vendor/** linguist-vendored
pkg/glfw/wayland-headers/** linguist-vendored
pkg/libintl/config.h linguist-generated=true
pkg/libintl/libintl.h linguist-generated=true

View File

@@ -63,7 +63,7 @@
},
// C libs
.cimgui = .{ .path = "./pkg/cimgui", .lazy = true },
.dcimgui = .{ .path = "./pkg/dcimgui", .lazy = true },
.fontconfig = .{ .path = "./pkg/fontconfig", .lazy = true },
.freetype = .{ .path = "./pkg/freetype", .lazy = true },
.gtk4_layer_shell = .{ .path = "./pkg/gtk4-layer-shell", .lazy = true },

11
build.zig.zon.json generated
View File

@@ -1,4 +1,9 @@
{
"N-V-__8AANT61wB--nJ95Gj_ctmzAtcjloZ__hRqNw5lC1Kr": {
"name": "bindings",
"url": "https://deps.files.ghostty.org/DearBindings_v0.17_ImGui_v1.92.5-docking.tar.gz",
"hash": "sha256-i/7FAOAJJvZ5hT7iPWfMOS08MYFzPKRwRzhlHT9wuqM="
},
"N-V-__8AALw2uwF_03u4JRkZwRLc3Y9hakkYV7NKRR9-RIZJ": {
"name": "breakpad",
"url": "https://deps.files.ghostty.org/breakpad-b99f444ba5f6b98cac261cbb391d8766b34a5918.tar.gz",
@@ -44,10 +49,10 @@
"url": "https://deps.files.ghostty.org/highway-66486a10623fa0d72fe91260f96c892e41aceb06.tar.gz",
"hash": "sha256-h9T4iT704I8iSXNgj/6/lCaKgTgLp5wS6IQZaMgKohI="
},
"N-V-__8AAH0GaQC8a52s6vfIxg88OZgFgEW6DFxfSK4lX_l3": {
"N-V-__8AAEbOfQBnvcFcCX2W5z7tDaN8vaNZGamEQtNOe0UI": {
"name": "imgui",
"url": "https://deps.files.ghostty.org/imgui-1220bc6b9daceaf7c8c60f3c3998058045ba0c5c5f48ae255ff97776d9cd8bfc6402.tar.gz",
"hash": "sha256-oF/QHgTPEat4Hig4fGIdLkIPHmBEyOJ6JeYD6pnveGA="
"url": "https://github.com/ocornut/imgui/archive/refs/tags/v1.92.5-docking.tar.gz",
"hash": "sha256-yBbCDox18+Fa6Gc1DnmSVQLRpqhZOLsac7iSfl8x+cs="
},
"N-V-__8AAIdIAwAOceDblkuOARUyuTKbDdGPjPClPLhMeIfU": {
"name": "iterm2_themes",

14
build.zig.zon.nix generated
View File

@@ -82,6 +82,14 @@
fetcher.${proto};
in
linkFarm name [
{
name = "N-V-__8AANT61wB--nJ95Gj_ctmzAtcjloZ__hRqNw5lC1Kr";
path = fetchZigArtifact {
name = "bindings";
url = "https://deps.files.ghostty.org/DearBindings_v0.17_ImGui_v1.92.5-docking.tar.gz";
hash = "sha256-i/7FAOAJJvZ5hT7iPWfMOS08MYFzPKRwRzhlHT9wuqM=";
};
}
{
name = "N-V-__8AALw2uwF_03u4JRkZwRLc3Y9hakkYV7NKRR9-RIZJ";
path = fetchZigArtifact {
@@ -155,11 +163,11 @@ in
};
}
{
name = "N-V-__8AAH0GaQC8a52s6vfIxg88OZgFgEW6DFxfSK4lX_l3";
name = "N-V-__8AAEbOfQBnvcFcCX2W5z7tDaN8vaNZGamEQtNOe0UI";
path = fetchZigArtifact {
name = "imgui";
url = "https://deps.files.ghostty.org/imgui-1220bc6b9daceaf7c8c60f3c3998058045ba0c5c5f48ae255ff97776d9cd8bfc6402.tar.gz";
hash = "sha256-oF/QHgTPEat4Hig4fGIdLkIPHmBEyOJ6JeYD6pnveGA=";
url = "https://github.com/ocornut/imgui/archive/refs/tags/v1.92.5-docking.tar.gz";
hash = "sha256-yBbCDox18+Fa6Gc1DnmSVQLRpqhZOLsac7iSfl8x+cs=";
};
}
{

3
build.zig.zon.txt generated
View File

@@ -1,4 +1,5 @@
git+https://github.com/jacobsandlund/uucode#5f05f8f83a75caea201f12cc8ea32a2d82ea9732
https://deps.files.ghostty.org/DearBindings_v0.17_ImGui_v1.92.5-docking.tar.gz
https://deps.files.ghostty.org/JetBrainsMono-2.304.tar.gz
https://deps.files.ghostty.org/NerdFontsSymbolsOnly-3.4.0.tar.gz
https://deps.files.ghostty.org/breakpad-b99f444ba5f6b98cac261cbb391d8766b34a5918.tar.gz
@@ -11,7 +12,6 @@ https://deps.files.ghostty.org/gobject-2025-11-08-23-1.tar.zst
https://deps.files.ghostty.org/gtk4-layer-shell-1.1.0.tar.gz
https://deps.files.ghostty.org/harfbuzz-11.0.0.tar.xz
https://deps.files.ghostty.org/highway-66486a10623fa0d72fe91260f96c892e41aceb06.tar.gz
https://deps.files.ghostty.org/imgui-1220bc6b9daceaf7c8c60f3c3998058045ba0c5c5f48ae255ff97776d9cd8bfc6402.tar.gz
https://deps.files.ghostty.org/libpng-1220aa013f0c83da3fb64ea6d327f9173fa008d10e28bc9349eac3463457723b1c66.tar.gz
https://deps.files.ghostty.org/libxev-34fa50878aec6e5fa8f532867001ab3c36fae23e.tar.gz
https://deps.files.ghostty.org/libxml2-2.11.5.tar.gz
@@ -32,4 +32,5 @@ https://deps.files.ghostty.org/zig_objc-f356ed02833f0f1b8e84d50bed9e807bf7cdc0ae
https://deps.files.ghostty.org/zig_wayland-1b5c038ec10da20ed3a15b0b2a6db1c21383e8ea.tar.gz
https://deps.files.ghostty.org/zlib-1220fed0c74e1019b3ee29edae2051788b080cd96e90d56836eea857b0b966742efb.tar.gz
https://github.com/ivanstepanovftw/zigimg/archive/d7b7ab0ba0899643831ef042bd73289510b39906.tar.gz
https://github.com/ocornut/imgui/archive/refs/tags/v1.92.5-docking.tar.gz
https://github.com/vancluever/z2d/archive/refs/tags/v0.10.0.tar.gz

View File

@@ -1,4 +1,10 @@
[
{
"type": "archive",
"url": "https://deps.files.ghostty.org/DearBindings_v0.17_ImGui_v1.92.5-docking.tar.gz",
"dest": "vendor/p/N-V-__8AANT61wB--nJ95Gj_ctmzAtcjloZ__hRqNw5lC1Kr",
"sha256": "8bfec500e00926f679853ee23d67cc392d3c3181733ca4704738651d3f70baa3"
},
{
"type": "archive",
"url": "https://deps.files.ghostty.org/breakpad-b99f444ba5f6b98cac261cbb391d8766b34a5918.tar.gz",
@@ -55,9 +61,9 @@
},
{
"type": "archive",
"url": "https://deps.files.ghostty.org/imgui-1220bc6b9daceaf7c8c60f3c3998058045ba0c5c5f48ae255ff97776d9cd8bfc6402.tar.gz",
"dest": "vendor/p/N-V-__8AAH0GaQC8a52s6vfIxg88OZgFgEW6DFxfSK4lX_l3",
"sha256": "a05fd01e04cf11ab781e28387c621d2e420f1e6044c8e27a25e603ea99ef7860"
"url": "https://github.com/ocornut/imgui/archive/refs/tags/v1.92.5-docking.tar.gz",
"dest": "vendor/p/N-V-__8AAEbOfQBnvcFcCX2W5z7tDaN8vaNZGamEQtNOe0UI",
"sha256": "c816c20e8c75f3e15ae867350e79925502d1a6a85938bb1a73b8927e5f31f9cb"
},
{
"type": "archive",

View File

@@ -152,7 +152,6 @@
Helpers/AppInfo.swift,
Helpers/CodableBridge.swift,
Helpers/Cursor.swift,
Helpers/DraggableWindowView.swift,
Helpers/ExpiringUndoManager.swift,
"Helpers/Extensions/Double+Extension.swift",
"Helpers/Extensions/EventModifiers+Extension.swift",

View File

@@ -952,7 +952,7 @@ class BaseTerminalController: NSWindowController,
// controller is a TerminalController this is easy because it has a way
// to do this.
if let c = sourceController as? TerminalController {
c.closeWindowImmediately()
c.closeTabImmediately()
} else {
// Not a TerminalController so we always undo into a new window.
_ = TerminalController.newWindow(

View File

@@ -614,7 +614,7 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
closeWindow(nil)
}
private func closeTabImmediately(registerRedo: Bool = true) {
func closeTabImmediately(registerRedo: Bool = true) {
guard let window = window else { return }
guard let tabGroup = window.tabGroup,
tabGroup.windows.count > 1 else {

View File

@@ -194,7 +194,7 @@ class TerminalWindow: NSWindow {
// Its possible we miss the accessory titlebar call so we check again
// whenever the window becomes main. Both of these are idempotent.
if hasTabBar {
if tabBarView != nil {
tabBarDidAppear()
} else {
tabBarDidDisappear()
@@ -243,31 +243,6 @@ class TerminalWindow: NSWindow {
/// added.
static let tabBarIdentifier: NSUserInterfaceItemIdentifier = .init("_ghosttyTabBar")
func findTitlebarView() -> NSView? {
// Find our tab bar. If it doesn't exist we don't do anything.
//
// In normal window, `NSTabBar` typically appears as a subview of `NSTitlebarView` within `NSThemeFrame`.
// In fullscreen, the system creates a dedicated fullscreen window and the view hierarchy changes;
// in that case, the `titlebarView` is only accessible via a reference on `NSThemeFrame`.
// ref: https://github.com/mozilla-firefox/firefox/blob/054e2b072785984455b3b59acad9444ba1eeffb4/widget/cocoa/nsCocoaWindow.mm#L7205
guard let themeFrameView = contentView?.rootView else { return nil }
let titlebarView = if themeFrameView.responds(to: Selector(("titlebarView"))) {
themeFrameView.value(forKey: "titlebarView") as? NSView
} else {
NSView?.none
}
return titlebarView
}
func findTabBar() -> NSView? {
findTitlebarView()?.firstDescendant(withClassName: "NSTabBar")
}
/// Returns true if there is a tab bar visible on this window.
var hasTabBar: Bool {
findTabBar() != nil
}
var hasMoreThanOneTabs: Bool {
/// accessing ``tabGroup?.windows`` here
/// will cause other edge cases, be careful

View File

@@ -85,7 +85,7 @@ class TitlebarTabsTahoeTerminalWindow: TransparentTitlebarTerminalWindow, NSTool
return
}
guard let tabBarView = findTabBar() else {
guard let tabBarView else {
super.sendEvent(event)
return
}
@@ -176,8 +176,8 @@ class TitlebarTabsTahoeTerminalWindow: TransparentTitlebarTerminalWindow, NSTool
guard tabBarObserver == nil else { return }
guard
let titlebarView = findTitlebarView(),
let tabBar = findTabBar()
let titlebarView,
let tabBarView = self.tabBarView
else { return }
// View model updates must happen on their own ticks.
@@ -186,13 +186,13 @@ class TitlebarTabsTahoeTerminalWindow: TransparentTitlebarTerminalWindow, NSTool
}
// Find our clip view
guard let clipView = tabBar.firstSuperview(withClassName: "NSTitlebarAccessoryClipView") else { return }
guard let clipView = tabBarView.firstSuperview(withClassName: "NSTitlebarAccessoryClipView") else { return }
guard let accessoryView = clipView.subviews[safe: 0] else { return }
guard let toolbarView = titlebarView.firstDescendant(withClassName: "NSToolbarView") else { return }
// Make sure tabBar's height won't be stretched
guard let newTabButton = titlebarView.firstDescendant(withClassName: "NSTabBarNewTabButton") else { return }
tabBar.frame.size.height = newTabButton.frame.width
tabBarView.frame.size.height = newTabButton.frame.width
// The container is the view that we'll constrain our tab bar within.
let container = toolbarView
@@ -228,10 +228,10 @@ class TitlebarTabsTahoeTerminalWindow: TransparentTitlebarTerminalWindow, NSTool
// other events occur, the tab bar can resize and clear our constraints. When this
// happens, we need to remove our custom constraints and re-apply them once the
// tab bar has proper dimensions again to avoid constraint conflicts.
tabBar.postsFrameChangedNotifications = true
tabBarView.postsFrameChangedNotifications = true
tabBarObserver = NotificationCenter.default.addObserver(
forName: NSView.frameDidChangeNotification,
object: tabBar,
object: tabBarView,
queue: .main
) { [weak self] _ in
guard let self else { return }

View File

@@ -104,19 +104,31 @@ extension Ghostty {
/// Whether the current drag was cancelled by pressing escape.
private var dragCancelledByEscape: Bool = false
deinit {
if let escapeMonitor {
NSEvent.removeMonitor(escapeMonitor)
}
}
override func acceptsFirstMouse(for event: NSEvent?) -> Bool {
// Ensure this view gets the mouse event before window dragging handlers
return true
}
override func mouseDown(with event: NSEvent) {
// Consume the mouseDown event to prevent it from propagating to the
// window's drag handler. This fixes issue #10110 where grab handles
// would drag the window instead of initiating pane drags.
// Don't call super - the drag will be initiated in mouseDragged.
}
override func updateTrackingAreas() {
super.updateTrackingAreas()
// To update our tracking area we just recreate it all.
trackingAreas.forEach { removeTrackingArea($0) }
// Add our tracking area for mouse events
addTrackingArea(NSTrackingArea(
rect: bounds,
@@ -225,7 +237,7 @@ extension Ghostty {
NSEvent.removeMonitor(escapeMonitor)
self.escapeMonitor = nil
}
if operation == [] && !dragCancelledByEscape {
let endsInWindow = NSApplication.shared.windows.contains { window in
window.isVisible && window.frame.contains(screenPoint)
@@ -238,7 +250,7 @@ extension Ghostty {
)
}
}
isTracking = false
onDragStateChanged?(false)
}

View File

@@ -1181,16 +1181,9 @@ extension Ghostty {
/// Special case handling for some control keys
override func performKeyEquivalent(with event: NSEvent) -> Bool {
switch (event.type) {
case .keyDown:
// Continue, we care about key down events
break
default:
// Any other key event we don't care about. I don't think its even
// possible to receive any other event type.
return false
}
// We only care about key down events. It might not even be possible
// to receive any other event type here.
guard event.type == .keyDown else { return false }
// Only process events if we're focused. Some key events like C-/ macOS
// appears to send to the first view in the hierarchy rather than the
@@ -1202,7 +1195,15 @@ extension Ghostty {
return false
}
// If this event as-is would result in a key binding then we send it.
// Let the menu system handle this event if we're not in a key sequence or key table.
// This allows the menu bar to flash for shortcuts like Command+V.
if keySequence.isEmpty && keyTables.isEmpty {
if let menu = NSApp.mainMenu, menu.performKeyEquivalent(with: event) {
return true
}
}
// If the menu didn't handle it, check Ghostty bindings for custom shortcuts.
if let surface {
var ghosttyEvent = event.ghosttyKeyEvent(GHOSTTY_ACTION_PRESS)
let match = (event.characters ?? "").withCString { ptr in
@@ -2216,7 +2217,7 @@ extension Ghostty.SurfaceView {
return NSAttributedString(string: plainString, attributes: attributes)
}
}
/// Caches a value for some period of time, evicting it automatically when that time expires.

View File

@@ -1,19 +0,0 @@
import Cocoa
import SwiftUI
struct DraggableWindowView: NSViewRepresentable {
func makeNSView(context: Context) -> DraggableWindowNSView {
return DraggableWindowNSView()
}
func updateNSView(_ nsView: DraggableWindowNSView, context: Context) {
// No need to update anything here
}
}
class DraggableWindowNSView: NSView {
override func mouseDown(with event: NSEvent) {
guard let window = self.window else { return }
window.performDrag(with: event)
}
}

View File

@@ -10,12 +10,6 @@ extension NSWindow {
return CGWindowID(windowNumber)
}
/// True if this is the first window in the tab group.
var isFirstWindowInTabGroup: Bool {
guard let firstWindow = tabGroup?.windows.first else { return true }
return firstWindow === self
}
/// Adjusts the window frame if necessary to ensure the window remains visible on screen.
/// This constrains both the size (to not exceed the screen) and the origin (to keep the window on screen).
func constrainToScreen() {
@@ -36,3 +30,53 @@ extension NSWindow {
}
}
}
// MARK: Native Tabbing
extension NSWindow {
/// True if this is the first window in the tab group.
var isFirstWindowInTabGroup: Bool {
guard let firstWindow = tabGroup?.windows.first else { return true }
return firstWindow === self
}
}
/// Native tabbing private API usage. :(
extension NSWindow {
var titlebarView: NSView? {
// In normal window, `NSTabBar` typically appears as a subview of `NSTitlebarView` within `NSThemeFrame`.
// In fullscreen, the system creates a dedicated fullscreen window and the view hierarchy changes;
// in that case, the `titlebarView` is only accessible via a reference on `NSThemeFrame`.
// ref: https://github.com/mozilla-firefox/firefox/blob/054e2b072785984455b3b59acad9444ba1eeffb4/widget/cocoa/nsCocoaWindow.mm#L7205
guard let themeFrameView = contentView?.rootView else { return nil }
guard themeFrameView.responds(to: Selector(("titlebarView"))) else { return nil }
return themeFrameView.value(forKey: "titlebarView") as? NSView
}
/// Returns the [private] NSTabBar view, if it exists.
var tabBarView: NSView? {
titlebarView?.firstDescendant(withClassName: "NSTabBar")
}
/// Returns the index of the tab button at the given screen point, if any.
func tabIndex(atScreenPoint screenPoint: NSPoint) -> Int? {
guard let tabBarView else { return nil }
let locationInWindow = convertPoint(fromScreen: screenPoint)
let locationInTabBar = tabBarView.convert(locationInWindow, from: nil)
guard tabBarView.bounds.contains(locationInTabBar) else { return nil }
// Find all tab buttons and sort by x position to get visual order.
// The view hierarchy order doesn't match the visual tab order.
let tabItemViews = tabBarView.descendants(withClassName: "NSTabButton")
.sorted { $0.frame.origin.x < $1.frame.origin.x }
for (index, tabItemView) in tabItemViews.enumerated() {
let locationInTab = tabItemView.convert(locationInWindow, from: nil)
if tabItemView.bounds.contains(locationInTab) {
return index
}
}
return nil
}
}

View File

@@ -1,129 +0,0 @@
const std = @import("std");
const NativeTargetInfo = std.zig.system.NativeTargetInfo;
pub fn build(b: *std.Build) !void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const module = b.addModule("cimgui", .{
.root_source_file = b.path("main.zig"),
.target = target,
.optimize = optimize,
});
const imgui_ = b.lazyDependency("imgui", .{});
const lib = b.addLibrary(.{
.name = "cimgui",
.root_module = b.createModule(.{
.target = target,
.optimize = optimize,
}),
.linkage = .static,
});
lib.linkLibC();
lib.linkLibCpp();
if (target.result.os.tag == .windows) {
lib.linkSystemLibrary("imm32");
}
// For dynamic linking, we prefer dynamic linking and to search by
// mode first. Mode first will search all paths for a dynamic library
// before falling back to static.
const dynamic_link_opts: std.Build.Module.LinkSystemLibraryOptions = .{
.preferred_link_mode = .dynamic,
.search_strategy = .mode_first,
};
if (b.systemIntegrationOption("freetype", .{})) {
lib.linkSystemLibrary2("freetype2", dynamic_link_opts);
} else {
const freetype = b.dependency("freetype", .{
.target = target,
.optimize = optimize,
.@"enable-libpng" = true,
});
lib.linkLibrary(freetype.artifact("freetype"));
if (freetype.builder.lazyDependency(
"freetype",
.{},
)) |freetype_dep| {
module.addIncludePath(freetype_dep.path("include"));
}
}
if (imgui_) |imgui| lib.addIncludePath(imgui.path(""));
module.addIncludePath(b.path("vendor"));
var flags: std.ArrayList([]const u8) = .empty;
defer flags.deinit(b.allocator);
try flags.appendSlice(b.allocator, &.{
"-DCIMGUI_FREETYPE=1",
"-DIMGUI_USE_WCHAR32=1",
"-DIMGUI_DISABLE_OBSOLETE_FUNCTIONS=1",
});
if (target.result.os.tag == .windows) {
try flags.appendSlice(b.allocator, &.{
"-DIMGUI_IMPL_API=extern\t\"C\"\t__declspec(dllexport)",
});
} else {
try flags.appendSlice(b.allocator, &.{
"-DIMGUI_IMPL_API=extern\t\"C\"",
});
}
if (target.result.os.tag == .freebsd) {
try flags.append(b.allocator, "-fPIC");
}
if (imgui_) |imgui| {
lib.addCSourceFile(.{ .file = b.path("vendor/cimgui.cpp"), .flags = flags.items });
lib.addCSourceFile(.{ .file = imgui.path("imgui.cpp"), .flags = flags.items });
lib.addCSourceFile(.{ .file = imgui.path("imgui_draw.cpp"), .flags = flags.items });
lib.addCSourceFile(.{ .file = imgui.path("imgui_demo.cpp"), .flags = flags.items });
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("misc/freetype/imgui_freetype.cpp"), .flags = flags.items });
lib.addCSourceFile(.{
.file = imgui.path("backends/imgui_impl_opengl3.cpp"),
.flags = flags.items,
});
if (target.result.os.tag.isDarwin()) {
if (!target.query.isNative()) {
try @import("apple_sdk").addPaths(b, lib);
}
lib.addCSourceFile(.{
.file = imgui.path("backends/imgui_impl_metal.mm"),
.flags = flags.items,
});
if (target.result.os.tag == .macos) {
lib.addCSourceFile(.{
.file = imgui.path("backends/imgui_impl_osx.mm"),
.flags = flags.items,
});
}
}
}
lib.installHeadersDirectory(
b.path("vendor"),
"",
.{ .include_extensions = &.{".h"} },
);
b.installArtifact(lib);
const test_exe = b.addTest(.{
.name = "test",
.root_module = b.createModule(.{
.root_source_file = b.path("main.zig"),
.target = target,
.optimize = optimize,
}),
});
test_exe.linkLibrary(lib);
const tests_run = b.addRunArtifact(test_exe);
const test_step = b.step("test", "Run tests");
test_step.dependOn(&tests_run.step);
}

View File

@@ -1,19 +0,0 @@
.{
.name = .cimgui,
.version = "1.90.6", // -docking branch
.fingerprint = 0x49726f5f8acbc90d,
.paths = .{""},
.dependencies = .{
// This should be kept in sync with the submodule in the cimgui source
// code in ./vendor/ to be safe that they're compatible.
.imgui = .{
// ocornut/imgui
.url = "https://deps.files.ghostty.org/imgui-1220bc6b9daceaf7c8c60f3c3998058045ba0c5c5f48ae255ff97776d9cd8bfc6402.tar.gz",
.hash = "N-V-__8AAH0GaQC8a52s6vfIxg88OZgFgEW6DFxfSK4lX_l3",
.lazy = true,
},
.apple_sdk = .{ .path = "../apple-sdk" },
.freetype = .{ .path = "../freetype" },
},
}

View File

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

View File

@@ -1,20 +0,0 @@
pub const c = @import("c.zig").c;
// OpenGL
pub extern fn ImGui_ImplOpenGL3_Init(?[*:0]const u8) callconv(.c) bool;
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;
// Metal
pub extern fn ImGui_ImplMetal_Init(*anyopaque) callconv(.c) bool;
pub extern fn ImGui_ImplMetal_Shutdown() callconv(.c) void;
pub extern fn ImGui_ImplMetal_NewFrame(*anyopaque) callconv(.c) void;
pub extern fn ImGui_ImplMetal_RenderDrawData(*c.ImDrawData, *anyopaque, *anyopaque) callconv(.c) void;
// OSX
pub extern fn ImGui_ImplOSX_Init(*anyopaque) callconv(.c) bool;
pub extern fn ImGui_ImplOSX_Shutdown() callconv(.c) void;
pub extern fn ImGui_ImplOSX_NewFrame(*anyopaque) callconv(.c) void;
test {}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

199
pkg/dcimgui/build.zig Normal file
View File

@@ -0,0 +1,199 @@
const std = @import("std");
const NativeTargetInfo = std.zig.system.NativeTargetInfo;
pub fn build(b: *std.Build) !void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const freetype = b.option(bool, "freetype", "Use Freetype") orelse false;
const backend_opengl3 = b.option(bool, "backend-opengl3", "OpenGL3 backend") orelse false;
const backend_metal = b.option(bool, "backend-metal", "Metal backend") orelse false;
const backend_osx = b.option(bool, "backend-osx", "OSX backend") orelse false;
// Build options
const options = b.addOptions();
options.addOption(bool, "freetype", freetype);
options.addOption(bool, "backend_opengl3", backend_opengl3);
options.addOption(bool, "backend_metal", backend_metal);
options.addOption(bool, "backend_osx", backend_osx);
// Main static lib
const lib = b.addLibrary(.{
.name = "dcimgui",
.root_module = b.createModule(.{
.target = target,
.optimize = optimize,
}),
.linkage = .static,
});
lib.linkLibC();
lib.linkLibCpp();
b.installArtifact(lib);
// Zig module
const mod = b.addModule("dcimgui", .{
.root_source_file = b.path("main.zig"),
.target = target,
.optimize = optimize,
});
mod.addOptions("build_options", options);
mod.linkLibrary(lib);
// We need to add proper Apple SDKs to find stdlib headers
if (target.result.os.tag.isDarwin()) {
if (!target.query.isNative()) {
try @import("apple_sdk").addPaths(b, lib);
}
}
// Flags for C compilation, common to all.
var flags: std.ArrayList([]const u8) = .empty;
defer flags.deinit(b.allocator);
try flags.appendSlice(b.allocator, &.{
"-DIMGUI_USE_WCHAR32=1",
"-DIMGUI_DISABLE_OBSOLETE_FUNCTIONS=1",
});
if (freetype) try flags.appendSlice(b.allocator, &.{
"-DIMGUI_ENABLE_FREETYPE=1",
});
if (target.result.os.tag == .windows) {
try flags.appendSlice(b.allocator, &.{
"-DIMGUI_IMPL_API=extern\t\"C\"\t__declspec(dllexport)",
});
} else {
try flags.appendSlice(b.allocator, &.{
"-DIMGUI_IMPL_API=extern\t\"C\"",
});
}
if (target.result.os.tag == .freebsd) {
try flags.append(b.allocator, "-fPIC");
}
// Add the core Dear Imgui source files
if (b.lazyDependency("imgui", .{})) |upstream| {
lib.addIncludePath(upstream.path(""));
lib.addCSourceFiles(.{
.root = upstream.path(""),
.files = &.{
"imgui_demo.cpp",
"imgui_draw.cpp",
"imgui_tables.cpp",
"imgui_widgets.cpp",
"imgui.cpp",
},
.flags = flags.items,
});
lib.installHeadersDirectory(
upstream.path(""),
"",
.{ .include_extensions = &.{".h"} },
);
if (freetype) {
lib.addCSourceFile(.{
.file = upstream.path("misc/freetype/imgui_freetype.cpp"),
.flags = flags.items,
});
if (b.systemIntegrationOption("freetype", .{})) {
lib.linkSystemLibrary2("freetype2", dynamic_link_opts);
} else {
const freetype_dep = b.dependency("freetype", .{
.target = target,
.optimize = optimize,
.@"enable-libpng" = true,
});
lib.linkLibrary(freetype_dep.artifact("freetype"));
if (freetype_dep.builder.lazyDependency(
"freetype",
.{},
)) |freetype_upstream| {
mod.addIncludePath(freetype_upstream.path("include"));
}
}
}
if (backend_metal) {
lib.addCSourceFiles(.{
.root = upstream.path("backends"),
.files = &.{"imgui_impl_metal.mm"},
.flags = flags.items,
});
lib.installHeadersDirectory(
upstream.path("backends"),
"",
.{ .include_extensions = &.{"imgui_impl_metal.h"} },
);
}
if (backend_osx) {
lib.addCSourceFiles(.{
.root = upstream.path("backends"),
.files = &.{"imgui_impl_osx.mm"},
.flags = flags.items,
});
lib.installHeadersDirectory(
upstream.path("backends"),
"",
.{ .include_extensions = &.{"imgui_impl_osx.h"} },
);
}
if (backend_opengl3) {
lib.addCSourceFiles(.{
.root = upstream.path("backends"),
.files = &.{"imgui_impl_opengl3.cpp"},
.flags = flags.items,
});
lib.installHeadersDirectory(
upstream.path("backends"),
"",
.{ .include_extensions = &.{"imgui_impl_opengl3.h"} },
);
}
}
// Add the C bindings
if (b.lazyDependency("bindings", .{})) |upstream| {
lib.addIncludePath(upstream.path(""));
lib.addCSourceFiles(.{
.root = upstream.path(""),
.files = &.{
"dcimgui.cpp",
"dcimgui_internal.cpp",
},
.flags = flags.items,
});
lib.addCSourceFiles(.{
.root = b.path(""),
.files = &.{"ext.cpp"},
.flags = flags.items,
});
lib.installHeadersDirectory(
upstream.path(""),
"",
.{ .include_extensions = &.{".h"} },
);
}
const test_exe = b.addTest(.{
.name = "test",
.root_module = b.createModule(.{
.root_source_file = b.path("main.zig"),
.target = target,
.optimize = optimize,
}),
});
test_exe.root_module.addOptions("build_options", options);
test_exe.linkLibrary(lib);
const tests_run = b.addRunArtifact(test_exe);
const test_step = b.step("test", "Run tests");
test_step.dependOn(&tests_run.step);
}
// For dynamic linking, we prefer dynamic linking and to search by
// mode first. Mode first will search all paths for a dynamic library
// before falling back to static.
const dynamic_link_opts: std.Build.Module.LinkSystemLibraryOptions = .{
.preferred_link_mode = .dynamic,
.search_strategy = .mode_first,
};

26
pkg/dcimgui/build.zig.zon Normal file
View File

@@ -0,0 +1,26 @@
.{
.name = .dcimgui,
.version = "1.92.5", // -docking branch
.fingerprint = 0x1a25797442c6324f,
.paths = .{""},
.dependencies = .{
// The bindings and imgui versions below must match exactly.
.bindings = .{
// https://github.com/dearimgui/dear_bindings
.url = "https://deps.files.ghostty.org/DearBindings_v0.17_ImGui_v1.92.5-docking.tar.gz",
.hash = "N-V-__8AANT61wB--nJ95Gj_ctmzAtcjloZ__hRqNw5lC1Kr",
.lazy = true,
},
.imgui = .{
// https://github.com/ocornut/imgui
.url = "https://github.com/ocornut/imgui/archive/refs/tags/v1.92.5-docking.tar.gz",
.hash = "N-V-__8AAEbOfQBnvcFcCX2W5z7tDaN8vaNZGamEQtNOe0UI",
.lazy = true,
},
.apple_sdk = .{ .path = "../apple-sdk" },
.freetype = .{ .path = "../freetype" },
},
}

30
pkg/dcimgui/ext.cpp Normal file
View File

@@ -0,0 +1,30 @@
#include "imgui.h"
// This file contains custom extensions for functionality that isn't
// properly supported by Dear Bindings yet. Namely:
// https://github.com/dearimgui/dear_bindings/issues/55
// Wrap this in a namespace to keep it separate from the C++ API
namespace cimgui
{
#include "dcimgui.h"
}
extern "C"
{
CIMGUI_API void ImFontConfig_ImFontConfig(cimgui::ImFontConfig* self)
{
static_assert(sizeof(cimgui::ImFontConfig) == sizeof(::ImFontConfig), "ImFontConfig size mismatch");
static_assert(alignof(cimgui::ImFontConfig) == alignof(::ImFontConfig), "ImFontConfig alignment mismatch");
::ImFontConfig defaults;
*reinterpret_cast<::ImFontConfig*>(self) = defaults;
}
CIMGUI_API void ImGuiStyle_ImGuiStyle(cimgui::ImGuiStyle* self)
{
static_assert(sizeof(cimgui::ImGuiStyle) == sizeof(::ImGuiStyle), "ImGuiStyle size mismatch");
static_assert(alignof(cimgui::ImGuiStyle) == alignof(::ImGuiStyle), "ImGuiStyle alignment mismatch");
::ImGuiStyle defaults;
*reinterpret_cast<::ImGuiStyle*>(self) = defaults;
}
}

43
pkg/dcimgui/main.zig Normal file
View File

@@ -0,0 +1,43 @@
pub const build_options = @import("build_options");
pub const c = @cImport({
// This is set during the build so it also has to be set
// during import time to get the right types. Without this
// you get stack size mismatches on some structs.
@cDefine("IMGUI_USE_WCHAR32", "1");
@cInclude("dcimgui.h");
});
// OpenGL3 backend
pub extern fn ImGui_ImplOpenGL3_Init(glsl_version: ?[*:0]const u8) callconv(.c) bool;
pub extern fn ImGui_ImplOpenGL3_Shutdown() callconv(.c) void;
pub extern fn ImGui_ImplOpenGL3_NewFrame() callconv(.c) void;
pub extern fn ImGui_ImplOpenGL3_RenderDrawData(draw_data: *c.ImDrawData) callconv(.c) void;
// Metal backend
pub extern fn ImGui_ImplMetal_Init(device: *anyopaque) callconv(.c) bool;
pub extern fn ImGui_ImplMetal_Shutdown() callconv(.c) void;
pub extern fn ImGui_ImplMetal_NewFrame(render_pass_descriptor: *anyopaque) callconv(.c) void;
pub extern fn ImGui_ImplMetal_RenderDrawData(draw_data: *c.ImDrawData, command_buffer: *anyopaque, command_encoder: *anyopaque) callconv(.c) void;
// OSX
pub extern fn ImGui_ImplOSX_Init(*anyopaque) callconv(.c) bool;
pub extern fn ImGui_ImplOSX_Shutdown() callconv(.c) void;
pub extern fn ImGui_ImplOSX_NewFrame(*anyopaque) callconv(.c) void;
// Internal API functions from dcimgui_internal.h
// We declare these manually because the internal header contains bitfields
// that Zig's cImport cannot translate.
pub extern fn ImGui_DockBuilderDockWindow(window_name: [*:0]const u8, node_id: c.ImGuiID) callconv(.c) void;
pub extern fn ImGui_DockBuilderSplitNode(node_id: c.ImGuiID, split_dir: c.ImGuiDir, size_ratio_for_node_at_dir: f32, out_id_at_dir: *c.ImGuiID, out_id_at_opposite_dir: *c.ImGuiID) callconv(.c) c.ImGuiID;
pub extern fn ImGui_DockBuilderFinish(node_id: c.ImGuiID) callconv(.c) void;
// Extension functions from ext.cpp
pub const ext = struct {
pub extern fn ImFontConfig_ImFontConfig(self: *c.ImFontConfig) callconv(.c) void;
pub extern fn ImGuiStyle_ImGuiStyle(self: *c.ImGuiStyle) callconv(.c) void;
};
test {
_ = c;
}

View File

@@ -947,7 +947,7 @@ pub const Surface = struct {
/// Inspector is the state required for the terminal inspector. A terminal
/// inspector is 1:1 with a Surface.
pub const Inspector = struct {
const cimgui = @import("cimgui");
const cimgui = @import("dcimgui");
surface: *Surface,
ig_ctx: *cimgui.c.ImGuiContext,
@@ -968,10 +968,10 @@ pub const Inspector = struct {
};
pub fn init(surface: *Surface) !Inspector {
const ig_ctx = cimgui.c.igCreateContext(null) orelse return error.OutOfMemory;
errdefer cimgui.c.igDestroyContext(ig_ctx);
cimgui.c.igSetCurrentContext(ig_ctx);
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
const ig_ctx = cimgui.c.ImGui_CreateContext(null) orelse return error.OutOfMemory;
errdefer cimgui.c.ImGui_DestroyContext(ig_ctx);
cimgui.c.ImGui_SetCurrentContext(ig_ctx);
const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO();
io.BackendPlatformName = "ghostty_embedded";
// Setup our core inspector
@@ -988,9 +988,9 @@ pub const Inspector = struct {
pub fn deinit(self: *Inspector) void {
self.surface.core_surface.deactivateInspector();
cimgui.c.igSetCurrentContext(self.ig_ctx);
cimgui.c.ImGui_SetCurrentContext(self.ig_ctx);
if (self.backend) |v| v.deinit();
cimgui.c.igDestroyContext(self.ig_ctx);
cimgui.c.ImGui_DestroyContext(self.ig_ctx);
}
/// Queue a render for the next frame.
@@ -1001,7 +1001,7 @@ pub const Inspector = struct {
/// Initialize the inspector for a metal backend.
pub fn initMetal(self: *Inspector, device: objc.Object) bool {
defer device.msgSend(void, objc.sel("release"), .{});
cimgui.c.igSetCurrentContext(self.ig_ctx);
cimgui.c.ImGui_SetCurrentContext(self.ig_ctx);
if (self.backend) |v| {
v.deinit();
@@ -1036,7 +1036,7 @@ pub const Inspector = struct {
for (0..2) |_| {
cimgui.ImGui_ImplMetal_NewFrame(desc.value);
try self.newFrame();
cimgui.c.igNewFrame();
cimgui.c.ImGui_NewFrame();
// Build our UI
render: {
@@ -1046,7 +1046,7 @@ pub const Inspector = struct {
}
// Render
cimgui.c.igRender();
cimgui.c.ImGui_Render();
}
// MTLRenderCommandEncoder
@@ -1057,7 +1057,7 @@ pub const Inspector = struct {
);
defer encoder.msgSend(void, objc.sel("endEncoding"), .{});
cimgui.ImGui_ImplMetal_RenderDrawData(
cimgui.c.igGetDrawData(),
cimgui.c.ImGui_GetDrawData(),
command_buffer.value,
encoder.value,
);
@@ -1065,22 +1065,24 @@ pub const Inspector = struct {
pub fn updateContentScale(self: *Inspector, x: f64, y: f64) void {
_ = y;
cimgui.c.igSetCurrentContext(self.ig_ctx);
cimgui.c.ImGui_SetCurrentContext(self.ig_ctx);
// Cache our scale because we use it for cursor position calculations.
self.content_scale = x;
// 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, @floatCast(x));
const active_style = cimgui.c.igGetStyle();
active_style.* = style.*;
// Setup a new style and scale it appropriately. We must use the
// ImGuiStyle constructor to get proper default values (e.g.,
// CurveTessellationTol) rather than zero-initialized values.
var style: cimgui.c.ImGuiStyle = undefined;
cimgui.ext.ImGuiStyle_ImGuiStyle(&style);
cimgui.c.ImGuiStyle_ScaleAllSizes(&style, @floatCast(x));
const active_style = cimgui.c.ImGui_GetStyle();
active_style.* = style;
}
pub fn updateSize(self: *Inspector, width: u32, height: u32) void {
cimgui.c.igSetCurrentContext(self.ig_ctx);
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
cimgui.c.ImGui_SetCurrentContext(self.ig_ctx);
const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO();
io.DisplaySize = .{ .x = @floatFromInt(width), .y = @floatFromInt(height) };
}
@@ -1093,8 +1095,8 @@ pub const Inspector = struct {
_ = mods;
self.queueRender();
cimgui.c.igSetCurrentContext(self.ig_ctx);
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
cimgui.c.ImGui_SetCurrentContext(self.ig_ctx);
const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO();
const imgui_button = switch (button) {
.left => cimgui.c.ImGuiMouseButton_Left,
@@ -1115,8 +1117,8 @@ pub const Inspector = struct {
_ = mods;
self.queueRender();
cimgui.c.igSetCurrentContext(self.ig_ctx);
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
cimgui.c.ImGui_SetCurrentContext(self.ig_ctx);
const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO();
cimgui.c.ImGuiIO_AddMouseWheelEvent(
io,
@floatCast(xoff),
@@ -1126,8 +1128,8 @@ pub const Inspector = struct {
pub fn cursorPosCallback(self: *Inspector, x: f64, y: f64) void {
self.queueRender();
cimgui.c.igSetCurrentContext(self.ig_ctx);
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
cimgui.c.ImGui_SetCurrentContext(self.ig_ctx);
const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO();
cimgui.c.ImGuiIO_AddMousePosEvent(
io,
@floatCast(x * self.content_scale),
@@ -1137,15 +1139,15 @@ pub const Inspector = struct {
pub fn focusCallback(self: *Inspector, focused: bool) void {
self.queueRender();
cimgui.c.igSetCurrentContext(self.ig_ctx);
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
cimgui.c.ImGui_SetCurrentContext(self.ig_ctx);
const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO();
cimgui.c.ImGuiIO_AddFocusEvent(io, focused);
}
pub fn textCallback(self: *Inspector, text: [:0]const u8) void {
self.queueRender();
cimgui.c.igSetCurrentContext(self.ig_ctx);
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
cimgui.c.ImGui_SetCurrentContext(self.ig_ctx);
const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO();
cimgui.c.ImGuiIO_AddInputCharactersUTF8(io, text.ptr);
}
@@ -1156,8 +1158,8 @@ pub const Inspector = struct {
mods: input.Mods,
) !void {
self.queueRender();
cimgui.c.igSetCurrentContext(self.ig_ctx);
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
cimgui.c.ImGui_SetCurrentContext(self.ig_ctx);
const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO();
// Update all our modifiers
cimgui.c.ImGuiIO_AddKeyEvent(io, cimgui.c.ImGuiKey_LeftShift, mods.shift);
@@ -1176,7 +1178,7 @@ pub const Inspector = struct {
}
fn newFrame(self: *Inspector) !void {
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO();
// Determine our delta time
const now = try std.time.Instant.now();

View File

@@ -1,7 +1,7 @@
const std = @import("std");
const assert = @import("../../../quirks.zig").inlineAssert;
const cimgui = @import("cimgui");
const cimgui = @import("dcimgui");
const gl = @import("opengl");
const adw = @import("adw");
const gdk = @import("gdk");
@@ -126,7 +126,7 @@ pub const ImguiWidget = extern struct {
log.warn("Dear ImGui context not initialized", .{});
return error.ContextNotInitialized;
};
cimgui.c.igSetCurrentContext(ig_context);
cimgui.c.ImGui_SetCurrentContext(ig_context);
}
/// Initialize the frame. Expects that the context is already current.
@@ -137,7 +137,7 @@ pub const ImguiWidget = extern struct {
const priv = self.private();
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO();
// Determine our delta time
const now = std.time.Instant.now() catch unreachable;
@@ -163,7 +163,7 @@ pub const ImguiWidget = extern struct {
self.setCurrentContext() catch return false;
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO();
const mods = key.translateMods(gtk_mods);
cimgui.c.ImGuiIO_AddKeyEvent(io, cimgui.c.ImGuiKey_LeftShift, mods.shift);
@@ -219,14 +219,14 @@ pub const ImguiWidget = extern struct {
return;
}
priv.ig_context = cimgui.c.igCreateContext(null) orelse {
priv.ig_context = cimgui.c.ImGui_CreateContext(null) orelse {
log.warn("unable to initialize Dear ImGui context", .{});
return;
};
self.setCurrentContext() catch return;
// Setup some basic config
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO();
io.BackendPlatformName = "ghostty_gtk";
// Realize means that our OpenGL context is ready, so we can now
@@ -247,7 +247,7 @@ pub const ImguiWidget = extern struct {
/// 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 io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO();
const scale_factor = area.as(gtk.Widget).getScaleFactor();
// Our display size is always unscaled. We'll do the scaling in the
@@ -255,12 +255,14 @@ pub const ImguiWidget = extern struct {
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.*;
// Setup a new style and scale it appropriately. We must use the
// ImGuiStyle constructor to get proper default values (e.g.,
// CurveTessellationTol) rather than zero-initialized values.
var style: cimgui.c.ImGuiStyle = undefined;
cimgui.ext.ImGuiStyle_ImGuiStyle(&style);
cimgui.c.ImGuiStyle_ScaleAllSizes(&style, @floatFromInt(scale_factor));
const active_style = cimgui.c.ImGui_GetStyle();
active_style.* = style;
}
/// Handle a request to render the contents of our GLArea
@@ -273,33 +275,33 @@ pub const ImguiWidget = extern struct {
for (0..2) |_| {
cimgui.ImGui_ImplOpenGL3_NewFrame();
self.newFrame();
cimgui.c.igNewFrame();
cimgui.c.ImGui_NewFrame();
// Call the virtual method to draw the UI.
self.render();
// Render
cimgui.c.igRender();
cimgui.c.ImGui_Render();
}
// 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());
cimgui.ImGui_ImplOpenGL3_RenderDrawData(cimgui.c.ImGui_GetDrawData());
return @intFromBool(true);
}
fn ecFocusEnter(_: *gtk.EventControllerFocus, self: *Self) callconv(.c) void {
self.setCurrentContext() catch return;
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO();
cimgui.c.ImGuiIO_AddFocusEvent(io, true);
self.queueRender();
}
fn ecFocusLeave(_: *gtk.EventControllerFocus, self: *Self) callconv(.c) void {
self.setCurrentContext() catch return;
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO();
cimgui.c.ImGuiIO_AddFocusEvent(io, false);
self.queueRender();
}
@@ -345,7 +347,7 @@ pub const ImguiWidget = extern struct {
) callconv(.c) void {
self.queueRender();
self.setCurrentContext() catch return;
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO();
const gdk_button = gesture.as(gtk.GestureSingle).getCurrentButton();
if (translateMouseButton(gdk_button)) |button| {
cimgui.c.ImGuiIO_AddMouseButtonEvent(io, button, true);
@@ -361,7 +363,7 @@ pub const ImguiWidget = extern struct {
) callconv(.c) void {
self.queueRender();
self.setCurrentContext() catch return;
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO();
const gdk_button = gesture.as(gtk.GestureSingle).getCurrentButton();
if (translateMouseButton(gdk_button)) |button| {
cimgui.c.ImGuiIO_AddMouseButtonEvent(io, button, false);
@@ -376,7 +378,7 @@ pub const ImguiWidget = extern struct {
) callconv(.c) void {
self.queueRender();
self.setCurrentContext() catch return;
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO();
const scale_factor = self.getScaleFactor();
cimgui.c.ImGuiIO_AddMousePosEvent(
io,
@@ -393,7 +395,7 @@ pub const ImguiWidget = extern struct {
) callconv(.c) c_int {
self.queueRender();
self.setCurrentContext() catch return @intFromBool(false);
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO();
cimgui.c.ImGuiIO_AddMouseWheelEvent(
io,
@floatCast(x),
@@ -409,7 +411,7 @@ pub const ImguiWidget = extern struct {
) callconv(.c) void {
self.queueRender();
self.setCurrentContext() catch return;
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO();
cimgui.c.ImGuiIO_AddInputCharactersUTF8(io, bytes);
}

View File

@@ -135,29 +135,28 @@ pub fn add(
// Every exe needs the terminal options
self.config.terminalOptions().add(b, step.root_module);
// Freetype
// Freetype. We always include this even if our font backend doesn't
// use it because Dear Imgui uses Freetype.
_ = b.systemIntegrationOption("freetype", .{}); // Shows it in help
if (self.config.font_backend.hasFreetype()) {
if (b.lazyDependency("freetype", .{
.target = target,
.optimize = optimize,
.@"enable-libpng" = true,
})) |freetype_dep| {
step.root_module.addImport(
"freetype",
freetype_dep.module("freetype"),
);
if (b.lazyDependency("freetype", .{
.target = target,
.optimize = optimize,
.@"enable-libpng" = true,
})) |freetype_dep| {
step.root_module.addImport(
"freetype",
freetype_dep.module("freetype"),
);
if (b.systemIntegrationOption("freetype", .{})) {
step.linkSystemLibrary2("bzip2", dynamic_link_opts);
step.linkSystemLibrary2("freetype2", dynamic_link_opts);
} else {
step.linkLibrary(freetype_dep.artifact("freetype"));
try static_libs.append(
b.allocator,
freetype_dep.artifact("freetype").getEmittedBin(),
);
}
if (b.systemIntegrationOption("freetype", .{})) {
step.linkSystemLibrary2("bzip2", dynamic_link_opts);
step.linkSystemLibrary2("freetype2", dynamic_link_opts);
} else {
step.linkLibrary(freetype_dep.artifact("freetype"));
try static_libs.append(
b.allocator,
freetype_dep.artifact("freetype").getEmittedBin(),
);
}
}
@@ -479,15 +478,19 @@ pub fn add(
}
// cimgui
if (b.lazyDependency("cimgui", .{
if (b.lazyDependency("dcimgui", .{
.target = target,
.optimize = optimize,
})) |cimgui_dep| {
step.root_module.addImport("cimgui", cimgui_dep.module("cimgui"));
step.linkLibrary(cimgui_dep.artifact("cimgui"));
.freetype = true,
.@"backend-metal" = target.result.os.tag.isDarwin(),
.@"backend-osx" = target.result.os.tag == .macos,
.@"backend-opengl3" = target.result.os.tag != .macos,
})) |dep| {
step.root_module.addImport("dcimgui", dep.module("dcimgui"));
step.linkLibrary(dep.artifact("dcimgui"));
try static_libs.append(
b.allocator,
cimgui_dep.artifact("cimgui").getEmittedBin(),
dep.artifact("dcimgui").getEmittedBin(),
);
}

View File

@@ -2783,6 +2783,22 @@ keybind: Keybinds = .{},
/// the same time as the `iTime` uniform, allowing you to compute the
/// time since the change by subtracting this from `iTime`.
///
/// * `float iTimeFocus` - Timestamp when the surface last gained iFocus.
///
/// When the surface gains focus, this is set to the current value of
/// `iTime`, similar to how `iTimeCursorChange` works. This allows you
/// to compute the time since focus was gained or lost by calculating
/// `iTime - iTimeFocus`. Use this to create animations that restart
/// when the terminal regains focus.
///
/// * `int iFocus` - Current focus state of the surface.
///
/// Set to 1.0 when the surface is focused, 0.0 when unfocused. This
/// allows shaders to detect unfocused state and avoid animation artifacts
/// from large time deltas caused by infrequent "deceptive frames"
/// (e.g., modifier key presses, link hover events in unfocused split panes).
/// Check `iFocus > 0` to determine if the surface is currently focused.
///
/// If the shader fails to compile, the shader will be ignored. Any errors
/// related to shader compilation will not show up as configuration errors
/// and only show up in the log, since shader compilation happens after

View File

@@ -1,7 +1,7 @@
const std = @import("std");
const builtin = @import("builtin");
const Allocator = std.mem.Allocator;
const cimgui = @import("cimgui");
const cimgui = @import("dcimgui");
const OptionAsAlt = @import("config.zig").OptionAsAlt;
/// A generic key input event. This is the information that is necessary
@@ -696,7 +696,7 @@ pub const Key = enum(c_int) {
}
/// Returns the cimgui key constant for this key.
pub fn imguiKey(self: Key) ?c_uint {
pub fn imguiKey(self: Key) ?c_int {
return switch (self) {
.key_a => cimgui.c.ImGuiKey_A,
.key_b => cimgui.c.ImGuiKey_B,

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
const std = @import("std");
const assert = @import("../quirks.zig").inlineAssert;
const Allocator = std.mem.Allocator;
const cimgui = @import("cimgui");
const cimgui = @import("dcimgui");
const terminal = @import("../terminal/main.zig");
/// A cell being inspected. This duplicates much of the data in
@@ -55,24 +55,22 @@ pub const Cell = struct {
y: usize,
) void {
// We have a selected cell, show information about it.
_ = cimgui.c.igBeginTable(
_ = cimgui.c.ImGui_BeginTable(
"table_cursor",
2,
cimgui.c.ImGuiTableFlags_None,
.{ .x = 0, .y = 0 },
0,
);
defer cimgui.c.igEndTable();
defer cimgui.c.ImGui_EndTable();
{
cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
cimgui.c.ImGui_TableNextRow();
{
_ = cimgui.c.igTableSetColumnIndex(0);
cimgui.c.igText("Grid Position");
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Grid Position");
}
{
_ = cimgui.c.igTableSetColumnIndex(1);
cimgui.c.igText("row=%d col=%d", y, x);
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("row=%d col=%d", y, x);
}
}
@@ -82,18 +80,18 @@ pub const Cell = struct {
// the single glyph in an image view so it looks _identical_ to the
// terminal.
codepoint: {
cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
cimgui.c.ImGui_TableNextRow();
{
_ = cimgui.c.igTableSetColumnIndex(0);
cimgui.c.igText("Codepoints");
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Codepoints");
}
{
_ = cimgui.c.igTableSetColumnIndex(1);
if (cimgui.c.igBeginListBox("##codepoints", .{ .x = 0, .y = 0 })) {
defer cimgui.c.igEndListBox();
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
if (cimgui.c.ImGui_BeginListBox("##codepoints", .{ .x = 0, .y = 0 })) {
defer cimgui.c.ImGui_EndListBox();
if (self.codepoint == 0) {
_ = cimgui.c.igSelectable_Bool("(empty)", false, 0, .{});
_ = cimgui.c.ImGui_SelectableEx("(empty)", false, 0, .{});
break :codepoint;
}
@@ -102,42 +100,42 @@ pub const Cell = struct {
{
const key = std.fmt.bufPrintZ(&buf, "U+{X}", .{self.codepoint}) catch
"<internal error>";
_ = cimgui.c.igSelectable_Bool(key.ptr, false, 0, .{});
_ = cimgui.c.ImGui_SelectableEx(key.ptr, false, 0, .{});
}
// All extras
for (self.cps) |cp| {
const key = std.fmt.bufPrintZ(&buf, "U+{X}", .{cp}) catch
"<internal error>";
_ = cimgui.c.igSelectable_Bool(key.ptr, false, 0, .{});
_ = cimgui.c.ImGui_SelectableEx(key.ptr, false, 0, .{});
}
}
}
}
// Character width property
cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
_ = cimgui.c.igTableSetColumnIndex(0);
cimgui.c.igText("Width Property");
_ = cimgui.c.igTableSetColumnIndex(1);
cimgui.c.igText(@tagName(self.wide));
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Width Property");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text(@tagName(self.wide));
// If we have a color then we show the color
cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
_ = cimgui.c.igTableSetColumnIndex(0);
cimgui.c.igText("Foreground Color");
_ = cimgui.c.igTableSetColumnIndex(1);
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Foreground Color");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
switch (self.style.fg_color) {
.none => cimgui.c.igText("default"),
.none => cimgui.c.ImGui_Text("default"),
.palette => |idx| {
const rgb = t.colors.palette.current[idx];
cimgui.c.igValue_Int("Palette", idx);
cimgui.c.ImGui_Text("Palette %d", idx);
var color: [3]f32 = .{
@as(f32, @floatFromInt(rgb.r)) / 255,
@as(f32, @floatFromInt(rgb.g)) / 255,
@as(f32, @floatFromInt(rgb.b)) / 255,
};
_ = cimgui.c.igColorEdit3(
_ = cimgui.c.ImGui_ColorEdit3(
"color_fg",
&color,
cimgui.c.ImGuiColorEditFlags_DisplayHex |
@@ -152,7 +150,7 @@ pub const Cell = struct {
@as(f32, @floatFromInt(rgb.g)) / 255,
@as(f32, @floatFromInt(rgb.b)) / 255,
};
_ = cimgui.c.igColorEdit3(
_ = cimgui.c.ImGui_ColorEdit3(
"color_fg",
&color,
cimgui.c.ImGuiColorEditFlags_DisplayHex |
@@ -162,21 +160,21 @@ pub const Cell = struct {
},
}
cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
_ = cimgui.c.igTableSetColumnIndex(0);
cimgui.c.igText("Background Color");
_ = cimgui.c.igTableSetColumnIndex(1);
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Background Color");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
switch (self.style.bg_color) {
.none => cimgui.c.igText("default"),
.none => cimgui.c.ImGui_Text("default"),
.palette => |idx| {
const rgb = t.colors.palette.current[idx];
cimgui.c.igValue_Int("Palette", idx);
cimgui.c.ImGui_Text("Palette %d", idx);
var color: [3]f32 = .{
@as(f32, @floatFromInt(rgb.r)) / 255,
@as(f32, @floatFromInt(rgb.g)) / 255,
@as(f32, @floatFromInt(rgb.b)) / 255,
};
_ = cimgui.c.igColorEdit3(
_ = cimgui.c.ImGui_ColorEdit3(
"color_bg",
&color,
cimgui.c.ImGuiColorEditFlags_DisplayHex |
@@ -191,7 +189,7 @@ pub const Cell = struct {
@as(f32, @floatFromInt(rgb.g)) / 255,
@as(f32, @floatFromInt(rgb.b)) / 255,
};
_ = cimgui.c.igColorEdit3(
_ = cimgui.c.ImGui_ColorEdit3(
"color_bg",
&color,
cimgui.c.ImGuiColorEditFlags_DisplayHex |
@@ -209,17 +207,17 @@ pub const Cell = struct {
inline for (styles) |style| style: {
if (!@field(self.style.flags, style)) break :style;
cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
cimgui.c.ImGui_TableNextRow();
{
_ = cimgui.c.igTableSetColumnIndex(0);
cimgui.c.igText(style.ptr);
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text(style.ptr);
}
{
_ = cimgui.c.igTableSetColumnIndex(1);
cimgui.c.igText("true");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("true");
}
}
cimgui.c.igTextDisabled("(Any styles not shown are not currently set)");
cimgui.c.ImGui_TextDisabled("(Any styles not shown are not currently set)");
}
};

View File

@@ -1,4 +1,4 @@
const cimgui = @import("cimgui");
const cimgui = @import("dcimgui");
const terminal = @import("../terminal/main.zig");
/// Render cursor information with a table already open.
@@ -7,57 +7,57 @@ pub fn renderInTable(
cursor: *const terminal.Screen.Cursor,
) void {
{
cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
cimgui.c.ImGui_TableNextRow();
{
_ = cimgui.c.igTableSetColumnIndex(0);
cimgui.c.igText("Position (x, y)");
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Position (x, y)");
}
{
_ = cimgui.c.igTableSetColumnIndex(1);
cimgui.c.igText("(%d, %d)", cursor.x, cursor.y);
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("(%d, %d)", cursor.x, cursor.y);
}
}
{
cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
cimgui.c.ImGui_TableNextRow();
{
_ = cimgui.c.igTableSetColumnIndex(0);
cimgui.c.igText("Style");
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Style");
}
{
_ = cimgui.c.igTableSetColumnIndex(1);
cimgui.c.igText("%s", @tagName(cursor.cursor_style).ptr);
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%s", @tagName(cursor.cursor_style).ptr);
}
}
if (cursor.pending_wrap) {
cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
cimgui.c.ImGui_TableNextRow();
{
_ = cimgui.c.igTableSetColumnIndex(0);
cimgui.c.igText("Pending Wrap");
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Pending Wrap");
}
{
_ = cimgui.c.igTableSetColumnIndex(1);
cimgui.c.igText("%s", if (cursor.pending_wrap) "true".ptr else "false".ptr);
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%s", if (cursor.pending_wrap) "true".ptr else "false".ptr);
}
}
// If we have a color then we show the color
cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
_ = cimgui.c.igTableSetColumnIndex(0);
cimgui.c.igText("Foreground Color");
_ = cimgui.c.igTableSetColumnIndex(1);
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Foreground Color");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
switch (cursor.style.fg_color) {
.none => cimgui.c.igText("default"),
.none => cimgui.c.ImGui_Text("default"),
.palette => |idx| {
const rgb = t.colors.palette.current[idx];
cimgui.c.igValue_Int("Palette", idx);
cimgui.c.ImGui_Text("Palette %d", idx);
var color: [3]f32 = .{
@as(f32, @floatFromInt(rgb.r)) / 255,
@as(f32, @floatFromInt(rgb.g)) / 255,
@as(f32, @floatFromInt(rgb.b)) / 255,
};
_ = cimgui.c.igColorEdit3(
_ = cimgui.c.ImGui_ColorEdit3(
"color_fg",
&color,
cimgui.c.ImGuiColorEditFlags_DisplayHex |
@@ -72,7 +72,7 @@ pub fn renderInTable(
@as(f32, @floatFromInt(rgb.g)) / 255,
@as(f32, @floatFromInt(rgb.b)) / 255,
};
_ = cimgui.c.igColorEdit3(
_ = cimgui.c.ImGui_ColorEdit3(
"color_fg",
&color,
cimgui.c.ImGuiColorEditFlags_DisplayHex |
@@ -82,21 +82,21 @@ pub fn renderInTable(
},
}
cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
_ = cimgui.c.igTableSetColumnIndex(0);
cimgui.c.igText("Background Color");
_ = cimgui.c.igTableSetColumnIndex(1);
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Background Color");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
switch (cursor.style.bg_color) {
.none => cimgui.c.igText("default"),
.none => cimgui.c.ImGui_Text("default"),
.palette => |idx| {
const rgb = t.colors.palette.current[idx];
cimgui.c.igValue_Int("Palette", idx);
cimgui.c.ImGui_Text("Palette %d", idx);
var color: [3]f32 = .{
@as(f32, @floatFromInt(rgb.r)) / 255,
@as(f32, @floatFromInt(rgb.g)) / 255,
@as(f32, @floatFromInt(rgb.b)) / 255,
};
_ = cimgui.c.igColorEdit3(
_ = cimgui.c.ImGui_ColorEdit3(
"color_bg",
&color,
cimgui.c.ImGuiColorEditFlags_DisplayHex |
@@ -111,7 +111,7 @@ pub fn renderInTable(
@as(f32, @floatFromInt(rgb.g)) / 255,
@as(f32, @floatFromInt(rgb.b)) / 255,
};
_ = cimgui.c.igColorEdit3(
_ = cimgui.c.ImGui_ColorEdit3(
"color_bg",
&color,
cimgui.c.ImGuiColorEditFlags_DisplayHex |
@@ -129,14 +129,14 @@ pub fn renderInTable(
inline for (styles) |style| style: {
if (!@field(cursor.style.flags, style)) break :style;
cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
cimgui.c.ImGui_TableNextRow();
{
_ = cimgui.c.igTableSetColumnIndex(0);
cimgui.c.igText(style.ptr);
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text(style.ptr);
}
{
_ = cimgui.c.igTableSetColumnIndex(1);
cimgui.c.igText("true");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("true");
}
}
}

View File

@@ -2,7 +2,7 @@ const std = @import("std");
const Allocator = std.mem.Allocator;
const input = @import("../input.zig");
const CircBuf = @import("../datastruct/main.zig").CircBuf;
const cimgui = @import("cimgui");
const cimgui = @import("dcimgui");
/// Circular buffer of key events.
pub const EventRing = CircBuf(Event, undefined);
@@ -72,30 +72,28 @@ pub const Event = struct {
/// Render this event in the inspector GUI.
pub fn render(self: *const Event) void {
_ = cimgui.c.igBeginTable(
_ = cimgui.c.ImGui_BeginTable(
"##event",
2,
cimgui.c.ImGuiTableFlags_None,
.{ .x = 0, .y = 0 },
0,
);
defer cimgui.c.igEndTable();
defer cimgui.c.ImGui_EndTable();
if (self.binding.len > 0) {
cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
_ = cimgui.c.igTableSetColumnIndex(0);
cimgui.c.igText("Triggered Binding");
_ = cimgui.c.igTableSetColumnIndex(1);
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Triggered Binding");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
const height: f32 = height: {
const item_count: f32 = @floatFromInt(@min(self.binding.len, 5));
const padding = cimgui.c.igGetStyle().*.FramePadding.y * 2;
break :height cimgui.c.igGetTextLineHeightWithSpacing() * item_count + padding;
const padding = cimgui.c.ImGui_GetStyle().*.FramePadding.y * 2;
break :height cimgui.c.ImGui_GetTextLineHeightWithSpacing() * item_count + padding;
};
if (cimgui.c.igBeginListBox("##bindings", .{ .x = 0, .y = height })) {
defer cimgui.c.igEndListBox();
if (cimgui.c.ImGui_BeginListBox("##bindings", .{ .x = 0, .y = height })) {
defer cimgui.c.ImGui_EndListBox();
for (self.binding) |action| {
_ = cimgui.c.igSelectable_Bool(
_ = cimgui.c.ImGui_SelectableEx(
@tagName(action).ptr,
false,
cimgui.c.ImGuiSelectableFlags_None,
@@ -106,64 +104,64 @@ pub const Event = struct {
}
pty: {
cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
_ = cimgui.c.igTableSetColumnIndex(0);
cimgui.c.igText("Encoding to Pty");
_ = cimgui.c.igTableSetColumnIndex(1);
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Encoding to Pty");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
if (self.pty.len == 0) {
cimgui.c.igTextDisabled("(no data)");
cimgui.c.ImGui_TextDisabled("(no data)");
break :pty;
}
self.renderPty() catch {
cimgui.c.igTextDisabled("(error rendering pty data)");
cimgui.c.ImGui_TextDisabled("(error rendering pty data)");
break :pty;
};
}
{
cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
_ = cimgui.c.igTableSetColumnIndex(0);
cimgui.c.igText("Action");
_ = cimgui.c.igTableSetColumnIndex(1);
cimgui.c.igText("%s", @tagName(self.event.action).ptr);
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Action");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%s", @tagName(self.event.action).ptr);
}
{
cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
_ = cimgui.c.igTableSetColumnIndex(0);
cimgui.c.igText("Key");
_ = cimgui.c.igTableSetColumnIndex(1);
cimgui.c.igText("%s", @tagName(self.event.key).ptr);
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Key");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%s", @tagName(self.event.key).ptr);
}
if (!self.event.mods.empty()) {
cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
_ = cimgui.c.igTableSetColumnIndex(0);
cimgui.c.igText("Mods");
_ = cimgui.c.igTableSetColumnIndex(1);
if (self.event.mods.shift) cimgui.c.igText("shift ");
if (self.event.mods.ctrl) cimgui.c.igText("ctrl ");
if (self.event.mods.alt) cimgui.c.igText("alt ");
if (self.event.mods.super) cimgui.c.igText("super ");
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Mods");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
if (self.event.mods.shift) cimgui.c.ImGui_Text("shift ");
if (self.event.mods.ctrl) cimgui.c.ImGui_Text("ctrl ");
if (self.event.mods.alt) cimgui.c.ImGui_Text("alt ");
if (self.event.mods.super) cimgui.c.ImGui_Text("super ");
}
if (self.event.composing) {
cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
_ = cimgui.c.igTableSetColumnIndex(0);
cimgui.c.igText("Composing");
_ = cimgui.c.igTableSetColumnIndex(1);
cimgui.c.igText("true");
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Composing");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("true");
}
utf8: {
cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
_ = cimgui.c.igTableSetColumnIndex(0);
cimgui.c.igText("UTF-8");
_ = cimgui.c.igTableSetColumnIndex(1);
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("UTF-8");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
if (self.event.utf8.len == 0) {
cimgui.c.igTextDisabled("(empty)");
cimgui.c.ImGui_TextDisabled("(empty)");
break :utf8;
}
self.renderUtf8(self.event.utf8) catch {
cimgui.c.igTextDisabled("(error rendering utf-8)");
cimgui.c.ImGui_TextDisabled("(error rendering utf-8)");
break :utf8;
};
}
@@ -187,13 +185,11 @@ pub const Event = struct {
try writer.writeByte(0);
// Render as a textbox
_ = cimgui.c.igInputText(
_ = cimgui.c.ImGui_InputText(
"##utf8",
&buf,
buf_stream.getWritten().len - 1,
cimgui.c.ImGuiInputTextFlags_ReadOnly,
null,
null,
);
}
@@ -223,13 +219,11 @@ pub const Event = struct {
try writer.writeByte(0);
// Render as a textbox
_ = cimgui.c.igInputText(
_ = cimgui.c.ImGui_InputText(
"##pty",
&buf,
buf_stream.getWritten().len - 1,
cimgui.c.ImGuiInputTextFlags_ReadOnly,
null,
null,
);
}
};

View File

@@ -1,167 +1,161 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const cimgui = @import("cimgui");
const cimgui = @import("dcimgui");
const terminal = @import("../terminal/main.zig");
const units = @import("units.zig");
pub fn render(page: *const terminal.Page) void {
cimgui.c.igPushID_Ptr(page);
defer cimgui.c.igPopID();
cimgui.c.ImGui_PushIDPtr(page);
defer cimgui.c.ImGui_PopID();
_ = cimgui.c.igBeginTable(
_ = cimgui.c.ImGui_BeginTable(
"##page_state",
2,
cimgui.c.ImGuiTableFlags_None,
.{ .x = 0, .y = 0 },
0,
);
defer cimgui.c.igEndTable();
defer cimgui.c.ImGui_EndTable();
{
cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
cimgui.c.ImGui_TableNextRow();
{
_ = cimgui.c.igTableSetColumnIndex(0);
cimgui.c.igText("Memory Size");
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Memory Size");
}
{
_ = cimgui.c.igTableSetColumnIndex(1);
cimgui.c.igText("%d bytes (%d KiB)", page.memory.len, units.toKibiBytes(page.memory.len));
cimgui.c.igText("%d VM pages", page.memory.len / std.heap.page_size_min);
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%d bytes (%d KiB)", page.memory.len, units.toKibiBytes(page.memory.len));
cimgui.c.ImGui_Text("%d VM pages", page.memory.len / std.heap.page_size_min);
}
}
{
cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
cimgui.c.ImGui_TableNextRow();
{
_ = cimgui.c.igTableSetColumnIndex(0);
cimgui.c.igText("Unique Styles");
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Unique Styles");
}
{
_ = cimgui.c.igTableSetColumnIndex(1);
cimgui.c.igText("%d", page.styles.count());
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%d", page.styles.count());
}
}
{
cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
cimgui.c.ImGui_TableNextRow();
{
_ = cimgui.c.igTableSetColumnIndex(0);
cimgui.c.igText("Grapheme Entries");
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Grapheme Entries");
}
{
_ = cimgui.c.igTableSetColumnIndex(1);
cimgui.c.igText("%d", page.graphemeCount());
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%d", page.graphemeCount());
}
}
{
cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
cimgui.c.ImGui_TableNextRow();
{
_ = cimgui.c.igTableSetColumnIndex(0);
cimgui.c.igText("Capacity");
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Capacity");
}
{
_ = cimgui.c.igTableSetColumnIndex(1);
_ = cimgui.c.igBeginTable(
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
_ = cimgui.c.ImGui_BeginTable(
"##capacity",
2,
cimgui.c.ImGuiTableFlags_None,
.{ .x = 0, .y = 0 },
0,
);
defer cimgui.c.igEndTable();
defer cimgui.c.ImGui_EndTable();
const cap = page.capacity;
{
cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
cimgui.c.ImGui_TableNextRow();
{
_ = cimgui.c.igTableSetColumnIndex(0);
cimgui.c.igText("Columns");
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Columns");
}
{
_ = cimgui.c.igTableSetColumnIndex(1);
cimgui.c.igText("%d", @as(u32, @intCast(cap.cols)));
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%d", @as(u32, @intCast(cap.cols)));
}
}
{
cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
cimgui.c.ImGui_TableNextRow();
{
_ = cimgui.c.igTableSetColumnIndex(0);
cimgui.c.igText("Rows");
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Rows");
}
{
_ = cimgui.c.igTableSetColumnIndex(1);
cimgui.c.igText("%d", @as(u32, @intCast(cap.rows)));
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%d", @as(u32, @intCast(cap.rows)));
}
}
{
cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
cimgui.c.ImGui_TableNextRow();
{
_ = cimgui.c.igTableSetColumnIndex(0);
cimgui.c.igText("Unique Styles");
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Unique Styles");
}
{
_ = cimgui.c.igTableSetColumnIndex(1);
cimgui.c.igText("%d", @as(u32, @intCast(cap.styles)));
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%d", @as(u32, @intCast(cap.styles)));
}
}
{
cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
cimgui.c.ImGui_TableNextRow();
{
_ = cimgui.c.igTableSetColumnIndex(0);
cimgui.c.igText("Grapheme Bytes");
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Grapheme Bytes");
}
{
_ = cimgui.c.igTableSetColumnIndex(1);
cimgui.c.igText("%d", cap.grapheme_bytes);
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%d", cap.grapheme_bytes);
}
}
}
}
{
cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
cimgui.c.ImGui_TableNextRow();
{
_ = cimgui.c.igTableSetColumnIndex(0);
cimgui.c.igText("Size");
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Size");
}
{
_ = cimgui.c.igTableSetColumnIndex(1);
_ = cimgui.c.igBeginTable(
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
_ = cimgui.c.ImGui_BeginTable(
"##size",
2,
cimgui.c.ImGuiTableFlags_None,
.{ .x = 0, .y = 0 },
0,
);
defer cimgui.c.igEndTable();
defer cimgui.c.ImGui_EndTable();
const size = page.size;
{
cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
cimgui.c.ImGui_TableNextRow();
{
_ = cimgui.c.igTableSetColumnIndex(0);
cimgui.c.igText("Columns");
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Columns");
}
{
_ = cimgui.c.igTableSetColumnIndex(1);
cimgui.c.igText("%d", @as(u32, @intCast(size.cols)));
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%d", @as(u32, @intCast(size.cols)));
}
}
{
cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
cimgui.c.ImGui_TableNextRow();
{
_ = cimgui.c.igTableSetColumnIndex(0);
cimgui.c.igText("Rows");
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Rows");
}
{
_ = cimgui.c.igTableSetColumnIndex(1);
cimgui.c.igText("%d", @as(u32, @intCast(size.rows)));
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%d", @as(u32, @intCast(size.rows)));
}
}
}

View File

@@ -1,6 +1,6 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const cimgui = @import("cimgui");
const cimgui = @import("dcimgui");
const terminal = @import("../terminal/main.zig");
const CircBuf = @import("../datastruct/main.zig").CircBuf;
const Surface = @import("../Surface.zig");
@@ -83,7 +83,7 @@ pub const VTEvent = struct {
/// Returns true if the event passes the given filter.
pub fn passFilter(
self: *const VTEvent,
filter: *cimgui.c.ImGuiTextFilter,
filter: *const cimgui.c.ImGuiTextFilter,
) bool {
// Check our main string
if (cimgui.c.ImGuiTextFilter_PassFilter(
@@ -318,19 +318,18 @@ pub const VTHandler = struct {
/// Exclude certain actions by tag.
filter_exclude: ActionTagSet = .initMany(&.{.print}),
filter_text: *cimgui.c.ImGuiTextFilter,
filter_text: cimgui.c.ImGuiTextFilter = .{},
const ActionTagSet = std.EnumSet(terminal.Parser.Action.Tag);
pub fn init(surface: *Surface) VTHandler {
return .{
.surface = surface,
.filter_text = cimgui.c.ImGuiTextFilter_ImGuiTextFilter(""),
};
}
pub fn deinit(self: *VTHandler) void {
cimgui.c.ImGuiTextFilter_destroy(self.filter_text);
_ = self;
}
pub fn vt(
@@ -371,7 +370,7 @@ pub const VTHandler = struct {
errdefer ev.deinit(alloc);
// Check if the event passes the filter
if (!ev.passFilter(self.filter_text)) {
if (!ev.passFilter(&self.filter_text)) {
ev.deinit(alloc);
return true;
}

View File

@@ -116,6 +116,10 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
/// True if the window is focused
focused: bool,
/// Flag to indicate that our focus state changed for custom
/// shaders to update their state.
custom_shader_focused_changed: bool = false,
/// The most recent scrollbar state. We use this as a cache to
/// determine if we need to notify the apprt that there was a
/// scrollbar change.
@@ -746,6 +750,8 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
.current_cursor_color = @splat(0),
.previous_cursor_color = @splat(0),
.cursor_change_time = 0,
.time_focus = 0,
.focus = 1, // assume focused initially
},
.bg_image_buffer = undefined,
@@ -1008,8 +1014,13 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
///
/// Must be called on the render thread.
pub fn setFocus(self: *Self, focus: bool) !void {
assert(self.focused != focus);
self.focused = focus;
// Flag that we need to update our custom shaders
self.custom_shader_focused_changed = true;
// If we're not focused, then we want to stop the display link
// because it is a waste of resources and we can move to pure
// change-driven updates.
@@ -2255,6 +2266,8 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
// We only need to do this if we have custom shaders.
if (!self.has_custom_shaders) return;
const uniforms = &self.custom_shader_uniforms;
const now = try std.time.Instant.now();
defer self.last_frame_time = now;
const first_frame_time = self.first_frame_time orelse t: {
@@ -2264,23 +2277,23 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
const last_frame_time = self.last_frame_time orelse now;
const since_ns: f32 = @floatFromInt(now.since(first_frame_time));
self.custom_shader_uniforms.time = since_ns / std.time.ns_per_s;
uniforms.time = since_ns / std.time.ns_per_s;
const delta_ns: f32 = @floatFromInt(now.since(last_frame_time));
self.custom_shader_uniforms.time_delta = delta_ns / std.time.ns_per_s;
uniforms.time_delta = delta_ns / std.time.ns_per_s;
self.custom_shader_uniforms.frame += 1;
uniforms.frame += 1;
const screen = self.size.screen;
const padding = self.size.padding;
const cell = self.size.cell;
self.custom_shader_uniforms.resolution = .{
uniforms.resolution = .{
@floatFromInt(screen.width),
@floatFromInt(screen.height),
1,
};
self.custom_shader_uniforms.channel_resolution[0] = .{
uniforms.channel_resolution[0] = .{
@floatFromInt(screen.width),
@floatFromInt(screen.height),
1,
@@ -2345,8 +2358,6 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
@as(f32, @floatFromInt(cursor.color[3])) / 255.0,
};
const uniforms = &self.custom_shader_uniforms;
const cursor_changed: bool =
!std.meta.eql(new_cursor, uniforms.current_cursor) or
!std.meta.eql(cursor_color, uniforms.current_cursor_color);
@@ -2359,6 +2370,19 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
uniforms.cursor_change_time = uniforms.time;
}
}
// Update focus uniforms
uniforms.focus = @intFromBool(self.focused);
// If we need to update the time our focus state changed
// then update it to our current frame time. This may not be
// exactly correct since it is frame time, not exact focus
// time, but focus time on its own isn't exactly correct anyways
// since it comes async from a message.
if (self.custom_shader_focused_changed and self.focused) {
uniforms.time_focus = uniforms.time;
self.custom_shader_focused_changed = false;
}
}
/// Convert the terminal state to GPU cells stored in CPU memory. These

View File

@@ -16,6 +16,8 @@ layout(binding = 1, std140) uniform Globals {
uniform vec4 iCurrentCursorColor;
uniform vec4 iPreviousCursorColor;
uniform float iTimeCursorChange;
uniform float iTimeFocus;
uniform int iFocus;
};
layout(binding = 0) uniform sampler2D iChannel0;

View File

@@ -0,0 +1,41 @@
// Test shader for iTimeFocus and iFocus
// Shows border when focused, green fade that restarts on each focus gain
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 uv = fragCoord / iResolution.xy;
// Sample the terminal content
vec4 terminal = texture2D(iChannel0, uv);
vec3 color = terminal.rgb;
if (iFocus > 0) {
// FOCUSED: Add border and fading green overlay
// Calculate time since focus was gained
float timeSinceFocus = iTime - iTimeFocus;
// Green fade: starts at 1.0 (full green), fades to 0.0 over 3 seconds
float fadeOut = max(0.0, 1.0 - (timeSinceFocus / 3.0));
// Add green overlay that fades out
color = mix(color, vec3(0.0, 1.0, 0.0), fadeOut * 0.4);
// Add border (5 pixels)
float borderSize = 5.0;
vec2 pixelCoord = fragCoord;
bool isBorder = pixelCoord.x < borderSize ||
pixelCoord.x > iResolution.x - borderSize ||
pixelCoord.y < borderSize ||
pixelCoord.y > iResolution.y - borderSize;
if (isBorder) {
// Bright cyan border that pulses subtly
float pulse = sin(timeSinceFocus * 2.0) * 0.1 + 0.9;
color = vec3(0.0, 1.0, 1.0) * pulse;
}
} else {
// UNFOCUSED: Solid red overlay (no border)
color = mix(color, vec3(1.0, 0.0, 0.0), 0.3);
}
fragColor = vec4(color, 1.0);
}

View File

@@ -25,6 +25,8 @@ pub const Uniforms = extern struct {
current_cursor_color: [4]f32 align(16),
previous_cursor_color: [4]f32 align(16),
cursor_change_time: f32 align(4),
time_focus: f32 align(4),
focus: i32 align(4),
};
/// The target to load shaders for.
@@ -412,3 +414,4 @@ test "shadertoy to glsl" {
const test_crt = @embedFile("shaders/test_shadertoy_crt.glsl");
const test_invalid = @embedFile("shaders/test_shadertoy_invalid.glsl");
const test_focus = @embedFile("shaders/test_shadertoy_focus.glsl");