feat: add macos-window-shadow

optionally enable the window shadow (and border) like Terminal.app
This commit is contained in:
Haze Booth
2024-06-07 00:21:09 -04:00
parent 54ccefe838
commit d5e6fcd170
6 changed files with 42 additions and 20 deletions

View File

@ -71,8 +71,8 @@ class TerminalController: NSWindowController, NSWindowDelegate,
super.init(window: nil) super.init(window: nil)
// Initialize our initial surface. // Initialize our initial surface.
guard let ghostty_app = ghostty.app else { preconditionFailure("app must be loaded") } guard ghostty.app != nil else { preconditionFailure("app must be loaded") }
self.surfaceTree = tree ?? .leaf(.init(ghostty_app, baseConfig: base)) self.surfaceTree = tree ?? .leaf(.init(ghostty, baseConfig: base))
// Setup our notifications for behaviors // Setup our notifications for behaviors
let center = NotificationCenter.default let center = NotificationCenter.default

View File

@ -236,6 +236,14 @@ extension Ghostty {
return v return v
} }
var macosWindowShadow: Bool {
guard let config = self.config else { return false }
var v = false;
let key = "macos-window-shadow"
_ = ghostty_config_get(config, &v, key, UInt(key.count))
return v
}
var backgroundColor: Color { var backgroundColor: Color {
var rgb: UInt32 = 0 var rgb: UInt32 = 0
let bg_key = "background" let bg_key = "background"

View File

@ -168,13 +168,13 @@ extension Ghostty {
} }
class Leaf: ObservableObject, Equatable, Hashable, Codable { class Leaf: ObservableObject, Equatable, Hashable, Codable {
let app: ghostty_app_t let app: Ghostty.App
@Published var surface: SurfaceView @Published var surface: SurfaceView
weak var parent: SplitNode.Container? weak var parent: SplitNode.Container?
/// Initialize a new leaf which creates a new terminal surface. /// Initialize a new leaf which creates a new terminal surface.
init(_ app: ghostty_app_t, baseConfig: SurfaceConfiguration? = nil, uuid: UUID? = nil) { init(_ app: Ghostty.App, baseConfig: SurfaceConfiguration? = nil, uuid: UUID? = nil) {
self.app = app self.app = app
self.surface = SurfaceView(app, baseConfig: baseConfig, uuid: uuid) self.surface = SurfaceView(app, baseConfig: baseConfig, uuid: uuid)
} }
@ -182,14 +182,14 @@ extension Ghostty {
// MARK: - Hashable // MARK: - Hashable
func hash(into hasher: inout Hasher) { func hash(into hasher: inout Hasher) {
hasher.combine(app) hasher.combine(app.app)
hasher.combine(surface) hasher.combine(surface)
} }
// MARK: - Equatable // MARK: - Equatable
static func == (lhs: Leaf, rhs: Leaf) -> Bool { static func == (lhs: Leaf, rhs: Leaf) -> Bool {
return lhs.app == rhs.app && lhs.surface === rhs.surface return lhs.app.app == rhs.app.app && lhs.surface === rhs.surface
} }
// MARK: - Codable // MARK: - Codable
@ -203,7 +203,7 @@ extension Ghostty {
// Decoding uses the global Ghostty app // Decoding uses the global Ghostty app
guard let del = NSApplication.shared.delegate, guard let del = NSApplication.shared.delegate,
let appDel = del as? AppDelegate, let appDel = del as? AppDelegate,
let app = appDel.ghostty.app else { appDel.ghostty.app != nil else {
throw TerminalRestoreError.delegateInvalid throw TerminalRestoreError.delegateInvalid
} }
@ -212,7 +212,7 @@ extension Ghostty {
var config = SurfaceConfiguration() var config = SurfaceConfiguration()
config.workingDirectory = try container.decode(String?.self, forKey: .pwd) config.workingDirectory = try container.decode(String?.self, forKey: .pwd)
self.init(app, baseConfig: config, uuid: uuid) self.init(appDel.ghostty, baseConfig: config, uuid: uuid)
} }
func encode(to encoder: Encoder) throws { func encode(to encoder: Encoder) throws {
@ -223,7 +223,7 @@ extension Ghostty {
} }
class Container: ObservableObject, Equatable, Hashable, Codable { class Container: ObservableObject, Equatable, Hashable, Codable {
let app: ghostty_app_t let app: Ghostty.App
let direction: SplitViewDirection let direction: SplitViewDirection
@Published var topLeft: SplitNode @Published var topLeft: SplitNode
@ -335,7 +335,7 @@ extension Ghostty {
// MARK: - Hashable // MARK: - Hashable
func hash(into hasher: inout Hasher) { func hash(into hasher: inout Hasher) {
hasher.combine(app) hasher.combine(app.app)
hasher.combine(direction) hasher.combine(direction)
hasher.combine(topLeft) hasher.combine(topLeft)
hasher.combine(bottomRight) hasher.combine(bottomRight)
@ -344,7 +344,7 @@ extension Ghostty {
// MARK: - Equatable // MARK: - Equatable
static func == (lhs: Container, rhs: Container) -> Bool { static func == (lhs: Container, rhs: Container) -> Bool {
return lhs.app == rhs.app && return lhs.app.app == rhs.app.app &&
lhs.direction == rhs.direction && lhs.direction == rhs.direction &&
lhs.topLeft == rhs.topLeft && lhs.topLeft == rhs.topLeft &&
lhs.bottomRight == rhs.bottomRight lhs.bottomRight == rhs.bottomRight
@ -363,12 +363,12 @@ extension Ghostty {
// Decoding uses the global Ghostty app // Decoding uses the global Ghostty app
guard let del = NSApplication.shared.delegate, guard let del = NSApplication.shared.delegate,
let appDel = del as? AppDelegate, let appDel = del as? AppDelegate,
let app = appDel.ghostty.app else { appDel.ghostty.app != nil else {
throw TerminalRestoreError.delegateInvalid throw TerminalRestoreError.delegateInvalid
} }
let container = try decoder.container(keyedBy: CodingKeys.self) let container = try decoder.container(keyedBy: CodingKeys.self)
self.app = app self.app = appDel.ghostty
self.direction = try container.decode(SplitViewDirection.self, forKey: .direction) self.direction = try container.decode(SplitViewDirection.self, forKey: .direction)
self.split = try container.decode(CGFloat.self, forKey: .split) self.split = try container.decode(CGFloat.self, forKey: .split)
self.topLeft = try container.decode(SplitNode.self, forKey: .topLeft) self.topLeft = try container.decode(SplitNode.self, forKey: .topLeft)

View File

@ -9,8 +9,8 @@ extension Ghostty {
@FocusedValue(\.ghosttySurfaceTitle) private var surfaceTitle: String? @FocusedValue(\.ghosttySurfaceTitle) private var surfaceTitle: String?
var body: some View { var body: some View {
if let app = self.ghostty.app { if self.ghostty.app != nil {
SurfaceForApp(app) { surfaceView in SurfaceForApp(self.ghostty) { surfaceView in
SurfaceWrapper(surfaceView: surfaceView) SurfaceWrapper(surfaceView: surfaceView)
} }
.navigationTitle(surfaceTitle ?? "Ghostty") .navigationTitle(surfaceTitle ?? "Ghostty")
@ -24,7 +24,7 @@ extension Ghostty {
@StateObject private var surfaceView: SurfaceView @StateObject private var surfaceView: SurfaceView
init(_ app: ghostty_app_t, @ViewBuilder content: @escaping ((SurfaceView) -> Content)) { init(_ app: Ghostty.App, @ViewBuilder content: @escaping ((SurfaceView) -> Content)) {
_surfaceView = StateObject(wrappedValue: SurfaceView(app)) _surfaceView = StateObject(wrappedValue: SurfaceView(app))
self.content = content self.content = content
} }

View File

@ -86,6 +86,8 @@ extension Ghostty {
// so we'll use that to tell ghostty to refresh. // so we'll use that to tell ghostty to refresh.
override var wantsUpdateLayer: Bool { return true } override var wantsUpdateLayer: Bool { return true }
private let app: Ghostty.App
// State machine for mouse cursor visibility because every call to // State machine for mouse cursor visibility because every call to
// NSCursor.hide/unhide must be balanced. // NSCursor.hide/unhide must be balanced.
enum CursorVisibility { enum CursorVisibility {
@ -95,9 +97,10 @@ extension Ghostty {
case pendingHidden case pendingHidden
} }
init(_ app: ghostty_app_t, baseConfig: SurfaceConfiguration? = nil, uuid: UUID? = nil) { init(_ app: Ghostty.App, baseConfig: SurfaceConfiguration? = nil, uuid: UUID? = nil) {
self.markedText = NSMutableAttributedString() self.markedText = NSMutableAttributedString()
self.uuid = uuid ?? .init() self.uuid = uuid ?? .init()
self.app = app
// Initialize with some default frame size. The important thing is that this // 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 // is non-zero so that our layer bounds are non-zero so that our renderer
@ -121,7 +124,7 @@ extension Ghostty {
// Setup our surface. This will also initialize all the terminal IO. // Setup our surface. This will also initialize all the terminal IO.
let surface_cfg = baseConfig ?? SurfaceConfiguration() let surface_cfg = baseConfig ?? SurfaceConfiguration()
var surface_cfg_c = surface_cfg.ghosttyConfig(view: self) var surface_cfg_c = surface_cfg.ghosttyConfig(view: self)
guard let surface = ghostty_surface_new(app, &surface_cfg_c) else { guard let surface = ghostty_surface_new(app.app, &surface_cfg_c) else {
self.error = AppError.surfaceCreateError self.error = AppError.surfaceCreateError
return return
} }
@ -369,8 +372,16 @@ extension Ghostty {
// Set the window transparency settings // Set the window transparency settings
window.isOpaque = false window.isOpaque = false
window.hasShadow = false
window.backgroundColor = .clear if app.config.macosWindowShadow {
window.hasShadow = true
// NOTE(haze): I have no idea why this works, but it does. This emulates the
// aesthetic of `Terminal.app`.
window.backgroundColor = .white.withAlphaComponent(0.001)
} else {
window.hasShadow = false
window.backgroundColor = .clear
}
// If we have a blur, set the blur // If we have a blur, set the blur
ghostty_set_window_background_blur(surface, Unmanaged.passUnretained(window).toOpaque()) ghostty_set_window_background_blur(surface, Unmanaged.passUnretained(window).toOpaque())

View File

@ -976,6 +976,9 @@ keybind: Keybinds = .{},
/// This option only applies to new windows when changed. /// This option only applies to new windows when changed.
@"macos-titlebar-tabs": bool = false, @"macos-titlebar-tabs": bool = false,
/// If `true`, render the drop shadow behind the macOS window.
@"macos-window-shadow": bool = true,
/// If `true`, the *Option* key will be treated as *Alt*. This makes terminal /// If `true`, the *Option* key will be treated as *Alt*. This makes terminal
/// sequences expecting *Alt* to work properly, but will break Unicode input /// sequences expecting *Alt* to work properly, but will break Unicode input
/// sequences on macOS if you use them via the *Alt* key. You may set this to /// sequences on macOS if you use them via the *Alt* key. You may set this to