Integrating new surface

This commit is contained in:
Mitchell Hashimoto
2023-02-22 14:37:37 -08:00
parent 3d8c62c41f
commit fbe35c226b
12 changed files with 1831 additions and 146 deletions

View File

@ -9,7 +9,7 @@ const assert = std.debug.assert;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const build_config = @import("build_config.zig"); const build_config = @import("build_config.zig");
const apprt = @import("apprt.zig"); const apprt = @import("apprt.zig");
const Window = @import("Window.zig"); const Surface = @import("Surface.zig");
const tracy = @import("tracy"); const tracy = @import("tracy");
const input = @import("input.zig"); const input = @import("input.zig");
const Config = @import("config.zig").Config; const Config = @import("config.zig").Config;
@ -22,7 +22,8 @@ const DevMode = @import("DevMode.zig");
const log = std.log.scoped(.app); const log = std.log.scoped(.app);
const WindowList = std.ArrayListUnmanaged(*Window); const SurfaceList = std.ArrayListUnmanaged(*apprt.Surface);
const SurfacePool = std.heap.MemoryPool(apprt.Surface);
/// The type used for sending messages to the app thread. /// The type used for sending messages to the app thread.
pub const Mailbox = BlockingQueue(Message, 64); pub const Mailbox = BlockingQueue(Message, 64);
@ -30,8 +31,14 @@ pub const Mailbox = BlockingQueue(Message, 64);
/// General purpose allocator /// General purpose allocator
alloc: Allocator, alloc: Allocator,
/// The list of windows that are currently open /// The list of surfaces that are currently active.
windows: WindowList, surfaces: SurfaceList,
/// The memory pool to request surfaces. We use a memory pool because surfaces
/// typically require stable pointers due to runtime GUI callbacks. Centralizing
/// all the allocations in this pool makes it so that all our pools remain
/// close in memory.
surface_pool: SurfacePool,
// The configuration for the app. // The configuration for the app.
config: *const Config, config: *const Config,
@ -64,20 +71,23 @@ pub fn create(
errdefer alloc.destroy(app); errdefer alloc.destroy(app);
app.* = .{ app.* = .{
.alloc = alloc, .alloc = alloc,
.windows = .{}, .surfaces = .{},
.surface_pool = try SurfacePool.initPreheated(alloc, 2),
.config = config, .config = config,
.mailbox = mailbox, .mailbox = mailbox,
.quit = false, .quit = false,
}; };
errdefer app.windows.deinit(alloc); errdefer app.surfaces.deinit(alloc);
errdefer app.surface_pool.deinit();
return app; return app;
} }
pub fn destroy(self: *App) void { pub fn destroy(self: *App) void {
// Clean up all our windows // Clean up all our surfaces
for (self.windows.items) |window| window.destroy(); for (self.surfaces.items) |surface| surface.deinit();
self.windows.deinit(self.alloc); self.surfaces.deinit(self.alloc);
self.surface_pool.deinit();
self.mailbox.destroy(self.alloc); self.mailbox.destroy(self.alloc);
self.alloc.destroy(self); self.alloc.destroy(self);
@ -94,13 +104,14 @@ pub fn wakeup(self: App) void {
/// ///
/// This returns whether the app should quit or not. /// This returns whether the app should quit or not.
pub fn tick(self: *App, rt_app: *apprt.runtime.App) !bool { pub fn tick(self: *App, rt_app: *apprt.runtime.App) !bool {
// If any windows are closing, destroy them // If any surfaces are closing, destroy them
var i: usize = 0; var i: usize = 0;
while (i < self.windows.items.len) { while (i < self.surfaces.items.len) {
const window = self.windows.items[i]; const surface = self.surfaces.items[i];
if (window.shouldClose()) { if (surface.shouldClose()) {
window.destroy(); surface.deinit();
_ = self.windows.swapRemove(i); _ = self.surfaces.swapRemove(i);
self.surface_pool.destroy(surface);
continue; continue;
} }
@ -110,52 +121,56 @@ pub fn tick(self: *App, rt_app: *apprt.runtime.App) !bool {
// Drain our mailbox only if we're not quitting. // Drain our mailbox only if we're not quitting.
if (!self.quit) try self.drainMailbox(rt_app); if (!self.quit) try self.drainMailbox(rt_app);
// We quit if our quit flag is on or if we have closed all windows. // We quit if our quit flag is on or if we have closed all surfaces.
return self.quit or self.windows.items.len == 0; return self.quit or self.surfaces.items.len == 0;
} }
/// Create a new window. This can be called only on the main thread. This /// Add an initialized surface. This is really only for the runtime
/// can be called prior to ever running the app loop. /// implementations to call and should NOT be called by general app users.
pub fn newWindow(self: *App, msg: Message.NewWindow) !*Window { /// The surface must be from the pool.
var window = try Window.create(self.alloc, self, self.config, msg.runtime); pub fn addSurface(self: *App, rt_surface: *apprt.Surface) !void {
errdefer window.destroy(); try self.surfaces.append(self.alloc, rt_surface);
}
try self.windows.append(self.alloc, window); /// Delete the surface from the known surface list. This will NOT call the
errdefer _ = self.windows.pop(); /// destructor or free the memory.
pub fn deleteSurface(self: *App, rt_surface: *apprt.Surface) void {
// Set initial font size if given var i: usize = 0;
if (msg.font_size) |size| window.setFontSize(size); while (i < self.surfaces.items.len) {
if (self.surfaces.items[i] == rt_surface) {
return window; _ = self.surfaces.swapRemove(i);
}
}
} }
/// Close a window and free all resources associated with it. This can /// Close a window and free all resources associated with it. This can
/// only be called from the main thread. /// only be called from the main thread.
pub fn closeWindow(self: *App, window: *Window) void { // pub fn closeWindow(self: *App, window: *Window) void {
var i: usize = 0; // var i: usize = 0;
while (i < self.windows.items.len) { // while (i < self.surfaces.items.len) {
const current = self.windows.items[i]; // const current = self.surfaces.items[i];
if (window == current) { // if (window == current) {
window.destroy(); // window.destroy();
_ = self.windows.swapRemove(i); // _ = self.surfaces.swapRemove(i);
return; // return;
} // }
//
i += 1; // i += 1;
} // }
} // }
/// Drain the mailbox. /// Drain the mailbox.
fn drainMailbox(self: *App, rt_app: *apprt.runtime.App) !void { fn drainMailbox(self: *App, rt_app: *apprt.runtime.App) !void {
_ = rt_app;
while (self.mailbox.pop()) |message| { while (self.mailbox.pop()) |message| {
log.debug("mailbox message={s}", .{@tagName(message)}); log.debug("mailbox message={s}", .{@tagName(message)});
switch (message) { switch (message) {
.new_window => |msg| _ = try self.newWindow(msg), .new_window => |msg| {
_ = msg; // TODO
try rt_app.newWindow();
},
.new_tab => |msg| try self.newTab(msg), .new_tab => |msg| try self.newTab(msg),
.quit => try self.setQuit(), .quit => try self.setQuit(),
.window_message => |msg| try self.windowMessage(msg.window, msg.message), .surface_message => |msg| try self.surfaceMessage(msg.surface, msg.message),
} }
} }
} }
@ -180,7 +195,7 @@ fn newTab(self: *App, msg: Message.NewWindow) !void {
}; };
// If the parent was closed prior to us handling the message, we do nothing. // If the parent was closed prior to us handling the message, we do nothing.
if (!self.hasWindow(parent)) { if (!self.hasSurface(parent)) {
log.warn("new_tab parent is gone, not launching a new tab", .{}); log.warn("new_tab parent is gone, not launching a new tab", .{});
return; return;
} }
@ -197,28 +212,28 @@ fn setQuit(self: *App) !void {
if (self.quit) return; if (self.quit) return;
self.quit = true; self.quit = true;
// Mark that all our windows should close // Mark that all our surfaces should close
for (self.windows.items) |window| { for (self.surfaces.items) |surface| {
window.window.setShouldClose(); surface.setShouldClose();
} }
} }
/// Handle a window message /// Handle a window message
fn windowMessage(self: *App, win: *Window, msg: Window.Message) !void { fn surfaceMessage(self: *App, surface: *Surface, msg: apprt.surface.Message) !void {
// We want to ensure our window is still active. Window messages // We want to ensure our window is still active. Window messages
// are quite rare and we normally don't have many windows so we do // are quite rare and we normally don't have many windows so we do
// a simple linear search here. // a simple linear search here.
if (self.hasWindow(win)) { if (self.hasSurface(surface)) {
try win.handleMessage(msg); try surface.handleMessage(msg);
} }
// Window was not found, it probably quit before we handled the message. // Window was not found, it probably quit before we handled the message.
// Not a problem. // Not a problem.
} }
fn hasWindow(self: *App, win: *Window) bool { fn hasSurface(self: *App, surface: *Surface) bool {
for (self.windows.items) |window| { for (self.surfaces.items) |v| {
if (window == win) return true; if (&v.core_surface == surface) return true;
} }
return false; return false;
@ -237,18 +252,18 @@ pub const Message = union(enum) {
/// Quit /// Quit
quit: void, quit: void,
/// A message for a specific window /// A message for a specific surface.
window_message: struct { surface_message: struct {
window: *Window, surface: *Surface,
message: Window.Message, message: apprt.surface.Message,
}, },
const NewWindow = struct { const NewWindow = struct {
/// Runtime-specific window options. /// Runtime-specific window options.
runtime: apprt.runtime.Window.Options = .{}, runtime: apprt.runtime.Surface.Options = .{},
/// The parent window, only used for new tabs. /// The parent surface, only used for new tabs.
parent: ?*Window = null, parent: ?*Surface = null,
/// The font size to create the window with or null to default to /// The font size to create the window with or null to default to
/// the configuration amount. /// the configuration amount.
@ -332,7 +347,7 @@ pub const CAPI = struct {
export fn ghostty_surface_new( export fn ghostty_surface_new(
app: *App, app: *App,
opts: *const apprt.runtime.Window.Options, opts: *const apprt.runtime.Window.Options,
) ?*Window { ) ?*Surface {
return surface_new_(app, opts) catch |err| { return surface_new_(app, opts) catch |err| {
log.err("error initializing surface err={}", .{err}); log.err("error initializing surface err={}", .{err});
return null; return null;
@ -342,46 +357,46 @@ pub const CAPI = struct {
fn surface_new_( fn surface_new_(
app: *App, app: *App,
opts: *const apprt.runtime.Window.Options, opts: *const apprt.runtime.Window.Options,
) !*Window { ) !*Surface {
const w = try app.newWindow(.{ const w = try app.newWindow(.{
.runtime = opts.*, .runtime = opts.*,
}); });
return w; return w;
} }
export fn ghostty_surface_free(ptr: ?*Window) void { export fn ghostty_surface_free(ptr: ?*Surface) void {
if (ptr) |v| v.app.closeWindow(v); if (ptr) |v| v.app.closeWindow(v);
} }
/// Returns the app associated with a surface. /// Returns the app associated with a surface.
export fn ghostty_surface_app(win: *Window) *App { export fn ghostty_surface_app(win: *Surface) *App {
return win.app; return win.app;
} }
/// Tell the surface that it needs to schedule a render /// Tell the surface that it needs to schedule a render
export fn ghostty_surface_refresh(win: *Window) void { export fn ghostty_surface_refresh(win: *Surface) void {
win.window.refresh(); win.window.refresh();
} }
/// Update the size of a surface. This will trigger resize notifications /// Update the size of a surface. This will trigger resize notifications
/// to the pty and the renderer. /// to the pty and the renderer.
export fn ghostty_surface_set_size(win: *Window, w: u32, h: u32) void { export fn ghostty_surface_set_size(win: *Surface, w: u32, h: u32) void {
win.window.updateSize(w, h); win.window.updateSize(w, h);
} }
/// Update the content scale of the surface. /// Update the content scale of the surface.
export fn ghostty_surface_set_content_scale(win: *Window, x: f64, y: f64) void { export fn ghostty_surface_set_content_scale(win: *Surface, x: f64, y: f64) void {
win.window.updateContentScale(x, y); win.window.updateContentScale(x, y);
} }
/// Update the focused state of a surface. /// Update the focused state of a surface.
export fn ghostty_surface_set_focus(win: *Window, focused: bool) void { export fn ghostty_surface_set_focus(win: *Surface, focused: bool) void {
win.window.focusCallback(focused); win.window.focusCallback(focused);
} }
/// Tell the surface that it needs to schedule a render /// Tell the surface that it needs to schedule a render
export fn ghostty_surface_key( export fn ghostty_surface_key(
win: *Window, win: *Surface,
action: input.Action, action: input.Action,
key: input.Key, key: input.Key,
mods: c_int, mods: c_int,
@ -394,13 +409,13 @@ pub const CAPI = struct {
} }
/// Tell the surface that it needs to schedule a render /// Tell the surface that it needs to schedule a render
export fn ghostty_surface_char(win: *Window, codepoint: u32) void { export fn ghostty_surface_char(win: *Surface, codepoint: u32) void {
win.window.charCallback(codepoint); win.window.charCallback(codepoint);
} }
/// Tell the surface that it needs to schedule a render /// Tell the surface that it needs to schedule a render
export fn ghostty_surface_mouse_button( export fn ghostty_surface_mouse_button(
win: *Window, win: *Surface,
action: input.MouseButtonState, action: input.MouseButtonState,
button: input.MouseButton, button: input.MouseButton,
mods: c_int, mods: c_int,
@ -413,15 +428,15 @@ pub const CAPI = struct {
} }
/// Update the mouse position within the view. /// Update the mouse position within the view.
export fn ghostty_surface_mouse_pos(win: *Window, x: f64, y: f64) void { export fn ghostty_surface_mouse_pos(win: *Surface, x: f64, y: f64) void {
win.window.cursorPosCallback(x, y); win.window.cursorPosCallback(x, y);
} }
export fn ghostty_surface_mouse_scroll(win: *Window, x: f64, y: f64) void { export fn ghostty_surface_mouse_scroll(win: *Surface, x: f64, y: f64) void {
win.window.scrollCallback(x, y); win.window.scrollCallback(x, y);
} }
export fn ghostty_surface_ime_point(win: *Window, x: *f64, y: *f64) void { export fn ghostty_surface_ime_point(win: *Surface, x: *f64, y: *f64) void {
const pos = win.imePoint(); const pos = win.imePoint();
x.* = pos.x; x.* = pos.x;
y.* = pos.y; y.* = pos.y;

View File

@ -10,7 +10,7 @@ const Allocator = std.mem.Allocator;
const assert = std.debug.assert; const assert = std.debug.assert;
const font = @import("font/main.zig"); const font = @import("font/main.zig");
const Window = @import("Window.zig"); const Surface = @import("Surface.zig");
const renderer = @import("renderer.zig"); const renderer = @import("renderer.zig");
const Config = @import("config.zig").Config; const Config = @import("config.zig").Config;
@ -30,7 +30,7 @@ visible: bool = false,
config: ?*const Config = null, config: ?*const Config = null,
/// The window we're tracking. /// The window we're tracking.
window: ?*Window = null, window: ?*Surface = null,
/// Update the state associated with the dev mode. This should generally /// Update the state associated with the dev mode. This should generally
/// only be called paired with a render since it otherwise wastes CPU /// only be called paired with a render since it otherwise wastes CPU

1635
src/Surface.zig Normal file

File diff suppressed because it is too large Load Diff

View File

@ -17,7 +17,7 @@ pub const glfw = @import("apprt/glfw.zig");
pub const gtk = @import("apprt/gtk.zig"); pub const gtk = @import("apprt/gtk.zig");
pub const browser = @import("apprt/browser.zig"); pub const browser = @import("apprt/browser.zig");
pub const embedded = @import("apprt/embedded.zig"); pub const embedded = @import("apprt/embedded.zig");
pub const Window = @import("apprt/Window.zig"); pub const surface = @import("apprt/Surface.zig");
/// The implementation to use for the app runtime. This is comptime chosen /// The implementation to use for the app runtime. This is comptime chosen
/// so that every build has exactly one application runtime implementation. /// so that every build has exactly one application runtime implementation.
@ -34,6 +34,7 @@ pub const runtime = switch (build_config.artifact) {
}; };
pub const App = runtime.App; pub const App = runtime.App;
pub const Surface = runtime.Surface;
/// Runtime is the runtime to use for Ghostty. All runtimes do not provide /// Runtime is the runtime to use for Ghostty. All runtimes do not provide
/// equivalent feature sets. For example, GTK offers tabbing and more features /// equivalent feature sets. For example, GTK offers tabbing and more features

View File

@ -1,5 +1,5 @@
const App = @import("../App.zig"); const App = @import("../App.zig");
const Window = @import("../Window.zig"); const Surface = @import("../Surface.zig");
const renderer = @import("../renderer.zig"); const renderer = @import("../renderer.zig");
const termio = @import("../termio.zig"); const termio = @import("../termio.zig");
@ -27,17 +27,17 @@ pub const Message = union(enum) {
/// A window mailbox. /// A window mailbox.
pub const Mailbox = struct { pub const Mailbox = struct {
window: *Window, window: *Surface,
app: *App.Mailbox, app: *App.Mailbox,
/// Send a message to the window. /// Send a message to the window.
pub fn push(self: Mailbox, msg: Message, timeout: App.Mailbox.Timeout) App.Mailbox.Size { pub fn push(self: Mailbox, msg: Message, timeout: App.Mailbox.Timeout) App.Mailbox.Size {
// Window message sending is actually implemented on the app // Surface message sending is actually implemented on the app
// thread, so we have to rewrap the message with our window // thread, so we have to rewrap the message with our window
// pointer and send it to the app thread. // pointer and send it to the app thread.
const result = self.app.push(.{ const result = self.app.push(.{
.window_message = .{ .surface_message = .{
.window = self.window, .surface = self.window,
.message = msg, .message = msg,
}, },
}, timeout); }, timeout);

View File

@ -18,7 +18,7 @@ const renderer = @import("../renderer.zig");
const Renderer = renderer.Renderer; const Renderer = renderer.Renderer;
const apprt = @import("../apprt.zig"); const apprt = @import("../apprt.zig");
const CoreApp = @import("../App.zig"); const CoreApp = @import("../App.zig");
const CoreWindow = @import("../Window.zig"); const CoreSurface = @import("../Surface.zig");
// Get native API access on certain platforms so we can do more customization. // Get native API access on certain platforms so we can do more customization.
const glfwNative = glfw.Native(.{ const glfwNative = glfw.Native(.{
@ -75,6 +75,16 @@ pub const App = struct {
glfw.postEmptyEvent(); glfw.postEmptyEvent();
} }
/// Create a new window for the app.
pub fn newWindow(self: *App) !void {
// Grab a surface allocation because we're going to need it.
const surface = try self.app.surface_pool.create();
errdefer self.app.surface_pool.destroy(surface);
// Create the surface -- because windows are surfaces for glfw.
try surface.init(self);
}
fn glfwErrorCallback(code: glfw.ErrorCode, desc: [:0]const u8) void { fn glfwErrorCallback(code: glfw.ErrorCode, desc: [:0]const u8) void {
std.log.warn("glfw error={} message={s}", .{ code, desc }); std.log.warn("glfw error={} message={s}", .{ code, desc });
@ -120,18 +130,32 @@ pub const App = struct {
}; };
}; };
pub const Window = struct { /// Surface represents the drawable surface for glfw. In glfw, a surface
/// is always a window because that is the only abstraction that glfw exposes.
///
/// This means that there is no way for the glfw runtime to support tabs,
/// splits, etc. without considerable effort. In fact, on Darwin, we do
/// support tabs because the minimal tabbing interface is a window abstraction,
/// but this is a bit of a hack. The native Swift runtime should be used instead
/// which uses real native tabbing.
///
/// Other runtimes a surface usually represents the equivalent of a "view"
/// or "widget" level granularity.
pub const Surface = struct {
/// The glfw window handle /// The glfw window handle
window: glfw.Window, window: glfw.Window,
/// The glfw mouse cursor handle. /// The glfw mouse cursor handle.
cursor: glfw.Cursor, cursor: glfw.Cursor,
/// A core surface
core_surface: CoreSurface,
pub const Options = struct {}; pub const Options = struct {};
pub fn init(app: *const CoreApp, core_win: *CoreWindow, opts: Options) !Window { /// Initialize the surface into the given self pointer. This gives a
_ = opts; /// stable pointer to the destination that can be used for callbacks.
pub fn init(self: *Surface, app: *App) !void {
// Create our window // Create our window
const win = glfw.Window.create( const win = glfw.Window.create(
640, 640,
@ -143,9 +167,9 @@ pub const Window = struct {
) orelse return glfw.mustGetErrorCode(); ) orelse return glfw.mustGetErrorCode();
errdefer win.destroy(); errdefer win.destroy();
// Get our physical DPI - debug only because we don't have a use for
// this but the logging of it may be useful
if (builtin.mode == .Debug) { 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: { const monitor = win.getMonitor() orelse monitor: {
log.warn("window had null monitor, getting primary monitor", .{}); log.warn("window had null monitor, getting primary monitor", .{});
break :monitor glfw.Monitor.getPrimary().?; break :monitor glfw.Monitor.getPrimary().?;
@ -160,8 +184,8 @@ pub const Window = struct {
}); });
} }
// On Mac, enable tabbing // On Mac, enable window tabbing
if (comptime builtin.target.isDarwin()) { if (App.Darwin.enabled) {
const NSWindowTabbingMode = enum(usize) { automatic = 0, preferred = 1, disallowed = 2 }; const NSWindowTabbingMode = enum(usize) { automatic = 0, preferred = 1, disallowed = 2 };
const nswindow = objc.Object.fromId(glfwNative.getCocoaWindow(win).?); const nswindow = objc.Object.fromId(glfwNative.getCocoaWindow(win).?);
@ -184,7 +208,7 @@ pub const Window = struct {
} }
// Set our callbacks // Set our callbacks
win.setUserPointer(core_win); win.setUserPointer(&self.core_surface);
win.setSizeCallback(sizeCallback); win.setSizeCallback(sizeCallback);
win.setCharCallback(charCallback); win.setCharCallback(charCallback);
win.setKeyCallback(keyCallback); win.setKeyCallback(keyCallback);
@ -195,13 +219,23 @@ pub const Window = struct {
win.setMouseButtonCallback(mouseButtonCallback); win.setMouseButtonCallback(mouseButtonCallback);
// Build our result // Build our result
return Window{ self.* = .{
.window = win, .window = win,
.cursor = cursor, .cursor = cursor,
.core_surface = undefined,
}; };
errdefer self.* = undefined;
// Add ourselves to the list of surfaces on the app.
try app.app.addSurface(self);
errdefer app.app.deleteSurface(self);
// Initialize our surface now that we have the stable pointer.
try self.core_surface.init(app.app, app.app.config, self);
errdefer self.core_surface.destroy();
} }
pub fn deinit(self: *Window) void { pub fn deinit(self: *Surface) void {
var tabgroup_opt: if (builtin.target.isDarwin()) ?objc.Object else void = undefined; var tabgroup_opt: if (builtin.target.isDarwin()) ?objc.Object else void = undefined;
if (comptime builtin.target.isDarwin()) { if (comptime builtin.target.isDarwin()) {
const nswindow = objc.Object.fromId(glfwNative.getCocoaWindow(self.window).?); const nswindow = objc.Object.fromId(glfwNative.getCocoaWindow(self.window).?);
@ -240,7 +274,7 @@ pub const Window = struct {
// If we have a tabgroup set, we want to manually focus the next window. // 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. // We should NOT have to do this usually, see the comments above.
if (comptime builtin.target.isDarwin()) { if (App.Darwin.enabled) {
if (tabgroup_opt) |tabgroup| { if (tabgroup_opt) |tabgroup| {
const selected = tabgroup.getProperty(objc.Object, "selectedWindow"); const selected = tabgroup.getProperty(objc.Object, "selectedWindow");
selected.msgSend(void, objc.sel("makeKeyWindow"), .{}); selected.msgSend(void, objc.sel("makeKeyWindow"), .{});
@ -252,7 +286,7 @@ pub const Window = struct {
/// Note: this interface is not good, we should redo it if we plan /// 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, /// to use this more. i.e. you can't set max width but no max height,
/// or no mins. /// or no mins.
pub fn setSizeLimits(self: *Window, min: apprt.WindowSize, max_: ?apprt.WindowSize) !void { pub fn setSizeLimits(self: *Surface, min: apprt.WindowSize, max_: ?apprt.WindowSize) !void {
self.window.setSizeLimits(.{ self.window.setSizeLimits(.{
.width = min.width, .width = min.width,
.height = min.height, .height = min.height,
@ -266,7 +300,7 @@ pub const Window = struct {
} }
/// Returns the content scale for the created window. /// Returns the content scale for the created window.
pub fn getContentScale(self: *const Window) !apprt.ContentScale { pub fn getContentScale(self: *const Surface) !apprt.ContentScale {
const scale = self.window.getContentScale(); const scale = self.window.getContentScale();
return apprt.ContentScale{ .x = scale.x_scale, .y = scale.y_scale }; return apprt.ContentScale{ .x = scale.x_scale, .y = scale.y_scale };
} }
@ -274,14 +308,14 @@ pub const Window = struct {
/// Returns the size of the window in pixels. The pixel size may /// Returns the size of the window in pixels. The pixel size may
/// not match screen coordinate size but we should be able to convert /// not match screen coordinate size but we should be able to convert
/// back and forth using getContentScale. /// back and forth using getContentScale.
pub fn getSize(self: *const Window) !apprt.WindowSize { pub fn getSize(self: *const Surface) !apprt.WindowSize {
const size = self.window.getFramebufferSize(); const size = self.window.getFramebufferSize();
return apprt.WindowSize{ .width = size.width, .height = size.height }; return apprt.WindowSize{ .width = size.width, .height = size.height };
} }
/// Returns the cursor position in scaled pixels relative to the /// Returns the cursor position in scaled pixels relative to the
/// upper-left of the window. /// upper-left of the window.
pub fn getCursorPos(self: *const Window) !apprt.CursorPos { pub fn getCursorPos(self: *const Surface) !apprt.CursorPos {
const unscaled_pos = self.window.getCursorPos(); const unscaled_pos = self.window.getCursorPos();
const pos = try self.cursorPosToPixels(unscaled_pos); const pos = try self.cursorPosToPixels(unscaled_pos);
return apprt.CursorPos{ return apprt.CursorPos{
@ -292,37 +326,37 @@ pub const Window = struct {
/// Set the flag that notes this window should be closed for the next /// Set the flag that notes this window should be closed for the next
/// iteration of the event loop. /// iteration of the event loop.
pub fn setShouldClose(self: *Window) void { pub fn setShouldClose(self: *Surface) void {
self.window.setShouldClose(true); self.window.setShouldClose(true);
} }
/// Returns true if the window is flagged to close. /// Returns true if the window is flagged to close.
pub fn shouldClose(self: *const Window) bool { pub fn shouldClose(self: *const Surface) bool {
return self.window.shouldClose(); return self.window.shouldClose();
} }
/// Set the title of the window. /// Set the title of the window.
pub fn setTitle(self: *Window, slice: [:0]const u8) !void { pub fn setTitle(self: *Surface, slice: [:0]const u8) !void {
self.window.setTitle(slice.ptr); self.window.setTitle(slice.ptr);
} }
/// Read the clipboard. The windowing system is responsible for allocating /// Read the clipboard. The windowing system is responsible for allocating
/// a buffer as necessary. This should be a stable pointer until the next /// a buffer as necessary. This should be a stable pointer until the next
/// time getClipboardString is called. /// time getClipboardString is called.
pub fn getClipboardString(self: *const Window) ![:0]const u8 { pub fn getClipboardString(self: *const Surface) ![:0]const u8 {
_ = self; _ = self;
return glfw.getClipboardString() orelse return glfw.mustGetErrorCode(); return glfw.getClipboardString() orelse return glfw.mustGetErrorCode();
} }
/// Set the clipboard. /// Set the clipboard.
pub fn setClipboardString(self: *const Window, val: [:0]const u8) !void { pub fn setClipboardString(self: *const Surface, val: [:0]const u8) !void {
_ = self; _ = self;
glfw.setClipboardString(val); glfw.setClipboardString(val);
} }
/// The cursor position from glfw directly is in screen coordinates but /// The cursor position from glfw directly is in screen coordinates but
/// all our interface works in pixels. /// all our interface works in pixels.
fn cursorPosToPixels(self: *const Window, pos: glfw.Window.CursorPos) !glfw.Window.CursorPos { fn cursorPosToPixels(self: *const Surface, pos: glfw.Window.CursorPos) !glfw.Window.CursorPos {
// The cursor position is in screen coordinates but we // The cursor position is in screen coordinates but we
// want it in pixels. we need to get both the size of the // want it in pixels. we need to get both the size of the
// window in both to get the ratio to make the conversion. // window in both to get the ratio to make the conversion.
@ -349,8 +383,8 @@ pub const Window = struct {
// Get the size. We are given a width/height but this is in screen // 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 // coordinates and we want raw pixels. The core window uses the content
// scale to scale appropriately. // scale to scale appropriately.
const core_win = window.getUserPointer(CoreWindow) orelse return; const core_win = window.getUserPointer(CoreSurface) orelse return;
const size = core_win.window.getSize() catch |err| { const size = core_win.rt_surface.getSize() catch |err| {
log.err("error querying window size for size callback err={}", .{err}); log.err("error querying window size for size callback err={}", .{err});
return; return;
}; };
@ -366,7 +400,7 @@ pub const Window = struct {
const tracy = trace(@src()); const tracy = trace(@src());
defer tracy.end(); defer tracy.end();
const core_win = window.getUserPointer(CoreWindow) orelse return; const core_win = window.getUserPointer(CoreSurface) orelse return;
core_win.charCallback(codepoint) catch |err| { core_win.charCallback(codepoint) catch |err| {
log.err("error in char callback err={}", .{err}); log.err("error in char callback err={}", .{err});
return; return;
@ -518,7 +552,7 @@ pub const Window = struct {
=> .invalid, => .invalid,
}; };
const core_win = window.getUserPointer(CoreWindow) orelse return; const core_win = window.getUserPointer(CoreSurface) orelse return;
core_win.keyCallback(action, key, mods) catch |err| { core_win.keyCallback(action, key, mods) catch |err| {
log.err("error in key callback err={}", .{err}); log.err("error in key callback err={}", .{err});
return; return;
@ -529,7 +563,7 @@ pub const Window = struct {
const tracy = trace(@src()); const tracy = trace(@src());
defer tracy.end(); defer tracy.end();
const core_win = window.getUserPointer(CoreWindow) orelse return; const core_win = window.getUserPointer(CoreSurface) orelse return;
core_win.focusCallback(focused) catch |err| { core_win.focusCallback(focused) catch |err| {
log.err("error in focus callback err={}", .{err}); log.err("error in focus callback err={}", .{err});
return; return;
@ -540,7 +574,7 @@ pub const Window = struct {
const tracy = trace(@src()); const tracy = trace(@src());
defer tracy.end(); defer tracy.end();
const core_win = window.getUserPointer(CoreWindow) orelse return; const core_win = window.getUserPointer(CoreSurface) orelse return;
core_win.refreshCallback() catch |err| { core_win.refreshCallback() catch |err| {
log.err("error in refresh callback err={}", .{err}); log.err("error in refresh callback err={}", .{err});
return; return;
@ -551,7 +585,7 @@ pub const Window = struct {
const tracy = trace(@src()); const tracy = trace(@src());
defer tracy.end(); defer tracy.end();
const core_win = window.getUserPointer(CoreWindow) orelse return; const core_win = window.getUserPointer(CoreSurface) orelse return;
core_win.scrollCallback(xoff, yoff) catch |err| { core_win.scrollCallback(xoff, yoff) catch |err| {
log.err("error in scroll callback err={}", .{err}); log.err("error in scroll callback err={}", .{err});
return; return;
@ -566,10 +600,10 @@ pub const Window = struct {
const tracy = trace(@src()); const tracy = trace(@src());
defer tracy.end(); defer tracy.end();
const core_win = window.getUserPointer(CoreWindow) orelse return; const core_win = window.getUserPointer(CoreSurface) orelse return;
// Convert our unscaled x/y to scaled. // Convert our unscaled x/y to scaled.
const pos = core_win.window.cursorPosToPixels(.{ const pos = core_win.rt_surface.cursorPosToPixels(.{
.xpos = unscaled_xpos, .xpos = unscaled_xpos,
.ypos = unscaled_ypos, .ypos = unscaled_ypos,
}) catch |err| { }) catch |err| {
@ -598,7 +632,7 @@ pub const Window = struct {
const tracy = trace(@src()); const tracy = trace(@src());
defer tracy.end(); defer tracy.end();
const core_win = window.getUserPointer(CoreWindow) orelse return; const core_win = window.getUserPointer(CoreSurface) orelse return;
// Convert glfw button to input button // Convert glfw button to input button
const mods = @bitCast(input.Mods, glfw_mods); const mods = @bitCast(input.Mods, glfw_mods);

View File

@ -100,6 +100,7 @@ pub fn main() !void {
defer app_runtime.terminate(); defer app_runtime.terminate();
// Create an initial window // Create an initial window
try app_runtime.newWindow();
// Run the GUI event loop // Run the GUI event loop
try app_runtime.run(); try app_runtime.run();

View File

@ -18,7 +18,7 @@ const trace = @import("tracy").trace;
const math = @import("../math.zig"); const math = @import("../math.zig");
const lru = @import("../lru.zig"); const lru = @import("../lru.zig");
const DevMode = @import("../DevMode.zig"); const DevMode = @import("../DevMode.zig");
const Window = @import("../Window.zig"); const Surface = @import("../Surface.zig");
const log = std.log.scoped(.grid); const log = std.log.scoped(.grid);
@ -89,7 +89,7 @@ focused: bool,
padding: renderer.Options.Padding, padding: renderer.Options.Padding,
/// The mailbox for communicating with the window. /// The mailbox for communicating with the window.
window_mailbox: Window.Mailbox, window_mailbox: apprt.surface.Mailbox,
/// The raw structure that maps directly to the buffer sent to the vertex shader. /// The raw structure that maps directly to the buffer sent to the vertex shader.
/// This must be "extern" so that the field order is not reordered by the /// This must be "extern" so that the field order is not reordered by the
@ -362,12 +362,11 @@ pub fn glfwWindowHints() glfw.Window.Hints {
}; };
} }
/// This is called early right after window creation to setup our /// This is called early right after surface creation.
/// window surface as necessary. pub fn surfaceInit(surface: *apprt.Surface) !void {
pub fn windowInit(win: apprt.runtime.Window) !void {
// Treat this like a thread entry // Treat this like a thread entry
const self: OpenGL = undefined; const self: OpenGL = undefined;
try self.threadEnter(win); try self.threadEnter(surface);
// Blending for text. We use GL_ONE here because we should be using // Blending for text. We use GL_ONE here because we should be using
// premultiplied alpha for all our colors in our fragment shaders. // premultiplied alpha for all our colors in our fragment shaders.
@ -388,19 +387,19 @@ pub fn windowInit(win: apprt.runtime.Window) !void {
/// This is called just prior to spinning up the renderer thread for /// This is called just prior to spinning up the renderer thread for
/// final main thread setup requirements. /// final main thread setup requirements.
pub fn finalizeWindowInit(self: *const OpenGL, win: apprt.runtime.Window) !void { pub fn finalizeSurfaceInit(self: *const OpenGL, surface: *apprt.Surface) !void {
_ = self; _ = self;
_ = win; _ = surface;
} }
/// This is called if this renderer runs DevMode. /// This is called if this renderer runs DevMode.
pub fn initDevMode(self: *const OpenGL, win: apprt.runtime.Window) !void { pub fn initDevMode(self: *const OpenGL, surface: *apprt.Surface) !void {
_ = self; _ = self;
if (DevMode.enabled) { if (DevMode.enabled) {
// Initialize for our window // Initialize for our window
assert(imgui.ImplGlfw.initForOpenGL( assert(imgui.ImplGlfw.initForOpenGL(
@ptrCast(*imgui.ImplGlfw.GLFWWindow, win.window.handle), @ptrCast(*imgui.ImplGlfw.GLFWWindow, surface.window.handle),
true, true,
)); ));
assert(imgui.ImplOpenGL3.init("#version 330 core")); assert(imgui.ImplOpenGL3.init("#version 330 core"));
@ -418,7 +417,7 @@ pub fn deinitDevMode(self: *const OpenGL) void {
} }
/// Callback called by renderer.Thread when it begins. /// Callback called by renderer.Thread when it begins.
pub fn threadEnter(self: *const OpenGL, win: apprt.runtime.Window) !void { pub fn threadEnter(self: *const OpenGL, surface: *apprt.Surface) !void {
_ = self; _ = self;
switch (apprt.runtime) { switch (apprt.runtime) {
@ -437,7 +436,7 @@ pub fn threadEnter(self: *const OpenGL, win: apprt.runtime.Window) !void {
// ensures that the context switches over to our thread. Important: // ensures that the context switches over to our thread. Important:
// the prior thread MUST have detached the context prior to calling // the prior thread MUST have detached the context prior to calling
// this entrypoint. // this entrypoint.
glfw.makeContextCurrent(win.window); glfw.makeContextCurrent(surface.window);
errdefer glfw.makeContextCurrent(null); errdefer glfw.makeContextCurrent(null);
glfw.swapInterval(1); glfw.swapInterval(1);
@ -548,7 +547,7 @@ fn resetFontMetrics(
/// The primary render callback that is completely thread-safe. /// The primary render callback that is completely thread-safe.
pub fn render( pub fn render(
self: *OpenGL, self: *OpenGL,
win: apprt.runtime.Window, surface: *apprt.Surface,
state: *renderer.State, state: *renderer.State,
) !void { ) !void {
// Data we extract out of the critical area. // Data we extract out of the critical area.
@ -669,7 +668,7 @@ pub fn render(
// Swap our window buffers // Swap our window buffers
if (apprt.runtime == apprt.gtk) @panic("TODO"); if (apprt.runtime == apprt.gtk) @panic("TODO");
win.window.swapBuffers(); surface.window.swapBuffers();
} }
/// rebuildCells rebuilds all the GPU cells from our CPU state. This is a /// rebuildCells rebuilds all the GPU cells from our CPU state. This is a

View File

@ -1,8 +1,8 @@
//! The options that are used to configure a renderer. //! The options that are used to configure a renderer.
const apprt = @import("../apprt.zig");
const font = @import("../font/main.zig"); const font = @import("../font/main.zig");
const renderer = @import("../renderer.zig"); const renderer = @import("../renderer.zig");
const Window = @import("../Window.zig");
const Config = @import("../config.zig").Config; const Config = @import("../config.zig").Config;
/// The app configuration. /// The app configuration.
@ -16,7 +16,7 @@ padding: Padding,
/// The mailbox for sending the window messages. This is only valid /// The mailbox for sending the window messages. This is only valid
/// once the thread has started and should not be used outside of the thread. /// once the thread has started and should not be used outside of the thread.
window_mailbox: Window.Mailbox, window_mailbox: apprt.surface.Mailbox,
pub const Padding = struct { pub const Padding = struct {
// Explicit padding options, in pixels. The windowing thread is // Explicit padding options, in pixels. The windowing thread is

View File

@ -47,8 +47,8 @@ cursor_h: xev.Timer,
cursor_c: xev.Completion = .{}, cursor_c: xev.Completion = .{},
cursor_c_cancel: xev.Completion = .{}, cursor_c_cancel: xev.Completion = .{},
/// The window we're rendering to. /// The surface we're rendering to.
window: apprt.runtime.Window, surface: *apprt.Surface,
/// The underlying renderer implementation. /// The underlying renderer implementation.
renderer: *renderer.Renderer, renderer: *renderer.Renderer,
@ -65,7 +65,7 @@ mailbox: *Mailbox,
/// is up to the caller to start the thread with the threadMain entrypoint. /// is up to the caller to start the thread with the threadMain entrypoint.
pub fn init( pub fn init(
alloc: Allocator, alloc: Allocator,
win: apprt.runtime.Window, surface: *apprt.Surface,
renderer_impl: *renderer.Renderer, renderer_impl: *renderer.Renderer,
state: *renderer.State, state: *renderer.State,
) !Thread { ) !Thread {
@ -100,7 +100,7 @@ pub fn init(
.stop = stop_h, .stop = stop_h,
.render_h = render_h, .render_h = render_h,
.cursor_h = cursor_timer, .cursor_h = cursor_timer,
.window = win, .surface = surface,
.renderer = renderer_impl, .renderer = renderer_impl,
.state = state, .state = state,
.mailbox = mailbox, .mailbox = mailbox,
@ -135,7 +135,7 @@ fn threadMain_(self: *Thread) !void {
// Run our thread start/end callbacks. This is important because some // Run our thread start/end callbacks. This is important because some
// renderers have to do per-thread setup. For example, OpenGL has to set // renderers have to do per-thread setup. For example, OpenGL has to set
// some thread-local state since that is how it works. // some thread-local state since that is how it works.
try self.renderer.threadEnter(self.window); try self.renderer.threadEnter(self.surface);
defer self.renderer.threadExit(); defer self.renderer.threadExit();
// Start the async handlers // Start the async handlers
@ -305,7 +305,7 @@ fn renderCallback(
return .disarm; return .disarm;
}; };
t.renderer.render(t.window, t.state) catch |err| t.renderer.render(t.surface, t.state) catch |err|
log.warn("error rendering err={}", .{err}); log.warn("error rendering err={}", .{err});
return .disarm; return .disarm;
} }

View File

@ -8,7 +8,6 @@ const assert = std.debug.assert;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const termio = @import("../termio.zig"); const termio = @import("../termio.zig");
const Command = @import("../Command.zig"); const Command = @import("../Command.zig");
const Window = @import("../Window.zig");
const Pty = @import("../Pty.zig"); const Pty = @import("../Pty.zig");
const SegmentedPool = @import("../segmented_pool.zig").SegmentedPool; const SegmentedPool = @import("../segmented_pool.zig").SegmentedPool;
const terminal = @import("../terminal/main.zig"); const terminal = @import("../terminal/main.zig");
@ -16,6 +15,7 @@ const xev = @import("xev");
const renderer = @import("../renderer.zig"); const renderer = @import("../renderer.zig");
const tracy = @import("tracy"); const tracy = @import("tracy");
const trace = tracy.trace; const trace = tracy.trace;
const apprt = @import("../apprt.zig");
const fastmem = @import("../fastmem.zig"); const fastmem = @import("../fastmem.zig");
const log = std.log.scoped(.io_exec); const log = std.log.scoped(.io_exec);
@ -52,7 +52,7 @@ renderer_wakeup: xev.Async,
renderer_mailbox: *renderer.Thread.Mailbox, renderer_mailbox: *renderer.Thread.Mailbox,
/// The mailbox for communicating with the window. /// The mailbox for communicating with the window.
window_mailbox: Window.Mailbox, window_mailbox: apprt.surface.Mailbox,
/// The cached grid size whenever a resize is called. /// The cached grid size whenever a resize is called.
grid_size: renderer.GridSize, grid_size: renderer.GridSize,
@ -638,7 +638,7 @@ const StreamHandler = struct {
alloc: Allocator, alloc: Allocator,
grid_size: *renderer.GridSize, grid_size: *renderer.GridSize,
terminal: *terminal.Terminal, terminal: *terminal.Terminal,
window_mailbox: Window.Mailbox, window_mailbox: apprt.surface.Mailbox,
/// This is set to true when a message was written to the writer /// This is set to true when a message was written to the writer
/// mailbox. This can be used by callers to determine if they need /// mailbox. This can be used by callers to determine if they need
@ -1003,7 +1003,7 @@ const StreamHandler = struct {
// Write clipboard contents // Write clipboard contents
_ = self.window_mailbox.push(.{ _ = self.window_mailbox.push(.{
.clipboard_write = try Window.Message.WriteReq.init( .clipboard_write = try apprt.surface.Message.WriteReq.init(
self.alloc, self.alloc,
data, data,
), ),

View File

@ -1,9 +1,9 @@
//! The options that are used to configure a terminal IO implementation. //! The options that are used to configure a terminal IO implementation.
const xev = @import("xev"); const xev = @import("xev");
const apprt = @import("../apprt.zig");
const renderer = @import("../renderer.zig"); const renderer = @import("../renderer.zig");
const Config = @import("../config.zig").Config; const Config = @import("../config.zig").Config;
const Window = @import("../Window.zig");
/// The size of the terminal grid. /// The size of the terminal grid.
grid_size: renderer.GridSize, grid_size: renderer.GridSize,
@ -28,4 +28,4 @@ renderer_wakeup: xev.Async,
renderer_mailbox: *renderer.Thread.Mailbox, renderer_mailbox: *renderer.Thread.Mailbox,
/// The mailbox for sending the window messages. /// The mailbox for sending the window messages.
window_mailbox: Window.Mailbox, window_mailbox: apprt.surface.Mailbox,