mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
macos: complete cimgui events
This commit is contained in:
@ -405,8 +405,14 @@ void ghostty_inspector_free(ghostty_surface_t);
|
|||||||
bool ghostty_inspector_metal_init(ghostty_inspector_t, void *);
|
bool ghostty_inspector_metal_init(ghostty_inspector_t, void *);
|
||||||
void ghostty_inspector_metal_render(ghostty_inspector_t, void *, void *);
|
void ghostty_inspector_metal_render(ghostty_inspector_t, void *, void *);
|
||||||
bool ghostty_inspector_metal_shutdown(ghostty_inspector_t);
|
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_content_scale(ghostty_inspector_t, double, double);
|
||||||
void ghostty_inspector_set_size(ghostty_inspector_t, uint32_t, uint32_t);
|
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.
|
// 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.
|
// Don't use these unless you know what you're doing.
|
||||||
|
@ -32,6 +32,27 @@ extension Ghostty {
|
|||||||
return flags
|
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.
|
/// A map from the Ghostty key enum to the keyEquivalent string for shortcuts.
|
||||||
static let keyToEquivalent: [ghostty_input_key_e : String] = [
|
static let keyToEquivalent: [ghostty_input_key_e : String] = [
|
||||||
// 0-9
|
// 0-9
|
||||||
|
@ -34,7 +34,7 @@ extension Ghostty {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class InspectorView: MTKView {
|
class InspectorView: MTKView, NSTextInputClient {
|
||||||
let commandQueue: MTLCommandQueue
|
let commandQueue: MTLCommandQueue
|
||||||
|
|
||||||
var surfaceView: SurfaceView? = nil {
|
var surfaceView: SurfaceView? = nil {
|
||||||
@ -46,6 +46,11 @@ extension Ghostty {
|
|||||||
return surfaceView.inspector
|
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?) {
|
override init(frame: CGRect, device: MTLDevice?) {
|
||||||
// Initialize our Metal primitives
|
// Initialize our Metal primitives
|
||||||
guard
|
guard
|
||||||
@ -98,6 +103,26 @@ extension Ghostty {
|
|||||||
|
|
||||||
// MARK: NSView
|
// 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() {
|
override func updateTrackingAreas() {
|
||||||
// To update our tracking area we just recreate it all.
|
// To update our tracking area we just recreate it all.
|
||||||
trackingAreas.forEach { removeTrackingArea($0) }
|
trackingAreas.forEach { removeTrackingArea($0) }
|
||||||
@ -129,6 +154,210 @@ extension Ghostty {
|
|||||||
updateSize()
|
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
|
// MARK: MTKView
|
||||||
|
|
||||||
override func draw(_ dirtyRect: NSRect) {
|
override func draw(_ dirtyRect: NSRect) {
|
||||||
|
@ -568,25 +568,25 @@ extension Ghostty {
|
|||||||
|
|
||||||
override func mouseDown(with event: NSEvent) {
|
override func mouseDown(with event: NSEvent) {
|
||||||
guard let surface = self.surface else { return }
|
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)
|
ghostty_surface_mouse_button(surface, GHOSTTY_MOUSE_PRESS, GHOSTTY_MOUSE_LEFT, mods)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func mouseUp(with event: NSEvent) {
|
override func mouseUp(with event: NSEvent) {
|
||||||
guard let surface = self.surface else { return }
|
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)
|
ghostty_surface_mouse_button(surface, GHOSTTY_MOUSE_RELEASE, GHOSTTY_MOUSE_LEFT, mods)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func rightMouseDown(with event: NSEvent) {
|
override func rightMouseDown(with event: NSEvent) {
|
||||||
guard let surface = self.surface else { return }
|
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)
|
ghostty_surface_mouse_button(surface, GHOSTTY_MOUSE_PRESS, GHOSTTY_MOUSE_RIGHT, mods)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func rightMouseUp(with event: NSEvent) {
|
override func rightMouseUp(with event: NSEvent) {
|
||||||
guard let surface = self.surface else { return }
|
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)
|
ghostty_surface_mouse_button(surface, GHOSTTY_MOUSE_RELEASE, GHOSTTY_MOUSE_RIGHT, mods)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -730,7 +730,7 @@ extension Ghostty {
|
|||||||
|
|
||||||
// The keyAction function will do this AGAIN below which sucks to repeat
|
// The keyAction function will do this AGAIN below which sucks to repeat
|
||||||
// but this is super cheap and flagsChanged isn't that common.
|
// 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
|
// If the key that pressed this is active, its a press, else release
|
||||||
var action = GHOSTTY_ACTION_RELEASE
|
var action = GHOSTTY_ACTION_RELEASE
|
||||||
@ -741,7 +741,7 @@ extension Ghostty {
|
|||||||
|
|
||||||
private func keyAction(_ action: ghostty_input_action_e, event: NSEvent) {
|
private func keyAction(_ action: ghostty_input_action_e, event: NSEvent) {
|
||||||
guard let surface = self.surface else { return }
|
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)
|
ghostty_surface_key(surface, action, UInt32(event.keyCode), mods)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -866,26 +866,6 @@ extension Ghostty {
|
|||||||
print("SEL: \(selector)")
|
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
|
// 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!
|
// glfw mostly since we started as a glfw-based app way back in the day!
|
||||||
static let keycodes: [UInt16 : ghostty_input_key_e] = [
|
static let keycodes: [UInt16 : ghostty_input_key_e] = [
|
||||||
|
@ -815,6 +815,7 @@ pub const Inspector = struct {
|
|||||||
surface: *Surface,
|
surface: *Surface,
|
||||||
ig_ctx: *cimgui.c.ImGuiContext,
|
ig_ctx: *cimgui.c.ImGuiContext,
|
||||||
backend: ?Backend = null,
|
backend: ?Backend = null,
|
||||||
|
keymap_state: input.Keymap.State = .{},
|
||||||
|
|
||||||
/// Our previous instant used to calculate delta time for animations.
|
/// Our previous instant used to calculate delta time for animations.
|
||||||
instant: ?std.time.Instant = null,
|
instant: ?std.time.Instant = null,
|
||||||
@ -848,6 +849,12 @@ pub const Inspector = struct {
|
|||||||
cimgui.c.igDestroyContext(self.ig_ctx);
|
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.
|
/// Initialize the inspector for a metal backend.
|
||||||
pub fn initMetal(self: *Inspector, device: objc.Object) bool {
|
pub fn initMetal(self: *Inspector, device: objc.Object) bool {
|
||||||
defer device.msgSend(void, objc.sel("release"), .{});
|
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 {
|
fn newFrame(self: *Inspector) !void {
|
||||||
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
|
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
|
||||||
|
|
||||||
@ -1252,6 +1415,69 @@ pub const CAPI = struct {
|
|||||||
ptr.updateContentScale(x, y);
|
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.
|
/// 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
|
/// I do this in Zig as an extern function because I don't know how to
|
||||||
/// call these functions in Swift.
|
/// call these functions in Swift.
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
const cimgui = @import("cimgui");
|
||||||
|
|
||||||
/// A generic key input event. This is the information that is necessary
|
/// A generic key input event. This is the information that is necessary
|
||||||
/// regardless of apprt in order to generate the proper terminal
|
/// 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" {
|
test "fromASCII should not return keypad keys" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
try testing.expect(Key.fromASCII('0').? == .zero);
|
try testing.expect(Key.fromASCII('0').? == .zero);
|
||||||
|
Reference in New Issue
Block a user