macos: store default size as computed property

This commit is contained in:
Mitchell Hashimoto
2025-02-28 14:51:41 -08:00
parent 8838ebf02a
commit afb154ee5d
2 changed files with 84 additions and 65 deletions

View File

@ -27,6 +27,9 @@ class TerminalController: BaseTerminalController {
/// The notification cancellable for focused surface property changes. /// The notification cancellable for focused surface property changes.
private var surfaceAppearanceCancellables: Set<AnyCancellable> = [] private var surfaceAppearanceCancellables: Set<AnyCancellable> = []
/// This will be set to the initial frame of the window from the xib on load.
private var initialFrame: NSRect? = nil
init(_ ghostty: Ghostty.App, init(_ ghostty: Ghostty.App,
withBaseConfig base: Ghostty.SurfaceConfiguration? = nil, withBaseConfig base: Ghostty.SurfaceConfiguration? = nil,
withSurfaceTree tree: Ghostty.SplitNode? = nil withSurfaceTree tree: Ghostty.SplitNode? = nil
@ -308,6 +311,55 @@ class TerminalController: BaseTerminalController {
y: frame.maxY - (CGFloat(y) + window.frame.height))) y: frame.maxY - (CGFloat(y) + window.frame.height)))
} }
/// Returns the default size of the window. This is contextual based on the focused surface because
/// the focused surface may specify a different default size than others.
private var defaultSize: NSRect? {
guard let screen = window?.screen ?? NSScreen.main else { return nil }
if derivedConfig.maximize {
return screen.visibleFrame
} else if let focusedSurface,
let initialSize = focusedSurface.initialSize {
// Get the current frame of the window
guard var frame = window?.frame else { return nil }
// Calculate the chrome size (window size minus view size)
let chromeWidth = frame.size.width - focusedSurface.frame.size.width
let chromeHeight = frame.size.height - focusedSurface.frame.size.height
// Calculate the new width and height, clamping to the screen's size
let newWidth = min(initialSize.width + chromeWidth, screen.visibleFrame.width)
let newHeight = min(initialSize.height + chromeHeight, screen.visibleFrame.height)
// Update the frame size while keeping the window's position intact
frame.size.width = newWidth
frame.size.height = newHeight
// Ensure the window doesn't go outside the screen boundaries
frame.origin.x = max(screen.frame.origin.x, min(frame.origin.x, screen.frame.maxX - newWidth))
frame.origin.y = max(screen.frame.origin.y, min(frame.origin.y, screen.frame.maxY - newHeight))
return frame
}
guard let initialFrame else { return nil }
guard var frame = window?.frame else { return nil }
// Calculate the new width and height, clamping to the screen's size
let newWidth = min(initialFrame.size.width, screen.visibleFrame.width)
let newHeight = min(initialFrame.size.height, screen.visibleFrame.height)
// Update the frame size while keeping the window's position intact
frame.size.width = newWidth
frame.size.height = newHeight
// Ensure the window doesn't go outside the screen boundaries
frame.origin.x = max(screen.frame.origin.x, min(frame.origin.x, screen.frame.maxX - newWidth))
frame.origin.y = max(screen.frame.origin.y, min(frame.origin.y, screen.frame.maxY - newHeight))
return frame
}
//MARK: - NSWindowController //MARK: - NSWindowController
override func windowWillLoad() { override func windowWillLoad() {
@ -356,6 +408,9 @@ class TerminalController: BaseTerminalController {
super.windowDidLoad() super.windowDidLoad()
guard let window = window as? TerminalWindow else { return } guard let window = window as? TerminalWindow else { return }
// Store our initial frame so we can know our default later.
initialFrame = window.frame
// I copy this because we may change the source in the future but also because // I copy this because we may change the source in the future but also because
// I regularly audit our codebase for "ghostty.config" access because generally // I regularly audit our codebase for "ghostty.config" access because generally
// you shouldn't use it. Its safe in this case because for a new window we should // you shouldn't use it. Its safe in this case because for a new window we should
@ -372,36 +427,15 @@ class TerminalController: BaseTerminalController {
// If window decorations are disabled, remove our title // If window decorations are disabled, remove our title
if (!config.windowDecorations) { window.styleMask.remove(.titled) } if (!config.windowDecorations) { window.styleMask.remove(.titled) }
// If we have only a single surface (no splits) and that surface requested // If we have only a single surface (no splits) and there is a default size then
// an initial size then we set it here now. // we should resize to that default size.
if case let .leaf(leaf) = surfaceTree { if case let .leaf(leaf) = surfaceTree {
if config.maximize { // If this is our first surface then our focused surface will be nil
if let screen = window.screen ?? NSScreen.main { // so we force the focused surface to the leaf.
window.setFrame(screen.visibleFrame, display: true) focusedSurface = leaf.surface
}
} else if let initialSize = leaf.surface.initialSize,
let screen = window.screen ?? NSScreen.main {
// Get the current frame of the window
var frame = window.frame
// Calculate the chrome size (window size minus view size) if let defaultSize {
let chromeWidth = frame.size.width - leaf.surface.frame.size.width window.setFrame(defaultSize, display: true)
let chromeHeight = frame.size.height - leaf.surface.frame.size.height
// Calculate the new width and height, clamping to the screen's size
let newWidth = min(initialSize.width + chromeWidth, screen.visibleFrame.width)
let newHeight = min(initialSize.height + chromeHeight, screen.visibleFrame.height)
// Update the frame size while keeping the window's position intact
frame.size.width = newWidth
frame.size.height = newHeight
// Ensure the window doesn't go outside the screen boundaries
frame.origin.x = max(screen.frame.origin.x, min(frame.origin.x, screen.frame.maxX - newWidth))
frame.origin.y = max(screen.frame.origin.y, min(frame.origin.y, screen.frame.maxY - newHeight))
// Set the updated frame to the window
window.setFrame(frame, display: true)
} }
} }
@ -579,20 +613,8 @@ class TerminalController: BaseTerminalController {
} }
@IBAction func returnToDefaultSize(_ sender: Any) { @IBAction func returnToDefaultSize(_ sender: Any) {
guard guard let defaultSize else { return }
let window = NSApp.keyWindow, window?.setFrame(defaultSize, display: true)
let initialWidth = ghostty.config.windowWidth,
let initialHeight = ghostty.config.windowHeight
else { return }
let currentOrigin = window.frame.origin
let newFrame = NSRect(
origin: currentOrigin,
size: .init(
width: CGFloat(initialWidth),
height: CGFloat(initialHeight)
)
)
window.setFrame(newFrame, display: true)
} }
@IBAction override func closeWindow(_ sender: Any?) { @IBAction override func closeWindow(_ sender: Any?) {
@ -828,15 +850,18 @@ class TerminalController: BaseTerminalController {
struct DerivedConfig { struct DerivedConfig {
let backgroundColor: Color let backgroundColor: Color
let macosTitlebarStyle: String let macosTitlebarStyle: String
let maximize: Bool
init() { init() {
self.backgroundColor = Color(NSColor.windowBackgroundColor) self.backgroundColor = Color(NSColor.windowBackgroundColor)
self.macosTitlebarStyle = "system" self.macosTitlebarStyle = "system"
self.maximize = false
} }
init(_ config: Ghostty.Config) { init(_ config: Ghostty.Config) {
self.backgroundColor = config.backgroundColor self.backgroundColor = config.backgroundColor
self.macosTitlebarStyle = config.macosTitlebarStyle self.macosTitlebarStyle = config.macosTitlebarStyle
self.maximize = config.maximize
} }
} }
} }
@ -846,23 +871,31 @@ extension TerminalController: NSMenuItemValidation {
func validateMenuItem(_ item: NSMenuItem) -> Bool { func validateMenuItem(_ item: NSMenuItem) -> Bool {
switch item.action { switch item.action {
case #selector(returnToDefaultSize): case #selector(returnToDefaultSize):
guard let focusedWindow = NSApp.keyWindow else { guard let window else { return false }
// Native fullscreen windows can't revert to default size.
if window.styleMask.contains(.fullScreen) {
return false return false
} }
if focusedWindow.styleMask.contains(.fullScreen) {
// If we're fullscreen at all then we can't change size
if fullscreenStyle?.isFullscreen ?? false {
return false return false
} }
guard
let windowWidth = ghostty.config.windowWidth, // If our window is already the default size or we don't have a
let windowHeight = ghostty.config.windowHeight, // default size, then disable.
focusedWindow.frame.size != .init( guard let defaultSize,
width: CGFloat(windowWidth), window.frame.size != .init(
height: CGFloat(windowHeight) width: defaultSize.size.width,
) height: defaultSize.size.height
)
else { else {
return false return false
} }
return true return true
default: default:
return true return true
} }

View File

@ -141,20 +141,6 @@ extension Ghostty {
return String(cString: ptr) return String(cString: ptr)
} }
var windowHeight: Int16? {
guard let config = self.config else { return nil }
var v: Int16 = 0
let key = "window-height"
return ghostty_config_get(config, &v, key, UInt(key.count)) ? v : nil
}
var windowWidth: Int16? {
guard let config = self.config else { return nil }
var v: Int16 = 0
let key = "window-width"
return ghostty_config_get(config, &v, key, UInt(key.count)) ? v : nil
}
var windowPositionX: Int16? { var windowPositionX: Int16? {
guard let config = self.config else { return nil } guard let config = self.config else { return nil }
var v: Int16 = 0 var v: Int16 = 0