mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-04-23 01:48:37 +03:00
macos: terminal controller reacts to surface config changes
This commit is contained in:
@ -93,21 +93,37 @@ class TerminalController: BaseTerminalController {
|
||||
//MARK: - Methods
|
||||
|
||||
@objc private func ghosttyConfigDidChange(_ notification: Notification) {
|
||||
// We only care if the configuration is a global configuration, not a
|
||||
// surface-specific one.
|
||||
guard notification.object == nil else { return }
|
||||
|
||||
// Get our managed configuration object out
|
||||
guard let config = notification.userInfo?[
|
||||
Notification.Name.GhosttyConfigChangeKey
|
||||
] as? Ghostty.Config else { return }
|
||||
|
||||
// Update our derived config
|
||||
self.derivedConfig = DerivedConfig(config)
|
||||
// If this is an app-level config update then we update some things.
|
||||
if (notification.object == nil) {
|
||||
// Update our derived config
|
||||
self.derivedConfig = DerivedConfig(config)
|
||||
|
||||
guard let window = window as? TerminalWindow else { return }
|
||||
window.focusFollowsMouse = config.focusFollowsMouse
|
||||
syncAppearance(config)
|
||||
guard let window = window as? TerminalWindow else { return }
|
||||
window.focusFollowsMouse = config.focusFollowsMouse
|
||||
|
||||
// If we have no surfaces in our window (is that possible?) then we update
|
||||
// our window appearance based on the root config. If we have surfaces, we
|
||||
// don't call this because the TODO
|
||||
if surfaceTree == nil {
|
||||
syncAppearance(.init(config))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// This is a surface-level config update. If we have the surface, we
|
||||
// update our appearance based on it.
|
||||
guard let surfaceView = notification.object as? Ghostty.SurfaceView else { return }
|
||||
guard surfaceTree?.contains(view: surfaceView) ?? false else { return }
|
||||
|
||||
// We can't use surfaceView.derivedConfig because it may not be updated
|
||||
// yet since it also responds to notifications.
|
||||
syncAppearance(.init(config))
|
||||
}
|
||||
|
||||
/// Update the accessory view of each tab according to the keyboard
|
||||
@ -168,7 +184,7 @@ class TerminalController: BaseTerminalController {
|
||||
self.relabelTabs()
|
||||
}
|
||||
|
||||
private func syncAppearance(_ config: Ghostty.Config) {
|
||||
private func syncAppearance(_ surfaceConfig: Ghostty.SurfaceView.DerivedConfig) {
|
||||
guard let window = self.window as? TerminalWindow else { return }
|
||||
|
||||
// If our window is not visible, then delay this. This is possible specifically
|
||||
@ -177,19 +193,19 @@ class TerminalController: BaseTerminalController {
|
||||
// APIs such as window blur have no effect unless the window is visible.
|
||||
guard window.isVisible else {
|
||||
// Weak window so that if the window changes or is destroyed we aren't holding a ref
|
||||
DispatchQueue.main.async { [weak self] in self?.syncAppearance(config) }
|
||||
DispatchQueue.main.async { [weak self] in self?.syncAppearance(surfaceConfig) }
|
||||
return
|
||||
}
|
||||
|
||||
// Set the font for the window and tab titles.
|
||||
if let titleFontName = config.windowTitleFontFamily {
|
||||
if let titleFontName = surfaceConfig.windowTitleFontFamily {
|
||||
window.titlebarFont = NSFont(name: titleFontName, size: NSFont.systemFontSize)
|
||||
} else {
|
||||
window.titlebarFont = nil
|
||||
}
|
||||
|
||||
// If we have window transparency then set it transparent. Otherwise set it opaque.
|
||||
if (config.backgroundOpacity < 1) {
|
||||
if (surfaceConfig.backgroundOpacity < 1) {
|
||||
window.isOpaque = false
|
||||
|
||||
// This is weird, but we don't use ".clear" because this creates a look that
|
||||
@ -203,14 +219,14 @@ class TerminalController: BaseTerminalController {
|
||||
window.backgroundColor = .windowBackgroundColor
|
||||
}
|
||||
|
||||
window.hasShadow = config.macosWindowShadow
|
||||
window.hasShadow = surfaceConfig.macosWindowShadow
|
||||
|
||||
guard window.hasStyledTabs else { return }
|
||||
|
||||
// The titlebar is always updated. We don't need to worry about opacity
|
||||
// because we handle it here.
|
||||
let backgroundColor = OSColor(config.backgroundColor)
|
||||
window.titlebarColor = backgroundColor.withAlphaComponent(config.backgroundOpacity)
|
||||
let backgroundColor = OSColor(surfaceConfig.backgroundColor)
|
||||
window.titlebarColor = backgroundColor.withAlphaComponent(surfaceConfig.backgroundOpacity)
|
||||
|
||||
if (window.isOpaque) {
|
||||
// Bg color is only synced if we have no transparency. This is because
|
||||
@ -377,8 +393,10 @@ class TerminalController: BaseTerminalController {
|
||||
|
||||
window.focusFollowsMouse = config.focusFollowsMouse
|
||||
|
||||
// Apply any additional appearance-related properties to the new window.
|
||||
syncAppearance(config)
|
||||
// Apply any additional appearance-related properties to the new window. We
|
||||
// apply this based on the root config but change it later based on surface
|
||||
// config (see focused surface change callback).
|
||||
syncAppearance(.init(config))
|
||||
}
|
||||
|
||||
// Shows the "+" button in the tab bar, responds to that click.
|
||||
@ -515,6 +533,15 @@ class TerminalController: BaseTerminalController {
|
||||
window.surfaceIsZoomed = to
|
||||
}
|
||||
|
||||
override func focusedSurfaceDidChange(to: Ghostty.SurfaceView?) {
|
||||
super.focusedSurfaceDidChange(to: to)
|
||||
|
||||
// When our focus changes, we update our window appearance based on the
|
||||
// currently focused surface.
|
||||
guard let focusedSurface else { return }
|
||||
syncAppearance(focusedSurface.derivedConfig)
|
||||
}
|
||||
|
||||
//MARK: - Notifications
|
||||
|
||||
@objc private func onMoveTab(notification: SwiftUI.Notification) {
|
||||
|
@ -48,6 +48,9 @@ extension Ghostty {
|
||||
// Whether the pointer should be visible or not
|
||||
@Published private(set) var pointerStyle: BackportPointerStyle = .default
|
||||
|
||||
/// The configuration derived from the Ghostty config so we don't need to rely on references.
|
||||
@Published private(set) var derivedConfig: DerivedConfig
|
||||
|
||||
// An initial size to request for a window. This will only affect
|
||||
// then the view is moved to a new window.
|
||||
var initialSize: NSSize? = nil
|
||||
@ -114,6 +117,13 @@ extension Ghostty {
|
||||
self.markedText = NSMutableAttributedString()
|
||||
self.uuid = uuid ?? .init()
|
||||
|
||||
// Our initial config always is our application wide config.
|
||||
if let appDelegate = NSApplication.shared.delegate as? AppDelegate {
|
||||
self.derivedConfig = DerivedConfig(appDelegate.ghostty.config)
|
||||
} else {
|
||||
self.derivedConfig = DerivedConfig()
|
||||
}
|
||||
|
||||
// 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.
|
||||
@ -137,6 +147,11 @@ extension Ghostty {
|
||||
selector: #selector(ghosttyDidEndKeySequence),
|
||||
name: Ghostty.Notification.didEndKeySequence,
|
||||
object: self)
|
||||
center.addObserver(
|
||||
self,
|
||||
selector: #selector(ghosttyConfigDidChange(_:)),
|
||||
name: .ghosttyConfigDidChange,
|
||||
object: self)
|
||||
center.addObserver(
|
||||
self,
|
||||
selector: #selector(windowDidChangeScreen),
|
||||
@ -333,6 +348,16 @@ extension Ghostty {
|
||||
keySequence = []
|
||||
}
|
||||
|
||||
@objc private func ghosttyConfigDidChange(_ notification: SwiftUI.Notification) {
|
||||
// Get our managed configuration object out
|
||||
guard let config = notification.userInfo?[
|
||||
SwiftUI.Notification.Name.GhosttyConfigChangeKey
|
||||
] as? Ghostty.Config else { return }
|
||||
|
||||
// Update our derived config
|
||||
self.derivedConfig = DerivedConfig(config)
|
||||
}
|
||||
|
||||
@objc private func windowDidChangeScreen(notification: SwiftUI.Notification) {
|
||||
guard let window = self.window else { return }
|
||||
guard let object = notification.object as? NSWindow, window == object else { return }
|
||||
@ -1025,6 +1050,27 @@ extension Ghostty {
|
||||
Ghostty.moveFocus(to: self)
|
||||
}
|
||||
}
|
||||
|
||||
struct DerivedConfig {
|
||||
let backgroundColor: Color
|
||||
let backgroundOpacity: Double
|
||||
let macosWindowShadow: Bool
|
||||
let windowTitleFontFamily: String?
|
||||
|
||||
init() {
|
||||
self.backgroundColor = Color(NSColor.windowBackgroundColor)
|
||||
self.backgroundOpacity = 1
|
||||
self.macosWindowShadow = true
|
||||
self.windowTitleFontFamily = nil
|
||||
}
|
||||
|
||||
init(_ config: Ghostty.Config) {
|
||||
self.backgroundColor = config.backgroundColor
|
||||
self.backgroundOpacity = config.backgroundOpacity
|
||||
self.macosWindowShadow = config.macosWindowShadow
|
||||
self.windowTitleFontFamily = config.windowTitleFontFamily
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -365,7 +365,6 @@ const c = @cImport({
|
||||
/// be fixed in a future update:
|
||||
///
|
||||
/// - macOS: titlebar tabs style is not updated when switching themes.
|
||||
/// - macOS: native titlebar style is not supported.
|
||||
///
|
||||
theme: ?Theme = null,
|
||||
|
||||
@ -2756,12 +2755,6 @@ pub fn finalize(self: *Config) !void {
|
||||
// This setting doesn't make sense with different light/dark themes
|
||||
// because it'll force the theme based on the Ghostty theme.
|
||||
if (self.@"window-theme" == .auto) self.@"window-theme" = .system;
|
||||
|
||||
// This is buggy with different light/dark themes and is noted
|
||||
// in the documentation.
|
||||
if (self.@"macos-titlebar-style" == .transparent) {
|
||||
self.@"macos-titlebar-style" = .native;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user