From d50ff6ece7b417af08bec2d7a4933742ae9f7af4 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 19 Oct 2023 14:09:26 -0700 Subject: [PATCH] macos: complete cimgui events --- include/ghostty.h | 6 + macos/Sources/Ghostty/Ghostty.Input.swift | 21 ++ macos/Sources/Ghostty/InspectorView.swift | 231 +++++++++++++++++++++- macos/Sources/Ghostty/SurfaceView.swift | 34 +--- src/apprt/embedded.zig | 226 +++++++++++++++++++++ src/input/key.zig | 132 +++++++++++++ 6 files changed, 622 insertions(+), 28 deletions(-) diff --git a/include/ghostty.h b/include/ghostty.h index 743f3b3b2..7e248df06 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -405,8 +405,14 @@ void ghostty_inspector_free(ghostty_surface_t); bool ghostty_inspector_metal_init(ghostty_inspector_t, void *); void ghostty_inspector_metal_render(ghostty_inspector_t, void *, void *); bool ghostty_inspector_metal_shutdown(ghostty_inspector_t); +void ghostty_inspector_set_focus(ghostty_inspector_t, bool); void ghostty_inspector_set_content_scale(ghostty_inspector_t, double, double); void ghostty_inspector_set_size(ghostty_inspector_t, uint32_t, uint32_t); +void ghostty_inspector_mouse_button(ghostty_inspector_t, ghostty_input_mouse_state_e, ghostty_input_mouse_button_e, ghostty_input_mods_e); +void ghostty_inspector_mouse_pos(ghostty_inspector_t, double, double); +void ghostty_inspector_mouse_scroll(ghostty_inspector_t, double, double, ghostty_input_scroll_mods_t); +void ghostty_inspector_key(ghostty_inspector_t, ghostty_input_action_e, uint32_t, ghostty_input_mods_e); +void ghostty_inspector_text(ghostty_inspector_t, const char *); // APIs I'd like to get rid of eventually but are still needed for now. // Don't use these unless you know what you're doing. diff --git a/macos/Sources/Ghostty/Ghostty.Input.swift b/macos/Sources/Ghostty/Ghostty.Input.swift index 1655b80ad..c3e8cfbf2 100644 --- a/macos/Sources/Ghostty/Ghostty.Input.swift +++ b/macos/Sources/Ghostty/Ghostty.Input.swift @@ -31,7 +31,28 @@ extension Ghostty { if (mods.rawValue & GHOSTTY_MODS_SUPER.rawValue != 0) { flags.insert(.command) } return flags } + + /// Translate event modifier flags to a ghostty mods enum. + static func ghosttyMods(_ flags: NSEvent.ModifierFlags) -> ghostty_input_mods_e { + var mods: UInt32 = GHOSTTY_MODS_NONE.rawValue + if (flags.contains(.shift)) { mods |= GHOSTTY_MODS_SHIFT.rawValue } + if (flags.contains(.control)) { mods |= GHOSTTY_MODS_CTRL.rawValue } + if (flags.contains(.option)) { mods |= GHOSTTY_MODS_ALT.rawValue } + if (flags.contains(.command)) { mods |= GHOSTTY_MODS_SUPER.rawValue } + if (flags.contains(.capsLock)) { mods |= GHOSTTY_MODS_CAPS.rawValue } + + // Handle sided input. We can't tell that both are pressed in the + // Ghostty structure but thats okay -- we don't use that information. + let rawFlags = flags.rawValue + if (rawFlags & UInt(NX_DEVICERSHIFTKEYMASK) != 0) { mods |= GHOSTTY_MODS_SHIFT_RIGHT.rawValue } + if (rawFlags & UInt(NX_DEVICERCTLKEYMASK) != 0) { mods |= GHOSTTY_MODS_CTRL_RIGHT.rawValue } + if (rawFlags & UInt(NX_DEVICERALTKEYMASK) != 0) { mods |= GHOSTTY_MODS_ALT_RIGHT.rawValue } + if (rawFlags & UInt(NX_DEVICERCMDKEYMASK) != 0) { mods |= GHOSTTY_MODS_SUPER_RIGHT.rawValue } + + return ghostty_input_mods_e(mods) + } + /// A map from the Ghostty key enum to the keyEquivalent string for shortcuts. static let keyToEquivalent: [ghostty_input_key_e : String] = [ // 0-9 diff --git a/macos/Sources/Ghostty/InspectorView.swift b/macos/Sources/Ghostty/InspectorView.swift index 7be50434f..2b5aca393 100644 --- a/macos/Sources/Ghostty/InspectorView.swift +++ b/macos/Sources/Ghostty/InspectorView.swift @@ -34,7 +34,7 @@ extension Ghostty { } } - class InspectorView: MTKView { + class InspectorView: MTKView, NSTextInputClient { let commandQueue: MTLCommandQueue var surfaceView: SurfaceView? = nil { @@ -46,6 +46,11 @@ extension Ghostty { return surfaceView.inspector } + private var markedText: NSMutableAttributedString = NSMutableAttributedString() + + // We need to support being a first responder so that we can get input events + override var acceptsFirstResponder: Bool { return true } + override init(frame: CGRect, device: MTLDevice?) { // Initialize our Metal primitives guard @@ -98,6 +103,26 @@ extension Ghostty { // MARK: NSView + override func becomeFirstResponder() -> Bool { + let result = super.becomeFirstResponder() + if (result) { + if let inspector = self.inspector { + ghostty_inspector_set_focus(inspector, true) + } + } + return result + } + + override func resignFirstResponder() -> Bool { + let result = super.resignFirstResponder() + if (result) { + if let inspector = self.inspector { + ghostty_inspector_set_focus(inspector, false) + } + } + return result + } + override func updateTrackingAreas() { // To update our tracking area we just recreate it all. trackingAreas.forEach { removeTrackingArea($0) } @@ -129,6 +154,210 @@ extension Ghostty { updateSize() } + override func mouseDown(with event: NSEvent) { + guard let inspector = self.inspector else { return } + let mods = Ghostty.ghosttyMods(event.modifierFlags) + ghostty_inspector_mouse_button(inspector, GHOSTTY_MOUSE_PRESS, GHOSTTY_MOUSE_LEFT, mods) + } + + override func mouseUp(with event: NSEvent) { + guard let inspector = self.inspector else { return } + let mods = Ghostty.ghosttyMods(event.modifierFlags) + ghostty_inspector_mouse_button(inspector, GHOSTTY_MOUSE_RELEASE, GHOSTTY_MOUSE_LEFT, mods) + } + + override func rightMouseDown(with event: NSEvent) { + guard let inspector = self.inspector else { return } + let mods = Ghostty.ghosttyMods(event.modifierFlags) + ghostty_inspector_mouse_button(inspector, GHOSTTY_MOUSE_PRESS, GHOSTTY_MOUSE_RIGHT, mods) + } + + override func rightMouseUp(with event: NSEvent) { + guard let inspector = self.inspector else { return } + let mods = Ghostty.ghosttyMods(event.modifierFlags) + ghostty_inspector_mouse_button(inspector, GHOSTTY_MOUSE_RELEASE, GHOSTTY_MOUSE_RIGHT, mods) + } + + override func mouseMoved(with event: NSEvent) { + guard let inspector = self.inspector else { return } + + // Convert window position to view position. Note (0, 0) is bottom left. + let pos = self.convert(event.locationInWindow, from: nil) + ghostty_inspector_mouse_pos(inspector, pos.x, frame.height - pos.y) + + } + + override func mouseDragged(with event: NSEvent) { + self.mouseMoved(with: event) + } + + override func scrollWheel(with event: NSEvent) { + guard let inspector = self.inspector else { return } + + // Builds up the "input.ScrollMods" bitmask + var mods: Int32 = 0 + + var x = event.scrollingDeltaX + var y = event.scrollingDeltaY + if event.hasPreciseScrollingDeltas { + mods = 1 + + // We do a 2x speed multiplier. This is subjective, it "feels" better to me. + x *= 2; + y *= 2; + + // TODO(mitchellh): do we have to scale the x/y here by window scale factor? + } + + // Determine our momentum value + var momentum: ghostty_input_mouse_momentum_e = GHOSTTY_MOUSE_MOMENTUM_NONE + switch (event.momentumPhase) { + case .began: + momentum = GHOSTTY_MOUSE_MOMENTUM_BEGAN + case .stationary: + momentum = GHOSTTY_MOUSE_MOMENTUM_STATIONARY + case .changed: + momentum = GHOSTTY_MOUSE_MOMENTUM_CHANGED + case .ended: + momentum = GHOSTTY_MOUSE_MOMENTUM_ENDED + case .cancelled: + momentum = GHOSTTY_MOUSE_MOMENTUM_CANCELLED + case .mayBegin: + momentum = GHOSTTY_MOUSE_MOMENTUM_MAY_BEGIN + default: + break + } + + // Pack our momentum value into the mods bitmask + mods |= Int32(momentum.rawValue) << 1 + + ghostty_inspector_mouse_scroll(inspector, x, y, mods) + } + + override func keyDown(with event: NSEvent) { + let action = event.isARepeat ? GHOSTTY_ACTION_REPEAT : GHOSTTY_ACTION_PRESS + keyAction(action, event: event) + + // We specifically DO NOT call interpretKeyEvents because ghostty_surface_key + // automatically handles all key translation, and we don't handle any commands + // currently. + // + // It is possible that in the future we'll have to modify ghostty_surface_key + // and the embedding API so that we can call this because macOS needs to do + // some things with certain keys. I'm not sure. For now this works. + // + // self.interpretKeyEvents([event]) + } + + override func keyUp(with event: NSEvent) { + keyAction(GHOSTTY_ACTION_RELEASE, event: event) + } + + override func flagsChanged(with event: NSEvent) { + let mod: UInt32; + switch (event.keyCode) { + case 0x39: mod = GHOSTTY_MODS_CAPS.rawValue + case 0x38, 0x3C: mod = GHOSTTY_MODS_SHIFT.rawValue + case 0x3B, 0x3E: mod = GHOSTTY_MODS_CTRL.rawValue + case 0x3A, 0x3D: mod = GHOSTTY_MODS_ALT.rawValue + case 0x37, 0x36: mod = GHOSTTY_MODS_SUPER.rawValue + default: return + } + + // The keyAction function will do this AGAIN below which sucks to repeat + // but this is super cheap and flagsChanged isn't that common. + let mods = Ghostty.ghosttyMods(event.modifierFlags) + + // If the key that pressed this is active, its a press, else release + var action = GHOSTTY_ACTION_RELEASE + if (mods.rawValue & mod != 0) { action = GHOSTTY_ACTION_PRESS } + + keyAction(action, event: event) + } + + private func keyAction(_ action: ghostty_input_action_e, event: NSEvent) { + guard let inspector = self.inspector else { return } + let mods = Ghostty.ghosttyMods(event.modifierFlags) + ghostty_inspector_key(inspector, action, UInt32(event.keyCode), mods) + } + + // MARK: NSTextInputClient + + func hasMarkedText() -> Bool { + return markedText.length > 0 + } + + func markedRange() -> NSRange { + guard markedText.length > 0 else { return NSRange() } + return NSRange(0...(markedText.length-1)) + } + + func selectedRange() -> NSRange { + return NSRange() + } + + func setMarkedText(_ string: Any, selectedRange: NSRange, replacementRange: NSRange) { + switch string { + case let v as NSAttributedString: + self.markedText = NSMutableAttributedString(attributedString: v) + + case let v as String: + self.markedText = NSMutableAttributedString(string: v) + + default: + print("unknown marked text: \(string)") + } + } + + func unmarkText() { + self.markedText.mutableString.setString("") + } + + func validAttributesForMarkedText() -> [NSAttributedString.Key] { + return [] + } + + func attributedSubstring(forProposedRange range: NSRange, actualRange: NSRangePointer?) -> NSAttributedString? { + return nil + } + + func characterIndex(for point: NSPoint) -> Int { + return 0 + } + + func firstRect(forCharacterRange range: NSRange, actualRange: NSRangePointer?) -> NSRect { + return NSMakeRect(frame.origin.x, frame.origin.y, 0, 0) + } + + func insertText(_ string: Any, replacementRange: NSRange) { + // We must have an associated event + guard NSApp.currentEvent != nil else { return } + guard let inspector = self.inspector else { return } + + // We want the string view of the any value + var chars = "" + switch (string) { + case let v as NSAttributedString: + chars = v.string + case let v as String: + chars = v + default: + return + } + + let len = chars.utf8CString.count + if (len == 0) { return } + + chars.withCString { ptr in + ghostty_inspector_text(inspector, ptr) + } + } + + override func doCommand(by selector: Selector) { + // This currently just prevents NSBeep from interpretKeyEvents but in the future + // we may want to make some of this work. + } + // MARK: MTKView override func draw(_ dirtyRect: NSRect) { diff --git a/macos/Sources/Ghostty/SurfaceView.swift b/macos/Sources/Ghostty/SurfaceView.swift index a0bfde298..3640e5daf 100644 --- a/macos/Sources/Ghostty/SurfaceView.swift +++ b/macos/Sources/Ghostty/SurfaceView.swift @@ -568,28 +568,28 @@ extension Ghostty { override func mouseDown(with event: NSEvent) { guard let surface = self.surface else { return } - let mods = Self.translateFlags(event.modifierFlags) + let mods = Ghostty.ghosttyMods(event.modifierFlags) ghostty_surface_mouse_button(surface, GHOSTTY_MOUSE_PRESS, GHOSTTY_MOUSE_LEFT, mods) } override func mouseUp(with event: NSEvent) { guard let surface = self.surface else { return } - let mods = Self.translateFlags(event.modifierFlags) + let mods = Ghostty.ghosttyMods(event.modifierFlags) ghostty_surface_mouse_button(surface, GHOSTTY_MOUSE_RELEASE, GHOSTTY_MOUSE_LEFT, mods) } override func rightMouseDown(with event: NSEvent) { guard let surface = self.surface else { return } - let mods = Self.translateFlags(event.modifierFlags) + let mods = Ghostty.ghosttyMods(event.modifierFlags) ghostty_surface_mouse_button(surface, GHOSTTY_MOUSE_PRESS, GHOSTTY_MOUSE_RIGHT, mods) } override func rightMouseUp(with event: NSEvent) { guard let surface = self.surface else { return } - let mods = Self.translateFlags(event.modifierFlags) + let mods = Ghostty.ghosttyMods(event.modifierFlags) ghostty_surface_mouse_button(surface, GHOSTTY_MOUSE_RELEASE, GHOSTTY_MOUSE_RIGHT, mods) } - + override func mouseMoved(with event: NSEvent) { guard let surface = self.surface else { return } @@ -730,7 +730,7 @@ extension Ghostty { // The keyAction function will do this AGAIN below which sucks to repeat // but this is super cheap and flagsChanged isn't that common. - let mods = Self.translateFlags(event.modifierFlags) + let mods = Ghostty.ghosttyMods(event.modifierFlags) // If the key that pressed this is active, its a press, else release var action = GHOSTTY_ACTION_RELEASE @@ -741,7 +741,7 @@ extension Ghostty { private func keyAction(_ action: ghostty_input_action_e, event: NSEvent) { guard let surface = self.surface else { return } - let mods = Self.translateFlags(event.modifierFlags) + let mods = Ghostty.ghosttyMods(event.modifierFlags) ghostty_surface_key(surface, action, UInt32(event.keyCode), mods) } @@ -866,26 +866,6 @@ extension Ghostty { print("SEL: \(selector)") } - private static func translateFlags(_ flags: NSEvent.ModifierFlags) -> ghostty_input_mods_e { - var mods: UInt32 = GHOSTTY_MODS_NONE.rawValue - - if (flags.contains(.shift)) { mods |= GHOSTTY_MODS_SHIFT.rawValue } - if (flags.contains(.control)) { mods |= GHOSTTY_MODS_CTRL.rawValue } - if (flags.contains(.option)) { mods |= GHOSTTY_MODS_ALT.rawValue } - if (flags.contains(.command)) { mods |= GHOSTTY_MODS_SUPER.rawValue } - if (flags.contains(.capsLock)) { mods |= GHOSTTY_MODS_CAPS.rawValue } - - // Handle sided input. We can't tell that both are pressed in the - // Ghostty structure but thats okay -- we don't use that information. - let rawFlags = flags.rawValue - if (rawFlags & UInt(NX_DEVICERSHIFTKEYMASK) != 0) { mods |= GHOSTTY_MODS_SHIFT_RIGHT.rawValue } - if (rawFlags & UInt(NX_DEVICERCTLKEYMASK) != 0) { mods |= GHOSTTY_MODS_CTRL_RIGHT.rawValue } - if (rawFlags & UInt(NX_DEVICERALTKEYMASK) != 0) { mods |= GHOSTTY_MODS_ALT_RIGHT.rawValue } - if (rawFlags & UInt(NX_DEVICERCMDKEYMASK) != 0) { mods |= GHOSTTY_MODS_SUPER_RIGHT.rawValue } - - return ghostty_input_mods_e(mods) - } - // Mapping of event keyCode to ghostty input key values. This is cribbed from // glfw mostly since we started as a glfw-based app way back in the day! static let keycodes: [UInt16 : ghostty_input_key_e] = [ diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index 2b4732531..cb9f1b77e 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -815,6 +815,7 @@ pub const Inspector = struct { surface: *Surface, ig_ctx: *cimgui.c.ImGuiContext, backend: ?Backend = null, + keymap_state: input.Keymap.State = .{}, /// Our previous instant used to calculate delta time for animations. instant: ?std.time.Instant = null, @@ -848,6 +849,12 @@ pub const Inspector = struct { cimgui.c.igDestroyContext(self.ig_ctx); } + /// Queue a render for the next frame. + pub fn queueRender(self: *Inspector) void { + // TODO + _ = self; + } + /// Initialize the inspector for a metal backend. pub fn initMetal(self: *Inspector, device: objc.Object) bool { defer device.msgSend(void, objc.sel("release"), .{}); @@ -928,6 +935,162 @@ pub const Inspector = struct { }; } + pub fn mouseButtonCallback( + self: *Inspector, + action: input.MouseButtonState, + button: input.MouseButton, + mods: input.Mods, + ) void { + _ = mods; + + self.queueRender(); + cimgui.c.igSetCurrentContext(self.ig_ctx); + const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO(); + + const imgui_button = switch (button) { + .left => cimgui.c.ImGuiMouseButton_Left, + .middle => cimgui.c.ImGuiMouseButton_Middle, + .right => cimgui.c.ImGuiMouseButton_Right, + else => return, // unsupported + }; + + cimgui.c.ImGuiIO_AddMouseButtonEvent(io, imgui_button, action == .press); + } + + pub fn scrollCallback( + self: *Inspector, + xoff: f64, + yoff: f64, + mods: input.ScrollMods, + ) void { + _ = mods; + + self.queueRender(); + cimgui.c.igSetCurrentContext(self.ig_ctx); + const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO(); + cimgui.c.ImGuiIO_AddMouseWheelEvent( + io, + @floatCast(xoff), + @floatCast(yoff), + ); + } + + 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.ImGuiIO_AddMousePosEvent(io, @floatCast(x), @floatCast(y)); + } + + 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.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.ImGuiIO_AddInputCharactersUTF8(io, text.ptr); + } + + pub fn keyCallback( + self: *Inspector, + action: input.Action, + keycode: u32, + mods: input.Mods, + ) !void { + // True if this is a key down event + const is_down = action == .press or action == .repeat; + + // Translate our key using the keymap for our localized keyboard layout. + // We only translate for keydown events. Otherwise, we only care about + // the raw keycode. + var buf: [128]u8 = undefined; + const result: input.Keymap.Translation = if (is_down) translate: { + const result = try self.surface.app.keymap.translate( + &buf, + &self.keymap_state, + @intCast(keycode), + mods, + ); + + // If this is a dead key, then we're composing a character and + // we don't do anything. + if (result.composing) return; + + // If the text is just a single non-printable ASCII character + // then we clear the text. We handle non-printables in the + // key encoder manual (such as tab, ctrl+c, etc.) + if (result.text.len == 1 and result.text[0] < 0x20) { + break :translate .{ .composing = false, .text = "" }; + } + + break :translate result; + } else .{ .composing = false, .text = "" }; + + // We want to get the physical unmapped key to process keybinds. + const physical_key = keycode: for (input.keycodes.entries) |entry| { + if (entry.native == keycode) break :keycode entry.key; + } else .invalid; + + // If the resulting text has length 1 then we can take its key + // and attempt to translate it to a key enum and call the key callback. + // If the length is greater than 1 then we're going to call the + // charCallback. + // + // We also only do key translation if this is not a dead key. + const key = if (!result.composing) key: { + // If our physical key is a keypad key, we use that. + if (physical_key.keypad()) break :key physical_key; + + // A completed key. If the length of the key is one then we can + // attempt to translate it to a key enum and call the key + // callback. First try plain ASCII. + if (result.text.len > 0) { + if (input.Key.fromASCII(result.text[0])) |key| { + break :key key; + } + } + + break :key physical_key; + } else .invalid; + + self.queueRender(); + cimgui.c.igSetCurrentContext(self.ig_ctx); + const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO(); + + // Update all our modifiers + 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); + + // Send our keypress + if (key.imguiKey()) |imgui_key| { + cimgui.c.ImGuiIO_AddKeyEvent( + io, + imgui_key, + action == .press or action == .repeat, + ); + } + + // Send any text + if (result.text.len > 0) text: { + const view = std.unicode.Utf8View.init(result.text) catch |err| { + log.warn("cannot build utf8 view over input: {}", .{err}); + break :text; + }; + var it = view.iterator(); + + while (it.nextCodepoint()) |cp| { + cimgui.c.ImGuiIO_AddInputCharacter(io, cp); + } + } + } + fn newFrame(self: *Inspector) !void { const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO(); @@ -1252,6 +1415,69 @@ pub const CAPI = struct { ptr.updateContentScale(x, y); } + export fn ghostty_inspector_mouse_button( + ptr: *Inspector, + action: input.MouseButtonState, + button: input.MouseButton, + mods: c_int, + ) void { + ptr.mouseButtonCallback( + action, + button, + @bitCast(@as( + input.Mods.Backing, + @truncate(@as(c_uint, @bitCast(mods))), + )), + ); + } + + export fn ghostty_inspector_mouse_pos(ptr: *Inspector, x: f64, y: f64) void { + ptr.cursorPosCallback(x, y); + } + + export fn ghostty_inspector_mouse_scroll( + ptr: *Inspector, + x: f64, + y: f64, + scroll_mods: c_int, + ) void { + ptr.scrollCallback( + x, + y, + @bitCast(@as(u8, @truncate(@as(c_uint, @bitCast(scroll_mods))))), + ); + } + + export fn ghostty_inspector_key( + ptr: *Inspector, + action: input.Action, + keycode: u32, + c_mods: c_int, + ) void { + ptr.keyCallback( + action, + keycode, + @bitCast(@as( + input.Mods.Backing, + @truncate(@as(c_uint, @bitCast(c_mods))), + )), + ) catch |err| { + log.err("error processing key event err={}", .{err}); + return; + }; + } + + export fn ghostty_inspector_text( + ptr: *Inspector, + str: [*:0]const u8, + ) void { + ptr.textCallback(std.mem.sliceTo(str, 0)); + } + + export fn ghostty_inspector_set_focus(ptr: *Inspector, focused: bool) void { + ptr.focusCallback(focused); + } + /// Sets the window background blur on macOS to the desired value. /// I do this in Zig as an extern function because I don't know how to /// call these functions in Swift. diff --git a/src/input/key.zig b/src/input/key.zig index b49e524c5..c4aa9c3b7 100644 --- a/src/input/key.zig +++ b/src/input/key.zig @@ -1,5 +1,6 @@ const std = @import("std"); const Allocator = std.mem.Allocator; +const cimgui = @import("cimgui"); /// A generic key input event. This is the information that is necessary /// regardless of apprt in order to generate the proper terminal @@ -377,6 +378,137 @@ pub const Key = enum(c_int) { }; } + /// Returns the cimgui key constant for this key. + pub fn imguiKey(self: Key) ?c_uint { + return switch (self) { + .a => cimgui.c.ImGuiKey_A, + .b => cimgui.c.ImGuiKey_B, + .c => cimgui.c.ImGuiKey_C, + .d => cimgui.c.ImGuiKey_D, + .e => cimgui.c.ImGuiKey_E, + .f => cimgui.c.ImGuiKey_F, + .g => cimgui.c.ImGuiKey_G, + .h => cimgui.c.ImGuiKey_H, + .i => cimgui.c.ImGuiKey_I, + .j => cimgui.c.ImGuiKey_J, + .k => cimgui.c.ImGuiKey_K, + .l => cimgui.c.ImGuiKey_L, + .m => cimgui.c.ImGuiKey_M, + .n => cimgui.c.ImGuiKey_N, + .o => cimgui.c.ImGuiKey_O, + .p => cimgui.c.ImGuiKey_P, + .q => cimgui.c.ImGuiKey_Q, + .r => cimgui.c.ImGuiKey_R, + .s => cimgui.c.ImGuiKey_S, + .t => cimgui.c.ImGuiKey_T, + .u => cimgui.c.ImGuiKey_U, + .v => cimgui.c.ImGuiKey_V, + .w => cimgui.c.ImGuiKey_W, + .x => cimgui.c.ImGuiKey_X, + .y => cimgui.c.ImGuiKey_Y, + .z => cimgui.c.ImGuiKey_Z, + + .zero => cimgui.c.ImGuiKey_0, + .one => cimgui.c.ImGuiKey_1, + .two => cimgui.c.ImGuiKey_2, + .three => cimgui.c.ImGuiKey_3, + .four => cimgui.c.ImGuiKey_4, + .five => cimgui.c.ImGuiKey_5, + .six => cimgui.c.ImGuiKey_6, + .seven => cimgui.c.ImGuiKey_7, + .eight => cimgui.c.ImGuiKey_8, + .nine => cimgui.c.ImGuiKey_9, + + .semicolon => cimgui.c.ImGuiKey_Semicolon, + .space => cimgui.c.ImGuiKey_Space, + .apostrophe => cimgui.c.ImGuiKey_Apostrophe, + .comma => cimgui.c.ImGuiKey_Comma, + .grave_accent => cimgui.c.ImGuiKey_GraveAccent, + .period => cimgui.c.ImGuiKey_Period, + .slash => cimgui.c.ImGuiKey_Slash, + .minus => cimgui.c.ImGuiKey_Minus, + .equal => cimgui.c.ImGuiKey_Equal, + .left_bracket => cimgui.c.ImGuiKey_LeftBracket, + .right_bracket => cimgui.c.ImGuiKey_RightBracket, + .backslash => cimgui.c.ImGuiKey_Backslash, + + .up => cimgui.c.ImGuiKey_UpArrow, + .down => cimgui.c.ImGuiKey_DownArrow, + .left => cimgui.c.ImGuiKey_LeftArrow, + .right => cimgui.c.ImGuiKey_RightArrow, + .home => cimgui.c.ImGuiKey_Home, + .end => cimgui.c.ImGuiKey_End, + .insert => cimgui.c.ImGuiKey_Insert, + .delete => cimgui.c.ImGuiKey_Delete, + .caps_lock => cimgui.c.ImGuiKey_CapsLock, + .scroll_lock => cimgui.c.ImGuiKey_ScrollLock, + .num_lock => cimgui.c.ImGuiKey_NumLock, + .page_up => cimgui.c.ImGuiKey_PageUp, + .page_down => cimgui.c.ImGuiKey_PageDown, + .escape => cimgui.c.ImGuiKey_Escape, + .enter => cimgui.c.ImGuiKey_Enter, + .tab => cimgui.c.ImGuiKey_Tab, + .backspace => cimgui.c.ImGuiKey_Backspace, + .print_screen => cimgui.c.ImGuiKey_PrintScreen, + .pause => cimgui.c.ImGuiKey_Pause, + + .f1 => cimgui.c.ImGuiKey_F1, + .f2 => cimgui.c.ImGuiKey_F2, + .f3 => cimgui.c.ImGuiKey_F3, + .f4 => cimgui.c.ImGuiKey_F4, + .f5 => cimgui.c.ImGuiKey_F5, + .f6 => cimgui.c.ImGuiKey_F6, + .f7 => cimgui.c.ImGuiKey_F7, + .f8 => cimgui.c.ImGuiKey_F8, + .f9 => cimgui.c.ImGuiKey_F9, + .f10 => cimgui.c.ImGuiKey_F10, + .f11 => cimgui.c.ImGuiKey_F11, + .f12 => cimgui.c.ImGuiKey_F12, + + .kp_0 => cimgui.c.ImGuiKey_Keypad0, + .kp_1 => cimgui.c.ImGuiKey_Keypad1, + .kp_2 => cimgui.c.ImGuiKey_Keypad2, + .kp_3 => cimgui.c.ImGuiKey_Keypad3, + .kp_4 => cimgui.c.ImGuiKey_Keypad4, + .kp_5 => cimgui.c.ImGuiKey_Keypad5, + .kp_6 => cimgui.c.ImGuiKey_Keypad6, + .kp_7 => cimgui.c.ImGuiKey_Keypad7, + .kp_8 => cimgui.c.ImGuiKey_Keypad8, + .kp_9 => cimgui.c.ImGuiKey_Keypad9, + .kp_decimal => cimgui.c.ImGuiKey_KeypadDecimal, + .kp_divide => cimgui.c.ImGuiKey_KeypadDivide, + .kp_multiply => cimgui.c.ImGuiKey_KeypadMultiply, + .kp_subtract => cimgui.c.ImGuiKey_KeypadSubtract, + .kp_add => cimgui.c.ImGuiKey_KeypadAdd, + .kp_enter => cimgui.c.ImGuiKey_KeypadEnter, + .kp_equal => cimgui.c.ImGuiKey_KeypadEqual, + + .left_shift => cimgui.c.ImGuiKey_LeftShift, + .left_control => cimgui.c.ImGuiKey_LeftCtrl, + .left_alt => cimgui.c.ImGuiKey_LeftAlt, + .left_super => cimgui.c.ImGuiKey_LeftSuper, + .right_shift => cimgui.c.ImGuiKey_RightShift, + .right_control => cimgui.c.ImGuiKey_RightCtrl, + .right_alt => cimgui.c.ImGuiKey_RightAlt, + .right_super => cimgui.c.ImGuiKey_RightSuper, + + .invalid, + .f13, + .f14, + .f15, + .f16, + .f17, + .f18, + .f19, + .f20, + .f21, + .f22, + .f23, + .f24, + .f25, + => null, + }; + } test "fromASCII should not return keypad keys" { const testing = std.testing; try testing.expect(Key.fromASCII('0').? == .zero);