it draws!

This commit is contained in:
Mitchell Hashimoto
2023-02-17 20:07:51 -08:00
parent ff9af8a07b
commit cd77408efc
5 changed files with 66 additions and 36 deletions

View File

@ -53,6 +53,7 @@ int ghostty_app_tick(ghostty_app_t);
ghostty_surface_t ghostty_surface_new(ghostty_app_t, ghostty_surface_config_s*); ghostty_surface_t ghostty_surface_new(ghostty_app_t, ghostty_surface_config_s*);
void ghostty_surface_free(ghostty_surface_t); void ghostty_surface_free(ghostty_surface_t);
void ghostty_surface_set_size(ghostty_surface_t, uint32_t, uint32_t);
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@ -10,17 +10,17 @@ import GhosttyKit
/// since that is what the Metal renderer in Ghostty expects. In the future, it may make more sense to /// since that is what the Metal renderer in Ghostty expects. In the future, it may make more sense to
/// wrap an MTKView and use that, but for legacy reasons we didn't do that to begin with. /// wrap an MTKView and use that, but for legacy reasons we didn't do that to begin with.
struct TerminalSurfaceView: NSViewRepresentable { struct TerminalSurfaceView: NSViewRepresentable {
@StateObject private var state: TerminalSurfaceState @StateObject private var state: TerminalSurfaceView_Real
init(app: ghostty_app_t) { init(app: ghostty_app_t) {
self._state = StateObject(wrappedValue: TerminalSurfaceState(app)) self._state = StateObject(wrappedValue: TerminalSurfaceView_Real(app))
} }
func makeNSView(context: Context) -> TerminalSurfaceView_Real { func makeNSView(context: Context) -> TerminalSurfaceView_Real {
// We need the view as part of the state to be created previously because // We need the view as part of the state to be created previously because
// the view is sent to the Ghostty API so that it can manipulate it // the view is sent to the Ghostty API so that it can manipulate it
// directly since we draw on a render thread. // directly since we draw on a render thread.
return state.view; return state;
} }
func updateNSView(_ view: TerminalSurfaceView_Real, context: Context) { func updateNSView(_ view: TerminalSurfaceView_Real, context: Context) {
@ -28,22 +28,23 @@ struct TerminalSurfaceView: NSViewRepresentable {
} }
} }
/// The state for the terminal surface view. // The actual NSView implementation for the terminal surface.
class TerminalSurfaceState: ObservableObject { class TerminalSurfaceView_Real: NSView, ObservableObject {
static let logger = Logger( // We need to support being a first responder so that we can get input events
subsystem: Bundle.main.bundleIdentifier!, override var acceptsFirstResponder: Bool { return true }
category: String(describing: TerminalSurfaceState.self)
)
var view: TerminalSurfaceView_Real
private var surface: ghostty_surface_t? = nil private var surface: ghostty_surface_t? = nil
private var error: Error? = nil private var error: Error? = nil
init(_ app: ghostty_app_t) { init(_ app: ghostty_app_t) {
view = TerminalSurfaceView_Real() // Initialize with some default frame size. The important thing is that this
// is non-zero so that our layer bounds are non-zero so that our renderer
// can do SOMETHING.
super.init(frame: NSMakeRect(0, 0, 800, 600))
// Setup our surface. This will also initialize all the terminal IO.
var surface_cfg = ghostty_surface_config_s( var surface_cfg = ghostty_surface_config_s(
nsview: Unmanaged.passUnretained(view).toOpaque(), nsview: Unmanaged.passUnretained(self).toOpaque(),
scale_factor: 2.0) scale_factor: 2.0)
guard let surface = ghostty_surface_new(app, &surface_cfg) else { guard let surface = ghostty_surface_new(app, &surface_cfg) else {
self.error = AppError.surfaceCreateError self.error = AppError.surfaceCreateError
@ -53,17 +54,16 @@ class TerminalSurfaceState: ObservableObject {
self.surface = surface; self.surface = surface;
} }
deinit { required init?(coder: NSCoder) {
if let surface = self.surface { fatalError("init(coder:) is not supported for this view")
ghostty_surface_free(surface) }
}
override func resize(withOldSuperviewSize oldSize: NSSize) {
print("LAYER: \(self.layer?.bounds)")
super.resize(withOldSuperviewSize: oldSize)
print("RESIZE: \(oldSize) NEW: \(self.bounds)")
print("LAYER: \(self.layer?.bounds)")
} }
}
// The actual NSView implementation for the terminal surface.
class TerminalSurfaceView_Real: NSView {
// We need to support being a first responder so that we can get input events
override var acceptsFirstResponder: Bool { return true }
override func draw(_ dirtyRect: NSRect) { override func draw(_ dirtyRect: NSRect) {
print("DRAW: \(dirtyRect)") print("DRAW: \(dirtyRect)")

View File

@ -396,4 +396,10 @@ pub const CAPI = struct {
export fn ghostty_surface_free(ptr: ?*Window) void { export fn ghostty_surface_free(ptr: ?*Window) void {
if (ptr) |v| v.app.closeWindow(v); if (ptr) |v| v.app.closeWindow(v);
} }
/// 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 {
win.window.updateSize(w, h);
}
}; };

View File

@ -13,6 +13,8 @@ const apprt = @import("../apprt.zig");
const CoreApp = @import("../App.zig"); const CoreApp = @import("../App.zig");
const CoreWindow = @import("../Window.zig"); const CoreWindow = @import("../Window.zig");
const log = std.log.scoped(.embedded_window);
pub const App = struct { pub const App = struct {
/// Because we only expect the embedding API to be used in embedded /// Because we only expect the embedding API to be used in embedded
/// environments, the options are extern so that we can expose it /// environments, the options are extern so that we can expose it
@ -48,6 +50,7 @@ pub const App = struct {
pub const Window = struct { pub const Window = struct {
nsview: objc.Object, nsview: objc.Object,
scale_factor: f64, scale_factor: f64,
core_win: *CoreWindow,
pub const Options = extern struct { pub const Options = extern struct {
/// The pointer to the backing NSView for the surface. /// The pointer to the backing NSView for the surface.
@ -59,9 +62,9 @@ pub const Window = struct {
pub fn init(app: *const CoreApp, core_win: *CoreWindow, opts: Options) !Window { pub fn init(app: *const CoreApp, core_win: *CoreWindow, opts: Options) !Window {
_ = app; _ = app;
_ = core_win;
return .{ return .{
.core_win = core_win,
.nsview = objc.Object.fromId(opts.nsview), .nsview = objc.Object.fromId(opts.nsview),
.scale_factor = opts.scale_factor, .scale_factor = opts.scale_factor,
}; };
@ -113,4 +116,17 @@ pub const Window = struct {
_ = self; _ = self;
return false; return false;
} }
pub fn updateSize(self: *const Window, width: u32, height: u32) void {
const size: apprt.WindowSize = .{
.width = width,
.height = height,
};
// Call the primary callback.
self.core_win.sizeCallback(size) catch |err| {
log.err("error in size callback err={}", .{err});
return;
};
}
}; };

View File

@ -568,9 +568,14 @@ pub fn render(
.{@as(c_ulong, 0)}, .{@as(c_ulong, 0)},
); );
// Texture is a property of CAMetalDrawable but if you run
// Ghostty in XCode in debug mode it returns a CaptureMTLDrawable
// which ironically doesn't implement CAMetalDrawable as a
// property so we just send a message.
const texture = surface.msgSend(objc.c.id, objc.sel("texture"), .{});
attachment.setProperty("loadAction", @enumToInt(MTLLoadAction.clear)); attachment.setProperty("loadAction", @enumToInt(MTLLoadAction.clear));
attachment.setProperty("storeAction", @enumToInt(MTLStoreAction.store)); attachment.setProperty("storeAction", @enumToInt(MTLStoreAction.store));
attachment.setProperty("texture", surface.getProperty(objc.c.id, "texture").?); attachment.setProperty("texture", texture);
attachment.setProperty("clearColor", MTLClearColor{ attachment.setProperty("clearColor", MTLClearColor{
.red = @intToFloat(f32, critical.bg.r) / 255, .red = @intToFloat(f32, critical.bg.r) / 255,
.green = @intToFloat(f32, critical.bg.g) / 255, .green = @intToFloat(f32, critical.bg.g) / 255,
@ -673,18 +678,20 @@ fn drawCells(
.{ buf.value, @as(c_ulong, 0), @as(c_ulong, 0) }, .{ buf.value, @as(c_ulong, 0), @as(c_ulong, 0) },
); );
encoder.msgSend( if (cells.items.len > 0) {
void, encoder.msgSend(
objc.sel("drawIndexedPrimitives:indexCount:indexType:indexBuffer:indexBufferOffset:instanceCount:"), void,
.{ objc.sel("drawIndexedPrimitives:indexCount:indexType:indexBuffer:indexBufferOffset:instanceCount:"),
@enumToInt(MTLPrimitiveType.triangle), .{
@as(c_ulong, 6), @enumToInt(MTLPrimitiveType.triangle),
@enumToInt(MTLIndexType.uint16), @as(c_ulong, 6),
self.buf_instance.value, @enumToInt(MTLIndexType.uint16),
@as(c_ulong, 0), self.buf_instance.value,
@as(c_ulong, cells.items.len), @as(c_ulong, 0),
}, @as(c_ulong, cells.items.len),
); },
);
}
} }
/// Resize the screen. /// Resize the screen.