mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
feat: add macos-window-shadow
optionally enable the window shadow (and border) like Terminal.app
This commit is contained in:
@ -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
|
||||||
|
@ -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"
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -85,6 +85,8 @@ extension Ghostty {
|
|||||||
// I don't think we need this but this lets us know we should redraw our layer
|
// I don't think we need this but this lets us know we should redraw our layer
|
||||||
// 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.
|
||||||
@ -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())
|
||||||
|
@ -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
|
||||||
|
Reference in New Issue
Block a user