mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
macos: listen for color change property to update window appearance
This commit is contained in:
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 }
|
||||
|
Reference in New Issue
Block a user