mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
it draws!
This commit is contained in:
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)")
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
@ -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;
|
||||||
|
};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
@ -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.
|
||||||
|
Reference in New Issue
Block a user