macos: listen for color change property to update window appearance

This commit is contained in:
Mitchell Hashimoto
2024-11-21 13:36:49 -08:00
parent f722e30bf5
commit 7fb86a3c9c
5 changed files with 110 additions and 2 deletions

View File

@ -1,6 +1,7 @@
import Foundation
import Cocoa
import SwiftUI
import Combine
import GhosttyKit
/// A classic, tabbed terminal experience.
@ -23,6 +24,9 @@ class TerminalController: BaseTerminalController {
/// The configuration derived from the Ghostty config so we don't need to rely on references.
private var derivedConfig: DerivedConfig
/// The notification cancellable for focused surface property changes.
private var surfaceAppearanceCancellables: Set<AnyCancellable> = []
init(_ ghostty: Ghostty.App,
withBaseConfig base: Ghostty.SurfaceConfiguration? = nil,
withSurfaceTree tree: Ghostty.SplitNode? = nil
@ -225,7 +229,7 @@ class TerminalController: BaseTerminalController {
// The titlebar is always updated. We don't need to worry about opacity
// because we handle it here.
let backgroundColor = OSColor(surfaceConfig.backgroundColor)
let backgroundColor = OSColor(focusedSurface?.backgroundColor ?? surfaceConfig.backgroundColor)
window.titlebarColor = backgroundColor.withAlphaComponent(surfaceConfig.backgroundOpacity)
if (window.isOpaque) {
@ -536,10 +540,31 @@ class TerminalController: BaseTerminalController {
override func focusedSurfaceDidChange(to: Ghostty.SurfaceView?) {
super.focusedSurfaceDidChange(to: to)
// We always cancel our event listener
surfaceAppearanceCancellables.removeAll()
// When our focus changes, we update our window appearance based on the
// currently focused surface.
guard let focusedSurface else { return }
syncAppearance(focusedSurface.derivedConfig)
// We also want to get notified of certain changes to update our appearance.
focusedSurface.$derivedConfig
.sink { [weak self, weak focusedSurface] _ in self?.syncAppearanceOnPropertyChange(focusedSurface) }
.store(in: &surfaceAppearanceCancellables)
focusedSurface.$backgroundColor
.sink { [weak self, weak focusedSurface] _ in self?.syncAppearanceOnPropertyChange(focusedSurface) }
.store(in: &surfaceAppearanceCancellables)
}
private func syncAppearanceOnPropertyChange(_ surface: Ghostty.SurfaceView?) {
guard let surface else { return }
DispatchQueue.main.async { [weak self, weak surface] in
guard let surface else { return }
guard let self else { return }
guard self.focusedSurface == surface else { return }
self.syncAppearance(surface.derivedConfig)
}
}
//MARK: - Notifications

View File

@ -1,3 +1,4 @@
import SwiftUI
import GhosttyKit
extension Ghostty {
@ -5,6 +6,33 @@ extension Ghostty {
}
extension Ghostty.Action {
struct ColorChange {
let kind: Kind
let color: Color
enum Kind {
case foreground
case background
case cursor
case palette(index: UInt8)
}
init(c: ghostty_action_color_change_s) {
switch (c.kind) {
case GHOSTTY_ACTION_COLOR_KIND_FOREGROUND:
self.kind = .foreground
case GHOSTTY_ACTION_COLOR_KIND_BACKGROUND:
self.kind = .background
case GHOSTTY_ACTION_COLOR_KIND_CURSOR:
self.kind = .cursor
default:
self.kind = .palette(index: UInt8(c.kind.rawValue))
}
self.color = Color(red: Double(c.r) / 255, green: Double(c.g) / 255, blue: Double(c.b) / 255)
}
}
struct MoveTab {
let amount: Int

View File

@ -515,7 +515,8 @@ extension Ghostty {
configChange(app, target: target, v: action.action.config_change)
case GHOSTTY_ACTION_COLOR_CHANGE:
fallthrough
colorChange(app, target: target, change: action.action.color_change)
case GHOSTTY_ACTION_CLOSE_ALL_WINDOWS:
fallthrough
case GHOSTTY_ACTION_TOGGLE_TAB_OVERVIEW:
@ -1188,6 +1189,32 @@ extension Ghostty {
}
}
private static func colorChange(
_ app: ghostty_app_t,
target: ghostty_target_s,
change: ghostty_action_color_change_s) {
switch (target.tag) {
case GHOSTTY_TARGET_APP:
Ghostty.logger.warning("color change does nothing with an app target")
return
case GHOSTTY_TARGET_SURFACE:
guard let surface = target.target.surface else { return }
guard let surfaceView = self.surfaceView(from: surface) else { return }
NotificationCenter.default.post(
name: .ghosttyColorDidChange,
object: surfaceView,
userInfo: [
SwiftUI.Notification.Name.GhosttyColorChangeKey: Action.ColorChange(c: change)
]
)
default:
assertionFailure()
}
}
// MARK: User Notifications
/// Handle a received user notification. This is called when a user notification is clicked or dismissed by the user

View File

@ -210,6 +210,10 @@ extension Notification.Name {
static let ghosttyConfigDidChange = Notification.Name("com.mitchellh.ghostty.configDidChange")
static let GhosttyConfigChangeKey = ghosttyConfigDidChange.rawValue
/// Color change. Object is the surface changing.
static let ghosttyColorDidChange = Notification.Name("com.mitchellh.ghostty.ghosttyColorDidChange")
static let GhosttyColorChangeKey = ghosttyColorDidChange.rawValue
/// Goto tab. Has tab index in the userinfo.
static let ghosttyMoveTab = Notification.Name("com.mitchellh.ghostty.moveTab")
static let GhosttyMoveTabKey = ghosttyMoveTab.rawValue

View File

@ -51,6 +51,10 @@ extension Ghostty {
/// The configuration derived from the Ghostty config so we don't need to rely on references.
@Published private(set) var derivedConfig: DerivedConfig
/// The background color within the color palette of the surface. This is only set if it is
/// dynamically updated. Otherwise, the background color is the default background color.
@Published private(set) var backgroundColor: Color? = nil
// 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
@ -152,6 +156,11 @@ extension Ghostty {
selector: #selector(ghosttyConfigDidChange(_:)),
name: .ghosttyConfigDidChange,
object: self)
center.addObserver(
self,
selector: #selector(ghosttyColorDidChange(_:)),
name: .ghosttyColorDidChange,
object: self)
center.addObserver(
self,
selector: #selector(windowDidChangeScreen),
@ -358,6 +367,21 @@ extension Ghostty {
self.derivedConfig = DerivedConfig(config)
}
@objc private func ghosttyColorDidChange(_ notification: SwiftUI.Notification) {
guard let change = notification.userInfo?[
SwiftUI.Notification.Name.GhosttyColorChangeKey
] as? Ghostty.Action.ColorChange else { return }
switch (change.kind) {
case .background:
self.backgroundColor = change.color
default:
// We don't do anything for the other colors yet.
break
}
}
@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 }