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 build_config = @import("build_config.zig");
const apprt = @import("apprt.zig");
const Window = @import("Window.zig");
const Surface = @import("Surface.zig");
const tracy = @import("tracy");
const input = @import("input.zig");
const Config = @import("config.zig").Config;
@ -22,7 +22,8 @@ const DevMode = @import("DevMode.zig");
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.
pub const Mailbox = BlockingQueue(Message, 64);
@ -30,8 +31,14 @@ pub const Mailbox = BlockingQueue(Message, 64);
/// General purpose allocator
alloc: Allocator,
/// The list of windows that are currently open
windows: WindowList,
/// The list of surfaces that are currently active.
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.
config: *const Config,
@ -64,20 +71,23 @@ pub fn create(
errdefer alloc.destroy(app);
app.* = .{
.alloc = alloc,
.windows = .{},
.surfaces = .{},
.surface_pool = try SurfacePool.initPreheated(alloc, 2),
.config = config,
.mailbox = mailbox,
.quit = false,
};
errdefer app.windows.deinit(alloc);
errdefer app.surfaces.deinit(alloc);
errdefer app.surface_pool.deinit();
return app;
}
pub fn destroy(self: *App) void {
// Clean up all our windows
for (self.windows.items) |window| window.destroy();
self.windows.deinit(self.alloc);
// Clean up all our surfaces
for (self.surfaces.items) |surface| surface.deinit();
self.surfaces.deinit(self.alloc);
self.surface_pool.deinit();
self.mailbox.destroy(self.alloc);
self.alloc.destroy(self);
@ -94,13 +104,14 @@ pub fn wakeup(self: App) void {
///
/// This returns whether the app should quit or not.
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;
while (i < self.windows.items.len) {
const window = self.windows.items[i];
if (window.shouldClose()) {
window.destroy();
_ = self.windows.swapRemove(i);
while (i < self.surfaces.items.len) {
const surface = self.surfaces.items[i];
if (surface.shouldClose()) {
surface.deinit();
_ = self.surfaces.swapRemove(i);
self.surface_pool.destroy(surface);
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.
if (!self.quit) try self.drainMailbox(rt_app);
// We quit if our quit flag is on or if we have closed all windows.
return self.quit or self.windows.items.len == 0;
// We quit if our quit flag is on or if we have closed all surfaces.
return self.quit or self.surfaces.items.len == 0;
}
/// Create a new window. This can be called only on the main thread. This
/// can be called prior to ever running the app loop.
pub fn newWindow(self: *App, msg: Message.NewWindow) !*Window {
var window = try Window.create(self.alloc, self, self.config, msg.runtime);
errdefer window.destroy();
/// Add an initialized surface. This is really only for the runtime
/// implementations to call and should NOT be called by general app users.
/// The surface must be from the pool.
pub fn addSurface(self: *App, rt_surface: *apprt.Surface) !void {
try self.surfaces.append(self.alloc, rt_surface);
}
try self.windows.append(self.alloc, window);
errdefer _ = self.windows.pop();
// Set initial font size if given
if (msg.font_size) |size| window.setFontSize(size);
return window;
/// Delete the surface from the known surface list. This will NOT call the
/// destructor or free the memory.
pub fn deleteSurface(self: *App, rt_surface: *apprt.Surface) void {
var i: usize = 0;
while (i < self.surfaces.items.len) {
if (self.surfaces.items[i] == rt_surface) {
_ = self.surfaces.swapRemove(i);
}
}
}
/// Close a window and free all resources associated with it. This can
/// only be called from the main thread.
pub fn closeWindow(self: *App, window: *Window) void {
var i: usize = 0;
while (i < self.windows.items.len) {
const current = self.windows.items[i];
if (window == current) {
window.destroy();
_ = self.windows.swapRemove(i);
return;
}
i += 1;
}
}
// pub fn closeWindow(self: *App, window: *Window) void {
// var i: usize = 0;
// while (i < self.surfaces.items.len) {
// const current = self.surfaces.items[i];
// if (window == current) {
// window.destroy();
// _ = self.surfaces.swapRemove(i);
// return;
// }
//
// i += 1;
// }
// }
/// Drain the mailbox.
fn drainMailbox(self: *App, rt_app: *apprt.runtime.App) !void {
_ = rt_app;
while (self.mailbox.pop()) |message| {
log.debug("mailbox message={s}", .{@tagName(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),
.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 (!self.hasWindow(parent)) {
if (!self.hasSurface(parent)) {
log.warn("new_tab parent is gone, not launching a new tab", .{});
return;
}
@ -197,28 +212,28 @@ fn setQuit(self: *App) !void {
if (self.quit) return;
self.quit = true;
// Mark that all our windows should close
for (self.windows.items) |window| {
window.window.setShouldClose();
// Mark that all our surfaces should close
for (self.surfaces.items) |surface| {
surface.setShouldClose();
}
}
/// 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
// are quite rare and we normally don't have many windows so we do
// a simple linear search here.
if (self.hasWindow(win)) {
try win.handleMessage(msg);
if (self.hasSurface(surface)) {
try surface.handleMessage(msg);
}
// Window was not found, it probably quit before we handled the message.
// Not a problem.
}
fn hasWindow(self: *App, win: *Window) bool {
for (self.windows.items) |window| {
if (window == win) return true;
fn hasSurface(self: *App, surface: *Surface) bool {
for (self.surfaces.items) |v| {
if (&v.core_surface == surface) return true;
}
return false;
@ -237,18 +252,18 @@ pub const Message = union(enum) {
/// Quit
quit: void,
/// A message for a specific window
window_message: struct {
window: *Window,
message: Window.Message,
/// A message for a specific surface.
surface_message: struct {
surface: *Surface,
message: apprt.surface.Message,
},
const NewWindow = struct {
/// Runtime-specific window options.
runtime: apprt.runtime.Window.Options = .{},
runtime: apprt.runtime.Surface.Options = .{},
/// The parent window, only used for new tabs.
parent: ?*Window = null,
/// The parent surface, only used for new tabs.
parent: ?*Surface = null,
/// The font size to create the window with or null to default to
/// the configuration amount.
@ -332,7 +347,7 @@ pub const CAPI = struct {
export fn ghostty_surface_new(
app: *App,
opts: *const apprt.runtime.Window.Options,
) ?*Window {
) ?*Surface {
return surface_new_(app, opts) catch |err| {
log.err("error initializing surface err={}", .{err});
return null;
@ -342,46 +357,46 @@ pub const CAPI = struct {
fn surface_new_(
app: *App,
opts: *const apprt.runtime.Window.Options,
) !*Window {
) !*Surface {
const w = try app.newWindow(.{
.runtime = opts.*,
});
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);
}
/// 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;
}
/// 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();
}
/// Update the size of a surface. This will trigger resize notifications
/// 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);
}
/// 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);
}
/// 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);
}
/// Tell the surface that it needs to schedule a render
export fn ghostty_surface_key(
win: *Window,
win: *Surface,
action: input.Action,
key: input.Key,
mods: c_int,
@ -394,13 +409,13 @@ pub const CAPI = struct {
}
/// 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);
}
/// Tell the surface that it needs to schedule a render
export fn ghostty_surface_mouse_button(
win: *Window,
win: *Surface,
action: input.MouseButtonState,
button: input.MouseButton,
mods: c_int,
@ -413,15 +428,15 @@ pub const CAPI = struct {
}
/// 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);
}
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);
}
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();
x.* = pos.x;
y.* = pos.y;

View File

@ -10,7 +10,7 @@ const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
const font = @import("font/main.zig");
const Window = @import("Window.zig");
const Surface = @import("Surface.zig");
const renderer = @import("renderer.zig");
const Config = @import("config.zig").Config;
@ -30,7 +30,7 @@ visible: bool = false,
config: ?*const Config = null,
/// The window we're tracking.
window: ?*Window = null,
window: ?*Surface = null,
/// Update the state associated with the dev mode. This should generally
/// 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 browser = @import("apprt/browser.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
/// 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 Surface = runtime.Surface;
/// Runtime is the runtime to use for Ghostty. All runtimes do not provide
/// equivalent feature sets. For example, GTK offers tabbing and more features

View File

@ -1,5 +1,5 @@
const App = @import("../App.zig");
const Window = @import("../Window.zig");
const Surface = @import("../Surface.zig");
const renderer = @import("../renderer.zig");
const termio = @import("../termio.zig");
@ -27,17 +27,17 @@ pub const Message = union(enum) {
/// A window mailbox.
pub const Mailbox = struct {
window: *Window,
window: *Surface,
app: *App.Mailbox,
/// Send a message to the window.
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
// pointer and send it to the app thread.
const result = self.app.push(.{
.window_message = .{
.window = self.window,
.surface_message = .{
.surface = self.window,
.message = msg,
},
}, timeout);

View File

@ -18,7 +18,7 @@ const renderer = @import("../renderer.zig");
const Renderer = renderer.Renderer;
const apprt = @import("../apprt.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.
const glfwNative = glfw.Native(.{
@ -75,6 +75,16 @@ pub const App = struct {
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 {
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
window: glfw.Window,
/// The glfw mouse cursor handle.
cursor: glfw.Cursor,
/// A core surface
core_surface: CoreSurface,
pub const Options = struct {};
pub fn init(app: *const CoreApp, core_win: *CoreWindow, opts: Options) !Window {
_ = opts;
/// Initialize the surface into the given self pointer. This gives a
/// stable pointer to the destination that can be used for callbacks.
pub fn init(self: *Surface, app: *App) !void {
// Create our window
const win = glfw.Window.create(
640,
@ -143,9 +167,9 @@ pub const Window = struct {
) orelse return glfw.mustGetErrorCode();
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
if (builtin.mode == .Debug) {
const monitor = win.getMonitor() orelse monitor: {
log.warn("window had null monitor, getting primary monitor", .{});
break :monitor glfw.Monitor.getPrimary().?;
@ -160,8 +184,8 @@ pub const Window = struct {
});
}
// On Mac, enable tabbing
if (comptime builtin.target.isDarwin()) {
// On Mac, enable window tabbing
if (App.Darwin.enabled) {
const NSWindowTabbingMode = enum(usize) { automatic = 0, preferred = 1, disallowed = 2 };
const nswindow = objc.Object.fromId(glfwNative.getCocoaWindow(win).?);
@ -184,7 +208,7 @@ pub const Window = struct {
}
// Set our callbacks
win.setUserPointer(core_win);
win.setUserPointer(&self.core_surface);
win.setSizeCallback(sizeCallback);
win.setCharCallback(charCallback);
win.setKeyCallback(keyCallback);
@ -195,13 +219,23 @@ pub const Window = struct {
win.setMouseButtonCallback(mouseButtonCallback);
// Build our result
return Window{
self.* = .{
.window = win,
.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;
if (comptime builtin.target.isDarwin()) {
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.
// 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| {
const selected = tabgroup.getProperty(objc.Object, "selectedWindow");
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
/// 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 {
pub fn setSizeLimits(self: *Surface, min: apprt.WindowSize, max_: ?apprt.WindowSize) !void {
self.window.setSizeLimits(.{
.width = min.width,
.height = min.height,
@ -266,7 +300,7 @@ pub const Window = struct {
}
/// 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();
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
/// 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 {
pub fn getSize(self: *const Surface) !apprt.WindowSize {
const size = self.window.getFramebufferSize();
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 {
pub fn getCursorPos(self: *const Surface) !apprt.CursorPos {
const unscaled_pos = self.window.getCursorPos();
const pos = try self.cursorPosToPixels(unscaled_pos);
return apprt.CursorPos{
@ -292,37 +326,37 @@ pub const Window = struct {
/// Set the flag that notes this window should be closed for the next
/// iteration of the event loop.
pub fn setShouldClose(self: *Window) void {
pub fn setShouldClose(self: *Surface) void {
self.window.setShouldClose(true);
}
/// 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();
}
/// 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);
}
/// 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 {
pub fn getClipboardString(self: *const Surface) ![:0]const u8 {
_ = self;
return glfw.getClipboardString() orelse return glfw.mustGetErrorCode();
}
/// 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;
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 {
fn cursorPosToPixels(self: *const Surface, 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.
@ -349,8 +383,8 @@ pub const Window = struct {
// 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| {
const core_win = window.getUserPointer(CoreSurface) orelse return;
const size = core_win.rt_surface.getSize() catch |err| {
log.err("error querying window size for size callback err={}", .{err});
return;
};
@ -366,7 +400,7 @@ pub const Window = struct {
const tracy = trace(@src());
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| {
log.err("error in char callback err={}", .{err});
return;
@ -518,7 +552,7 @@ pub const Window = struct {
=> .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| {
log.err("error in key callback err={}", .{err});
return;
@ -529,7 +563,7 @@ pub const Window = struct {
const tracy = trace(@src());
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| {
log.err("error in focus callback err={}", .{err});
return;
@ -540,7 +574,7 @@ pub const Window = struct {
const tracy = trace(@src());
defer tracy.end();
const core_win = window.getUserPointer(CoreWindow) orelse return;
const core_win = window.getUserPointer(CoreSurface) orelse return;
core_win.refreshCallback() catch |err| {
log.err("error in refresh callback err={}", .{err});
return;
@ -551,7 +585,7 @@ pub const Window = struct {
const tracy = trace(@src());
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| {
log.err("error in scroll callback err={}", .{err});
return;
@ -566,10 +600,10 @@ pub const Window = struct {
const tracy = trace(@src());
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.
const pos = core_win.window.cursorPosToPixels(.{
const pos = core_win.rt_surface.cursorPosToPixels(.{
.xpos = unscaled_xpos,
.ypos = unscaled_ypos,
}) catch |err| {
@ -598,7 +632,7 @@ pub const Window = struct {
const tracy = trace(@src());
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
const mods = @bitCast(input.Mods, glfw_mods);

View File

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

View File

@ -18,7 +18,7 @@ const trace = @import("tracy").trace;
const math = @import("../math.zig");
const lru = @import("../lru.zig");
const DevMode = @import("../DevMode.zig");
const Window = @import("../Window.zig");
const Surface = @import("../Surface.zig");
const log = std.log.scoped(.grid);
@ -89,7 +89,7 @@ focused: bool,
padding: renderer.Options.Padding,
/// 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.
/// 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
/// window surface as necessary.
pub fn windowInit(win: apprt.runtime.Window) !void {
/// This is called early right after surface creation.
pub fn surfaceInit(surface: *apprt.Surface) !void {
// Treat this like a thread entry
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
// 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
/// 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;
_ = win;
_ = surface;
}
/// 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;
if (DevMode.enabled) {
// Initialize for our window
assert(imgui.ImplGlfw.initForOpenGL(
@ptrCast(*imgui.ImplGlfw.GLFWWindow, win.window.handle),
@ptrCast(*imgui.ImplGlfw.GLFWWindow, surface.window.handle),
true,
));
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.
pub fn threadEnter(self: *const OpenGL, win: apprt.runtime.Window) !void {
pub fn threadEnter(self: *const OpenGL, surface: *apprt.Surface) !void {
_ = self;
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:
// the prior thread MUST have detached the context prior to calling
// this entrypoint.
glfw.makeContextCurrent(win.window);
glfw.makeContextCurrent(surface.window);
errdefer glfw.makeContextCurrent(null);
glfw.swapInterval(1);
@ -548,7 +547,7 @@ fn resetFontMetrics(
/// The primary render callback that is completely thread-safe.
pub fn render(
self: *OpenGL,
win: apprt.runtime.Window,
surface: *apprt.Surface,
state: *renderer.State,
) !void {
// Data we extract out of the critical area.
@ -669,7 +668,7 @@ pub fn render(
// Swap our window buffers
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

View File

@ -1,8 +1,8 @@
//! The options that are used to configure a renderer.
const apprt = @import("../apprt.zig");
const font = @import("../font/main.zig");
const renderer = @import("../renderer.zig");
const Window = @import("../Window.zig");
const Config = @import("../config.zig").Config;
/// The app configuration.
@ -16,7 +16,7 @@ padding: Padding,
/// 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.
window_mailbox: Window.Mailbox,
window_mailbox: apprt.surface.Mailbox,
pub const Padding = struct {
// 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_cancel: xev.Completion = .{},
/// The window we're rendering to.
window: apprt.runtime.Window,
/// The surface we're rendering to.
surface: *apprt.Surface,
/// The underlying renderer implementation.
renderer: *renderer.Renderer,
@ -65,7 +65,7 @@ mailbox: *Mailbox,
/// is up to the caller to start the thread with the threadMain entrypoint.
pub fn init(
alloc: Allocator,
win: apprt.runtime.Window,
surface: *apprt.Surface,
renderer_impl: *renderer.Renderer,
state: *renderer.State,
) !Thread {
@ -100,7 +100,7 @@ pub fn init(
.stop = stop_h,
.render_h = render_h,
.cursor_h = cursor_timer,
.window = win,
.surface = surface,
.renderer = renderer_impl,
.state = state,
.mailbox = mailbox,
@ -135,7 +135,7 @@ fn threadMain_(self: *Thread) !void {
// Run our thread start/end callbacks. This is important because some
// renderers have to do per-thread setup. For example, OpenGL has to set
// 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();
// Start the async handlers
@ -305,7 +305,7 @@ fn renderCallback(
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});
return .disarm;
}

View File

@ -8,7 +8,6 @@ const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const termio = @import("../termio.zig");
const Command = @import("../Command.zig");
const Window = @import("../Window.zig");
const Pty = @import("../Pty.zig");
const SegmentedPool = @import("../segmented_pool.zig").SegmentedPool;
const terminal = @import("../terminal/main.zig");
@ -16,6 +15,7 @@ const xev = @import("xev");
const renderer = @import("../renderer.zig");
const tracy = @import("tracy");
const trace = tracy.trace;
const apprt = @import("../apprt.zig");
const fastmem = @import("../fastmem.zig");
const log = std.log.scoped(.io_exec);
@ -52,7 +52,7 @@ renderer_wakeup: xev.Async,
renderer_mailbox: *renderer.Thread.Mailbox,
/// 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.
grid_size: renderer.GridSize,
@ -638,7 +638,7 @@ const StreamHandler = struct {
alloc: Allocator,
grid_size: *renderer.GridSize,
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
/// mailbox. This can be used by callers to determine if they need
@ -1003,7 +1003,7 @@ const StreamHandler = struct {
// Write clipboard contents
_ = self.window_mailbox.push(.{
.clipboard_write = try Window.Message.WriteReq.init(
.clipboard_write = try apprt.surface.Message.WriteReq.init(
self.alloc,
data,
),

View File

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