mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-22 11:46:11 +03:00
Integrating new surface
This commit is contained in:
169
src/App.zig
169
src/App.zig
@ -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;
|
||||
|
@ -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
1635
src/Surface.zig
Normal file
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
|
@ -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);
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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,
|
||||
),
|
||||
|
@ -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,
|
||||
|
Reference in New Issue
Block a user