Match Ghostty split dimming and divider colors
This commit is contained in:
parent
62136dbdd3
commit
4440e0ae10
2 changed files with 184 additions and 0 deletions
|
|
@ -7,6 +7,9 @@ struct GhosttyConfig {
|
|||
var theme: String?
|
||||
var workingDirectory: String?
|
||||
var scrollbackLimit: Int = 10000
|
||||
var unfocusedSplitOpacity: Double = 0.7
|
||||
var unfocusedSplitFill: NSColor?
|
||||
var splitDividerColor: NSColor?
|
||||
|
||||
// Colors (from theme or config)
|
||||
var backgroundColor: NSColor = NSColor(hex: "#272822")!
|
||||
|
|
@ -19,6 +22,24 @@ struct GhosttyConfig {
|
|||
// Palette colors (0-15)
|
||||
var palette: [Int: NSColor] = [:]
|
||||
|
||||
var unfocusedSplitOverlayOpacity: Double {
|
||||
let clamped = min(1.0, max(0.15, unfocusedSplitOpacity))
|
||||
return min(1.0, max(0.0, 1.0 - clamped))
|
||||
}
|
||||
|
||||
var unfocusedSplitOverlayFill: NSColor {
|
||||
unfocusedSplitFill ?? backgroundColor
|
||||
}
|
||||
|
||||
var resolvedSplitDividerColor: NSColor {
|
||||
if let splitDividerColor {
|
||||
return splitDividerColor
|
||||
}
|
||||
|
||||
let isLightBackground = backgroundColor.isLightColor
|
||||
return backgroundColor.darken(by: isLightBackground ? 0.08 : 0.4)
|
||||
}
|
||||
|
||||
static func load() -> GhosttyConfig {
|
||||
var config = GhosttyConfig()
|
||||
|
||||
|
|
@ -96,6 +117,18 @@ struct GhosttyConfig {
|
|||
let color = NSColor(hex: String(paletteParts[1])) {
|
||||
palette[index] = color
|
||||
}
|
||||
case "unfocused-split-opacity":
|
||||
if let opacity = Double(value) {
|
||||
unfocusedSplitOpacity = opacity
|
||||
}
|
||||
case "unfocused-split-fill":
|
||||
if let color = NSColor(hex: value) {
|
||||
unfocusedSplitFill = color
|
||||
}
|
||||
case "split-divider-color":
|
||||
if let color = NSColor(hex: value) {
|
||||
splitDividerColor = color
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
|
@ -140,4 +173,33 @@ extension NSColor {
|
|||
|
||||
self.init(red: r, green: g, blue: b, alpha: 1.0)
|
||||
}
|
||||
|
||||
var isLightColor: Bool {
|
||||
luminance > 0.5
|
||||
}
|
||||
|
||||
var luminance: Double {
|
||||
var r: CGFloat = 0
|
||||
var g: CGFloat = 0
|
||||
var b: CGFloat = 0
|
||||
var a: CGFloat = 0
|
||||
|
||||
guard let rgb = usingColorSpace(.sRGB) else { return 0 }
|
||||
rgb.getRed(&r, green: &g, blue: &b, alpha: &a)
|
||||
return (0.299 * r) + (0.587 * g) + (0.114 * b)
|
||||
}
|
||||
|
||||
func darken(by amount: CGFloat) -> NSColor {
|
||||
var h: CGFloat = 0
|
||||
var s: CGFloat = 0
|
||||
var b: CGFloat = 0
|
||||
var a: CGFloat = 0
|
||||
getHue(&h, saturation: &s, brightness: &b, alpha: &a)
|
||||
return NSColor(
|
||||
hue: h,
|
||||
saturation: s,
|
||||
brightness: min(b * (1 - amount), 1),
|
||||
alpha: a
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
122
Sources/Splits/TerminalSplitTreeView.swift
Normal file
122
Sources/Splits/TerminalSplitTreeView.swift
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
import SwiftUI
|
||||
|
||||
struct TerminalSplitTreeView: View {
|
||||
@ObservedObject var tab: Tab
|
||||
let isTabActive: Bool
|
||||
@State private var config = GhosttyConfig.load()
|
||||
|
||||
var body: some View {
|
||||
let appearance = SplitAppearance(
|
||||
dividerColor: Color(nsColor: config.resolvedSplitDividerColor),
|
||||
unfocusedOverlayColor: Color(nsColor: config.unfocusedSplitOverlayFill),
|
||||
unfocusedOverlayOpacity: config.unfocusedSplitOverlayOpacity
|
||||
)
|
||||
Group {
|
||||
if let node = tab.splitTree.zoomed ?? tab.splitTree.root {
|
||||
TerminalSplitSubtreeView(
|
||||
node: node,
|
||||
isRoot: node == tab.splitTree.root,
|
||||
isSplit: tab.splitTree.isSplit,
|
||||
isTabActive: isTabActive,
|
||||
focusedSurfaceId: tab.focusedSurfaceId,
|
||||
appearance: appearance,
|
||||
onFocus: { tab.focusSurface($0) },
|
||||
onResize: { tab.updateSplitRatio(node: $0, ratio: $1) },
|
||||
onEqualize: { tab.equalizeSplits() }
|
||||
)
|
||||
.id(node.structuralIdentity)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.background(GeometryReader { proxy in
|
||||
Color.clear
|
||||
.onAppear { tab.updateSplitViewSize(proxy.size) }
|
||||
.onChange(of: proxy.size) { tab.updateSplitViewSize($0) }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate struct TerminalSplitSubtreeView: View {
|
||||
let node: SplitTree<TerminalSurface>.Node
|
||||
let isRoot: Bool
|
||||
let isSplit: Bool
|
||||
let isTabActive: Bool
|
||||
let focusedSurfaceId: UUID?
|
||||
let appearance: SplitAppearance
|
||||
let onFocus: (UUID) -> Void
|
||||
let onResize: (SplitTree<TerminalSurface>.Node, Double) -> Void
|
||||
let onEqualize: () -> Void
|
||||
|
||||
var body: some View {
|
||||
switch node {
|
||||
case .leaf(let surface):
|
||||
let isFocused = isTabActive && focusedSurfaceId == surface.id
|
||||
ZStack {
|
||||
GhosttyTerminalView(
|
||||
terminalSurface: surface,
|
||||
isActive: isFocused,
|
||||
onFocus: { _ in onFocus(surface.id) }
|
||||
)
|
||||
.background(Color(nsColor: .windowBackgroundColor))
|
||||
|
||||
if isSplit && !isFocused && appearance.unfocusedOverlayOpacity > 0 {
|
||||
Rectangle()
|
||||
.fill(appearance.unfocusedOverlayColor)
|
||||
.opacity(appearance.unfocusedOverlayOpacity)
|
||||
.allowsHitTesting(false)
|
||||
}
|
||||
}
|
||||
case .split(let split):
|
||||
let splitViewDirection: SplitViewDirection = switch split.direction {
|
||||
case .horizontal: .horizontal
|
||||
case .vertical: .vertical
|
||||
}
|
||||
|
||||
SplitView(
|
||||
splitViewDirection,
|
||||
.init(get: {
|
||||
CGFloat(split.ratio)
|
||||
}, set: {
|
||||
onResize(node, Double($0))
|
||||
}),
|
||||
dividerColor: appearance.dividerColor,
|
||||
resizeIncrements: .init(width: 1, height: 1),
|
||||
left: {
|
||||
TerminalSplitSubtreeView(
|
||||
node: split.left,
|
||||
isRoot: false,
|
||||
isSplit: isSplit,
|
||||
isTabActive: isTabActive,
|
||||
focusedSurfaceId: focusedSurfaceId,
|
||||
appearance: appearance,
|
||||
onFocus: onFocus,
|
||||
onResize: onResize,
|
||||
onEqualize: onEqualize
|
||||
)
|
||||
},
|
||||
right: {
|
||||
TerminalSplitSubtreeView(
|
||||
node: split.right,
|
||||
isRoot: false,
|
||||
isSplit: isSplit,
|
||||
isTabActive: isTabActive,
|
||||
focusedSurfaceId: focusedSurfaceId,
|
||||
appearance: appearance,
|
||||
onFocus: onFocus,
|
||||
onResize: onResize,
|
||||
onEqualize: onEqualize
|
||||
)
|
||||
},
|
||||
onEqualize: {
|
||||
onEqualize()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct SplitAppearance {
|
||||
let dividerColor: Color
|
||||
let unfocusedOverlayColor: Color
|
||||
let unfocusedOverlayOpacity: Double
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue