mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 08:16:13 +03:00
Merge pull request #68 from mitchellh/window-abs
Enable alternate windowing runtimes
This commit is contained in:
@ -203,7 +203,7 @@ fn setQuit(self: *App) !void {
|
||||
|
||||
// Mark that all our windows should close
|
||||
for (self.windows.items) |window| {
|
||||
window.window.setShouldClose(true);
|
||||
window.window.setShouldClose();
|
||||
}
|
||||
}
|
||||
|
||||
|
675
src/Window.zig
675
src/Window.zig
File diff suppressed because it is too large
Load Diff
27
src/apprt.zig
Normal file
27
src/apprt.zig
Normal file
@ -0,0 +1,27 @@
|
||||
//! "apprt" is the "application runtime" package. This abstracts the
|
||||
//! application runtime and lifecycle management such as creating windows,
|
||||
//! getting user input (mouse/keyboard), etc.
|
||||
//!
|
||||
//! This enables compile-time interfaces to be built to swap out the underlying
|
||||
//! application runtime. For example: glfw, pure macOS Cocoa, GTK+, browser, etc.
|
||||
//!
|
||||
//! The goal is to have different implementations share as much of the core
|
||||
//! logic as possible, and to only reach out to platform-specific implementation
|
||||
//! code when absolutely necessary.
|
||||
const builtin = @import("builtin");
|
||||
|
||||
pub usingnamespace @import("apprt/structs.zig");
|
||||
pub const glfw = @import("apprt/glfw.zig");
|
||||
pub const Window = @import("apprt/Window.zig");
|
||||
|
||||
/// The implementation to use for the app runtime. This is comptime chosen
|
||||
/// so that every build has exactly one application runtime implementation.
|
||||
/// Note: it is very rare to use Runtime directly; most usage will use
|
||||
/// Window or something.
|
||||
pub const runtime = switch (builtin.os.tag) {
|
||||
else => glfw,
|
||||
};
|
||||
|
||||
test {
|
||||
@import("std").testing.refAllDecls(@This());
|
||||
}
|
531
src/apprt/glfw.zig
Normal file
531
src/apprt/glfw.zig
Normal file
@ -0,0 +1,531 @@
|
||||
//! Application runtime implementation that uses GLFW (https://www.glfw.org/).
|
||||
//!
|
||||
//! This works on macOS and Linux with OpenGL and Metal.
|
||||
//! (The above sentence may be out of date).
|
||||
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const assert = std.debug.assert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const trace = @import("tracy").trace;
|
||||
const glfw = @import("glfw");
|
||||
const objc = @import("objc");
|
||||
const App = @import("../App.zig");
|
||||
const input = @import("../input.zig");
|
||||
const internal_os = @import("../os/main.zig");
|
||||
const renderer = @import("../renderer.zig");
|
||||
const Renderer = renderer.Renderer;
|
||||
const apprt = @import("../apprt.zig");
|
||||
const CoreWindow = @import("../Window.zig");
|
||||
|
||||
// Get native API access on certain platforms so we can do more customization.
|
||||
const glfwNative = glfw.Native(.{
|
||||
.cocoa = builtin.target.isDarwin(),
|
||||
});
|
||||
|
||||
const log = std.log.scoped(.glfw);
|
||||
|
||||
pub const Window = struct {
|
||||
/// The glfw window handle
|
||||
window: glfw.Window,
|
||||
|
||||
/// The glfw mouse cursor handle.
|
||||
cursor: glfw.Cursor,
|
||||
|
||||
pub fn init(app: *const App, core_win: *CoreWindow) !Window {
|
||||
// Create our window
|
||||
const win = try glfw.Window.create(
|
||||
640,
|
||||
480,
|
||||
"ghostty",
|
||||
null,
|
||||
null,
|
||||
Renderer.glfwWindowHints(),
|
||||
);
|
||||
errdefer win.destroy();
|
||||
|
||||
if (builtin.mode == .Debug) {
|
||||
// Get our physical DPI - debug only because we don't have a use for
|
||||
// this but the logging of it may be useful
|
||||
const monitor = win.getMonitor() orelse monitor: {
|
||||
log.warn("window had null monitor, getting primary monitor", .{});
|
||||
break :monitor glfw.Monitor.getPrimary().?;
|
||||
};
|
||||
const physical_size = monitor.getPhysicalSize();
|
||||
const video_mode = try monitor.getVideoMode();
|
||||
const physical_x_dpi = @intToFloat(f32, video_mode.getWidth()) / (@intToFloat(f32, physical_size.width_mm) / 25.4);
|
||||
const physical_y_dpi = @intToFloat(f32, video_mode.getHeight()) / (@intToFloat(f32, physical_size.height_mm) / 25.4);
|
||||
log.debug("physical dpi x={} y={}", .{
|
||||
physical_x_dpi,
|
||||
physical_y_dpi,
|
||||
});
|
||||
}
|
||||
|
||||
// On Mac, enable tabbing
|
||||
if (comptime builtin.target.isDarwin()) {
|
||||
const NSWindowTabbingMode = enum(usize) { automatic = 0, preferred = 1, disallowed = 2 };
|
||||
const nswindow = objc.Object.fromId(glfwNative.getCocoaWindow(win).?);
|
||||
|
||||
// Tabbing mode enables tabbing at all
|
||||
nswindow.setProperty("tabbingMode", NSWindowTabbingMode.automatic);
|
||||
|
||||
// All windows within a tab bar must have a matching tabbing ID.
|
||||
// The app sets this up for us.
|
||||
nswindow.setProperty("tabbingIdentifier", app.darwin.tabbing_id);
|
||||
}
|
||||
|
||||
// Create the cursor
|
||||
const cursor = try glfw.Cursor.createStandard(.ibeam);
|
||||
errdefer cursor.destroy();
|
||||
if ((comptime !builtin.target.isDarwin()) or internal_os.macosVersionAtLeast(13, 0, 0)) {
|
||||
// We only set our cursor if we're NOT on Mac, or if we are then the
|
||||
// macOS version is >= 13 (Ventura). On prior versions, glfw crashes
|
||||
// since we use a tab group.
|
||||
try win.setCursor(cursor);
|
||||
}
|
||||
|
||||
// Set our callbacks
|
||||
win.setUserPointer(core_win);
|
||||
win.setSizeCallback(sizeCallback);
|
||||
win.setCharCallback(charCallback);
|
||||
win.setKeyCallback(keyCallback);
|
||||
win.setFocusCallback(focusCallback);
|
||||
win.setRefreshCallback(refreshCallback);
|
||||
win.setScrollCallback(scrollCallback);
|
||||
win.setCursorPosCallback(cursorPosCallback);
|
||||
win.setMouseButtonCallback(mouseButtonCallback);
|
||||
|
||||
// Build our result
|
||||
return Window{
|
||||
.window = win,
|
||||
.cursor = cursor,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Window) void {
|
||||
var tabgroup_opt: if (builtin.target.isDarwin()) ?objc.Object else void = undefined;
|
||||
if (comptime builtin.target.isDarwin()) {
|
||||
const nswindow = objc.Object.fromId(glfwNative.getCocoaWindow(self.window).?);
|
||||
const tabgroup = nswindow.getProperty(objc.Object, "tabGroup");
|
||||
|
||||
// On macOS versions prior to Ventura, we lose window focus on tab close
|
||||
// for some reason. We manually fix this by keeping track of the tab
|
||||
// group and just selecting the next window.
|
||||
if (internal_os.macosVersionAtLeast(13, 0, 0))
|
||||
tabgroup_opt = null
|
||||
else
|
||||
tabgroup_opt = tabgroup;
|
||||
|
||||
const windows = tabgroup.getProperty(objc.Object, "windows");
|
||||
switch (windows.getProperty(usize, "count")) {
|
||||
// If we're going down to one window our tab bar is going to be
|
||||
// destroyed so unset it so that the later logic doesn't try to
|
||||
// use it.
|
||||
1 => tabgroup_opt = null,
|
||||
|
||||
// If our tab bar is visible and we are going down to 1 window,
|
||||
// hide the tab bar. The check is "2" because our current window
|
||||
// is still present.
|
||||
2 => if (tabgroup.getProperty(bool, "tabBarVisible")) {
|
||||
nswindow.msgSend(void, objc.sel("toggleTabBar:"), .{nswindow.value});
|
||||
},
|
||||
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
// We can now safely destroy our windows. We have to do this BEFORE
|
||||
// setting up the new focused window below.
|
||||
self.window.destroy();
|
||||
self.cursor.destroy();
|
||||
|
||||
// If we have a tabgroup set, we want to manually focus the next window.
|
||||
// We should NOT have to do this usually, see the comments above.
|
||||
if (comptime builtin.target.isDarwin()) {
|
||||
if (tabgroup_opt) |tabgroup| {
|
||||
const selected = tabgroup.getProperty(objc.Object, "selectedWindow");
|
||||
selected.msgSend(void, objc.sel("makeKeyWindow"), .{});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the size limits of the window.
|
||||
/// Note: this interface is not good, we should redo it if we plan
|
||||
/// to use this more. i.e. you can't set max width but no max height,
|
||||
/// or no mins.
|
||||
pub fn setSizeLimits(self: *Window, min: apprt.WindowSize, max_: ?apprt.WindowSize) !void {
|
||||
try self.window.setSizeLimits(.{
|
||||
.width = min.width,
|
||||
.height = min.height,
|
||||
}, if (max_) |max| .{
|
||||
.width = max.width,
|
||||
.height = max.height,
|
||||
} else .{
|
||||
.width = null,
|
||||
.height = null,
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns the content scale for the created window.
|
||||
pub fn getContentScale(self: *const Window) !apprt.ContentScale {
|
||||
const scale = try self.window.getContentScale();
|
||||
return apprt.ContentScale{ .x = scale.x_scale, .y = scale.y_scale };
|
||||
}
|
||||
|
||||
/// Returns the size of the window in pixels. The pixel size may
|
||||
/// not match screen coordinate size but we should be able to convert
|
||||
/// back and forth using getContentScale.
|
||||
pub fn getSize(self: *const Window) !apprt.WindowSize {
|
||||
const size = self.window.getFramebufferSize() catch |err| err: {
|
||||
log.err("error querying window size in pixels, will use screen size err={}", .{err});
|
||||
break :err try self.window.getSize();
|
||||
};
|
||||
|
||||
return apprt.WindowSize{ .width = size.width, .height = size.height };
|
||||
}
|
||||
|
||||
/// Returns the cursor position in scaled pixels relative to the
|
||||
/// upper-left of the window.
|
||||
pub fn getCursorPos(self: *const Window) !apprt.CursorPos {
|
||||
const unscaled_pos = try self.window.getCursorPos();
|
||||
const pos = try self.cursorPosToPixels(unscaled_pos);
|
||||
return apprt.CursorPos{
|
||||
.x = @floatCast(f32, pos.xpos),
|
||||
.y = @floatCast(f32, pos.ypos),
|
||||
};
|
||||
}
|
||||
|
||||
/// Set the flag that notes this window should be closed for the next
|
||||
/// iteration of the event loop.
|
||||
pub fn setShouldClose(self: *Window) void {
|
||||
self.window.setShouldClose(true);
|
||||
}
|
||||
|
||||
/// Returns true if the window is flagged to close.
|
||||
pub fn shouldClose(self: *const Window) bool {
|
||||
return self.window.shouldClose();
|
||||
}
|
||||
|
||||
/// Set the title of the window.
|
||||
pub fn setTitle(self: *Window, slice: [:0]const u8) !void {
|
||||
try self.window.setTitle(slice.ptr);
|
||||
}
|
||||
|
||||
/// Read the clipboard. The windowing system is responsible for allocating
|
||||
/// a buffer as necessary. This should be a stable pointer until the next
|
||||
/// time getClipboardString is called.
|
||||
pub fn getClipboardString(self: *const Window) ![:0]const u8 {
|
||||
_ = self;
|
||||
return try glfw.getClipboardString();
|
||||
}
|
||||
|
||||
/// Set the clipboard.
|
||||
pub fn setClipboardString(self: *const Window, val: [:0]const u8) !void {
|
||||
_ = self;
|
||||
try glfw.setClipboardString(val);
|
||||
}
|
||||
|
||||
/// The cursor position from glfw directly is in screen coordinates but
|
||||
/// all our interface works in pixels.
|
||||
fn cursorPosToPixels(self: *const Window, pos: glfw.Window.CursorPos) !glfw.Window.CursorPos {
|
||||
// The cursor position is in screen coordinates but we
|
||||
// want it in pixels. we need to get both the size of the
|
||||
// window in both to get the ratio to make the conversion.
|
||||
const size = try self.window.getSize();
|
||||
const fb_size = try self.window.getFramebufferSize();
|
||||
|
||||
// If our framebuffer and screen are the same, then there is no scaling
|
||||
// happening and we can short-circuit by returning the pos as-is.
|
||||
if (fb_size.width == size.width and fb_size.height == size.height)
|
||||
return pos;
|
||||
|
||||
const x_scale = @intToFloat(f64, fb_size.width) / @intToFloat(f64, size.width);
|
||||
const y_scale = @intToFloat(f64, fb_size.height) / @intToFloat(f64, size.height);
|
||||
return .{
|
||||
.xpos = pos.xpos * x_scale,
|
||||
.ypos = pos.ypos * y_scale,
|
||||
};
|
||||
}
|
||||
|
||||
fn sizeCallback(window: glfw.Window, width: i32, height: i32) void {
|
||||
_ = width;
|
||||
_ = height;
|
||||
|
||||
// Get the size. We are given a width/height but this is in screen
|
||||
// coordinates and we want raw pixels. The core window uses the content
|
||||
// scale to scale appropriately.
|
||||
const core_win = window.getUserPointer(CoreWindow) orelse return;
|
||||
const size = core_win.window.getSize() catch |err| {
|
||||
log.err("error querying window size for size callback err={}", .{err});
|
||||
return;
|
||||
};
|
||||
|
||||
// Call the primary callback.
|
||||
core_win.sizeCallback(size) catch |err| {
|
||||
log.err("error in size callback err={}", .{err});
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
fn charCallback(window: glfw.Window, codepoint: u21) void {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
const core_win = window.getUserPointer(CoreWindow) orelse return;
|
||||
core_win.charCallback(codepoint) catch |err| {
|
||||
log.err("error in char callback err={}", .{err});
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
fn keyCallback(
|
||||
window: glfw.Window,
|
||||
glfw_key: glfw.Key,
|
||||
scancode: i32,
|
||||
glfw_action: glfw.Action,
|
||||
glfw_mods: glfw.Mods,
|
||||
) void {
|
||||
_ = scancode;
|
||||
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
// Convert our glfw types into our input types
|
||||
const mods = @bitCast(input.Mods, glfw_mods);
|
||||
const action: input.Action = switch (glfw_action) {
|
||||
.release => .release,
|
||||
.press => .press,
|
||||
.repeat => .repeat,
|
||||
};
|
||||
const key: input.Key = switch (glfw_key) {
|
||||
.a => .a,
|
||||
.b => .b,
|
||||
.c => .c,
|
||||
.d => .d,
|
||||
.e => .e,
|
||||
.f => .f,
|
||||
.g => .g,
|
||||
.h => .h,
|
||||
.i => .i,
|
||||
.j => .j,
|
||||
.k => .k,
|
||||
.l => .l,
|
||||
.m => .m,
|
||||
.n => .n,
|
||||
.o => .o,
|
||||
.p => .p,
|
||||
.q => .q,
|
||||
.r => .r,
|
||||
.s => .s,
|
||||
.t => .t,
|
||||
.u => .u,
|
||||
.v => .v,
|
||||
.w => .w,
|
||||
.x => .x,
|
||||
.y => .y,
|
||||
.z => .z,
|
||||
.zero => .zero,
|
||||
.one => .one,
|
||||
.two => .three,
|
||||
.three => .four,
|
||||
.four => .four,
|
||||
.five => .five,
|
||||
.six => .six,
|
||||
.seven => .seven,
|
||||
.eight => .eight,
|
||||
.nine => .nine,
|
||||
.up => .up,
|
||||
.down => .down,
|
||||
.right => .right,
|
||||
.left => .left,
|
||||
.home => .home,
|
||||
.end => .end,
|
||||
.page_up => .page_up,
|
||||
.page_down => .page_down,
|
||||
.escape => .escape,
|
||||
.F1 => .f1,
|
||||
.F2 => .f2,
|
||||
.F3 => .f3,
|
||||
.F4 => .f4,
|
||||
.F5 => .f5,
|
||||
.F6 => .f6,
|
||||
.F7 => .f7,
|
||||
.F8 => .f8,
|
||||
.F9 => .f9,
|
||||
.F10 => .f10,
|
||||
.F11 => .f11,
|
||||
.F12 => .f12,
|
||||
.F13 => .f13,
|
||||
.F14 => .f14,
|
||||
.F15 => .f15,
|
||||
.F16 => .f16,
|
||||
.F17 => .f17,
|
||||
.F18 => .f18,
|
||||
.F19 => .f19,
|
||||
.F20 => .f20,
|
||||
.F21 => .f21,
|
||||
.F22 => .f22,
|
||||
.F23 => .f23,
|
||||
.F24 => .f24,
|
||||
.F25 => .f25,
|
||||
.kp_0 => .kp_0,
|
||||
.kp_1 => .kp_1,
|
||||
.kp_2 => .kp_2,
|
||||
.kp_3 => .kp_3,
|
||||
.kp_4 => .kp_4,
|
||||
.kp_5 => .kp_5,
|
||||
.kp_6 => .kp_6,
|
||||
.kp_7 => .kp_7,
|
||||
.kp_8 => .kp_8,
|
||||
.kp_9 => .kp_9,
|
||||
.kp_decimal => .kp_decimal,
|
||||
.kp_divide => .kp_divide,
|
||||
.kp_multiply => .kp_multiply,
|
||||
.kp_subtract => .kp_subtract,
|
||||
.kp_add => .kp_add,
|
||||
.kp_enter => .kp_enter,
|
||||
.kp_equal => .kp_equal,
|
||||
.grave_accent => .grave_accent,
|
||||
.minus => .minus,
|
||||
.equal => .equal,
|
||||
.space => .space,
|
||||
.semicolon => .semicolon,
|
||||
.apostrophe => .apostrophe,
|
||||
.comma => .comma,
|
||||
.period => .period,
|
||||
.slash => .slash,
|
||||
.left_bracket => .left_bracket,
|
||||
.right_bracket => .right_bracket,
|
||||
.backslash => .backslash,
|
||||
.enter => .enter,
|
||||
.tab => .tab,
|
||||
.backspace => .backspace,
|
||||
.insert => .insert,
|
||||
.delete => .delete,
|
||||
.caps_lock => .caps_lock,
|
||||
.scroll_lock => .scroll_lock,
|
||||
.num_lock => .num_lock,
|
||||
.print_screen => .print_screen,
|
||||
.pause => .pause,
|
||||
.left_shift => .left_shift,
|
||||
.left_control => .left_control,
|
||||
.left_alt => .left_alt,
|
||||
.left_super => .left_super,
|
||||
.right_shift => .right_shift,
|
||||
.right_control => .right_control,
|
||||
.right_alt => .right_alt,
|
||||
.right_super => .right_super,
|
||||
|
||||
.menu,
|
||||
.world_1,
|
||||
.world_2,
|
||||
.unknown,
|
||||
=> .invalid,
|
||||
};
|
||||
|
||||
const core_win = window.getUserPointer(CoreWindow) orelse return;
|
||||
core_win.keyCallback(action, key, mods) catch |err| {
|
||||
log.err("error in key callback err={}", .{err});
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
fn focusCallback(window: glfw.Window, focused: bool) void {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
const core_win = window.getUserPointer(CoreWindow) orelse return;
|
||||
core_win.focusCallback(focused) catch |err| {
|
||||
log.err("error in focus callback err={}", .{err});
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
fn refreshCallback(window: glfw.Window) void {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
const core_win = window.getUserPointer(CoreWindow) orelse return;
|
||||
core_win.refreshCallback() catch |err| {
|
||||
log.err("error in refresh callback err={}", .{err});
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
fn scrollCallback(window: glfw.Window, xoff: f64, yoff: f64) void {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
const core_win = window.getUserPointer(CoreWindow) orelse return;
|
||||
core_win.scrollCallback(xoff, yoff) catch |err| {
|
||||
log.err("error in scroll callback err={}", .{err});
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
fn cursorPosCallback(
|
||||
window: glfw.Window,
|
||||
unscaled_xpos: f64,
|
||||
unscaled_ypos: f64,
|
||||
) void {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
const core_win = window.getUserPointer(CoreWindow) orelse return;
|
||||
|
||||
// Convert our unscaled x/y to scaled.
|
||||
const pos = core_win.window.cursorPosToPixels(.{
|
||||
.xpos = unscaled_xpos,
|
||||
.ypos = unscaled_ypos,
|
||||
}) catch |err| {
|
||||
log.err(
|
||||
"error converting cursor pos to scaled pixels in cursor pos callback err={}",
|
||||
.{err},
|
||||
);
|
||||
return;
|
||||
};
|
||||
|
||||
core_win.cursorPosCallback(.{
|
||||
.x = @floatCast(f32, pos.xpos),
|
||||
.y = @floatCast(f32, pos.ypos),
|
||||
}) catch |err| {
|
||||
log.err("error in cursor pos callback err={}", .{err});
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
fn mouseButtonCallback(
|
||||
window: glfw.Window,
|
||||
glfw_button: glfw.MouseButton,
|
||||
glfw_action: glfw.Action,
|
||||
glfw_mods: glfw.Mods,
|
||||
) void {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
const core_win = window.getUserPointer(CoreWindow) orelse return;
|
||||
|
||||
// Convert glfw button to input button
|
||||
const mods = @bitCast(input.Mods, glfw_mods);
|
||||
const button: input.MouseButton = switch (glfw_button) {
|
||||
.left => .left,
|
||||
.right => .right,
|
||||
.middle => .middle,
|
||||
.four => .four,
|
||||
.five => .five,
|
||||
.six => .six,
|
||||
.seven => .seven,
|
||||
.eight => .eight,
|
||||
};
|
||||
const action: input.MouseButtonState = switch (glfw_action) {
|
||||
.press => .press,
|
||||
.release => .release,
|
||||
else => unreachable,
|
||||
};
|
||||
|
||||
core_win.mouseButtonCallback(action, button, mods) catch |err| {
|
||||
log.err("error in scroll callback err={}", .{err});
|
||||
return;
|
||||
};
|
||||
}
|
||||
};
|
19
src/apprt/structs.zig
Normal file
19
src/apprt/structs.zig
Normal file
@ -0,0 +1,19 @@
|
||||
/// ContentScale is the ratio between the current DPI and the platform's
|
||||
/// default DPI. This is used to determine how much certain rendered elements
|
||||
/// need to be scaled up or down.
|
||||
pub const ContentScale = struct {
|
||||
x: f32,
|
||||
y: f32,
|
||||
};
|
||||
|
||||
/// The size of the window in pixels.
|
||||
pub const WindowSize = struct {
|
||||
width: u32,
|
||||
height: u32,
|
||||
};
|
||||
|
||||
/// The position of the cursor in pixels.
|
||||
pub const CursorPos = struct {
|
||||
x: f32,
|
||||
y: f32,
|
||||
};
|
@ -13,6 +13,13 @@ pub const Mods = packed struct {
|
||||
_padding: u2 = 0,
|
||||
};
|
||||
|
||||
/// The action associated with an input event.
|
||||
pub const Action = enum {
|
||||
release,
|
||||
press,
|
||||
repeat,
|
||||
};
|
||||
|
||||
/// The set of keys that can map to keybindings. These have no fixed enum
|
||||
/// values because we map platform-specific keys to this set. Note that
|
||||
/// this only needs to accomodate what maps to a key. If a key is not bound
|
||||
@ -61,10 +68,19 @@ pub const Key = enum {
|
||||
eight,
|
||||
nine,
|
||||
|
||||
// other
|
||||
// puncuation
|
||||
semicolon,
|
||||
space,
|
||||
apostrophe,
|
||||
comma,
|
||||
grave_accent, // `
|
||||
period,
|
||||
slash,
|
||||
minus,
|
||||
equal,
|
||||
left_bracket, // [
|
||||
right_bracket, // ]
|
||||
backslash, // /
|
||||
|
||||
// control
|
||||
up,
|
||||
@ -73,10 +89,21 @@ pub const Key = enum {
|
||||
left,
|
||||
home,
|
||||
end,
|
||||
insert,
|
||||
delete,
|
||||
caps_lock,
|
||||
scroll_lock,
|
||||
num_lock,
|
||||
page_up,
|
||||
page_down,
|
||||
escape,
|
||||
enter,
|
||||
tab,
|
||||
backspace,
|
||||
print_screen,
|
||||
pause,
|
||||
|
||||
// function keys
|
||||
f1,
|
||||
f2,
|
||||
f3,
|
||||
@ -89,6 +116,48 @@ pub const Key = enum {
|
||||
f10,
|
||||
f11,
|
||||
f12,
|
||||
f13,
|
||||
f14,
|
||||
f15,
|
||||
f16,
|
||||
f17,
|
||||
f18,
|
||||
f19,
|
||||
f20,
|
||||
f21,
|
||||
f22,
|
||||
f23,
|
||||
f24,
|
||||
f25,
|
||||
|
||||
// keypad
|
||||
kp_0,
|
||||
kp_1,
|
||||
kp_2,
|
||||
kp_3,
|
||||
kp_4,
|
||||
kp_5,
|
||||
kp_6,
|
||||
kp_7,
|
||||
kp_8,
|
||||
kp_9,
|
||||
kp_decimal,
|
||||
kp_divide,
|
||||
kp_multiply,
|
||||
kp_subtract,
|
||||
kp_add,
|
||||
kp_enter,
|
||||
kp_equal,
|
||||
|
||||
// modifiers
|
||||
left_shift,
|
||||
left_control,
|
||||
left_alt,
|
||||
left_super,
|
||||
right_shift,
|
||||
right_control,
|
||||
right_alt,
|
||||
right_super,
|
||||
|
||||
// To support more keys (there are obviously more!) add them here
|
||||
// and ensure the mapping is up to date in the Window key handler.
|
||||
|
@ -10,6 +10,7 @@ const glfw = @import("glfw");
|
||||
const objc = @import("objc");
|
||||
const macos = @import("macos");
|
||||
const imgui = @import("imgui");
|
||||
const apprt = @import("../apprt.zig");
|
||||
const font = @import("../font/main.zig");
|
||||
const terminal = @import("../terminal/main.zig");
|
||||
const renderer = @import("../renderer.zig");
|
||||
@ -124,7 +125,7 @@ const GPUCellMode = enum(u8) {
|
||||
};
|
||||
|
||||
/// Returns the hints that we want for this
|
||||
pub fn windowHints() glfw.Window.Hints {
|
||||
pub fn glfwWindowHints() glfw.Window.Hints {
|
||||
return .{
|
||||
.client_api = .no_api,
|
||||
// .cocoa_graphics_switching = builtin.os.tag == .macos,
|
||||
@ -134,8 +135,8 @@ pub fn windowHints() glfw.Window.Hints {
|
||||
|
||||
/// This is called early right after window creation to setup our
|
||||
/// window surface as necessary.
|
||||
pub fn windowInit(window: glfw.Window) !void {
|
||||
_ = window;
|
||||
pub fn windowInit(win: apprt.runtime.Window) !void {
|
||||
_ = win;
|
||||
|
||||
// We don't do anything else here because we want to set everything
|
||||
// else up during actual initialization.
|
||||
@ -303,9 +304,12 @@ pub fn deinit(self: *Metal) void {
|
||||
|
||||
/// This is called just prior to spinning up the renderer thread for
|
||||
/// final main thread setup requirements.
|
||||
pub fn finalizeWindowInit(self: *const Metal, window: glfw.Window) !void {
|
||||
pub fn finalizeWindowInit(self: *const Metal, win: apprt.runtime.Window) !void {
|
||||
// Set our window backing layer to be our swapchain
|
||||
const nswindow = objc.Object.fromId(glfwNative.getCocoaWindow(window).?);
|
||||
const nswindow = switch (apprt.runtime) {
|
||||
apprt.glfw => objc.Object.fromId(glfwNative.getCocoaWindow(win.window).?),
|
||||
else => @compileError("unsupported apprt for metal"),
|
||||
};
|
||||
const contentView = objc.Object.fromId(nswindow.getProperty(?*anyopaque, "contentView").?);
|
||||
contentView.setProperty("layer", self.swapchain.value);
|
||||
contentView.setProperty("wantsLayer", true);
|
||||
@ -319,11 +323,11 @@ pub fn finalizeWindowInit(self: *const Metal, window: glfw.Window) !void {
|
||||
}
|
||||
|
||||
/// This is called if this renderer runs DevMode.
|
||||
pub fn initDevMode(self: *const Metal, window: glfw.Window) !void {
|
||||
pub fn initDevMode(self: *const Metal, win: apprt.runtime.Window) !void {
|
||||
if (DevMode.enabled) {
|
||||
// Initialize for our window
|
||||
assert(imgui.ImplGlfw.initForOther(
|
||||
@ptrCast(*imgui.ImplGlfw.GLFWWindow, window.handle),
|
||||
@ptrCast(*imgui.ImplGlfw.GLFWWindow, win.window.handle),
|
||||
true,
|
||||
));
|
||||
assert(imgui.ImplMetal.init(self.device.value));
|
||||
@ -341,9 +345,9 @@ pub fn deinitDevMode(self: *const Metal) void {
|
||||
}
|
||||
|
||||
/// Callback called by renderer.Thread when it begins.
|
||||
pub fn threadEnter(self: *const Metal, window: glfw.Window) !void {
|
||||
pub fn threadEnter(self: *const Metal, win: apprt.runtime.Window) !void {
|
||||
_ = self;
|
||||
_ = window;
|
||||
_ = win;
|
||||
|
||||
// Metal requires no per-thread state.
|
||||
}
|
||||
@ -425,10 +429,10 @@ pub fn setFontSize(self: *Metal, size: font.face.DesiredSize) !void {
|
||||
/// The primary render callback that is completely thread-safe.
|
||||
pub fn render(
|
||||
self: *Metal,
|
||||
window: glfw.Window,
|
||||
win: apprt.runtime.Window,
|
||||
state: *renderer.State,
|
||||
) !void {
|
||||
_ = window;
|
||||
_ = win;
|
||||
|
||||
// Data we extract out of the critical area.
|
||||
const Critical = struct {
|
||||
|
@ -7,6 +7,7 @@ const glfw = @import("glfw");
|
||||
const assert = std.debug.assert;
|
||||
const testing = std.testing;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const apprt = @import("../apprt.zig");
|
||||
const font = @import("../font/main.zig");
|
||||
const imgui = @import("imgui");
|
||||
const renderer = @import("../renderer.zig");
|
||||
@ -350,7 +351,7 @@ fn resetCellsLRU(self: *OpenGL) void {
|
||||
}
|
||||
|
||||
/// Returns the hints that we want for this
|
||||
pub fn windowHints() glfw.Window.Hints {
|
||||
pub fn glfwWindowHints() glfw.Window.Hints {
|
||||
return .{
|
||||
.context_version_major = 3,
|
||||
.context_version_minor = 3,
|
||||
@ -363,10 +364,10 @@ pub fn windowHints() glfw.Window.Hints {
|
||||
|
||||
/// This is called early right after window creation to setup our
|
||||
/// window surface as necessary.
|
||||
pub fn windowInit(window: glfw.Window) !void {
|
||||
pub fn windowInit(win: apprt.runtime.Window) !void {
|
||||
// Treat this like a thread entry
|
||||
const self: OpenGL = undefined;
|
||||
try self.threadEnter(window);
|
||||
try self.threadEnter(win);
|
||||
|
||||
// Blending for text
|
||||
try gl.enable(gl.c.GL_BLEND);
|
||||
@ -380,40 +381,23 @@ pub fn windowInit(window: glfw.Window) !void {
|
||||
// log.debug("OpenGL extension available name={s}", .{ext});
|
||||
// }
|
||||
// }
|
||||
|
||||
if (builtin.mode == .Debug) {
|
||||
// Get our physical DPI - debug only because we don't have a use for
|
||||
// this but the logging of it may be useful
|
||||
const monitor = window.getMonitor() orelse monitor: {
|
||||
log.warn("window had null monitor, getting primary monitor", .{});
|
||||
break :monitor glfw.Monitor.getPrimary().?;
|
||||
};
|
||||
const physical_size = monitor.getPhysicalSize();
|
||||
const video_mode = try monitor.getVideoMode();
|
||||
const physical_x_dpi = @intToFloat(f32, video_mode.getWidth()) / (@intToFloat(f32, physical_size.width_mm) / 25.4);
|
||||
const physical_y_dpi = @intToFloat(f32, video_mode.getHeight()) / (@intToFloat(f32, physical_size.height_mm) / 25.4);
|
||||
log.debug("physical dpi x={} y={}", .{
|
||||
physical_x_dpi,
|
||||
physical_y_dpi,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// This is called just prior to spinning up the renderer thread for
|
||||
/// final main thread setup requirements.
|
||||
pub fn finalizeWindowInit(self: *const OpenGL, window: glfw.Window) !void {
|
||||
pub fn finalizeWindowInit(self: *const OpenGL, win: apprt.runtime.Window) !void {
|
||||
_ = self;
|
||||
_ = window;
|
||||
_ = win;
|
||||
}
|
||||
|
||||
/// This is called if this renderer runs DevMode.
|
||||
pub fn initDevMode(self: *const OpenGL, window: glfw.Window) !void {
|
||||
pub fn initDevMode(self: *const OpenGL, win: apprt.runtime.Window) !void {
|
||||
_ = self;
|
||||
|
||||
if (DevMode.enabled) {
|
||||
// Initialize for our window
|
||||
assert(imgui.ImplGlfw.initForOpenGL(
|
||||
@ptrCast(*imgui.ImplGlfw.GLFWWindow, window.handle),
|
||||
@ptrCast(*imgui.ImplGlfw.GLFWWindow, win.window.handle),
|
||||
true,
|
||||
));
|
||||
assert(imgui.ImplOpenGL3.init("#version 330 core"));
|
||||
@ -431,7 +415,7 @@ pub fn deinitDevMode(self: *const OpenGL) void {
|
||||
}
|
||||
|
||||
/// Callback called by renderer.Thread when it begins.
|
||||
pub fn threadEnter(self: *const OpenGL, window: glfw.Window) !void {
|
||||
pub fn threadEnter(self: *const OpenGL, win: apprt.runtime.Window) !void {
|
||||
_ = self;
|
||||
|
||||
// We need to make the OpenGL context current. OpenGL requires
|
||||
@ -439,7 +423,7 @@ pub fn threadEnter(self: *const OpenGL, window: glfw.Window) !void {
|
||||
// ensures that the context switches over to our thread. Important:
|
||||
// the prior thread MUST have detached the context prior to calling
|
||||
// this entrypoint.
|
||||
try glfw.makeContextCurrent(window);
|
||||
try glfw.makeContextCurrent(win.window);
|
||||
errdefer glfw.makeContextCurrent(null) catch |err|
|
||||
log.warn("failed to cleanup OpenGL context err={}", .{err});
|
||||
try glfw.swapInterval(1);
|
||||
@ -541,7 +525,7 @@ fn resetFontMetrics(
|
||||
/// The primary render callback that is completely thread-safe.
|
||||
pub fn render(
|
||||
self: *OpenGL,
|
||||
window: glfw.Window,
|
||||
win: apprt.runtime.Window,
|
||||
state: *renderer.State,
|
||||
) !void {
|
||||
// Data we extract out of the critical area.
|
||||
@ -657,7 +641,7 @@ pub fn render(
|
||||
}
|
||||
|
||||
// Swap our window buffers
|
||||
try window.swapBuffers();
|
||||
try win.window.swapBuffers();
|
||||
}
|
||||
|
||||
/// rebuildCells rebuilds all the GPU cells from our CPU state. This is a
|
||||
|
@ -4,9 +4,9 @@ pub const Thread = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const glfw = @import("glfw");
|
||||
const libuv = @import("libuv");
|
||||
const renderer = @import("../renderer.zig");
|
||||
const apprt = @import("../apprt.zig");
|
||||
const BlockingQueue = @import("../blocking_queue.zig").BlockingQueue;
|
||||
const tracy = @import("tracy");
|
||||
const trace = tracy.trace;
|
||||
@ -37,8 +37,8 @@ render_h: libuv.Timer,
|
||||
/// The timer used for cursor blinking
|
||||
cursor_h: libuv.Timer,
|
||||
|
||||
/// The windo we're rendering to.
|
||||
window: glfw.Window,
|
||||
/// The window we're rendering to.
|
||||
window: apprt.runtime.Window,
|
||||
|
||||
/// The underlying renderer implementation.
|
||||
renderer: *renderer.Renderer,
|
||||
@ -55,7 +55,7 @@ mailbox: *Mailbox,
|
||||
/// is up to the caller to start the thread with the threadMain entrypoint.
|
||||
pub fn init(
|
||||
alloc: Allocator,
|
||||
window: glfw.Window,
|
||||
win: apprt.runtime.Window,
|
||||
renderer_impl: *renderer.Renderer,
|
||||
state: *renderer.State,
|
||||
) !Thread {
|
||||
@ -120,7 +120,7 @@ pub fn init(
|
||||
.stop = stop_h,
|
||||
.render_h = render_h,
|
||||
.cursor_h = cursor_timer,
|
||||
.window = window,
|
||||
.window = win,
|
||||
.renderer = renderer_impl,
|
||||
.state = state,
|
||||
.mailbox = mailbox,
|
||||
|
Reference in New Issue
Block a user